core/builtins/xmodem.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 26 Aug 2010 00:49:35 +0100
changeset 45 534b01198c2d
parent 0 7f656887cf89
permissions -rw-r--r--
Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase. Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.

// xmodem.cpp
// 
// Copyright (c) 2008 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
// 
// Initial Contributors:
// Accenture - Initial contribution
//

#include <fshell/ioutils.h>
#include "xmodem.h"

using namespace IoUtils;

const TInt KMaxRetries = 5;
const TInt KSmallBlockSize = 128;
const TInt KLargeBlockSize = 1024;
const TInt KLongTimeout = 60;
const TInt KMediumTimeout = 10;
const TInt KShortTimeout = 1;
const TUint8 KByteSoh = 0x01;
const TUint8 KByteStx = 0x02;
const TUint8 KByteEot = 0x04;
const TUint8 KByteAck = 0x06;
const TUint8 KByteNak = 0x15;
const TUint8 KByteCan = 0x18;
const TUint8 KByteSub = 0x1a;
const TUint8 KByteTelnetIac = 0xff;
const TUint8 KByteTelnetDo = 0xfd;
const TUint8 KByteTelnetWill = 0xfb;
const TUint8 KByteTelnetBinaryMode = 0x00;
_LIT(KLitEot, "\x04");
_LIT(KLitAck, "\x06");
_LIT(KLitNak, "\x15");
_LIT(KLitCancel, "\x18\x18\x18");
_LIT(KLitC, "C");
_LIT(KLitTelnetDoBinaryMode, "\xff\xfd\x00");
_LIT(KLitTelnetWillBinaryMode, "\xff\xfb\x00");

#ifdef FSHELL_CORE_SUPPORT_XMODEM_CRCNOTAB

TUint16 Crc16(const TDesC& aData)
	{
	TInt crc	= 0;
	const TInt length = aData.Length();
	for (TInt i = 0; i < length; ++i)
		{
		crc = crc ^ ((TInt)(aData[i]&0x00FF) << 8);
		for (TInt j = 0; j < 8; ++j)
			{
			if (crc & 0x8000)
				{
				crc = crc << 1 ^ 0x1021;
				}
			else
				{
				crc = crc << 1;
				}
			}
		}
	return (crc	& 0xFFFF);
	}

#else

static TUint16 KCrc16Tab[256]= {
	0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
	0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
	0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
	0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
	0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
	0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
	0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
	0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
	0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
	0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
	0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
	0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
	0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
	0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
	0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
	0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
	0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
	0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
	0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
	0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
	0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
	0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
	0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
	0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
	0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
	0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
	0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
	0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
	0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
	0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
	0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
	0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};
  
TUint16 Crc16(const TDesC& aData)
	{
	TUint16 crc = 0;
	const TInt length = aData.Length();
	for (TInt i = 0; i < length; ++i)
		{
		crc = (crc<<8) ^ KCrc16Tab[((crc>>8) ^ (TUint8)aData[i])&0x00FF];
		}
	return crc;
	}

#endif // FSHELL_CORE_SUPPORT_XMODEM_CRCNOTAB

CCommandBase* CCmdXmodem::NewLC()
	{
	CCmdXmodem* self = new(ELeave) CCmdXmodem();
	CleanupStack::PushL(self);
	self->BaseConstructL();
	self->ConstructL();
	return self;
	}

CCmdXmodem::~CCmdXmodem()
	{
	delete iBuf;
	iTimer.Close();
	}

CCmdXmodem::CCmdXmodem() : iBlockSize(KSmallBlockSize), iPacketNumber(1)
	{
	}

void CCmdXmodem::ConstructL()
	{
	User::LeaveIfError(iTimer.CreateLocal());
	}

TUint8 CCmdXmodem::ReceiveByteL(TInt aTimeout, TBool* aTimeoutOccurred)
	{
	return (TUint8)ReceiveShortL(aTimeout, aTimeoutOccurred);
	}

