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