core/builtins/xmodem.cpp
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/builtins/xmodem.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,1029 @@
+// 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);
+	}