TUint16 CCmdXmodem::ReceiveShortL(TInt aTimeout, TBool* aTimeoutOccurred)
	{
	TBuf<1> buf;
	ReceiveWithTimeoutL(buf, aTimeout, aTimeoutOccurred);
	if (buf.Length() == 1)
		{
		return buf[0];
		}
	return 0;
	}

void CCmdXmodem::ReceiveWithTimeoutL(TDes& aBuf, TInt aTimeout, TBool* aTimeoutOccurred)
	{
	aBuf.Zero();
	TBool timeOutOccurred(EFalse);

	if (iTelnetMode & ERecvBinary)
		{
		TBool prevReceiveEndedWithIac(EFalse);
		while ((aBuf.Length() < aBuf.MaxLength()) && !timeOutOccurred)
			{
			TPtr ptr(const_cast<TUint16*>(aBuf.Ptr()) + aBuf.Length(), 0, aBuf.MaxLength() - aBuf.Length()); // Create a TPtr over the unused part of aBuf.
			DoReceiveWithTimeoutL(ptr, aTimeout, &timeOutOccurred);
			if (!timeOutOccurred)
				{
				if (prevReceiveEndedWithIac && (ptr.Length() >= 1) && (ptr[0] == KByteTelnetIac))
					{
					ptr.Delete(0, 1);
					}
				prevReceiveEndedWithIac = EFalse;
				TInt numChars = ptr.Length();
				for (TInt i = 0; i < numChars; ++i)
					{
					if (ptr[i] == KByteTelnetIac)
						{
						if ((i < (numChars - 1)) && (ptr[i + 1] == KByteTelnetIac))
							{
							ptr.Delete(i, 1);
							--numChars;
							}
						else if (i == (numChars - 1))
							{
							prevReceiveEndedWithIac = ETrue;
							}
						}
					}
				aBuf.SetLength(aBuf.Length() + ptr.Length());
				}
			}
		}
	else
		{
		DoReceiveWithTimeoutL(aBuf, aTimeout, &timeOutOccurred);
		}

	if (!timeOutOccurred)
		{
		Progress(_L("Received:\r\n"));
		Dump(aBuf);
		}

	if (aTimeoutOccurred)
		{
		*aTimeoutOccurred = timeOutOccurred;
		}
	}

void CCmdXmodem::DoReceiveWithTimeoutL(TDes& aBuf, TInt aTimeout, TBool* aTimeoutOccurred)
	{
	iStarted = ETrue;
	aBuf.Zero();
	if (aBuf.MaxLength() <= iUngetBuf.Length())
		{
		aBuf.Copy(iUngetBuf.Mid(0, aBuf.MaxLength()));
		iUngetBuf.Delete(0, aBuf.MaxLength());
		Progress(_L("Received (from unget buf):\r\n"));
		Dump(aBuf);
		}
	else
		{
		aBuf.Copy(iUngetBuf);
		TPtr ptr(const_cast<TUint16*>(aBuf.Ptr()) + aBuf.Length(), 0, aBuf.MaxLength() - aBuf.Length()); // Create a TPtr over the unused part of aBuf.
		iUngetBuf.Zero();
		TRequestStatus readStatus;
		TRequestStatus timeoutStatus;
		iTimer.After(timeoutStatus, aTimeout * 1000000);
		Stdin().Read(aBuf, readStatus);
		User::WaitForRequest(readStatus, timeoutStatus);
		if (readStatus == KRequestPending)
			{
			Progress(_L("Timeout expired, cancelling read...\r\n"));
			if (aTimeoutOccurred)
				{
				*aTimeoutOccurred = ETrue;
				}
			Stdin().ReadCancel();
			User::WaitForRequest(readStatus);
			return;
			}
		else
			{
			if (aTimeoutOccurred)
				{
				*aTimeoutOccurred = EFalse;
				}
			iTimer.Cancel();
			User::WaitForRequest(timeoutStatus);
			User::LeaveIfError(readStatus.Int());
			aBuf.SetLength(aBuf.Length() + ptr.Length());
			Progress(_L("Received (raw):\r\n"));
			Dump(aBuf);
			return;
			}
		}
	}

