Coverage Report

Created: 2026-10-15 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/core/streamdump.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 *
4
 * RDP session stream dump interface
5
 *
6
 * Copyright 2022 Armin Novak
7
 * Copyright 2022 Thincast Technologies GmbH
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
#include <time.h>
23
24
#include <winpr/sysinfo.h>
25
#include <winpr/path.h>
26
#include <winpr/string.h>
27
28
#include <freerdp/freerdp.h>
29
#include <freerdp/streamdump.h>
30
#include <freerdp/transport_io.h>
31
32
#include "streamdump.h"
33
34
21.4k
#define TAG FREERDP_TAG("streamdump")
35
36
struct stream_dump_context
37
{
38
  rdpTransportIo io;
39
  size_t writeDumpOffset;
40
  size_t readDumpOffset;
41
  size_t replayOffset;
42
  UINT64 replayTime;
43
  CONNECTION_STATE state;
44
  BOOL isServer;
45
  BOOL nodelay;
46
  wLog* log;
47
};
48
49
static UINT32 crc32b(const BYTE* data, size_t length)
50
0
{
51
0
  UINT32 crc = 0xFFFFFFFFul;
52
53
0
  for (size_t x = 0; x < length; x++)
54
0
  {
55
0
    const UINT32 d = data[x] & 0xFFul;
56
0
    crc = crc ^ d;
57
0
    for (int j = 7; j >= 0; j--)
58
0
    {
59
0
      UINT32 mask = 0;
60
0
      if ((crc & 1ul) != 0)
61
0
        mask = 0xEDB88320ul;
62
0
      crc = (crc >> 1ul) ^ mask;
63
0
    }
64
0
  }
65
0
  return ~crc;
66
0
}
67
68
#if !defined(BUILD_TESTING_INTERNAL)
69
static
70
#endif
71
    BOOL stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset, UINT32* flags)
72
0
{
73
0
  BOOL rc = FALSE;
74
0
  UINT64 ts = 0;
75
0
  UINT64 size = 0;
76
0
  size_t r = 0;
77
0
  UINT32 crc32 = 0;
78
0
  BYTE received = 0;
79
80
0
  if (!fp || !s || !flags)
81
0
    return FALSE;
82
83
0
  if (pOffset)
84
0
  {
85
0
    if (_fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *pOffset), SEEK_SET) < 0)
86
0
      goto fail;
87
0
  }
88
89
0
  r = fread(&ts, 1, sizeof(ts), fp);
90
0
  if (r != sizeof(ts))
91
0
    goto fail;
92
0
  r = fread(&received, 1, sizeof(received), fp);
93
0
  if (r != sizeof(received))
94
0
    goto fail;
95
0
  r = fread(&crc32, 1, sizeof(crc32), fp);
96
0
  if (r != sizeof(crc32))
97
0
    goto fail;
98
0
  r = fread(&size, 1, sizeof(size), fp);
99
0
  if (r != sizeof(size))
100
0
    goto fail;
101
0
  if (received)
102
0
    *flags = STREAM_MSG_SRV_RX;
103
0
  else
104
0
    *flags = STREAM_MSG_SRV_TX;
105
106
0
  {
107
0
    const size_t usize = WINPR_ASSERTING_INT_CAST(size_t, size);
108
0
    if (!Stream_EnsureRemainingCapacity(s, usize))
109
0
      goto fail;
110
0
    r = fread(Stream_Pointer(s), 1, usize, fp);
111
0
    if (r != size)
112
0
      goto fail;
113
0
    if (crc32 != crc32b(Stream_ConstPointer(s), usize))
114
0
      goto fail;
115
0
    Stream_Seek(s, usize);
116
0
  }
117
118
0
  if (pOffset)
119
0
  {
120
0
    INT64 tmp = _ftelli64(fp);
121
0
    if (tmp < 0)
122
0
      goto fail;
123
0
    *pOffset = (size_t)tmp;
124
0
  }
125
126
0
  if (pts)
127
0
    *pts = ts;
128
0
  rc = TRUE;
129
130
0
fail:
131
0
  Stream_SealLength(s);
132
0
  return rc;
133
0
}
134
135
#if !defined(BUILD_TESTING_INTERNAL)
136
static
137
#endif
138
    BOOL stream_dump_write_line(FILE* fp, UINT32 flags, wStream* s)
