summaryrefslogtreecommitdiffstats
path: root/libfreerdp/codec/zgfx.c
diff options
context:
space:
mode:
Diffstat (limited to 'libfreerdp/codec/zgfx.c')
-rw-r--r--libfreerdp/codec/zgfx.c603
1 files changed, 603 insertions, 0 deletions
diff --git a/libfreerdp/codec/zgfx.c b/libfreerdp/codec/zgfx.c
new file mode 100644
index 0000000..881823a
--- /dev/null
+++ b/libfreerdp/codec/zgfx.c
@@ -0,0 +1,603 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * ZGFX (RDP8) Bulk Data Compression
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2017 Armin Novak <armin.novak@thincast.com>
+ * 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 <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/bitstream.h>
+
+#include <freerdp/log.h>
+#include <freerdp/codec/zgfx.h>
+
+#define TAG FREERDP_TAG("codec")
+
+/**
+ * RDP8 Compressor Limits:
+ *
+ * Maximum number of uncompressed bytes in a single segment: 65535
+ * Maximum match distance / minimum history size: 2500000 bytes.
+ * Maximum number of segments: 65535
+ * Maximum expansion of a segment (when compressed size exceeds uncompressed): 1000 bytes
+ * Minimum match length: 3 bytes
+ */
+
+typedef struct
+{
+ UINT32 prefixLength;
+ UINT32 prefixCode;
+ UINT32 valueBits;
+ UINT32 tokenType;
+ UINT32 valueBase;
+} ZGFX_TOKEN;
+
+struct S_ZGFX_CONTEXT
+{
+ BOOL Compressor;
+
+ const BYTE* pbInputCurrent;
+ const BYTE* pbInputEnd;
+
+ UINT32 bits;
+ UINT32 cBitsRemaining;
+ UINT32 BitsCurrent;
+ UINT32 cBitsCurrent;
+
+ BYTE OutputBuffer[65536];
+ UINT32 OutputCount;
+
+ BYTE HistoryBuffer[2500000];
+ UINT32 HistoryIndex;
+ UINT32 HistoryBufferSize;
+};
+
+static const ZGFX_TOKEN ZGFX_TOKEN_TABLE[] = {
+ // 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 }
+};
+
+static INLINE BOOL zgfx_GetBits(ZGFX_CONTEXT* zgfx, UINT32 nbits)
+{
+ if (!zgfx)
+ return FALSE;
+
+ while (zgfx->cBitsCurrent < nbits)
+ {
+ zgfx->BitsCurrent <<= 8;
+
+ if (zgfx->pbInputCurrent < zgfx->pbInputEnd)
+ zgfx->BitsCurrent += *(zgfx->pbInputCurrent)++;
+
+ zgfx->cBitsCurrent += 8;
+ }
+
+ zgfx->cBitsRemaining -= nbits;
+ zgfx->cBitsCurrent -= nbits;
+ zgfx->bits = zgfx->BitsCurrent >> zgfx->cBitsCurrent;
+ zgfx->BitsCurrent &= ((1 << zgfx->cBitsCurrent) - 1);
+ return TRUE;
+}
+
+static void zgfx_history_buffer_ring_write(ZGFX_CONTEXT* zgfx, const BYTE* src, size_t count)
+{
+ UINT32 front = 0;
+
+ if (count <= 0)
+ return;
+
+ if (count > zgfx->HistoryBufferSize)
+ {
+ const size_t residue = count - zgfx->HistoryBufferSize;
+ count = zgfx->HistoryBufferSize;
+ src += residue;
+ zgfx->HistoryIndex = (zgfx->HistoryIndex + residue) % zgfx->HistoryBufferSize;
+ }
+
+ if (zgfx->HistoryIndex + count <= zgfx->HistoryBufferSize)
+ {
+ CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, count);
+
+ if ((zgfx->HistoryIndex += count) == zgfx->HistoryBufferSize)
+ zgfx->HistoryIndex = 0;
+ }
+ else
+ {
+ front = zgfx->HistoryBufferSize - zgfx->HistoryIndex;
+ CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, front);
+ CopyMemory(zgfx->HistoryBuffer, &src[front], count - front);
+ zgfx->HistoryIndex = count - front;
+ }
+}
+
+static void zgfx_history_buffer_ring_read(ZGFX_CONTEXT* zgfx, int offset, BYTE* dst, UINT32 count)
+{
+ UINT32 front = 0;
+ UINT32 index = 0;
+ INT32 bytes = 0;
+ UINT32 valid = 0;
+ INT32 bytesLeft = 0;
+ BYTE* dptr = dst;
+ BYTE* origDst = dst;
+
+ if ((count <= 0) || (count > INT32_MAX))
+ return;
+
+ bytesLeft = (INT32)count;
+ index = (zgfx->HistoryIndex + zgfx->HistoryBufferSize - offset) % zgfx->HistoryBufferSize;
+ bytes = MIN(bytesLeft, offset);
+
+ if ((index + bytes) <= zgfx->HistoryBufferSize)
+ {
+ CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), bytes);
+ }
+ else
+ {
+ front = zgfx->HistoryBufferSize - index;
+ CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), front);
+ CopyMemory(&dptr[front], zgfx->HistoryBuffer, bytes - front);
+ }
+
+ if ((bytesLeft -= bytes) == 0)
+ return;
+
+ dptr += bytes;
+ valid = bytes;
+
+ do
+ {
+ bytes = valid;
+
+ if (bytes > bytesLeft)
+ bytes = bytesLeft;
+
+ CopyMemory(dptr, origDst, bytes);
+ dptr += bytes;
+ valid <<= 1;
+ } while ((bytesLeft -= bytes) > 0);
+}
+
+static BOOL zgfx_decompress_segment(ZGFX_CONTEXT* zgfx, wStream* stream, size_t segmentSize)
+{
+ BYTE c = 0;
+ BYTE flags = 0;
+ UINT32 extra = 0;
+ int opIndex = 0;
+ UINT32 haveBits = 0;
+ UINT32 inPrefix = 0;
+ UINT32 count = 0;
+ UINT32 distance = 0;
+ BYTE* pbSegment = NULL;
+ size_t cbSegment = 0;
+
+ if (!zgfx || !stream || (segmentSize < 2))
+ return FALSE;
+
+ cbSegment = segmentSize - 1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, segmentSize) || (segmentSize > UINT32_MAX))
+ return FALSE;
+
+ Stream_Read_UINT8(stream, flags); /* header (1 byte) */
+ zgfx->OutputCount = 0;
+ pbSegment = Stream_Pointer(stream);
+ if (!Stream_SafeSeek(stream, cbSegment))
+ return FALSE;
+
+ if (!(flags & PACKET_COMPRESSED))
+ {
+ zgfx_history_buffer_ring_write(zgfx, pbSegment, cbSegment);
+
+ if (cbSegment > sizeof(zgfx->OutputBuffer))
+ return FALSE;
+
+ CopyMemory(zgfx->OutputBuffer, pbSegment, cbSegment);
+ zgfx->OutputCount = cbSegment;
+ return TRUE;
+ }
+
+ zgfx->pbInputCurrent = pbSegment;
+ zgfx->pbInputEnd = &pbSegment[cbSegment - 1];
+ /* NumberOfBitsToDecode = ((NumberOfBytesToDecode - 1) * 8) - ValueOfLastByte */
+ const UINT32 bits = 8u * (cbSegment - 1u);
+ if (bits < *zgfx->pbInputEnd)
+ return FALSE;
+
+ zgfx->cBitsRemaining = bits - *zgfx->pbInputEnd;
+ zgfx->cBitsCurrent = 0;
+ zgfx->BitsCurrent = 0;
+
+ while (zgfx->cBitsRemaining)
+ {
+ haveBits = 0;
+ inPrefix = 0;
+
+ for (opIndex = 0; ZGFX_TOKEN_TABLE[opIndex].prefixLength != 0; opIndex++)
+ {
+ while (haveBits < ZGFX_TOKEN_TABLE[opIndex].prefixLength)
+ {
+ zgfx_GetBits(zgfx, 1);
+ inPrefix = (inPrefix << 1) + zgfx->bits;
+ haveBits++;
+ }
+
+ if (inPrefix == ZGFX_TOKEN_TABLE[opIndex].prefixCode)
+ {
+ if (ZGFX_TOKEN_TABLE[opIndex].tokenType == 0)
+ {
+ /* Literal */
+ zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
+ c = (BYTE)(ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits);
+ zgfx->HistoryBuffer[zgfx->HistoryIndex] = c;
+
+ if (++zgfx->HistoryIndex == zgfx->HistoryBufferSize)
+ zgfx->HistoryIndex = 0;
+
+ if (zgfx->OutputCount >= sizeof(zgfx->OutputBuffer))
+ return FALSE;
+
+ zgfx->OutputBuffer[zgfx->OutputCount++] = c;
+ }
+ else
+ {
+ zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits);
+ distance = ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits;
+
+ if (distance != 0)
+ {
+ /* Match */
+ zgfx_GetBits(zgfx, 1);
+
+ if (zgfx->bits == 0)
+ {
+ count = 3;
+ }
+ else
+ {
+ count = 4;
+ extra = 2;
+ zgfx_GetBits(zgfx, 1);
+
+ while (zgfx->bits == 1)
+ {
+ count *= 2;
+ extra++;
+ zgfx_GetBits(zgfx, 1);
+ }
+
+ zgfx_GetBits(zgfx, extra);
+ count += zgfx->bits;
+ }
+
+ if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount)
+ return FALSE;
+
+ zgfx_history_buffer_ring_read(
+ zgfx, distance, &(zgfx->OutputBuffer[zgfx->OutputCount]), count);
+ zgfx_history_buffer_ring_write(
+ zgfx, &(zgfx->OutputBuffer[zgfx->OutputCount]), count);
+ zgfx->OutputCount += count;
+ }
+ else
+ {
+ /* Unencoded */
+ zgfx_GetBits(zgfx, 15);
+ count = zgfx->bits;
+ zgfx->cBitsRemaining -= zgfx->cBitsCurrent;
+ zgfx->cBitsCurrent = 0;
+ zgfx->BitsCurrent = 0;
+
+ if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount)
+ return FALSE;
+
+ if (count > zgfx->cBitsRemaining / 8)
+ return FALSE;
+
+ CopyMemory(&(zgfx->OutputBuffer[zgfx->OutputCount]), zgfx->pbInputCurrent,
+ count);
+ zgfx_history_buffer_ring_write(zgfx, zgfx->pbInputCurrent, count);
+ zgfx->pbInputCurrent += count;
+ zgfx->cBitsRemaining -= (8 * count);
+ zgfx->OutputCount += count;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* Allocate the buffers a bit larger.
+ *
+ * Due to optimizations some h264 decoders will read data beyond
+ * the actual available data, so ensure that it will never be a
+ * out of bounds read.
+ */
+static BYTE* aligned_zgfx_malloc(size_t size)
+{
+ return malloc(size + 64);
+}
+
+int zgfx_decompress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize, UINT32 flags)
+{
+ int status = -1;
+ BYTE descriptor = 0;
+ wStream sbuffer = { 0 };
+ wStream* stream = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize);
+
+ if (!stream)
+ return -1;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, 1))
+ goto fail;
+
+ Stream_Read_UINT8(stream, descriptor); /* descriptor (1 byte) */
+
+ if (descriptor == ZGFX_SEGMENTED_SINGLE)
+ {
+ if (!zgfx_decompress_segment(zgfx, stream, Stream_GetRemainingLength(stream)))
+ goto fail;
+
+ *ppDstData = NULL;
+
+ if (zgfx->OutputCount > 0)
+ *ppDstData = aligned_zgfx_malloc(zgfx->OutputCount);
+
+ if (!*ppDstData)
+ goto fail;
+
+ *pDstSize = zgfx->OutputCount;
+ CopyMemory(*ppDstData, zgfx->OutputBuffer, zgfx->OutputCount);
+ }
+ else if (descriptor == ZGFX_SEGMENTED_MULTIPART)
+ {
+ UINT32 segmentSize = 0;
+ UINT16 segmentNumber = 0;
+ UINT16 segmentCount = 0;
+ UINT32 uncompressedSize = 0;
+ BYTE* pConcatenated = NULL;
+ size_t used = 0;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, 6))
+ goto fail;
+
+ Stream_Read_UINT16(stream, segmentCount); /* segmentCount (2 bytes) */
+ Stream_Read_UINT32(stream, uncompressedSize); /* uncompressedSize (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, stream, segmentCount, sizeof(UINT32)))
+ goto fail;
+
+ pConcatenated = aligned_zgfx_malloc(uncompressedSize);
+
+ if (!pConcatenated)
+ goto fail;
+
+ *ppDstData = pConcatenated;
+ *pDstSize = uncompressedSize;
+
+ for (segmentNumber = 0; segmentNumber < segmentCount; segmentNumber++)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, stream, sizeof(UINT32)))
+ goto fail;
+
+ Stream_Read_UINT32(stream, segmentSize); /* segmentSize (4 bytes) */
+
+ if (!zgfx_decompress_segment(zgfx, stream, segmentSize))
+ goto fail;
+
+ if (zgfx->OutputCount > UINT32_MAX - used)
+ goto fail;
+
+ if (used + zgfx->OutputCount > uncompressedSize)
+ goto fail;
+
+ CopyMemory(pConcatenated, zgfx->OutputBuffer, zgfx->OutputCount);
+ pConcatenated += zgfx->OutputCount;
+ used += zgfx->OutputCount;
+ }
+ }
+ else
+ {
+ goto fail;
+ }
+
+ status = 1;
+fail:
+ return status;
+}
+
+static BOOL zgfx_compress_segment(ZGFX_CONTEXT* zgfx, wStream* s, const BYTE* pSrcData,
+ UINT32 SrcSize, UINT32* pFlags)
+{
+ /* FIXME: Currently compression not implemented. Just copy the raw source */
+ if (!Stream_EnsureRemainingCapacity(s, SrcSize + 1))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return FALSE;
+ }
+
+ (*pFlags) |= ZGFX_PACKET_COMPR_TYPE_RDP8; /* RDP 8.0 compression format */
+ Stream_Write_UINT8(s, (*pFlags)); /* header (1 byte) */
+ Stream_Write(s, pSrcData, SrcSize);
+ return TRUE;
+}
+
+int zgfx_compress_to_stream(ZGFX_CONTEXT* zgfx, wStream* sDst, const BYTE* pUncompressed,
+ UINT32 uncompressedSize, UINT32* pFlags)
+{
+ int fragment = 0;
+ UINT16 maxLength = 0;
+ UINT32 totalLength = 0;
+ size_t posSegmentCount = 0;
+ const BYTE* pSrcData = NULL;
+ int status = 0;
+ maxLength = ZGFX_SEGMENTED_MAXSIZE;
+ totalLength = uncompressedSize;
+ pSrcData = pUncompressed;
+
+ for (; (totalLength > 0) || (fragment == 0); fragment++)
+ {
+ UINT32 SrcSize = 0;
+ size_t posDstSize = 0;
+ size_t posDataStart = 0;
+ UINT32 DstSize = 0;
+ SrcSize = (totalLength > maxLength) ? maxLength : totalLength;
+ posDstSize = 0;
+ totalLength -= SrcSize;
+
+ /* Ensure we have enough space for headers */
+ if (!Stream_EnsureRemainingCapacity(sDst, 12))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ return -1;
+ }
+
+ if (fragment == 0)
+ {
+ /* First fragment */
+ /* descriptor (1 byte) */
+ Stream_Write_UINT8(sDst, (totalLength == 0) ? ZGFX_SEGMENTED_SINGLE
+ : ZGFX_SEGMENTED_MULTIPART);
+
+ if (totalLength > 0)
+ {
+ posSegmentCount = Stream_GetPosition(sDst); /* segmentCount (2 bytes) */
+ Stream_Seek(sDst, 2);
+ Stream_Write_UINT32(sDst, uncompressedSize); /* uncompressedSize (4 bytes) */
+ }
+ }
+
+ if (fragment > 0 || totalLength > 0)
+ {
+ /* Multipart */
+ posDstSize = Stream_GetPosition(sDst); /* size (4 bytes) */
+ Stream_Seek(sDst, 4);
+ }
+
+ posDataStart = Stream_GetPosition(sDst);
+
+ if (!zgfx_compress_segment(zgfx, sDst, pSrcData, SrcSize, pFlags))
+ return -1;
+
+ if (posDstSize)
+ {
+ /* Fill segment data size */
+ DstSize = Stream_GetPosition(sDst) - posDataStart;
+ Stream_SetPosition(sDst, posDstSize);
+ Stream_Write_UINT32(sDst, DstSize);
+ Stream_SetPosition(sDst, posDataStart + DstSize);
+ }
+
+ pSrcData += SrcSize;
+ }
+
+ Stream_SealLength(sDst);
+
+ /* fill back segmentCount */
+ if (posSegmentCount)
+ {
+ Stream_SetPosition(sDst, posSegmentCount);
+ Stream_Write_UINT16(sDst, fragment);
+ Stream_SetPosition(sDst, Stream_Length(sDst));
+ }
+
+ return status;
+}
+
+int zgfx_compress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData,
+ UINT32* pDstSize, UINT32* pFlags)
+{
+ int status = 0;
+ wStream* s = Stream_New(NULL, SrcSize);
+ status = zgfx_compress_to_stream(zgfx, s, pSrcData, SrcSize, pFlags);
+ (*ppDstData) = Stream_Buffer(s);
+ (*pDstSize) = Stream_GetPosition(s);
+ Stream_Free(s, FALSE);
+ return status;
+}
+
+void zgfx_context_reset(ZGFX_CONTEXT* zgfx, BOOL flush)
+{
+ zgfx->HistoryIndex = 0;
+}
+
+ZGFX_CONTEXT* zgfx_context_new(BOOL Compressor)
+{
+ ZGFX_CONTEXT* zgfx = NULL;
+ zgfx = (ZGFX_CONTEXT*)calloc(1, sizeof(ZGFX_CONTEXT));
+
+ if (zgfx)
+ {
+ zgfx->Compressor = Compressor;
+ zgfx->HistoryBufferSize = sizeof(zgfx->HistoryBuffer);
+ zgfx_context_reset(zgfx, FALSE);
+ }
+
+ return zgfx;
+}
+
+void zgfx_context_free(ZGFX_CONTEXT* zgfx)
+{
+ free(zgfx);
+}