FreeRDP
Loading...
Searching...
No Matches
streamdump.c
1
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#define TAG FREERDP_TAG("streamdump")
35
36struct 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
49static UINT32 crc32b(const BYTE* data, size_t length)
50{
51 UINT32 crc = 0xFFFFFFFF;
52
53 for (size_t x = 0; x < length; x++)
54 {
55 const UINT32 d = data[x] & 0xFF;
56 crc = crc ^ d;
57 for (int j = 7; j >= 0; j--)
58 {
59 UINT32 mask = ~(crc & 1);
60 crc = (crc >> 1) ^ (0xEDB88320 & mask);
61 }
62 }
63 return ~crc;
64}
65
66#if !defined(BUILD_TESTING_INTERNAL)
67static
68#endif
69 BOOL stream_dump_read_line(FILE* fp, wStream* s, UINT64* pts, size_t* pOffset, UINT32* flags)
70{
71 BOOL rc = FALSE;
72 UINT64 ts = 0;
73 UINT64 size = 0;
74 size_t r = 0;
75 UINT32 crc32 = 0;
76 BYTE received = 0;
77
78 if (!fp || !s || !flags)
79 return FALSE;
80
81 if (pOffset)
82 {
83 if (_fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *pOffset), SEEK_SET) < 0)
84 goto fail;
85 }
86
87 r = fread(&ts, 1, sizeof(ts), fp);
88 if (r != sizeof(ts))
89 goto fail;
90 r = fread(&received, 1, sizeof(received), fp);
91 if (r != sizeof(received))
92 goto fail;
93 r = fread(&crc32, 1, sizeof(crc32), fp);
94 if (r != sizeof(crc32))
95 goto fail;
96 r = fread(&size, 1, sizeof(size), fp);
97 if (r != sizeof(size))
98 goto fail;
99 if (received)
100 *flags = STREAM_MSG_SRV_RX;
101 else
102 *flags = STREAM_MSG_SRV_TX;
103
104 {
105 const size_t usize = WINPR_ASSERTING_INT_CAST(size_t, size);
106 if (!Stream_EnsureRemainingCapacity(s, usize))
107 goto fail;
108 r = fread(Stream_Pointer(s), 1, usize, fp);
109 if (r != size)
110 goto fail;
111 if (crc32 != crc32b(Stream_ConstPointer(s), usize))
112 goto fail;
113 Stream_Seek(s, usize);
114 }
115
116 if (pOffset)
117 {
118 INT64 tmp = _ftelli64(fp);
119 if (tmp < 0)
120 goto fail;
121 *pOffset = (size_t)tmp;
122 }
123
124 if (pts)
125 *pts = ts;
126 rc = TRUE;
127
128fail:
129 Stream_SealLength(s);
130 return rc;
131}
132
133#if !defined(BUILD_TESTING_INTERNAL)
134static
135#endif
136 BOOL stream_dump_write_line(FILE* fp, UINT32 flags, wStream* s)
137{
138 BOOL rc = FALSE;
139 const UINT64 t = GetTickCount64();
140 const BYTE* data = Stream_Buffer(s);
141 const size_t usize = Stream_Length(s);
142 const uint64_t size = (uint64_t)usize;
143
144 if (!fp || !s)
145 return FALSE;
146
147 {
148 const UINT32 crc32 = crc32b(data, usize);
149 const BYTE received = flags & STREAM_MSG_SRV_RX;
150 size_t r = fwrite(&t, 1, sizeof(t), fp);
151 if (r != sizeof(t))
152 goto fail;
153 r = fwrite(&received, 1, sizeof(received), fp);
154 if (r != sizeof(received))
155 goto fail;
156 r = fwrite(&crc32, 1, sizeof(crc32), fp);
157 if (r != sizeof(crc32))
158 goto fail;
159 r = fwrite(&size, 1, sizeof(size), fp);
160 if (r != sizeof(size))
161 goto fail;
162 r = fwrite(data, 1, usize, fp);
163 if (r != usize)
164 goto fail;
165 }
166
167 rc = TRUE;
168fail:
169 return rc;
170}
171
172static FILE* stream_dump_get_file(const rdpSettings* settings, const char* mode)
173{
174 const char* cfolder = nullptr;
175 char* file = nullptr;
176 FILE* fp = nullptr;
177
178 if (!settings || !mode)
179 return nullptr;
180
181 cfolder = freerdp_settings_get_string(settings, FreeRDP_TransportDumpFile);
182 if (!cfolder)
183 file = GetKnownSubPath(KNOWN_PATH_TEMP, "freerdp-transport-dump");
184 else
185 file = _strdup(cfolder);
186
187 if (!file)
188 goto fail;
189
190 fp = winpr_fopen(file, mode);
191fail:
192 free(file);
193 return fp;
194}
195
196SSIZE_T stream_dump_append(const rdpContext* context, UINT32 flags, wStream* s, size_t* offset)
197{
198 SSIZE_T rc = -1;
199 FILE* fp = nullptr;
200 const UINT32 mask = STREAM_MSG_SRV_RX | STREAM_MSG_SRV_TX;
201 CONNECTION_STATE state = freerdp_get_state(context);
202 int r = 0;
203
204 if (!context || !s || !offset)
205 return -1;
206
207 if ((flags & STREAM_MSG_SRV_RX) && (flags & STREAM_MSG_SRV_TX))
208 return -1;
209
210 if ((flags & mask) == 0)
211 return -1;
212
213 if (state < context->dump->state)
214 return 0;
215
216 fp = stream_dump_get_file(context->settings, "ab");
217 if (!fp)
218 return -1;
219
220 r = _fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *offset), SEEK_SET);
221 if (r < 0)
222 goto fail;
223
224 if (!stream_dump_write_line(fp, flags, s))
225 goto fail;
226 {
227 const int64_t rt = _ftelli64(fp);
228 if (rt < 0)
229 {
230 rc = -1;
231 goto fail;
232 }
233 rc = WINPR_ASSERTING_INT_CAST(SSIZE_T, rt);
234 }
235 *offset = (size_t)rc;
236
237fail:
238 if (fp)
239 (void)fclose(fp);
240 return rc;
241}
242
243SSIZE_T stream_dump_get(const rdpContext* context, UINT32* flags, wStream* s, size_t* offset,
244 UINT64* pts)
245{
246 SSIZE_T rc = -1;
247 FILE* fp = nullptr;
248 int r = 0;
249
250 if (!context || !s || !offset)
251 return -1;
252 fp = stream_dump_get_file(context->settings, "rb");
253 if (!fp)
254 return -1;
255 r = _fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *offset), SEEK_SET);
256 if (r < 0)
257 goto fail;
258
259 if (!stream_dump_read_line(fp, s, pts, offset, flags))
260 goto fail;
261
262 {
263 const int64_t rt = _ftelli64(fp);
264 if (rt < 0)
265 goto fail;
266 rc = WINPR_ASSERTING_INT_CAST(SSIZE_T, rt);
267 }
268
269fail:
270 if (fp)
271 (void)fclose(fp);
272 return rc;
273}
274
275static int stream_dump_transport_write(rdpTransport* transport, wStream* s)
276{
277 SSIZE_T r = 0;
278 rdpContext* ctx = transport_get_context(transport);
279
280 WINPR_ASSERT(ctx);
281 WINPR_ASSERT(ctx->dump);
282 WINPR_ASSERT(s);
283
284 r = stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_TX : STREAM_MSG_SRV_RX, s,
285 &ctx->dump->writeDumpOffset);
286 if (r < 0)
287 return -1;
288
289 WINPR_ASSERT(ctx->dump->io.WritePdu);
290 return ctx->dump->io.WritePdu(transport, s);
291}
292
293static int stream_dump_transport_read(rdpTransport* transport, wStream* s)
294{
295 int rc = 0;
296 rdpContext* ctx = transport_get_context(transport);
297
298 WINPR_ASSERT(ctx);
299 WINPR_ASSERT(ctx->dump);
300 WINPR_ASSERT(s);
301
302 WINPR_ASSERT(ctx->dump->io.ReadPdu);
303 rc = ctx->dump->io.ReadPdu(transport, s);
304 if (rc > 0)
305 {
306 SSIZE_T r =
307 stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_RX : STREAM_MSG_SRV_TX, s,
308 &ctx->dump->readDumpOffset);
309 if (r < 0)
310 return -1;
311 }
312 return rc;
313}
314
315static BOOL stream_dump_register_write_handlers(rdpContext* context)
316{
317 rdpTransportIo dump = WINPR_C_ARRAY_INIT;
318 const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
319
320 if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDump))
321 return TRUE;
322
323 WINPR_ASSERT(dfl);
324 dump = *dfl;
325
326 /* Remember original callbacks for later */
327 WINPR_ASSERT(context->dump);
328 context->dump->io.ReadPdu = dfl->ReadPdu;
329 context->dump->io.WritePdu = dfl->WritePdu;
330
331 /* Set our dump wrappers */
332 dump.WritePdu = stream_dump_transport_write;
333 dump.ReadPdu = stream_dump_transport_read;
334 return freerdp_set_io_callbacks(context, &dump);
335}
336
337static int stream_dump_replay_transport_write(rdpTransport* transport, wStream* s)
338{
339 rdpContext* ctx = transport_get_context(transport);
340 size_t size = 0;
341
342 WINPR_ASSERT(ctx);
343 WINPR_ASSERT(s);
344
345 size = Stream_Length(s);
346 WLog_Print(ctx->dump->log, WLOG_TRACE, "replay write %" PRIuz, size);
347 // TODO: Compare with write file
348
349 return 1;
350}
351
352static int stream_dump_replay_transport_read(rdpTransport* transport, wStream* s)
353{
354 rdpContext* ctx = transport_get_context(transport);
355
356 size_t size = 0;
357 UINT64 slp = 0;
358 UINT64 ts = 0;
359 UINT32 flags = 0;
360
361 WINPR_ASSERT(ctx);
362 WINPR_ASSERT(ctx->dump);
363 WINPR_ASSERT(s);
364
365 const size_t start = Stream_GetPosition(s);
366 do
367 {
368 if (!Stream_SetPosition(s, start))
369 return -1;
370 if (stream_dump_get(ctx, &flags, s, &ctx->dump->replayOffset, &ts) < 0)
371 return -1;
372 } while (flags & STREAM_MSG_SRV_RX);
373
374 if (!ctx->dump->nodelay)
375 {
376 if ((ctx->dump->replayTime > 0) && (ts > ctx->dump->replayTime))
377 slp = ts - ctx->dump->replayTime;
378 }
379 ctx->dump->replayTime = ts;
380
381 size = Stream_Length(s);
382 Stream_ResetPosition(s);
383 WLog_Print(ctx->dump->log, WLOG_TRACE, "replay read %" PRIuz, size);
384
385 if (slp > 0)
386 {
387 uint64_t duration = slp;
388 do
389 {
390 const DWORD actual = (DWORD)MIN(duration, UINT32_MAX);
391 Sleep(actual);
392 duration -= actual;
393 } while (duration > 0);
394 }
395
396 return 1;
397}
398
399static int stream_dump_replay_transport_tcp_connect(WINPR_ATTR_UNUSED rdpContext* context,
400 WINPR_ATTR_UNUSED rdpSettings* settings,
401 WINPR_ATTR_UNUSED const char* hostname,
402 WINPR_ATTR_UNUSED int port,
403 WINPR_ATTR_UNUSED DWORD timeout)
404{
405 WINPR_ASSERT(context);
406 WINPR_ASSERT(settings);
407 WINPR_ASSERT(hostname);
408
409 return 42;
410}
411
412static rdpTransportLayer* stream_dump_replay_transport_connect_layer(
413 WINPR_ATTR_UNUSED rdpTransport* transport, WINPR_ATTR_UNUSED const char* hostname,
414 WINPR_ATTR_UNUSED int port, WINPR_ATTR_UNUSED DWORD timeout)
415{
416 WINPR_ASSERT(transport);
417 WINPR_ASSERT(hostname);
418
419 return nullptr;
420}
421
422static BOOL stream_dump_replay_transport_tls_connect(WINPR_ATTR_UNUSED rdpTransport* transport)
423{
424 WINPR_ASSERT(transport);
425 return TRUE;
426}
427
428static BOOL stream_dump_replay_transport_accept(WINPR_ATTR_UNUSED rdpTransport* transport)
429{
430 WINPR_ASSERT(transport);
431 return TRUE;
432}
433
434static BOOL stream_dump_register_read_handlers(rdpContext* context)
435{
436 const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
437
438 if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplay))
439 return TRUE;
440
441 WINPR_ASSERT(dfl);
442 rdpTransportIo dump = *dfl;
443
444 /* Remember original callbacks for later */
445 WINPR_ASSERT(context->dump);
446 context->dump->nodelay =
447 freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplayNodelay);
448 context->dump->io.ReadPdu = dfl->ReadPdu;
449 context->dump->io.WritePdu = dfl->WritePdu;
450
451 /* Set our dump wrappers */
452 dump.WritePdu = stream_dump_transport_write;
453 dump.ReadPdu = stream_dump_transport_read;
454
455 /* Set our dump wrappers */
456 dump.WritePdu = stream_dump_replay_transport_write;
457 dump.ReadPdu = stream_dump_replay_transport_read;
458 dump.TCPConnect = stream_dump_replay_transport_tcp_connect;
459 dump.TLSAccept = stream_dump_replay_transport_accept;
460 dump.TLSConnect = stream_dump_replay_transport_tls_connect;
461 dump.ConnectLayer = stream_dump_replay_transport_connect_layer;
462 if (!freerdp_set_io_callbacks(context, &dump))
463 return FALSE;
464 return freerdp_io_callback_set_event(context, TRUE);
465}
466
467BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state, BOOL isServer)
468{
469 WINPR_ASSERT(context);
470 WINPR_ASSERT(context->dump);
471 context->dump->state = state;
472 context->dump->isServer = isServer;
473 if (!stream_dump_register_write_handlers(context))
474 return FALSE;
475 return stream_dump_register_read_handlers(context);
476}
477
478void stream_dump_free(rdpStreamDumpContext* dump)
479{
480 free(dump);
481}
482
483rdpStreamDumpContext* stream_dump_new(void)
484{
485 rdpStreamDumpContext* dump = calloc(1, sizeof(rdpStreamDumpContext));
486 if (!dump)
487 return nullptr;
488 dump->log = WLog_Get(TAG);
489
490 return dump;
491}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.