139
0
{
140
0
  BOOL rc = FALSE;
141
0
  const UINT64 t = GetTickCount64();
142
0
  const BYTE* data = Stream_Buffer(s);
143
0
  const size_t usize = Stream_Length(s);
144
0
  const uint64_t size = (uint64_t)usize;
145
146
0
  if (!fp || !s)
147
0
    return FALSE;
148
149
0
  {
150
0
    const UINT32 crc32 = crc32b(data, usize);
151
0
    const BYTE received = flags & STREAM_MSG_SRV_RX;
152
0
    size_t r = fwrite(&t, 1, sizeof(t), fp);
153
0
    if (r != sizeof(t))
154
0
      goto fail;
155
0
    r = fwrite(&received, 1, sizeof(received), fp);
156
0
    if (r != sizeof(received))
157
0
      goto fail;
158
0
    r = fwrite(&crc32, 1, sizeof(crc32), fp);
159
0
    if (r != sizeof(crc32))
160
0
      goto fail;
161
0
    r = fwrite(&size, 1, sizeof(size), fp);
162
0
    if (r != sizeof(size))
163
0
      goto fail;
164
0
    r = fwrite(data, 1, usize, fp);
165
0
    if (r != usize)
166
0
      goto fail;
167
0
  }
168
169
0
  rc = TRUE;
170
0
fail:
171
0
  return rc;
172
0
}
173
174
static FILE* stream_dump_get_file(const rdpSettings* settings, const char* mode)
175
0
{
176
0
  const char* cfolder = nullptr;
177
0
  char* file = nullptr;
178
0
  FILE* fp = nullptr;
179
180
0
  if (!settings || !mode)
181
0
    return nullptr;
182
183
0
  cfolder = freerdp_settings_get_string(settings, FreeRDP_TransportDumpFile);
184
0
  if (!cfolder)
185
0
    file = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump");
186
0
  else
187
0
    file = _strdup(cfolder);
188
189
0
  if (!file)
190
0
    goto fail;
191
192
0
  fp = winpr_fopen(file, mode);
193
0
fail:
194
0
  free(file);
195
0
  return fp;
196
0
}
197
198
SSIZE_T stream_dump_append(const rdpContext* context, UINT32 flags, wStream* s, size_t* offset)
199
0
{
200
0
  SSIZE_T rc = -1;
201
0
  FILE* fp = nullptr;
202
0
  const UINT32 mask = STREAM_MSG_SRV_RX | STREAM_MSG_SRV_TX;
203
0
  CONNECTION_STATE state = freerdp_get_state(context);
204
0
  int r = 0;
205
206
0
  if (!context || !s || !offset)
207
0
    return -1;
208
209
0
  if ((flags & STREAM_MSG_SRV_RX) && (flags & STREAM_MSG_SRV_TX))
210
0
    return -1;
211
212
0
  if ((flags & mask) == 0)
213
0
    return -1;
214
215
0
  if (state < context->dump->state)
216
0
    return 0;
217
218
0
  fp = stream_dump_get_file(context->settings, "ab");
219
0
  if (!fp)
220
0
    return -1;
221
222
0
  r = _fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *offset), SEEK_SET);
223
0
  if (r < 0)
224
0
    goto fail;
225
226
0
  if (!stream_dump_write_line(fp, flags, s))
227
0
    goto fail;
228
0
  {
229
0
    const int64_t rt = _ftelli64(fp);
230
0
    if (rt < 0)
231
0
    {
232
0
      rc = -1;
233
0
      goto fail;
234
0
    }
235
0
    rc = WINPR_ASSERTING_INT_CAST(SSIZE_T, rt);
236
0
  }
237
0
  *offset = (size_t)rc;
238
239
0
fail:
240
0
  if (fp)
241
0
    (void)fclose(fp);
242
0
  return rc;
243
0
}
244
245
SSIZE_T stream_dump_get(const rdpContext* context, UINT32* flags, wStream* s, size_t* offset,
246
                        UINT64* pts)
247
0
{
248
0
  SSIZE_T rc = -1;
249
0
  FILE* fp = nullptr;
250
0
  int r = 0;
251
252
0
  if (!context || !s || !offset)
253
0
    return -1;
254
0
  fp = stream_dump_get_file(context->settings, "rb");
255
0
  if (!fp)
256
0
    return -1;
257
0
  r = _fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *offset), SEEK_SET);
258
0
  if (r < 0)
259
0
    goto fail;
260
261
0
  if (!stream_dump_read_line(fp, s, pts, offset, flags))
262
0
    goto fail;
263
264
0
  {
265
0
    const int64_t rt = _ftelli64(fp);
266
0
    if (rt < 0)
267
0
      goto fail;
268
0
    rc = WINPR_ASSERTING_INT_CAST(SSIZE_T, rt);
269
0
  }
