summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-applemidi.c
blob: 8637092c56b5c4dc023057dde88125f018e75bfc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
/* packet-applemidi.c
 * Routines for dissection of Apple network-midi session establishment.
 * Copyright 2006-2012, Tobias Erichsen <t.erichsen@gmx.de>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * Copied from packet-data.c, README.developer, and various other files.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 *
 * Apple network-midi session establishment is a lightweight protocol for
 * providing a simple session establishment for MIDI-data sent in the form
 * of RTP-MIDI (RFC 4695 / 6295).  Peers recognize each other using the
 * Apple Bonjour scheme with the service-name "_apple-midi._udp", establish
 * a connection using AppleMIDI (no official name, just an abbreviation)
 * and then send payload using RTP-MIDI.  The implementation of this
 * dissector is based on the Apple implementation summary from May 6th, 2005
 * and the extension from August 13th, 2010.
 *
 * 2010-11-29
 * - initial version of dissector
 * 2012-02-24
 * - implemented dynamic payloadtype support to automatically punt
 *   the decoding to the RTP-MIDI dissector via the RTP dissector
 * - added new bitrate receive limit feature
 *
 * Here are some links:
 *
 * http://www.cs.berkeley.edu/~lazzaro/rtpmidi/
 * https://tools.ietf.org/html/rfc4695
 * https://tools.ietf.org/html/rfc6925
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/conversation.h>

#include "packet-rtp.h"

void proto_register_applemidi(void);
void proto_reg_handoff_applemidi(void);

/* Definitions for protocol name during dissector-register */
#define APPLEMIDI_DISSECTOR_NAME			"Apple Network-MIDI Session Protocol"
#define APPLEMIDI_DISSECTOR_SHORTNAME			"AppleMIDI"
#define APPLEMIDI_DISSECTOR_ABBREVIATION		"applemidi"

/* Signature "Magic Value" for Apple network MIDI session establishment */
#define APPLEMIDI_PROTOCOL_SIGNATURE			0xffff

/* Apple network MIDI valid commands */
#define APPLEMIDI_COMMAND_INVITATION			0x494e		/*   "IN"   */
#define APPLEMIDI_COMMAND_INVITATION_REJECTED		0x4e4f		/*   "NO"   */
#define APLLEMIDI_COMMAND_INVITATION_ACCEPTED		0x4f4b		/*   "OK"   */
#define APPLEMIDI_COMMAND_ENDSESSION			0x4259		/*   "BY"   */
#define APPLEMIDI_COMMAND_SYNCHRONIZATION		0x434b		/*   "CK"   */
#define APPLEMIDI_COMMAND_RECEIVER_FEEDBACK		0x5253		/*   "RS"   */
#define APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT		0x524c		/*   "RL"   */

static int	hf_applemidi_signature;
static int	hf_applemidi_command;
static int	hf_applemidi_protocol_version;
static int	hf_applemidi_token;
static int	hf_applemidi_ssrc;
static int	hf_applemidi_name;
static int	hf_applemidi_count;
static int	hf_applemidi_padding;
static int	hf_applemidi_timestamp1;
static int	hf_applemidi_timestamp2;
static int	hf_applemidi_timestamp3;
static int	hf_applemidi_sequence_num;
static int	hf_applemidi_rtp_sequence_num;
static int	hf_applemidi_rtp_bitrate_limit;
static int	hf_applemidi_unknown_data;


static int	ett_applemidi;
static int	ett_applemidi_seq_num;


static const value_string applemidi_commands[] = {
	{ APPLEMIDI_COMMAND_INVITATION,			"Invitation" },
	{ APPLEMIDI_COMMAND_INVITATION_REJECTED,	"Invitation Rejected" },
	{ APLLEMIDI_COMMAND_INVITATION_ACCEPTED,	"Invitation Accepted" },
	{ APPLEMIDI_COMMAND_ENDSESSION,			"End Session" },
	{ APPLEMIDI_COMMAND_SYNCHRONIZATION,		"Synchronization" },
	{ APPLEMIDI_COMMAND_RECEIVER_FEEDBACK,		"Receiver Feedback" },
	{ APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT,	"Bitrate Receive Limit" },
	{ 0,						NULL },
};


