using System; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using System.IO; using System.IO.Ports; using System.Threading; using GHI.Utilities; namespace CPF { public class XModem { // TERMINAL PROGRAM OBSERVATIONS: // // TeraTerm UTF-8 Pro: // XModem-1K ALWAYS sends 1024 data bytes/packet. // When Receiver cancels, only 1 byte is sent. The byte is sent as a standalone byte outside a packet. The Sender simply stops transmitting // at the nearest whole packet, and no acknowledgement is returned. // When Sender cancels, no control bytes are sent. Transmission simply stops at the nearest whole packet. Only the Receiver interpacket timeout will abort // the transfer on the Receiver side. // // HyperTerminal 7.0: // XModem-1K USUALLY sends 1024 data bytes/packet. However, it can also switch to 128-byte packets if the portion to send is short. // When Receiver cancels, 5 bytes are sent. The Sender simply stops transmitting at the nearest whole packet, and no acknowledgement is returned. // When Sender cancels, 5 bytes are sent. The Receiver does not acknowledge. (An is sent, but it's actually in response to the final // whole packet sent, not the cancellation notification.) // // This Implementation: // For both Sender and Receiver, requesting a cancellation is done by sending 5 times. If the Sender or Receiver receives at least 1 byte, // transfer will abort. If the Sender receives the cancellation, it will stop at the nearest whole packet. No acknowledgement is sent under any // circumstances by any party, in response to a cancellation. When transmitting over a a long distance noisy channel, such as a telephone line, // aborting on a single byte can be problemmatic due to other standalone control bytes possibly being corrupted into and triggering an // unintended abort. However, this implementation is intended to be operated by devices that are directly connected via a serial cable, and so the risk // of byte corruption is much less. Aborting on a single allows this implementation to respond to terminal software that only sends 1 // like TeraTerm. However, if this is problematic, NumCancellationBytesRequired can be used to set the minimum number of cancellation bytes that // will result in cancellation. // // On G120 largest byte array size permissible = 786364 bytes // *********************************** BEGIN: RECEIVER CUSTOMIZABLE PARAMETERS *********************************** /// /// The Receiver is responsible for initiating a file transfer. /// It does this by sending a FileInitiationByte, which is NAK for XModem-Checksum and C for XModem-CRC and XModem-1K. /// If the receiver has sent a FileInitiationByte, yet it has not received the first packet within the timeout specified /// below, it should resend the FileInitiationByte. /// public int ReceiverFileInitiationRetryMillisec = 250; /// /// This is the maximum number of times the Receiver will send the FileInitiationByte if the Sender has not sent the first packet. /// If this limit is reached, what happens next will depend on the Receiver's variant. /// /// SITUATION 1A: The Receiver is requesting XModem-CRC or XModem-1K --AND-- FallBackAllowed == True /// A Receiver wishing to use the newer CRC or 1K protocol requests a file transfer by sending . /// The Sender is supposed to respond by sending the first packet. However, if the Sender does not support either of these new variants, /// it will not recognize the and will not respond. If the maximum number of file initiation attempts is reached, the Receiver /// will fall back to the older XModem-Checksum protocol and send as its FileInitiationByte instead. /// The number of attempts is reset. If the limit is reached again and the Sender still has not sent the first packet, then the file /// transfer is aborted. /// /// SITUATION 1B: The Receiver is requesting XModem-CRC or XModem-1K --AND-- FallBackAllowed == False /// If this limit is reached and the first packet has not been received, the Receiver will abort the file transfer. /// /// SITUATION 2: The Receiver is requesting XModem-Checksum /// If this limit is reached and the first packet has not been received, the Receiver will abort the file transfer. /// public int ReceiverFileInitiationMaxAttempts = 240; /// /// XModem-CRC and XModem-1K evolved from XModem-Checksum. /// The official specification states that a Receiver which is requesting XModem-CRC or XModem-1K must have the /// ability to "fall back" to XModem-Checksum in case the Sender does not support newer formats. /// /// When True, this flag allows fallback to occur. /// When False, fallback will not occur, and the Receiver will simply abort the file transfer if the newer protocols aren't supported. /// This flag only has an effect if the Receiver is configured for XModem-CRC or XModem-1K. It has no impact if the Receiver is /// already configured for XModem-Checksum. /// public bool ReceiverFallBackAllowed = true; /// /// If the Receiver is expecting data from the Sender, this is the maximum amount of time it will wait before sending NAK to prompt /// the Sender to either resend the packet or finish the file. /// public int ReceiverTimeoutMillisec = 10000; /// /// If the Sender has not responded to repeated NAK nagging after this number of attempts, then the Receiver should abort the transfer. /// public int ReceiverMaxConsecutiveRetries = 10; private int _NumCancellationBytesRequired = 1; /// This is the minimum number of cancellation bytes that a Sender or Receiver must receive in order to cancel the file transfer. /// If 0 or less is specified, CAN bytes are ignored and will not result in cancellation, in which case cancellation will depend /// entirely on the timeout mechanism. public int NumCancellationBytesRequired { get { return _NumCancellationBytesRequired; } set { _NumCancellationBytesRequired = value; } } private int _NumCancellationBytesToSend = 5; /// /// This is the number of CAN bytes that a Sender or Receiver will transmit to the connected party if the user requests a cancellation. /// public int NumCancellationBytesToSend { get { return _NumCancellationBytesToSend; } set { if (value >= 0) _NumCancellationBytesToSend = value; else NumCancellationBytesToSend = 5; } } // *********************************** END: RECEIVER CUSTOMIZABLE PARAMETERS *********************************** // *********************************** BEGIN: SENDER CUSTOMIZABLE PARAMETERS *********************************** /// /// The Receiver checks each packet for errors. If a packet contains errors, the Receiver is supposed to send NAK, telling the Sender to resend the packet. /// This is the total number of times the Sender will resend the same packet whenever consecutive NAKs are received. /// If this limit is reached and the Receiver is still sending NAK, the communication channel is assumed to be terminally unreliable /// and file transfer should abort. /// public int MaxSenderRetries = 10; /// /// The Receiver is supposed to validate each packet with ACK or NAK. /// If validation has not been received from the Receiver for this amount of time, the packet is resent. /// public int SenderPacketRetryTimeoutMillisec = 15000; /// /// If the Sender has not heard ANYTHING from the Receiver after this amount of time has elapsed, the file transfer is aborted. /// public int SendInactivityTimeoutMillisec = 60000; // *********************************** END: SENDER CUSTOMIZABLE PARAMETERS *********************************** // *********************************** BEGIN: COMMON CUSTOMIZABLE PARAMETERS *********************************** // ASCII codes for common character constants. // These are exposed as public fields in case the user needs to customize them with nonstandard values. public byte SOH = 1; // Sender begins each 128-byte packet with this header public byte STX = 2; // Sender begins each 1024-byte packet with this header public byte ACK = 6; // Receiver sends this to indicate packet was received successfully with no errors public byte NAK = 21; // Receiver sends this to initiate XModem-Checksum file transfer -- OR -- indicate packet errors public byte C = 67; // Receiver sends this to initiate XModem-CRC or XModem-1K file transfer public byte EOT = 4; // Sender sends this to mark the end of file. Receiver must acknowledge receipt of this byte with , otherwise Sender resends public byte SUB = 26; // This is used as a padding byte in the original specification public byte CAN = 24; // [Commonly used but unofficial] Sender or Receiver sends this byte to abort file transfer public byte EOF = 26; // [Commonly used but unofficial] MS-DOS version of /// /// Defines the number of data bytes in a nominal 1024-byte packet. /// This allows the user to redefine a custom packet size for non-standard XModem implementations. /// private int _Packet1024NominalSize = 1024; public int Packet1024NominalSize { get { return _Packet1024NominalSize; } set { if (value > 0) { _Packet1024NominalSize = value; DefineDataPacketTemplate(); } } } /// /// Defines the number of data bytes in a nominal 128-byte packet. /// This allows the user to redefine a custom packet size for non-standard XModem implementations. /// private int _Packet128NominalSize = 128; public int Packet128NominalSize { get { return _Packet128NominalSize; } set { if (value > 0) { _Packet128NominalSize = value; DefineDataPacketTemplate(); } } } // *********************************** END: COMMON CUSTOMIZABLE PARAMETERS *********************************** /// /// CONSTRUCTOR. /// /// SerialPort to use when sending or receiving. /// /// The particular flavor of XModem to use. /// See Variants enumeration for a description of each XModem variant. /// /// /// If sending, this is the byte value that will be used to pad a packet if the data being sent is shorter than /// the required packet length. If omitted, defaults to SUB (byte decimal 26). /// /// /// If sending, this is the byte value that will be sent to indicate that the end-of-file has been reached. /// Of omitted, defaults to EOT (byte decimal 4). Some XModem receiver implementations may require that EOF /// is used instead to indicate end-of-file. /// public XModem(SerialPort port, Variants variant, byte paddingByte = 26, byte endOfFileByteToSend = 4) { // Field initialization PaddingByte = paddingByte; EndOfFileByteToSend = endOfFileByteToSend; // Property Initialization Port = port; Variant = variant; } /// /// Cancels the file transfer operation (send or receive) and notifies the other party of the /// cancellation by transmitting CAN bytes. /// public void CancelFileTransfer() { // Abort what we're currently doing Abort(); // Send cancellation bytes for (int k = 1; k <= _NumCancellationBytesToSend; k++) { Port.WriteByte(CAN); } _TerminationReason = TerminationReasonEnum.UserCancelled; } /// /// Describes the various flavors of XModem. /// /// XModemChecksum: This is the original, classic version of XModem. It is also the slowest and most error-prone. /// 128 data bytes per packet with checksum error-detection /// 132 bytes/packet total = 3 header bytes + 128 data bytes + 1 error-detection byte /// /// XModemCRC: This similar to the original, except with CRC-16 error detection instead of a simple checksum for more reliability. /// 128 data bytes per packet with CRC-16 error-detection /// 133 bytes/packet total = 3 header bytes + 128 data bytes + 2 error-detection bytes /// /// XModem1K: This is an updated version of XModem-CRC, expanded to 1024 data bytes (though 128 data bytes are still accepted). /// 128 and/or 1024 data bytes per packet and CRC-16 error-detection /// 133 bytes/packet total = 3 header bytes + 128 data bytes + 2 error-detection bytes /// 1029 bytes/packet total = 3 header bytes + 1024 data bytes + 2 error-detection bytes /// public enum Variants { XModemChecksum, XModemCRC, XModem1K }; private Variants _Variant; /// /// Gets/sets the XModem flavor that this implementation adheres to. /// public Variants Variant { get { return _Variant; } set { _Variant = value; DefineDataPacketTemplate(); // In case user wants to send data } } /// /// XModem's possible operational states. /// private enum States { Inactive, // The object is neither Sending nor Receiving ReceiverFileInitiation, // Receiver is sending the file initiation byte at regular intervals ReceiverHeaderSearch, // Receiver is expecting SOH or STX packet header ReceiverBlockNumSearch, // Receiver is expecting the block number ReceiverBlockNumComplementSearch, // Receiver is expecting the block number complement ReceiverDataBytesSearch, // Receiver is populating data bytes ReceiverErrorCheckSearch, // Receiver is expecting 1-byte or 2-byte check value(s) SenderAwaitingFileInitiation, // Sender is expecting file transmission request from Receiver SenderPacketSent, // Sender has sent a packet and is waiting for Receiver to validate it SenderAlertForPossibleCancellation, // Sender is not expecting anything in particular but can respond to a cancellation request if needed SenderAwaitingEndOfFileConfirmation // Sender has transmitted the end-of-file byte and is waiting for Receiver to acknowledge receipt of that byte } private States CurrentState = States.Inactive; /// /// Describes how the current XModem session has ended. /// public enum TerminationReasonEnum { TransferStillActiveNotTerminated, // File transfer is still active and has not terminated yet UserCancelled, // User has cancelled the file transfer EndOfFile, // End-of-file was reached. This is the ideal outcome when sending or receiving. FileInitiationTimeout, // Receiver has repeatedly requested file transfer to begin, but Sender has not responded CancelNotificationReceived, // Transfer aborted because cancellation bytes were detected from the Sender or Receiver TooManyRetries, // Too many erroneous packets have been sent or received. Indicates corruption or total communication loss. NoResponseFromReceiver // When Sending a file, the Receiver has become completely silent for an extended period. } public TerminationReasonEnum _TerminationReason = TerminationReasonEnum.TransferStillActiveNotTerminated; /// /// Describes under what conditions the file transfer has terminated. /// public TerminationReasonEnum TerminationReason { get { return _TerminationReason; } } /// /// PacketReceived event handler. /// /// The XModem object that raised the event. /// The complete, validated data packet received. /// /// Boolean flag that indicates if this packet is the final packet expected. /// True if the end-of-file byte was received, and the current packet is therefore the last one. /// False if the end-of-file byte has not been received, and more packets are still expected. /// public delegate void PacketReceivedEventHandler(XModem sender, byte[] packet, bool endOfFileDetected); /// /// Raised whenever a complete packet has been received. /// public event PacketReceivedEventHandler PacketReceived; /// /// Communication port that will be used to send and receive. /// public SerialPort Port; /// /// Performs blocking when the Receive() method is called by the user. /// private ManualResetEvent ReceiverUserBlock = new ManualResetEvent(false); private int _NumCancellationBytesReceived = 0; /// /// Tracks the number of CAN bytes that have currently been received by this Sender or Receiver. /// public int NumCancellationBytesReceived { get { return _NumCancellationBytesRequired; } } /// /// MemoryStream used to store all data received if user wants to receive data in one big lump. /// This remains null if user wants to receive data packet-by-packet instead. /// private MemoryStream AllDataReceivedBuffer; /// /// Initiates the file receive process. This method blocks until the file transfer has terminated. /// /// /// Optional MemoryStream object, which may be omitted. /// By instantiating an empty MemoryStream object and supplying it as an argument, the user can collect all data /// received into a single data structure and process it in one big lump once the transfer has finished. /// This is NOT recommended if the expected file size can exceed the contiguous memory capacity of the device. /// If the expected file size is large, subscribing to the PacketReceived event is recommended so that processing /// can be done one packet at a time within the available memory. /// /// /// A TerminationReasonEnum that describes under what conditions the receive process has terminated. This may /// be due to a successful EndOfFile being reached, or due to timeouts, errors, etc. /// public TerminationReasonEnum Receive(MemoryStream allDataReceivedBuffer = null) { // Initialize control variables _TerminationReason = TerminationReasonEnum.TransferStillActiveNotTerminated; Aborted = false; BlockNumExpected = 1; _NumFileInitiationBytesSent = 0; _NumCancellationBytesReceived = 0; Remainder = new byte[0]; DataPacketNumBytesStored = 0; ExpectingFirstPacket = true; ValidPacketReceived = false; // Define file initiation byte according to variant if (_Variant == Variants.XModemChecksum) FileInitiationByteToSend = NAK; else FileInitiationByteToSend = C; // Initialize state CurrentState = States.ReceiverFileInitiation; // Open port if it isn't open already if (Port.IsOpen == false) Port.Open(); // Clear out serial port buffers Port.DiscardInBuffer(); Port.DiscardOutBuffer(); // If user wants to get all data in one big lump, initialize the buffer with whatever MemoryStream object // is passed. If a memorystream is omitted, this defaults to null, which means received data // will not be stored. AllDataReceivedBuffer = allDataReceivedBuffer; // Attach event handler Port.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived); // Begin file initiation if (ReceiverFileInitiationTimer == null) ReceiverFileInitiationTimer = new Timer(ReceiverFileInitiationRoutine, null, 0, ReceiverFileInitiationRetryMillisec); else ReceiverFileInitiationTimer.Change(0, ReceiverFileInitiationRetryMillisec); // Block here ReceiverUserBlock.Reset(); ReceiverUserBlock.WaitOne(); ReceiverUserBlock.Reset(); return TerminationReason; } // The byte value that a Receiver sends when requesting file transfer: // XModem Checksum = // XModem CRC = // XModem 1K = private byte FileInitiationByteToSend; /// /// Sends the file initiation byte at regular intervals to request start of transfer from Sender. /// private Timer ReceiverFileInitiationTimer; /// /// Timer callback for file initiation timer. /// /// private void ReceiverFileInitiationRoutine(object notUsed) { Port.WriteByte(FileInitiationByteToSend); NumFileInitiationBytesSent += 1; } // Tracks how many file initiation bytes have already been sent to the Sender private int _NumFileInitiationBytesSent = 0; public int NumFileInitiationBytesSent { get { return _NumFileInitiationBytesSent; } private set { _NumFileInitiationBytesSent = value; // Determine if Receiver should fall back to an older variant if (_NumFileInitiationBytesSent > ReceiverFileInitiationMaxAttempts) { if (FileInitiationByteToSend == C && ReceiverFallBackAllowed == true) { // Fall back to older variant FileInitiationByteToSend = NAK; _NumFileInitiationBytesSent = 0; } else { Abort(); _TerminationReason = TerminationReasonEnum.FileInitiationTimeout; ReceiverUserBlock.Set(); } } } } // Indicates whether a valid packet has been received and is fit to be forwarded to user private bool ValidPacketReceived = false; /// /// State machine dispatcher. /// /// /// private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e) { SerialPort sp = sender as SerialPort; int numBytes = sp.BytesToRead; byte[] recv = new byte[numBytes]; sp.Read(recv, 0, numBytes); if (numBytes > 0) { switch (CurrentState) { // RECEIVER STATES: case States.ReceiverFileInitiation: // A response was received from the Sender after 1 or more file initiation bytes have been sent. // Halt the file initiation sender if (ReceiverFileInitiationTimer != null) ReceiverFileInitiationTimer.Change(Timeout.Infinite, Timeout.Infinite); // Start the timeout/NAK watchdog if (ReceiverNAKWatchdog == null) ReceiverNAKWatchdog = new Timer(NAKNag, null, ReceiverTimeoutMillisec, ReceiverTimeoutMillisec); else ReceiverNAKWatchdog.Change(ReceiverTimeoutMillisec, ReceiverTimeoutMillisec); // Advance to the next stage and process the current data received CurrentState = States.ReceiverHeaderSearch; ReceiverPacketBuilder(recv); break; case States.ReceiverHeaderSearch: case States.ReceiverBlockNumSearch: case States.ReceiverBlockNumComplementSearch: case States.ReceiverDataBytesSearch: case States.ReceiverErrorCheckSearch: ResetNAKWatchdog(); ReceiverPacketBuilder(recv); break; // SENDER STATES: case States.SenderAwaitingFileInitiation: ResetReceiverStillAliveWatchdog(); if (DetectCancellation(recv) == false) { // Determine if file initiation byte for CRC or Checksum error-checking received. if (Array.IndexOf(recv, C) > -1 || Array.IndexOf(recv, NAK) > -1) { // If user originally specified older XModem-Checksum but was received instead, // upgrade variant to new XModem-1K (which can handle both 128 and 1024 data bytes) // for maximum flexibility. if (_Variant == Variants.XModemChecksum && Array.IndexOf(recv, C) > -1) Variant = Variants.XModem1K; if (Array.IndexOf(recv, NAK) > -1) Variant = Variants.XModemChecksum; CurrentState = States.SenderAlertForPossibleCancellation; WaitForResponseFromReceiver.Set(); } } break; case States.SenderPacketSent: ResetReceiverStillAliveWatchdog(); if (DetectCancellation(recv) == false) { if (Array.IndexOf(recv, ACK) > -1) { // ACK received ResetSenderPacketResponseWatchdog(); _SenderConsecutiveRetryAttempts = 0; PacketSuccessfullySent = true; _BlockNumToSend += 1; WaitForResponseFromReceiver.Set(); } else if (Array.IndexOf(recv, NAK) > -1) { // NAK received ResetSenderPacketResponseWatchdog(); _SenderConsecutiveRetryAttempts += 1; WaitForResponseFromReceiver.Set(); } } break; case States.SenderAlertForPossibleCancellation: ResetReceiverStillAliveWatchdog(); DetectCancellation(recv); break; case States.SenderAwaitingEndOfFileConfirmation: ResetReceiverStillAliveWatchdog(); if (DetectCancellation(recv) == false) { if (Array.IndexOf(recv, ACK) > -1) { // ACK received EndOfFileAcknowledgementReceived = true; Abort(); } } break; } } } /// /// Enforces timeouts if no data is received or the packet repeatedly fails the checksum. /// This timer must be reset within a set amount of time or NAK is transmitted. /// private Timer ReceiverNAKWatchdog; /// /// Resets the NAK watchdog. /// private void ResetNAKWatchdog() { if (ReceiverNAKWatchdog != null) ReceiverNAKWatchdog.Change(ReceiverTimeoutMillisec, ReceiverTimeoutMillisec); ReceiverNumConsecutiveNAKSent = 0; } /// /// Timer callback for the NAK watchdog. /// /// private void NAKNag(object notUsed) { SendNAK(); } private void SendNAK() { Port.WriteByte(NAK); ReceiverNumConsecutiveNAKSent += 1; } private void SendACK() { Port.WriteByte(ACK); ReceiverNumConsecutiveNAKSent = 0; } // Tracks how many consecutive bytes have been sent public int _ReceiverNumConsecutiveNAKSent = 0; public int ReceiverNumConsecutiveNAKSent { get { return _ReceiverNumConsecutiveNAKSent; } set { _ReceiverNumConsecutiveNAKSent = value; if (_ReceiverNumConsecutiveNAKSent >= ReceiverMaxConsecutiveRetries) { Abort(); _TerminationReason = TerminationReasonEnum.TooManyRetries; ReceiverUserBlock.Set(); } } } /// /// The expected data packet size as specified by the incoming packet header. /// private int ExpectedDataPacketSize; // Stores the value of a candidate block number private byte BlockNumReceived; // Stores the value of a candidate block number complement (255 minus block number) private byte BlockNumComplementCandidateReceived; private bool ExpectingFirstPacket = true; private byte BlockNumExpected = 1; private byte[] BytesToParse; private byte[] Remainder = new byte[0]; // Stores data bytes received. It is instantiated to be the same length as the expected data packet size. private byte[] DataPacketReceived; // Keeps track of the number of bytes actually placed inside the current data packet private int DataPacketNumBytesStored = 0; // Stores the check value. This will contain 1 byte for XModem-Checksum, and 2 bytes for XModem-CRC or XModem-1K. private byte[] ErrorCheck; private void ReceiverPacketBuilder(byte[] freshBytes) { // The DataReceived event is occasionally triggered even when there are no bytes to read, so guard against this if (freshBytes.Length == 0) return; // We only want this to run once per DataReceived event and only if we are searching for a valid header byte if (Remainder.Length > 0 && CurrentState == States.ReceiverHeaderSearch) BytesToParse = Utility.CombineArrays(Remainder, freshBytes); else BytesToParse = freshBytes; // This keeps track of the index positions that searches begin at int headerByteSearchStartIndex = 0; int searchStartIndex = 0; // Loop while we are within bounds of the bytes to parse while (searchStartIndex < BytesToParse.Length && headerByteSearchStartIndex < BytesToParse.Length) { if (CurrentState == States.ReceiverHeaderSearch) { // Empty the remainder if populated, since it should have fulfilled its purpose prior to us // reaching this point. We don't want the remainder to carry over into the next DataReceived event // if we are in the header byte search state. if (Remainder.Length > 0) Remainder = new byte[0]; // Check for file termination tokens if (BytesToParse[headerByteSearchStartIndex] == EOT || BytesToParse[headerByteSearchStartIndex] == EOF) { SendACK(); Abort(); _TerminationReason = TerminationReasonEnum.EndOfFile; // If subscribed to, raise packet received event and indicate end of file reached if (PacketReceived != null && ValidPacketReceived == true && DataPacketReceived != null && DataPacketReceived.Length > 0) PacketReceived(this, DataPacketReceived, true); ReceiverUserBlock.Set(); return; } else { // If subscribed to, raise packet received event and indicate end of file NOT yet reached if (PacketReceived != null && ValidPacketReceived == true && DataPacketReceived != null && DataPacketReceived.Length > 0) PacketReceived(this, DataPacketReceived, false); } // Reset packet received validation flag ValidPacketReceived = false; // Check for cancellation bytes if (_NumCancellationBytesRequired > 0) { // Check if current byte is a cancellation request if (BytesToParse[headerByteSearchStartIndex] == CAN) { _NumCancellationBytesReceived += 1; if (_NumCancellationBytesReceived >= _NumCancellationBytesRequired) { // CANCEL THE FILE TRANSFER Abort(); _TerminationReason = TerminationReasonEnum.CancelNotificationReceived; ReceiverUserBlock.Set(); return; } else { // Move on to the next byte headerByteSearchStartIndex += 1; continue; } } else { // If not a cancellation byte, reset the cancellation byte counter _NumCancellationBytesReceived = 0; } } // Determine if we have a candidate header byte based on our XModem variant if (_Variant == Variants.XModemChecksum || _Variant == Variants.XModemCRC) { // XModem-Checksum and XModem-CRC should always have 128 data byte packets headed by . // Determine the location of this header byte, if it's present. int foundIndex = Array.IndexOf(BytesToParse, SOH, headerByteSearchStartIndex); if (foundIndex == -1) // No header byte found { // Quit this search and ignore the remaining bytes. // Look for a header byte in the next transmission. return; } else if (foundIndex > -1) { // Save the index position where we THINK a valid header byte resides at. // The candidate header byte may be discovered to be invalid later on, and we // need an index to return to in case we need to repeat the header search // starting from the next index. headerByteSearchStartIndex = foundIndex + 1; // Specify the packet size corresponding to this header ExpectedDataPacketSize = _Packet128NominalSize; // Move on to the next stage searchStartIndex = foundIndex + 1; CurrentState = States.ReceiverBlockNumSearch; continue; } } else if (_Variant == Variants.XModem1K) { // XModem-1K can receive 1024 data byte packets headed by --OR-- 128 data byte packets headed by . // The official standard allows a Sender to send a mixture of packet sizes, so a Receiver has to look for both. int packetStartIndexSTX = Array.IndexOf(BytesToParse, STX, headerByteSearchStartIndex); int packetStartIndexSOH = Array.IndexOf(BytesToParse, SOH, headerByteSearchStartIndex); // Look for the packet header byte int foundIndex = 0; if (packetStartIndexSTX > -1 && packetStartIndexSOH > -1) { // There is a (slim) possibility that both and bytes may be found. // If both are found, our candidate packet start index should be the earlier of the two. // If we're wrong, we can always get to the later one during the next iteration. if (packetStartIndexSTX <= packetStartIndexSOH) { // Header for 1024 data bytes/packet ExpectedDataPacketSize = _Packet1024NominalSize; foundIndex = packetStartIndexSTX; } else { // Header for 128 data bytes/packet ExpectedDataPacketSize = _Packet128NominalSize; foundIndex = packetStartIndexSOH; } } else if (packetStartIndexSTX > -1) // Only 1 found { // Header for 1024 data bytes/packet ExpectedDataPacketSize = _Packet1024NominalSize; foundIndex = packetStartIndexSTX; } else if (packetStartIndexSOH > -1) // Only 1 found { // Header for 128 data bytes/packet ExpectedDataPacketSize = _Packet128NominalSize; foundIndex = packetStartIndexSOH; } else { // If neither candidate headers were found, quit this search and ignore the remaining bytes. // Look for a header byte in the next transmission. return; } // Save the index position where we THINK a valid header byte resides at. // The candidate header byte may be discovered to be invalid later on, and we // need an index to return to in case we need to repeat the header search // starting from the next index. headerByteSearchStartIndex = foundIndex + 1; // Move on to the next stage searchStartIndex = foundIndex + 1; CurrentState = States.ReceiverBlockNumSearch; continue; } } if (CurrentState == States.ReceiverBlockNumSearch) { // The block number should be immediately after the packet header byte BlockNumReceived = BytesToParse[searchStartIndex]; // If the candidate block number is equal to a header byte, there is a slim possibility that what we currently believe // is the block number may actually be the true header byte, especially if the current candidate header byte is discovered // to be invalid later. Therefore, we should save the "block number" as a remainder so we can re-examine it later if // the current header byte canididate does not pan out and another DataReceived event is raised. if ((_Variant == Variants.XModemChecksum || _Variant == Variants.XModemCRC) && BlockNumReceived == SOH) { Remainder = new byte[] { BlockNumReceived }; // Initialize remainder } else if (_Variant == Variants.XModem1K && (BlockNumReceived == SOH || BlockNumReceived == STX)) { Remainder = new byte[] { BlockNumReceived }; // Initialize remainder } // Move on to the next stage searchStartIndex += 1; CurrentState = States.ReceiverBlockNumComplementSearch; continue; } if (CurrentState == States.ReceiverBlockNumComplementSearch) { // The complement of the block number should be right after the block number BlockNumComplementCandidateReceived = BytesToParse[searchStartIndex]; // Determine if we have received a valid block number and complement. // This will determine if we have a valid packet header and can proceed to the next stage. if (BlockNumComplementCandidateReceived == 255 - BlockNumReceived) { // Valid packet header.... // The packet header is valid, so we don't need to re-examine the remainder and can therefore // discard it if (Remainder.Length > 0) Remainder = new byte[0]; // Instantiate the data packet array which will hold incoming data DataPacketReceived = new byte[ExpectedDataPacketSize]; DataPacketNumBytesStored = 0; // Move on to the next stage searchStartIndex += 1; CurrentState = States.ReceiverDataBytesSearch; continue; } else { // Not a valid packet header.... // Add the candidate block number complement to the remainder if: // 1.) the remainder already contains an alternate candidate header byte --OR-- // 2.) the candidate block number complement has the same value as a header byte, and could therefore be a valid header byte itself if (Remainder.Length > 0 || ((_Variant == Variants.XModemChecksum || _Variant == Variants.XModemCRC) && BlockNumReceived == SOH)) { Remainder = Utility.CombineArrays(Remainder, new byte[] { BlockNumComplementCandidateReceived }); // Add to remainder } else if (Remainder.Length > 0 || (_Variant == Variants.XModem1K && (BlockNumReceived == SOH || BlockNumReceived == STX))) { Remainder = Utility.CombineArrays(Remainder, new byte[] { BlockNumComplementCandidateReceived }); // Add to remainder } // Search for another header byte CurrentState = States.ReceiverHeaderSearch; continue; } } if (CurrentState == States.ReceiverDataBytesSearch) { // Begin filling the data packet.... // Determine if there are enough unparsed bytes to fill the data packet int numUnparsedBytesRemaining = BytesToParse.Length - searchStartIndex; int numDataPacketBytesStillMissing = DataPacketReceived.Length - DataPacketNumBytesStored; int numDataBytesToPull; if (numUnparsedBytesRemaining >= numDataPacketBytesStillMissing) numDataBytesToPull = numDataPacketBytesStillMissing; else numDataBytesToPull = numUnparsedBytesRemaining; Array.Copy(BytesToParse, searchStartIndex, DataPacketReceived, DataPacketNumBytesStored, numDataBytesToPull); DataPacketNumBytesStored += numDataBytesToPull; searchStartIndex += numDataBytesToPull; if (DataPacketNumBytesStored >= ExpectedDataPacketSize) { // If all expected data bytes have been gathered, move on to the next stage CurrentState = States.ReceiverErrorCheckSearch; ErrorCheck = new byte[0]; } continue; } if (CurrentState == States.ReceiverErrorCheckSearch) { if (_Variant == Variants.XModemChecksum) { // 1 error-check byte expected. ErrorCheck = new byte[] { BytesToParse[searchStartIndex] }; // Validate packet ValidatePacket(); // Start over headerByteSearchStartIndex = searchStartIndex + 1; CurrentState = States.ReceiverHeaderSearch; } else // XModem-CRC or XModem-1K { // 2 error-check bytes expected. // If we have not yet gathered the required number of error check bytes, append to the error check // array. if (ErrorCheck.Length < 2) { ErrorCheck = Utility.CombineArrays(ErrorCheck, new byte[] { BytesToParse[searchStartIndex] }); } if (ErrorCheck.Length >= 2) { // We have enough error-check bytes, so validate packet ValidatePacket(); // Return to the initial state and start over headerByteSearchStartIndex = searchStartIndex + 1; CurrentState = States.ReceiverHeaderSearch; } else { // Advance to the next byte searchStartIndex += 1; } } } // End if } // End while } // End method private void ValidatePacket() { // In order for a packet to be accepted, it must be an expected block number, and the transmitted // check value must match the calculated check value. // Dealing with a block number that is out of sequence: // // Normally, successive block numbers must be monotonically increasing (accounting for binary wraparound) in order // to be valid. is normally sent if a block number is out of sequence. However, there's an exception. // // Exception: // If the current block number has been duplicated (it has the same value as a block number that was // received previously), is sent anyway on the theory that the Sender may not have received // the previous and decided to send the same packet again. will prompt the Sender to move on to the // next packet, which is what we want. ( will make it resend the packet yet again, which is unwanted.) // // Exception to the exception: // If this is the very first packet, the block number MUST be 1. Otherwise, will be sent. if (BlockNumReceived == BlockNumExpected) { if (ValidateChecksum() == true) { // Update control variables BlockNumExpected += 1; ExpectingFirstPacket = false; // If user wants all received data to be outputed in one lump, add this packet to the buffer if (AllDataReceivedBuffer != null) AllDataReceivedBuffer.Write(DataPacketReceived, 0, DataPacketReceived.Length); ValidPacketReceived = true; // Notify Sender to send the next packet SendACK(); } else { // Inform sender that checksum invalid SendNAK(); ValidPacketReceived = false; } } else if (ExpectingFirstPacket == false && BlockNumReceived == (byte)(BlockNumExpected - 1)) { // Receiver got a duplicate packet. // Send to prompt the Sender to advance to the next packet. Ignore the current (redundant) packet. SendACK(); ValidPacketReceived = false; } else { // The block number is completely out of sequence, so send NAK SendNAK(); ValidPacketReceived = false; } } /// /// Calculates the check value (simple checksum or CRC-16) appended to the end of a packet. /// /// /// True if the calculated check value and received check value match. /// False if there is a mismatch between the calculated and received check values. /// private bool ValidateChecksum() { switch (_Variant) { // Arithmetic checksum: case Variants.XModemChecksum: byte checksum = CheckSum(DataPacketReceived); if (checksum == ErrorCheck[0]) return true; else return false; // CRC-16: case Variants.XModemCRC: case Variants.XModem1K: ushort crcChecksumCalculated = Crc.Crc16(DataPacketReceived, 0, DataPacketReceived.Length, 0); ushort crcChecksumReceived = BytesToUShort(ErrorCheck[0], ErrorCheck[1]); if (crcChecksumCalculated == crcChecksumReceived) return true; else return false; // Just so VS2010 doesn't complain: default: return false; } } private ushort BytesToUShort(byte highByte, byte lowByte) { return (ushort)((highByte << 8) + lowByte); } private byte[] UShortToBytes(ushort val) { byte highByte = (byte)(val / 256); byte lowByte = (byte)(val % 256); return new byte[] { highByte, lowByte }; } /// /// Calculates the simple checksum by summing all values in a byte array and returning the remainder. /// /// /// Byte array whose checksum to calculate. /// /// /// Modulo-256 checksum. /// private byte CheckSum(byte[] seq) { byte sum = 0; for (int k = 0; k < seq.Length; k++) { sum += seq[k]; } return sum; } private bool Aborted = false; /// /// Internal method used to cancel the file transfer. /// private void Abort() { CurrentState = States.Inactive; TerminateSend = true; SenderInitialized = false; Aborted = true; // Detach event handler Port.DataReceived -= Port_DataReceived; // If we are sending data, tell Sender not to expect any more responses from Receiver. // This has no ill effect if we are receiving instead. WaitForResponseFromReceiver.Set(); // Deactivate Send and Receive watchdogs if (ReceiverNAKWatchdog != null) ReceiverNAKWatchdog.Change(Timeout.Infinite, Timeout.Infinite); if (ReceiverFileInitiationTimer != null) ReceiverFileInitiationTimer.Change(Timeout.Infinite, Timeout.Infinite); if (SenderPacketResponseWatchdog != null) SenderPacketResponseWatchdog.Change(Timeout.Infinite, Timeout.Infinite); if (ReceiverStillAliveWatchdog != null) ReceiverStillAliveWatchdog.Change(Timeout.Infinite, Timeout.Infinite); // Flush serial data so they don't contaminate a future session Port.DiscardInBuffer(); Port.DiscardOutBuffer(); } // ************************************* SENDER SENDER SENDER SENDER SENDER SENDER SENDER *********************************** private ManualResetEvent WaitForResponseFromReceiver = new ManualResetEvent(false); private Timer SenderPacketResponseWatchdog; private Timer ReceiverStillAliveWatchdog; /// /// This is an array that will be instantiated to the specified data packet size and filled with padding bytes. /// The intent is for this array to be copied for each outgoing data packet and populated with data bytes. /// This avoids the overhead of having to populate new arrays with padding bytes each time. /// private byte[] SenderDataPacketMasterTemplate; private byte[] DataPacketToSend; private bool TerminateSend = false; /// /// Tracks the number of data bytes that have currently been added to the outbound packet. /// private int NumUserDataBytesAddedToCurrentPacket = 0; /// /// The byte value used to pad a packet in order to meet its 128-byte or 1024-byte required length. /// public byte PaddingByte; /// /// When using this XModem to send data, the official specification requires that EOT is transmitted to signal the end of file. /// However, some programs may require a different byte value, such as EOF instead. This allows the user to specify /// a custom byte value to transmit to the Receiver when the file is complete. /// public byte EndOfFileByteToSend; /// /// Defines a master outbound data packet that will be filled with padding bytes. /// The purpose of this template is to be copied for each new outbound packet and subsequently /// populated with actual data. /// private void DefineDataPacketTemplate() { int dataPacketSize; if (_Variant == Variants.XModem1K) dataPacketSize = _Packet1024NominalSize; else dataPacketSize = _Packet128NominalSize; SenderDataPacketMasterTemplate = new byte[dataPacketSize]; // Fill the template with padding bytes for (int k = 0; k < SenderDataPacketMasterTemplate.Length; k++) SenderDataPacketMasterTemplate[k] = PaddingByte; } private byte _BlockNumToSend = 1; public byte BlockNumToSend { get { return _BlockNumToSend; } } /// /// Initializes the modem send process. /// /// /// Optional argument. /// If provided, this is the file that should be transmitted in its entirety. /// The EndOfFile byte is automatically transmitted once this array is finished. /// If omitted, the file may be sent piece-by-piece using the AddToOutboundPacket() method. /// /// /// The number of data bytes successfully transmitted. /// public int Send(byte[] dataToSend = null) { // Initialize control variables _TerminationReason = TerminationReasonEnum.TransferStillActiveNotTerminated; Aborted = false; _BlockNumToSend = 1; // Current outbound block number _SenderConsecutiveRetryAttempts = 0; PacketSuccessfullySent = false; DataPacketToSend = null; NumUserDataBytesAddedToCurrentPacket = 0; _TotalUserDataBytesPacketized = 0; _TotalUserDataBytesSent = 0; _NumCancellationBytesReceived = 0; TerminateSend = false; EndOfFileAcknowledgementReceived = false; SenderInitialized = true; // Ensures that this method is called first before AddToOutboundPacket() WaitForResponseFromReceiver.Reset(); CurrentState = States.SenderAwaitingFileInitiation; // Open port if it isn't open already if (Port.IsOpen == false) Port.Open(); Port.DiscardInBuffer(); Port.DiscardOutBuffer(); if (ReceiverStillAliveWatchdog == null) ReceiverStillAliveWatchdog = new Timer(ReceiverStillAliveWatchdogRoutine, null, SendInactivityTimeoutMillisec, SendInactivityTimeoutMillisec); else ReceiverStillAliveWatchdog.Change(SendInactivityTimeoutMillisec, SendInactivityTimeoutMillisec); if (SenderPacketResponseWatchdog == null) SenderPacketResponseWatchdog = new Timer(SenderPacketResponseWatchdogRoutine, null, SenderPacketRetryTimeoutMillisec, SenderPacketRetryTimeoutMillisec); else SenderPacketResponseWatchdog.Change(SenderPacketRetryTimeoutMillisec, SenderPacketRetryTimeoutMillisec); // Attach event handler Port.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived); // Wait here for file initiation byte to be received from Receiver WaitForResponseFromReceiver.WaitOne(); WaitForResponseFromReceiver.Reset(); if (dataToSend != null && Aborted == false) { AddToOutboundPacket(dataToSend); if (TerminateSend == false) EndFile(); return _TotalUserDataBytesSent; } else return 0; } private int _TotalUserDataBytesPacketized = 0; /// /// Returns the total number of user data bytes that have been incorporated into outbound packets since the current /// send session was started. This counts data bytes that have already been successfully transmitted as well as /// those data bytes that have been added to a packet that is still waiting for completion. Header bytes, padding bytes, /// checksum bytes (any overhead incurred by the XModem infrastructure) are NOT included. /// /// This allows the user, for example, to calculate the proper file offset if reading an extremely large file /// from persistent storage. /// public int TotalUserDataBytesPacketized { get { return _TotalUserDataBytesPacketized; } } private int _TotalUserDataBytesSent = 0; /// /// Returns the total number of user data bytes that have been successfully transmitted since the current send session /// was started. Only data bytes are included in this count. Header bytes, padding bytes, checksum bytes (any overhead /// incurred by the XModem infrastructure) are NOT included. /// /// When the transfer has terminated, this can also tell the user if the file was successfully sent in its entirety. /// public int TotalUserDataBytesSent { get { return _TotalUserDataBytesSent; } } /// /// Sentinel variable that verifies that Send() method is called before AddToOutboundPacket(). /// private bool SenderInitialized = false; /// /// Assembles one or more packets from the bytes passed to this method and automatically transmits them when the /// packet size has been satisfied. /// /// The number of bytes supplied by the user during each call may be completely arbitrary. Supplied arguments may be /// extremely short or extremely long. This method automatically parses the supplied byte arrays and builds packets /// on the fly. Once enough data has been received for a packet, that packet is automatically transmitted. /// /// This method is useful when reading a very large file piece-by-piece from a source, and for packetizing those /// pieces automatically. /// /// /// The data bytes that should be added to the current pending packet. This array may be any length. /// /// /// The number of data bytes successfully sent during this particular method call. /// This is 0 if the packet is still too short to send using the data bytes collected thus far. By knowing how many data /// bytes were successfully transmitted during this method invocation, the user can calculate the correct file offset /// if the source file is extremely lage and is being read from persistent storage, for example. /// /// Note that only the data bytes successfully transmitted during THIS method call are counted. Use the /// TotalUserDataBytesSent property to keep track of the accumulated total of all user data bytes sent during the /// current send session. /// public int AddToOutboundPacket(byte[] dataToSend) { // Tracks the number of user data bytes that have been successfully sent during this method invocation int numUnpaddedDataBytesSentThisCall = 0; // Ensure that the Send() method is first called before this method if (SenderInitialized == false) { throw new ArgumentException("The XModem.Send() method must first be called before XModem.AddToOutboundPacket() is used."); } int dataOffset = 0; while (dataOffset < dataToSend.Length && TerminateSend == false) { // Instantiate outbound data packet if empty if (DataPacketToSend == null) { if (_Variant == Variants.XModem1K) DataPacketToSend = new byte[_Packet1024NominalSize]; else DataPacketToSend = new byte[_Packet128NominalSize]; Array.Copy(SenderDataPacketMasterTemplate, DataPacketToSend, DataPacketToSend.Length); } int numUnparsedDataBytes = dataToSend.Length - dataOffset; int numPacketDataBytesNeeded = DataPacketToSend.Length - NumUserDataBytesAddedToCurrentPacket; int numBytesToAdd; if (numPacketDataBytesNeeded >= numUnparsedDataBytes) numBytesToAdd = numUnparsedDataBytes; else numBytesToAdd = numPacketDataBytesNeeded; Array.Copy(dataToSend, dataOffset, DataPacketToSend, NumUserDataBytesAddedToCurrentPacket, numBytesToAdd); NumUserDataBytesAddedToCurrentPacket += numBytesToAdd; dataOffset += numBytesToAdd; _TotalUserDataBytesPacketized += numBytesToAdd; if (NumUserDataBytesAddedToCurrentPacket >= DataPacketToSend.Length) { TransmitPacket(); // Determine if packet transmission was successful, or the maximum number of retries has been exhausted: if (PacketSuccessfullySent == true) { // If packet successfully transmitted, keep a running tally of data bytes sent out. // Only count actual user-provided data. Padding bytes are not counted. _TotalUserDataBytesSent += numBytesToAdd; numUnpaddedDataBytesSentThisCall += numBytesToAdd; // Reset control variables NumUserDataBytesAddedToCurrentPacket = 0; // Re-initialize a new packet DataPacketToSend = null; } else if (TerminateSend == false) { // Terminal condition if ACK not received even after multiple attempts Abort(); _TerminationReason = TerminationReasonEnum.TooManyRetries; break; } } } return numUnpaddedDataBytesSentThisCall; } private bool PacketSuccessfullySent = false; private int _SenderConsecutiveRetryAttempts = 0; public int SenderConsecutiveRetryAttempts { get { return _SenderConsecutiveRetryAttempts; } } private void TransmitPacket() { // Calculate check-value byte[] checkValueBytes; if (_Variant == Variants.XModemChecksum) checkValueBytes = new byte[] { CheckSum(DataPacketToSend) }; else { ushort checkValueShort = Crc.Crc16(DataPacketToSend, 0, DataPacketToSend.Length, 0); checkValueBytes = UShortToBytes(checkValueShort); } // Determine packet size header byte packetSizeHeader; if (_Variant == Variants.XModem1K) packetSizeHeader = STX; else packetSizeHeader = SOH; PacketSuccessfullySent = false; while (PacketSuccessfullySent == false && _SenderConsecutiveRetryAttempts < MaxSenderRetries && TerminateSend == false) { // Send packet size header Port.WriteByte(packetSizeHeader); // Send block number Port.WriteByte(_BlockNumToSend); // Send block number complement Port.WriteByte((byte)(255 - _BlockNumToSend)); // Send data packet Port.Write(DataPacketToSend, 0, DataPacketToSend.Length); // Update state. Do this just before the Receiver is expected to respond so its response // doesn't fall through the cracks if it replies extremely quickly. WaitForResponseFromReceiver.Reset(); CurrentState = States.SenderPacketSent; // Send check-value. This completes the packet. A response from the Receiver is expected after this. Port.Write(checkValueBytes, 0, checkValueBytes.Length); // Wait for ACK or NAK or CAN WaitForResponseFromReceiver.WaitOne(); WaitForResponseFromReceiver.Reset(); // Once we've gotten a response, the only message expected from the Receiver at this point is a possible cancellation CurrentState = States.SenderAlertForPossibleCancellation; } } /// /// Determines if the minimum number of consecutive cancellation bytes are present in a byte array. /// /// /// The byte array to search for consecutive cancellation bytes. /// /// /// True if cancellation condition has been met. /// False if cancellation request is absent. /// private bool DetectCancellation(byte[] recv) { if (NumCancellationBytesRequired > 0) { int foundIndex = Array.IndexOf(recv, CAN); if (foundIndex > -1) { for (int indexToCheck = foundIndex; indexToCheck < recv.Length; indexToCheck++) { // If more than 1 CAN byte is required for cancellation, they must be consecutive // for cancellation to occur. if (recv[indexToCheck] == CAN) _NumCancellationBytesReceived += 1; else _NumCancellationBytesReceived = 0; if (_NumCancellationBytesReceived >= NumCancellationBytesRequired) { Abort(); _TerminationReason = TerminationReasonEnum.CancelNotificationReceived; return true; // Cancellation detected } } } else { _NumCancellationBytesReceived = 0; } } return false; // No cancellation detected } private bool EndOfFileAcknowledgementReceived = false; /// /// Informs the Receiver that the transmitted file is complete. /// Any pending packets are sent, followed by the end-of-file byte. /// /// /// The number of user data bytes successfully transmitted during this method call. /// public int EndFile() { // Check if there are unsent data bytes remaining in a pending packet, and if so, send the packet // containing the unsent bytes if (NumUserDataBytesAddedToCurrentPacket > 0) { TransmitPacket(); _TotalUserDataBytesSent += NumUserDataBytesAddedToCurrentPacket; } CurrentState = States.SenderAwaitingEndOfFileConfirmation; int numEndOfFileBytesSent = 0; while (EndOfFileAcknowledgementReceived == false && numEndOfFileBytesSent <= MaxSenderRetries) { WaitForResponseFromReceiver.Reset(); Port.WriteByte(EndOfFileByteToSend); numEndOfFileBytesSent += 1; WaitForResponseFromReceiver.WaitOne(); } if (EndOfFileAcknowledgementReceived == true) { Abort(); _TerminationReason = TerminationReasonEnum.EndOfFile; return NumUserDataBytesAddedToCurrentPacket; } else { Abort(); _TerminationReason = TerminationReasonEnum.TooManyRetries; return 0; } } private void SenderPacketResponseWatchdogRoutine(object notUsed) { // , or is expected from the Receiver after each packet sent. // If an appropriate response is not received within the expected timeout, release the WaitHandle which is enforcing // the wait-for-response. This will make the Sender resend the packet once more (until the retry limit is reached). if (CurrentState == States.SenderPacketSent) WaitForResponseFromReceiver.Set(); } private void ResetSenderPacketResponseWatchdog() { if (SenderPacketResponseWatchdog != null) SenderPacketResponseWatchdog.Change(SenderPacketRetryTimeoutMillisec, SenderPacketRetryTimeoutMillisec); } private void ReceiverStillAliveWatchdogRoutine(object notUsed) { Abort(); _TerminationReason = TerminationReasonEnum.NoResponseFromReceiver; } private void ResetReceiverStillAliveWatchdog() { if (ReceiverStillAliveWatchdog != null) ReceiverStillAliveWatchdog.Change(SendInactivityTimeoutMillisec, SendInactivityTimeoutMillisec); } /// /// Removes one or more padding bytes that may exist at the end of a byte array. /// /// /// The byte array to trim. /// /// /// The byte value which defines a padding byte. /// If omitted, this defaults to SUB (byte decimal 26). /// /// /// A byte array without trailing padding bytes (if any are found). /// public byte[] TrimPaddingBytesFromEnd(byte[] input, byte paddingByteToRemove = 26) { int numBytesToDiscard = 0; for (int k = input.Length - 1; k >= 0; k--) { if (input[k] == paddingByteToRemove) numBytesToDiscard += 1; else break; } int numBytesToKeep = input.Length - numBytesToDiscard; byte[] output = new byte[numBytesToKeep]; Array.Copy(input, output, numBytesToKeep); return output; } } // End class } // End namespace