270
271
0
fail:
272
0
  if (fp)
273
0
    (void)fclose(fp);
274
0
  return rc;
275
0
}
276
277
static int stream_dump_transport_write(rdpTransport* transport, wStream* s)
278
0
{
279
0
  SSIZE_T r = 0;
280
0
  rdpContext* ctx = transport_get_context(transport);
281
282
0
  WINPR_ASSERT(ctx);
283
0
  WINPR_ASSERT(ctx->dump);
284
0
  WINPR_ASSERT(s);
285
286
0
  r = stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_TX : STREAM_MSG_SRV_RX, s,
287
0
                         &ctx->dump->writeDumpOffset);
288
0
  if (r < 0)
289
0
    return -1;
290
291
0
  WINPR_ASSERT(ctx->dump->io.WritePdu);
292
0
  return ctx->dump->io.WritePdu(transport, s);
293
0
}
294
295
static int stream_dump_transport_read(rdpTransport* transport, wStream* s)
296
0
{
297
0
  int rc = 0;
298
0
  rdpContext* ctx = transport_get_context(transport);
299
300
0
  WINPR_ASSERT(ctx);
301
0
  WINPR_ASSERT(ctx->dump);
302
0
  WINPR_ASSERT(s);
303
304
0
  WINPR_ASSERT(ctx->dump->io.ReadPdu);
305
0
  rc = ctx->dump->io.ReadPdu(transport, s);
306
0
  if (rc > 0)
307
0
  {
308
0
    SSIZE_T r =
309
0
        stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_RX : STREAM_MSG_SRV_TX, s,
310
0
                           &ctx->dump->readDumpOffset);
311
0
    if (r < 0)
312
0
      return -1;
313
0
  }
314
0
  return rc;
315
0
}
316
317
static BOOL stream_dump_register_write_handlers(rdpContext* context)
318
0
{
319
0
  rdpTransportIo dump = WINPR_C_ARRAY_INIT;
320
0
  const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
321
322
0
  if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDump))
323
0
    return TRUE;
324
325
0
  WINPR_ASSERT(dfl);
326
0
  dump = *dfl;
327
328
  /* Remember original callbacks for later */
329
0
  WINPR_ASSERT(context->dump);
330
0
  context->dump->io.ReadPdu = dfl->ReadPdu;
331
0
  context->dump->io.WritePdu = dfl->WritePdu;
332
333
  /* Set our dump wrappers */
334
0
  dump.WritePdu = stream_dump_transport_write;
335
0
  dump.ReadPdu = stream_dump_transport_read;
336
0
  return freerdp_set_io_callbacks(context, &dump);
337
0
}
338
339
static int stream_dump_replay_transport_write(rdpTransport* transport, wStream* s)
340
0
{
341
0
  rdpContext* ctx = transport_get_context(transport);
342
0
  size_t size = 0;
343
344
0
  WINPR_ASSERT(ctx);
345
0
  WINPR_ASSERT(s);
346
347
0
  size = Stream_Length(s);
348
0
  WLog_Print(ctx->dump->log, WLOG_TRACE, "replay write %" PRIuz, size);
349
  // TODO: Compare with write file
350
351
0
  return 1;
352
0
}
353
354
static int stream_dump_replay_transport_read(rdpTransport* transport, wStream* s)
355
0
{
356
0
  rdpContext* ctx = transport_get_context(transport);
357
358
0
  size_t size = 0;
359
0
  UINT64 slp = 0;
360
0
  UINT64 ts = 0;
361
0
  UINT32 flags = 0;
362
363
0
  WINPR_ASSERT(ctx);
364
0
  WINPR_ASSERT(ctx->dump);
365
0
  WINPR_ASSERT(s);
366
367
0
  const size_t start = Stream_GetPosition(s);
368
0
  do
369
0
  {
370
0
    if (!Stream_SetPosition(s, start))
371
0
      return -1;
372
0
    if (stream_dump_get(ctx, &flags, s, &ctx->dump->replayOffset, &ts) < 0)
373
0
      return -1;
374
0
  } while (flags & STREAM_MSG_SRV_RX);
375
376
0
  if (!ctx->dump->nodelay)
377
0
  {
378
0
    if ((ctx->dump->replayTime > 0) && (ts > ctx->dump->replayTime))
379
0
      slp = ts - ctx->dump->replayTime;
380
0
  }
381
0
  ctx->dump->replayTime = ts;
382
383
0
  size = Stream_Length(s);
384
0
  Stream_ResetPosition(s);
385
0
  WLog_Print(ctx->dump->log, WLOG_TRACE, "replay read %" PRIuz, size);
386
387
0
  if (slp > 0)
388
0
  {
389
0
    uint64_t duration = slp;
390
0
    do
391
0
    {
392
0
      const DWORD actual = (DWORD)MIN(duration, UINT32_MAX);
393
0
      Sleep(actual);
394
0
      duration -= actual;
395
0
    } while (duration > 0);
396
0
  }
397
398
0
  return 1;
399
0
}
400
401
static int stream_dump_replay_transport_tcp_connect(WINPR_ATTR_UNUSED rdpContext* context,
402
                                                    WINPR_ATTR_UNUSED rdpSettings* settings,
403
                                                    WINPR_ATTR_UNUSED const char* hostname,
404
                                                    WINPR_ATTR_UNUSED int port,
405
                                                    WINPR_ATTR_UNUSED DWORD timeout)
