You have reached the legacy GHI Electronics, LLC website, for the new website please visit here. For the new forum please visit here.

This legacy website will be taken offline at the end of this year. If there is anything that you would like to archive and save for future reference please do so.

XMODEM for .NET Micro Framework by Iggmoe

Apr. 3, 2013   |   Snippet   |   Licensed as Apache 2.0   |   3817 views

Note: This version of XMODEM is designed to be run on a .NET Micro Framework device. A full .NET Framework PC version can also be found on Codeshare.

This is a fairly complete implementation of the XModem protocol that supports XModem-Checksum, XModem-CRC, and XModem-1K. Send and Receive have been tested on HyperTerminal and TeraTerm, and the code has been written to be compatible with as many terminal emulator programs as possible. Various public properties have been exposed to allow the user to specify custom timeouts, retry limits, packet sizes, etc. Extensive comments and usage examples throughout.

This was written to adhere as closely as possible to Chuck Forsberg's documentation, but also incorporates some later de-facto additions like file transfer cancellation and other common features.

Compatible with NETMF 4.2 but may be adapted for NETMF 4.1 with namespace changes.

Primary references:
http://www.textfiles.com/programming/ymodem.txt
http://wiki.synchro.net/ref:xmodem

Comments or questions?   Discuss on the forum.



