1/*
2Copyright (c) 2012, Broadcom Europe Ltd
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the copyright holder nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*/
27
28#include <stdlib.h>
29#include <string.h>
30#include <stdio.h>
31
32#include "containers/containers.h"
33#include "containers/core/containers_logging.h"
34#include "containers/core/containers_io.h"
35
36#include "nb_io.h"
37
38#define MAXIMUM_BUFFER_SIZE 65000
39#define MINIMUM_BUFFER_SPACE 1500
40
41#define INITIAL_READ_BUFFER_SIZE 8000
42#define MAXIMUM_READ_BUFFER_SIZE 64000
43
44#define BYTES_PER_ROW 32
45
46#define HAS_PADDING 0x20
47#define HAS_EXTENSION 0x10
48#define CSRC_COUNT_MASK 0x0F
49
50#define HAS_MARKER 0x80
51#define PAYLOAD_TYPE_MASK 0x7F
52
53#define EXTENSION_LENGTH_MASK 0x0000FFFF
54#define EXTENSION_ID_SHIFT 16
55
56#define LOWEST_VERBOSITY 1
57#define BASIC_HEADER_VERBOSITY 2
58#define FULL_HEADER_VERBOSITY 3
59#define FULL_PACKET_VERBOSITY 4
60
61#define ESCAPE_CHARACTER 0x1B
62
63static bool seen_first_packet;
64static uint16_t expected_next_seq_num;
65
66static bool do_print_usage;
67static uint32_t verbosity;
68static const char *read_uri;
69static const char *packet_save_file;
70static bool packet_save_is_pktfile;
71
72static uint16_t network_to_host_16(const uint8_t *buffer)
73{
74 return (buffer[0] << 8) | buffer[1];
75}
76
77static uint32_t network_to_host_32(const uint8_t *buffer)
78{
79 return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
80}
81
82/** Avoid alignment problems when writing a word value to the buffer */
83static void store_u32(uint8_t *buffer, uint32_t value)
84{
85 buffer[0] = (uint8_t)value;
86 buffer[1] = (uint8_t)(value >> 8);
87 buffer[2] = (uint8_t)(value >> 16);
88 buffer[3] = (uint8_t)(value >> 24);
89}
90
91/** Avoid alignment problems when reading a word value from the buffer */
92static uint32_t fetch_u32(uint8_t *buffer)
93{
94 return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
95}
96
97static bool marker_bit_set(const uint8_t *buffer, size_t buffer_len)
98{
99 if (buffer_len < 2)
100 return false;
101
102 return (buffer[1] & HAS_MARKER);
103}
104
105static void dump_bytes(const uint8_t *buffer, size_t buffer_len)
106{
107 char dump_str[3 * BYTES_PER_ROW + 1];
108 int in_row = 0;
109
110 while (buffer_len--)
111 {
112 sprintf(dump_str + 3 * in_row, "%2.2X ", *buffer++);
113 if (++in_row == BYTES_PER_ROW)
114 {
115 LOG_INFO(NULL, dump_str);
116 in_row = 0;
117 }
118 }
119
120 if (in_row)
121 {
122 LOG_INFO(NULL, dump_str);
123 }
124}
125
126static bool decode_packet(const uint8_t *buffer, size_t buffer_len)
127{
128 uint8_t flags;
129 uint8_t payload_type;
130 uint16_t seq_num;
131 uint32_t timestamp;
132 uint32_t ssrc;
133 uint32_t csrc_count;
134
135 if (buffer_len < 12)
136 {
137 LOG_ERROR(NULL, "Packet too small: basic header missing");
138 return false;
139 }
140
141 flags = buffer[0];
142 payload_type = buffer[1];
143 seq_num = network_to_host_16(buffer + 2);
144 timestamp = network_to_host_32(buffer + 4);
145 ssrc = network_to_host_32(buffer + 8);
146
147 if (seen_first_packet && seq_num != expected_next_seq_num)
148 {
149 int16_t missing_packets = seq_num - expected_next_seq_num;
150
151 LOG_INFO(NULL, "*** Sequence break, expected %hu, got %hu ***", expected_next_seq_num, seq_num);
152 if (missing_packets > 0)
153 LOG_INFO(NULL, "*** Jumped forward %hd packets ***", missing_packets);
154 else
155 LOG_INFO(NULL, "*** Jumped backward %hd packets ***", -missing_packets);
156 }
157 seen_first_packet = true;
158 expected_next_seq_num = seq_num + 1;
159
160 /* Dump the basic header information */
161 if (verbosity >= BASIC_HEADER_VERBOSITY)
162 {
163 LOG_INFO(NULL, "Version: %d\nPayload type: %d%s\nSequence: %d\nTimestamp: %u\nSSRC: 0x%8.8X",
164 flags >> 6, payload_type & PAYLOAD_TYPE_MASK,
165 (const char *)((payload_type & HAS_MARKER) ? " (M)" : ""),
166 seq_num, timestamp, ssrc);
167 }
168
169 buffer += 12;
170 buffer_len -= 12;
171
172 if (verbosity >= FULL_HEADER_VERBOSITY)
173 {
174 /* Dump the CSRCs, if any */
175 csrc_count = flags & CSRC_COUNT_MASK;
176 if (csrc_count)
177 {
178 uint32_t ii;
179
180 if (buffer_len < (csrc_count * 4))
181 {
182 LOG_ERROR(NULL, "Packet too small: CSRCs missing");
183 return false;
184 }
185
186 LOG_INFO(NULL, "CSRCs:");
187 for (ii = 0; ii < csrc_count; ii++)
188 {
189 LOG_INFO(NULL, " 0x%8.8X", network_to_host_32(buffer));
190 buffer += 4;
191 buffer_len -= 4;
192 }
193 }
194
195 /* Dump any extension, if present */
196 if (flags & HAS_EXTENSION)
197 {
198 uint32_t extension_hdr;
199 uint32_t extension_id;
200 size_t extension_len;
201
202 if (buffer_len < 4)
203 {
204 LOG_ERROR(NULL, "Packet too small: extension header missing");
205 return false;
206 }
207
208 extension_hdr = network_to_host_32(buffer);
209 buffer += 4;
210 buffer_len -= 4;
211
212 extension_len = (size_t)(extension_hdr & EXTENSION_LENGTH_MASK);
213 extension_id = extension_hdr >> EXTENSION_ID_SHIFT;
214
215 if (buffer_len < extension_len)
216 {
217 LOG_ERROR(NULL, "Packet too small: extension content missing");
218 return false;
219 }
220
221 LOG_INFO(NULL, "Extension: 0x%4.4X (%u bytes)", extension_id, (unsigned)extension_len);
222 dump_bytes(buffer, extension_len);
223 buffer += extension_len;
224 buffer_len -= extension_len;
225 }
226 }
227
228 /* And finally the payload data */
229 if (verbosity >= FULL_PACKET_VERBOSITY)
230 {
231 LOG_INFO(NULL, "Data: (%u bytes)", (unsigned)buffer_len);
232 dump_bytes(buffer, buffer_len);
233 }
234
235 return true;
236}
237
238static void increase_read_buffer_size(VC_CONTAINER_IO_T *p_ctx)
239{
240 uint32_t buffer_size = INITIAL_READ_BUFFER_SIZE;
241
242 /* Iteratively enlarge read buffer until either operation fails or maximum is reached. */
243 while (vc_container_io_control(p_ctx, VC_CONTAINER_CONTROL_IO_SET_READ_BUFFER_SIZE, buffer_size) == VC_CONTAINER_SUCCESS)
244 {
245 buffer_size <<= 1; /* Double and try again */
246 if (buffer_size > MAXIMUM_READ_BUFFER_SIZE)
247 break;
248 }
249}
250
251static void parse_command_line(int argc, char **argv)
252{
253 int arg = 1;
254
255 while (arg < argc)
256 {
257 if (*argv[arg] != '-') /* End of options, next should be URI */
258 break;
259
260 switch (argv[arg][1])
261 {
262 case 'h':
263 do_print_usage = true;
264 break;
265 case 's':
266 arg++;
267 if (arg >= argc)
268 break;
269 packet_save_file = argv[arg];
270 packet_save_is_pktfile = (strncmp(packet_save_file, "pktfile:", 8) == 0);
271 break;
272 case 'v':
273 {
274 const char *ptr = &argv[arg][2];
275
276 verbosity = 1;
277 while (*ptr++ == 'v')
278 verbosity++;
279 }
280 break;
281 default: LOG_ERROR(NULL, "Unrecognised option: %s", argv[arg]); return;
282 }
283
284 arg++;
285 }
286
287 if (arg < argc)
288 read_uri = argv[arg];
289}
290
291static void print_usage(char *program_name)
292{
293 LOG_INFO(NULL, "Usage:");
294 LOG_INFO(NULL, " %s [opts] <uri>", program_name);
295 LOG_INFO(NULL, "Reads RTP packets from <uri>, decodes to standard output.");
296 LOG_INFO(NULL, "Press the escape key to terminate the program.");
297 LOG_INFO(NULL, "Options:");
298 LOG_INFO(NULL, " -h Print this information");
299 LOG_INFO(NULL, " -s x Save packets to URI x");
300 LOG_INFO(NULL, " -v Dump standard packet header");
301 LOG_INFO(NULL, " -vv Dump entire header");
302 LOG_INFO(NULL, " -vvv Dump entire header and data");
303}
304
305int main(int argc, char **argv)
306{
307 int result = 0;
308 uint8_t *buffer = NULL;
309 VC_CONTAINER_IO_T *read_io = NULL;
310 VC_CONTAINER_IO_T *write_io = NULL;
311 VC_CONTAINER_STATUS_T status;
312 size_t received_bytes;
313 bool ready = true;
314 uint32_t available_bytes;
315 uint8_t *packet_ptr;
316
317 parse_command_line(argc, argv);
318
319 if (do_print_usage || !read_uri)
320 {
321 print_usage(argv[0]);
322 result = 1; goto tidyup;
323 }
324
325 buffer = (uint8_t *)malloc(MAXIMUM_BUFFER_SIZE);
326 if (!buffer)
327 {
328 LOG_ERROR(NULL, "Allocating %d bytes for the buffer failed", MAXIMUM_BUFFER_SIZE);
329 result = 2; goto tidyup;
330 }
331
332 read_io = vc_container_io_open(read_uri, VC_CONTAINER_IO_MODE_READ, &status);
333 if (!read_io)
334 {
335 LOG_ERROR(NULL, "Opening <%s> for read failed: %d", read_uri, status);
336 result = 3; goto tidyup;
337 }
338
339 increase_read_buffer_size(read_io);
340
341 if (packet_save_file)
342 {
343 write_io = vc_container_io_open(packet_save_file, VC_CONTAINER_IO_MODE_WRITE, &status);
344 if (!write_io)
345 {
346 LOG_ERROR(NULL, "Opening <%s> for write failed: %d", packet_save_file, status);
347 result = 4; goto tidyup;
348 }
349 if (!packet_save_is_pktfile)
350 {
351 store_u32(buffer, 0x50415753);
352 vc_container_io_write(write_io, buffer, sizeof(uint32_t));
353 }
354 }
355
356 /* Use non-blocking I/O for both network and console */
357 vc_container_io_control(read_io, VC_CONTAINER_CONTROL_IO_SET_READ_TIMEOUT_MS, 20);
358 nb_set_nonblocking_input(1);
359
360 packet_ptr = buffer;
361 available_bytes = MAXIMUM_BUFFER_SIZE - sizeof(uint32_t);
362 while (ready)
363 {
364 /* Read a packet and store its length in the word before it */
365 received_bytes = vc_container_io_read(read_io, packet_ptr + sizeof(uint32_t), available_bytes);
366 if (received_bytes)
367 {
368 bool packet_has_marker;
369
370 store_u32(packet_ptr, received_bytes);
371 packet_ptr += sizeof(uint32_t);
372 packet_has_marker = marker_bit_set(packet_ptr, received_bytes);
373 packet_ptr += received_bytes;
374 available_bytes -= received_bytes + sizeof(uint32_t);
375
376 if (packet_has_marker || (available_bytes < MINIMUM_BUFFER_SPACE))
377 {
378 uint8_t *decode_ptr;
379
380 if (write_io && !packet_save_is_pktfile)
381 {
382 uint32_t total_bytes = packet_ptr - buffer;
383 if (vc_container_io_write(write_io, buffer, total_bytes) != total_bytes)
384 {
385 LOG_ERROR(NULL, "Error saving packets to file");
386 break;
387 }
388 if (verbosity >= LOWEST_VERBOSITY)
389 LOG_INFO(NULL, "Written %u bytes to file", total_bytes);
390 }
391
392 for (decode_ptr = buffer; decode_ptr < packet_ptr;)
393 {
394 received_bytes = fetch_u32(decode_ptr);
395 decode_ptr += sizeof(uint32_t);
396
397 if (write_io && packet_save_is_pktfile)
398 {
399 if (vc_container_io_write(write_io, buffer, received_bytes) != received_bytes)
400 {
401 LOG_ERROR(NULL, "Error saving packets to file");
402 break;
403 }
404 if (verbosity >= LOWEST_VERBOSITY)
405 LOG_INFO(NULL, "Written %u bytes to file", received_bytes);
406 }
407
408 if (!decode_packet(decode_ptr, received_bytes))
409 {
410 LOG_ERROR(NULL, "Failed to decode packet");
411 break;
412 }
413 decode_ptr += received_bytes;
414 }
415
416 /* Reset to start of buffer */
417 packet_ptr = buffer;
418 available_bytes = MAXIMUM_BUFFER_SIZE - sizeof(uint32_t);
419 }
420 }
421
422 if (nb_char_available())
423 {
424 if (nb_get_char() == ESCAPE_CHARACTER)
425 ready = false;
426 }
427
428 switch (read_io->status)
429 {
430 case VC_CONTAINER_SUCCESS:
431 case VC_CONTAINER_ERROR_CONTINUE:
432 break;
433 default:
434 ready = false;
435 }
436 }
437
438 nb_set_nonblocking_input(0);
439
440tidyup:
441 if (write_io)
442 vc_container_io_close(write_io);
443 if (read_io)
444 vc_container_io_close(read_io);
445 if (buffer)
446 free(buffer);
447
448 return result;
449}
450