406
0
{
407
0
  WINPR_ASSERT(context);
408
0
  WINPR_ASSERT(settings);
409
0
  WINPR_ASSERT(hostname);
410
411
0
  return 42;
412
0
}
413
414
static rdpTransportLayer* stream_dump_replay_transport_connect_layer(
415
    WINPR_ATTR_UNUSED rdpTransport* transport, WINPR_ATTR_UNUSED const char* hostname,
416
    WINPR_ATTR_UNUSED int port, WINPR_ATTR_UNUSED DWORD timeout)
417
0
{
418
0
  WINPR_ASSERT(transport);
419
0
  WINPR_ASSERT(hostname);
420
421
0
  return nullptr;
422
0
}
423
424
static BOOL stream_dump_replay_transport_tls_connect(WINPR_ATTR_UNUSED rdpTransport* transport)
425
0
{
426
0
  WINPR_ASSERT(transport);
427
0
  return TRUE;
428
0
}
429
430
static BOOL stream_dump_replay_transport_accept(WINPR_ATTR_UNUSED rdpTransport* transport)
431
0
{
432
0
  WINPR_ASSERT(transport);
433
0
  return TRUE;
434
0
}
435
436
static BOOL stream_dump_register_read_handlers(rdpContext* context)
437
0
{
438
0
  const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
439
440
0
  if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplay))
441
0
    return TRUE;
442
443
0
  WINPR_ASSERT(dfl);
444
0
  rdpTransportIo dump = *dfl;
445
446
  /* Remember original callbacks for later */
447
0
  WINPR_ASSERT(context->dump);
448
0
  context->dump->nodelay =
449
0
      freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplayNodelay);
450
0
  context->dump->io.ReadPdu = dfl->ReadPdu;
451
0
  context->dump->io.WritePdu = dfl->WritePdu;
452
453
  /* Set our dump wrappers */
454
0
  dump.WritePdu = stream_dump_replay_transport_write;
455
0
  dump.ReadPdu = stream_dump_replay_transport_read;
456
0
  dump.TCPConnect = stream_dump_replay_transport_tcp_connect;
457
0
  dump.TLSAccept = stream_dump_replay_transport_accept;
458
0
  dump.TLSConnect = stream_dump_replay_transport_tls_connect;
459
0
  dump.ConnectLayer = stream_dump_replay_transport_connect_layer;
460
0
  if (!freerdp_set_io_callbacks(context, &dump))
461
0
    return FALSE;
462
0
  return freerdp_io_callback_set_event(context, TRUE);
463
0
}
464
465
BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state, BOOL isServer)
466
0
{
467
0
  WINPR_ASSERT(context);
468
0
  WINPR_ASSERT(context->dump);
469
0
  context->dump->state = state;
470
0
  context->dump->isServer = isServer;
471
0
  if (!stream_dump_register_write_handlers(context))
472
0
    return FALSE;
473
0
  return stream_dump_register_read_handlers(context);
474
0
}
475
476
void stream_dump_free(rdpStreamDumpContext* dump)
477
21.4k
{
478
21.4k
  free(dump);
479
21.4k
}
480
481
rdpStreamDumpContext* stream_dump_new(void)
482
21.4k
{
483
21.4k
  rdpStreamDumpContext* dump = calloc(1, sizeof(rdpStreamDumpContext));
484
21.4k
  if (!dump)
485
0
    return nullptr;
486
21.4k
  dump->log = WLog_Get(TAG);
487
488
21.4k
  return dump;
489
21.4k
}