/* NOTE: this function processes incoming frames and stores flags/data; the TcpStateMachine function processes stored flags, advances the state machine as necessary, and sends out responses */ internal override void OnPacketReceived(UInt32 sourceIPAddress, UInt32 destinationIPAddress, byte[] buffer, Int32 index, Int32 count) { if (_isDisposed) return; bool triggerStateMachine = false; /* we set this to true if the state machine needs to process our data */ UInt32 sequenceNumber = ((UInt32)buffer[index + 4] << 24) + ((UInt32)buffer[index + 5] << 16) + ((UInt32)buffer[index + 6] << 8) + (UInt32)buffer[index + 7]; UInt32 acknowledgmentNumber = ((UInt32)buffer[index + 8] << 24) + ((UInt32)buffer[index + 9] << 16) + ((UInt32)buffer[index + 10] << 8) + (UInt32)buffer[index + 11]; Int32 headerLength = (((Int32)buffer[index + 12]) >> 4) * 4; bool flagFin = ((buffer[index + 13] & (1 << 0)) > 0); bool flagSyn = ((buffer[index + 13] & (1 << 1)) > 0); bool flagRst = ((buffer[index + 13] & (1 << 2)) > 0); bool flagPsh = ((buffer[index + 13] & (1 << 3)) > 0); bool flagAck = ((buffer[index + 13] & (1 << 4)) > 0); bool flagUrg = ((buffer[index + 13] & (1 << 5)) > 0); UInt16 windowSize = (UInt16)( (((UInt16)buffer[index + 14]) << 8) + ((UInt16)buffer[index + 15])); /* TODO: process header options */ if (headerLength > TcpHandler.TCP_HEADER_MIN_LENGTH) { bool endOfListProcessed = false; byte optionLength = 0; int headerBufferPos = TcpHandler.TCP_HEADER_MIN_LENGTH; while (!endOfListProcessed && (headerBufferPos < headerLength)) { byte optionKind; optionKind = buffer[headerBufferPos]; if (optionKind == 0 /* EOL = end of option list */) { endOfListProcessed = true; optionLength = 1; } else if (optionKind == 1 /* NOP = no operation; used for padding */) { optionLength = 1; } else { if (headerBufferPos < headerLength - 1) optionLength = (byte)(buffer[headerBufferPos + 1]); else optionLength = 1; /* backup in case other option length/list is corrupted */ /* interpret known TCP options */ switch (buffer[headerBufferPos]) { case 2: /* MSS = Maximum Segment Size */ { if (optionLength == 4) { _transmitWindowMaximumSegmentSize = (UInt16)( (((UInt16)buffer[headerBufferPos + 2]) << 8) + buffer[headerBufferPos + 3] ); } } break; default: break; } } headerBufferPos += optionLength; } } /* recalculate transmit window size */ /* NOTE: in edge cases, the transmit window could actually shrink. if that happens, we need to pull in our "sent but not acknowledged" marker as well */ if (_transmitWindowSentButNotAcknowledgedMarker > _transmitWindowLeftEdge + windowSize) _transmitWindowSentButNotAcknowledgedMarker = _transmitWindowLeftEdge + windowSize; _transmitWindowRightEdge = _transmitWindowLeftEdge + windowSize; /* if we receive a reset frame, close down our socket */ if (flagRst && (acknowledgmentNumber >= _transmitWindowLeftEdge) && (acknowledgmentNumber <= (_transmitWindowRightEdge + 1))) { // close down our socket _connectionState = ConnectionStateFlags.None; if (_tcpStateMachine_CurrentState != TcpStateMachineState.CLOSING) { /* TODO: clear our transmit buffers, notify user, etc. */ if (_receiveBufferSpaceFilledEvent != null) _receiveBufferSpaceFilledEvent.Set(); } if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.Set(); _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSED; triggerStateMachine = true; } // if we received a SYN and are in a valid state, store it and the corresponding sequence number now. if (flagSyn) { switch (_tcpStateMachine_CurrentState) { case TcpStateMachineState.OPENING: /* special case of simultaneous connection request from both sides */ case TcpStateMachineState.SYN_SENT: { _receiveWindowLeftEdge = sequenceNumber + 1; _receiveWindowRightEdge = (UInt32)(_receiveWindowLeftEdge + _receiveBuffer.Length); _connectionState |= ConnectionStateFlags.ConnectedFromDestination; _tcpStateMachine_SendAckRequired = true; triggerStateMachine = true; } break; case TcpStateMachineState.LISTEN: { try { if (_incomingConnectionBacklog + 1 > _incomingConnectionSockets.Count) { Int32 newIncomingSocketHandle = _tcpHandler.IPv4Layer.CreateSocket(IPv4Layer.ProtocolType.Tcp, Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks, false); TcpSocket newIncomingSocket = (TcpSocket)_tcpHandler.IPv4Layer.GetSocket(newIncomingSocketHandle); UInt16 sourceIPPort = (UInt16)((((UInt16)buffer[index + 0]) << 8) + buffer[index + 1]); newIncomingSocket.ConnectionComplete += _incomingConnectionSockets_ConnectionComplete; newIncomingSocket.ConnectionAborted += _incomingConnectionSockets_ConnectionAborted; newIncomingSocket.JoinIncomingConnection(_srcIPAddress, _srcIPPort, sourceIPAddress, sourceIPPort, sequenceNumber, windowSize); _incomingConnectionSockets.Add(newIncomingSocket); _incomingConnectionBacklog++; } else { // if our backlog is already full, discard the frame return; } } catch { // if we could not create the incoming socket, discard the frame. return; } } break; case TcpStateMachineState.ESTABLISHED: { // we will drop this packet to avoid connection takeover attacks, but we will still ACK the message in case the destination host's original SYN packet was lost. /* TODO: ideally, we would only send an ACK in the scenario that we have not received any TCP stream data yet */ _tcpStateMachine_SendAckRequired = true; triggerStateMachine = true; } break; default: // we are not in a valid state for an incoming connection request: drop this packet. return; } } Int32 payloadLength = count - headerLength; /* if some of our data has been acknowledged, process the ACK and move the transmit window forward */ if (flagAck && (acknowledgmentNumber > _transmitWindowLeftEdge) && (acknowledgmentNumber <= _transmitWindowSentButNotAcknowledgedMarker)) { if (_currentOutgoingTransmission.TransmissionType == TransmissionType.SYN) { _transmitWindowLeftEdge++; _currentOutgoingTransmission = new TransmissionDetails(TransmissionType.None); triggerStateMachine = true; } else { // move our transmit buffer pointer forward UInt32 bytesAcknowledged = (UInt32)((((UInt64)(acknowledgmentNumber - _transmitWindowLeftEdge)) + UInt32.MaxValue) % UInt32.MaxValue); _transmitBufferFirstBytePos = (UInt32)((((UInt64)_transmitBufferFirstBytePos) + bytesAcknowledged) % UInt32.MaxValue); // move our transmit window left edge forward _transmitWindowLeftEdge = acknowledgmentNumber; if (_transmitWindowLeftEdge == _transmitWindowSentButNotAcknowledgedMarker) { // all transmitted has been ACK'd, so abort our retry timer and send more data _currentOutgoingTransmission = new TransmissionDetails(TransmissionType.None); } _transmitBufferSpaceFreedEvent.Set(); triggerStateMachine = true; } _tcpStateMachine_SendAckRequired = true; } if (_currentOutgoingTransmission.TransmissionType == TransmissionType.FIN) { if (acknowledgmentNumber == _transmitWindowLeftEdge + 1) { _transmitWindowLeftEdge++; _currentOutgoingTransmission = new TransmissionDetails(TransmissionType.None); _connectionState &= ~ConnectionStateFlags.ConnectedToDestination; triggerStateMachine = true; } } Int32 receiveBufferBytesAvailable = GetReceiveBufferBytesFree(); if ( ((_tcpStateMachine_CurrentState == TcpStateMachineState.ESTABLISHED) || (_tcpStateMachine_CurrentState == TcpStateMachineState.CLOSING) || (_tcpStateMachine_CurrentState == TcpStateMachineState.TIME_WAIT)) && (payloadLength > 0) && (receiveBufferBytesAvailable >= payloadLength)) { if ((_connectionState & ConnectionStateFlags.ConnectedFromDestination) != 0) { if (_receiveWindowLeftEdge == sequenceNumber) { // we have received data; store it as long as we have room for it. lock (_receiveBufferLockObject) { Int32 numBytesStoredBeforeWrap = System.Math.Min((Int32)(_receiveBufferSize - _receiveBufferNextBytePos), (Int32)payloadLength); Array.Copy(buffer, index + headerLength, _receiveBuffer, (Int32)_receiveBufferNextBytePos, numBytesStoredBeforeWrap); _receiveBufferNextBytePos += (UInt32)numBytesStoredBeforeWrap; if (_receiveBufferNextBytePos == _receiveBufferSize) _receiveBufferNextBytePos = 0; // handle wrap-around // if we wrapped around, store the remaining bytes if (numBytesStoredBeforeWrap < payloadLength) { Array.Copy(buffer, index + headerLength + numBytesStoredBeforeWrap, _receiveBuffer, (Int32)_receiveBufferNextBytePos, payloadLength - numBytesStoredBeforeWrap); _receiveBufferNextBytePos += (UInt32)(payloadLength - numBytesStoredBeforeWrap); } } _receiveWindowLeftEdge = (UInt32)(((UInt64)_receiveWindowLeftEdge + (UInt64)payloadLength) % UInt32.MaxValue); /* TODO: handle _receiveWindowScale here */ _receiveBufferSpaceFilledEvent.Set(); _tcpStateMachine_SendAckRequired = true; triggerStateMachine = true; } else { // out of order packet; send an ACK so that the destination host knows what segment data we are expecting /* TODO: we should really have an "out of order/duplicate packet counter" here, and only send a dummy ACK when that counter meets its upper threshold */ _tcpStateMachine_SendAckRequired = true; triggerStateMachine = true; } } } // if we received a FIN and are in a valid state, store it and the corresponding sequence number now. if (flagFin && (acknowledgmentNumber >= _transmitWindowLeftEdge) && (acknowledgmentNumber <= (_transmitWindowRightEdge + 1))) { switch (_tcpStateMachine_CurrentState) { case TcpStateMachineState.ESTABLISHED: case TcpStateMachineState.CLOSING: { _receiveWindowLeftEdge = (UInt32)(sequenceNumber + payloadLength + 1); _receiveWindowRightEdge = _receiveWindowLeftEdge; _connectionState &= ~ConnectionStateFlags.ConnectedFromDestination; _tcpStateMachine_SendAckRequired = true; // if we weren't already closing the connection from our end, make sure that our TCP socket is now in closing state. _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSING; triggerStateMachine = true; } break; default: // we are not in a valid state for an incoming close request: drop this packet. break; } } if (triggerStateMachine) _tcpStateMachine_ActionRequiredEvent.Set(); }
/* NOTE: this function advances the state machine as necessary and sends out responses as necessary; the OnPacketReceived function processes incoming frames and stores the flags/data */ void TcpStateMachine() { while (true) { // wait for a change which requires the TCP state machine to change state or process data Int32 millisecondsTimeout = System.Math.Max(0, (Int32)(((_currentOutgoingTransmission.TransmissionType == TransmissionType.None) || (_currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks == Int64.MaxValue)) ? Timeout.Infinite : (_currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks - Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks) / TimeSpan.TicksPerMillisecond)); _tcpStateMachine_ActionRequiredEvent.WaitOne(millisecondsTimeout, false); if (_isDisposed) return; switch (_tcpStateMachine_CurrentState) { case TcpStateMachineState.CLOSED: { /* there is nothing to be done if our socket is CLOSED */ } break; case TcpStateMachineState.OPENING: { // if our app has requested an outgoing connection, try connecting now. // set the initial sequence number UInt32 initialSequenceNumber = (UInt32)(_randomGenerator.Next() + _randomGenerator.Next()); /* initial sequence number */ _transmitWindowLeftEdge = initialSequenceNumber; _transmitWindowSentButNotAcknowledgedMarker = _transmitWindowLeftEdge + 1; _transmitWindowRightEdge = _transmitWindowLeftEdge + _transmitWindowMaximumSegmentSize; Int64 currentTimeInMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; // move the state machine forward to "SYN_SENT" so that it can respond to an incoming SYN + ACK message _tcpStateMachine_CurrentState = TcpStateMachineState.SYN_SENT; lock (_currentOutgoingTransmissionLock) { // set our outstanding transmission type to SYN (which flag occupies a sequence #) _currentOutgoingTransmission = new TransmissionDetails(TransmissionType.SYN); _currentOutgoingTransmission.TransmissionAttemptsCounter = 1; _currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds = SYN_INITIAL_RETRY_TIMEOUT_IN_MS; _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks = currentTimeInMachineTicks + (_currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds * TimeSpan.TicksPerMillisecond); _currentOutgoingTransmission.MaximumTimeoutInMachineTicks = currentTimeInMachineTicks + (SYN_MAX_RETRY_TIMEOUT_IN_MS * TimeSpan.TicksPerMillisecond); } _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, _transmitWindowLeftEdge, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), ((_connectionState & ConnectionStateFlags.ConnectedFromDestination) > 0), false, false, true, false, new TcpHandler.TcpOption[] { new TcpHandler.TcpOption(2 /* Maximum Segment Size */, new byte[] { (byte)((_receiveWindowMaximumSegmentSize >> 8) & 0xFF), (byte)(_receiveWindowMaximumSegmentSize & 0xFF) }) }, new byte[] { }, 0, 0, Int64.MaxValue); /* TODO: use timeout on sending segment */ _tcpStateMachine_SendAckRequired = false; } break; case TcpStateMachineState.SYN_SENT: { if (_currentOutgoingTransmission.TransmissionType == TransmissionType.SYN) { // if we have not yet received an ACK for our SYN and our timeout has expired, resend it now. Int64 currentTimeInMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; if (currentTimeInMachineTicks > _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks /* timeout occurred */) { if (currentTimeInMachineTicks > _currentOutgoingTransmission.MaximumTimeoutInMachineTicks) { /* TODO: abort connection attempt */ /* TODO: should we send a RESET segment? */ _connectionState = ConnectionStateFlags.None; _outgoingConnectionCompleteEvent.Set(); /* raise our connection aborted event; this is used to notify a Listener socket that our connection has been successfully established */ if (ConnectionAborted != null) ConnectionAborted(this); } else { _currentOutgoingTransmission.TransmissionAttemptsCounter += 1; _currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds *= 2; _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks = currentTimeInMachineTicks + (_currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds * TimeSpan.TicksPerMillisecond); _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, _transmitWindowLeftEdge, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), ((_connectionState & ConnectionStateFlags.ConnectedFromDestination) > 0), false, false, true, false, new TcpHandler.TcpOption[] { new TcpHandler.TcpOption(2 /* Maximum Segment Size */, new byte[] { (byte)((_receiveWindowMaximumSegmentSize >> 8) & 0xFF), (byte)(_receiveWindowMaximumSegmentSize & 0xFF) }) }, new byte[] { }, 0, 0, Int64.MaxValue); /* TODO: enable timeout */ } } } else { // our SYN has been ack'd; make sure we've marked our outgoing connection as valid _connectionState |= ConnectionStateFlags.ConnectedToDestination; } if (((_connectionState & ConnectionStateFlags.ConnectedFromDestination) != 0) && ((_connectionState & ConnectionStateFlags.ConnectedToDestination) != 0)) { _tcpStateMachine_CurrentState = TcpStateMachineState.ESTABLISHED; _outgoingConnectionCompleteEvent.Set(); /* raise our connected event; this is used to notify a Listener socket that our connection has been successfully established */ if (ConnectionComplete != null) ConnectionComplete(this); } } break; case TcpStateMachineState.ESTABLISHED: { Int32 transmitBufferFilledLength = GetTransmitBufferBytesFilled(); if (transmitBufferFilledLength == 0) break; // if we have data to send, first make sure we send the initial attempt. if (_currentOutgoingTransmission.TransmissionType == TransmissionType.None) { Int64 currentTimeInMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; lock (_currentOutgoingTransmissionLock) { // set our outstanding transmission type to data _currentOutgoingTransmission = new TransmissionDetails(TransmissionType.Data); _currentOutgoingTransmission.TransmissionAttemptsCounter = 1; _currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds = _tcpStateMachine_CalculatedRetryTimeoutInMilliseconds; _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks = currentTimeInMachineTicks + (_currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds * TimeSpan.TicksPerMillisecond); _currentOutgoingTransmission.MaximumTimeoutInMachineTicks = currentTimeInMachineTicks + (((_transmitTimeoutInMilliseconds > 0) ? _transmitTimeoutInMilliseconds : DATA_MAX_RETRY_TIMEOUT_IN_MS) * TimeSpan.TicksPerMillisecond); } // calculate the number of bytes to send Int32 transmitWindowWidth = (Int32)((((UInt64)_transmitWindowRightEdge + UInt32.MaxValue) - _transmitWindowLeftEdge) % UInt32.MaxValue); Int32 totalBytesToSend = System.Math.Min(transmitWindowWidth, transmitBufferFilledLength); /* NOTE: this code is copy-and-paste synchronized with the "send data" code in the "re-attempt" section below */ // max bytes per segment (_transmitWindowMaximumSegmentSize) UInt32 absoluteWindowOffset = _transmitWindowLeftEdge; Int32 absoluteBufferOffset = (Int32)_transmitBufferFirstBytePos; Int32 relativeOffset = 0; while (relativeOffset < totalBytesToSend) { Int32 bytesToSendBeforeWrap = (Int32)System.Math.Min(_transmitBuffer.Length - absoluteBufferOffset, totalBytesToSend); Int32 currentBytesToSend = (Int32)System.Math.Min(bytesToSendBeforeWrap, _transmitWindowMaximumSegmentSize); _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, (UInt32)absoluteWindowOffset, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), true, (totalBytesToSend - relativeOffset == currentBytesToSend), false, false, false, null, _transmitBuffer, absoluteBufferOffset, currentBytesToSend, Int64.MaxValue); /* TODO: use timeout on sending segment */ relativeOffset += currentBytesToSend; absoluteBufferOffset = (absoluteBufferOffset + currentBytesToSend) % _transmitBuffer.Length; absoluteWindowOffset = (UInt32)((absoluteWindowOffset + currentBytesToSend) % UInt32.MaxValue); } _transmitWindowSentButNotAcknowledgedMarker = absoluteWindowOffset; _tcpStateMachine_SendAckRequired = false; } else if (_currentOutgoingTransmission.TransmissionType == TransmissionType.Data) { // if timeout has occured, resend all previously-sent segments Int64 currentTimeInMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; if (currentTimeInMachineTicks > _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks /* timeout occurred */) { if (currentTimeInMachineTicks > _currentOutgoingTransmission.MaximumTimeoutInMachineTicks) { /* TODO: send RESET to destination host */ /* abort transmission attempt and close socket */ _connectionState = ConnectionStateFlags.None; _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSED; if (_receiveBufferSpaceFilledEvent != null) _receiveBufferSpaceFilledEvent.Set(); if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.Set(); } else { /* retry transmission attempt */ _currentOutgoingTransmission.TransmissionAttemptsCounter += 1; _currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds *= 2; _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks = currentTimeInMachineTicks + (_currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds * TimeSpan.TicksPerMillisecond); // calculate the number of bytes to send Int32 transmitWindowUsedWidth = (Int32)((((UInt64)_transmitWindowSentButNotAcknowledgedMarker + UInt32.MaxValue) - _transmitWindowLeftEdge) % UInt32.MaxValue); Int32 totalBytesToSend = System.Math.Min(transmitWindowUsedWidth, transmitBufferFilledLength); /* NOTE: this code is copy-and-paste synchronized with the "send data" code in the "initial attempt" section above */ // max bytes per segment (_transmitWindowMaximumSegmentSize) UInt32 absoluteWindowOffset = _transmitWindowLeftEdge; Int32 absoluteBufferOffset = (Int32)_transmitBufferFirstBytePos; Int32 relativeOffset = 0; while (relativeOffset < totalBytesToSend) { Int32 bytesToSendBeforeWrap = (Int32)System.Math.Min(_transmitBuffer.Length - absoluteBufferOffset, totalBytesToSend); Int32 currentBytesToSend = (Int32)System.Math.Min(bytesToSendBeforeWrap, _transmitWindowMaximumSegmentSize); _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, (UInt32)absoluteWindowOffset, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), true, (totalBytesToSend - relativeOffset == currentBytesToSend), false, false, false, null, _transmitBuffer, absoluteBufferOffset, currentBytesToSend, Int64.MaxValue); /* TODO: use timeout on sending segment */ relativeOffset += currentBytesToSend; absoluteBufferOffset = (absoluteBufferOffset + currentBytesToSend) % _transmitBuffer.Length; absoluteWindowOffset = (UInt32)((absoluteWindowOffset + currentBytesToSend) % UInt32.MaxValue); } _transmitWindowSentButNotAcknowledgedMarker = absoluteWindowOffset; _tcpStateMachine_SendAckRequired = false; } } } } break; case TcpStateMachineState.CLOSING: { /* if we are no longer connected to or from the destination host, move to the CLOSED state. */ if (((_connectionState & ConnectionStateFlags.ConnectedToDestination) == 0) && ((_connectionState & ConnectionStateFlags.ConnectedFromDestination) == 0)) { /* TODO: we should move to 2MSL state (blocking this endpoint pair from being used for 2 * MSL) */ if (_receiveBufferSpaceFilledEvent != null) _receiveBufferSpaceFilledEvent.Set(); if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.Set(); break; } /* if we are still connected to the destination, send a FIN packet (or retry sending, if it has already been sent */ if ((_connectionState & ConnectionStateFlags.ConnectedToDestination) > 0) { // if we have not yet sent the FIN request, do so now. if (_currentOutgoingTransmission.TransmissionType != TransmissionType.FIN) { /* purge outgoing buffers */ _transmitWindowSentButNotAcknowledgedMarker = _transmitWindowLeftEdge; _transmitWindowRightEdge = _transmitWindowLeftEdge; /* TODO: notify any pending Send(...) calls that their transmission is being aborted. */ Int64 currentTimeInMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; lock (_currentOutgoingTransmissionLock) { // set our outstanding transmission type to FIN (which flag occupies a sequence #) _currentOutgoingTransmission = new TransmissionDetails(TransmissionType.FIN); _currentOutgoingTransmission.TransmissionAttemptsCounter = 1; _currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds = SYN_INITIAL_RETRY_TIMEOUT_IN_MS; _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks = currentTimeInMachineTicks + (_currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds * TimeSpan.TicksPerMillisecond); _currentOutgoingTransmission.MaximumTimeoutInMachineTicks = currentTimeInMachineTicks + (SYN_MAX_RETRY_TIMEOUT_IN_MS * TimeSpan.TicksPerMillisecond); } _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, _transmitWindowLeftEdge, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), true, false, false, false, true, null, new byte[] { }, 0, 0, Int64.MaxValue); /* TODO: use timeout on sending segment */ _tcpStateMachine_SendAckRequired = false; } else { // otherwise, if we have not received an ACK for our FIN and our timeout has expired, resend it now. Int64 currentTimeInMachineTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; if (currentTimeInMachineTicks > _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks /* timeout occurred */) { if (currentTimeInMachineTicks > _currentOutgoingTransmission.MaximumTimeoutInMachineTicks) { /* abort close attempt */ _connectionState = ConnectionStateFlags.None; _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSED; if (_receiveBufferSpaceFilledEvent != null) _receiveBufferSpaceFilledEvent.Set(); if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.Set(); } else { /* retry close attempt */ _currentOutgoingTransmission.TransmissionAttemptsCounter += 1; _currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds *= 2; _currentOutgoingTransmission.CurrentRetryTimeoutInMachineTicks = currentTimeInMachineTicks + (_currentOutgoingTransmission.CurrentRetryTimeoutInMilliseconds * TimeSpan.TicksPerMillisecond); _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, _transmitWindowLeftEdge, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), true, false, false, false, true, null, new byte[] { }, 0, 0, Int64.MaxValue); /* TODO: enable timeout */ } } } } /* we ignore the destination host's request to stay connected to us after we initiate a FIN; once our FIN transmission timeout occurs we move to CLOSED or 2MSL state */ //if ((_connectionState & ConnectionStateFlags.ConnectedFromDestination) > 0) //{ //} } break; default: break; } /* send any outgoing data along with any required ACKs */ if (_tcpStateMachine_SendAckRequired) { _tcpStateMachine_SendAckRequired = false; _tcpHandler.SendTcpSegment(_srcIPAddress, _destIPAddress, _srcIPPort, _destIPPort, _transmitWindowLeftEdge, _receiveWindowLeftEdge, GetReceiveBufferBytesFree(), true, false, false, (_currentOutgoingTransmission.TransmissionType == TransmissionType.SYN), false, null, new byte[] { }, 0, 0, Int64.MaxValue); /* TODO: enable timeout */ } } }
public override Int32 Receive(byte[] buffer, Int32 offset, Int32 count, Int32 flags, Int64 timeoutInMachineTicks) { switch (_tcpStateMachine_CurrentState) { case TcpStateMachineState.ESTABLISHED: { // if there is no data available and our connection is not being shut down, wait until data is available _receiveBufferSpaceFilledEvent.Reset(); // ensure that the "buffer filled" event is not triggered before checking our buffer fill state (so that waiting on the event doesn't falsely trigger if (GetReceiveBufferBytesFilled() == 0) { Int32 timeoutMilliseconds = (timeoutInMachineTicks == Int64.MaxValue ? Timeout.Infinite : (Int32)((timeoutInMachineTicks - Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks) / TimeSpan.TicksPerMillisecond)); _receiveBufferSpaceFilledEvent.WaitOne(timeoutMilliseconds, false); } } break; case TcpStateMachineState.CLOSING: case TcpStateMachineState.TIME_WAIT: break; default: return 0; /* if we are not in a valid state to receive data, return 0 bytes as the available data length. */ } bool receiveWindowWasFull; bool receiveWindowRemainingWasLessThanButIsNowGreaterThanSegmentSize = false; Int32 bytesRead = 0; lock (_receiveBufferLockObject) { UInt16 receiveBufferBytesFilled = GetReceiveBufferBytesFilled(); Int32 bytesToRead = System.Math.Min(receiveBufferBytesFilled, count); Int32 receiveBufferSize = _receiveBuffer.Length - 1; /* NOTE: one byte of teh actual buffer is unused, so the usable size is one less than the length */ Int32 receiveBufferBytesRemaining = receiveBufferSize - receiveBufferBytesFilled; receiveWindowWasFull = (bytesToRead == receiveBufferSize); receiveWindowRemainingWasLessThanButIsNowGreaterThanSegmentSize = ( (receiveBufferBytesRemaining < _receiveWindowMaximumSegmentSize) && ((receiveBufferBytesRemaining + bytesToRead) >= _receiveWindowMaximumSegmentSize) ); Int32 bytesToReadBeforeWrap = (Int32)System.Math.Min(bytesToRead, _receiveBuffer.Length - _receiveBufferFirstBytePos); Array.Copy(_receiveBuffer, (Int32)_receiveBufferFirstBytePos, buffer, offset, bytesToReadBeforeWrap); _receiveBufferFirstBytePos = (UInt32)((_receiveBufferFirstBytePos + bytesToReadBeforeWrap) % _receiveBuffer.Length); bytesRead += bytesToReadBeforeWrap; if ((_receiveBufferFirstBytePos == 0) && (bytesToReadBeforeWrap < bytesToRead)) { Array.Copy(_receiveBuffer, (Int32)_receiveBufferFirstBytePos, buffer, offset + bytesToReadBeforeWrap, bytesToRead - bytesToReadBeforeWrap); _receiveBufferFirstBytePos = (UInt32)((_receiveBufferFirstBytePos + (bytesToRead - bytesToReadBeforeWrap)) % _receiveBuffer.Length); bytesRead += (bytesToRead - bytesToReadBeforeWrap); } } /* if window size was zero, but is no longer zero, then send a window update to the destination host (via an ACK) */ if (receiveWindowWasFull) { _tcpStateMachine_SendAckRequired = true; _tcpStateMachine_ActionRequiredEvent.Set(); } /* if we window size was smaller than our minimum segemnt size, and is now larger than our minimum segment size, then send a window update to the destination host (via an ACK) */ if (receiveWindowRemainingWasLessThanButIsNowGreaterThanSegmentSize) { _tcpStateMachine_SendAckRequired = true; _tcpStateMachine_ActionRequiredEvent.Set(); } /* if we were in TIME_WAIT state and the caller requested our last data, move to CLOSED state now. */ if (_tcpStateMachine_CurrentState == TcpStateMachineState.TIME_WAIT && GetReceiveBufferBytesFilled() == 0) { _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSED; if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.Set(); _tcpStateMachine_ActionRequiredEvent.Set(); } return bytesRead; }
public override void Listen(int backlog) { if (_tcpStateMachine_CurrentState != TcpStateMachineState.CLOSED) throw Utility.NewSocketException(SocketError.IsConnected); if (!_sourceIpAddressAndPortAssigned) throw Utility.NewSocketException(SocketError.SocketError); /* "must be bound to address/port before .listen() can be called" */ // move to the listening state _tcpStateMachine_CurrentState = TcpStateMachineState.LISTEN; _tcpStateMachine_ActionRequiredEvent.Set(); }
public override void Close() { // if we were waiting for the client code to read our last-received data, go ahead and move to closed state now. if (_tcpStateMachine_CurrentState == TcpStateMachineState.TIME_WAIT) { _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSED; if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.Set(); _tcpStateMachine_ActionRequiredEvent.Set(); } if (_tcpStateMachine_CurrentState == TcpStateMachineState.CLOSED) return; if ((_outgoingConnectionClosedEvent != null) && (_tcpStateMachine_CurrentState != TcpStateMachineState.CLOSING)) _outgoingConnectionClosedEvent.Reset(); _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSING; _tcpStateMachine_ActionRequiredEvent.Set(); if (_outgoingConnectionClosedEvent != null) _outgoingConnectionClosedEvent.WaitOne(); }
public override void Connect(UInt32 ipAddress, UInt16 ipPort) { /* TODO: if the client wishes to reuse a socket object to connect a different server than before while TIME_WAIT's 2MSL have not yet expired, * we may want to support that scenario here--possibly by caching the TIME_WAIT state and endpoint pairs in our TcpHandler so it can handle the TIME_WAIT state. */ if (_tcpStateMachine_CurrentState != TcpStateMachineState.CLOSED) throw Utility.NewSocketException(SocketError.IsConnected); if (!_sourceIpAddressAndPortAssigned) Bind(IP_ADDRESS_ANY, IP_PORT_ANY); _destIPAddress = ipAddress; _destIPPort = ipPort; try { _tcpStateMachine_CurrentState = TcpStateMachineState.OPENING; _tcpStateMachine_ActionRequiredEvent.Set(); _outgoingConnectionCompleteEvent.Reset(); // wait for our connection to be established _outgoingConnectionCompleteEvent.WaitOne(); /* TODO: implement timeout here */ if (((_connectionState & ConnectionStateFlags.ConnectedFromDestination) != 0) && ((_connectionState & ConnectionStateFlags.ConnectedToDestination) != 0)) { return; // success! } else { throw Utility.NewSocketException(SocketError.TimedOut); } } catch { _tcpStateMachine_CurrentState = TcpStateMachineState.CLOSED; } }
internal void JoinIncomingConnection(UInt32 localIPAddress, UInt16 localIPPort, UInt32 destinationIPAddress, UInt16 destinationIPPort, UInt32 sequenceNumber, UInt16 windowSize) { if (_sourceIpAddressAndPortAssigned) throw Utility.NewSocketException(SocketError.SocketError); /* is this correct; should we throw an exception if we already have an IP address/port assigned? */ _sourceIpAddressAndPortAssigned = true; _srcIPAddress = localIPAddress; _srcIPPort = localIPPort; _destIPAddress = destinationIPAddress; _destIPPort = destinationIPPort; _receiveWindowLeftEdge = sequenceNumber + 1; _receiveWindowRightEdge = (UInt32)(_receiveWindowLeftEdge + _receiveBuffer.Length); //_transmitWindowLeftEdge = acknowledgementNumber; //_transmitWindowRightEdge = _transmitWindowLeftEdge + windowSize; _tcpStateMachine_CurrentState = TcpStateMachineState.OPENING; _connectionState = ConnectionStateFlags.ConnectedFromDestination; _tcpStateMachine_SendAckRequired = true; _tcpStateMachine_ActionRequiredEvent.Set(); }