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