/** * FreeRDP: A Remote Desktop Protocol Implementation * XCrush (RDP6.1) Bulk Data Compression * * Copyright 2014 Marc-Andre Moreau * Copyright 2017 Armin Novak * Copyright 2017 Thincast Technologies GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include "xcrush.h" #pragma pack(push, 1) typedef struct { UINT32 MatchOffset; UINT32 ChunkOffset; UINT32 MatchLength; } XCRUSH_MATCH_INFO; typedef struct { UINT32 offset; UINT32 next; } XCRUSH_CHUNK; typedef struct { UINT16 seed; UINT16 size; } XCRUSH_SIGNATURE; typedef struct { UINT16 MatchLength; UINT16 MatchOutputOffset; UINT32 MatchHistoryOffset; } RDP61_MATCH_DETAILS; typedef struct { BYTE Level1ComprFlags; BYTE Level2ComprFlags; UINT16 MatchCount; RDP61_MATCH_DETAILS* MatchDetails; BYTE* Literals; } RDP61_COMPRESSED_DATA; #pragma pack(pop) struct s_XCRUSH_CONTEXT { ALIGN64 BOOL Compressor; ALIGN64 MPPC_CONTEXT* mppc; ALIGN64 BYTE* HistoryPtr; ALIGN64 UINT32 HistoryOffset; ALIGN64 UINT32 HistoryBufferSize; ALIGN64 BYTE HistoryBuffer[2000000]; ALIGN64 BYTE BlockBuffer[16384]; ALIGN64 UINT32 CompressionFlags; ALIGN64 UINT32 SignatureIndex; ALIGN64 UINT32 SignatureCount; ALIGN64 XCRUSH_SIGNATURE Signatures[1000]; ALIGN64 UINT32 ChunkHead; ALIGN64 UINT32 ChunkTail; ALIGN64 XCRUSH_CHUNK Chunks[65534]; ALIGN64 UINT16 NextChunks[65536]; ALIGN64 UINT32 OriginalMatchCount; ALIGN64 UINT32 OptimizedMatchCount; ALIGN64 XCRUSH_MATCH_INFO OriginalMatches[1000]; ALIGN64 XCRUSH_MATCH_INFO OptimizedMatches[1000]; }; //#define DEBUG_XCRUSH 1 #if defined(DEBUG_XCRUSH) static const char* xcrush_get_level_2_compression_flags_string(UINT32 flags) { flags &= 0xE0; if (flags == 0) return "PACKET_UNCOMPRESSED"; else if (flags == PACKET_COMPRESSED) return "PACKET_COMPRESSED"; else if (flags == PACKET_AT_FRONT) return "PACKET_AT_FRONT"; else if (flags == PACKET_FLUSHED) return "PACKET_FLUSHED"; else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT)) return "PACKET_COMPRESSED | PACKET_AT_FRONT"; else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED)) return "PACKET_COMPRESSED | PACKET_FLUSHED"; else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED)) return "PACKET_AT_FRONT | PACKET_FLUSHED"; else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED)) return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED"; return "PACKET_UNKNOWN"; } static const char* xcrush_get_level_1_compression_flags_string(UINT32 flags) { flags &= 0x17; if (flags == 0) return "L1_UNKNOWN"; else if (flags == L1_PACKET_AT_FRONT) return "L1_PACKET_AT_FRONT"; else if (flags == L1_NO_COMPRESSION) return "L1_NO_COMPRESSION"; else if (flags == L1_COMPRESSED) return "L1_COMPRESSED"; else if (flags == L1_INNER_COMPRESSION) return "L1_INNER_COMPRESSION"; else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION)) return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION"; else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED)) return "L1_PACKET_AT_FRONT | L1_COMPRESSED"; else if (flags == (L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION)) return "L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION"; else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED)) return "L1_NO_COMPRESSION | L1_COMPRESSED"; else if (flags == (L1_NO_COMPRESSION | L1_INNER_COMPRESSION)) return "L1_NO_COMPRESSION | L1_INNER_COMPRESSION"; else if (flags == (L1_COMPRESSED | L1_INNER_COMPRESSION)) return "L1_COMPRESSED | L1_INNER_COMPRESSION"; else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION)) return "L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION"; else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION)) return "L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION"; else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION)) return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION"; else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED)) return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED"; else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION)) return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION"; return "L1_UNKNOWN"; } #endif static UINT32 xcrush_update_hash(const BYTE* WINPR_RESTRICT data, UINT32 size) { const BYTE* end = NULL; UINT32 seed = 5381; /* same value as in djb2 */ WINPR_ASSERT(data); WINPR_ASSERT(size >= 4); if (size > 32) { size = 32; seed = 5413; } end = &data[size - 4]; while (data < end) { seed += (data[3] ^ data[0]) + (data[1] << 8); data += 4; } return (UINT16)seed; } static int xcrush_append_chunk(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT data, UINT32* WINPR_RESTRICT beg, UINT32 end) { UINT32 size = 0; WINPR_ASSERT(xcrush); WINPR_ASSERT(data); WINPR_ASSERT(beg); if (xcrush->SignatureIndex >= xcrush->SignatureCount) return 0; size = end - *beg; if (size > 65535) return 0; if (size >= 15) { UINT32 seed = xcrush_update_hash(&data[*beg], (UINT16)size); xcrush->Signatures[xcrush->SignatureIndex].size = size; xcrush->Signatures[xcrush->SignatureIndex].seed = seed; xcrush->SignatureIndex++; *beg = end; } return 1; } static int xcrush_compute_chunks(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT data, UINT32 size, UINT32* WINPR_RESTRICT pIndex) { UINT32 offset = 0; UINT32 rotation = 0; UINT32 accumulator = 0; WINPR_ASSERT(xcrush); WINPR_ASSERT(data); WINPR_ASSERT(pIndex); *pIndex = 0; xcrush->SignatureIndex = 0; if (size < 128) return 0; for (UINT32 i = 0; i < 32; i++) { rotation = _rotl(accumulator, 1); accumulator = data[i] ^ rotation; } for (UINT32 i = 0; i < size - 64; i++) { rotation = _rotl(accumulator, 1); accumulator = data[i + 32] ^ data[i] ^ rotation; if (!(accumulator & 0x7F)) { if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) return 0; } i++; rotation = _rotl(accumulator, 1); accumulator = data[i + 32] ^ data[i] ^ rotation; if (!(accumulator & 0x7F)) { if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) return 0; } i++; rotation = _rotl(accumulator, 1); accumulator = data[i + 32] ^ data[i] ^ rotation; if (!(accumulator & 0x7F)) { if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) return 0; } i++; rotation = _rotl(accumulator, 1); accumulator = data[i + 32] ^ data[i] ^ rotation; if (!(accumulator & 0x7F)) { if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) return 0; } } if ((size == offset) || xcrush_append_chunk(xcrush, data, &offset, size)) { *pIndex = xcrush->SignatureIndex; return 1; } return 0; } static UINT32 xcrush_compute_signatures(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT data, UINT32 size) { UINT32 index = 0; if (xcrush_compute_chunks(xcrush, data, size, &index)) return index; return 0; } static void xcrush_clear_hash_table_range(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, UINT32 beg, UINT32 end) { WINPR_ASSERT(xcrush); for (UINT32 index = 0; index < 65536; index++) { if (xcrush->NextChunks[index] >= beg) { if (xcrush->NextChunks[index] <= end) { xcrush->NextChunks[index] = 0; } } } for (UINT32 index = 0; index < 65534; index++) { if (xcrush->Chunks[index].next >= beg) { if (xcrush->Chunks[index].next <= end) { xcrush->Chunks[index].next = 0; } } } } static int xcrush_find_next_matching_chunk(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, XCRUSH_CHUNK* WINPR_RESTRICT chunk, XCRUSH_CHUNK** WINPR_RESTRICT pNextChunk) { XCRUSH_CHUNK* next = NULL; WINPR_ASSERT(xcrush); if (!chunk) return -4001; /* error */ if (chunk->next) { UINT32 index = (chunk - xcrush->Chunks) / sizeof(XCRUSH_CHUNK); if (index >= 65534) return -4002; /* error */ if ((index < xcrush->ChunkHead) || (chunk->next >= xcrush->ChunkHead)) { if (chunk->next >= 65534) return -4003; /* error */ next = &xcrush->Chunks[chunk->next]; } } WINPR_ASSERT(pNextChunk); *pNextChunk = next; return 1; } static int xcrush_insert_chunk(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, XCRUSH_SIGNATURE* WINPR_RESTRICT signature, UINT32 offset, XCRUSH_CHUNK** WINPR_RESTRICT pPrevChunk) { UINT32 seed = 0; UINT32 index = 0; WINPR_ASSERT(xcrush); if (xcrush->ChunkHead >= 65530) { xcrush->ChunkHead = 1; xcrush->ChunkTail = 1; } if (xcrush->ChunkHead >= xcrush->ChunkTail) { xcrush_clear_hash_table_range(xcrush, xcrush->ChunkTail, xcrush->ChunkTail + 10000); xcrush->ChunkTail += 10000; } index = xcrush->ChunkHead++; if (xcrush->ChunkHead >= 65534) return -3001; /* error */ xcrush->Chunks[index].offset = offset; seed = signature->seed; if (seed >= 65536) return -3002; /* error */ if (xcrush->NextChunks[seed]) { if (xcrush->NextChunks[seed] >= 65534) return -3003; /* error */ WINPR_ASSERT(pPrevChunk); *pPrevChunk = &xcrush->Chunks[xcrush->NextChunks[seed]]; } xcrush->Chunks[index].next = xcrush->NextChunks[seed] & 0xFFFF; xcrush->NextChunks[seed] = index; return 1; } static int xcrush_find_match_length(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, UINT32 MatchOffset, UINT32 ChunkOffset, UINT32 HistoryOffset, UINT32 SrcSize, UINT32 MaxMatchLength, XCRUSH_MATCH_INFO* WINPR_RESTRICT MatchInfo) { UINT32 MatchSymbol = 0; UINT32 ChunkSymbol = 0; BYTE* ChunkBuffer = NULL; BYTE* MatchBuffer = NULL; BYTE* MatchStartPtr = NULL; BYTE* ForwardChunkPtr = NULL; BYTE* ReverseChunkPtr = NULL; BYTE* ForwardMatchPtr = NULL; BYTE* ReverseMatchPtr = NULL; BYTE* HistoryBufferEnd = NULL; UINT32 ReverseMatchLength = 0; UINT32 ForwardMatchLength = 0; UINT32 TotalMatchLength = 0; BYTE* HistoryBuffer = NULL; UINT32 HistoryBufferSize = 0; WINPR_ASSERT(xcrush); WINPR_ASSERT(MatchInfo); HistoryBuffer = xcrush->HistoryBuffer; HistoryBufferSize = xcrush->HistoryBufferSize; HistoryBufferEnd = &HistoryBuffer[HistoryOffset + SrcSize]; if (MatchOffset > HistoryBufferSize) return -2001; /* error */ MatchBuffer = &HistoryBuffer[MatchOffset]; if (ChunkOffset > HistoryBufferSize) return -2002; /* error */ ChunkBuffer = &HistoryBuffer[ChunkOffset]; if (MatchOffset == ChunkOffset) return -2003; /* error */ if (MatchBuffer < HistoryBuffer) return -2004; /* error */ if (ChunkBuffer < HistoryBuffer) return -2005; /* error */ ForwardMatchPtr = &HistoryBuffer[MatchOffset]; ForwardChunkPtr = &HistoryBuffer[ChunkOffset]; if ((&MatchBuffer[MaxMatchLength + 1] < HistoryBufferEnd) && (MatchBuffer[MaxMatchLength + 1] != ChunkBuffer[MaxMatchLength + 1])) { return 0; } while (1) { MatchSymbol = *ForwardMatchPtr++; ChunkSymbol = *ForwardChunkPtr++; if (MatchSymbol != ChunkSymbol) break; if (ForwardMatchPtr > HistoryBufferEnd) break; ForwardMatchLength++; } ReverseMatchPtr = MatchBuffer - 1; ReverseChunkPtr = ChunkBuffer - 1; while ((ReverseMatchPtr > &HistoryBuffer[HistoryOffset]) && (ReverseChunkPtr > HistoryBuffer) && (*ReverseMatchPtr == *ReverseChunkPtr)) { ReverseMatchLength++; ReverseMatchPtr--; ReverseChunkPtr--; } MatchStartPtr = MatchBuffer - ReverseMatchLength; TotalMatchLength = ReverseMatchLength + ForwardMatchLength; if (TotalMatchLength < 11) return 0; if (MatchStartPtr < HistoryBuffer) return -2006; /* error */ MatchInfo->MatchOffset = MatchStartPtr - HistoryBuffer; MatchInfo->ChunkOffset = ChunkBuffer - ReverseMatchLength - HistoryBuffer; MatchInfo->MatchLength = TotalMatchLength; return (int)TotalMatchLength; } static int xcrush_find_all_matches(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, UINT32 SignatureIndex, UINT32 HistoryOffset, UINT32 SrcOffset, UINT32 SrcSize) { UINT32 j = 0; int status = 0; UINT32 ChunkIndex = 0; UINT32 ChunkCount = 0; XCRUSH_CHUNK* chunk = NULL; UINT32 MatchLength = 0; UINT32 MaxMatchLength = 0; UINT32 PrevMatchEnd = 0; XCRUSH_SIGNATURE* Signatures = NULL; XCRUSH_MATCH_INFO MaxMatchInfo = { 0 }; WINPR_ASSERT(xcrush); Signatures = xcrush->Signatures; for (UINT32 i = 0; i < SignatureIndex; i++) { XCRUSH_MATCH_INFO MatchInfo = { 0 }; UINT32 offset = SrcOffset + HistoryOffset; if (!Signatures[i].size) return -1001; /* error */ status = xcrush_insert_chunk(xcrush, &Signatures[i], offset, &chunk); if (status < 0) return status; if (chunk && (SrcOffset + HistoryOffset + Signatures[i].size >= PrevMatchEnd)) { ChunkCount = 0; MaxMatchLength = 0; while (chunk) { if ((chunk->offset < HistoryOffset) || (chunk->offset < offset) || (chunk->offset > SrcSize + HistoryOffset)) { status = xcrush_find_match_length(xcrush, offset, chunk->offset, HistoryOffset, SrcSize, MaxMatchLength, &MatchInfo); if (status < 0) return status; /* error */ MatchLength = (UINT32)status; if (MatchLength > MaxMatchLength) { MaxMatchLength = MatchLength; MaxMatchInfo.MatchOffset = MatchInfo.MatchOffset; MaxMatchInfo.ChunkOffset = MatchInfo.ChunkOffset; MaxMatchInfo.MatchLength = MatchInfo.MatchLength; if (MatchLength > 256) break; } } ChunkIndex = ChunkCount++; if (ChunkIndex > 4) break; status = xcrush_find_next_matching_chunk(xcrush, chunk, &chunk); if (status < 0) return status; /* error */ } if (MaxMatchLength) { xcrush->OriginalMatches[j].MatchOffset = MaxMatchInfo.MatchOffset; xcrush->OriginalMatches[j].ChunkOffset = MaxMatchInfo.ChunkOffset; xcrush->OriginalMatches[j].MatchLength = MaxMatchInfo.MatchLength; if (xcrush->OriginalMatches[j].MatchOffset < HistoryOffset) return -1002; /* error */ PrevMatchEnd = xcrush->OriginalMatches[j].MatchLength + xcrush->OriginalMatches[j].MatchOffset; j++; if (j >= 1000) return -1003; /* error */ } } SrcOffset += Signatures[i].size; if (SrcOffset > SrcSize) return -1004; /* error */ } if (SrcOffset > SrcSize) return -1005; /* error */ return (int)j; } static int xcrush_optimize_matches(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush) { UINT32 j = 0; UINT32 MatchDiff = 0; UINT32 PrevMatchEnd = 0; UINT32 TotalMatchLength = 0; UINT32 OriginalMatchCount = 0; UINT32 OptimizedMatchCount = 0; XCRUSH_MATCH_INFO* OriginalMatch = NULL; XCRUSH_MATCH_INFO* OptimizedMatch = NULL; XCRUSH_MATCH_INFO* OriginalMatches = NULL; XCRUSH_MATCH_INFO* OptimizedMatches = NULL; WINPR_ASSERT(xcrush); OriginalMatches = xcrush->OriginalMatches; OriginalMatchCount = xcrush->OriginalMatchCount; OptimizedMatches = xcrush->OptimizedMatches; for (UINT32 i = 0; i < OriginalMatchCount; i++) { if (OriginalMatches[i].MatchOffset <= PrevMatchEnd) { if ((OriginalMatches[i].MatchOffset < PrevMatchEnd) && (OriginalMatches[i].MatchLength + OriginalMatches[i].MatchOffset > PrevMatchEnd + 6)) { MatchDiff = PrevMatchEnd - OriginalMatches[i].MatchOffset; OriginalMatch = &OriginalMatches[i]; OptimizedMatch = &OptimizedMatches[j]; OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset; OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset; OptimizedMatch->MatchLength = OriginalMatch->MatchLength; if (OptimizedMatches[j].MatchLength <= MatchDiff) return -5001; /* error */ if (MatchDiff >= 20000) return -5002; /* error */ OptimizedMatches[j].MatchLength -= MatchDiff; OptimizedMatches[j].MatchOffset += MatchDiff; OptimizedMatches[j].ChunkOffset += MatchDiff; PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset; TotalMatchLength += OptimizedMatches[j].MatchLength; j++; } } else { OriginalMatch = &OriginalMatches[i]; OptimizedMatch = &OptimizedMatches[j]; OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset; OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset; OptimizedMatch->MatchLength = OriginalMatch->MatchLength; PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset; TotalMatchLength += OptimizedMatches[j].MatchLength; j++; } } OptimizedMatchCount = j; xcrush->OptimizedMatchCount = OptimizedMatchCount; return (int)TotalMatchLength; } static int xcrush_generate_output(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, BYTE* WINPR_RESTRICT OutputBuffer, UINT32 OutputSize, UINT32 HistoryOffset, UINT32* WINPR_RESTRICT pDstSize) { BYTE* Literals = NULL; BYTE* OutputEnd = NULL; UINT32 MatchIndex = 0; UINT32 MatchOffset = 0; UINT16 MatchLength = 0; UINT32 MatchCount = 0; UINT32 CurrentOffset = 0; UINT32 MatchOffsetDiff = 0; UINT32 HistoryOffsetDiff = 0; RDP61_MATCH_DETAILS* MatchDetails = NULL; WINPR_ASSERT(xcrush); WINPR_ASSERT(OutputBuffer); WINPR_ASSERT(OutputSize >= 2); WINPR_ASSERT(pDstSize); MatchCount = xcrush->OptimizedMatchCount; OutputEnd = &OutputBuffer[OutputSize]; if (&OutputBuffer[2] >= &OutputBuffer[OutputSize]) return -6001; /* error */ Data_Write_UINT16(OutputBuffer, MatchCount); MatchDetails = (RDP61_MATCH_DETAILS*)&OutputBuffer[2]; Literals = (BYTE*)&MatchDetails[MatchCount]; if (Literals > OutputEnd) return -6002; /* error */ for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++) { Data_Write_UINT16(&MatchDetails[MatchIndex].MatchLength, xcrush->OptimizedMatches[MatchIndex].MatchLength); Data_Write_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset, xcrush->OptimizedMatches[MatchIndex].MatchOffset - HistoryOffset); Data_Write_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset, xcrush->OptimizedMatches[MatchIndex].ChunkOffset); } CurrentOffset = HistoryOffset; for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++) { MatchLength = (UINT16)(xcrush->OptimizedMatches[MatchIndex].MatchLength); MatchOffset = xcrush->OptimizedMatches[MatchIndex].MatchOffset; if (MatchOffset <= CurrentOffset) { if (MatchOffset != CurrentOffset) return -6003; /* error */ CurrentOffset = MatchOffset + MatchLength; } else { MatchOffsetDiff = MatchOffset - CurrentOffset; if (Literals + MatchOffset - CurrentOffset >= OutputEnd) return -6004; /* error */ CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], MatchOffsetDiff); if (Literals >= OutputEnd) return -6005; /* error */ Literals += MatchOffsetDiff; CurrentOffset = MatchOffset + MatchLength; } } HistoryOffsetDiff = xcrush->HistoryOffset - CurrentOffset; if (Literals + HistoryOffsetDiff >= OutputEnd) return -6006; /* error */ CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], HistoryOffsetDiff); *pDstSize = Literals + HistoryOffsetDiff - OutputBuffer; return 1; } static INLINE size_t xcrush_copy_bytes_no_overlap(BYTE* WINPR_RESTRICT dst, const BYTE* WINPR_RESTRICT src, size_t num) { // src and dst overlaps // we should copy the area that doesn't overlap repeatly const size_t diff = (dst > src) ? dst - src : src - dst; const size_t rest = num % diff; const size_t end = num - rest; for (size_t a = 0; a < end; a += diff) memcpy(&dst[a], &src[a], diff); if (rest != 0) memcpy(&dst[end], &src[end], rest); return num; } static INLINE size_t xcrush_copy_bytes(BYTE* dst, const BYTE* src, size_t num) { WINPR_ASSERT(dst); WINPR_ASSERT(src); if (src + num < dst || src > dst + num) memcpy(dst, src, num); else if (src != dst) return xcrush_copy_bytes_no_overlap(dst, src, num); return num; } static int xcrush_decompress_l1(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize, const BYTE** WINPR_RESTRICT ppDstData, UINT32* WINPR_RESTRICT pDstSize, UINT32 flags) { const BYTE* pSrcEnd = NULL; const BYTE* Literals = NULL; UINT16 MatchCount = 0; UINT16 MatchIndex = 0; BYTE* OutputPtr = NULL; size_t OutputLength = 0; UINT32 OutputOffset = 0; BYTE* HistoryPtr = NULL; BYTE* HistoryBuffer = NULL; BYTE* HistoryBufferEnd = NULL; UINT32 HistoryBufferSize = 0; UINT16 MatchLength = 0; UINT16 MatchOutputOffset = 0; UINT32 MatchHistoryOffset = 0; const RDP61_MATCH_DETAILS* MatchDetails = NULL; WINPR_ASSERT(xcrush); if (SrcSize < 1) return -1001; WINPR_ASSERT(pSrcData); WINPR_ASSERT(ppDstData); WINPR_ASSERT(pDstSize); if (flags & L1_PACKET_AT_FRONT) xcrush->HistoryOffset = 0; pSrcEnd = &pSrcData[SrcSize]; HistoryBuffer = xcrush->HistoryBuffer; HistoryBufferSize = xcrush->HistoryBufferSize; HistoryBufferEnd = &(HistoryBuffer[HistoryBufferSize]); xcrush->HistoryPtr = HistoryPtr = &(HistoryBuffer[xcrush->HistoryOffset]); if (flags & L1_NO_COMPRESSION) { Literals = pSrcData; } else { if (!(flags & L1_COMPRESSED)) return -1002; if ((pSrcData + 2) > pSrcEnd) return -1003; Data_Read_UINT16(pSrcData, MatchCount); MatchDetails = (const RDP61_MATCH_DETAILS*)&pSrcData[2]; Literals = (const BYTE*)&MatchDetails[MatchCount]; OutputOffset = 0; if (Literals > pSrcEnd) return -1004; for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++) { Data_Read_UINT16(&MatchDetails[MatchIndex].MatchLength, MatchLength); Data_Read_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset, MatchOutputOffset); Data_Read_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset, MatchHistoryOffset); if (MatchOutputOffset < OutputOffset) return -1005; if (MatchLength > HistoryBufferSize) return -1006; if (MatchHistoryOffset > HistoryBufferSize) return -1007; OutputLength = MatchOutputOffset - OutputOffset; if ((MatchOutputOffset - OutputOffset) > HistoryBufferSize) return -1008; if (OutputLength > 0) { if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (Literals >= pSrcEnd) || (&Literals[OutputLength] > pSrcEnd)) return -1009; xcrush_copy_bytes(HistoryPtr, Literals, OutputLength); HistoryPtr += OutputLength; Literals += OutputLength; OutputOffset += OutputLength; if (Literals > pSrcEnd) return -1010; } OutputPtr = &xcrush->HistoryBuffer[MatchHistoryOffset]; if ((&HistoryPtr[MatchLength] >= HistoryBufferEnd) || (&OutputPtr[MatchLength] >= HistoryBufferEnd)) return -1011; xcrush_copy_bytes(HistoryPtr, OutputPtr, MatchLength); OutputOffset += MatchLength; HistoryPtr += MatchLength; } } if (Literals < pSrcEnd) { OutputLength = pSrcEnd - Literals; if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (&Literals[OutputLength] > pSrcEnd)) return -1012; xcrush_copy_bytes(HistoryPtr, Literals, OutputLength); HistoryPtr += OutputLength; } xcrush->HistoryOffset = HistoryPtr - HistoryBuffer; *pDstSize = HistoryPtr - xcrush->HistoryPtr; *ppDstData = xcrush->HistoryPtr; return 1; } int xcrush_decompress(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize, const BYTE** WINPR_RESTRICT ppDstData, UINT32* WINPR_RESTRICT pDstSize, UINT32 flags) { int status = 0; UINT32 DstSize = 0; const BYTE* pDstData = NULL; BYTE Level1ComprFlags = 0; BYTE Level2ComprFlags = 0; WINPR_ASSERT(xcrush); if (SrcSize < 2) return -1; WINPR_ASSERT(pSrcData); WINPR_ASSERT(ppDstData); WINPR_ASSERT(pDstSize); Level1ComprFlags = pSrcData[0]; Level2ComprFlags = pSrcData[1]; pSrcData += 2; SrcSize -= 2; if (flags & PACKET_FLUSHED) { ZeroMemory(xcrush->HistoryBuffer, xcrush->HistoryBufferSize); xcrush->HistoryOffset = 0; } if (!(Level2ComprFlags & PACKET_COMPRESSED)) { status = xcrush_decompress_l1(xcrush, pSrcData, SrcSize, ppDstData, pDstSize, Level1ComprFlags); return status; } status = mppc_decompress(xcrush->mppc, pSrcData, SrcSize, &pDstData, &DstSize, Level2ComprFlags); if (status < 0) return status; status = xcrush_decompress_l1(xcrush, pDstData, DstSize, ppDstData, pDstSize, Level1ComprFlags); return status; } static int xcrush_compress_l1(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize, BYTE* WINPR_RESTRICT pDstData, UINT32* WINPR_RESTRICT pDstSize, UINT32* WINPR_RESTRICT pFlags) { int status = 0; UINT32 Flags = 0; UINT32 HistoryOffset = 0; BYTE* HistoryPtr = NULL; BYTE* HistoryBuffer = NULL; UINT32 SignatureIndex = 0; WINPR_ASSERT(xcrush); WINPR_ASSERT(pSrcData); WINPR_ASSERT(SrcSize > 0); WINPR_ASSERT(pDstData); WINPR_ASSERT(pDstSize); WINPR_ASSERT(pFlags); if (xcrush->HistoryOffset + SrcSize + 8 > xcrush->HistoryBufferSize) { xcrush->HistoryOffset = 0; Flags |= L1_PACKET_AT_FRONT; } HistoryOffset = xcrush->HistoryOffset; HistoryBuffer = xcrush->HistoryBuffer; HistoryPtr = &HistoryBuffer[HistoryOffset]; MoveMemory(HistoryPtr, pSrcData, SrcSize); xcrush->HistoryOffset += SrcSize; if (SrcSize > 50) { SignatureIndex = xcrush_compute_signatures(xcrush, pSrcData, SrcSize); if (SignatureIndex) { status = xcrush_find_all_matches(xcrush, SignatureIndex, HistoryOffset, 0, SrcSize); if (status < 0) return status; xcrush->OriginalMatchCount = (UINT32)status; xcrush->OptimizedMatchCount = 0; if (xcrush->OriginalMatchCount) { status = xcrush_optimize_matches(xcrush); if (status < 0) return status; } if (xcrush->OptimizedMatchCount) { status = xcrush_generate_output(xcrush, pDstData, SrcSize, HistoryOffset, pDstSize); if (status < 0) return status; Flags |= L1_COMPRESSED; } } } if (!(Flags & L1_COMPRESSED)) { Flags |= L1_NO_COMPRESSION; *pDstSize = SrcSize; } *pFlags = Flags; return 1; } int xcrush_compress(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize, BYTE* WINPR_RESTRICT pDstBuffer, const BYTE** WINPR_RESTRICT ppDstData, UINT32* WINPR_RESTRICT pDstSize, UINT32* WINPR_RESTRICT pFlags) { int status = 0; UINT32 DstSize = 0; BYTE* pDstData = NULL; const BYTE* CompressedData = NULL; UINT32 CompressedDataSize = 0; BYTE* OriginalData = NULL; UINT32 OriginalDataSize = 0; UINT32 Level1ComprFlags = 0; UINT32 Level2ComprFlags = 0; UINT32 CompressionLevel = 3; WINPR_ASSERT(xcrush); WINPR_ASSERT(pSrcData); WINPR_ASSERT(SrcSize > 0); WINPR_ASSERT(ppDstData); WINPR_ASSERT(pDstSize); WINPR_ASSERT(pFlags); if (SrcSize > 16384) return -1001; if ((SrcSize + 2) > *pDstSize) return -1002; OriginalData = pDstBuffer; *ppDstData = pDstBuffer; OriginalDataSize = SrcSize; pDstData = xcrush->BlockBuffer; CompressedDataSize = SrcSize; status = xcrush_compress_l1(xcrush, pSrcData, SrcSize, pDstData, &CompressedDataSize, &Level1ComprFlags); if (status < 0) return status; if (Level1ComprFlags & L1_COMPRESSED) { CompressedData = pDstData; if (CompressedDataSize > SrcSize) return -1003; } else { CompressedData = pSrcData; if (CompressedDataSize != SrcSize) return -1004; } status = 0; pDstData = &OriginalData[2]; DstSize = OriginalDataSize - 2; if (CompressedDataSize > 50) { const BYTE* pUnusedDstData = NULL; status = mppc_compress(xcrush->mppc, CompressedData, CompressedDataSize, pDstData, &pUnusedDstData, &DstSize, &Level2ComprFlags); } if (status < 0) return status; if (!status || (Level2ComprFlags & PACKET_FLUSHED)) { if (CompressedDataSize > DstSize) { xcrush_context_reset(xcrush, TRUE); *ppDstData = pSrcData; *pDstSize = SrcSize; *pFlags = 0; return 1; } DstSize = CompressedDataSize; CopyMemory(&OriginalData[2], CompressedData, CompressedDataSize); } if (Level2ComprFlags & PACKET_COMPRESSED) { Level2ComprFlags |= xcrush->CompressionFlags; xcrush->CompressionFlags = 0; } else if (Level2ComprFlags & PACKET_FLUSHED) { xcrush->CompressionFlags = PACKET_FLUSHED; } Level1ComprFlags |= L1_INNER_COMPRESSION; OriginalData[0] = (BYTE)Level1ComprFlags; OriginalData[1] = (BYTE)Level2ComprFlags; #if defined(DEBUG_XCRUSH) WLog_DBG(TAG, "XCrushCompress: Level1ComprFlags: %s Level2ComprFlags: %s", xcrush_get_level_1_compression_flags_string(Level1ComprFlags), xcrush_get_level_2_compression_flags_string(Level2ComprFlags)); #endif if (*pDstSize < (DstSize + 2)) return -1006; *pDstSize = DstSize + 2; *pFlags = PACKET_COMPRESSED | CompressionLevel; return 1; } void xcrush_context_reset(XCRUSH_CONTEXT* WINPR_RESTRICT xcrush, BOOL flush) { WINPR_ASSERT(xcrush); xcrush->SignatureIndex = 0; xcrush->SignatureCount = 1000; ZeroMemory(&(xcrush->Signatures), sizeof(XCRUSH_SIGNATURE) * xcrush->SignatureCount); xcrush->CompressionFlags = 0; xcrush->ChunkHead = xcrush->ChunkTail = 1; ZeroMemory(&(xcrush->Chunks), sizeof(xcrush->Chunks)); ZeroMemory(&(xcrush->NextChunks), sizeof(xcrush->NextChunks)); ZeroMemory(&(xcrush->OriginalMatches), sizeof(xcrush->OriginalMatches)); ZeroMemory(&(xcrush->OptimizedMatches), sizeof(xcrush->OptimizedMatches)); if (flush) xcrush->HistoryOffset = xcrush->HistoryBufferSize + 1; else xcrush->HistoryOffset = 0; mppc_context_reset(xcrush->mppc, flush); } XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor) { XCRUSH_CONTEXT* xcrush = (XCRUSH_CONTEXT*)calloc(1, sizeof(XCRUSH_CONTEXT)); if (!xcrush) goto fail; xcrush->Compressor = Compressor; xcrush->mppc = mppc_context_new(1, Compressor); if (!xcrush->mppc) goto fail; xcrush->HistoryBufferSize = 2000000; xcrush_context_reset(xcrush, FALSE); return xcrush; fail: xcrush_context_free(xcrush); return NULL; } void xcrush_context_free(XCRUSH_CONTEXT* xcrush) { if (xcrush) { mppc_context_free(xcrush->mppc); free(xcrush); } }