| /* Copyright (c) 2001-2010, The HSQL Development Group |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * Neither the name of the HSQL Development Group nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, |
| * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| |
| package org.hsqldb.server; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.hsqldb.HsqlException; |
| import org.hsqldb.types.BinaryData; |
| |
| /** |
| * An atomic transfer packet received from a HyperSQL client. |
| * |
| * Since we read and cache all data for the packet upon instantiation, the |
| * available method is reliable and may be relied upon. |
| * |
| * @see #available() |
| */ |
| class OdbcPacketInputStream extends DataInputStream { |
| char packetType; |
| private InputStream bufferStream; |
| |
| /** |
| * Instantiate a packet of the specified type and size. |
| */ |
| static OdbcPacketInputStream newOdbcPacketInputStream( |
| char cType, InputStream streamSource, int sizeInt) throws IOException { |
| return newOdbcPacketInputStream( |
| cType, streamSource, new Integer(sizeInt)); |
| } |
| |
| /** |
| * Instantiate a packet of the specified type, with size determined by |
| * the first int read from the given stream. |
| */ |
| static OdbcPacketInputStream newOdbcPacketInputStream( |
| char cType, InputStream streamSource) throws IOException { |
| return newOdbcPacketInputStream(cType, streamSource, null); |
| } |
| |
| static private OdbcPacketInputStream newOdbcPacketInputStream( |
| char cType, InputStream streamSource, Integer packetSizeObj) |
| throws IOException { |
| int bytesRead, i; |
| int packetSize = 0; |
| |
| if (packetSizeObj == null) { |
| byte[] fourBytes = new byte[4]; |
| bytesRead = 0; |
| while ((i = streamSource.read(fourBytes, bytesRead, |
| fourBytes.length - bytesRead)) > 0) { |
| bytesRead += i; |
| } |
| if (bytesRead != fourBytes.length) { |
| throw new EOFException("Failed to read size header int"); |
| } |
| packetSize = |
| ((fourBytes[0] & 0xff) << 24) + ((fourBytes[1] & 0xff) <<16) |
| + ((fourBytes[2] & 0xff) << 8) + (fourBytes[3] & 0xff) - 4; |
| // Minus 4 because this counts the size int itself. |
| } else { |
| packetSize = packetSizeObj.intValue(); |
| } |
| byte[] xferBuffer = new byte[packetSize]; |
| bytesRead = 0; |
| while ((i = streamSource.read(xferBuffer, bytesRead, |
| xferBuffer.length - bytesRead)) > 0) { |
| bytesRead += i; |
| } |
| if (bytesRead != xferBuffer.length) { |
| throw new EOFException ( |
| "Failed to read packet contents from given stream"); |
| } |
| return new OdbcPacketInputStream( |
| cType, new ByteArrayInputStream(xferBuffer)); |
| } |
| |
| private OdbcPacketInputStream(char packetType, InputStream bufferStream) { |
| super(bufferStream); |
| this.packetType = packetType; |
| } |
| |
| /** |
| * Generate a String/String Map from null-terminated String pairs, until |
| * a '\0' character is read in place of the first key character. |
| * |
| * @return the generated Map |
| * @throws EOFException if the rest of packet does not contained the |
| * required, well-formed null-terminated string pairs. |
| */ |
| Map readStringPairs() throws IOException { |
| String key; |
| Map map = new HashMap(); |
| while (true) { |
| key = readString(); |
| if (key.length() < 1) { |
| break; |
| } |
| map.put(key, readString()); |
| } |
| return map; |
| } |
| |
| /** |
| * Reads a NULL-TERMINATED String. |
| * |
| * @throws IOException if attempt to read past end of packet. |
| */ |
| String readString() throws IOException { |
| /* Would be MUCH easier to do this with Java6's String |
| * encoding/decoding operations */ |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| baos.write((byte) 'X'); |
| baos.write((byte) 'X'); |
| // Place-holders to be replaced with short length |
| |
| int i; |
| while ((i = readByte()) > 0) { |
| baos.write((byte) i); |
| } |
| byte[] ba = baos.toByteArray(); |
| baos.close(); |
| |
| int len = ba.length - 2; |
| ba[0] = (byte) (len >>> 8); |
| ba[1] = (byte) len; |
| |
| DataInputStream dis = new DataInputStream(new ByteArrayInputStream(ba)); |
| String s = dis.readUTF(); |
| //String s = DataInputStream.readUTF(dis); |
| // TODO: Test the previous two to see if one works better for |
| // high-order characters. |
| dis.close(); |
| return s; |
| } |
| |
| BinaryData readSizedBinaryData() throws IOException { |
| int len = readInt(); |
| try { |
| return (len < 0) ? null : new BinaryData((long) len, this); |
| } catch (HsqlException he) { |
| throw new IOException(he.getMessage()); |
| } |
| } |
| |
| String readSizedString() throws IOException { |
| int len = readInt(); |
| return (len < 0) ? null : readString(len); |
| } |
| |
| /** |
| * These Strings are not null-terminated. |
| * |
| * @param len Bytes to read (not necessarily characters to be returned! |
| * @throws IOException if attempt to read past end of packet. |
| */ |
| String readString(int len) throws IOException { |
| /* Would be MUCH easier to do this with Java6's String |
| * encoding/decoding operations */ |
| int bytesRead = 0; |
| int i; |
| byte[] ba = new byte[len + 2]; |
| ba[0] = (byte) (len >>> 8); |
| ba[1] = (byte) len; |
| while ((i = read(ba, 2 + bytesRead, len - bytesRead)) > -1 |
| && bytesRead < len) { |
| bytesRead += i; |
| } |
| if (bytesRead != len) { |
| throw new EOFException("Packet ran dry"); |
| } |
| for (i = 2; i < ba.length - 1; i++) { |
| if (ba[i] == 0) { |
| throw new RuntimeException( |
| "Null internal to String at offset " + (i - 2)); |
| } |
| } |
| |
| DataInputStream dis = new DataInputStream(new ByteArrayInputStream(ba)); |
| String s = dis.readUTF(); |
| //String s = DataInputStream.readUTF(dis); |
| // TODO: Test the previous two to see if one works better for |
| // high-order characters. |
| dis.close(); |
| return s; |
| } |
| |
| public char readByteChar() throws IOException { |
| return (char) readByte(); |
| } |
| } |