XMODEM for PC Full .NET Framework by Iggmoe

Nov. 14, 2013   |   Snippet   |   Licensed as Apache 2.0   |   5417 views

Note: This version of XMODEM is designed to be run on a PC. The .NET Micro Framework 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.

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