/// <summary> /// Transfers a file using Y Modem. /// </summary> /// <param name="stream">The stream to perform the transfer on.</param> /// <param name="dataStream">The datastream to transfer.</param> /// <param name="fileName">The name of the file to transfer.</param> /// <param name="cancellationToken">Cancellation token for cancelling the task.</param> public override async Task Send(Stream channel, Stream dataStream, string fileName, CancellationToken cancellationToken) { // Setup the timer. ModemTimer timer = new ModemTimer(WAIT_FOR_RECEIVER_TIMEOUT); timer.Start(); // Choose a CRC calculation. bool useCrc16 = await WaitReceiverRequest(channel, timer, cancellationToken); ICrc crc = await WaitReceiverRequest(channel, timer, cancellationToken) ? Crc.Crc16 : Crc.Crc8; // Convert the filename to bytes. string fileNameString = $"{fileName.ToLower()}"; byte[] fileNameBytes = new byte[128]; Encoding.UTF8.GetBytes(fileNameString, 0, fileNameString.Length, fileNameBytes, 0); // Send the filename block. await SendBlock(channel, 0, fileNameBytes, 128, crc, cancellationToken); // Wait till the device says it's good. await WaitReceiverRequest(channel, timer, cancellationToken); // Send the file contents. await SendBlocks(channel, dataStream, 1, crc, cancellationToken); // Send the EOT (we're done sending data) character. await SendEot(channel, cancellationToken); }
/// <summary> /// Writes the CRC value to the stream. /// </summary> /// <param name="channel">Stream to write to.</param> /// <param name="block">Block to write.</param> /// <param name="crc">CRC calculation to use.</param> protected void WriteCrc(Stream channel, byte[] block, ICrc crc) { byte[] crcBytes = new byte[crc.Length]; long crcValue = crc.Compute(block); for (int i = 0; i < crc.Length; i++) { crcBytes[crc.Length - i - 1] = (byte)((crcValue >> (8 * i)) & 0xFF); } channel.Write(crcBytes, 0, crcBytes.Length); }
/// <summary> /// Sends a block of data. /// </summary> /// <param name="channel">The channel to send on.</param> /// <param name="dataStream">The stream of data to send.</param> /// <param name="blockNumber">The block number</param> /// <param name="crc">CRC calculation to use.</param> /// <param name="cancellationToken">The cancellation token to use.</param> protected async Task SendBlocks(Stream channel, Stream dataStream, int blockNumber, ICrc crc, CancellationToken cancellationToken) { byte[] block = new byte[1024]; int dataLength = await dataStream.ReadAsync(block, 0, 1024, cancellationToken); while (dataLength > 0) { await SendBlock(channel, blockNumber ++, block, dataLength, crc, cancellationToken); dataLength = await dataStream.ReadAsync(block, 0, 1024, cancellationToken); } }
/// <summary> /// Sends a block of data. /// </summary> /// <param name="channel">Channel to send the data on.</param> /// <param name="blockNumber">The block number we're sending.</param> /// <param name="block">The block contents to send.</param> /// <param name="dataLength">The length of the block.</param> /// <param name="crc">CRC calculation to use.</param> /// <param name="cancellationToken">Cancellation token to use.</param> protected async Task SendBlock(Stream channel, int blockNumber, byte[] block, int dataLength, ICrc crc, CancellationToken cancellationToken) { ModemTimer timer = new ModemTimer(SEND_BLOCK_TIMEOUT); int errorCount = 0; if (dataLength < block.Length) { for (int k = dataLength; k < block.Length; k++) { block[k] = CPMEOF; } } while (errorCount < MAXERRORS) { bool keepGoing = true; timer.Start(); if (block.Length == 1024) { channel.WriteByte(STX); } else //128 { channel.WriteByte(SOH); } channel.WriteByte((byte)blockNumber); channel.WriteByte((byte)~blockNumber); channel.Write(block, 0, block.Length); WriteCrc(channel, block, crc); channel.Flush(); while (keepGoing) { try { byte character = await ReadByte(channel, timer, cancellationToken); if (character == ACK) { return; } else if (character == NAK) { errorCount++; keepGoing = false; } else if (character == CAN) { throw new IOException("Transmission terminated"); } } catch (TimeoutException) { errorCount++; keepGoing = false; } } } throw new IOException("Too many errors caught, abandoning transfer"); }