Coverage Report

Created: 2026-04-12 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/codec/bulk.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Bulk Compression
4
 *
5
 * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <math.h>
21
#include <winpr/assert.h>
22
23
#include <freerdp/config.h>
24
25
#include "../core/settings.h"
26
#include "bulk.h"
27
#include "../codec/mppc.h"
28
#include "../codec/ncrush.h"
29
#include "../codec/xcrush.h"
30
31
#include <freerdp/log.h>
32
#define TAG FREERDP_TAG("core")
33
34
//#define WITH_BULK_DEBUG 1
35
36
struct rdp_bulk
37
{
38
  ALIGN64 rdpContext* context;
39
  ALIGN64 UINT32 CompressionLevel;
40
  ALIGN64 UINT16 CompressionMaxSize;
41
  ALIGN64 MPPC_CONTEXT* mppcSend;
42
  ALIGN64 MPPC_CONTEXT* mppcRecv;
43
  ALIGN64 NCRUSH_CONTEXT* ncrushRecv;
44
  ALIGN64 NCRUSH_CONTEXT* ncrushSend;
45
  ALIGN64 XCRUSH_CONTEXT* xcrushRecv;
46
  ALIGN64 XCRUSH_CONTEXT* xcrushSend;
47
  ALIGN64 BYTE OutputBuffer[65536];
48
};
49
50
#if defined(WITH_BULK_DEBUG)
51
static inline const char* bulk_get_compression_flags_string(UINT32 flags)
52
{
53
  flags &= BULK_COMPRESSION_FLAGS_MASK;
54
55
  if (flags == 0)
56
    return "PACKET_UNCOMPRESSED";
57
  else if (flags == PACKET_COMPRESSED)
58
    return "PACKET_COMPRESSED";
59
  else if (flags == PACKET_AT_FRONT)
60
    return "PACKET_AT_FRONT";
61
  else if (flags == PACKET_FLUSHED)
62
    return "PACKET_FLUSHED";
63
  else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT))
64
    return "PACKET_COMPRESSED | PACKET_AT_FRONT";
65
  else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED))
66
    return "PACKET_COMPRESSED | PACKET_FLUSHED";
67
  else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED))
68
    return "PACKET_AT_FRONT | PACKET_FLUSHED";
69
  else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED))
70
    return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED";
71
72
  return "PACKET_UNKNOWN";
73
}
74
#endif
75
76
WINPR_ATTR_NODISCARD
77
static UINT32 bulk_compression_level(rdpBulk* WINPR_RESTRICT bulk)
78
39.2k
{
79
39.2k
  WINPR_ASSERT(bulk);
80
39.2k
  WINPR_ASSERT(bulk->context);
81
39.2k
  const rdpSettings* settings = bulk->context->settings;
82
39.2k
  WINPR_ASSERT(settings);
83
84
39.2k
  const UINT32 CompressionLevel = freerdp_settings_get_uint32(settings, FreeRDP_CompressionLevel);
85
39.2k
  bulk->CompressionLevel =
86
39.2k
      (CompressionLevel >= PACKET_COMPR_TYPE_RDP61) ? PACKET_COMPR_TYPE_RDP61 : CompressionLevel;
87
39.2k
  WINPR_ASSERT(bulk->CompressionLevel <= UINT16_MAX);
88
39.2k
  return bulk->CompressionLevel;
89
39.2k
}
90
91
static void bulk_update_compression_max_size(rdpBulk* WINPR_RESTRICT bulk)
92
39.2k
{
93
39.2k
  WINPR_ASSERT(bulk);
94
39.2k
  const UINT32 CompressionLevel = bulk_compression_level(bulk);
95
39.2k
  bulk->CompressionMaxSize = (CompressionLevel < PACKET_COMPR_TYPE_64K) ? 8192 : UINT16_MAX;
96
39.2k
}
97
UINT16 bulk_compression_max_size(rdpBulk* WINPR_RESTRICT bulk)
98
3.94k
{
99
3.94k
  bulk_update_compression_max_size(bulk);
100
3.94k
  return bulk->CompressionMaxSize;
101
3.94k
}
102
103
#if defined(WITH_BULK_DEBUG)
104
static inline int bulk_compress_validate(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize,
105
                                         const BYTE* pDstData, UINT32 DstSize, UINT32 Flags)
