// // returns the token length of the token or tds type // Returns -1 for partially length prefixed (plp) types for metadata info. // DOES NOT handle plp data streams correctly!!! // Plp data streams length information should be obtained from GetDataLength // internal bool TryGetTokenLength(byte token, TdsParserStateObject stateObj, out int tokenLength) { Debug.Assert(token != 0, "0 length token!"); switch (token) { // rules about SQLLenMask no longer apply to new tokens (as of 7.4) case TdsEnums.SQLFEATUREEXTACK: tokenLength = -1; return true; case TdsEnums.SQLSESSIONSTATE: return stateObj.TryReadInt32(out tokenLength); } { if (token == TdsEnums.SQLUDT) { // special case for UDTs tokenLength = -1; // Should we return -1 or not call GetTokenLength for UDTs? return true; } else if (token == TdsEnums.SQLRETURNVALUE) { tokenLength = -1; // In Yukon, the RETURNVALUE token stream no longer has length return true; } else if (token == TdsEnums.SQLXMLTYPE) { ushort value; if (!stateObj.TryReadUInt16(out value)) { tokenLength = 0; return false; } tokenLength = (int)value; Debug.Assert(tokenLength == TdsEnums.SQL_USHORTVARMAXLEN, "Invalid token stream for xml datatype"); return true; } } switch (token & TdsEnums.SQLLenMask) { case TdsEnums.SQLFixedLen: tokenLength = ((0x01 << ((token & 0x0c) >> 2))) & 0xff; return true; case TdsEnums.SQLZeroLen: tokenLength = 0; return true; case TdsEnums.SQLVarLen: case TdsEnums.SQLVarCnt: if (0 != (token & 0x80)) { ushort value; if (!stateObj.TryReadUInt16(out value)) { tokenLength = 0; return false; } tokenLength = value; return true; } else if (0 == (token & 0x0c)) { if (!stateObj.TryReadInt32(out tokenLength)) { return false; } return true; } else { byte value; if (!stateObj.TryReadByte(out value)) { tokenLength = 0; return false; } tokenLength = value; return true; } default: Debug.Assert(false, "Unknown token length!"); tokenLength = 0; return true; } }
internal bool TryReadSqlValueInternal(SqlBuffer value, byte tdsType, int length, TdsParserStateObject stateObj) { switch (tdsType) { case TdsEnums.SQLBIT: case TdsEnums.SQLBITN: Debug.Assert(length == 1, "invalid length for SqlBoolean type!"); byte byteValue; if (!stateObj.TryReadByte(out byteValue)) { return false; } value.Boolean = (byteValue != 0); break; case TdsEnums.SQLINTN: if (length == 1) { goto case TdsEnums.SQLINT1; } else if (length == 2) { goto case TdsEnums.SQLINT2; } else if (length == 4) { goto case TdsEnums.SQLINT4; } else { goto case TdsEnums.SQLINT8; } case TdsEnums.SQLINT1: Debug.Assert(length == 1, "invalid length for SqlByte type!"); if (!stateObj.TryReadByte(out byteValue)) { return false; } value.Byte = byteValue; break; case TdsEnums.SQLINT2: Debug.Assert(length == 2, "invalid length for SqlInt16 type!"); short shortValue; if (!stateObj.TryReadInt16(out shortValue)) { return false; } value.Int16 = shortValue; break; case TdsEnums.SQLINT4: Debug.Assert(length == 4, "invalid length for SqlInt32 type!"); int intValue; if (!stateObj.TryReadInt32(out intValue)) { return false; } value.Int32 = intValue; break; case TdsEnums.SQLINT8: Debug.Assert(length == 8, "invalid length for SqlInt64 type!"); long longValue; if (!stateObj.TryReadInt64(out longValue)) { return false; } value.Int64 = longValue; break; case TdsEnums.SQLFLTN: if (length == 4) { goto case TdsEnums.SQLFLT4; } else { goto case TdsEnums.SQLFLT8; } case TdsEnums.SQLFLT4: Debug.Assert(length == 4, "invalid length for SqlSingle type!"); float singleValue; if (!stateObj.TryReadSingle(out singleValue)) { return false; } value.Single = singleValue; break; case TdsEnums.SQLFLT8: Debug.Assert(length == 8, "invalid length for SqlDouble type!"); double doubleValue; if (!stateObj.TryReadDouble(out doubleValue)) { return false; } value.Double = doubleValue; break; case TdsEnums.SQLMONEYN: if (length == 4) { goto case TdsEnums.SQLMONEY4; } else { goto case TdsEnums.SQLMONEY; } case TdsEnums.SQLMONEY: { int mid; uint lo; if (!stateObj.TryReadInt32(out mid)) { return false; } if (!stateObj.TryReadUInt32(out lo)) { return false; } long l = (((long)mid) << 0x20) + ((long)lo); value.SetToMoney(l); break; } case TdsEnums.SQLMONEY4: if (!stateObj.TryReadInt32(out intValue)) { return false; } value.SetToMoney(intValue); break; case TdsEnums.SQLDATETIMN: if (length == 4) { goto case TdsEnums.SQLDATETIM4; } else { goto case TdsEnums.SQLDATETIME; } case TdsEnums.SQLDATETIM4: ushort daypartShort, timepartShort; if (!stateObj.TryReadUInt16(out daypartShort)) { return false; } if (!stateObj.TryReadUInt16(out timepartShort)) { return false; } value.SetToDateTime(daypartShort, timepartShort * SqlDateTime.SQLTicksPerMinute); break; case TdsEnums.SQLDATETIME: int daypart; uint timepart; if (!stateObj.TryReadInt32(out daypart)) { return false; } if (!stateObj.TryReadUInt32(out timepart)) { return false; } value.SetToDateTime(daypart, (int)timepart); break; case TdsEnums.SQLUNIQUEID: { Debug.Assert(length == 16, "invalid length for SqlGuid type!"); byte[] b = new byte[length]; if (!stateObj.TryReadByteArray(b, 0, length)) { return false; } value.SqlGuid = new SqlGuid(b, true); // doesn't copy the byte array break; } case TdsEnums.SQLBINARY: case TdsEnums.SQLBIGBINARY: case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLVARBINARY: case TdsEnums.SQLIMAGE: { // Note: Better not come here with plp data!! Debug.Assert(length <= TdsEnums.MAXSIZE); byte[] b = new byte[length]; if (!stateObj.TryReadByteArray(b, 0, length)) { return false; } value.SqlBinary = new SqlBinary(b, true); // doesn't copy the byte array break; } case TdsEnums.SQLVARIANT: if (!TryReadSqlVariant(value, length, stateObj)) { return false; } break; default: Debug.Assert(false, "Unknown SqlType!" + tdsType.ToString(CultureInfo.InvariantCulture)); break; } // switch return true; }
// @devnote: length should be size of decimal without the sign // @devnote: sign should have already been read off the wire private bool TryReadDecimalBits(int length, TdsParserStateObject stateObj, out int[] bits) { bits = stateObj._decimalBits; // used alloc'd array if we have one already int i; if (null == bits) bits = new int[4]; else { for (i = 0; i < bits.Length; i++) bits[i] = 0; } Debug.Assert((length > 0) && (length <= TdsEnums.MAX_NUMERIC_LEN - 1) && (length % 4 == 0), "decimal should have 4, 8, 12, or 16 bytes of data"); int decLength = length >> 2; for (i = 0; i < decLength; i++) { // up to 16 bytes of data following the sign byte if (!stateObj.TryReadInt32(out bits[i])) { return false; } } return true; }
private bool TryProcessSessionState(TdsParserStateObject stateObj, int length, SessionData sdata) { if (length < 5) { throw SQL.ParsingError(); } UInt32 seqNum; if (!stateObj.TryReadUInt32(out seqNum)) { return false; } if (seqNum == UInt32.MaxValue) { _connHandler.DoNotPoolThisConnection(); } byte status; if (!stateObj.TryReadByte(out status)) { return false; } if (status > 1) { throw SQL.ParsingError(); } bool recoverable = status != 0; length -= 5; while (length > 0) { byte stateId; if (!stateObj.TryReadByte(out stateId)) { return false; } int stateLen; byte stateLenByte; if (!stateObj.TryReadByte(out stateLenByte)) { return false; } if (stateLenByte < 0xFF) { stateLen = stateLenByte; } else { if (!stateObj.TryReadInt32(out stateLen)) { return false; } } byte[] buffer = null; lock (sdata._delta) { if (sdata._delta[stateId] == null) { buffer = new byte[stateLen]; sdata._delta[stateId] = new SessionStateRecord { _version = seqNum, _dataLength = stateLen, _data = buffer, _recoverable = recoverable }; sdata._deltaDirty = true; if (!recoverable) { checked { sdata._unrecoverableStatesCount++; } } } else { if (sdata._delta[stateId]._version <= seqNum) { SessionStateRecord sv = sdata._delta[stateId]; sv._version = seqNum; sv._dataLength = stateLen; if (sv._recoverable != recoverable) { if (recoverable) { Debug.Assert(sdata._unrecoverableStatesCount > 0, "Unrecoverable states count >0"); sdata._unrecoverableStatesCount--; } else { checked { sdata._unrecoverableStatesCount++; } } sv._recoverable = recoverable; } buffer = sv._data; if (buffer.Length < stateLen) { buffer = new byte[stateLen]; sv._data = buffer; } } } } if (buffer != null) { if (!stateObj.TryReadByteArray(buffer, 0, stateLen)) { return false; } } else { if (!stateObj.TrySkipBytes(stateLen)) return false; } if (stateLenByte < 0xFF) { length -= 2 + stateLen; } else { length -= 6 + stateLen; } } sdata.AssertUnrecoverableStateCountIsCorrect(); return true; }
internal bool TryProcessError(byte token, TdsParserStateObject stateObj, out SqlError error) { ushort shortLen; byte byteLen; int number; byte state; byte errorClass; error = null; if (!stateObj.TryReadInt32(out number)) { return false; } if (!stateObj.TryReadByte(out state)) { return false; } if (!stateObj.TryReadByte(out errorClass)) { return false; } Debug.Assert(((errorClass >= TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLERROR) || ((errorClass < TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLINFO), "class and token don't match!"); if (!stateObj.TryReadUInt16(out shortLen)) { return false; } string message; if (!stateObj.TryReadString(shortLen, out message)) { return false; } if (!stateObj.TryReadByte(out byteLen)) { return false; } string server; // If the server field is not recieved use the locally cached value. if (byteLen == 0) { server = _server; } else { if (!stateObj.TryReadString(byteLen, out server)) { return false; } } if (!stateObj.TryReadByte(out byteLen)) { return false; } string procedure; if (!stateObj.TryReadString(byteLen, out procedure)) { return false; } int line; if (!stateObj.TryReadInt32(out line)) { return false; } error = new SqlError(number, state, errorClass, _server, message, procedure, line); return true; }
// Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) { Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined ((stateObj._attentionSent) || ((SniContext.Snix_Execute != stateObj.SniContext) && (SniContext.Snix_SendRows != stateObj.SniContext))), // SniContext should not be Execute or SendRows unless attention was sent (and, therefore, we are looking for an ACK) String.Format("Unexpected SniContext on call to TryRun; SniContext={0}", stateObj.SniContext)); if (TdsParserState.Broken == State || TdsParserState.Closed == State) { dataReady = true; return true; // Just in case this is called in a loop, expecting data to be returned. } dataReady = false; do { // If there is data ready, but we didn't exit the loop, then something is wrong Debug.Assert(!dataReady, "dataReady not expected - did we forget to skip the row?"); if (stateObj._internalTimeout) { runBehavior = RunBehavior.Attention; } if (TdsParserState.Broken == State || TdsParserState.Closed == State) break; // jump out of the loop if the state is already broken or closed. if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) { if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) { SqlConnection connection = null; if (_connHandler != null) connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref // We are omitting checks for error.Class in the code below (see processing of INFO) since we know (and assert) that error class // error.Class < TdsEnums.MIN_ERROR_CLASS for info message. // Also we know that TdsEnums.MIN_ERROR_CLASS<TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS if ((connection != null) && connection.FireInfoMessageEventOnUserErrors) { foreach (SqlError error in stateObj._pendingInfoEvents) FireInfoMessageEvent(connection, stateObj, error); } else foreach (SqlError error in stateObj._pendingInfoEvents) stateObj.AddWarning(error); } stateObj._pendingInfoEvents = null; } byte token; if (!stateObj.TryReadByte(out token)) { return false; } if (!IsValidTdsToken(token)) { Debug.Assert(false, String.Format((IFormatProvider)null, "unexpected token; token = {0,-2:X2}", token)); _state = TdsParserState.Broken; _connHandler.BreakConnection(); throw SQL.ParsingError(); } int tokenLength; if (!TryGetTokenLength(token, stateObj, out tokenLength)) { return false; } switch (token) { case TdsEnums.SQLERROR: case TdsEnums.SQLINFO: { if (token == TdsEnums.SQLERROR) { stateObj._errorTokenReceived = true; // Keep track of the fact error token was received - for Done processing. } SqlError error; if (!TryProcessError(token, stateObj, out error)) { return false; } if (token == TdsEnums.SQLINFO && stateObj._accumulateInfoEvents) { Debug.Assert(error.Class < TdsEnums.MIN_ERROR_CLASS, "INFO with class > TdsEnums.MIN_ERROR_CLASS"); if (stateObj._pendingInfoEvents == null) stateObj._pendingInfoEvents = new List<SqlError>(); stateObj._pendingInfoEvents.Add(error); stateObj._syncOverAsync = true; break; } if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) { // If FireInfoMessageEventOnUserErrors is true, we have to fire event without waiting. // Otherwise we can go ahead and add it to errors/warnings collection. SqlConnection connection = null; if (_connHandler != null) connection = _connHandler.Connection; // SqlInternalConnection holds the user connection object as a weak ref if ((connection != null) && (connection.FireInfoMessageEventOnUserErrors == true) && (error.Class <= TdsEnums.MAX_USER_CORRECTABLE_ERROR_CLASS)) { // Fire SqlInfoMessage here FireInfoMessageEvent(connection, stateObj, error); } else { // insert error/info into the appropriate exception - warning if info, exception if error if (error.Class < TdsEnums.MIN_ERROR_CLASS) { stateObj.AddWarning(error); } else if (error.Class < TdsEnums.FATAL_ERROR_CLASS) { // Continue results processing for all non-fatal errors (<20) stateObj.AddError(error); // Add it to collection - but do NOT change run behavior UNLESS // we are in an ExecuteReader call - at which time we will be throwing // anyways so we need to consume all errors. This is not the case // if we have already given out a reader. If we have already given out // a reader we need to throw the error but not halt further processing. We used to // halt processing. if (null != dataStream) { if (!dataStream.IsInitialized) { runBehavior = RunBehavior.UntilDone; } } } else { stateObj.AddError(error); // Else we have a fatal error and we need to change the behavior // since we want the complete error information in the exception. // Besides - no further results will be received. runBehavior = RunBehavior.UntilDone; } } } else if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) { stateObj.AddError(error); } break; } case TdsEnums.SQLCOLINFO: { if (null != dataStream) { _SqlMetaDataSet metaDataSet; if (!TryProcessColInfo(dataStream.MetaData, dataStream, stateObj, out metaDataSet)) { return false; } if (!dataStream.TrySetMetaData(metaDataSet, false)) { return false; } dataStream.BrowseModeInfoConsumed = true; } else { // no dataStream if (!stateObj.TrySkipBytes(tokenLength)) { return false; } } break; } case TdsEnums.SQLDONE: case TdsEnums.SQLDONEPROC: case TdsEnums.SQLDONEINPROC: { // RunBehavior can be modified if (!TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj)) { return false; } if ((token == TdsEnums.SQLDONEPROC) && (cmdHandler != null)) { cmdHandler.OnDoneProc(); } break; } case TdsEnums.SQLORDER: { // don't do anything with the order token so read off the pipe if (!stateObj.TrySkipBytes(tokenLength)) { return false; } break; } case TdsEnums.SQLALTMETADATA: { stateObj.CloneCleanupAltMetaDataSetArray(); if (stateObj._cleanupAltMetaDataSetArray == null) { // create object on demand (lazy creation) stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection(); } _SqlMetaDataSet cleanupAltMetaDataSet; if (!TryProcessAltMetaData(tokenLength, stateObj, out cleanupAltMetaDataSet)) { return false; } stateObj._cleanupAltMetaDataSetArray.SetAltMetaData(cleanupAltMetaDataSet); if (null != dataStream) { byte metadataConsumedByte; if (!stateObj.TryPeekByte(out metadataConsumedByte)) { return false; } if (!dataStream.TrySetAltMetaDataSet(cleanupAltMetaDataSet, (TdsEnums.SQLALTMETADATA != metadataConsumedByte))) { return false; } } break; } case TdsEnums.SQLALTROW: { if (!stateObj.TryStartNewRow(isNullCompressed: false)) { // altrows are not currently null compressed return false; } // read will call run until dataReady. Must not read any data if returnimmetiately set if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) { ushort altRowId; if (!stateObj.TryReadUInt16(out altRowId)) { // get altRowId return false; } if (!TrySkipRow(stateObj._cleanupAltMetaDataSetArray.GetAltMetaData(altRowId), stateObj)) { // skip altRow return false; } } else { dataReady = true; } break; } case TdsEnums.SQLENVCHANGE: { // ENVCHANGE must be processed synchronously (since it can modify the state of many objects) stateObj._syncOverAsync = true; SqlEnvChange[] env; if (!TryProcessEnvChange(tokenLength, stateObj, out env)) { return false; } for (int ii = 0; ii < env.Length; ii++) { if (env[ii] != null && !this.Connection.IgnoreEnvChange) { switch (env[ii].type) { case TdsEnums.ENV_BEGINTRAN: // When we get notification from the server of a new // transaction, we move any pending transaction over to // the current transaction, then we store the token in it. // if there isn't a pending transaction, then it's either // a TSQL transaction or a distributed transaction. Debug.Assert(null == _currentTransaction, "non-null current transaction with an ENV Change"); _currentTransaction = _pendingTransaction; _pendingTransaction = null; if (null != _currentTransaction) { _currentTransaction.TransactionId = env[ii].newLongValue; // this is defined as a ULongLong in the server and in the TDS Spec. } else { TransactionType transactionType = TransactionType.LocalFromTSQL; _currentTransaction = new SqlInternalTransaction(_connHandler, transactionType, null, env[ii].newLongValue); } if (null != _statistics && !_statisticsIsInTransaction) { _statistics.SafeIncrement(ref _statistics._transactions); } _statisticsIsInTransaction = true; break; case TdsEnums.ENV_COMMITTRAN: // SQLHOT 483 // Must clear the retain id if the server-side transaction ends by anything other // than rollback. goto case TdsEnums.ENV_ROLLBACKTRAN; case TdsEnums.ENV_ROLLBACKTRAN: // When we get notification of a completed transaction // we null out the current transaction. if (null != _currentTransaction) { #if DEBUG // Check null for case where Begin and Rollback obtained in the same message. if (SqlInternalTransaction.NullTransactionId != _currentTransaction.TransactionId) { Debug.Assert(_currentTransaction.TransactionId != env[ii].newLongValue, "transaction id's are not equal!"); } #endif if (TdsEnums.ENV_COMMITTRAN == env[ii].type) { _currentTransaction.Completed(TransactionState.Committed); } else if (TdsEnums.ENV_ROLLBACKTRAN == env[ii].type) { // Hold onto transaction id if distributed tran is rolled back. This must // be sent to the server on subsequent executions even though the transaction // is considered to be rolled back. _currentTransaction.Completed(TransactionState.Aborted); } else { _currentTransaction.Completed(TransactionState.Unknown); } _currentTransaction = null; } _statisticsIsInTransaction = false; break; case TdsEnums.ENV_ENLISTDTC: case TdsEnums.ENV_DEFECTDTC: case TdsEnums.ENV_TRANSACTIONENDED: Debug.Assert(false, "Should have thrown if DTC token encountered"); break; default: _connHandler.OnEnvChange(env[ii]); break; } } } break; } case TdsEnums.SQLLOGINACK: { SqlLoginAck ack; if (!TryProcessLoginAck(stateObj, out ack)) { return false; } _connHandler.OnLoginAck(ack); break; } case TdsEnums.SQLFEATUREEXTACK: { if (!TryProcessFeatureExtAck(stateObj)) { return false; } break; } case TdsEnums.SQLSESSIONSTATE: { if (!TryProcessSessionState(stateObj, tokenLength, _connHandler._currentSessionData)) { return false; } break; } case TdsEnums.SQLCOLMETADATA: { if (tokenLength != TdsEnums.VARNULL) { _SqlMetaDataSet metadata; if (!TryProcessMetaData(tokenLength, stateObj, out metadata)) { return false; } stateObj._cleanupMetaData = metadata; } else { if (cmdHandler != null) { stateObj._cleanupMetaData = cmdHandler.MetaData; } } if (null != dataStream) { byte peekedToken; if (!stateObj.TryPeekByte(out peekedToken)) { // temporarily cache next byte return false; } if (!dataStream.TrySetMetaData(stateObj._cleanupMetaData, (TdsEnums.SQLTABNAME == peekedToken || TdsEnums.SQLCOLINFO == peekedToken))) { return false; } } else if (null != bulkCopyHandler) { bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData); } break; } case TdsEnums.SQLROW: case TdsEnums.SQLNBCROW: { Debug.Assert(stateObj._cleanupMetaData != null, "Reading a row, but the metadata is null"); if (token == TdsEnums.SQLNBCROW) { if (!stateObj.TryStartNewRow(isNullCompressed: true, nullBitmapColumnsCount: stateObj._cleanupMetaData.Length)) { return false; } } else { if (!stateObj.TryStartNewRow(isNullCompressed: false)) { return false; } } if (null != bulkCopyHandler) { if (!TryProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj)) { return false; } } else if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) { if (!TrySkipRow(stateObj._cleanupMetaData, stateObj)) { // skip rows return false; } } else { dataReady = true; } if (_statistics != null) { _statistics.WaitForDoneAfterRow = true; } break; } case TdsEnums.SQLRETURNSTATUS: int status; if (!stateObj.TryReadInt32(out status)) { return false; } if (cmdHandler != null) { cmdHandler.OnReturnStatus(status); } break; case TdsEnums.SQLRETURNVALUE: { SqlReturnValue returnValue; if (!TryProcessReturnValue(tokenLength, stateObj, out returnValue)) { return false; } if (cmdHandler != null) { cmdHandler.OnReturnValue(returnValue); } break; } case TdsEnums.SQLSSPI: { // token length is length of SSPI data - call ProcessSSPI with it Debug.Assert(stateObj._syncOverAsync, "ProcessSSPI does not support retry, do not attempt asynchronously"); stateObj._syncOverAsync = true; ProcessSSPI(tokenLength); break; } case TdsEnums.SQLTABNAME: { { if (!stateObj.TrySkipBytes(tokenLength)) { return false; } } break; } default: Debug.Assert(false, "Unhandled token: " + token.ToString(CultureInfo.InvariantCulture)); break; } Debug.Assert(stateObj._pendingData || !dataReady, "dataReady is set, but there is no pending data"); } // Loop while data pending & runbehavior not return immediately, OR // if in attention case, loop while no more pending data & attention has not yet been // received. while ((stateObj._pendingData && (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) || (!stateObj._pendingData && stateObj._attentionSent && !stateObj._attentionReceived)); #if DEBUG if ((stateObj._pendingData) && (!dataReady)) { byte token; if (!stateObj.TryPeekByte(out token)) { return false; } Debug.Assert(IsValidTdsToken(token), string.Format("DataReady is false, but next token is not valid: {0,-2:X2}", token)); } #endif if (!stateObj._pendingData) { if (null != CurrentTransaction) { CurrentTransaction.Activate(); } } // if we recieved an attention (but this thread didn't send it) then // we throw an Operation Cancelled error if (stateObj._attentionReceived) { // Dev11 #344723: SqlClient stress hang System_Data!Tcp::ReadSync via a call to SqlDataReader::Close // Spin until SendAttention has cleared _attentionSending, this prevents a race condition between receiving the attention ACK and setting _attentionSent SpinWait.SpinUntil(() => !stateObj._attentionSending); Debug.Assert(stateObj._attentionSent, "Attention ACK has been received without attention sent"); if (stateObj._attentionSent) { // Reset attention state. stateObj._attentionSent = false; stateObj._attentionReceived = false; if (RunBehavior.Clean != (RunBehavior.Clean & runBehavior) && !stateObj._internalTimeout) { // Add attention error to collection - if not RunBehavior.Clean! stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.OperationCancelled(), "", 0)); } } } if (stateObj.HasErrorOrWarning) { ThrowExceptionAndWarning(stateObj); } return true; }
internal bool TryProcessError(byte token, TdsParserStateObject stateObj, out SqlError error) { ushort shortLen; byte byteLen; int number; byte state; byte errorClass; error = null; if (!stateObj.TryReadInt32(out number)) { return false; } if (!stateObj.TryReadByte(out state)) { return false; } if (!stateObj.TryReadByte(out errorClass)) { return false; } Debug.Assert(((errorClass >= TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLERROR) || ((errorClass < TdsEnums.MIN_ERROR_CLASS) && token == TdsEnums.SQLINFO), "class and token don't match!"); if (!stateObj.TryReadUInt16(out shortLen)) { return false; } string message; if (!stateObj.TryReadString(shortLen, out message)) { return false; } if (!stateObj.TryReadByte(out byteLen)) { return false; } string server; // If the server field is not received use the locally cached value. if (byteLen == 0) { server = _server; } else { if (!stateObj.TryReadString(byteLen, out server)) { return false; } } if (!stateObj.TryReadByte(out byteLen)) { return false; } string procedure; if (!stateObj.TryReadString(byteLen, out procedure)) { return false; } int line; if (_isYukon) { if (!stateObj.TryReadInt32(out line)) { return false; } } else { ushort shortLine; if (!stateObj.TryReadUInt16(out shortLine)) { return false; } line = shortLine; // If we haven't yet completed processing login token stream yet, we may be talking to a Yukon server // In that case we still have to read another 2 bytes if (_state == TdsParserState.OpenNotLoggedIn) { // Login incomplete byte b; if (!stateObj.TryPeekByte(out b)) { return false; } if (b == 0) { // This is an invalid token value ushort value; if (!stateObj.TryReadUInt16(out value)) { return false; } line = (line << 16) + value; } } } error = new SqlError(number, state, errorClass, _server, message, procedure, line); return true; }
/// <summary> /// <para> Parses the TDS message to read single CIPHER_INFO entry.</para> /// </summary> internal bool TryReadCipherInfoEntry (TdsParserStateObject stateObj, out SqlTceCipherInfoEntry entry) { byte cekValueCount = 0; entry = new SqlTceCipherInfoEntry(ordinal: 0); // Read the DB ID int dbId; if (!stateObj.TryReadInt32(out dbId)) { return false; } // Read the keyID int keyId; if (!stateObj.TryReadInt32(out keyId)) { return false; } // Read the key version int keyVersion; if (!stateObj.TryReadInt32(out keyVersion)) { return false; } // Read the key MD Version byte[] keyMDVersion = new byte[8]; if (!stateObj.TryReadByteArray(keyMDVersion, 0, 8)) { return false; } // Read the value count if (!stateObj.TryReadByte (out cekValueCount)) { return false; } for (int i = 0; i < cekValueCount; i++) { // Read individual CEK values byte[] encryptedCek; string keyPath; string keyStoreName; byte algorithmLength; string algorithmName; ushort shortValue; byte byteValue; int length; // Read the length of encrypted CEK if (!stateObj.TryReadUInt16 (out shortValue)) { return false; } length = shortValue; encryptedCek = new byte[length]; // Read the actual encrypted CEK if (!stateObj.TryReadByteArray (encryptedCek, 0, length)) { return false; } // Read the length of key store name if (!stateObj.TryReadByte (out byteValue)) { return false; } length = byteValue; // And read the key store name now if (!stateObj.TryReadString(length, out keyStoreName)) { return false; } // Read the length of key Path if (!stateObj.TryReadUInt16 (out shortValue)) { return false; } length = shortValue; // Read the key path string if (!stateObj.TryReadString(length, out keyPath)) { return false; } // Read the length of the string carrying the encryption algo if (!stateObj.TryReadByte(out algorithmLength)) { return false; } length = (int)algorithmLength; // Read the string carrying the encryption algo (eg. RSA_PKCS_OAEP) if (!stateObj.TryReadString(length, out algorithmName)) { return false; } // Add this encrypted CEK blob to our list of encrypted values for the CEK entry.Add(encryptedCek, databaseId: dbId, cekId: keyId, cekVersion: keyVersion, cekMdVersion: keyMDVersion, keyPath: keyPath, keyStoreName: keyStoreName, algorithmName: algorithmName); } return true; }
private bool TryProcessDone(SqlCommand cmd, SqlDataReader reader, ref RunBehavior run, TdsParserStateObject stateObj) { ushort curCmd; ushort status; int count; // Can't retry TryProcessDone stateObj._syncOverAsync = true; // status // command // rowcount (valid only if DONE_COUNT bit is set) if (!stateObj.TryReadUInt16(out status)) { return false; } if (!stateObj.TryReadUInt16(out curCmd)) { return false; } if (_isYukon) { long longCount; if (!stateObj.TryReadInt64(out longCount)) { return false; } count = (int) longCount; } else { if (!stateObj.TryReadInt32(out count)) { return false; } // If we haven't yet completed processing login token stream yet, we may be talking to a Yukon server // In that case we still have to read another 4 bytes // But don't try to read beyond the TDS stream in this case, because it generates errors if login failed. if ( _state == TdsParserState.OpenNotLoggedIn) { // Login incomplete, if we are reading from Yukon we need to read another int if (stateObj._inBytesRead > stateObj._inBytesUsed) { byte b; if (!stateObj.TryPeekByte(out b)) { return false; } if (b == 0) { // This is an invalid token value if (!stateObj.TryReadInt32(out count)) { return false; } } } } } // We get a done token with the attention bit set if (TdsEnums.DONE_ATTN == (status & TdsEnums.DONE_ATTN)) { Debug.Assert(TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE),"Not expecting DONE_MORE when receiving DONE_ATTN"); Debug.Assert(stateObj._attentionSent, "Received attention done without sending one!"); stateObj._attentionReceived = true; Debug.Assert(stateObj._inBytesUsed == stateObj._inBytesRead && stateObj._inBytesPacket == 0, "DONE_ATTN received with more data left on wire"); } if ((null != cmd) && (TdsEnums.DONE_COUNT == (status & TdsEnums.DONE_COUNT))) { if (curCmd != TdsEnums.SELECT) { if (cmd.IsDescribeParameterEncryptionRPCCurrentlyInProgress) { // The below line is used only for debug asserts and not exposed publicly or impacts functionality otherwise. cmd.RowsAffectedByDescribeParameterEncryption = count; } else { cmd.InternalRecordsAffected = count; } } // Skip the bogus DONE counts sent by the server if (stateObj._receivedColMetaData || (curCmd != TdsEnums.SELECT)) { cmd.OnStatementCompleted(count); } } stateObj._receivedColMetaData = false; // Surface exception for DONE_ERROR in the case we did not receive an error token // in the stream, but an error occurred. In these cases, we throw a general server error. The // situations where this can occur are: an invalid buffer received from client, login error // and the server refused our connection, and the case where we are trying to log in but // the server has reached its max connection limit. Bottom line, we need to throw general // error in the cases where we did not receive a error token along with the DONE_ERROR. if ((TdsEnums.DONE_ERROR == (TdsEnums.DONE_ERROR & status)) && stateObj.ErrorCount == 0 && stateObj._errorTokenReceived == false && (RunBehavior.Clean != (RunBehavior.Clean & run))) { stateObj.AddError(new SqlError(0, 0, TdsEnums.MIN_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0)); if (null != reader) { // SQL BU DT 269516 if (!reader.IsInitialized) { run = RunBehavior.UntilDone; } } } // Similar to above, only with a more severe error. In this case, if we received // the done_srverror, this exception will be added to the collection regardless. // MDAC #93896. Also, per Ashwin, the server will always break the connection in this case. if ((TdsEnums.DONE_SRVERROR == (TdsEnums.DONE_SRVERROR & status)) && (RunBehavior.Clean != (RunBehavior.Clean & run))) { stateObj.AddError(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, _server, SQLMessage.SevereError(), "", 0)); if (null != reader) { // SQL BU DT 269516 if (!reader.IsInitialized) { run = RunBehavior.UntilDone; } } } ProcessSqlStatistics(curCmd, status, count); // stop if the DONE_MORE bit isn't set (see above for attention handling) if (TdsEnums.DONE_MORE != (status & TdsEnums.DONE_MORE)) { stateObj._errorTokenReceived = false; if (stateObj._inBytesUsed >= stateObj._inBytesRead) { stateObj._pendingData = false; } } // _pendingData set by e.g. 'TdsExecuteSQLBatch' // _hasOpenResult always set to true by 'WriteMarsHeader' // if (!stateObj._pendingData && stateObj._hasOpenResult) { /* Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || (_userStartedLocalTransaction != null && _distributedTransaction != null)) , "ProcessDone - have both distributed and local transactions not null!"); */ // WebData 112722 stateObj.DecrementOpenResultCount(); } return true; }
private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, out SqlEnvChange[] sqlEnvChange) { // There could be multiple environment change messages following this token. byte byteLength; int processedLength = 0; int nvalues = 0; SqlEnvChange[] envarray = new SqlEnvChange[3]; // Why is this hardcoded to 3? sqlEnvChange = null; while (tokenLength > processedLength) { if (nvalues >= envarray.Length) { // This is a rare path. Most of the time we will have 1 or 2 envchange data streams. SqlEnvChange[] newenvarray = new SqlEnvChange[envarray.Length + 3]; for (int ii = 0; ii < envarray.Length; ii++) newenvarray[ii] = envarray[ii]; envarray = newenvarray; } SqlEnvChange env = new SqlEnvChange(); if (!stateObj.TryReadByte(out env.type)) { return false; } envarray[nvalues] = env; nvalues++; switch (env.type) { case TdsEnums.ENV_DATABASE: case TdsEnums.ENV_LANG: if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_CHARSET: // we copied this behavior directly from luxor - see charset envchange // section from sqlctokn.c Debug.Assert(!_isShiloh, "Received ENV_CHARSET on non 7.0 server!"); if (!TryReadTwoStringFields(env, stateObj)) { return false; } if (env.newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) { _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } else { Debug.Assert(env.newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); string stringCodePage = env.newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); _defaultCodePage = Int32.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } break; case TdsEnums.ENV_PACKETSIZE: // take care of packet size right here Debug.Assert(stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); if (!TryReadTwoStringFields(env, stateObj)) { // Changing packet size does not support retry, should not pend" throw SQL.SynchronousCallMayNotPend(); } // Only set on physical state object - this should only occur on LoginAck prior // to MARS initialization! Int32 packetSize = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); if (_physicalStateObj.SetPacketSize(packetSize)) { // If packet size changed, we need to release our SNIPackets since // those are tied to packet size of connection. _physicalStateObj.ClearAllWritePackets(); // Update SNI ConsumerInfo value to be resulting packet size UInt32 unsignedPacketSize = (UInt32) packetSize; UInt32 result = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, SNINativeMethodWrapper.QTypes.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); } break; case TdsEnums.ENV_LOCALEID: // if (!TryReadTwoStringFields(env, stateObj)) { return false; } _defaultLCID = Int32.Parse(env.newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); break; case TdsEnums.ENV_COMPFLAGS: if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_COLLATION: Debug.Assert(env.newLength == 5 || env.newLength == 0, "Improper length in new collation!"); if (!stateObj.TryReadByte(out byteLength)) { return false; } env.newLength = byteLength; if (env.newLength == 5) { if (!TryProcessCollation(stateObj, out env.newCollation)) { return false; } // give the parser the new collation values in case parameters don't specify one _defaultCollation = env.newCollation; int newCodePage = GetCodePage(env.newCollation, stateObj); if (newCodePage != _defaultCodePage) { _defaultCodePage = newCodePage; _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); } _defaultLCID = env.newCollation.LCID; } if (!stateObj.TryReadByte(out byteLength)) { return false; } env.oldLength = byteLength; Debug.Assert(env.oldLength == 5 || env.oldLength == 0, "Improper length in old collation!"); if (env.oldLength == 5) { if (!TryProcessCollation(stateObj, out env.oldCollation)) { return false; } } env.length = 3 + env.newLength + env.oldLength; break; case TdsEnums.ENV_BEGINTRAN: case TdsEnums.ENV_COMMITTRAN: case TdsEnums.ENV_ROLLBACKTRAN: case TdsEnums.ENV_ENLISTDTC: case TdsEnums.ENV_DEFECTDTC: case TdsEnums.ENV_TRANSACTIONENDED: Debug.Assert(_isYukon, "Received new ENVCHANGE transaction/DTC token on pre 9.0 server!"); if (!stateObj.TryReadByte(out byteLength)) { return false; } env.newLength = byteLength; Debug.Assert(env.newLength == 0 || env.newLength == 8, "Improper length for new transaction id!"); if (env.newLength > 0) { if (!stateObj.TryReadInt64(out env.newLongValue)) { return false; } Debug.Assert(env.newLongValue != SqlInternalTransaction.NullTransactionId, "New transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { env.newLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } if (!stateObj.TryReadByte(out byteLength)) { return false; } env.oldLength = byteLength; Debug.Assert(env.oldLength == 0 || env.oldLength == 8, "Improper length for old transaction id!"); if (env.oldLength > 0) { if (!stateObj.TryReadInt64(out env.oldLongValue)) { return false; } Debug.Assert(env.oldLongValue != SqlInternalTransaction.NullTransactionId, "Old transaction id is null?"); // the server guarantees that zero is an invalid transaction id. } else { env.oldLongValue = SqlInternalTransaction.NullTransactionId; // the server guarantees that zero is an invalid transaction id. } // env.length includes 1 byte type token env.length = 3 + env.newLength + env.oldLength; break; case TdsEnums.ENV_LOGSHIPNODE: // env.newBinValue is secondary node, env.oldBinValue is witness node // comes before LoginAck so we can't assert this if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_PROMOTETRANSACTION: Debug.Assert(_isYukon, "Received new ENVCHANGE tokens on pre 9.0 server!"); if (!stateObj.TryReadInt32(out env.newLength)) { // new value has 4 byte length return false; } env.newBinValue = new byte[env.newLength]; if (!stateObj.TryReadByteArray(env.newBinValue, 0, env.newLength)) { // read new value with 4 byte length return false; } if (!stateObj.TryReadByte(out byteLength)) { return false; } env.oldLength = byteLength; Debug.Assert(0 == env.oldLength, "old length should be zero"); // env.length includes 1 byte for type token env.length = 5 + env.newLength; break; case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: case TdsEnums.ENV_SPRESETCONNECTIONACK: // Debug.Assert(_isYukon, "Received new ENVCHANGE tokens on pre 9.0 server!"); if (!TryReadTwoBinaryFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_USERINSTANCE: Debug.Assert(!_isYukon, "Received ENV_USERINSTANCE on non 9.0 server!"); if (!TryReadTwoStringFields(env, stateObj)) { return false; } break; case TdsEnums.ENV_ROUTING: ushort newLength; if (!stateObj.TryReadUInt16(out newLength)) { return false; } env.newLength = newLength; byte protocol; if (!stateObj.TryReadByte(out protocol)) { return false; } ushort port; if (!stateObj.TryReadUInt16(out port)) { return false; } UInt16 serverLen; if (!stateObj.TryReadUInt16(out serverLen)) { return false; } string serverName; if (!stateObj.TryReadString(serverLen, out serverName)) { return false; } env.newRoutingInfo = new RoutingInfo(protocol, port, serverName); UInt16 oldLength; if (!stateObj.TryReadUInt16(out oldLength)) { return false; } if (!stateObj.TrySkipBytes(oldLength)) { return false; } env.length = env.newLength + oldLength + 5; // 5=2*sizeof(UInt16)+sizeof(byte) [token+newLength+oldLength] break; default: Debug.Assert(false, "Unknown environment change token: " + env.type); break; } processedLength += env.length; } sqlEnvChange = envarray; return true; }