/// <summary> /// Sends the EOT character. /// </summary> /// <param name="channel">The channel to send on.</param> /// <param name="cancellationToken">Cancellation token to use.</param> protected async Task SendEot(Stream channel, CancellationToken cancellationToken) { int errorCount = 0; ModemTimer timer = new ModemTimer(BLOCK_TIMEOUT); while (errorCount < 10) { await SendByte(channel, EOT, cancellationToken); try { int character = await ReadByte(channel, timer, cancellationToken); if (character == ACK) { return; } else if (character == CAN) { throw new IOException("Transmission terminated"); } } catch (TimeoutException) { // Ignore timeout exceptions. } errorCount++; } }
/// <summary> /// Reads a byte from the stream. /// </summary> /// <param name="channel">The channel to read from.</param> /// <param name="timer">Max time to wait to read from the buffer.</param> /// <param name="cancellationToken">Cancellation token used to cancel the task.</param> /// <returns>A single byte.</returns> protected async Task <byte> ReadByte(Stream channel, ModemTimer timer, CancellationToken cancellationToken) { byte?retVal = null; // Keep trying to read until we have some data or until it times out. while (!timer.Expired && !retVal.HasValue) { byte[] buffer = new byte[1]; int numRead = await channel.ReadAsync(buffer, 0, 1, cancellationToken); if (numRead > 0) { retVal = buffer[0]; } Thread.Sleep(10); } // Error out if we didn't read anything. if (!retVal.HasValue) { throw new TimeoutException(); } return(retVal.Value); }
/// <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> /// Waits for the receiver request token. /// </summary> /// <param name="channel">Channel to wait upon.</param> /// <param name="timer">Timer to use.</param> /// <param name="cancellationToken">Cancellation token to use.</param> /// <returns>True if we should continue, false if we should resend.</returns> protected async Task <bool> WaitReceiverRequest(Stream channel, ModemTimer timer, CancellationToken cancellationToken) { bool?retVal = null; while (!retVal.HasValue && !timer.Expired) // TODO: Validate this change. { int character = await ReadByte(channel, timer, cancellationToken); if (character == NAK) { retVal = false; } if (character == ST_C) { retVal = true; } } return(retVal.Value); }
/// <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"); }