106
{
107
  int status;
108
  const BYTE* v_pSrcData = nullptr;
109
  const BYTE* v_pDstData = nullptr;
110
  UINT32 v_SrcSize = 0;
111
  UINT32 v_DstSize = 0;
112
  UINT32 v_Flags = 0;
113
114
  WINPR_ASSERT(bulk);
115
  WINPR_ASSERT(pSrcData);
116
  WINPR_ASSERT(pDstData);
117
118
  v_pSrcData = pDstData;
119
  v_SrcSize = DstSize;
120
  v_Flags = Flags | bulk->CompressionLevel;
121
  status = bulk_decompress(bulk, v_pSrcData, v_SrcSize, &v_pDstData, &v_DstSize, v_Flags);
122
123
  if (status < 0)
124
  {
125
    WLog_DBG(TAG, "compression/decompression failure");
126
    return status;
127
  }
128
129
  if (v_DstSize != SrcSize)
130
  {
131
    WLog_DBG(TAG,
132
             "compression/decompression size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32
133
             "",
134
             v_DstSize, SrcSize);
135
    return -1;
136
  }
137
138
  if (memcmp(v_pDstData, pSrcData, SrcSize) != 0)
139
  {
140
    WLog_DBG(TAG, "compression/decompression input/output mismatch! flags: 0x%08" PRIX32 "",
141
             v_Flags);
142
#if 1
143
    WLog_DBG(TAG, "Actual:");
144
    winpr_HexDump(TAG, WLOG_DEBUG, v_pDstData, SrcSize);
145
    WLog_DBG(TAG, "Expected:");
146
    winpr_HexDump(TAG, WLOG_DEBUG, pSrcData, SrcSize);
147
#endif
148
    return -1;
149
  }
150
151
  return status;
152
}
153
#endif
154
155
int bulk_decompress(rdpBulk* WINPR_RESTRICT bulk, const BYTE* WINPR_RESTRICT pSrcData,
156
                    UINT32 SrcSize, const BYTE** WINPR_RESTRICT ppDstData,
157
                    UINT32* WINPR_RESTRICT pDstSize, UINT32 flags)
158
35.3k
{
159
35.3k
  int status = -1;
160
161
35.3k
  WINPR_ASSERT(bulk);
162
35.3k
  WINPR_ASSERT(bulk->context);
163
35.3k
  WINPR_ASSERT(pSrcData);
164
35.3k
  WINPR_ASSERT(ppDstData);
165
35.3k
  WINPR_ASSERT(pDstSize);
166
167
35.3k
  rdpMetrics* metrics = bulk->context->metrics;
168
35.3k
  WINPR_ASSERT(metrics);
169
170
35.3k
  bulk_update_compression_max_size(bulk);
171
35.3k
  const UINT32 type = flags & BULK_COMPRESSION_TYPE_MASK;
172
173
35.3k
  if (flags & BULK_COMPRESSION_FLAGS_MASK)
174
13.9k
  {
175
13.9k
    switch (type)
176
13.9k
    {
177
3.01k
      case PACKET_COMPR_TYPE_8K:
178
3.01k
        mppc_set_compression_level(bulk->mppcRecv, 0);
179
3.01k
        status =
180
3.01k
            mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags);
181
3.01k
        break;
182
183
4.62k
      case PACKET_COMPR_TYPE_64K:
184
4.62k
        mppc_set_compression_level(bulk->mppcRecv, 1);
185
4.62k
        status =
186
4.62k
            mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags);
187
4.62k
        break;
188
189
1.73k
      case PACKET_COMPR_TYPE_RDP6:
190
1.73k
        status = ncrush_decompress(bulk->ncrushRecv, pSrcData, SrcSize, ppDstData, pDstSize,
191
1.73k
                                   flags);
192
1.73k
        break;
193
194
4.52k
      case PACKET_COMPR_TYPE_RDP61:
195
4.52k
        status = xcrush_decompress(bulk->xcrushRecv, pSrcData, SrcSize, ppDstData, pDstSize,
196
4.52k
                                   flags);
197
4.52k
        break;
198
199
9
      case PACKET_COMPR_TYPE_RDP8:
200
9
        WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32,
201
9
                 bulk->CompressionLevel);
