4.2.1.2 Sample Code
The following C++ code implements a sample decompressor for RDP 8.0 Bulk Compression. Error handling has been omitted for clarity.
-
#include <memory.h> // for memcpy() // RDP8 definitions typedef unsigned __int8 byte; typedef unsigned __int16 uint16; typedef unsigned __int32 uint32; #pragma pack(push, 1) typedef struct { byte descriptor; uint16 segmentCount; uint32 uncompressedSize; // RDP_DATA_SEGMENT first; } RDP_SEGMENTED_DATA; // descriptor values #define SEGMENTED_SINGLE ( 0xE0 ) #define SEGMENTED_MULTIPART ( 0xE1 ) typedef struct { uint32 size; // byte data[size]; } RDP_DATA_SEGMENT; #pragma pack(pop) #define PACKET_COMPRESSED ( 0x20 ) #define PACKET_COMPR_TYPE_RDP8 ( 0x04 ) // token assignments from the spec, sorted by prefixLength typedef struct { int prefixLength; // number of bits in the prefix int prefixCode; // bit pattern of this prefix int valueBits; // number of value bits to read int tokenType; // 0=literal, 1=match uint32 valueBase; // added to the value bits } Token; const Token tokenTable[] = { // len code vbits type vbase { 1, 0, 8, 0, 0 }, // 0 { 5, 17, 5, 1, 0 }, // 10001 { 5, 18, 7, 1, 32 }, // 10010 { 5, 19, 9, 1, 160 }, // 10011 { 5, 20, 10, 1, 672 }, // 10100 { 5, 21, 12, 1, 1696 }, // 10101 { 5, 24, 0, 0, 0x00 }, // 11000 { 5, 25, 0, 0, 0x01 }, // 11001 { 6, 44, 14, 1, 5792 }, // 101100 { 6, 45, 15, 1, 22176 }, // 101101 { 6, 52, 0, 0, 0x02 }, // 110100 { 6, 53, 0, 0, 0x03 }, // 110101 { 6, 54, 0, 0, 0xFF }, // 110110 { 7, 92, 18, 1, 54944 }, // 1011100 { 7, 93, 20, 1, 317088 }, // 1011101 { 7, 110, 0, 0, 0x04 }, // 1101110 { 7, 111, 0, 0, 0x05 }, // 1101111 { 7, 112, 0, 0, 0x06 }, // 1110000 { 7, 113, 0, 0, 0x07 }, // 1110001 { 7, 114, 0, 0, 0x08 }, // 1110010 { 7, 115, 0, 0, 0x09 }, // 1110011 { 7, 116, 0, 0, 0x0A }, // 1110100 { 7, 117, 0, 0, 0x0B }, // 1110101 { 7, 118, 0, 0, 0x3A }, // 1110110 { 7, 119, 0, 0, 0x3B }, // 1110111 { 7, 120, 0, 0, 0x3C }, // 1111000 { 7, 121, 0, 0, 0x3D }, // 1111001 { 7, 122, 0, 0, 0x3E }, // 1111010 { 7, 123, 0, 0, 0x3F }, // 1111011 { 7, 124, 0, 0, 0x40 }, // 1111100 { 7, 125, 0, 0, 0x80 }, // 1111101 { 8, 188, 20, 1, 1365664 }, // 10111100 { 8, 189, 21, 1, 2414240 }, // 10111101 { 8, 252, 0, 0, 0x0C }, // 11111100 { 8, 253, 0, 0, 0x38 }, // 11111101 { 8, 254, 0, 0, 0x39 }, // 11111110 { 8, 255, 0, 0, 0x66 }, // 11111111 { 9, 380, 22, 1, 4511392 }, // 101111100 { 9, 381, 23, 1, 8705696 }, // 101111101 { 9, 382, 24, 1, 17094304 }, // 101111110 { 0 } }; class Rdp8Decompressor { public: Rdp8Decompressor() { m_historyIndex = 0; } // input buffer byte * m_pbInputCurrent; // ptr into input bytes byte * m_pbInputEnd; // ptr past end of input // input bit stream uint32 m_cBitsRemaining; // # bits input remaining uint32 m_BitsCurrent; // remainder of most-recent byte uint32 m_cBitsCurrent; // number of bits in m_BitsCurrent // decompressed output byte m_outputBuffer[65536]; // most-recent Decompress result uint32 m_outputCount; // length in m_outputBuffer // decompression history byte m_historyBuffer[2500000]; // last N bytes of output uint32 m_historyIndex; // index for next byte out // decompress, return data in an allocated buffer void Decompress( byte * pbInput, int cbInput, byte ** ppbOutput, int * pcbOutput ) { RDP_SEGMENTED_DATA * pSegmentedData = (RDP_SEGMENTED_DATA *) pbInput; if (pSegmentedData->descriptor == SEGMENTED_SINGLE) { OutputFromSegment(pbInput + 1, cbInput - 1); *ppbOutput = new byte[m_outputCount]; *pcbOutput = m_outputCount; memcpy(*ppbOutput, m_outputBuffer, m_outputCount); } else if (pSegmentedData->descriptor == SEGMENTED_MULTIPART) { uint32 segmentOffset = sizeof(RDP_SEGMENTED_DATA); byte * pConcatenated = new byte[pSegmentedData->uncompressedSize]; *ppbOutput = pConcatenated; *pcbOutput = pSegmentedData->uncompressedSize; for (uint16 segmentNumber = 0; segmentNumber < pSegmentedData->segmentCount; segmentNumber++) { RDP_DATA_SEGMENT * pSegment = (RDP_DATA_SEGMENT *) (pbInput + segmentOffset); OutputFromSegment( pbInput + segmentOffset + sizeof(RDP_DATA_SEGMENT), pSegment->size); segmentOffset += sizeof(RDP_DATA_SEGMENT) + pSegment->size; memcpy(pConcatenated, m_outputBuffer, m_outputCount); pConcatenated += m_outputCount; } } } // decompress one segment into m_outputBuffer void OutputFromSegment( byte * pbSegment, int cbSegment ) { if (pbSegment[0] & PACKET_COMPRESSED) { OutputFromCompressed(pbSegment + 1, cbSegment - 1); } else { OutputFromNotCompressed(pbSegment + 1, cbSegment - 1); } } // decompress an unencoded segment into m_outputBuffer void OutputFromNotCompressed( byte * pbRaw, int cbRaw ) { m_outputCount = 0; for (int iRaw = 0; iRaw < cbRaw; iRaw++) { byte c = pbRaw[iRaw]; m_historyBuffer[m_historyIndex++] = c; if (m_historyIndex == sizeof(m_historyBuffer)) { m_historyIndex = 0; } m_outputBuffer[m_outputCount++] = c; } } // decompress a Huffman-encoded segment into m_outputBuffer void OutputFromCompressed( byte * pbEncoded, int cbEncoded ) { m_outputCount = 0; m_pbInputCurrent = pbEncoded; m_pbInputEnd = pbEncoded + cbEncoded - 1; m_cBitsRemaining = 8 * (cbEncoded - 1) - *m_pbInputEnd; m_cBitsCurrent = 0; m_BitsCurrent = 0; while (m_cBitsRemaining) { int haveBits = 0; int inPrefix = 0; byte c; uint32 count; uint32 distance; // Scan the token table, considering more bits as needed, // until the resulting token is found. for (int opIndex = 0; tokenTable[opIndex].prefixLength != 0; opIndex++) { // get more bits if needed while (haveBits < tokenTable[opIndex].prefixLength) { inPrefix = (inPrefix << 1) + GetBits(1); haveBits++; } if (inPrefix == tokenTable[opIndex].prefixCode) { if (tokenTable[opIndex].tokenType == 0) { c = (byte)(tokenTable[opIndex].valueBase + GetBits(tokenTable[opIndex].valueBits)); goto output_literal; } else { distance = tokenTable[opIndex].valueBase + GetBits(tokenTable[opIndex].valueBits); if (distance != 0) { if (GetBits(1) == 0) { count = 3; } else { count = 4; int extra = 2; while (GetBits(1) == 1) { count *= 2; extra++; } count += GetBits(extra); } goto output_match; } else // match distance == 0 is special case { count = GetBits(15); // discard remaining bits m_cBitsRemaining -= m_cBitsCurrent; m_cBitsCurrent = 0; m_BitsCurrent = 0; goto output_unencoded; } } } } break; output_literal: // Add one byte 'c' to output and history m_historyBuffer[m_historyIndex] = c; if (++m_historyIndex == sizeof(m_historyBuffer)) { m_historyIndex = 0; } m_outputBuffer[m_outputCount++] = c; continue; output_match: // Add 'count' bytes from 'distance' back in history. // Output these bytes again, and add to history again. uint32 prevIndex = m_historyIndex + sizeof(m_historyBuffer) - distance; prevIndex = prevIndex % sizeof(m_historyBuffer); // n.b. memcpy or movsd, for example, will not work here. // Overlapping matches have to replicate. movsb might work. while (count--) { c = m_historyBuffer[prevIndex]; if (++prevIndex == sizeof(m_historyBuffer)) { prevIndex = 0; } m_historyBuffer[m_historyIndex] = c; if (++m_historyIndex == sizeof(m_historyBuffer)) { m_historyIndex = 0; } m_outputBuffer[m_outputCount] = c; ++m_outputCount; } continue; output_unencoded: // Copy 'count' bytes from stream input to output // and add to history. while (count--) { c = *m_pbInputCurrent++; m_cBitsRemaining -= 8; m_historyBuffer[m_historyIndex] = c; if (++m_historyIndex == sizeof(m_historyBuffer)) { m_historyIndex = 0; } m_outputBuffer[m_outputCount] = c; ++m_outputCount; } continue; } } // Return the value of the next 'bitCount' bits as unsigned. uint32 GetBits( uint32 bitCount ) { while (m_cBitsCurrent < bitCount) { m_BitsCurrent <<= 8; if (m_pbInputCurrent < m_pbInputEnd) { m_BitsCurrent += *m_pbInputCurrent++; } m_cBitsCurrent += 8; } m_cBitsRemaining -= bitCount; m_cBitsCurrent -= bitCount; uint32 result = m_BitsCurrent >> m_cBitsCurrent; m_BitsCurrent &= ((1 << m_cBitsCurrent) - 1); return result; } };