void CCmdXmodem::PurgeInputL()
	{
	Progress(_L("Purging input...\r\n"));
	TBool timeoutOccurred;
	do
		{
		ReceiveByteL(KShortTimeout, &timeoutOccurred);
		if (timeoutOccurred)
			{
			Progress(_L("Timed out\r\n"));
			}
		else
			{
			Progress(_L("Received byte\r\n"));
			}
		}
		while (!timeoutOccurred);
	}

void CCmdXmodem::SendL(const TDesC& aData)
	{
	Progress(_L("Sending:\r\n"));
	Dump(aData);
	iStarted = ETrue;

	if (iTelnetMode & ESendBinary)
		{
		const TInt numChars = aData.Length();
		TInt numIacs = 0;
		for (TInt i = 0; i < numChars; ++i)
			{
			if (aData[i] == KByteTelnetIac)
				{
				++numIacs;
				}
			}
		if (numIacs > 0)
			{
			Progress(_L("Escaping %d IACs\r\n"), numIacs);
			HBufC* newBuf = HBufC::NewLC(aData.Length() + numIacs);
			TPtr newBufPtr(newBuf->Des());
			for (TInt i = 0; i < numChars; ++i)
				{
				if (aData[i] == KByteTelnetIac)
					{
					newBufPtr.Append(KByteTelnetIac);
					}
				newBufPtr.Append(aData[i]);
				}
			Dump(*newBuf);
			User::LeaveIfError(Stdout().Write(*newBuf));
			CleanupStack::PopAndDestroy(newBuf);
			}
		else
			{
			User::LeaveIfError(Stdout().Write(aData));
			}
		}
	else
		{
		User::LeaveIfError(Stdout().Write(aData));
		}
	}

TInt CCmdXmodem::CheckSize() const
	{
	if (iCrc)
		{
		return 2;
		}
	else
		{
		return 1;
		}
	}

TInt CCmdXmodem::ProtocolOverhead() const
	{
	return 3 + CheckSize();
	}

TBool CCmdXmodem::CheckBlock() const
	{
	TBool match(EFalse);

	if (iCrc)
		{
		TUint16 crc = Crc16(iBuf->Mid(2, iBlockSize));
		TUint16 receivedCrc = ((*iBuf)[iBlockSize + 2] << 8) + (*iBuf)[iBlockSize + 3];
		match = (crc == receivedCrc);
		if (!match)
			{
			Progress(_L("CRC failed (calculated %u, received %u)\r\n"), crc, receivedCrc);
			}
		}
	else
		{
		TUint8 sum = 0;
		for (TInt i = 2; i < (iBlockSize + 2); ++i)	// Plus 2 because iBuf contains the two packet number bytes.
			{
			sum += (*iBuf)[i];
			}
		TUint8 received = (*iBuf)[iBlockSize + 2];
		match = (sum == received);
		if (!match)
			{
			Progress(_L("CheckSum failed (calculated %u, received %u)\r\n"), sum, received);
			}
		}

	return match;
	}

void CCmdXmodem::Progress(TRefByValue<const TDesC> aFmt, ...) const
	{
	if (iVerbose)
		{
		TOverflowTruncate overflow;
		VA_LIST list;
		VA_START(list, aFmt);
		TBuf<0x100> buf;
		buf.AppendFormatList(aFmt, list, &overflow);
		const_cast<CCmdXmodem*>(this)->Stderr().Write(buf);
		}
	}