static int			proto_applemidi;

static dissector_handle_t	applemidi_handle;
static dissector_handle_t	rtp_handle;

static const char applemidi_unknown_command[]		= "unknown command: 0x%04x";

static void
dissect_applemidi_common( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint16_t command ) {

	proto_item *ti;
	uint16_t		 seq_num;
	uint8_t		 count;
	uint8_t		*name;
	int		 offset			= 0;
	int		 len;
	int		 string_size;
	proto_tree	*applemidi_tree;
	proto_tree	*applemidi_tree_seq_num;


	col_set_str( pinfo->cinfo, COL_PROTOCOL, APPLEMIDI_DISSECTOR_SHORTNAME );

	col_add_str( pinfo->cinfo, COL_INFO, val_to_str( command, applemidi_commands, applemidi_unknown_command ) );

	ti = proto_tree_add_item( tree, proto_applemidi, tvb, 0, -1, ENC_NA  );
	applemidi_tree = proto_item_add_subtree( ti, ett_applemidi );

	proto_tree_add_item( applemidi_tree, hf_applemidi_signature, tvb, offset, 2, ENC_BIG_ENDIAN  );
	offset += 2;

	proto_tree_add_item( applemidi_tree, hf_applemidi_command, tvb, offset, 2, ENC_BIG_ENDIAN  );
	offset += 2;

	/* the format of packets for "IN", "NO", "OK" and "BY" is identical and contains
	 * the protocol version, a random number generated by the initiator of the session,
	 * the SSRC that is used by the respective sides RTP-entity and optionally the
	 * name of the participant */
	if ( ( APPLEMIDI_COMMAND_INVITATION == command ) ||
	     ( APPLEMIDI_COMMAND_INVITATION_REJECTED == command ) ||
	     ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED == command ) ||
	     ( APPLEMIDI_COMMAND_ENDSESSION == command ) ) {

		proto_tree_add_item( applemidi_tree, hf_applemidi_protocol_version, tvb, offset, 4, ENC_BIG_ENDIAN  );
		offset += 4;

		proto_tree_add_item( applemidi_tree, hf_applemidi_token, tvb, offset, 4, ENC_BIG_ENDIAN  );
		offset += 4;

		proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN  );
		offset += 4;

		len = tvb_reported_length(tvb) - offset;

		/* Name is optional */
		if ( len > 0 ) {
			name = tvb_get_string_enc( pinfo->pool, tvb, offset, len, ENC_UTF_8|ENC_NA );
			string_size = (int)( strlen( name ) + 1 );
			proto_tree_add_item( applemidi_tree, hf_applemidi_name, tvb, offset, string_size, ENC_UTF_8 );
			col_append_fstr( pinfo->cinfo, COL_INFO, ": peer = \"%s\"", name );
			offset += string_size;
		}

		/* the synchronization packet contains three 64bit timestamps,  and a value to define how
		 * many of the timestamps transmitted are valid */
	} else if ( APPLEMIDI_COMMAND_SYNCHRONIZATION == command ) {
		proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
		offset += 4;

		count = tvb_get_uint8( tvb, offset );
		proto_tree_add_item( applemidi_tree, hf_applemidi_count, tvb, offset, 1, ENC_BIG_ENDIAN );
		col_append_fstr( pinfo->cinfo, COL_INFO, ": count = %u", count );
		offset += 1;

		proto_tree_add_item( applemidi_tree, hf_applemidi_padding, tvb, offset, 3, ENC_BIG_ENDIAN );
		offset += 3;

		proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp1, tvb, offset, 8, ENC_BIG_ENDIAN );
		offset += 8;

		proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp2, tvb, offset, 8, ENC_BIG_ENDIAN );
		offset += 8;

		proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp3, tvb, offset, 8, ENC_BIG_ENDIAN );
		offset += 8;
		/* With the receiver feedback packet, the recipient can tell the sender up to what sequence
		 * number in the RTP-stream the packets have been received; this can be used to shorten the
		 * recovery-journal-section in the RTP-session */
	} else if ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK == command ) {
		proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
		offset += 4;

		ti = proto_tree_add_item( applemidi_tree, hf_applemidi_sequence_num, tvb, offset, 4, ENC_BIG_ENDIAN );
		/* Apple includes a 32bit sequence-number, but the RTP-packet only specifies 16bit.
		 * this subtree and subitem are added to be able to associate the sequence-number
		 * here easier with the one specified in the corresponding RTP-packet */
		applemidi_tree_seq_num = proto_item_add_subtree( ti, ett_applemidi_seq_num );
		seq_num = tvb_get_ntohs( tvb, offset );
		proto_tree_add_uint( applemidi_tree_seq_num, hf_applemidi_rtp_sequence_num, tvb, offset, 2, seq_num );
		offset += 4;

		col_append_fstr( pinfo->cinfo, COL_INFO, ": seq = %u", seq_num );
		/* With the bitrate receive limit packet, the recipient can tell the sender to limit
		   the transmission to a certain bitrate.  This is important if the peer is a gateway
		   to a hardware-device that only supports a certain speed.  Like the MIDI 1.0 DIN-cable
		   MIDI-implementation which is limited to 31250.  */
	} else if ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT == command ) {
		proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
		offset += 4;

		proto_tree_add_item( applemidi_tree, hf_applemidi_rtp_bitrate_limit,
				     tvb, offset, 4, ENC_BIG_ENDIAN );
		offset += 4;
	}
	/* If there is any remaining data (possibly because an unknown command was encountered),
	 * we just dump it here */
	len = tvb_reported_length_remaining( tvb, offset );
	if ( len > 0 ) {
		proto_tree_add_item( applemidi_tree, hf_applemidi_unknown_data, tvb, offset, len, ENC_NA );
	}
}

