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 (void)_fseeki64(fp, WINPR_ASSERTING_INT_CAST(int64_t, *pOffset), SEEK_SET);
83
84 r = fread(&ts, 1, sizeof(ts), fp);
85 if (r != sizeof(ts))
86 goto fail;
87 r = fread(&received, 1, sizeof(received), fp);
88 if (r != sizeof(received))
89 goto fail;
90 r = fread(&crc32, 1, sizeof(crc32), fp);
91 if (r != sizeof(crc32))
92 goto fail;
93 r = fread(&size, 1, sizeof(size), fp);
94 if (r != sizeof(size))
95 goto fail;
96 if (received)
97 *flags = STREAM_MSG_SRV_RX;
98 else
99 *flags = STREAM_MSG_SRV_TX;
100
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
113 if (pOffset)
114 {
115 INT64 tmp = _ftelli64(fp);
116 if (tmp < 0)
117 goto fail;
118 *pOffset = (size_t)tmp;
119 }
120
121 if (pts)
122 *pts = ts;
123 rc = TRUE;
124
125fail:
126 Stream_SealLength(s);
127 return rc;
128}
129
130#if !defined(BUILD_TESTING_INTERNAL)
131static
132#endif
133 BOOL 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 {
260 const int64_t rt = _ftelli64(fp);
261 if (rt < 0)
262 goto fail;
263 rc = WINPR_ASSERTING_INT_CAST(SSIZE_T, rt);
264 }
265
266fail:
267 if (fp)
268 (void)fclose(fp);
269 return rc;
270}
271
272static int stream_dump_transport_write(rdpTransport* transport, wStream* s)
273{
274 SSIZE_T r = 0;
275 rdpContext* ctx = transport_get_context(transport);
276
277 WINPR_ASSERT(ctx);
278 WINPR_ASSERT(ctx->dump);
279 WINPR_ASSERT(s);
280
281 r = stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_TX : STREAM_MSG_SRV_RX, s,
282 &ctx->dump->writeDumpOffset);
283 if (r < 0)
284 return -1;
285
286 WINPR_ASSERT(ctx->dump->io.WritePdu);
287 return ctx->dump->io.WritePdu(transport, s);
288}
289
290static int stream_dump_transport_read(rdpTransport* transport, wStream* s)
291{
292 int rc = 0;
293 rdpContext* ctx = transport_get_context(transport);
294
295 WINPR_ASSERT(ctx);
296 WINPR_ASSERT(ctx->dump);
297 WINPR_ASSERT(s);
298
299 WINPR_ASSERT(ctx->dump->io.ReadPdu);
300 rc = ctx->dump->io.ReadPdu(transport, s);
301 if (rc > 0)
302 {
303 SSIZE_T r =
304 stream_dump_append(ctx, ctx->dump->isServer ? STREAM_MSG_SRV_RX : STREAM_MSG_SRV_TX, s,
305 &ctx->dump->readDumpOffset);
306 if (r < 0)
307 return -1;
308 }
309 return rc;
310}
311
312static BOOL stream_dump_register_write_handlers(rdpContext* context)
313{
314 rdpTransportIo dump = { 0 };
315 const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
316
317 if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDump))
318 return TRUE;
319
320 WINPR_ASSERT(dfl);
321 dump = *dfl;
322
323 /* Remember original callbacks for later */
324 WINPR_ASSERT(context->dump);
325 context->dump->io.ReadPdu = dfl->ReadPdu;
326 context->dump->io.WritePdu = dfl->WritePdu;
327
328 /* Set our dump wrappers */
329 dump.WritePdu = stream_dump_transport_write;
330 dump.ReadPdu = stream_dump_transport_read;
331 return freerdp_set_io_callbacks(context, &dump);
332}
333
334static int stream_dump_replay_transport_write(rdpTransport* transport, wStream* s)
335{
336 rdpContext* ctx = transport_get_context(transport);
337 size_t size = 0;
338
339 WINPR_ASSERT(ctx);
340 WINPR_ASSERT(s);
341
342 size = Stream_Length(s);
343 WLog_Print(ctx->dump->log, WLOG_TRACE, "replay write %" PRIuz, size);
344 // TODO: Compare with write file
345
346 return 1;
347}
348
349static int stream_dump_replay_transport_read(rdpTransport* transport, wStream* s)
350{
351 rdpContext* ctx = transport_get_context(transport);
352
353 size_t size = 0;
354 UINT64 slp = 0;
355 UINT64 ts = 0;
356 UINT32 flags = 0;
357
358 WINPR_ASSERT(ctx);
359 WINPR_ASSERT(ctx->dump);
360 WINPR_ASSERT(s);
361
362 const size_t start = Stream_GetPosition(s);
363 do
364 {
365 Stream_SetPosition(s, start);
366 if (stream_dump_get(ctx, &flags, s, &ctx->dump->replayOffset, &ts) < 0)
367 return -1;
368 } while (flags & STREAM_MSG_SRV_RX);
369
370 if (!ctx->dump->nodelay)
371 {
372 if ((ctx->dump->replayTime > 0) && (ts > ctx->dump->replayTime))
373 slp = ts - ctx->dump->replayTime;
374 }
375 ctx->dump->replayTime = ts;
376
377 size = Stream_Length(s);
378 Stream_SetPosition(s, 0);
379 WLog_Print(ctx->dump->log, WLOG_TRACE, "replay read %" PRIuz, size);
380
381 if (slp > 0)
382 {
383 uint64_t duration = slp;
384 do
385 {
386 const DWORD actual = (DWORD)MIN(duration, UINT32_MAX);
387 Sleep(actual);
388 duration -= actual;
389 } while (duration > 0);
390 }
391
392 return 1;
393}
394
395static int stream_dump_replay_transport_tcp_connect(WINPR_ATTR_UNUSED rdpContext* context,
396 WINPR_ATTR_UNUSED rdpSettings* settings,
397 WINPR_ATTR_UNUSED const char* hostname,
398 WINPR_ATTR_UNUSED int port,
399 WINPR_ATTR_UNUSED DWORD timeout)
400{
401 WINPR_ASSERT(context);
402 WINPR_ASSERT(settings);
403 WINPR_ASSERT(hostname);
404
405 return 42;
406}
407
408static rdpTransportLayer* stream_dump_replay_transport_connect_layer(
409 WINPR_ATTR_UNUSED rdpTransport* transport, WINPR_ATTR_UNUSED const char* hostname,
410 WINPR_ATTR_UNUSED int port, WINPR_ATTR_UNUSED DWORD timeout)
411{
412 WINPR_ASSERT(transport);
413 WINPR_ASSERT(hostname);
414
415 return NULL;
416}
417
418static BOOL stream_dump_replay_transport_tls_connect(WINPR_ATTR_UNUSED rdpTransport* transport)
419{
420 WINPR_ASSERT(transport);
421 return TRUE;
422}
423
424static BOOL stream_dump_replay_transport_accept(WINPR_ATTR_UNUSED rdpTransport* transport)
425{
426 WINPR_ASSERT(transport);
427 return TRUE;
428}
429
430static BOOL stream_dump_register_read_handlers(rdpContext* context)
431{
432 const rdpTransportIo* dfl = freerdp_get_io_callbacks(context);
433
434 if (!freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplay))
435 return TRUE;
436
437 WINPR_ASSERT(dfl);
438 rdpTransportIo dump = *dfl;
439
440 /* Remember original callbacks for later */
441 WINPR_ASSERT(context->dump);
442 context->dump->nodelay =
443 freerdp_settings_get_bool(context->settings, FreeRDP_TransportDumpReplayNodelay);
444 context->dump->io.ReadPdu = dfl->ReadPdu;
445 context->dump->io.WritePdu = dfl->WritePdu;
446
447 /* Set our dump wrappers */
448 dump.WritePdu = stream_dump_transport_write;
449 dump.ReadPdu = stream_dump_transport_read;
450
451 /* Set our dump wrappers */
452 dump.WritePdu = stream_dump_replay_transport_write;
453 dump.ReadPdu = stream_dump_replay_transport_read;
454 dump.TCPConnect = stream_dump_replay_transport_tcp_connect;
455 dump.TLSAccept = stream_dump_replay_transport_accept;
456 dump.TLSConnect = stream_dump_replay_transport_tls_connect;
457 dump.ConnectLayer = stream_dump_replay_transport_connect_layer;
458 if (!freerdp_set_io_callbacks(context, &dump))
459 return FALSE;
460 return freerdp_io_callback_set_event(context, TRUE);
461}
462
463BOOL stream_dump_register_handlers(rdpContext* context, CONNECTION_STATE state, BOOL isServer)
464{
465 WINPR_ASSERT(context);
466 WINPR_ASSERT(context->dump);
467 context->dump->state = state;
468 context->dump->isServer = isServer;
469 if (!stream_dump_register_write_handlers(context))
470 return FALSE;
471 return stream_dump_register_read_handlers(context);
472}
473
474void stream_dump_free(rdpStreamDumpContext* dump)
475{
476 free(dump);
477}
478
479rdpStreamDumpContext* stream_dump_new(void)
480{
481 rdpStreamDumpContext* dump = calloc(1, sizeof(rdpStreamDumpContext));
482 if (!dump)
483 return NULL;
484 dump->log = WLog_Get(TAG);
485
486 return dump;
487}
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.