--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.drda.DDMWriter\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to You under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.drda;\r
+\r
+import java.io.OutputStream;\r
+import java.io.InputStream;\r
+import java.io.BufferedOutputStream;\r
+import java.io.UnsupportedEncodingException;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import java.sql.SQLException;\r
+import java.math.BigDecimal;\r
+import java.util.Arrays;\r
+import org.apache.derby.iapi.reference.Property;\r
+import org.apache.derby.iapi.services.property.PropertyUtil;\r
+\r
+import java.io.IOException;\r
+\r
+/**\r
+ The DDMWriter is used to write DRDA protocol. The DRDA Protocol is\r
+ described in the DDMReader class.\r
+ For more details, see DRDA Volume 3 (Distributed Data Management(DDM)\r
+ Architecture (DDS definition)\r
+*/\r
+class DDMWriter\r
+{\r
+\r
+ // number of nesting levels for collections. We need to mark the length\r
+ // location of the collection so that we can update it as we add more stuff\r
+ // to the collection\r
+ private final static int MAX_MARKS_NESTING = 10;\r
+\r
+ // Default buffer size\r
+ private final static int DEFAULT_BUFFER_SIZE = 32767;\r
+\r
+\r
+ static final BigDecimal ZERO = BigDecimal.valueOf(0L);\r
+ \r
+ private static final byte MULTI_BYTE_MASK = (byte) 0xC0;\r
+ private static final byte CONTINUATION_BYTE = (byte) 0x80;\r
+\r
+ // output buffer\r
+ private byte[] bytes;\r
+\r
+ // offset into output buffer\r
+ private int offset;\r
+\r
+ // A saved mark in the stream is saved temporarily to revisit the location.\r
+ private int[] markStack = new int[MAX_MARKS_NESTING];\r
+\r
+ // top of the stack\r
+ private int top;\r
+\r
+ // CCSID manager for translation of strings in the protocol to EBCDIC\r
+ private CcsidManager ccsidManager;\r
+\r
+ // DRDA connection thread for this writer\r
+ private DRDAConnThread agent;\r
+\r
+ // This Object tracks the location of the current\r
+ // Dss header length bytes. This is done so\r
+ // the length bytes can be automatically\r
+ // updated as information is added to this stream.\r
+ private int dssLengthLocation;\r
+\r
+ // Current correlation ID\r
+ private int correlationID;\r
+\r
+ // Next correlation ID\r
+ private int nextCorrelationID;\r
+\r
+ // is this DRDA protocol or CMD protocol\r
+ private boolean isDRDAProtocol;\r
+ // trace object of the associated session\r
+ private DssTrace dssTrace;\r
+\r
+ // Location within the "bytes" array of the start of the header\r
+ // of the DSS most recently written to the buffer.\r
+ private int prevHdrLocation;\r
+\r
+ // Correlation id of the last DSS that was written to buffer.\r
+ private int previousCorrId;\r
+\r
+ // Chaining bit of the last DSS that was written to buffer.\r
+ private byte previousChainByte;\r
+\r
+ // Whether or not the current DSS is a continuation DSS.\r
+ private boolean isContinuationDss;\r
+\r
+ // In situations where we want to "mark" a buffer location so that\r
+ // we can "back-out" of a write to handle errors, this holds the\r
+ // location within the "bytes" array of the start of the header\r
+ // that immediately precedes the mark.\r
+ private int lastDSSBeforeMark;\r
+\r
+ // Constructors\r
+ DDMWriter (int minSize, CcsidManager ccsidManager, DRDAConnThread agent, DssTrace dssTrace)\r
+ {\r
+ this.bytes = new byte[minSize];\r
+ this.ccsidManager = ccsidManager;\r
+ this.agent = agent;\r
+ this.prevHdrLocation = -1;\r
+ this.previousCorrId = DssConstants.CORRELATION_ID_UNKNOWN;\r
+ this.previousChainByte = DssConstants.DSS_NOCHAIN;\r
+ this.isContinuationDss = false;\r
+ this.lastDSSBeforeMark = -1;\r
+ reset(dssTrace);\r
+ }\r
+\r
+ DDMWriter (CcsidManager ccsidManager, DRDAConnThread agent, DssTrace dssTrace)\r
+ {\r
+ this.bytes = new byte[DEFAULT_BUFFER_SIZE];\r
+ this.ccsidManager = ccsidManager;\r
+ this.agent = agent;\r
+ this.prevHdrLocation = -1;\r
+ this.previousCorrId = DssConstants.CORRELATION_ID_UNKNOWN;\r
+ this.previousChainByte = DssConstants.DSS_NOCHAIN;\r
+ this.isContinuationDss = false;\r
+ this.lastDSSBeforeMark = -1;\r
+ reset(dssTrace);\r
+ }\r
+\r
+ /**\r
+ * reset values for sending next message\r
+ *\r
+ */\r
+ protected void reset(DssTrace dssTrace)\r
+ {\r
+ offset = 0;\r
+ top = 0;\r
+ dssLengthLocation = 0;\r
+ nextCorrelationID = 1;\r
+ correlationID = DssConstants.CORRELATION_ID_UNKNOWN;\r
+ isDRDAProtocol = true;\r
+ this.dssTrace = dssTrace;\r
+ }\r
+\r
+ /**\r
+ * set protocol to CMD protocol\r
+ */\r
+ protected void setCMDProtocol()\r
+ {\r
+ isDRDAProtocol = false;\r
+ }\r
+\r
+ /**\r
+ * Create DSS reply object\r
+ */\r
+ protected void createDssReply()\r
+ {\r
+ beginDss(DssConstants.DSSFMT_RPYDSS, true);\r
+ }\r
+\r
+ /**\r
+ * Create DSS request object\r
+ * NOTE: This is _ONLY_ used for testing the protocol\r
+ * (via the TestProto.java file in this package)!\r
+ * We should never create a DSS request in normal\r
+ * DRDA processing (we should only create DSS replies\r
+ * and DSS objects).\r
+ */\r
+ protected void createDssRequest()\r
+ {\r
+ beginDss(DssConstants.DSSFMT_RQSDSS, true);\r
+ }\r
+\r
+ /**\r
+ * Create DSS data object\r
+ */\r
+ protected void createDssObject()\r
+ {\r
+ beginDss(DssConstants.DSSFMT_OBJDSS, true);\r
+ }\r
+\r
+ /**\r
+ * Mark the DSS that we're currently writing as\r
+ * a continued DSS, which is done by setting\r
+ * the high-order bit to "1", per DDM spec.\r
+ * This means:\r
+ *\r
+ * 1. One or more continuation DSSes will immediately\r
+ * follow the current (continued) DSS.\r
+ * 2. All continuation DSSes will have a 2-byte\r
+ * continuation header, followed by data; in\r
+ * other words, chaining state, correlation\r
+ * id, dss format info, and code point will\r
+ * NOT be included. All of that info is \r
+ * present ONLY in the FIRST DSS in the\r
+ * list of continued DSSes.\r
+ *\r
+ * NOTE: A DSS can be a "continuation" DSS _and_\r
+ * a "continued" DSS at the same time. However,\r
+ * the FIRST DSS to be continued canNOT be\r
+ * a continuation DSS.\r
+ */\r
+ private void markDssAsContinued(boolean forLob)\r
+ {\r
+\r
+ if (!forLob) {\r
+ // continuation bit defaults to '1' for lobs, so\r
+ // we only have to switch it if we're not writing\r
+ // lobs.\r
+ bytes[dssLengthLocation] |= 0x80;\r
+ }\r
+\r
+ // We need to set the chaining state, but ONLY\r
+ // IF this is the FIRST DSS in the continuation\r
+ // list (only the first one has chaining state\r
+ // in it's header; the others do not).\r
+ if (!isContinuationDss)\r
+ endDss(!forLob);\r
+\r
+ }\r
+\r
+ /**\r
+ * End DSS header by writing the length in the length location\r
+ * and setting the chain bit. Unlike the other two endDss\r
+ * methods, this one overrides the default chaining byte\r
+ * (which is set in beginDss) with the chaining byte that\r
+ * is passed in. NOTE: This method is only used in\r
+ * association with createDssRequest, and thus is for\r
+ * TESTING purposes only (via TestProto.java). No calls\r
+ * should be made to this method in normal DRDA processing\r
+ * (because for normal processing, chaining must be\r
+ * determined automatically based on DSS requests).\r
+ */\r
+ protected void endDss(byte chainByte)\r
+ {\r
+\r
+ // Do regular endDss processing.\r
+ endDss(true);\r
+\r
+ // Now override default chain state.\r
+ bytes[dssLengthLocation + 3] &= 0x0F; // Zero out default\r
+ bytes[dssLengthLocation + 3] |= chainByte;\r
+ previousChainByte = chainByte;\r
+\r
+ }\r
+\r
+ /**\r
+ * End DSS header by writing the length in the length location\r
+ * and setting the chain bit.\r
+ */\r
+ protected void endDss() {\r
+ endDss(true);\r
+ }\r
+\r
+ /**\r
+ * End DSS header by writing the length in the length location\r
+ * and setting the chain bit.\r
+ */\r
+ private void endDss (boolean finalizeLength)\r
+ {\r
+\r
+ if (finalizeLength)\r
+ finalizeDssLength();\r
+\r
+ if (isContinuationDss) {\r
+ // no chaining information for this DSS; so we're done.\r
+ isContinuationDss = false;\r
+ return;\r
+ }\r
+\r
+ previousCorrId = correlationID;\r
+ prevHdrLocation = dssLengthLocation;\r
+ previousChainByte = DssConstants.DSSCHAIN_SAME_ID;\r
+\r
+ }\r
+\r
+ /**\r
+ * End final DDM and DSS header by writing the length in the length location\r
+ *\r
+ */\r
+ protected void endDdmAndDss ()\r
+ {\r
+ endDdm(); // updates last DDM object\r
+ endDss();\r
+ }\r
+ /**\r
+ * Copy Data to End\r
+ * Create a buffer and copy from the position given to the end of data\r
+ *\r
+ * Note that the position given is treated as relative to the\r
+ * current DSS, for there may be other DSS blocks (chained, presumably)\r
+ * which are sitting unwritten in the buffer. The caller doesn't\r
+ * know this, though, and works only with the current DSS.\r
+ *\r
+ * getDSSLength, copyDSSDataToEnd, and truncateDSS work together to\r
+ * provide a sub-protocol for DRDAConnThread to use in its\r
+ * implementation of the LMTBLKPRC protocol. They enable the caller\r
+ * to determine when it has written too much data into the current\r
+ * DSS, to reclaim the extra data that won't fit, and to truncate\r
+ * that extra data once it has been reclaimed and stored elsewhere.\r
+ * Note that this support only works for the current DSS. Earlier,\r
+ * chained DSS blocks cannot be accessed using these methods. For\r
+ * additional background information, the interested reader should\r
+ * investigate bugs DERBY-491 and 492 at:\r
+ * http://issues.apache.org/jira/browse/DERBY-491 and\r
+ * http://issues.apache.org/jira/browse/DERBY-492\r
+ *\r
+ * @param start\r
+ */\r
+ protected byte [] copyDSSDataToEnd(int start)\r
+ {\r
+ start = start + dssLengthLocation;\r
+ int length = offset - start;\r
+ byte [] temp = new byte[length];\r
+ System.arraycopy(bytes,start,temp,0,length);\r
+ return temp;\r
+ }\r
+\r
+ // Collection methods\r
+\r
+ /**\r
+ * Mark the location of the length bytes for the collection so they\r
+ * can be updated later\r
+ *\r
+ */\r
+ protected void startDdm (int codePoint)\r
+ {\r
+ // save the location of the beginning of the collection so\r
+ // that we can come back and fill in the length bytes\r
+ markStack[top++] = offset;\r
+ ensureLength (4); // verify space for length bytes and code point\r
+ offset += 2; // move past the length bytes before writing the code point\r
+ bytes[offset] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) (codePoint & 0xff);\r
+ offset += 2;\r
+ }\r
+\r
+ /**\r
+ * Erase all writes for the current ddm and reset the\r
+ * top\r
+ */\r
+ protected void clearDdm ()\r
+ {\r
+ offset = markStack[top--];\r
+ }\r
+\r
+ /**\r
+ * Clear the entire send buffer\r
+ *\r
+ */\r
+ protected void clearBuffer()\r
+ {\r
+ offset = 0;\r
+ top = 0;\r
+ dssLengthLocation = 0;\r
+ correlationID = DssConstants.CORRELATION_ID_UNKNOWN;\r
+ nextCorrelationID = 1;\r
+ isDRDAProtocol = true;\r
+ }\r
+\r
+ /**\r
+ * End the current DDM\r
+ *\r
+ */\r
+ protected void endDdm ()\r
+ {\r
+ // remove the top length location offset from the mark stack\r
+ // calculate the length based on the marked location and end of data.\r
+ int lengthLocation = markStack[--top];\r
+ int length = offset - lengthLocation;\r
+\r
+ // determine if any extended length bytes are needed. the value returned\r
+ // from calculateExtendedLengthByteCount is the number of extended length\r
+ // bytes required. 0 indicates no exteneded length.\r
+ int extendedLengthByteCount = calculateExtendedLengthByteCount (length);\r
+ if (extendedLengthByteCount != 0)\r
+ {\r
+ // ensure there is enough room in the buffer for the extended length bytes.\r
+ ensureLength (extendedLengthByteCount);\r
+\r
+ // calculate the length to be placed in the extended length bytes.\r
+ // this length does not include the 4 byte llcp.\r
+ int extendedLength = length - 4;\r
+\r
+ // shift the data to the right by the number of extended\r
+ // length bytes needed.\r
+ int extendedLengthLocation = lengthLocation + 4;\r
+ System.arraycopy (bytes,\r
+ extendedLengthLocation,\r
+ bytes,\r
+ extendedLengthLocation + extendedLengthByteCount,\r
+ extendedLength);\r
+\r
+ // write the extended length\r
+ int shiftSize = (extendedLengthByteCount -1) * 8;\r
+ for (int i = 0; i < extendedLengthByteCount; i++)\r
+ {\r
+ bytes[extendedLengthLocation++] =\r
+ (byte) ((extendedLength >>> shiftSize ) & 0xff);\r
+ shiftSize -= 8;\r
+ }\r
+\r
+ // adjust the offset to account for the shift and insert\r
+ offset += extendedLengthByteCount;\r
+\r
+ // the two byte length field before the codepoint contains the length\r
+ // of itself, the length of the codepoint, and the number of bytes used\r
+ // to hold the extended length. the 2 byte length field also has the first\r
+ // bit on to indicate extended length bytes were used.\r
+ length = extendedLengthByteCount + 4;\r
+ length |= DssConstants.CONTINUATION_BIT;\r
+ }\r
+\r
+ // write the 2 byte length field (2 bytes before codepoint).\r
+ bytes[lengthLocation] = (byte) ((length >>> 8) & 0xff);\r
+ bytes[lengthLocation+1] = (byte) (length & 0xff);\r
+\r
+ }\r
+\r
+ /**\r
+ * Get the length of the current DSS block we're working on. This is\r
+ * used by the LMTBLKPRC protocol, which does its own conversational\r
+ * blocking protocol above the layer of the DRDA blocking. The LMTBLKPRC\r
+ * implementation (in DRDAConnThread) needs to be able to truncate a\r
+ * DSS block when splitting a QRYDTA response.\r
+ *\r
+ * @return current DSS block length\r
+ */\r
+ protected int getDSSLength()\r
+ {\r
+ return offset - dssLengthLocation;\r
+ }\r
+ \r
+ /**\r
+ * Truncate the current DSS. Before making this call, you should ensure\r
+ * that you have copied the data to be truncated somewhere else, by\r
+ * calling copyDSSDataToEnd\r
+ *\r
+ * @param value DSS length\r
+ */\r
+ protected void truncateDSS(int value)\r
+ {\r
+ offset = dssLengthLocation + value;\r
+ }\r
+\r
+\r
+ // Write routines\r
+\r
+ /**\r
+ * Write byte\r
+ *\r
+ * @param value byte to be written\r
+ */\r
+ protected void writeByte (int value)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (value > 255)\r
+ SanityManager.THROWASSERT(\r
+ "writeByte value: " + value +\r
+ " may not be > 255");\r
+ }\r
+\r
+ ensureLength (1);\r
+ bytes[offset++] = (byte) (value & 0xff);\r
+ }\r
+\r
+\r
+ /**\r
+ * Write network short\r
+ *\r
+ * @param value value to be written\r
+ */\r
+ protected void writeNetworkShort (int value)\r
+ {\r
+ ensureLength (2);\r
+ bytes[offset] = (byte) ((value >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) (value & 0xff);\r
+ offset += 2;\r
+ }\r
+\r
+ /**\r
+ * Write network int\r
+ *\r
+ * @param value value to be written\r
+ */\r
+ protected void writeNetworkInt (int value)\r
+ {\r
+ ensureLength (4);\r
+ bytes[offset] = (byte) ((value >>> 24) & 0xff);\r
+ bytes[offset + 1] = (byte) ((value >>> 16) & 0xff);\r
+ bytes[offset + 2] = (byte) ((value >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (value & 0xff);\r
+ offset += 4;\r
+ }\r
+\r
+\r
+ /**\r
+ * Write byte array\r
+ *\r
+ * @param buf byte array to be written\r
+ * @param length - length to write\r
+ */\r
+ protected void writeBytes (byte[] buf, int length)\r
+ {\r
+ writeBytes(buf, 0,length);\r
+ }\r
+\r
+\r
+\r
+ /**\r
+ * Write byte array\r
+ *\r
+ * @param buf byte array to be written\r
+ * @param start - starting position\r
+ * @param length - length to write\r
+ */\r
+ protected void writeBytes (byte[] buf, int start, int length)\r
+ {\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (buf == null && length > 0)\r
+ SanityManager.THROWASSERT("Buf is null");\r
+ if (length + start - 1 > buf.length)\r
+ SanityManager.THROWASSERT("Not enough bytes in buffer");\r
+\r
+ }\r
+ ensureLength (length);\r
+ System.arraycopy(buf,start,bytes,offset,length);\r
+ offset += length;\r
+ }\r
+ /**\r
+ * Write byte array\r
+ *\r
+ * @param buf byte array to be written\r
+ **/\r
+ protected void writeBytes (byte[] buf)\r
+ {\r
+ writeBytes(buf,buf.length);\r
+ }\r
+\r
+\r
+\r
+ protected void writeLDBytes(byte[] buf)\r
+ {\r
+ writeLDBytes(buf, 0);\r
+ }\r
+\r
+ protected void writeLDBytes(byte[] buf, int index)\r
+ {\r
+\r
+ int length = buf.length;\r
+ int writeLen = buf.length;\r
+\r
+ writeShort(writeLen);\r
+\r
+ writeBytes(buf,0,writeLen);\r
+ }\r
+\r
+\r
+ /**\r
+ * Write code point and 4 bytes\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param value - value to write after code point\r
+ */\r
+ void writeCodePoint4Bytes (int codePoint, int value)\r
+ {\r
+ ensureLength (4);\r
+ bytes[offset] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) (codePoint & 0xff);\r
+ bytes[offset + 2] = (byte) ((value >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (value & 0xff);\r
+ offset += 4;\r
+ }\r
+\r
+ /**\r
+ * Write scalar 1 byte object includes length, codepoint and value\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param value - value to write after code point\r
+ */\r
+ void writeScalar1Byte (int codePoint, int value)\r
+ {\r
+ ensureLength (5);\r
+ bytes[offset] = 0x00;\r
+ bytes[offset + 1] = 0x05;\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ bytes[offset + 4] = (byte) (value & 0xff);\r
+ offset += 5;\r
+ }\r
+\r
+ /**\r
+ * Write scalar 2 byte object includes length, codepoint and value\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param value - value to write after code point\r
+ */\r
+ protected void writeScalar2Bytes (int codePoint, int value)\r
+ {\r
+ ensureLength (6);\r
+ bytes[offset] = 0x00;\r
+ bytes[offset + 1] = 0x06;\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ bytes[offset + 4] = (byte) ((value >>> 8) & 0xff);\r
+ bytes[offset + 5] = (byte) (value & 0xff);\r
+ offset += 6;\r
+ }\r
+\r
+ protected void writeScalar2Bytes ( int value)\r
+ {\r
+ ensureLength (2);\r
+ bytes[offset] = (byte) ((value >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) (value & 0xff);\r
+ offset += 2;\r
+ }\r
+\r
+ /**\r
+ * Write length and codepoint\r
+ *\r
+ * @param length - length of object\r
+ * @param codePoint - code point to write\r
+ */\r
+ protected void startDdm (int length, int codePoint)\r
+ {\r
+ ensureLength (4);\r
+ bytes[offset] = (byte) ((length >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) (length & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset += 4;\r
+ }\r
+\r
+ /**\r
+ * Write scalar byte array object includes length, codepoint and value\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param buf - value to write after code point\r
+ * @param length - number of bytes to write\r
+ */\r
+ protected void writeScalarBytes (int codePoint, byte[] buf, int length)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (buf == null && length > 0)\r
+ SanityManager.THROWASSERT("Buf is null");\r
+ if (length > buf.length)\r
+ SanityManager.THROWASSERT("Not enough bytes in buffer");\r
+ }\r
+ ensureLength (length + 4);\r
+ bytes[offset] = (byte) (((length+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((length+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ System.arraycopy(buf,0,bytes,offset + 4, length);\r
+ offset += length + 4;\r
+ }\r
+\r
+\r
+ \r
+ protected void writeScalarStream (boolean chainedWithSameCorrelator,\r
+ int codePoint,\r
+ EXTDTAInputStream in,\r
+ boolean writeNullByte) \r
+ throws DRDAProtocolException\r
+ {\r
+\r
+ \r
+\r
+ // Stream equivalent of "beginDss"...\r
+ int spareDssLength = prepScalarStream( chainedWithSameCorrelator,\r
+ codePoint,\r
+ writeNullByte);\r
+ \r
+ // write the data\r
+ int bytesRead = 0;\r
+ int totalBytesRead = 0;\r
+\r
+ try {\r
+ \r
+ OutputStream out = \r
+ placeLayerBStreamingBuffer( agent.getOutputStream() );\r
+ \r
+ boolean isLastSegment = false;\r
+ \r
+ while( !isLastSegment ){\r
+ \r
+ int spareBufferLength = bytes.length - offset;\r
+ \r
+ if( SanityManager.DEBUG ){\r
+ \r
+ if( PropertyUtil.getSystemBoolean("derby.debug.suicideOfLayerBStreaming") )\r
+ throw new IOException();\r
+ }\r
+ \r
+ bytesRead = in.read(bytes,\r
+ offset,\r
+ Math.min(spareDssLength,\r
+ spareBufferLength));\r
+ \r
+ totalBytesRead += bytesRead;\r
+ offset += bytesRead;\r
+ spareDssLength -= bytesRead;\r
+ spareBufferLength -= bytesRead;\r
+\r
+ isLastSegment = peekStream(in) < 0;\r
+ \r
+ if(isLastSegment || \r
+ spareDssLength == 0){\r
+ \r
+ flushScalarStreamSegment (isLastSegment, \r
+ out);\r
+ \r
+ if( ! isLastSegment )\r
+ spareDssLength = DssConstants.MAX_DSS_LENGTH - 2;\r
+\r
+ }\r
+ \r
+ }\r
+ \r
+ out.flush();\r
+ \r
+ }catch(IOException e){\r
+ agent.markCommunicationsFailure ("DDMWriter.writeScalarStream()",\r
+ "",\r
+ e.getMessage(),\r
+ "*");\r
+ }\r
+ \r
+ }\r
+ \r
+ /**\r
+ * Begins a DSS stream (for writing LOB data).\r
+ */\r
+ private void beginDss (boolean chainedToNextStructure,\r
+ int dssType)\r
+ {\r
+ beginDss(dssType, false); // false => don't ensure length.\r
+\r
+ // always turn on continuation flags... this is helpful for lobs...\r
+ // these bytes will get rest if dss lengths are finalized.\r
+ bytes[dssLengthLocation] = (byte) 0xFF;\r
+ bytes[dssLengthLocation + 1] = (byte) 0xFF;\r
+\r
+ // Set whether or not this DSS should be chained to\r
+ // the next one. If it's chained, it has to be chained\r
+ // with same id (that's the nature of EXTDTA chaining).\r
+ if (chainedToNextStructure) {\r
+ dssType |= DssConstants.GDSCHAIN_SAME_ID;\r
+ }\r
+\r
+ bytes[dssLengthLocation + 3] = (byte) (dssType & 0xff);\r
+ }\r
+\r
+\r
+ /**\r
+ * prepScalarStream does the following prep for writing stream data:\r
+ * 1. Flushes an existing DSS segment, if necessary\r
+ * 2. Determines if extended length bytes are needed\r
+ * 3. Creates a new DSS/DDM header and a null byte indicator, if applicable\r
+ *\r
+ * If value of length was less than 0, this method processes streaming as Layer B Streaming.\r
+ * cf. page 315 of specification of DRDA, Version 3, Volume 3 \r
+ *\r
+ */\r
+ private int prepScalarStream( boolean chainedWithSameCorrelator,\r
+ int codePoint,\r
+ boolean writeNullByte) throws DRDAProtocolException\r
+ {\r
+\r
+ ensureLength( DEFAULT_BUFFER_SIZE - offset );\r
+ \r
+ final int nullIndicatorSize = writeNullByte ? 1:0;\r
+\r
+ \r
+ // flush the existing DSS segment ,\r
+ // if this stream will not fit in the send buffer or \r
+ // length of this stream is unknown.\r
+ // Here, 10 stands for sum of headers of layer A and B.\r
+\r
+ try {\r
+ // The existing DSS segment was finalized by endDss; all\r
+ // we have to do is send it across the wire.\r
+ sendBytes(agent.getOutputStream());\r
+ }\r
+ catch (java.io.IOException e) {\r
+ agent.markCommunicationsFailure ("DDMWriter.writeScalarStream()",\r
+ "OutputStream.flush()",\r
+ e.getMessage(),"*");\r
+ }\r
+\r
+ // buildStreamDss should not call ensure length.\r
+ beginDss(chainedWithSameCorrelator, DssConstants.GDSFMT_OBJDSS);\r
+\r
+ writeLengthCodePoint(0x8004,codePoint);\r
+\r
+\r
+ // write the null byte, if necessary\r
+ if (writeNullByte)\r
+ writeByte(0x0);\r
+\r
+ //Here, 6 stands for header of layer A and \r
+ //4 stands for header of layer B.\r
+ return DssConstants.MAX_DSS_LENGTH - 6 - 4 - nullIndicatorSize;\r
+\r
+\r
+ }\r
+\r
+\r
+ // method to determine if any data is in the request.\r
+ // this indicates there is a dss object already in the buffer.\r
+ protected boolean doesRequestContainData()\r
+ {\r
+ return offset != 0;\r
+ }\r
+\r
+\r
+ // Writes out a scalar stream DSS segment, along with DSS continuation\r
+ // headers if necessary.\r
+ private void flushScalarStreamSegment ( boolean lastSegment,\r
+ OutputStream out)\r
+ throws DRDAProtocolException\r
+ {\r
+\r
+ // either at end of data, end of dss segment, or both.\r
+ if (! lastSegment) {\r
+\r
+ // 32k segment filled and not at end of data.\r
+ try {\r
+ // Mark current DSS as continued, set its chaining state,\r
+ // then send the data across.\r
+ markDssAsContinued(true); // true => for lobs\r
+ sendBytes (out,\r
+ false);\r
+ \r
+ }catch (java.io.IOException ioe) {\r
+ agent.markCommunicationsFailure ("DDMWriter.flushScalarStreamSegment()",\r
+ "",\r
+ ioe.getMessage(),\r
+ "*");\r
+ }\r
+\r
+\r
+ // Prepare a DSS continuation header for next DSS.\r
+ dssLengthLocation = offset;\r
+ bytes[offset++] = (byte) (0xff);\r
+ bytes[offset++] = (byte) (0xff);\r
+ isContinuationDss = true;\r
+ }else{\r
+ // we're done writing the data, so end the DSS.\r
+ endDss();\r
+\r
+ }\r
+\r
+ }\r
+\r
+\r
+ private void writeExtendedLengthBytes (int extendedLengthByteCount, long length)\r
+ {\r
+ int shiftSize = (extendedLengthByteCount -1) * 8;\r
+ for (int i = 0; i < extendedLengthByteCount; i++) {\r
+ bytes[offset + i] = (byte) ((length >>> shiftSize) & 0xff);\r
+ shiftSize -= 8;\r
+ }\r
+ offset += extendedLengthByteCount;\r
+ }\r
+\r
+\r
+ // insert a 4 byte length/codepoint pair into the buffer.\r
+ // total of 4 bytes inserted in buffer.\r
+ // Note: the length value inserted in the buffer is the same as the value\r
+ // passed in as an argument (this value is NOT incremented by 4 before being\r
+ // inserted).\r
+ void writeLengthCodePoint (int length, int codePoint)\r
+ {\r
+ ensureLength (4);\r
+ bytes[offset] = (byte) ((length >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) (length & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset +=4;\r
+ }\r
+\r
+ /**\r
+ * Write scalar object header includes length and codepoint\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param dataLength - length of object data\r
+ */\r
+ protected void writeScalarHeader (int codePoint, int dataLength)\r
+ {\r
+ ensureLength (dataLength + 4);\r
+ bytes[offset] = (byte) (((dataLength+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((dataLength+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset += 4;\r
+ }\r
+\r
+ /**\r
+ * Write scalar string object includes length, codepoint and value\r
+ * the string is converted into the appropriate codeset (EBCDIC)\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param string - string to be written\r
+ */\r
+ void writeScalarString (int codePoint, String string)\r
+ {\r
+ int stringLength = string.length();\r
+ ensureLength ((stringLength * 2) + 4);\r
+ bytes[offset] = (byte) (((stringLength+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((stringLength+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset = ccsidManager.convertFromUCS2 (string, bytes, offset + 4);\r
+ }\r
+\r
+ /**\r
+ * Write padded scalar string object includes length, codepoint and value\r
+ * the string is converted into the appropriate codeset (EBCDIC)\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param string - string to be written\r
+ * @param paddedLength - length to pad string to\r
+ */\r
+ void writeScalarPaddedString (int codePoint, String string, int paddedLength)\r
+ {\r
+ int stringLength = string.length();\r
+ int fillLength = paddedLength - stringLength;\r
+ ensureLength (paddedLength + 4);\r
+ bytes[offset] = (byte) (((paddedLength+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((paddedLength+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset = ccsidManager.convertFromUCS2 (string, bytes, offset + 4);\r
+ Arrays.fill(bytes,offset, offset + fillLength,ccsidManager.space);\r
+ offset += fillLength;\r
+ }\r
+\r
+ /**\r
+ * Write padded scalar string object value\r
+ * the string is converted into the appropriate codeset (EBCDIC)\r
+ *\r
+ * @param string - string to be written\r
+ * @param paddedLength - length to pad string to\r
+ */\r
+ protected void writeScalarPaddedString (String string, int paddedLength)\r
+ {\r
+ int stringLength = string.length();\r
+\r
+ int fillLength = paddedLength -stringLength;\r
+ ensureLength (paddedLength);\r
+ offset = ccsidManager.convertFromUCS2 (string, bytes, offset);\r
+ Arrays.fill(bytes,offset, offset + fillLength,ccsidManager.space);\r
+ offset += fillLength;\r
+ }\r
+\r
+ /**\r
+ * Write padded scalar <code>DRDAString</code> object value. The\r
+ * string is converted into the appropriate codeset.\r
+ *\r
+ * @param drdaString string to be written\r
+ * @param paddedLength length to pad string to\r
+ */\r
+ protected void writeScalarPaddedString (DRDAString drdaString, int paddedLength)\r
+ {\r
+ int stringLength = drdaString.length();\r
+ int fillLength = paddedLength - stringLength;\r
+ ensureLength(paddedLength);\r
+ System.arraycopy(drdaString.getBytes(), 0, bytes, offset, stringLength);\r
+ offset += stringLength;\r
+ Arrays.fill(bytes, offset, offset + fillLength, ccsidManager.space);\r
+ offset += fillLength;\r
+ }\r
+\r
+ /**\r
+ * Write padded scalar byte array object includes length, codepoint and value\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param buf - byte array to be written\r
+ * @param paddedLength - length to pad string to\r
+ * @param padByte - byte to be used for padding\r
+ */\r
+ protected void writeScalarPaddedBytes (int codePoint, byte[] buf, int paddedLength, byte padByte)\r
+ {\r
+ int bufLength = buf.length;\r
+ ensureLength (paddedLength + 4);\r
+ bytes[offset] = (byte) (((paddedLength+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((paddedLength+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset += 4;\r
+ System.arraycopy(buf,0,bytes,offset,bufLength);\r
+ offset += bufLength;\r
+ int fillLength = paddedLength - bufLength;\r
+ Arrays.fill(bytes,offset,offset + fillLength,padByte);\r
+ offset += fillLength;\r
+ }\r
+\r
+ /**\r
+ * Write padded scalar byte array object value\r
+ *\r
+ * @param buf - byte array to be written\r
+ * @param paddedLength - length to pad string to\r
+ * @param padByte - byte to be used for padding\r
+ */\r
+ protected void writeScalarPaddedBytes (byte[] buf, int paddedLength, byte padByte)\r
+ {\r
+ int bufLength = buf.length;\r
+ int fillLength = paddedLength - bufLength;\r
+ ensureLength (paddedLength);\r
+ System.arraycopy(buf,0,bytes,offset,bufLength);\r
+ offset +=bufLength;\r
+ Arrays.fill(bytes,offset,offset + fillLength,padByte);\r
+ offset += fillLength;\r
+ }\r
+\r
+ /**\r
+ * Write scalar byte array object includes length, codepoint and value\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param buf - byte array to be written\r
+ */\r
+ protected void writeScalarBytes (int codePoint, byte[] buf)\r
+ {\r
+ int bufLength = buf.length;\r
+ ensureLength (bufLength + 4);\r
+ bytes[offset] = (byte) (((bufLength+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((bufLength+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ System.arraycopy(buf,0,bytes,offset + 4,bufLength);\r
+ offset += bufLength + 4;\r
+ }\r
+\r
+ /**\r
+ * Write scalar byte array object includes length, codepoint and value\r
+ *\r
+ * @param codePoint - code point to write\r
+ * @param buf - byte array to be written\r
+ * @param start - starting point\r
+ * @param length - length to write\r
+ */\r
+ protected void writeScalarBytes (int codePoint, byte[] buf, int start, int length)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (buf == null && length > start)\r
+ SanityManager.THROWASSERT("Buf is null");\r
+ if (length - start > buf.length)\r
+ SanityManager.THROWASSERT("Not enough bytes in buffer");\r
+ }\r
+ int numBytes = length - start;\r
+ ensureLength (numBytes + 4);\r
+ bytes[offset] = (byte) (((numBytes+4) >>> 8) & 0xff);\r
+ bytes[offset + 1] = (byte) ((numBytes+4) & 0xff);\r
+ bytes[offset + 2] = (byte) ((codePoint >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (codePoint & 0xff);\r
+ offset += 4;\r
+ System.arraycopy(buf,start,bytes,offset,numBytes);\r
+ offset += numBytes;\r
+ }\r
+ // The following methods write data in the platform format\r
+ // The platform format was indicated during connection time as ASC since\r
+ // JCC doesn't read JVM platform (yet)\r
+\r
+ /**\r
+ * Write platform short\r
+ *\r
+ * @param v value to be written\r
+ */\r
+ protected void writeShort (int v)\r
+ {\r
+ writeNetworkShort(v);\r
+ }\r
+\r
+ /**\r
+ * Write boolean as short\r
+ * @param b boolean value true = 1 false = 0\r
+ *\r
+ */\r
+ protected void writeShort(boolean b)\r
+ {\r
+ writeNetworkShort(b ? 1 : 0);\r
+ }\r
+\r
+ /**\r
+ * Write platform int\r
+ *\r
+ * @param v value to be written\r
+ */\r
+ protected void writeInt (int v)\r
+ {\r
+ writeNetworkInt(v);\r
+ }\r
+\r
+ /**\r
+ * Write platform long\r
+ *\r
+ * @param v value to be written\r
+ */\r
+ protected void writeLong (long v)\r
+ {\r
+ ensureLength (8);\r
+ bytes[offset] = (byte) ((v >>> 56) & 0xff);\r
+ bytes[offset + 1] = (byte) ((v >>> 48) & 0xff);\r
+ bytes[offset + 2] = (byte) ((v >>> 40) & 0xff);\r
+ bytes[offset + 3] = (byte) ((v >>> 32) & 0xff);\r
+ bytes[offset + 4] = (byte) ((v >>> 24) & 0xff);\r
+ bytes[offset + 5] = (byte) ((v >>> 16) & 0xff);\r
+ bytes[offset + 6] = (byte) ((v >>> 8) & 0xff);\r
+ bytes[offset + 7] = (byte) ((v >>> 0) & 0xff);\r
+ offset += 8;\r
+ }\r
+\r
+ /**\r
+ * Write platform float\r
+ *\r
+ * @param v value to be written\r
+ */\r
+ protected void writeFloat (float v)\r
+ {\r
+ writeInt (Float.floatToIntBits (v));\r
+ }\r
+\r
+ /**\r
+ * Write platform double\r
+ *\r
+ * @param v value to be written\r
+ */\r
+ protected void writeDouble (double v)\r
+ {\r
+ writeLong (Double.doubleToLongBits (v));\r
+ }\r
+\r
+ /**\r
+ * Write big decimal to buffer\r
+ *\r
+ * @param v value to write\r
+ * @param precision Precison of decimal or numeric type\r
+ * @param scale declared scale\r
+ * @exception SQLException thrown if number of digits > 31\r
+ */\r
+ protected void writeBigDecimal (java.math.BigDecimal v, int precision, int scale)\r
+ throws SQLException\r
+ {\r
+ int length = precision / 2 + 1;\r
+ ensureLength (offset + length);\r
+ bigDecimalToPackedDecimalBytes (v,precision, scale);\r
+ offset += length;\r
+ }\r
+\r
+ /**\r
+ * Write platform boolean\r
+ *\r
+ * @param v value to be written\r
+ */\r
+ protected void writeBoolean (boolean v)\r
+ {\r
+ ensureLength (1);\r
+ bytes[offset++] = (byte) ((v ? 1 : 0) & 0xff);\r
+ }\r
+\r
+ /**\r
+ * Write length delimited string\r
+ *\r
+ * @param s value to be written with integer\r
+ *\r
+ * @exception DRDAProtocolException\r
+ */\r
+ protected void writeLDString(String s) throws DRDAProtocolException\r
+ {\r
+ writeLDString(s,0);\r
+ }\r
+\r
+\r
+ /**\r
+ * Write length delimited string\r
+ *\r
+ * @param s value to be written with integer\r
+ * @param index column index to put in warning\r
+ * @exception DRDAProtocolException\r
+ */\r
+ protected void writeLDString(String s, int index) throws DRDAProtocolException\r
+ {\r
+ try {\r
+ byte [] byteval = s.getBytes(NetworkServerControlImpl.DEFAULT_ENCODING);\r
+ int origLen = byteval.length;\r
+ int writeLen =\r
+ java.lang.Math.min(FdocaConstants.LONGVARCHAR_MAX_LEN,\r
+ origLen);\r
+ /*\r
+ Need to make sure we truncate on character boundaries.\r
+ We are assuming\r
+ http://www.sun.com/developers/gadc/technicalpublications/articles/utf8.html\r
+ To find the beginning of a multibyte character:\r
+ 1) Does the current byte start with the bit pattern 10xxxxxx?\r
+ 2) If yes, move left and go to step #1.\r
+ 3) Finished\r
+ We assume that NetworkServerControlImpl.DEFAULT_ENCODING remains UTF-8\r
+ */\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!(NetworkServerControlImpl.DEFAULT_ENCODING.equals("UTF8")))\r
+ SanityManager.THROWASSERT("Encoding assumed to be UTF8, but is actually" + NetworkServerControlImpl.DEFAULT_ENCODING);\r
+ }\r
+\r
+ if (writeLen != origLen) {\r
+ //find the first byte of the multibyte char in case\r
+ //the last byte is part of a multibyte char\r
+ while (isContinuationChar (byteval [writeLen])) {\r
+ writeLen--;\r
+ }\r
+ //\r
+ // Now byteval[ writeLen ] is either a standalone 1-byte char\r
+ // or the first byte of a multi-byte character. That means that\r
+ // byteval[ writeLen -1 ] is the last (perhaps only) byte of the\r
+ // previous character.\r
+ //\r
+ }\r
+ \r
+\r
+ writeShort(writeLen);\r
+ writeBytes(byteval,writeLen);\r
+ }\r
+ catch (UnsupportedEncodingException e) {\r
+ //this should never happen\r
+ agent.agentError("Encoding " + NetworkServerControlImpl.DEFAULT_ENCODING + " not supported");\r
+ }\r
+ }\r
+ private boolean isContinuationChar( byte b ) { \r
+ return ( (b & MULTI_BYTE_MASK) == CONTINUATION_BYTE );\r
+ }\r
+\r
+ /**\r
+ * Write string with default encoding\r
+ *\r
+ * @param s value to be written\r
+ *\r
+ * @exception DRDAProtocolException\r
+ */\r
+ protected void writeString(String s) throws DRDAProtocolException\r
+ {\r
+ try {\r
+ writeBytes(s.getBytes(NetworkServerControlImpl.DEFAULT_ENCODING));\r
+ } catch (UnsupportedEncodingException e) {\r
+ //this should never happen\r
+ agent.agentError("Encoding " + NetworkServerControlImpl.DEFAULT_ENCODING + " not supported");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Write string with default encoding and specified length\r
+ *\r
+ * @param s value to be written\r
+ * @param length number of bytes to be written\r
+ *\r
+ * @exception DRDAProtocolException\r
+ */\r
+ protected void writeString(String s, int length) throws DRDAProtocolException\r
+ {\r
+ byte[] bs = null;\r
+ try {\r
+ bs = s.getBytes(NetworkServerControlImpl.DEFAULT_ENCODING);\r
+ } catch (UnsupportedEncodingException e) {\r
+ //this should never happen\r
+ agent.agentError("Encoding " + NetworkServerControlImpl.DEFAULT_ENCODING + " not supported");\r
+ }\r
+ int len = bs.length;\r
+ if (len >= length)\r
+ writeBytes(bs, length);\r
+ else\r
+ {\r
+ writeBytes(bs);\r
+ padBytes(NetworkServerControlImpl.SPACE_CHAR, length-len);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Write pad bytes using spaceChar\r
+ *\r
+ * @param val value to be written\r
+ * @param length length to be written\r
+ */\r
+ protected void padBytes (byte val, int length)\r
+ {\r
+ Arrays.fill(bytes,offset, offset + length,val);\r
+ offset += length;\r
+ }\r
+\r
+ /**\r
+ * Flush buffer to outputstream\r
+ *\r
+ *\r
+ * @exception IOException\r
+ */\r
+ protected void flush () throws java.io.IOException\r
+ {\r
+ flush(agent.getOutputStream());\r
+ }\r
+\r
+ /**\r
+ * Flush buffer to specified stream\r
+ *\r
+ * @param socketOutputStream\r
+ *\r
+ * @exception IOException\r
+ */\r
+ protected void flush(OutputStream socketOutputStream)\r
+ throws java.io.IOException\r
+ {\r
+ try {\r
+ socketOutputStream.write (bytes, 0, offset);\r
+ socketOutputStream.flush();\r
+ }\r
+ finally {\r
+ if ((dssTrace != null) && dssTrace.isComBufferTraceOn()) {\r
+ dssTrace.writeComBufferData (bytes,\r
+ 0,\r
+ offset,\r
+ DssTrace.TYPE_TRACE_SEND,\r
+ "Reply",\r
+ "flush",\r
+ 5);\r
+ }\r
+ reset(dssTrace);\r
+ }\r
+ }\r
+\r
+ // private methods\r
+\r
+ /**\r
+ * Write DSS header\r
+ * DSS Header format is\r
+ * 2 bytes - length\r
+ * 1 byte - 'D0' - indicates DDM data\r
+ * 1 byte - DSS format\r
+ * |---|---------|----------|\r
+ * | 0 | flags | type |\r
+ * |---|---------|----------|\r
+ * | 0 | 1 2 3 | 4 5 6 7 |\r
+ * |---|---------|----------|\r
+ * bit 0 - '0'\r
+ * bit 1 - '0' - unchained, '1' - chained\r
+ * bit 2 - '0' - do not continue on error, '1' - continue on error\r
+ * bit 3 - '0' - next DSS has different correlator, '1' - next DSS has\r
+ * same correlator\r
+ * type - 1 - Request DSS\r
+ * - 2 - Reply DSS\r
+ * - 3 - Object DSS\r
+ * - 4 - Communications DSS\r
+ * - 5 - Request DSS where no reply is expected\r
+ */\r
+ private void beginDss (int dssType, boolean ensureLen)\r
+ {\r
+\r
+ // save length position, the length will be written at the end\r
+ dssLengthLocation = offset;\r
+\r
+ // Should this really only be for non-stream DSSes?\r
+ if (ensureLen)\r
+ ensureLength(6);\r
+\r
+ // Skip past length; we'll come back and set it later.\r
+ offset += 2;\r
+\r
+ // write gds info\r
+ bytes[offset] = (byte) 0xD0;\r
+\r
+ // Write DSS type, and default chain bit to be \r
+ // DssConstants.DSSCHAIN_SAME_ID. This default\r
+ // will be overridden by calls to "finalizeChain()"\r
+ // and/or calls to "beginDss(boolean, int)" for\r
+ // writing LOB data.\r
+ bytes[offset + 1] = (byte) dssType;\r
+ bytes[offset + 1] |= DssConstants.DSSCHAIN_SAME_ID;\r
+\r
+ // save correlationID for use in error messages while processing\r
+ // this DSS\r
+ correlationID = getCorrelationID();\r
+\r
+ // write the reply correlation id\r
+ bytes[offset + 2] = (byte) ((correlationID >>> 8) & 0xff);\r
+ bytes[offset + 3] = (byte) (correlationID & 0xff);\r
+ offset += 4;\r
+ }\r
+\r
+ /**\r
+ * Finish a DSS Layer A object.\r
+ * The length of dss object will be calculated based on the difference between the\r
+ * start of the dss, saved on the beginDss call, and the current\r
+ * offset into the buffer which marks the end of the data. In the event\r
+ * the length requires the use of continuation Dss headers, one for each 32k\r
+ * chunk of data, the data will be shifted and the continuation headers\r
+ * will be inserted with the correct values as needed.\r
+ */\r
+ private void finalizeDssLength ()\r
+ {\r
+ // calculate the total size of the dss and the number of bytes which would\r
+ // require continuation dss headers. The total length already includes the\r
+ // the 6 byte dss header located at the beginning of the dss. It does not\r
+ // include the length of any continuation headers.\r
+ int totalSize = offset - dssLengthLocation;\r
+ int bytesRequiringContDssHeader = totalSize - DssConstants.MAX_DSS_LENGTH;\r
+\r
+ // determine if continuation headers are needed\r
+ if (bytesRequiringContDssHeader > 0)\r
+ {\r
+ // the continuation headers are needed, so calculate how many.\r
+ // after the first 32767 worth of data, a continuation header is\r
+ // needed for every 32765 bytes (32765 bytes of data + 2 bytes of\r
+ // continuation header = 32767 Dss Max Size).\r
+ int contDssHeaderCount = bytesRequiringContDssHeader / 32765;\r
+ if (bytesRequiringContDssHeader % 32765 != 0)\r
+ contDssHeaderCount++;\r
+\r
+ // right now the code will shift to the right. In the future we may want\r
+ // to try something fancier to help reduce the copying (maybe keep\r
+ // space in the beginning of the buffer??).\r
+ // the offset points to the next available offset in the buffer to place\r
+ // a piece of data, so the last dataByte is at offset -1.\r
+ // various bytes will need to be shifted by different amounts\r
+ // depending on how many dss headers to insert so the amount to shift\r
+ // will be calculated and adjusted as needed. ensure there is enough room\r
+ // for all the conutinuation headers and adjust the offset to point to the\r
+ // new end of the data.\r
+ int dataByte = offset - 1;\r
+ int shiftSize = contDssHeaderCount * 2;\r
+ ensureLength (shiftSize);\r
+ offset += shiftSize;\r
+\r
+ // Notes on the behavior of the Layer B segmenting loop below:\r
+ //\r
+ // We start with the right most chunk. For a 3-segment object we'd\r
+ // shift 2 segments: shift the first (rightmost) one 4 bytes and \r
+ // the second one 2. Note that by 'first' we mean 'first time\r
+ // through the loop', but that is actually the last segment\r
+ // of data since we are moving right-to-left. For an object\r
+ // of K segments we will pass through this loop K-1 times.\r
+ // The 0th (leftmost) segment is not shifted, as it is\r
+ // already in the right place. When we are done, we will\r
+ // have made room in each segment for an additional\r
+ // 2 bytes for the continuation header. Thus, each\r
+ // segment K is shifted K*2 bytes to the right.\r
+ //\r
+ // Each time through the loop, "dataByte" points to the\r
+ // last byte in the segment; "dataToShift" is the amount of\r
+ // data that we need to shift, and "shiftSize" is the\r
+ // distance that we need to shift it. Since dataByte points\r
+ // at the last byte, not one byte beyond it (as with the\r
+ // "offset" variable used elsewhere in DDMWriter), the start\r
+ // of the segement is actually at (dataByte-dataToShift+1).\r
+ //\r
+ // After we have shifted the segment, we move back to the\r
+ // start of the segment and set the value of the 2-byte DSS\r
+ // continuation header, which needs to hold the length of\r
+ // this segment's data, together with the continuation flag\r
+ // if this is not the rightmost (passOne) segment.\r
+ //\r
+ // In general, each segment except the rightmost will contain\r
+ // 32765 bytes of data, plus the 2-byte header, and its\r
+ // continuation flag will be set, so the header value will\r
+ // be 0xFFFF. The rightmost segment will not have the\r
+ // continuation flag set, so its value may be anything from\r
+ // 0x0001 to 0x7FFF, depending on the amount of data in that\r
+ // segment.\r
+ //\r
+ // Note that the 0th (leftmost) segment also has a 2-byte\r
+ // DSS header, which needs to have its continuation flag set.\r
+ // This is done by resetting the "totalSize" variable below,\r
+ // at which point that variable no longer holds the total size\r
+ // of the object, but rather just the length of segment 0. The\r
+ // total size of the object was written using extended length\r
+ // bytes by the endDdm() method earlier.\r
+ //\r
+ // Additional information about this routine is available in the\r
+ // bug notes for DERBY-125:\r
+ // http://issues.apache.org/jira/browse/DERBY-125\r
+ \r
+ // mark passOne to help with calculating the length of the final (first or\r
+ // rightmost) continuation header.\r
+ boolean passOne = true;\r
+ do {\r
+ // calculate chunk of data to shift\r
+ int dataToShift = bytesRequiringContDssHeader % 32765;\r
+ if (dataToShift == 0)\r
+ dataToShift = 32765;\r
+ int startOfCopyData = dataByte - dataToShift + 1;\r
+ System.arraycopy(bytes,startOfCopyData, bytes, \r
+ startOfCopyData + shiftSize, dataToShift);\r
+ dataByte -= dataToShift;\r
+\r
+\r
+ // calculate the value the value of the 2 byte continuation dss\r
+ // header which includes the length of itself. On the first pass,\r
+ // if the length is 32767\r
+ // we do not want to set the continuation dss header flag.\r
+ int twoByteContDssHeader = dataToShift + 2;\r
+ if (passOne)\r
+ passOne = false;\r
+ else\r
+ {\r
+ if (twoByteContDssHeader == DssConstants.MAX_DSS_LENGTH)\r
+ twoByteContDssHeader = (twoByteContDssHeader |\r
+ DssConstants.CONTINUATION_BIT);\r
+\r
+ }\r
+\r
+ // insert the header's length bytes\r
+ bytes[dataByte + shiftSize - 1] = (byte)\r
+ ((twoByteContDssHeader >>> 8) & 0xff);\r
+ bytes[dataByte + shiftSize] = (byte)\r
+ (twoByteContDssHeader & 0xff);\r
+\r
+ // adjust the bytesRequiringContDssHeader and the amount to shift for\r
+ // data in upstream headers.\r
+ bytesRequiringContDssHeader -= dataToShift;\r
+ shiftSize -= 2;\r
+\r
+ // shift and insert another header for more data.\r
+ }\r
+ while (bytesRequiringContDssHeader > 0);\r
+\r
+ // set the continuation dss header flag on for the first header\r
+ totalSize = (DssConstants.MAX_DSS_LENGTH |\r
+ DssConstants.CONTINUATION_BIT);\r
+\r
+\r
+ }\r
+\r
+ // insert the length bytes in the 6 byte dss header.\r
+ bytes[dssLengthLocation] = (byte) ((totalSize >>> 8) & 0xff);\r
+ bytes[dssLengthLocation + 1] = (byte) (totalSize & 0xff);\r
+ }\r
+\r
+ protected void writeExtendedLength(long size)\r
+ {\r
+ int numbytes = calculateExtendedLengthByteCount(size);\r
+ if (size > 0)\r
+ writeInt(0x8000 | numbytes);\r
+ else\r
+ writeInt(numbytes);\r
+ }\r
+\r
+\r
+ /**\r
+ * Calculate extended length byte count which follows the DSS header\r
+ * for extended DDM.\r
+ *\r
+ * @param ddmSize - size of DDM command\r
+ * @return minimum number of extended length bytes needed. 0 indicates no\r
+ * extended length needed.\r
+ */\r
+ private int calculateExtendedLengthByteCount (long ddmSize)\r
+ {\r
+ if (ddmSize <= 0x7fff)\r
+ return 0;\r
+ // JCC does not support 2 at this time, so we always send\r
+ // at least 4\r
+ // else if (ddmSize <= 0xffff)\r
+ // return 2;\r
+ else if (ddmSize <= 0xffffffffL)\r
+ return 4;\r
+ else if (ddmSize <= 0xffffffffffffL)\r
+ return 6;\r
+ else if (ddmSize <= 0x7fffffffffffffffL)\r
+ return 8;\r
+ else\r
+ // shouldn't happen\r
+ // XXX - add sanity debug stuff here\r
+ return 0;\r
+ }\r
+\r
+ /**\r
+ * Ensure that there is space in the buffer\r
+ *\r
+ * @param length space required\r
+ */\r
+ private void ensureLength (int length)\r
+ {\r
+ length += offset;\r
+ if (length > bytes.length) {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ agent.trace("DANGER - Expensive expansion of buffer");\r
+ }\r
+ byte newBytes[] = new byte[Math.max (bytes.length << 1, length)];\r
+ System.arraycopy (bytes, 0, newBytes, 0, offset);\r
+ bytes = newBytes;\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Write a Java <code>java.math.BigDecimal</code> to packed decimal bytes.\r
+ *\r
+ * @param b BigDecimal to write\r
+ * @param precision Precision of decimal or numeric type\r
+ * @return length written.\r
+ *\r
+ * @exception SQLException Thrown if # digits > 31\r
+ */\r
+ private int bigDecimalToPackedDecimalBytes (java.math.BigDecimal b,\r
+ int precision, int scale)\r
+ throws SQLException\r
+ {\r
+ int declaredPrecision = precision;\r
+ int declaredScale = scale;\r
+\r
+ // packed decimal may only be up to 31 digits.\r
+ if (declaredPrecision > 31) // this is a bugcheck only !!!\r
+ {\r
+ clearDdm ();\r
+ throw new java.sql.SQLException ("Packed decimal may only be up to 31 digits!");\r
+ }\r
+\r
+ // get absolute unscaled value of the BigDecimal as a String.\r
+ String unscaledStr = b.unscaledValue().abs().toString();\r
+\r
+ // get precision of the BigDecimal.\r
+ int bigPrecision = unscaledStr.length();\r
+\r
+ if (bigPrecision > 31)\r
+ {\r
+ clearDdm ();\r
+ throw new SQLException ("The numeric literal \"" +\r
+ b.toString() +\r
+ "\" is not valid because its value is out of range.",\r
+ "42820",\r
+ -405);\r
+ }\r
+ int bigScale = b.scale();\r
+ int bigWholeIntegerLength = bigPrecision - bigScale;\r
+ if ( (bigWholeIntegerLength > 0) && (!unscaledStr.equals ("0")) ) {\r
+ // if whole integer part exists, check if overflow.\r
+ int declaredWholeIntegerLength = declaredPrecision - declaredScale;\r
+ if (bigWholeIntegerLength > declaredWholeIntegerLength)\r
+ {\r
+ clearDdm ();\r
+ throw new SQLException ("Overflow occurred during numeric data type conversion of \"" +\r
+ b.toString() +\r
+ "\".",\r
+ "22003",\r
+ -413);\r
+ }\r
+ }\r
+\r
+ // convert the unscaled value to a packed decimal bytes.\r
+\r
+ // get unicode '0' value.\r
+ int zeroBase = '0';\r
+\r
+ // start index in target packed decimal.\r
+ int packedIndex = declaredPrecision-1;\r
+\r
+ // start index in source big decimal.\r
+ int bigIndex;\r
+\r
+ if (bigScale >= declaredScale) {\r
+ // If target scale is less than source scale,\r
+ // discard excessive fraction.\r
+\r
+ // set start index in source big decimal to ignore excessive fraction.\r
+ bigIndex = bigPrecision-1-(bigScale-declaredScale);\r
+\r
+ if (bigIndex < 0) {\r
+ // all digits are discarded, so only process the sign nybble.\r
+ bytes[offset+(packedIndex+1)/2] =\r
+ (byte) ( (b.signum()>=0)?12:13 ); // sign nybble\r
+ }\r
+ else {\r
+ // process the last nybble together with the sign nybble.\r
+ bytes[offset+(packedIndex+1)/2] =\r
+ (byte) ( ( (unscaledStr.charAt(bigIndex)-zeroBase) << 4 ) + // last nybble\r
+ ( (b.signum()>=0)?12:13 ) ); // sign nybble\r
+ }\r
+ packedIndex-=2;\r
+ bigIndex-=2;\r
+ }\r
+ else {\r
+ // If target scale is greater than source scale,\r
+ // pad the fraction with zero.\r
+\r
+ // set start index in source big decimal to pad fraction with zero.\r
+ bigIndex = declaredScale-bigScale-1;\r
+\r
+ // process the sign nybble.\r
+ bytes[offset+(packedIndex+1)/2] =\r
+ (byte) ( (b.signum()>=0)?12:13 ); // sign nybble\r
+\r
+ for (packedIndex-=2, bigIndex-=2; bigIndex>=0; packedIndex-=2, bigIndex-=2)\r
+ bytes[offset+(packedIndex+1)/2] = (byte) 0;\r
+\r
+ if (bigIndex == -1) {\r
+ bytes[offset+(packedIndex+1)/2] =\r
+ (byte) ( (unscaledStr.charAt(bigPrecision-1)-zeroBase) << 4 ); // high nybble\r
+\r
+ packedIndex-=2;\r
+ bigIndex = bigPrecision-3;\r
+ }\r
+ else {\r
+ bigIndex = bigPrecision-2;\r
+ }\r
+ }\r
+\r
+ // process the rest.\r
+ for (; bigIndex>=0; packedIndex-=2, bigIndex-=2) {\r
+ bytes[offset+(packedIndex+1)/2] =\r
+ (byte) ( ( (unscaledStr.charAt(bigIndex)-zeroBase) << 4 ) + // high nybble\r
+ ( unscaledStr.charAt(bigIndex+1)-zeroBase ) ); // low nybble\r
+ }\r
+\r
+ // process the first nybble when there is one left.\r
+ if (bigIndex == -1) {\r
+ bytes[offset+(packedIndex+1)/2] =\r
+ (byte) (unscaledStr.charAt(0) - zeroBase);\r
+\r
+ packedIndex-=2;\r
+ }\r
+\r
+ // pad zero in front of the big decimal if necessary.\r
+ for (; packedIndex>=-1; packedIndex-=2)\r
+ bytes[offset+(packedIndex+1)/2] = (byte) 0;\r
+\r
+ return declaredPrecision/2 + 1;\r
+ }\r
+\r
+\r
+ /***\r
+ * Prepend zeros to numeric string\r
+ *\r
+ * @param s string\r
+ * @param precision - length of padded string\r
+ *\r
+ * @return zero padded string\r
+ */\r
+ public static String zeroPadString(String s, int precision)\r
+ {\r
+\r
+ if (s == null)\r
+ return s;\r
+\r
+ int slen = s.length();\r
+ if (precision == slen)\r
+ return s;\r
+ else if (precision > slen)\r
+ {\r
+ char[] ca = new char[precision - slen];\r
+ Arrays.fill(ca,0,precision - slen,'0');\r
+ return new String(ca) + s;\r
+ }\r
+ else\r
+ {\r
+ // Shouldn't happen but just in case \r
+ // truncate\r
+ return s.substring(0,precision);\r
+ }\r
+\r
+ }\r
+\r
+ \r
+ private void sendBytes (java.io.OutputStream socketOutputStream) \r
+ throws java.io.IOException{\r
+ \r
+ sendBytes(socketOutputStream,\r
+ true);\r
+ \r
+ }\r
+ \r
+\r
+ private void sendBytes (java.io.OutputStream socketOutputStream,\r
+ boolean flashStream ) \r
+ throws java.io.IOException\r
+ {\r
+ resetChainState();\r
+ try {\r
+ socketOutputStream.write (bytes, 0, offset);\r
+ if(flashStream)\r
+ socketOutputStream.flush();\r
+ }\r
+ finally {\r
+ if ((dssTrace != null) && dssTrace.isComBufferTraceOn()) {\r
+ dssTrace.writeComBufferData (bytes,\r
+ 0,\r
+ offset,\r
+ DssTrace.TYPE_TRACE_SEND,\r
+ "Reply",\r
+ "flush",\r
+ 5);\r
+ }\r
+ clearBuffer();\r
+ }\r
+ }\r
+\r
+ protected String toDebugString(String indent)\r
+ {\r
+ String s = indent + "***** DDMWriter toDebugString ******\n";\r
+ int byteslen = 0;\r
+ if ( bytes != null)\r
+ byteslen = bytes.length;\r
+ s += indent + "byte array length = " + bytes.length + "\n";\r
+ return s;\r
+ }\r
+\r
+ /**\r
+ * Reset any chaining state that needs to be reset\r
+ * at time of the send\r
+ */\r
+ protected void resetChainState()\r
+ {\r
+ prevHdrLocation = -1;\r
+ }\r
+\r
+ /**\r
+ * Looks at chaining info for previous DSS written, and use\r
+ * that to figure out what the correlation id for the current\r
+ * DSS should be. Return that correlation id.\r
+ */\r
+ private int getCorrelationID() {\r
+\r
+ int cId;\r
+ if (previousCorrId != DssConstants.CORRELATION_ID_UNKNOWN) {\r
+ if (previousChainByte == DssConstants.DSSCHAIN_SAME_ID)\r
+ // then we have to use the last correlation id we sent.\r
+ cId = previousCorrId;\r
+ else\r
+ // get correlation id as normal.\r
+ cId = nextCorrelationID++;\r
+ }\r
+ else {\r
+ // must be the case that this is the first DSS we're\r
+ // writing for this connection (because we haven't\r
+ // called "endDss" yet). So, get the corr id as\r
+ // normal.\r
+ cId = nextCorrelationID++;\r
+ }\r
+\r
+ return cId;\r
+\r
+ }\r
+\r
+ /**\r
+ * Finalize the current DSS chain and send it if\r
+ * needed.\r
+ *\r
+ * Updates the chaining state of the most recently-written-\r
+ * to-buffer DSS to correspond to the most recently-read-\r
+ * from-client request. If that chaining state indicates\r
+ * we've reached the end of a chain, then we go ahead\r
+ * and send the buffer across the wire.\r
+ * @param socketOutputStream Output stream to which we're flushing.\r
+ */\r
+ protected void finalizeChain(byte currChainByte,\r
+ OutputStream socketOutputStream) throws DRDAProtocolException\r
+ {\r
+\r
+ // Go back to previous DSS and override the default\r
+ // chain state (WITH_SAME_ID) with whatever the last\r
+ // request dictates.\r
+\r
+ if (prevHdrLocation != -1) {\r
+ // Note: == -1 => the previous DSS was already sent; this\r
+ // should only happen in cases where the buffer filled up\r
+ // and we had to send it (which means we were probably\r
+ // writing EXTDTA). In such cases, proper chaining\r
+ // should already have been handled @ time of send.\r
+ bytes[prevHdrLocation + 3] &= 0x0F; // Zero out old chain value.\r
+ bytes[prevHdrLocation + 3] |= currChainByte;\r
+ }\r
+\r
+ // previousChainByte needs to match what we just did.\r
+ previousChainByte = currChainByte;\r
+\r
+ if (currChainByte != DssConstants.DSS_NOCHAIN)\r
+ // then we're still inside a chain, so don't send.\r
+ return;\r
+\r
+ // Else, we just ended the chain, so send it across.\r
+\r
+ if ((SanityManager.DEBUG) && (agent != null))\r
+ agent.trace("Sending data");\r
+\r
+ resetChainState();\r
+ if (doesRequestContainData()) {\r
+ try {\r
+ flush(socketOutputStream);\r
+ } catch (java.io.IOException e) {\r
+ agent.markCommunicationsFailure(\r
+ "DDMWriter.finalizeChain()",\r
+ "OutputStream.flush()",\r
+ e.getMessage(),"*");\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Takes note of the location of the most recently completed\r
+ * DSS in the buffer, and then returns the current offset.\r
+ * This method is used in conjunction with "clearDSSesBackToMark"\r
+ * to allow for DRDAConnThread to "back-out" DSSes in the\r
+ * event of errors.\r
+ */\r
+ protected int markDSSClearPoint()\r
+ {\r
+\r
+ lastDSSBeforeMark = prevHdrLocation;\r
+ return offset;\r
+\r
+ }\r
+\r
+ /**\r
+ * Does a logical "clear" of everything written to the buffer after\r
+ * the received mark. It's assumed that this method will be used\r
+ * in error cases when we've started writing one or more DSSes,\r
+ * but then hit an error and need to back out. After backing out,\r
+ * we'll always need to write _something_ back to the client to\r
+ * indicate an error (typically, we just write an SQLCARD) but what\r
+ * exactly gets written is handled in DRDAConnThread. Here, we\r
+ * just do the necessary prep so that whatever comes next will\r
+ * succeed.\r
+ */\r
+ protected void clearDSSesBackToMark(int mark)\r
+ {\r
+\r
+ // Logical clear.\r
+ offset = mark;\r
+\r
+ // Because we've just cleared out the most recently-\r
+ // written DSSes, we have to make sure the next thing\r
+ // we write will have the correct correlation id. We\r
+ // do this by setting the value of 'nextCorrelationID'\r
+ // based on the chaining byte from the last remaining\r
+ // DSS (where "remaining" means that it still exists\r
+ // in the buffer after the clear).\r
+ if (lastDSSBeforeMark == -1)\r
+ // we cleared out the entire buffer; reset corr id.\r
+ nextCorrelationID = 1;\r
+ else {\r
+ // last remaining DSS had chaining, so we set "nextCorrelationID"\r
+ // to be 1 greater than whatever the last remaining DSS had as\r
+ // its correlation id.\r
+ nextCorrelationID = 1 + (int)\r
+ (((bytes[lastDSSBeforeMark + 4] & 0xff) << 8) +\r
+ (bytes[lastDSSBeforeMark + 5] & 0xff));\r
+ }\r
+\r
+ }\r
+\r
+ \r
+ private static int peekStream(InputStream in) throws IOException{\r
+ \r
+ in.mark(1);\r
+\r
+ try{\r
+ return in.read();\r
+ \r
+ }finally{\r
+ in.reset();\r
+ \r
+ }\r
+ }\r
+\r
+ \r
+ private static int getLayerBStreamingBufferSize(){\r
+ return PropertyUtil.getSystemInt( Property.DRDA_PROP_STREAMOUTBUFFERSIZE , 0 );\r
+ }\r
+ \r
+ \r
+ private static OutputStream placeLayerBStreamingBuffer(OutputStream original){\r
+ \r
+ int size = getLayerBStreamingBufferSize();\r
+ \r
+ if(size < 1)\r
+ return original;\r
+ else\r
+ return new BufferedOutputStream( original, size );\r
+\r
+ }\r
+ \r
+}\r
+\r