void CCmdXmodem::Dump(const TDesC& aData)
	{
	if (iVerbose)
		{
		TBuf<80> out;
		TBuf<16> ascii;
		TInt dataIndex = 0;
		TInt pos = 0;
		do
			{
			out.Zero();
			ascii.Zero();
			out.AppendNumFixedWidthUC(pos, EHex, 8);
			out.Append(_L(": "));
			for (TInt i = 0; i < 16; ++i)
				{
				if (dataIndex < aData.Length())
					{
					TUint8 byte = (TUint8)aData[dataIndex++];
					out.AppendNumFixedWidthUC(byte, EHex, 2);
					out.Append(_L(" "));
					if ((byte < 0x20) || (byte >= 0x7f) || byte == '%')
						{
						byte = '.';
						}
					ascii.Append(TChar(byte));
					++pos;
					}
				else
					{
					out.Append(_L("   "));
					}
				}
			out.Append(ascii);
			out.Append(_L("\r\n"));
			Stderr().Write(out);
			}
			while (dataIndex < aData.Length());
		}
	}

void CCmdXmodem::Abort()
	{
	TRAP_IGNORE(
				SendL(KLitCancel);
				PurgeInputL();
				);
	}

void CCmdXmodem::WaitForSyncL()
	{
	Progress(_L("Waiting for sync...\r\n"));
	for (TInt numRetries = 0; numRetries < KMaxRetries; ++numRetries)
		{
		switch (ReceiveByteL(KLongTimeout))
			{
			case 'C':
				Progress(_L("Receiver supports CRC\r\n"));
				iCrc = ETrue;
				return;
			case KByteNak:
				Progress(_L("Receiver does not support CRC\r\n"));
				iCrc = EFalse;
				return;
			case KByteCan:
				Progress(_L("Receiver cancelled\r\n"));
				SendL(KLitAck);
				User::Leave(KErrCancel);
				break;
			case KByteTelnetIac:
				HandleTelnetCommandL();
				break;
			default:
				break;
			}
		}

	Progress(_L("Failed to sync\r\n"));
	SendL(KLitCancel);
	User::Leave(KErrCancel);
	}

CCmdXmodem::TSyncResult CCmdXmodem::SendSyncL()
	{
	Progress(_L("Sending sync...\r\n"));
	iCrc = ETrue;
	const TDesC* syncLit = &KLitC();
	FOREVER
		{
		TBool skipNextSync(EFalse);
		for (TInt numRetries = 0; numRetries < KMaxRetries; ++numRetries)
			{
			if (skipNextSync)
				{
				skipNextSync = EFalse;
				}
			else
				{
				SendL(*syncLit);
				}
			switch (ReceiveByteL(KMediumTimeout))
				{
				case KByteSoh:
					Progress(_L("Using 128 byte block size\r\n"));
					iBlockSize = KSmallBlockSize;
					return ENormal;
				case KByteStx:
					Progress(_L("Using 1024 byte block size\r\n"));
					iBlockSize = KLargeBlockSize;
					return ENormal;
				case KByteCan:
					Progress(_L("Sender cancelled\r\n"));
					SendL(KLitAck);
					PurgeInputL();
					User::Leave(KErrCancel);
					break;
				case KByteEot:
					Progress(_L("Received EOT\r\n"));
					SendL(KLitAck);
					return EEot;
				case KByteTelnetIac:
					HandleTelnetCommandL();
					--numRetries;
					skipNextSync = ETrue; // This is to avoid HyperTerminal complaining about an "Unexpected response".
					break;
				default:
					break;
				}
			}
		if (syncLit == &KLitC)
			{
			Progress(_L("Sender doesn't support CRC\r\n"));
			syncLit = &KLitNak;
			iCrc = EFalse;
			}
		else
			{
			Progress(_L("Failed to sync\r\n"));
			SendL(KLitCancel);
			User::Leave(KErrCancel);
			}
		}
	}

TBool IsValidTelnetCommand(TChar aChar)
	{
	return (aChar == KByteTelnetDo) || (aChar == KByteTelnetWill);
	}

