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