Author Version Date
Iggmoe 4 06/05 '18 at 03:41pm
Iggmoe 3 02/20 '17 at 10:48pm
Iggmoe 2 11/15 '13 at 02:55pm
Iggmoe 1 04/03 '13 at 05:25pm
1 — Source
  1. // Required references:
  2. // GHI.Premium.System
  3. // Microsoft.SPOT.Hardware
  4. // Microsoft.SPOT.Hardware.SerialPort
  5. // Microsoft.SPOT.Native
  6. // mscorlib
  7. // System.IO
  8.  
  9. using System;
  10. using Microsoft.SPOT;
  11. using Microsoft.SPOT.Hardware;
  12. using GHI.Premium.System;
  13. using System.IO;
  14. using System.IO.Ports;
  15. using System.Threading;
  16.  
  17. // SUMMARY:
  18. // Embedded version of XMODEM based on .NET Micro Framework 4.2.
  19. //
  20. // Author:
  21. // Adrian Abordo
  22. // adrian.abordo@iggmoe.com
  23. //
  24. // FEATURES:
  25. // Sender and Receive cancel a file transfer upon receipt of a single <CAN> byte, for compatibility with terminals that only send 1 <CAN>
  26. // byte per cancellation, like TeraTerm.
  27. //
  28. // When Sender or Receiver wishes to cancel a file transfer, they send multiple <CAN> bytes, for compatibility with those terminals that
  29. // require consecutive <CAN> bytes in order to cancel.
  30. //
  31. // An XModem-CRC or XModem-1K Receiver automatically reverts to the older XModem-Checksum if the Sender does not support the newer formats,
  32. // as outlined in Chuck Forsberg's official documentation.
  33. //
  34. // An XModem-1K Receiver can accept data packets that are a mixture of 128-bytes and/or 1024-bytes long. Chuck Forsberg's official documentation
  35. // allows XModem-1K Senders to transmit data packets of either length, so for maximum compatibility with various implementations, this
  36. // XModem-1K Receiver is flexible as well.
  37. //
  38. // However, to accomodate XModem-1K Receiver implementations that are not capable of receiving packets with different lengths, this XModem-1K
  39. // Sender always sends 1024 data bytes per packet.
  40. //
  41. // Receiver accepts both <EOT> and <EOF> as file termination bytes. The official documentation specifies <EOT> to indicate end of file,
  42. // but some MS-DOS and Microsoft implementations of XModem send <EOF> instead. Both values are accepted for maximum compatibility.
  43. //
  44. // This Sender uses <EOT> to indicate end of file. However, a custom byte value can be specified via EndOfFileByteToSend.
  45. //
  46. // This Sender automatically updates its variant (XModem-1K, XModem-CRC or XModem-Checksum) according to the file initiation byte received. This
  47. // allows it to upgrade to a newer variant or downgrade to an older one according to the variant requested by the Receiver.
  48. //
  49. // TrimPaddingBytesFromEnd() can be used to remove trailing padding bytes that are normally added to the end of the file to create a uniform packet
  50. // length. Removing trailing padding bytes is recommended to ensure that the received file is identical byte-for-byte to its original version.
  51.  
  52. namespace YOUR_NAMESPACE_HERE
  53. {
  54. public class XMODEM
  55. {
  56. // TERMINAL PROGRAM OBSERVATIONS:
  57. //
  58. // TeraTerm UTF-8 Pro:
  59. // XModem-1K ALWAYS sends 1024 data bytes/packet.
  60. // When Receiver cancels, only 1 <CAN> byte is sent. The <CAN> byte is sent as a standalone byte outside a packet. The Sender simply stops transmitting
  61. // at the nearest whole packet, and no acknowledgement is returned.
  62. // When Sender cancels, no control bytes are sent. Transmission simply stops at the nearest whole packet. Only the Receiver interpacket timeout will abort
  63. // the transfer on the Receiver side.
  64. //
  65. // HyperTerminal 7.0:
  66. // XModem-1K USUALLY sends 1024 data bytes/packet. However, it can also switch to 128-byte packets if the portion to send is short.
  67. // When Receiver cancels, 5 <CAN> bytes are sent. The Sender simply stops transmitting at the nearest whole packet, and no acknowledgement is returned.
  68. // When Sender cancels, 5 <CAN> bytes are sent. The Receiver does not acknowledge. (An <ACK> is sent, but it's actually in response to the final
  69. // whole packet sent, not the cancellation notification.)
  70. //
  71. // This Implementation:
  72. // For both Sender and Receiver, requesting a cancellation is done by sending <CAN> 5 times. If the Sender or Receiver receives at least 1 <CAN> byte,
  73. // transfer will abort. If the Sender receives the cancellation, it will stop at the nearest whole packet. No acknowledgement is sent under any
  74. // circumstances by any party, in response to a cancellation. When transmitting over a a long distance noisy channel, such as a telephone line,
  75. // aborting on a single <CAN> byte can be problemmatic due to other standalone control bytes possibly being corrupted into <CAN> and triggering an
  76. // unintended abort. However, this implementation is intended to be operated by devices that are directly connected via a serial cable, and so the risk
  77. // of byte corruption is much less. Aborting on a single <CAN> allows this implementation to respond to terminal software that only sends 1 <CAN>
  78. // like TeraTerm. However, if this is problematic, NumCancellationBytesRequired can be used to set the minimum number of cancellation bytes that
  79. // will result in cancellation.
  80. //
  81. // On G120 Processor, largest byte array size permissible = 786364 bytes
  82.  
  83.  
  84. // *********************************** BEGIN: RECEIVER CUSTOMIZABLE PARAMETERS ***********************************
  85.  
  86. /// <summary>
  87. /// The Receiver is responsible for initiating a file transfer.
  88. /// It does this by sending a FileInitiationByte, which is NAK for XModem-Checksum and C for XModem-CRC and XModem-1K.
  89. /// If the receiver has sent a FileInitiationByte, yet it has not received the first packet within the timeout specified
  90. /// below, it should resend the FileInitiationByte.
  91. /// </summary>
  92. public int ReceiverFileInitiationRetryMillisec = 250;
  93.  
  94. /// <summary>
  95. /// This is the maximum number of times the Receiver will send the FileInitiationByte if the Sender has not sent the first packet.
  96. /// If this limit is reached, what happens next will depend on the Receiver's variant.
  97. ///
  98. /// SITUATION 1A: The Receiver is requesting XModem-CRC or XModem-1K --AND-- FallBackAllowed == True
  99. /// A Receiver wishing to use the newer CRC or 1K protocol requests a file transfer by sending <C>.
  100. /// The Sender is supposed to respond by sending the first packet. However, if the Sender does not support either of these new variants,
  101. /// it will not recognize the <C> and will not respond. If the maximum number of file initiation attempts is reached, the Receiver
  102. /// will fall back to the older XModem-Checksum protocol and send <NAK> as its FileInitiationByte instead.
  103. /// 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
  104. /// transfer is aborted.
  105. ///
  106. /// SITUATION 1B: The Receiver is requesting XModem-CRC or XModem-1K --AND-- FallBackAllowed == False
  107. /// If this limit is reached and the first packet has not been received, the Receiver will abort the file transfer.
  108. ///
  109. /// SITUATION 2: The Receiver is requesting XModem-Checksum
  110. /// If this limit is reached and the first packet has not been received, the Receiver will abort the file transfer.
  111. /// </summary>
  112. public int ReceiverFileInitiationMaxAttempts = 240;
  113.  
  114. /// <summary>
  115. /// XModem-CRC and XModem-1K evolved from XModem-Checksum.
  116. /// The official specification states that a Receiver which is requesting XModem-CRC or XModem-1K must have the
  117. /// ability to "fall back" to XModem-Checksum in case the Sender does not support newer formats.
  118. ///
  119. /// When True, this flag allows fallback to occur.
  120. /// When False, fallback will not occur, and the Receiver will simply abort the file transfer if the newer protocols aren't supported.
  121. /// 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
  122. /// already configured for XModem-Checksum.
  123. /// </summary>
  124. public bool ReceiverFallBackAllowed = true;
  125.  
  126. /// <summary>
  127. /// If the Receiver is expecting data from the Sender, this is the maximum amount of time it will wait before sending NAK to prompt
  128. /// the Sender to either resend the packet or finish the file.
  129. /// </summary>
  130. public int ReceiverTimeoutMillisec = 10000;
  131.  
  132. /// <summary>
  133. /// If the Sender has not responded to repeated NAK nagging after this number of attempts, then the Receiver should abort the transfer.
  134. /// </summary>
  135. public int ReceiverMaxConsecutiveRetries = 10;
  136.  
  137. private int _NumCancellationBytesRequired = 1;
  138. /// This is the minimum number of cancellation bytes that a Sender or Receiver must receive in order to cancel the file transfer.
  139. /// If 0 or less is specified, CAN bytes are ignored and will not result in cancellation, in which case cancellation will depend
  140. /// entirely on the timeout mechanism.
  141. public int NumCancellationBytesRequired
  142. {
  143. get { return _NumCancellationBytesRequired; }
  144. set
  145. {
  146. _NumCancellationBytesRequired = value;
  147. }
  148. }
  149.  
  150. private int _NumCancellationBytesToSend = 5;
  151. /// <summary>
  152. /// This is the number of CAN bytes that a Sender or Receiver will transmit to the connected party if the user requests a cancellation.
  153. /// </summary>
  154. public int NumCancellationBytesToSend
  155. {
  156. get { return _NumCancellationBytesToSend; }
  157. set
  158. {
  159. if (value >= 0)
  160. _NumCancellationBytesToSend = value;
  161. else
  162. NumCancellationBytesToSend = 5;
  163. }
  164. }
  165.  
  166. // *********************************** END: RECEIVER CUSTOMIZABLE PARAMETERS ***********************************
  167.  
  168. // *********************************** BEGIN: SENDER CUSTOMIZABLE PARAMETERS ***********************************
  169.  
  170. /// <summary>
  171. /// 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.
  172. /// This is the total number of times the Sender will resend the same packet whenever consecutive NAKs are received.
  173. /// If this limit is reached and the Receiver is still sending NAK, the communication channel is assumed to be terminally unreliable
  174. /// and file transfer should abort.
  175. /// </summary>
  176. public int MaxSenderRetries = 10;
  177.  
  178. /// <summary>
  179. /// The Receiver is supposed to validate each packet with ACK or NAK.
  180. /// If validation has not been received from the Receiver for this amount of time, the packet is resent.
  181. /// </summary>
  182. public int SenderPacketRetryTimeoutMillisec = 15000;
  183.  
  184. /// <summary>
  185. /// If the Sender has not heard ANYTHING from the Receiver after this amount of time has elapsed, the file transfer is aborted.
  186. /// </summary>
  187. public int SendInactivityTimeoutMillisec = 60000;
  188.  
  189. // *********************************** END: SENDER CUSTOMIZABLE PARAMETERS ***********************************
  190.  
  191. // *********************************** BEGIN: COMMON CUSTOMIZABLE PARAMETERS ***********************************
  192.  
  193. // ASCII codes for common character constants.
  194. // These are exposed as public fields in case the user needs to customize them with nonstandard values.
  195. public byte SOH = 1; // Sender begins each 128-byte packet with this header
  196. public byte STX = 2; // Sender begins each 1024-byte packet with this header
  197. public byte ACK = 6; // Receiver sends this to indicate packet was received successfully with no errors
  198. public byte NAK = 21; // Receiver sends this to initiate XModem-Checksum file transfer -- OR -- indicate packet errors
  199. public byte C = 67; // Receiver sends this to initiate XModem-CRC or XModem-1K file transfer
  200. public byte EOT = 4; // Sender sends this to mark the end of file. Receiver must acknowledge receipt of this byte with <ACK>, otherwise Sender resends <EOT>
  201. public byte SUB = 26; // This is used as a padding byte in the original specification
  202. public byte CAN = 24; // [Commonly used but unofficial] Sender or Receiver sends this byte to abort file transfer
  203. public byte EOF = 26; // [Commonly used but unofficial] MS-DOS version of <EOT>
  204.  
  205. /// <summary>
  206. /// Defines the number of data bytes in a nominal 1024-byte packet.
  207. /// This allows the user to redefine a custom packet size for non-standard XModem implementations.
  208. /// </summary>
  209. private int _Packet1024NominalSize = 1024;
  210. public int Packet1024NominalSize
  211. {
  212. get { return _Packet1024NominalSize; }
  213. set
  214. {
  215. if (value > 0)
  216. {
  217. _Packet1024NominalSize = value;
  218. DefineDataPacketTemplate();
  219. }
  220. }
  221. }
  222.  
  223. /// <summary>
  224. /// Defines the number of data bytes in a nominal 128-byte packet.
  225. /// This allows the user to redefine a custom packet size for non-standard XModem implementations.
  226. /// </summary>
  227. private int _Packet128NominalSize = 128;
  228. public int Packet128NominalSize
  229. {
  230. get { return _Packet128NominalSize; }
  231. set
  232. {
  233. if (value > 0)
  234. {
  235. _Packet128NominalSize = value;
  236. DefineDataPacketTemplate();
  237. }
  238. }
  239. }
  240.  
  241. // *********************************** END: COMMON CUSTOMIZABLE PARAMETERS ***********************************
  242.  
  243. /// <summary>
  244. /// CONSTRUCTOR.
  245. /// </summary>
  246. /// <param name="port">SerialPort to use when sending or receiving.</param>
  247. /// <param name="variant">
  248. /// The particular flavor of XModem to use.
  249. /// See Variants enumeration for a description of each XModem variant.
  250. /// </param>
  251. /// <param name="paddingByte">
  252. /// If sending, this is the byte value that will be used to pad a packet if the data being sent is shorter than
  253. /// the required packet length. If omitted, defaults to SUB (byte decimal 26).
  254. /// </param>
  255. /// <param name="endOfFileByteToSend">
  256. /// If sending, this is the byte value that will be sent to indicate that the end-of-file has been reached.
  257. /// Of omitted, defaults to EOT (byte decimal 4). Some XModem receiver implementations may require that EOF
  258. /// is used instead to indicate end-of-file.
  259. /// </param>
  260. public XMODEM(SerialPort port, Variants variant, byte paddingByte = 26, byte endOfFileByteToSend = 4)
  261. {
  262. // Field initialization
  263. PaddingByte = paddingByte;
  264. EndOfFileByteToSend = endOfFileByteToSend;
  265.  
  266. // Property Initialization
  267. Port = port;
  268. Variant = variant;
  269. }
  270.  
  271. /// <summary>
  272. /// Cancels the file transfer operation (send or receive) and notifies the other party of the
  273. /// cancellation by transmitting CAN bytes.
  274. /// </summary>
  275. public void CancelFileTransfer()
  276. {
  277. // Abort what we're currently doing
  278. Abort();
  279.  
  280. // Send cancellation bytes
  281. for (int k = 1; k <= _NumCancellationBytesToSend; k++)
  282. {
  283. Port.WriteByte(CAN);
  284. }
  285.  
  286. _TerminationReason = TerminationReasonEnum.UserCancelled;
  287. }
  288.  
  289. /// <summary>
  290. /// Describes the various flavors of XModem.
  291. ///
  292. /// XModemChecksum: This is the original, classic version of XModem. It is also the slowest and most error-prone.
  293. /// 128 data bytes per packet with checksum error-detection
  294. /// 132 bytes/packet total = 3 header bytes + 128 data bytes + 1 error-detection byte
  295. ///
  296. /// XModemCRC: This similar to the original, except with CRC-16 error detection instead of a simple checksum for more reliability.
  297. /// 128 data bytes per packet with CRC-16 error-detection
  298. /// 133 bytes/packet total = 3 header bytes + 128 data bytes + 2 error-detection bytes
  299. ///
  300. /// XModem1K: This is an updated version of XModem-CRC, expanded to 1024 data bytes (though 128 data bytes are still accepted).
  301. /// 128 and/or 1024 data bytes per packet and CRC-16 error-detection
  302. /// 133 bytes/packet total = 3 header bytes + 128 data bytes + 2 error-detection bytes
  303. /// 1029 bytes/packet total = 3 header bytes + 1024 data bytes + 2 error-detection bytes
  304. /// </summary>
  305. public enum Variants
  306. {
  307. XModemChecksum,
  308. XModemCRC,
  309. XModem1K
  310. };
  311.  
  312. private Variants _Variant;
  313. /// <summary>
  314. /// Gets/sets the XModem flavor that this implementation adheres to.
  315. /// </summary>
  316. public Variants Variant
  317. {
  318. get { return _Variant; }
  319. set
  320. {
  321. _Variant = value;
  322. DefineDataPacketTemplate(); // In case user wants to send data
  323. }
  324. }
  325.  
  326. /// <summary>
  327. /// XModem's possible operational states.
  328. /// </summary>
  329. private enum States
  330. {
  331. Inactive, // The object is neither Sending nor Receiving
  332. ReceiverFileInitiation, // Receiver is sending the file initiation byte at regular intervals
  333. ReceiverHeaderSearch, // Receiver is expecting SOH or STX packet header
  334. ReceiverBlockNumSearch, // Receiver is expecting the block number
  335. ReceiverBlockNumComplementSearch, // Receiver is expecting the block number complement
  336. ReceiverDataBytesSearch, // Receiver is populating data bytes
  337. ReceiverErrorCheckSearch, // Receiver is expecting 1-byte or 2-byte check value(s)
  338. SenderAwaitingFileInitiation, // Sender is expecting file transmission request from Receiver
  339. SenderPacketSent, // Sender has sent a packet and is waiting for Receiver to validate it
  340. SenderAlertForPossibleCancellation, // Sender is not expecting anything in particular but can respond to a cancellation request if needed
  341. SenderAwaitingEndOfFileConfirmation // Sender has transmitted the end-of-file byte and is waiting for Receiver to acknowledge receipt of that byte
  342. }
  343.  
  344. private States CurrentState = States.Inactive;
  345.  
  346. /// <summary>
  347. /// Describes how the current XModem session has ended.
  348. /// </summary>
  349. public enum TerminationReasonEnum
  350. {
  351. TransferStillActiveNotTerminated, // File transfer is still active and has not terminated yet
  352. UserCancelled, // User has cancelled the file transfer
  353. EndOfFile, // End-of-file was reached. This is the ideal outcome when sending or receiving.
  354. FileInitiationTimeout, // Receiver has repeatedly requested file transfer to begin, but Sender has not responded
  355. CancelNotificationReceived, // Transfer aborted because cancellation bytes were detected from the Sender or Receiver
  356. TooManyRetries, // Too many erroneous packets have been sent or received. Indicates corruption or total communication loss.
  357. NoResponseFromReceiver // When Sending a file, the Receiver has become completely silent for an extended period.
  358. }
  359.  
  360. public TerminationReasonEnum _TerminationReason = TerminationReasonEnum.TransferStillActiveNotTerminated;
  361. /// <summary>
  362. /// Describes under what conditions the file transfer has terminated.
  363. /// </summary>
  364. public TerminationReasonEnum TerminationReason
  365. {
  366. get { return _TerminationReason; }
  367. }
  368.  
  369. /// <summary>
  370. /// PacketReceived event handler.
  371. /// </summary>
  372. /// <param name="sender">The XMODEM object that raised the event.</param>
  373. /// <param name="packet">The complete, validated data packet received.</param>
  374. /// <param name="endOfFileDetected">
  375. /// Boolean flag that indicates if this packet is the final packet expected.
  376. /// True if the end-of-file byte was received, and the current packet is therefore the last one.
  377. /// False if the end-of-file byte has not been received, and more packets are still expected.
  378. /// </param>
  379. public delegate void PacketReceivedEventHandler(XMODEM sender, byte[] packet, bool endOfFileDetected);
  380.  
  381. /// <summary>
  382. /// Raised whenever a complete packet has been received.
  383. /// </summary>
  384. public event PacketReceivedEventHandler PacketReceived;
  385.  
  386. /// <summary>
  387. /// Communication port that will be used to send and receive.
  388. /// </summary>
  389. public SerialPort Port;
  390.  
  391. /// <summary>
  392. /// Performs blocking when the Receive() method is called by the user.
  393. /// </summary>
  394. private ManualResetEvent ReceiverUserBlock = new ManualResetEvent(false);
  395.  
  396. private int _NumCancellationBytesReceived = 0;
  397. /// <summary>
  398. /// Tracks the number of CAN bytes that have currently been received by this Sender or Receiver.
  399. /// </summary>
  400. public int NumCancellationBytesReceived
  401. {
  402. get { return _NumCancellationBytesRequired; }
  403. }
  404.  
  405. /// <summary>
  406. /// MemoryStream used to store all data received if user wants to receive data in one big lump.
  407. /// This remains null if user wants to receive data packet-by-packet instead.
  408. /// </summary>
  409. private MemoryStream AllDataReceivedBuffer;
  410.  
  411. /// <summary>
  412. /// Initiates the file receive process. This method blocks until the file transfer has terminated.
  413. /// </summary>
  414. /// <param name="allDataReceivedBuffer">
  415. /// Optional MemoryStream object, which may be omitted.
  416. /// By instantiating an empty MemoryStream object and supplying it as an argument, the user can collect all data
  417. /// received into a single data structure and process it in one big lump once the transfer has finished.
  418. /// This is NOT recommended if the expected file size can exceed the contiguous memory capacity of the device.
  419. /// If the expected file size is large, subscribing to the PacketReceived event is recommended so that processing
  420. /// can be done one packet at a time within the available memory.
  421. /// </param>
  422. /// <returns>
  423. /// A TerminationReasonEnum that describes under what conditions the receive process has terminated. This may
  424. /// be due to a successful EndOfFile being reached, or due to timeouts, errors, etc.
  425. /// </returns>
  426. public TerminationReasonEnum Receive(MemoryStream allDataReceivedBuffer = null)
  427. {
  428. // Initialize control variables
  429. _TerminationReason = TerminationReasonEnum.TransferStillActiveNotTerminated;
  430. Aborted = false;
  431. BlockNumExpected = 1;
  432. _NumFileInitiationBytesSent = 0;
  433. _NumCancellationBytesReceived = 0;
  434. Remainder = new byte[0];
  435. DataPacketNumBytesStored = 0;
  436. ExpectingFirstPacket = true;
  437. ValidPacketReceived = false;
  438.  
  439. // Define file initiation byte according to variant
  440. if (_Variant == Variants.XModemChecksum)
  441. FileInitiationByteToSend = NAK;
  442. else
  443. FileInitiationByteToSend = C;
  444.  
  445. // Initialize state
  446. CurrentState = States.ReceiverFileInitiation;
  447.  
  448. // Open port if it isn't open already
  449. if (Port.IsOpen == false)
  450. Port.Open();
  451.  
  452. // Clear out serial port buffers
  453. Port.DiscardInBuffer();
  454. Port.DiscardOutBuffer();
  455.  
  456. // If user wants to get all data in one big lump, initialize the buffer with whatever MemoryStream object
  457. // is passed. If a memorystream is omitted, this defaults to null, which means received data
  458. // will not be stored.
  459. AllDataReceivedBuffer = allDataReceivedBuffer;
  460.  
  461. // Attach event handler
  462. Port.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived);
  463.  
  464. // Begin file initiation
  465. if (ReceiverFileInitiationTimer == null)
  466. ReceiverFileInitiationTimer = new Timer(ReceiverFileInitiationRoutine, null, 0, ReceiverFileInitiationRetryMillisec);
  467. else
  468. ReceiverFileInitiationTimer.Change(0, ReceiverFileInitiationRetryMillisec);
  469.  
  470. // Block here
  471. ReceiverUserBlock.Reset();
  472. ReceiverUserBlock.WaitOne();
  473. ReceiverUserBlock.Reset();
  474.  
  475. return TerminationReason;
  476. }
  477.  
  478. // The byte value that a Receiver sends when requesting file transfer:
  479. // XModem Checksum = <NAK>
  480. // XModem CRC = <C>
  481. // XModem 1K = <C>
  482. private byte FileInitiationByteToSend;
  483.  
  484. /// <summary>
  485. /// Sends the file initiation byte at regular intervals to request start of transfer from Sender.
  486. /// </summary>
  487. private Timer ReceiverFileInitiationTimer;
  488.  
  489. /// <summary>
  490. /// Timer callback for file initiation timer.
  491. /// </summary>
  492. /// <param name="notUsed"></param>
  493. private void ReceiverFileInitiationRoutine(object notUsed)
  494. {
  495. Port.WriteByte(FileInitiationByteToSend);
  496. NumFileInitiationBytesSent += 1;
  497. }
  498.  
  499. // Tracks how many file initiation bytes have already been sent to the Sender
  500. private int _NumFileInitiationBytesSent = 0;
  501. public int NumFileInitiationBytesSent
  502. {
  503. get { return _NumFileInitiationBytesSent; }
  504. private set
  505. {
  506. _NumFileInitiationBytesSent = value;
  507.  
  508. // Determine if Receiver should fall back to an older variant
  509. if (_NumFileInitiationBytesSent > ReceiverFileInitiationMaxAttempts)
  510. {
  511. if (FileInitiationByteToSend == C && ReceiverFallBackAllowed == true)
  512. {
  513. // Fall back to older variant
  514. FileInitiationByteToSend = NAK;
  515. _NumFileInitiationBytesSent = 0;
  516. }
  517. else
  518. {
  519. Abort();
  520. _TerminationReason = TerminationReasonEnum.FileInitiationTimeout;
  521. ReceiverUserBlock.Set();
  522. }
  523. }
  524. }
  525. }
  526.  
  527. // Indicates whether a valid packet has been received and is fit to be forwarded to user
  528. private bool ValidPacketReceived = false;
  529.  
  530. /// <summary>
  531. /// State machine dispatcher.
  532. /// </summary>
  533. /// <param name="sender"></param>
  534. /// <param name="e"></param>
  535. private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
  536. {
  537. SerialPort sp = sender as SerialPort;
  538. int numBytes = sp.BytesToRead;
  539. byte[] recv = new byte[numBytes];
  540. sp.Read(recv, 0, numBytes);
  541.  
  542. if (numBytes > 0)
  543. {
  544. switch (CurrentState)
  545. {
  546. // RECEIVER STATES:
  547. case States.ReceiverFileInitiation:
  548. // A response was received from the Sender after 1 or more file initiation bytes have been sent.
  549.  
  550. // Halt the file initiation sender
  551. if (ReceiverFileInitiationTimer != null)
  552. ReceiverFileInitiationTimer.Change(Timeout.Infinite, Timeout.Infinite);
  553.  
  554. // Start the timeout/NAK watchdog
  555. if (ReceiverNAKWatchdog == null)
  556. ReceiverNAKWatchdog = new Timer(NAKNag, null, ReceiverTimeoutMillisec, ReceiverTimeoutMillisec);
  557. else
  558. ReceiverNAKWatchdog.Change(ReceiverTimeoutMillisec, ReceiverTimeoutMillisec);
  559.  
  560. // Advance to the next stage and process the current data received
  561. CurrentState = States.ReceiverHeaderSearch;
  562. ReceiverPacketBuilder(recv);
  563. break;
  564.  
  565. case States.ReceiverHeaderSearch:
  566. case States.ReceiverBlockNumSearch:
  567. case States.ReceiverBlockNumComplementSearch:
  568. case States.ReceiverDataBytesSearch:
  569. case States.ReceiverErrorCheckSearch:
  570. ResetNAKWatchdog();
  571. ReceiverPacketBuilder(recv);
  572. break;
  573.  
  574.  
  575. // SENDER STATES:
  576. case States.SenderAwaitingFileInitiation:
  577. ResetReceiverStillAliveWatchdog();
  578. if (DetectCancellation(recv) == false)
  579. {
  580. // Determine if file initiation byte for CRC or Checksum error-checking received.
  581. if (Array.IndexOf(recv, C) > -1 || Array.IndexOf(recv, NAK) > -1)
  582. {
  583. // If user originally specified older XModem-Checksum but <C> was received instead,
  584. // upgrade variant to new XModem-1K (which can handle both 128 and 1024 data bytes)
  585. // for maximum flexibility.
  586. if (_Variant == Variants.XModemChecksum && Array.IndexOf(recv, C) > -1)
  587. Variant = Variants.XModem1K;
  588.  
  589. if (Array.IndexOf(recv, NAK) > -1)
  590. Variant = Variants.XModemChecksum;
  591.  
  592. CurrentState = States.SenderAlertForPossibleCancellation;
  593. WaitForResponseFromReceiver.Set();
  594. }
  595. }
  596. break;
  597.  
  598. case States.SenderPacketSent:
  599. ResetReceiverStillAliveWatchdog();
  600. if (DetectCancellation(recv) == false)
  601. {
  602. if (Array.IndexOf(recv, ACK) > -1)
  603. {
  604. // ACK received
  605. ResetSenderPacketResponseWatchdog();
  606. _SenderConsecutiveRetryAttempts = 0;
  607. PacketSuccessfullySent = true;
  608. _BlockNumToSend += 1;
  609. WaitForResponseFromReceiver.Set();
  610. }
  611. else if (Array.IndexOf(recv, NAK) > -1)
  612. {
  613. // NAK received
  614. ResetSenderPacketResponseWatchdog();
  615. _SenderConsecutiveRetryAttempts += 1;
  616. WaitForResponseFromReceiver.Set();
  617. }
  618. }
  619. break;
  620.  
  621. case States.SenderAlertForPossibleCancellation:
  622. ResetReceiverStillAliveWatchdog();
  623. DetectCancellation(recv);
  624. break;
  625.  
  626. case States.SenderAwaitingEndOfFileConfirmation:
  627. ResetReceiverStillAliveWatchdog();
  628. if (DetectCancellation(recv) == false)
  629. {
  630. if (Array.IndexOf(recv, ACK) > -1)
  631. {
  632. // ACK received
  633. EndOfFileAcknowledgementReceived = true;
  634. Abort();
  635. }
  636. }
  637. break;
  638.  
  639. }
  640. }
  641. }
  642.  
  643. /// <summary>
  644. /// Enforces timeouts if no data is received or the packet repeatedly fails the checksum.
  645. /// This timer must be reset within a set amount of time or NAK is transmitted.
  646. /// </summary>
  647. private Timer ReceiverNAKWatchdog;
  648.  
  649. /// <summary>
  650. /// Resets the NAK watchdog.
  651. /// </summary>
  652. private void ResetNAKWatchdog()
  653. {
  654. if (ReceiverNAKWatchdog != null)
  655. ReceiverNAKWatchdog.Change(ReceiverTimeoutMillisec, ReceiverTimeoutMillisec);
  656.  
  657. ReceiverNumConsecutiveNAKSent = 0;
  658. }
  659.  
  660. /// <summary>
  661. /// Timer callback for the NAK watchdog.
  662. /// </summary>
  663. /// <param name="notUsed"></param>
  664. private void NAKNag(object notUsed)
  665. {
  666. SendNAK();
  667. }
  668.  
  669. private void SendNAK()
  670. {
  671. Port.WriteByte(NAK);
  672. ReceiverNumConsecutiveNAKSent += 1;
  673. }
  674.  
  675. private void SendACK()
  676. {
  677. Port.WriteByte(ACK);
  678. ReceiverNumConsecutiveNAKSent = 0;
  679. }
  680.  
  681. // Tracks how many consecutive <NAK> bytes have been sent
  682. public int _ReceiverNumConsecutiveNAKSent = 0;
  683. public int ReceiverNumConsecutiveNAKSent
  684. {
  685. get { return _ReceiverNumConsecutiveNAKSent; }
  686. set
  687. {
  688. _ReceiverNumConsecutiveNAKSent = value;
  689.  
  690. if (_ReceiverNumConsecutiveNAKSent >= ReceiverMaxConsecutiveRetries)
  691. {
  692. Abort();
  693. _TerminationReason = TerminationReasonEnum.TooManyRetries;
  694. ReceiverUserBlock.Set();
  695. }
  696. }
  697. }
  698.  
  699. /// <summary>
  700. /// The expected data packet size as specified by the incoming packet header.
  701. /// </summary>
  702. private int ExpectedDataPacketSize;
  703.  
  704. // Stores the value of a candidate block number
  705. private byte BlockNumReceived;
  706.  
  707. // Stores the value of a candidate block number complement (255 minus block number)
  708. private byte BlockNumComplementCandidateReceived;
  709.  
  710. private bool ExpectingFirstPacket = true;
  711. private byte BlockNumExpected = 1;
  712.  
  713. private byte[] BytesToParse;
  714. private byte[] Remainder = new byte[0];
  715.  
  716. // Stores data bytes received. It is instantiated to be the same length as the expected data packet size.
  717. private byte[] DataPacketReceived;
  718.  
  719. // Keeps track of the number of bytes actually placed inside the current data packet
  720. private int DataPacketNumBytesStored = 0;
  721.  
  722. // Stores the check value. This will contain 1 byte for XModem-Checksum, and 2 bytes for XModem-CRC or XModem-1K.
  723. private byte[] ErrorCheck;
  724.  
  725. private void ReceiverPacketBuilder(byte[] freshBytes)
  726. {
  727. // The DataReceived event is occasionally triggered even when there are no bytes to read, so guard against this
  728. if (freshBytes.Length == 0)
  729. return;
  730.  
  731. // We only want this to run once per DataReceived event and only if we are searching for a valid header byte
  732. if (Remainder.Length > 0 && CurrentState == States.ReceiverHeaderSearch)
  733. BytesToParse = Utility.CombineArrays(Remainder, freshBytes);
  734. else
  735. BytesToParse = freshBytes;
  736.  
  737. // This keeps track of the index positions that searches begin at
  738. int headerByteSearchStartIndex = 0;
  739. int searchStartIndex = 0;
  740.  
  741. // Loop while we are within bounds of the bytes to parse
  742. while (searchStartIndex < BytesToParse.Length && headerByteSearchStartIndex < BytesToParse.Length)
  743. {
  744.  
  745. if (CurrentState == States.ReceiverHeaderSearch)
  746. {
  747. // Empty the remainder if populated, since it should have fulfilled its purpose prior to us
  748. // reaching this point. We don't want the remainder to carry over into the next DataReceived event
  749. // if we are in the header byte search state.
  750. if (Remainder.Length > 0)
  751. Remainder = new byte[0];
  752.  
  753. // Check for file termination tokens
  754. if (BytesToParse[headerByteSearchStartIndex] == EOT ||
  755. BytesToParse[headerByteSearchStartIndex] == EOF)
  756. {
  757. SendACK();
  758. Abort();
  759. _TerminationReason = TerminationReasonEnum.EndOfFile;
  760.  
  761. // If subscribed to, raise packet received event and indicate end of file reached
  762. if (PacketReceived != null && ValidPacketReceived == true && DataPacketReceived != null && DataPacketReceived.Length > 0)
  763. PacketReceived(this, DataPacketReceived, true);
  764.  
  765. ReceiverUserBlock.Set();
  766. return;
  767. }
  768. else
  769. {
  770. // If subscribed to, raise packet received event and indicate end of file NOT yet reached
  771. if (PacketReceived != null && ValidPacketReceived == true && DataPacketReceived != null && DataPacketReceived.Length > 0)
  772. PacketReceived(this, DataPacketReceived, false);
  773. }
  774.  
  775. // Reset packet received validation flag
  776. ValidPacketReceived = false;
  777.  
  778. // Check for cancellation bytes
  779. if (_NumCancellationBytesRequired > 0)
  780. {
  781. // Check if current byte is a cancellation request
  782. if (BytesToParse[headerByteSearchStartIndex] == CAN)
  783. {
  784. _NumCancellationBytesReceived += 1;
  785.  
  786. if (_NumCancellationBytesReceived >= _NumCancellationBytesRequired)
  787. {
  788. // CANCEL THE FILE TRANSFER
  789. Abort();
  790. _TerminationReason = TerminationReasonEnum.CancelNotificationReceived;
  791. ReceiverUserBlock.Set();
  792. return;
  793. }
  794. else
  795. {
  796. // Move on to the next byte
  797. headerByteSearchStartIndex += 1;
  798. continue;
  799. }
  800. }
  801. else
  802. {
  803. // If not a cancellation byte, reset the cancellation byte counter
  804. _NumCancellationBytesReceived = 0;
  805. }
  806. }
  807.  
  808. // Determine if we have a candidate header byte based on our XModem variant
  809. if (_Variant == Variants.XModemChecksum || _Variant == Variants.XModemCRC)
  810. {
  811. // XModem-Checksum and XModem-CRC should always have 128 data byte packets headed by <SOH>.
  812. // Determine the location of this header byte, if it's present.
  813. int foundIndex = Array.IndexOf(BytesToParse, SOH, headerByteSearchStartIndex);
  814.  
  815. if (foundIndex == -1) // No header byte found
  816. {
  817. // Quit this search and ignore the remaining bytes.
  818. // Look for a header byte in the next transmission.
  819. return;
  820. }
  821. else if (foundIndex > -1)
  822. {
  823. // Save the index position where we THINK a valid header byte resides at.
  824. // The candidate header byte may be discovered to be invalid later on, and we
  825. // need an index to return to in case we need to repeat the header search
  826. // starting from the next index.
  827. headerByteSearchStartIndex = foundIndex + 1;
  828.  
  829. // Specify the packet size corresponding to this header
  830. ExpectedDataPacketSize = _Packet128NominalSize;
  831.  
  832. // Move on to the next stage
  833. searchStartIndex = foundIndex + 1;
  834. CurrentState = States.ReceiverBlockNumSearch;
  835. continue;
  836. }
  837. }
  838. else if (_Variant == Variants.XModem1K)
  839. {
  840. // XModem-1K can receive 1024 data byte packets headed by <STX> --OR-- 128 data byte packets headed by <SOH>.
  841. // The official standard allows a Sender to send a mixture of packet sizes, so a Receiver has to look for both.
  842. int packetStartIndexSTX = Array.IndexOf(BytesToParse, STX, headerByteSearchStartIndex);
  843. int packetStartIndexSOH = Array.IndexOf(BytesToParse, SOH, headerByteSearchStartIndex);
  844.  
  845. // Look for the packet header byte
  846. int foundIndex = 0;
  847. if (packetStartIndexSTX > -1 && packetStartIndexSOH > -1)
  848. {
  849. // There is a (slim) possibility that both <SOH> and <STX> bytes may be found.
  850. // If both are found, our candidate packet start index should be the earlier of the two.
  851. // If we're wrong, we can always get to the later one during the next iteration.
  852. if (packetStartIndexSTX <= packetStartIndexSOH)
  853. {
  854. // Header for 1024 data bytes/packet
  855. ExpectedDataPacketSize = _Packet1024NominalSize;
  856. foundIndex = packetStartIndexSTX;
  857. }
  858. else
  859. {
  860. // Header for 128 data bytes/packet
  861. ExpectedDataPacketSize = _Packet128NominalSize;
  862. foundIndex = packetStartIndexSOH;
  863. }
  864. }
  865. else if (packetStartIndexSTX > -1) // Only 1 found
  866. {
  867. // Header for 1024 data bytes/packet
  868. ExpectedDataPacketSize = _Packet1024NominalSize;
  869. foundIndex = packetStartIndexSTX;
  870. }
  871. else if (packetStartIndexSOH > -1) // Only 1 found
  872. {
  873. // Header for 128 data bytes/packet
  874. ExpectedDataPacketSize = _Packet128NominalSize;
  875. foundIndex = packetStartIndexSOH;
  876. }
  877. else
  878. {
  879. // If neither candidate headers were found, quit this search and ignore the remaining bytes.
  880. // Look for a header byte in the next transmission.
  881. return;
  882. }
  883.  
  884. // Save the index position where we THINK a valid header byte resides at.
  885. // The candidate header byte may be discovered to be invalid later on, and we
  886. // need an index to return to in case we need to repeat the header search
  887. // starting from the next index.
  888. headerByteSearchStartIndex = foundIndex + 1;
  889.  
  890. // Move on to the next stage
  891. searchStartIndex = foundIndex + 1;
  892. CurrentState = States.ReceiverBlockNumSearch;
  893. continue;
  894. }
  895. }
  896.  
  897. if (CurrentState == States.ReceiverBlockNumSearch)
  898. {
  899. // The block number should be immediately after the packet header byte
  900. BlockNumReceived = BytesToParse[searchStartIndex];
  901.  
  902. // If the candidate block number is equal to a header byte, there is a slim possibility that what we currently believe
  903. // is the block number may actually be the true header byte, especially if the current candidate header byte is discovered
  904. // to be invalid later. Therefore, we should save the "block number" as a remainder so we can re-examine it later if
  905. // the current header byte canididate does not pan out and another DataReceived event is raised.
  906. if ((_Variant == Variants.XModemChecksum || _Variant == Variants.XModemCRC)
  907. && BlockNumReceived == SOH)
  908. {
  909. Remainder = new byte[] { BlockNumReceived }; // Initialize remainder
  910. }
  911. else if (_Variant == Variants.XModem1K && (BlockNumReceived == SOH || BlockNumReceived == STX))
  912. {
  913. Remainder = new byte[] { BlockNumReceived }; // Initialize remainder
  914. }
  915.  
  916. // Move on to the next stage
  917. searchStartIndex += 1;
  918. CurrentState = States.ReceiverBlockNumComplementSearch;
  919. continue;
  920. }
  921.  
  922. if (CurrentState == States.ReceiverBlockNumComplementSearch)
  923. {
  924. // The complement of the block number should be right after the block number
  925. BlockNumComplementCandidateReceived = BytesToParse[searchStartIndex];
  926.  
  927. // Determine if we have received a valid block number and complement.
  928. // This will determine if we have a valid packet header and can proceed to the next stage.
  929. if (BlockNumComplementCandidateReceived == 255 - BlockNumReceived)
  930. {
  931. // Valid packet header....
  932.  
  933. // The packet header is valid, so we don't need to re-examine the remainder and can therefore
  934. // discard it
  935. if (Remainder.Length > 0)
  936. Remainder = new byte[0];
  937.  
  938. // Instantiate the data packet array which will hold incoming data
  939. DataPacketReceived = new byte[ExpectedDataPacketSize];
  940. DataPacketNumBytesStored = 0;
  941.  
  942. // Move on to the next stage
  943. searchStartIndex += 1;
  944. CurrentState = States.ReceiverDataBytesSearch;
  945. continue;
  946. }
  947. else
  948. {
  949. // Not a valid packet header....
  950.  
  951. // Add the candidate block number complement to the remainder if:
  952. // 1.) the remainder already contains an alternate candidate header byte --OR--
  953. // 2.) the candidate block number complement has the same value as a header byte, and could therefore be a valid header byte itself
  954. if (Remainder.Length > 0 || ((_Variant == Variants.XModemChecksum || _Variant == Variants.XModemCRC)
  955. && BlockNumReceived == SOH))
  956. {
  957. Remainder = Utility.CombineArrays(Remainder, new byte[] { BlockNumComplementCandidateReceived }); // Add to remainder
  958. }
  959. else if (Remainder.Length > 0 || (_Variant == Variants.XModem1K && (BlockNumReceived == SOH || BlockNumReceived == STX)))
  960. {
  961. Remainder = Utility.CombineArrays(Remainder, new byte[] { BlockNumComplementCandidateReceived }); // Add to remainder
  962. }
  963.  
  964. // Search for another header byte
  965. CurrentState = States.ReceiverHeaderSearch;
  966. continue;
  967. }
  968. }
  969.  
  970. if (CurrentState == States.ReceiverDataBytesSearch)
  971. {
  972. // Begin filling the data packet....
  973.  
  974. // Determine if there are enough unparsed bytes to fill the data packet
  975. int numUnparsedBytesRemaining = BytesToParse.Length - searchStartIndex;
  976. int numDataPacketBytesStillMissing = DataPacketReceived.Length - DataPacketNumBytesStored;
  977.  
  978. int numDataBytesToPull;
  979. if (numUnparsedBytesRemaining >= numDataPacketBytesStillMissing)
  980. numDataBytesToPull = numDataPacketBytesStillMissing;
  981. else
  982. numDataBytesToPull = numUnparsedBytesRemaining;
  983.  
  984. Array.Copy(BytesToParse, searchStartIndex, DataPacketReceived, DataPacketNumBytesStored, numDataBytesToPull);
  985. DataPacketNumBytesStored += numDataBytesToPull;
  986. searchStartIndex += numDataBytesToPull;
  987.  
  988. if (DataPacketNumBytesStored >= ExpectedDataPacketSize)
  989. {
  990. // If all expected data bytes have been gathered, move on to the next stage
  991. CurrentState = States.ReceiverErrorCheckSearch;
  992. ErrorCheck = new byte[0];
  993. }
  994.  
  995. continue;
  996. }
  997.  
  998. if (CurrentState == States.ReceiverErrorCheckSearch)
  999. {
  1000. if (_Variant == Variants.XModemChecksum)
  1001. {
  1002. // 1 error-check byte expected.
  1003. ErrorCheck = new byte[] { BytesToParse[searchStartIndex] };
  1004.  
  1005. // Validate packet
  1006. ValidatePacket();
  1007.  
  1008. // Start over
  1009. headerByteSearchStartIndex = searchStartIndex + 1;
  1010. CurrentState = States.ReceiverHeaderSearch;
  1011. }
  1012. else // XModem-CRC or XModem-1K
  1013. {
  1014. // 2 error-check bytes expected.
  1015.  
  1016. // If we have not yet gathered the required number of error check bytes, append to the error check
  1017. // array.
  1018. if (ErrorCheck.Length < 2)
  1019. {
  1020. ErrorCheck = Utility.CombineArrays(ErrorCheck, new byte[] { BytesToParse[searchStartIndex] });
  1021. }
  1022.  
  1023. if (ErrorCheck.Length >= 2)
  1024. {
  1025. // We have enough error-check bytes, so validate packet
  1026. ValidatePacket();
  1027.  
  1028. // Return to the initial state and start over
  1029. headerByteSearchStartIndex = searchStartIndex + 1;
  1030. CurrentState = States.ReceiverHeaderSearch;
  1031. }
  1032. else
  1033. {
  1034. // Advance to the next byte
  1035. searchStartIndex += 1;
  1036. }
  1037. }
  1038. } // End if
  1039. } // End while
  1040. } // End method
  1041.  
  1042. private void ValidatePacket()
  1043. {
  1044. // In order for a packet to be accepted, it must be an expected block number, and the transmitted
  1045. // check value must match the calculated check value.
  1046.  
  1047. // Dealing with a block number that is out of sequence:
  1048. //
  1049. // Normally, successive block numbers must be monotonically increasing (accounting for binary wraparound) in order
  1050. // to be valid. <NAK> is normally sent if a block number is out of sequence. However, there's an exception.
  1051. //
  1052. // Exception:
  1053. // If the current block number has been duplicated (it has the same value as a block number that was
  1054. // received previously), <ACK> is sent anyway on the theory that the Sender may not have received
  1055. // the previous <ACK> and decided to send the same packet again. <ACK> will prompt the Sender to move on to the
  1056. // next packet, which is what we want. (<NAK> will make it resend the packet yet again, which is unwanted.)
  1057. //
  1058. // Exception to the exception:
  1059. // If this is the very first packet, the block number MUST be 1. Otherwise, <NAK> will be sent.
  1060.  
  1061. if (BlockNumReceived == BlockNumExpected)
  1062. {
  1063. if (ValidateChecksum() == true)
  1064. {
  1065. // Update control variables
  1066. BlockNumExpected += 1;
  1067. ExpectingFirstPacket = false;
  1068.  
  1069. // If user wants all received data to be outputed in one lump, add this packet to the buffer
  1070. if (AllDataReceivedBuffer != null)
  1071. AllDataReceivedBuffer.Write(DataPacketReceived, 0, DataPacketReceived.Length);
  1072.  
  1073. ValidPacketReceived = true;
  1074.  
  1075. // Notify Sender to send the next packet
  1076. SendACK();
  1077. }
  1078. else
  1079. {
  1080. // Inform sender that checksum invalid
  1081. SendNAK();
  1082. ValidPacketReceived = false;
  1083. }
  1084. }
  1085. else if (ExpectingFirstPacket == false && BlockNumReceived == (byte)(BlockNumExpected - 1))
  1086. {
  1087. // Receiver got a duplicate packet.
  1088. // Send <ACK> to prompt the Sender to advance to the next packet. Ignore the current (redundant) packet.
  1089. SendACK();
  1090. ValidPacketReceived = false;
  1091. }
  1092. else
  1093. {
  1094. // The block number is completely out of sequence, so send NAK
  1095. SendNAK();
  1096. ValidPacketReceived = false;
  1097. }
  1098. }
  1099.  
  1100. /// <summary>
  1101. /// Calculates the check value (simple checksum or CRC-16) appended to the end of a packet.
  1102. /// </summary>
  1103. /// <returns>
  1104. /// True if the calculated check value and received check value match.
  1105. /// False if there is a mismatch between the calculated and received check values.
  1106. /// </returns>
  1107. private bool ValidateChecksum()
  1108. {
  1109. switch (_Variant)
  1110. {
  1111. // Arithmetic checksum:
  1112. case Variants.XModemChecksum:
  1113. byte checksum = CheckSum(DataPacketReceived);
  1114. if (checksum == ErrorCheck[0])
  1115. return true;
  1116. else
  1117. return false;
  1118.  
  1119. // CRC-16:
  1120. case Variants.XModemCRC:
  1121. case Variants.XModem1K:
  1122. ushort crcChecksumCalculated = GHI.Premium.System.CRC.CRC_16_CCITT(DataPacketReceived, 0, DataPacketReceived.Length, 0);
  1123. ushort crcChecksumReceived = BytesToUShort(ErrorCheck[0], ErrorCheck[1]);
  1124. if (crcChecksumCalculated == crcChecksumReceived)
  1125. return true;
  1126. else
  1127. return false;
  1128.  
  1129. // Just so VS2010 doesn't complain:
  1130. default:
  1131. return false;
  1132. }
  1133. }
  1134.  
  1135. private ushort BytesToUShort(byte highByte, byte lowByte)
  1136. {
  1137. return (ushort)((highByte << 8) + lowByte);
  1138. }
  1139.  
  1140. private byte[] UShortToBytes(ushort val)
  1141. {
  1142. byte highByte = (byte)(val / 256);
  1143. byte lowByte = (byte)(val % 256);
  1144.  
  1145. return new byte[] { highByte, lowByte };
  1146. }
  1147.  
  1148. /// <summary>
  1149. /// Calculates the simple checksum by summing all values in a byte array and returning the remainder.
  1150. /// </summary>
  1151. /// <param name="seq">
  1152. /// Byte array whose checksum to calculate.
  1153. /// </param>
  1154. /// <returns>
  1155. /// Modulo-256 checksum.
  1156. /// </returns>
  1157. private byte CheckSum(byte[] seq)
  1158. {
  1159. byte sum = 0;
  1160. for (int k = 0; k < seq.Length; k++)
  1161. {
  1162. sum += seq[k];
  1163. }
  1164. return sum;
  1165. }
  1166.  
  1167. private bool Aborted = false;
  1168.  
  1169. /// <summary>
  1170. /// Internal method used to cancel the file transfer.
  1171. /// </summary>
  1172. private void Abort()
  1173. {
  1174. CurrentState = States.Inactive;
  1175. TerminateSend = true;
  1176. SenderInitialized = false;
  1177. Aborted = true;
  1178.  
  1179. // Detach event handler
  1180. Port.DataReceived -= Port_DataReceived;
  1181.  
  1182. // If we are sending data, tell Sender not to expect any more responses from Receiver.
  1183. // This has no ill effect if we are receiving instead.
  1184. WaitForResponseFromReceiver.Set();
  1185.  
  1186. // Deactivate Send and Receive watchdogs
  1187. if (ReceiverNAKWatchdog != null)
  1188. ReceiverNAKWatchdog.Change(Timeout.Infinite, Timeout.Infinite);
  1189.  
  1190. if (ReceiverFileInitiationTimer != null)
  1191. ReceiverFileInitiationTimer.Change(Timeout.Infinite, Timeout.Infinite);
  1192.  
  1193. if (SenderPacketResponseWatchdog != null)
  1194. SenderPacketResponseWatchdog.Change(Timeout.Infinite, Timeout.Infinite);
  1195.  
  1196. if (ReceiverStillAliveWatchdog != null)
  1197. ReceiverStillAliveWatchdog.Change(Timeout.Infinite, Timeout.Infinite);
  1198.  
  1199. // Flush serial data so they don't contaminate a future session
  1200. Port.DiscardInBuffer();
  1201. Port.DiscardOutBuffer();
  1202. }
  1203.  
  1204. // ************************************* SENDER SENDER SENDER SENDER SENDER SENDER SENDER ***********************************
  1205.  
  1206. private ManualResetEvent WaitForResponseFromReceiver = new ManualResetEvent(false);
  1207.  
  1208. private Timer SenderPacketResponseWatchdog;
  1209.  
  1210. private Timer ReceiverStillAliveWatchdog;
  1211.  
  1212. /// <summary>
  1213. /// This is an array that will be instantiated to the specified data packet size and filled with padding bytes.
  1214. /// The intent is for this array to be copied for each outgoing data packet and populated with data bytes.
  1215. /// This avoids the overhead of having to populate new arrays with padding bytes each time.
  1216. /// </summary>
  1217. private byte[] SenderDataPacketMasterTemplate;
  1218.  
  1219. private byte[] DataPacketToSend;
  1220.  
  1221. private bool TerminateSend = false;
  1222.  
  1223. /// <summary>
  1224. /// Tracks the number of data bytes that have currently been added to the outbound packet.
  1225. /// </summary>
  1226. private int NumUserDataBytesAddedToCurrentPacket = 0;
  1227.  
  1228. /// <summary>
  1229. /// The byte value used to pad a packet in order to meet its 128-byte or 1024-byte required length.
  1230. /// </summary>
  1231. public byte PaddingByte;
  1232.  
  1233. /// <summary>
  1234. /// When using this XModem to send data, the official specification requires that EOT is transmitted to signal the end of file.
  1235. /// However, some programs may require a different byte value, such as EOF instead. This allows the user to specify
  1236. /// a custom byte value to transmit to the Receiver when the file is complete.
  1237. /// </summary>
  1238. public byte EndOfFileByteToSend;
  1239.  
  1240. /// <summary>
  1241. /// Defines a master outbound data packet that will be filled with padding bytes.
  1242. /// The purpose of this template is to be copied for each new outbound packet and subsequently
  1243. /// populated with actual data.
  1244. /// </summary>
  1245. private void DefineDataPacketTemplate()
  1246. {
  1247. int dataPacketSize;
  1248. if (_Variant == Variants.XModem1K)
  1249. dataPacketSize = _Packet1024NominalSize;
  1250. else
  1251. dataPacketSize = _Packet128NominalSize;
  1252.  
  1253. SenderDataPacketMasterTemplate = new byte[dataPacketSize];
  1254.  
  1255. // Fill the template with padding bytes
  1256. for (int k = 0; k < SenderDataPacketMasterTemplate.Length; k++)
  1257. SenderDataPacketMasterTemplate[k] = PaddingByte;
  1258. }
  1259.  
  1260. private byte _BlockNumToSend = 1;
  1261. public byte BlockNumToSend
  1262. {
  1263. get { return _BlockNumToSend; }
  1264. }
  1265.  
  1266. /// <summary>
  1267. /// Initializes the modem send process.
  1268. /// </summary>
  1269. /// <param name="dataToSend">
  1270. /// Optional argument.
  1271. /// If provided, this is the file that should be transmitted in its entirety.
  1272. /// The EndOfFile byte is automatically transmitted once this array is finished.
  1273. /// If omitted, the file may be sent piece-by-piece using the AddToOutboundPacket() method.
  1274. /// </param>
  1275. /// <returns>
  1276. /// The number of data bytes successfully transmitted.
  1277. /// </returns>
  1278. public int Send(byte[] dataToSend = null)
  1279. {
  1280. // Initialize control variables
  1281. _TerminationReason = TerminationReasonEnum.TransferStillActiveNotTerminated;
  1282. Aborted = false;
  1283. _BlockNumToSend = 1; // Current outbound block number
  1284. _SenderConsecutiveRetryAttempts = 0;
  1285. PacketSuccessfullySent = false;
  1286. DataPacketToSend = null;
  1287. NumUserDataBytesAddedToCurrentPacket = 0;
  1288. _TotalUserDataBytesPacketized = 0;
  1289. _TotalUserDataBytesSent = 0;
  1290. _NumCancellationBytesReceived = 0;
  1291. TerminateSend = false;
  1292. EndOfFileAcknowledgementReceived = false;
  1293. SenderInitialized = true; // Ensures that this method is called first before AddToOutboundPacket()
  1294.  
  1295. WaitForResponseFromReceiver.Reset();
  1296. CurrentState = States.SenderAwaitingFileInitiation;
  1297.  
  1298. // Open port if it isn't open already
  1299. if (Port.IsOpen == false)
  1300. Port.Open();
  1301.  
  1302. Port.DiscardInBuffer();
  1303. Port.DiscardOutBuffer();
  1304.  
  1305. if (ReceiverStillAliveWatchdog == null)
  1306. ReceiverStillAliveWatchdog = new Timer(ReceiverStillAliveWatchdogRoutine, null, SendInactivityTimeoutMillisec, SendInactivityTimeoutMillisec);
  1307. else
  1308. ReceiverStillAliveWatchdog.Change(SendInactivityTimeoutMillisec, SendInactivityTimeoutMillisec);
  1309.  
  1310. if (SenderPacketResponseWatchdog == null)
  1311. SenderPacketResponseWatchdog = new Timer(SenderPacketResponseWatchdogRoutine, null, SenderPacketRetryTimeoutMillisec, SenderPacketRetryTimeoutMillisec);
  1312. else
  1313. SenderPacketResponseWatchdog.Change(SenderPacketRetryTimeoutMillisec, SenderPacketRetryTimeoutMillisec);
  1314.  
  1315. // Attach event handler
  1316. Port.DataReceived += new SerialDataReceivedEventHandler(Port_DataReceived);
  1317.  
  1318. // Wait here for file initiation byte to be received from Receiver
  1319. WaitForResponseFromReceiver.WaitOne();
  1320. WaitForResponseFromReceiver.Reset();
  1321.  
  1322. if (dataToSend != null && Aborted == false)
  1323. {
  1324. AddToOutboundPacket(dataToSend);
  1325. if (TerminateSend == false)
  1326. EndFile();
  1327.  
  1328. return _TotalUserDataBytesSent;
  1329. }
  1330. else
  1331. return 0;
  1332. }
  1333.  
  1334. private int _TotalUserDataBytesPacketized = 0;
  1335. /// <summary>
  1336. /// Returns the total number of user data bytes that have been incorporated into outbound packets since the current
  1337. /// send session was started. This counts data bytes that have already been successfully transmitted as well as
  1338. /// those data bytes that have been added to a packet that is still waiting for completion. Header bytes, padding bytes,
  1339. /// checksum bytes (any overhead incurred by the XModem infrastructure) are NOT included.
  1340. ///
  1341. /// This allows the user, for example, to calculate the proper file offset if reading an extremely large file
  1342. /// from persistent storage.
  1343. /// </summary>
  1344. public int TotalUserDataBytesPacketized
  1345. {
  1346. get { return _TotalUserDataBytesPacketized; }
  1347. }
  1348.  
  1349. private int _TotalUserDataBytesSent = 0;
  1350. /// <summary>
  1351. /// Returns the total number of user data bytes that have been successfully transmitted since the current send session
  1352. /// was started. Only data bytes are included in this count. Header bytes, padding bytes, checksum bytes (any overhead
  1353. /// incurred by the XModem infrastructure) are NOT included.
  1354. ///
  1355. /// When the transfer has terminated, this can also tell the user if the file was successfully sent in its entirety.
  1356. /// </summary>
  1357. public int TotalUserDataBytesSent
  1358. {
  1359. get { return _TotalUserDataBytesSent; }
  1360. }
  1361.  
  1362. /// <summary>
  1363. /// Sentinel variable that verifies that Send() method is called before AddToOutboundPacket().
  1364. /// </summary>
  1365. private bool SenderInitialized = false;
  1366.  
  1367. /// <summary>
  1368. /// Assembles one or more packets from the bytes passed to this method and automatically transmits them when the
  1369. /// packet size has been satisfied.
  1370. ///
  1371. /// The number of bytes supplied by the user during each call may be completely arbitrary. Supplied arguments may be
  1372. /// extremely short or extremely long. This method automatically parses the supplied byte arrays and builds packets
  1373. /// on the fly. Once enough data has been received for a packet, that packet is automatically transmitted.
  1374. ///
  1375. /// This method is useful when reading a very large file piece-by-piece from a source, and for packetizing those
  1376. /// pieces automatically.
  1377. /// </summary>
  1378. /// <param name="dataToSend">
  1379. /// The data bytes that should be added to the current pending packet. This array may be any length.
  1380. /// </param>
  1381. /// <returns>
  1382. /// The number of data bytes successfully sent during this particular method call.
  1383. /// This is 0 if the packet is still too short to send using the data bytes collected thus far. By knowing how many data
  1384. /// bytes were successfully transmitted during this method invocation, the user can calculate the correct file offset
  1385. /// if the source file is extremely lage and is being read from persistent storage, for example.
  1386. ///
  1387. /// Note that only the data bytes successfully transmitted during THIS method call are counted. Use the
  1388. /// TotalUserDataBytesSent property to keep track of the accumulated total of all user data bytes sent during the
  1389. /// current send session.
  1390. /// </returns>
  1391. public int AddToOutboundPacket(byte[] dataToSend)
  1392. {
  1393. // Tracks the number of user data bytes that have been successfully sent during this method invocation
  1394. int numUnpaddedDataBytesSentThisCall = 0;
  1395.  
  1396. // Ensure that the Send() method is first called before this method
  1397. if (SenderInitialized == false)
  1398. {
  1399. throw new ArgumentException("The XMODEM.Send() method must first be called before XMODEM.AddToOutboundPacket() is used.");
  1400. }
  1401.  
  1402. int dataOffset = 0;
  1403. while (dataOffset < dataToSend.Length && TerminateSend == false)
  1404. {
  1405.  
  1406. // Instantiate outbound data packet if empty
  1407. if (DataPacketToSend == null)
  1408. {
  1409. if (_Variant == Variants.XModem1K)
  1410. DataPacketToSend = new byte[_Packet1024NominalSize];
  1411. else
  1412. DataPacketToSend = new byte[_Packet128NominalSize];
  1413.  
  1414. Array.Copy(SenderDataPacketMasterTemplate, DataPacketToSend, DataPacketToSend.Length);
  1415. }
  1416.  
  1417. int numUnparsedDataBytes = dataToSend.Length - dataOffset;
  1418. int numPacketDataBytesNeeded = DataPacketToSend.Length - NumUserDataBytesAddedToCurrentPacket;
  1419.  
  1420. int numBytesToAdd;
  1421. if (numPacketDataBytesNeeded >= numUnparsedDataBytes)
  1422. numBytesToAdd = numUnparsedDataBytes;
  1423. else
  1424. numBytesToAdd = numPacketDataBytesNeeded;
  1425.  
  1426. Array.Copy(dataToSend, dataOffset, DataPacketToSend, NumUserDataBytesAddedToCurrentPacket, numBytesToAdd);
  1427.  
  1428. NumUserDataBytesAddedToCurrentPacket += numBytesToAdd;
  1429. dataOffset += numBytesToAdd;
  1430. _TotalUserDataBytesPacketized += numBytesToAdd;
  1431.  
  1432. if (NumUserDataBytesAddedToCurrentPacket >= DataPacketToSend.Length)
  1433. {
  1434. TransmitPacket();
  1435.  
  1436. // Determine if packet transmission was successful, or the maximum number of retries has been exhausted:
  1437. if (PacketSuccessfullySent == true)
  1438. {
  1439. // If packet successfully transmitted, keep a running tally of data bytes sent out.
  1440. // Only count actual user-provided data. Padding bytes are not counted.
  1441. _TotalUserDataBytesSent += NumUserDataBytesAddedToCurrentPacket;
  1442. numUnpaddedDataBytesSentThisCall += NumUserDataBytesAddedToCurrentPacket;
  1443.  
  1444. // Reset control variables
  1445. NumUserDataBytesAddedToCurrentPacket = 0;
  1446.  
  1447. // Re-initialize a new packet
  1448. DataPacketToSend = null;
  1449. }
  1450. else if (TerminateSend == false)
  1451. {
  1452. // Terminal condition if ACK not received even after multiple attempts
  1453. Abort();
  1454. _TerminationReason = TerminationReasonEnum.TooManyRetries;
  1455. break;
  1456. }
  1457. }
  1458. }
  1459.  
  1460. return numUnpaddedDataBytesSentThisCall;
  1461. }
  1462.  
  1463. private bool PacketSuccessfullySent = false;
  1464.  
  1465. private int _SenderConsecutiveRetryAttempts = 0;
  1466. public int SenderConsecutiveRetryAttempts
  1467. {
  1468. get { return _SenderConsecutiveRetryAttempts; }
  1469. }
  1470.  
  1471. private void TransmitPacket()
  1472. {
  1473. // Calculate check-value
  1474. byte[] checkValueBytes;
  1475. if (_Variant == Variants.XModemChecksum)
  1476. checkValueBytes = new byte[] { CheckSum(DataPacketToSend) };
  1477. else
  1478. {
  1479. ushort checkValueShort = GHI.Premium.System.CRC.CRC_16_CCITT(DataPacketToSend, 0, DataPacketToSend.Length, 0);
  1480. checkValueBytes = UShortToBytes(checkValueShort);
  1481. }
  1482.  
  1483. // Determine packet size header
  1484. byte packetSizeHeader;
  1485. if (_Variant == Variants.XModem1K)
  1486. packetSizeHeader = STX;
  1487. else
  1488. packetSizeHeader = SOH;
  1489.  
  1490. PacketSuccessfullySent = false;
  1491. while (PacketSuccessfullySent == false && _SenderConsecutiveRetryAttempts < MaxSenderRetries && TerminateSend == false)
  1492. {
  1493. // Send packet size header
  1494. Port.WriteByte(packetSizeHeader);
  1495.  
  1496. // Send block number
  1497. Port.WriteByte(_BlockNumToSend);
  1498.  
  1499. // Send block number complement
  1500. Port.WriteByte((byte)(255 - _BlockNumToSend));
  1501.  
  1502. // Send data packet
  1503. Port.Write(DataPacketToSend, 0, DataPacketToSend.Length);
  1504.  
  1505. // Update state. Do this just before the Receiver is expected to respond so its response
  1506. // doesn't fall through the cracks if it replies extremely quickly.
  1507. WaitForResponseFromReceiver.Reset();
  1508. CurrentState = States.SenderPacketSent;
  1509.  
  1510. // Send check-value. This completes the packet. A response from the Receiver is expected after this.
  1511. Port.Write(checkValueBytes, 0, checkValueBytes.Length);
  1512.  
  1513. // Wait for ACK or NAK or CAN
  1514. WaitForResponseFromReceiver.WaitOne();
  1515. WaitForResponseFromReceiver.Reset();
  1516.  
  1517. // Once we've gotten a response, the only message expected from the Receiver at this point is a possible cancellation
  1518. CurrentState = States.SenderAlertForPossibleCancellation;
  1519. }
  1520. }
  1521.  
  1522. /// <summary>
  1523. /// Determines if the minimum number of consecutive cancellation bytes are present in a byte array.
  1524. /// </summary>
  1525. /// <param name="recv">
  1526. /// The byte array to search for consecutive cancellation bytes.
  1527. /// </param>
  1528. /// <returns>
  1529. /// True if cancellation condition has been met.
  1530. /// False if cancellation request is absent.
  1531. /// </returns>
  1532. private bool DetectCancellation(byte[] recv)
  1533. {
  1534. if (NumCancellationBytesRequired > 0)
  1535. {
  1536. int foundIndex = Array.IndexOf(recv, CAN);
  1537. if (foundIndex > -1)
  1538. {
  1539. for (int indexToCheck = foundIndex; indexToCheck < recv.Length; indexToCheck++)
  1540. {
  1541. // If more than 1 CAN byte is required for cancellation, they must be consecutive
  1542. // for cancellation to occur.
  1543. if (recv[indexToCheck] == CAN)
  1544. _NumCancellationBytesReceived += 1;
  1545. else
  1546. _NumCancellationBytesReceived = 0;
  1547.  
  1548. if (_NumCancellationBytesReceived >= NumCancellationBytesRequired)
  1549. {
  1550. Abort();
  1551. _TerminationReason = TerminationReasonEnum.CancelNotificationReceived;
  1552. return true; // Cancellation detected
  1553. }
  1554. }
  1555. }
  1556. else
  1557. {
  1558. _NumCancellationBytesReceived = 0;
  1559. }
  1560. }
  1561. return false; // No cancellation detected
  1562. }
  1563.  
  1564. private bool EndOfFileAcknowledgementReceived = false;
  1565.  
  1566. /// <summary>
  1567. /// Informs the Receiver that the transmitted file is complete.
  1568. /// Any pending packets are sent, followed by the end-of-file byte.
  1569. /// </summary>
  1570. /// <returns>
  1571. /// The number of user data bytes successfully transmitted during this method call.
  1572. /// </returns>
  1573. public int EndFile()
  1574. {
  1575. // Check if there are unsent data bytes remaining in a pending packet, and if so, send the packet
  1576. // containing the unsent bytes
  1577. if (NumUserDataBytesAddedToCurrentPacket > 0)
  1578. {
  1579. TransmitPacket();
  1580. _TotalUserDataBytesSent += NumUserDataBytesAddedToCurrentPacket;
  1581. }
  1582.  
  1583. CurrentState = States.SenderAwaitingEndOfFileConfirmation;
  1584.  
  1585. int numEndOfFileBytesSent = 0;
  1586. while (EndOfFileAcknowledgementReceived == false && numEndOfFileBytesSent <= MaxSenderRetries)
  1587. {
  1588. WaitForResponseFromReceiver.Reset();
  1589. Port.WriteByte(EndOfFileByteToSend);
  1590. numEndOfFileBytesSent += 1;
  1591. WaitForResponseFromReceiver.WaitOne();
  1592. }
  1593.  
  1594. if (EndOfFileAcknowledgementReceived == true)
  1595. {
  1596. Abort();
  1597. _TerminationReason = TerminationReasonEnum.EndOfFile;
  1598. return NumUserDataBytesAddedToCurrentPacket;
  1599. }
  1600. else
  1601. {
  1602. Abort();
  1603. _TerminationReason = TerminationReasonEnum.TooManyRetries;
  1604. return 0;
  1605. }
  1606. }
  1607.  
  1608. private void SenderPacketResponseWatchdogRoutine(object notUsed)
  1609. {
  1610. // <ACK>, <NAK> or <CAN> is expected from the Receiver after each packet sent.
  1611. // If an appropriate response is not received within the expected timeout, release the WaitHandle which is enforcing
  1612. // the wait-for-response. This will make the Sender resend the packet once more (until the retry limit is reached).
  1613. if (CurrentState == States.SenderPacketSent)
  1614. WaitForResponseFromReceiver.Set();
  1615. }
  1616.  
  1617. private void ResetSenderPacketResponseWatchdog()
  1618. {
  1619. if (SenderPacketResponseWatchdog != null)
  1620. SenderPacketResponseWatchdog.Change(SenderPacketRetryTimeoutMillisec, SenderPacketRetryTimeoutMillisec);
  1621. }
  1622.  
  1623. private void ReceiverStillAliveWatchdogRoutine(object notUsed)
  1624. {
  1625. Abort();
  1626. _TerminationReason = TerminationReasonEnum.NoResponseFromReceiver;
  1627. }
  1628.  
  1629. private void ResetReceiverStillAliveWatchdog()
  1630. {
  1631. if (ReceiverStillAliveWatchdog != null)
  1632. ReceiverStillAliveWatchdog.Change(SendInactivityTimeoutMillisec, SendInactivityTimeoutMillisec);
  1633. }
  1634.  
  1635. /// <summary>
  1636. /// Removes one or more padding bytes that may exist at the end of a byte array.
  1637. /// </summary>
  1638. /// <param name="input">
  1639. /// The byte array to trim.
  1640. /// </param>
  1641. /// <param name="paddingByteToRemove">
  1642. /// The byte value which defines a padding byte.
  1643. /// If omitted, this defaults to SUB (byte decimal 26).
  1644. /// </param>
  1645. /// <returns>
  1646. /// A byte array without trailing padding bytes (if any are found).
  1647. /// </returns>
  1648. public byte[] TrimPaddingBytesFromEnd(byte[] input, byte paddingByteToRemove = 26)
  1649. {
  1650. int numBytesToDiscard = 0;
  1651.  
  1652. for (int k = input.Length - 1; k >= 0; k--)
  1653. {
  1654. if (input[k] == paddingByteToRemove)
  1655. numBytesToDiscard += 1;
  1656. else
  1657. break;
  1658. }
  1659.  
  1660. int numBytesToKeep = input.Length - numBytesToDiscard;
  1661.  
  1662. byte[] output = new byte[numBytesToKeep];
  1663. Array.Copy(input, output, numBytesToKeep);
  1664.  
  1665. return output;
  1666. }
  1667.  
  1668. } // End class
  1669.  
  1670. } // End namespace