void CCmdXmodem::HandleTelnetCommandL()
	{
	Progress(_L("Received (what looks like) the start of a Telnet command\r\n"));
	TBuf<1> buf;
	TBool timedOut(EFalse);
	ReceiveWithTimeoutL(buf, KShortTimeout, &timedOut);
	if (!timedOut)
		{
		TChar command = buf[0];
		if (IsValidTelnetCommand(command))
			{
			Progress(_L("Valid telnet command received\r\n"));
			timedOut = EFalse;
			ReceiveWithTimeoutL(buf, KShortTimeout, &timedOut);
			if (timedOut)
				{
				Progress(_L("Timed-out, ungetting last character... (2)\r\n"));
				Unget(command);
				}
			else if (buf[0] == KByteTelnetBinaryMode)
				{
				if (command == KByteTelnetDo)
					{
					iTelnetMode &= (~ESendBinary);
					SendL(KLitTelnetWillBinaryMode);
					iTelnetMode |= ESendBinary;
					Progress(_L("Set ESendBinary\r\n"));
					}
				else if (command == KByteTelnetWill)
					{
					TBool enableSend = iTelnetMode & ESendBinary;
					iTelnetMode &= (~ESendBinary);
					SendL(KLitTelnetDoBinaryMode);
					iTelnetMode |= ERecvBinary;
					Progress(_L("Set ERecvBinary\r\n"));
					if (enableSend)
						{
						iTelnetMode |= ESendBinary;
						}
					}
				else
					{
					ASSERT(EFalse);
					}
				}
			else
				{
				Progress(_L("Unknown command option, ungetting last two characters...\r\n"));
				Unget(command);
				Unget(buf[0]);
				}
			}
		else
			{
			Progress(_L("Timed-out, ungetting last character... (1)\r\n"));
			Unget(command);
			}
		}
	Progress(_L("Telnet command handled - resuming...\r\n"));
	}

void CCmdXmodem::Unget(TChar aChar)
	{
	ASSERT(iUngetBuf.Length() <= 1);
	iUngetBuf.Append(aChar);
	}

void CCmdXmodem::PrepareConsoleToTransferL()
	{
	Write(_L("Please start the file transfer in your terminal...\r\n"));
	User::LeaveIfError(Stdin().CaptureAllKeys());			// To prevent other things (like fshell) interpreting binary data as special key presses (like cntrl-C).
	LeaveIfErr(Stdin().SetMode(RIoReadWriteHandle::EBinary), _L("Unable to set stdin to binary mode"));	// To prevent vt100cons from scanning for ANSI escape sequences. 
	LeaveIfErr(Stdout().SetMode(RIoReadWriteHandle::EBinary), _L("Unable to set stdout to binary mode")); // To tell iosrv to not mess about with line endings.
	}

void CCmdXmodem::CleanupClonsoleAfterTransferL()
	{
	PurgeInputL();
	LeaveIfErr(Stdin().SetMode(RIoReadWriteHandle::EText), _L("Unable to set stdin back to text mode"));
	LeaveIfErr(Stdout().SetMode(RIoReadWriteHandle::EText), _L("Unable to set stdout back to text mode"));
	if (Stdout().AttachedToConsole())
		{
		RIoConsoleWriteHandle stdout = Stdout();
		TPoint pos(stdout.GetCursorPosL());
		stdout.SetCursorPosAbsL(TPoint(0, pos.iY));
		stdout.ClearToEndOfLineL();
		}
	}