static bool
test_applemidi(tvbuff_t *tvb, uint16_t *command_p, bool conversation_established ) {

	*command_p = 0xffff;

	/* An applemidi session protocol UDP-packet must start with the "magic value" of 0xffff ... */
	if ( APPLEMIDI_PROTOCOL_SIGNATURE != tvb_get_ntohs( tvb, 0 ) )
		return false;

	*command_p = tvb_get_ntohs( tvb, 2 );

	/* If the conversation is establised (one prior packet with a valid known command)
	 * we won't check the commands anymore - this way we still show new commands
	 * Apple might introduce as "unknown" instead of punting to RTP-dissector */
	if ( conversation_established ) {
		return true;
	}


	/* ... followed by packet-command: "IN", "NO", "OK", "BY", "CK" and "RS" and "RL" */
	if ( ( APPLEMIDI_COMMAND_INVITATION            == *command_p ) ||
	     ( APPLEMIDI_COMMAND_INVITATION_REJECTED   == *command_p ) ||
	     ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED   == *command_p ) ||
	     ( APPLEMIDI_COMMAND_ENDSESSION            == *command_p ) ||
	     ( APPLEMIDI_COMMAND_SYNCHRONIZATION       == *command_p ) ||
	     ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK     == *command_p ) ||
	     ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT == *command_p ) )
		return true;

	return false;
}



/* dissect_applemidi() is called when a packet is seen from a previously identified applemidi conversation */
/*  If the packet isn't a valid applemidi packet, assume it's an RTP-MIDI packet.                          */

static int
dissect_applemidi( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_ ) {
	uint16_t		command;

	if ( test_applemidi( tvb, &command, true ) )
		dissect_applemidi_common( tvb, pinfo, tree, command );
	else
		call_dissector( rtp_handle, tvb, pinfo, tree );

	return tvb_captured_length(tvb);
}