202
9
        status = -1;
203
9
        break;
204
70
      default:
205
70
        WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel);
206
70
        status = -1;
207
70
        break;
208
13.9k
    }
209
13.9k
  }
210
21.3k
  else
211
21.3k
  {
212
21.3k
    *ppDstData = pSrcData;
213
21.3k
    *pDstSize = SrcSize;
214
21.3k
    status = 0;
215
21.3k
  }
216
217
35.3k
  if (status >= 0)
218
33.8k
  {
219
33.8k
    const UINT32 CompressedBytes = SrcSize;
220
33.8k
    const UINT32 UncompressedBytes = *pDstSize;
221
33.8k
    const double CompressionRatio =
222
33.8k
        metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes);
223
#ifdef WITH_BULK_DEBUG
224
    {
225
      WLog_DBG(TAG,
226
               "Decompress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32
227
               ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64
228
               " / %" PRIu64 ")",
229
               type, bulk_get_compression_flags_string(flags), flags, CompressionRatio,
230
               CompressedBytes, UncompressedBytes, metrics->TotalCompressionRatio,
231
               metrics->TotalCompressedBytes, metrics->TotalUncompressedBytes);
232
    }
233
#else
234
33.8k
    WINPR_UNUSED(CompressionRatio);
235
33.8k
#endif
236
33.8k
  }
237
1.53k
  else
238
1.53k
  {
239
1.53k
    WLog_ERR(TAG, "Decompression failure!");
240
1.53k
  }
241
242
35.3k
  return status;
243
35.3k
}
244
245
int bulk_compress(rdpBulk* WINPR_RESTRICT bulk, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize,
246
                  const BYTE** WINPR_RESTRICT ppDstData, UINT32* WINPR_RESTRICT pDstSize,
247
                  UINT32* WINPR_RESTRICT pFlags)
248
127
{
249
127
  int status = -1;
250
251
127
  WINPR_ASSERT(bulk);
252
127
  WINPR_ASSERT(bulk->context);
253
127
  WINPR_ASSERT(pSrcData);
254
127
  WINPR_ASSERT(ppDstData);
255
127
  WINPR_ASSERT(pDstSize);
256
257
127
  rdpMetrics* metrics = bulk->context->metrics;
258
127
  WINPR_ASSERT(metrics);
259
260
127
  if ((SrcSize <= 50) || (SrcSize >= 16384))
261
127
  {
262
127
    *ppDstData = pSrcData;
263
127
    *pDstSize = SrcSize;
264
127
    return 0;
265
127
  }
266
267
0
  *pDstSize = sizeof(bulk->OutputBuffer);
268
0
  const UINT32 CompressionLevel = bulk_compression_level(bulk);
269
0
  bulk_update_compression_max_size(bulk);
270
271
0
  switch (CompressionLevel)
272
0
  {
273
0
    case PACKET_COMPR_TYPE_8K:
274
0
    case PACKET_COMPR_TYPE_64K:
275
0
      mppc_set_compression_level(bulk->mppcSend, CompressionLevel);
276
0
      status = mppc_compress(bulk->mppcSend, pSrcData, SrcSize, bulk->OutputBuffer, ppDstData,
277
0
                             pDstSize, pFlags);
278
0
      break;
279
0
    case PACKET_COMPR_TYPE_RDP6:
280
0
      status = ncrush_compress(bulk->ncrushSend, pSrcData, SrcSize, bulk->OutputBuffer,
281
0
                               ppDstData, pDstSize, pFlags);
282
0
      break;
283
0
    case PACKET_COMPR_TYPE_RDP61:
284
0
      status = xcrush_compress(bulk->xcrushSend, pSrcData, SrcSize, bulk->OutputBuffer,
285
0
                               ppDstData, pDstSize, pFlags);
286
0
      break;
287
0
    case PACKET_COMPR_TYPE_RDP8:
288
0
      WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32, CompressionLevel);
289
0
      status = -1;
290
0
      break;
291
0
    default:
292
0
      WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, CompressionLevel);
293
0
      status = -1;
294
0
      break;
295
0
  }
296
297
0
  if (status >= 0)
298
0
  {
299
0
    const UINT32 CompressedBytes = *pDstSize;
300
0
    const UINT32 UncompressedBytes = SrcSize;
301
0
    const double CompressionRatio =
302
0
        metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes);