void CCmdXmodem::SendBlockL(const TDesC& aBlock)
	{
	Progress(_L("Sending block...\r\n"));
	ASSERT(aBlock.Length() <= iBlockSize);

	if (iBuf == NULL)
		{
		iBuf = HBufC::NewL(iBlockSize + ProtocolOverhead());
		}

	TPtr buf(iBuf->Des());

	FOREVER
		{
		buf.Zero();
		buf.Append((TUint16)KByteSoh);
		buf.Append((TUint16)iPacketNumber);
		buf.Append((TUint16)((TUint8)(~iPacketNumber)));
		buf.Append(aBlock);
		if (aBlock.Length() < iBlockSize)
			{
			// There's not enough data to fill this block, so pad with SUB.
			buf.AppendFill((TUint16)KByteSub, iBlockSize - aBlock.Length());
			}
		if (iCrc)
			{
			TUint16 crc = Crc16(buf.Mid(3, iBlockSize));
			buf.Append((crc >> 8) & 0xFF);
			buf.Append(crc & 0xFF);
			}
		else
			{
			TUint8 sum = 0;
			for (TInt i = 3; i < (iBlockSize + 3); ++i)
				{
				sum += (*iBuf)[i];
				}
			buf.Append((TUint16)sum);
			}

		Progress(_L("Sending block %d\r\n"), iPacketNumber);

		for (TInt numRetries = 0; numRetries < KMaxRetries; ++numRetries)
			{
			SendL(*iBuf);
			TBool timedOut(EFalse);
			TUint8 byte = ReceiveByteL(KLongTimeout, &timedOut);
			if (timedOut)
				{
				User::Leave(KErrTimedOut);
				}
			switch (byte) 
				{
				case KByteAck:
					Progress(_L("Block %d successfully sent\r\n"), iPacketNumber);
					++iPacketNumber;
					return;
				case KByteCan:
					Progress(_L("Receiver cancelled\r\n"));
					SendL(KLitAck);
					PurgeInputL();
					User::Leave(KErrCancel);
					break;
				case KByteNak:
					Progress(_L("Block NAK'd, sending again...\r\n"));
					break;
				default:
					Progress(_L("Unexpected response (0x%x), sending again...\r\n"), byte);
					break;
				}
			}
		}
	}

TPtrC CCmdXmodem::ReceiveBlockL(TBool aIsFirstBlock, TBool& aIsFinalBlock)
	{
	_LIT(KFirst, "first ");
	Progress(_L("Receiving %Sblock...\r\n"), aIsFirstBlock ? &KFirst : &KNullDesC);
	TPtrC ret(NULL, 0);
	TInt repeats = 0;
	aIsFinalBlock = EFalse;

	if (iBuf == NULL)
		{
		iBuf = HBufC::NewL(iBlockSize + ProtocolOverhead());
		}
	if (!aIsFirstBlock)	// The first byte of the first block should have already been read by SendSyncL.
		{
again:
		iBuf->Des().Zero();
		TInt numRetries;
		for (numRetries = 0; numRetries < KMaxRetries; ++numRetries)
			{
			TBool startReceiving(EFalse);
			Progress(_L("Receiving first byte of block...\r\n"));
			TBool timedOut(EFalse);
			TUint8 c = ReceiveByteL(KMediumTimeout, &timedOut);
			if (timedOut)
				{
				Progress(_L("Timed out - sending NAK...\r\n"));
				SendL(KLitNak);
				}
			else
				{
				switch (c)
					{
					case KByteSoh:
						if (iBlockSize != KSmallBlockSize)
							{
							Progress(_L("Block size set to 128 bytes\r\n"));
							iBlockSize = KSmallBlockSize;
							}
						startReceiving = ETrue;
						break;
					case KByteStx:
						if (iBlockSize != KLargeBlockSize)
							{
							Progress(_L("Block size set to 1024 bytes\r\n"));
							iBlockSize = KLargeBlockSize;
							}
						startReceiving = ETrue;
						break;
					case KByteEot:
						Progress(_L("Last block received\r\n"));
						aIsFinalBlock = ETrue;
						SendL(KLitAck);
						return ret;
					case KByteCan:
						Progress(_L("Sender cancelled\r\n"));
						SendL(KLitAck);
						PurgeInputL();
						User::Leave(KErrCancel);
						break;
					default:
						break;
					}

				if (startReceiving)
					{
					break;
					}
				}
			}

		if (numRetries == KMaxRetries)
			{
			Progress(_L("Failed to receive valid block\r\n"));
			User::Leave(KErrCommsLineFail);
			}
		}

	if (iBuf->Des().MaxLength() < (iBlockSize + ProtocolOverhead()))
		{
		Progress(_L("Reallocating buffer from %d to %d bytes\r\n"), iBuf->Des().MaxLength(), iBlockSize + ProtocolOverhead());
		iBuf = iBuf->ReAllocL(iBlockSize + ProtocolOverhead());
		}

	Progress(_L("Receiving remainder of block...\r\n"));
	TPtr ptr(const_cast<TUint16*>(iBuf->Ptr()), 0, iBlockSize + ProtocolOverhead() - 1); // - 1 because the first byte of the header isn't in the buffer. Note, can't use HBufC::Des because the max length of the resulting TPtr could be larger than the amount of data we are expecting.
	TBool timeoutOccurred;
	ReceiveWithTimeoutL(ptr, KLongTimeout, &timeoutOccurred);
	if (timeoutOccurred)
		{
		Progress(_L("Timed out, retrying...\r\n"));
		SendL(KLitNak);
		PurgeInputL();
		goto again;
		}
	iBuf->Des().SetLength(ptr.Length());

	TUint8 b0 = (*iBuf)[0];
	TUint8 b1 = (*iBuf)[1];
	// workaround for a compiler bug: and b0 and ~b1 with 0xff, otherwise some compilers will compare
	// the full 32-bit values, the top 3 bytes of which contain garbage. Observed with WINSW and GCCE
	// compilers.
	if (((b0 & 0xff) == ((~b1) & 0xff)) && ((b0 == iPacketNumber) || (b0 == (iPacketNumber - 1))) && CheckBlock())
		{
		Progress(_L("Block successfully received\r\n"));
		if ((*iBuf)[0] == iPacketNumber)
			{
			Progress(_L("Packet number matched (%d)\r\n"), iPacketNumber);
			ret.Set(iBuf->Mid(2, iBuf->Length() - CheckSize() - 2));
			++iPacketNumber;
			}
		repeats = 0;
		SendL(KLitAck);
		}
	else if (++repeats >= KMaxRetries)
		{
		Progress(_L("Block not successfully received after %d retries, cancelling...\r\n"), KMaxRetries);
		SendL(KLitCancel);
		PurgeInputL();
		User::Leave(KErrCancel);
		}
	else
		{
		Progress(_L("Block not successfully received, retrying...\r\n"));
		SendL(KLitNak);
		goto again;
		}

	return ret;
	}