static bool
dissect_applemidi_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_ ) {

	uint16_t		 command;
	conversation_t	*p_conv;
	rtp_dyn_payload_t *rtp_dyn_payload;

	if ( tvb_captured_length( tvb ) < 4)
		return false;  /* not enough bytes to check */

	if ( !test_applemidi( tvb, &command, false ) ) {
		return false;
	}

	/* set dynamic payload-type 97 which is used by Apple for their RTP-MIDI implementation for this
	   address/port-tuple to cause RTP-dissector to call the RTP-MIDI-dissector for payload-decoding */

	rtp_dyn_payload = rtp_dyn_payload_new();
	rtp_dyn_payload_insert(rtp_dyn_payload, 97, "rtp-midi", 10000, 1);
	rtp_add_address( pinfo, PT_UDP, &pinfo->src, pinfo->srcport, 0, APPLEMIDI_DISSECTOR_SHORTNAME,
			 pinfo->num, false, rtp_dyn_payload);

	/* call dissect_applemidi() from now on for UDP packets on this "connection"
	   it is important to do this step after calling rtp_add_address, otherwise
	   all further packets will go directly to the RTP-dissector!                */

	p_conv = find_or_create_conversation(pinfo);
	conversation_set_dissector( p_conv, applemidi_handle );

	/* punt to actual decoding */

	dissect_applemidi_common( tvb, pinfo, tree, command );
	return true;

}


void
proto_register_applemidi( void )
{
	static hf_register_info hf[] =	{
		{
			&hf_applemidi_signature,
			{
				"Signature",
				"applemidi.signature",
				FT_UINT16,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_command,
			{
				"Command",
				"applemidi.command",
				FT_UINT16,
				BASE_HEX,
				VALS( applemidi_commands ),
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_protocol_version,
			{
				"Protocol Version",
				"applemidi.protocol_version",
				FT_UINT32,
				BASE_DEC,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_token,
			{
				"Initiator Token",
				"applemidi.initiator_token",
				FT_UINT32,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_ssrc,
			{
				"Sender SSRC",
				"applemidi.sender_ssrc",
				FT_UINT32,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_name,
			{
				"Name",
				"applemidi.name",
				FT_STRING,
				BASE_NONE,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_count,
			{
				"Count",
				"applemidi.count",
				FT_UINT8,
				BASE_DEC,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_padding,
			{
				"Padding",
				"applemidi.padding",
				FT_UINT24,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_timestamp1,
			{
				"Timestamp 1",
				"applemidi.timestamp1",
				FT_UINT64,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_timestamp2,
			{
				"Timestamp 2",
				"applemidi.timestamp2",
				FT_UINT64,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_timestamp3,
			{
				"Timestamp 3",
				"applemidi.timestamp3",
				FT_UINT64,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_sequence_num,
			{
				"Sequence Number",
				"applemidi.sequence_number",
				FT_UINT32,
				BASE_HEX,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_rtp_sequence_num,
			{
				"RTP Sequence Number",
				"applemidi.rtp_sequence_number",
				FT_UINT16,
				BASE_DEC,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_rtp_bitrate_limit,
			{
				"Bitrate limit",
				"applemidi.bitrate_limit",
				FT_UINT32,
				BASE_DEC,
				NULL,
				0x0,
				NULL, HFILL
			}
		},
		{
			&hf_applemidi_unknown_data,
			{
				"Unknown Data",
				"applemidi.unknown_data",
				FT_BYTES,
				BASE_NONE,
				NULL,
				0x00,
				NULL, HFILL
			}
		},
	};


	static int *ett[] = {
		&ett_applemidi,
		&ett_applemidi_seq_num
	};

	proto_applemidi = proto_register_protocol( APPLEMIDI_DISSECTOR_NAME,
						   APPLEMIDI_DISSECTOR_SHORTNAME,
						   APPLEMIDI_DISSECTOR_ABBREVIATION );
	proto_register_field_array( proto_applemidi, hf, array_length( hf ) );
	proto_register_subtree_array( ett, array_length( ett ) );

	applemidi_handle = register_dissector( "applemidi", dissect_applemidi, proto_applemidi );
}

void
proto_reg_handoff_applemidi( void ) {
	/* If we cannot decode the data it will be RTP-MIDI since the Apple session protocol uses
	 * two ports: the control-port and the MIDI-port.  On both ports an invitation is being sent.
	 * The second port is then used for the RTP-MIDI-data. So if we can't find valid AppleMidi
	 * packets, it will be most likely RTP-MIDI...
	 */
	rtp_handle = find_dissector_add_dependency( "rtp", proto_applemidi );
	heur_dissector_add( "udp", dissect_applemidi_heur, "Apple MIDI over UDP", "applemidi_udp", proto_applemidi, HEURISTIC_ENABLE );
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: t
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
 * :indentSize=8:tabSize=8:noTabs=false:
 */