303
#ifdef WITH_BULK_DEBUG
304
    {
305
      WLog_DBG(TAG,
306
               "Compress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32
307
               ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64
308
               " / %" PRIu64 ")",
309
               bulk->CompressionLevel, bulk_get_compression_flags_string(*pFlags), *pFlags,
310
               CompressionRatio, CompressedBytes, UncompressedBytes,
311
               metrics->TotalCompressionRatio, metrics->TotalCompressedBytes,
312
               metrics->TotalUncompressedBytes);
313
    }
314
#else
315
0
    WINPR_UNUSED(CompressionRatio);
316
0
#endif
317
0
  }
318
319
#if defined(WITH_BULK_DEBUG)
320
321
  if (bulk_compress_validate(bulk, pSrcData, SrcSize, *ppDstData, *pDstSize, *pFlags) < 0)
322
    status = -1;
323
324
#endif
325
0
  return status;
326
0
}
327
328
void bulk_reset(rdpBulk* WINPR_RESTRICT bulk)
329
0
{
330
0
  WINPR_ASSERT(bulk);
331
332
0
  mppc_context_reset(bulk->mppcSend, FALSE);
333
0
  mppc_context_reset(bulk->mppcRecv, FALSE);
334
0
  ncrush_context_reset(bulk->ncrushRecv, FALSE);
335
0
  ncrush_context_reset(bulk->ncrushSend, FALSE);
336
0
  xcrush_context_reset(bulk->xcrushRecv, FALSE);
337
0
  xcrush_context_reset(bulk->xcrushSend, FALSE);
338
0
}
339
340
rdpBulk* bulk_new(rdpContext* context)
341
17.7k
{
342
17.7k
  rdpBulk* bulk = nullptr;
343
17.7k
  WINPR_ASSERT(context);
344
345
17.7k
  bulk = (rdpBulk*)calloc(1, sizeof(rdpBulk));
346
347
17.7k
  if (!bulk)
348
0
    goto fail;
349
350
17.7k
  bulk->context = context;
351
17.7k
  bulk->mppcSend = mppc_context_new(1, TRUE);
352
17.7k
  if (!bulk->mppcSend)
353
0
    goto fail;
354
17.7k
  bulk->mppcRecv = mppc_context_new(1, FALSE);
355
17.7k
  if (!bulk->mppcRecv)
356
0
    goto fail;
357
17.7k
  bulk->ncrushRecv = ncrush_context_new(FALSE);
358
17.7k
  if (!bulk->ncrushRecv)
359
0
    goto fail;
360
17.7k
  bulk->ncrushSend = ncrush_context_new(TRUE);
361
17.7k
  if (!bulk->ncrushSend)
362
0
    goto fail;
363
17.7k
  bulk->xcrushRecv = xcrush_context_new(FALSE);
364
17.7k
  if (!bulk->xcrushRecv)
365
0
    goto fail;
366
17.7k
  bulk->xcrushSend = xcrush_context_new(TRUE);
367
17.7k
  if (!bulk->xcrushSend)
368
0
    goto fail;
369
17.7k
  bulk->CompressionLevel =
370
17.7k
      freerdp_settings_get_uint32(context->settings, FreeRDP_CompressionLevel);
371
372
17.7k
  return bulk;
373
0
fail:
374
0
  WINPR_PRAGMA_DIAG_PUSH
375
0
  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
376
0
  bulk_free(bulk);
377
0
  WINPR_PRAGMA_DIAG_POP
378
0
  return nullptr;
379
17.7k
}
380
381
void bulk_free(rdpBulk* bulk)
382
17.7k
{
383
17.7k
  if (!bulk)
384
0
    return;
385
386
17.7k
  mppc_context_free(bulk->mppcSend);
387
17.7k
  mppc_context_free(bulk->mppcRecv);
388
17.7k
  ncrush_context_free(bulk->ncrushRecv);
389
17.7k
  ncrush_context_free(bulk->ncrushSend);
390
17.7k
  xcrush_context_free(bulk->xcrushRecv);
391
17.7k
  xcrush_context_free(bulk->xcrushSend);
392
17.7k
  free(bulk);
393
17.7k
}