void CCmdXmodem::SendTerminateL()
	{
	for (TInt numRetries = 0; numRetries < KMaxRetries; ++numRetries)
		{
		SendL(KLitEot);
		if (ReceiveByteL(KMediumTimeout) == KByteAck)
			{
			return;
			}
		}
	PurgeInputL();
	User::Leave(KErrCompletion);
	}

void CCmdXmodem::SendStdinL()
	{
	HBufC* buf = HBufC::NewLC(iBlockSize);
	TPtr bufPtr(buf->Des());

	while (Stdin().Read(bufPtr) == KErrNone)
		{
		SendBlockL(*buf);
		bufPtr.Zero();
		}
	SendTerminateL();

	CleanupStack::PopAndDestroy(buf);
	}

void CCmdXmodem::SendFileL(const TDesC& aFileName)
	{
	Progress(_L("Sending file \"%S\"...\r\n"), &aFileName);
	RFile file;
	User::LeaveIfError(file.Open(FsL(), aFileName, EFileRead));
	CleanupClosePushL(file);

	HBufC8* buf = HBufC8::NewLC(iBlockSize);
	HBufC* wideBuf = HBufC::NewLC(iBlockSize);
	TPtr8 bufPtr(buf->Des());
	while ((file.Read(bufPtr, iBlockSize) == KErrNone) && (buf->Length() > 0))
		{
		wideBuf->Des().Copy(*buf);
		SendBlockL(*wideBuf);
		}
	SendTerminateL();

	CleanupStack::PopAndDestroy(3, &file);
	}

void CCmdXmodem::ReceiveToStdoutL()
	{
	TBool firstBlock(ETrue);
	TBool finalBlock(EFalse);
	while (!finalBlock)
		{
		TPtrC block(ReceiveBlockL(firstBlock, finalBlock));
		User::LeaveIfError(Stdout().Write(block));
		firstBlock = EFalse;
		}
	}

void CCmdXmodem::ReceiveToFileL(const TDesC& aFileName)
	{
	RFile file;
	if (iOverwrite)
		{
		FsL().MkDirAll(aFileName); // Create the directory if it doesn't already exist
		User::LeaveIfError(file.Replace(FsL(), aFileName, EFileWrite));
		}
	else
		{
		User::LeaveIfError(file.Create(FsL(), aFileName, EFileWrite));
		}
	CleanupClosePushL(file);

	HBufC8* narrowBuf = HBufC8::NewLC(iBlockSize);
	TPtr8 narrowBufPtr(narrowBuf->Des());
	TBool firstBlock(ETrue);
	TBool finalBlock(EFalse);
	while (!finalBlock)
		{
		TPtrC block(ReceiveBlockL(firstBlock, finalBlock));
		firstBlock = EFalse;
		if (block.Length() > narrowBufPtr.MaxLength())
			{
			HBufC8* newNarrowBuf = narrowBuf->ReAllocL(iBuf->Length());
			CleanupStack::Pop(narrowBuf);
			narrowBuf = newNarrowBuf;
			CleanupStack::PushL(narrowBuf);
			narrowBufPtr.Set(narrowBuf->Des());
			}
		narrowBufPtr.Copy(block);
		User::LeaveIfError(file.Write(*narrowBuf));
		narrowBufPtr.Zero();
		}

	CleanupStack::PopAndDestroy(2, &file);
	}

void CCmdXmodem::ReceiveToNullL()
	{
	TBool firstBlock(EFalse);
	TBool finalBlock(EFalse);
	while (!finalBlock)
		{
		ReceiveBlockL(firstBlock, finalBlock);
		firstBlock = EFalse;
		}
	}

const TDesC& CCmdXmodem::Name() const
	{
	_LIT(KName, "xmodem");	
	return KName;
	}

void CCmdXmodem::DoRunL()
	{
	PrepareConsoleToTransferL();

	if (iMode == EReceive)
		{
		if (iFileName.Length() > 0)
			{
			LeaveIfFileExists(iFileName);
			}
		TSyncResult syncResult = SendSyncL();
		if (syncResult == ENormal)
			{
			if (iFileName.Length() > 0)
				{
				ReceiveToFileL(iFileName);
				}
			else
				{
				ReceiveToStdoutL();
				}

			CleanupClonsoleAfterTransferL();
			if (iFileName.Length() > 0)
				{
				Printf(_L("Successfully received \"%S\".\r\n"), &iFileName);
				}
			}
		}
	else if (iMode == ESend)
		{
		if (iFileName.Length() > 0)
			{
			LeaveIfFileNotFound(iFileName);
			}
		WaitForSyncL();
		if (iFileName.Length() > 0)
			{
			SendFileL(iFileName);
			}
		else
			{
			SendStdinL();
			}

		CleanupClonsoleAfterTransferL();
		if (iFileName.Length() > 0)
			{
			Printf(_L("Successfully sent \"%S\".\r\n"), &iFileName);
			}
		else
			{
			Printf(_L("Successfully stdin.\r\n"));
			}
		}
	User::LeaveIfError(Stdin().CancelCaptureAllKeys());
	}

void CCmdXmodem::ArgumentsL(RCommandArgumentList& aArguments)
	{
	_LIT(KArg1, "mode");
	aArguments.AppendEnumL((TInt&)iMode, KArg1);
	_LIT(KArg2, "file_name");
	aArguments.AppendFileNameL(iFileName, KArg2);
	}

void CCmdXmodem::OptionsL(RCommandOptionList& aOptions)
	{
	_LIT(KOptVerbose, "verbose");
	aOptions.AppendBoolL(iVerbose, KOptVerbose);

	_LIT(KOptOverwrite, "overwrite");
	aOptions.AppendBoolL(iOverwrite, KOptOverwrite);
	}

void CCmdXmodem::HandleLeave(TInt aError)
	{
	if (iStarted)
		{
		Abort();
		CleanupClonsoleAfterTransferL();
		}
	CCommandBase::HandleLeave(aError);
	}