Coverage Report

Created: 2026-07-12 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-applemidi.c
Line
Count
Source
1
/* packet-applemidi.c
2
 * Routines for dissection of Apple network-midi session establishment.
3
 * Copyright 2006-2012, Tobias Erichsen <t.erichsen@gmx.de>
4
 *
5
 * Wireshark - Network traffic analyzer
6
 * By Gerald Combs <gerald@wireshark.org>
7
 * Copyright 1998 Gerald Combs
8
 *
9
 * Copied from packet-data.c, README.developer, and various other files.
10
 *
11
 * SPDX-License-Identifier: GPL-2.0-or-later
12
 *
13
 *
14
 * Apple network-midi session establishment is a lightweight protocol for
15
 * providing a simple session establishment for MIDI-data sent in the form
16
 * of RTP-MIDI (RFC 4695 / 6295).  Peers recognize each other using the
17
 * Apple Bonjour scheme with the service-name "_apple-midi._udp", establish
18
 * a connection using AppleMIDI (no official name, just an abbreviation)
19
 * and then send payload using RTP-MIDI.  The implementation of this
20
 * dissector is based on the Apple implementation summary from May 6th, 2005
21
 * and the extension from August 13th, 2010.
22
 *
23
 * 2010-11-29
24
 * - initial version of dissector
25
 * 2012-02-24
26
 * - implemented dynamic payloadtype support to automatically punt
27
 *   the decoding to the RTP-MIDI dissector via the RTP dissector
28
 * - added new bitrate receive limit feature
29
 *
30
 * Here are some links:
31
 *
32
 * http://www.cs.berkeley.edu/~lazzaro/rtpmidi/
33
 * https://tools.ietf.org/html/rfc4695
34
 * https://tools.ietf.org/html/rfc6925
35
 */
36
37
#include "config.h"
38
39
#include <epan/packet.h>
40
#include <epan/conversation.h>
41
42
#include "packet-rtp.h"
43
44
void proto_register_applemidi(void);
45
void proto_reg_handoff_applemidi(void);
46
47
/* Definitions for protocol name during dissector-register */
48
15
#define APPLEMIDI_DISSECTOR_NAME      "Apple Network-MIDI Session Protocol"
49
15
#define APPLEMIDI_DISSECTOR_SHORTNAME     "AppleMIDI"
50
15
#define APPLEMIDI_DISSECTOR_ABBREVIATION    "applemidi"
51
52
/* Signature "Magic Value" for Apple network MIDI session establishment */
53
0
#define APPLEMIDI_PROTOCOL_SIGNATURE      0xffff
54
55
/* Apple network MIDI valid commands */
56
0
#define APPLEMIDI_COMMAND_INVITATION      0x494e    /*   "IN"   */
57
0
#define APPLEMIDI_COMMAND_INVITATION_REJECTED   0x4e4f    /*   "NO"   */
58
0
#define APPLEMIDI_COMMAND_INVITATION_ACCEPTED   0x4f4b    /*   "OK"   */
59
0
#define APPLEMIDI_COMMAND_ENDSESSION      0x4259    /*   "BY"   */
60
0
#define APPLEMIDI_COMMAND_SYNCHRONIZATION   0x434b    /*   "CK"   */
61
0
#define APPLEMIDI_COMMAND_RECEIVER_FEEDBACK   0x5253    /*   "RS"   */
62
0
#define APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT   0x524c    /*   "RL"   */
63
64
static int  hf_applemidi_signature;
65
static int  hf_applemidi_command;
66
static int  hf_applemidi_protocol_version;
67
static int  hf_applemidi_token;
68
static int  hf_applemidi_ssrc;
69
static int  hf_applemidi_name;
70
static int  hf_applemidi_count;
71
static int  hf_applemidi_padding;
72
static int  hf_applemidi_timestamp1;
73
static int  hf_applemidi_timestamp2;
74
static int  hf_applemidi_timestamp3;
75
static int  hf_applemidi_sequence_num;
76
static int  hf_applemidi_rtp_sequence_num;
77
static int  hf_applemidi_rtp_bitrate_limit;
78
static int  hf_applemidi_unknown_data;
79
80
81
static int  ett_applemidi;
82
static int  ett_applemidi_seq_num;
83
84
85
static const value_string applemidi_commands[] = {
86
  { APPLEMIDI_COMMAND_INVITATION,     "Invitation" },
87
  { APPLEMIDI_COMMAND_INVITATION_REJECTED,  "Invitation Rejected" },
88
  { APPLEMIDI_COMMAND_INVITATION_ACCEPTED,  "Invitation Accepted" },
89
  { APPLEMIDI_COMMAND_ENDSESSION,     "End Session" },
90
  { APPLEMIDI_COMMAND_SYNCHRONIZATION,    "Synchronization" },
91
  { APPLEMIDI_COMMAND_RECEIVER_FEEDBACK,    "Receiver Feedback" },
92
  { APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT,  "Bitrate Receive Limit" },
93
  { 0,            NULL },
94
};
95
96
97
static int      proto_applemidi;
98
99
static dissector_handle_t applemidi_handle;
100
static dissector_handle_t rtp_handle;
101
102
static const char applemidi_unknown_command[]   = "unknown command: 0x%04x";
103
104
static void
105
0
dissect_applemidi_common( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint16_t command ) {
106
107
0
  proto_item *ti;
108
0
  uint16_t     seq_num;
109
0
  uint8_t    count;
110
0
  const char  *name;
111
0
  int    offset     = 0;
112
0
  int    len;
113
0
  int    string_size;
114
0
  proto_tree  *applemidi_tree;
115
0
  proto_tree  *applemidi_tree_seq_num;
116
117
118
0
  col_set_str( pinfo->cinfo, COL_PROTOCOL, APPLEMIDI_DISSECTOR_SHORTNAME );
119
120
0
  col_add_str( pinfo->cinfo, COL_INFO, val_to_str(pinfo->pool, command, applemidi_commands, applemidi_unknown_command ) );
121
122
0
  ti = proto_tree_add_item( tree, proto_applemidi, tvb, 0, -1, ENC_NA  );
123
0
  applemidi_tree = proto_item_add_subtree( ti, ett_applemidi );
124
125
0
  proto_tree_add_item( applemidi_tree, hf_applemidi_signature, tvb, offset, 2, ENC_BIG_ENDIAN  );
126
0
  offset += 2;
127
128
0
  proto_tree_add_item( applemidi_tree, hf_applemidi_command, tvb, offset, 2, ENC_BIG_ENDIAN  );
129
0
  offset += 2;
130
131
  /* the format of packets for "IN", "NO", "OK" and "BY" is identical and contains
132
   * the protocol version, a random number generated by the initiator of the session,
133
   * the SSRC that is used by the respective sides RTP-entity and optionally the
134
   * name of the participant */
135
0
  if ( ( APPLEMIDI_COMMAND_INVITATION == command ) ||
136
0
       ( APPLEMIDI_COMMAND_INVITATION_REJECTED == command ) ||
137
0
       ( APPLEMIDI_COMMAND_INVITATION_ACCEPTED == command ) ||
138
0
       ( APPLEMIDI_COMMAND_ENDSESSION == command ) ) {
139
140
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_protocol_version, tvb, offset, 4, ENC_BIG_ENDIAN  );
141
0
    offset += 4;
142
143
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_token, tvb, offset, 4, ENC_BIG_ENDIAN  );
144
0
    offset += 4;
145
146
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN  );
147
0
    offset += 4;
148
149
0
    len = tvb_reported_length(tvb) - offset;
150
151
    /* Name is optional */
152
0
    if ( len > 0 ) {
153
0
      name = (char *)tvb_get_string_enc( pinfo->pool, tvb, offset, len, ENC_UTF_8|ENC_NA );
154
0
      string_size = (int)( strlen( name ) + 1 );
155
0
      proto_tree_add_item( applemidi_tree, hf_applemidi_name, tvb, offset, string_size, ENC_UTF_8 );
156
0
      col_append_fstr( pinfo->cinfo, COL_INFO, ": peer = \"%s\"", name );
157
0
      offset += string_size;
158
0
    }
159
160
    /* the synchronization packet contains three 64bit timestamps,  and a value to define how
161
     * many of the timestamps transmitted are valid */
162
0
  } else if ( APPLEMIDI_COMMAND_SYNCHRONIZATION == command ) {
163
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
164
0
    offset += 4;
165
166
0
    count = tvb_get_uint8( tvb, offset );
167
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_count, tvb, offset, 1, ENC_BIG_ENDIAN );
168
0
    col_append_fstr( pinfo->cinfo, COL_INFO, ": count = %u", count );
169
0
    offset += 1;
170
171
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_padding, tvb, offset, 3, ENC_BIG_ENDIAN );
172
0
    offset += 3;
173
174
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp1, tvb, offset, 8, ENC_BIG_ENDIAN );
175
0
    offset += 8;
176
177
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp2, tvb, offset, 8, ENC_BIG_ENDIAN );
178
0
    offset += 8;
179
180
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp3, tvb, offset, 8, ENC_BIG_ENDIAN );
181
0
    offset += 8;
182
    /* With the receiver feedback packet, the recipient can tell the sender up to what sequence
183
     * number in the RTP-stream the packets have been received; this can be used to shorten the
184
     * recovery-journal-section in the RTP-session */
185
0
  } else if ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK == command ) {
186
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
187
0
    offset += 4;
188
189
0
    ti = proto_tree_add_item( applemidi_tree, hf_applemidi_sequence_num, tvb, offset, 4, ENC_BIG_ENDIAN );
190
    /* Apple includes a 32bit sequence-number, but the RTP-packet only specifies 16bit.
191
     * this subtree and subitem are added to be able to associate the sequence-number
192
     * here easier with the one specified in the corresponding RTP-packet */
193
0
    applemidi_tree_seq_num = proto_item_add_subtree( ti, ett_applemidi_seq_num );
194
0
    seq_num = tvb_get_ntohs( tvb, offset );
195
0
    proto_tree_add_uint( applemidi_tree_seq_num, hf_applemidi_rtp_sequence_num, tvb, offset, 2, seq_num );
196
0
    offset += 4;
197
198
0
    col_append_fstr( pinfo->cinfo, COL_INFO, ": seq = %u", seq_num );
199
    /* With the bitrate receive limit packet, the recipient can tell the sender to limit
200
       the transmission to a certain bitrate.  This is important if the peer is a gateway
201
       to a hardware-device that only supports a certain speed.  Like the MIDI 1.0 DIN-cable
202
       MIDI-implementation which is limited to 31250.  */
203
0
  } else if ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT == command ) {
204
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
205
0
    offset += 4;
206
207
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_rtp_bitrate_limit,
208
0
             tvb, offset, 4, ENC_BIG_ENDIAN );
209
0
    offset += 4;
210
0
  }
211
  /* If there is any remaining data (possibly because an unknown command was encountered),
212
   * we just dump it here */
213
0
  len = tvb_reported_length_remaining( tvb, offset );
214
0
  if ( len > 0 ) {
215
0
    proto_tree_add_item( applemidi_tree, hf_applemidi_unknown_data, tvb, offset, len, ENC_NA );
216
0
  }
217
0
}
218
219
static bool
220
0
test_applemidi(tvbuff_t *tvb, uint16_t *command_p, bool conversation_established ) {
221
222
0
  *command_p = 0xffff;
223
224
  /* An applemidi session protocol UDP-packet must start with the "magic value" of 0xffff ... */
225
0
  if ( APPLEMIDI_PROTOCOL_SIGNATURE != tvb_get_ntohs( tvb, 0 ) )
226
0
    return false;
227
228
0
  *command_p = tvb_get_ntohs( tvb, 2 );
229
230
  /* If the conversation is established (one prior packet with a valid known command)
231
   * we won't check the commands anymore - this way we still show new commands
232
   * Apple might introduce as "unknown" instead of punting to RTP-dissector */
233
0
  if ( conversation_established ) {
234
0
    return true;
235
0
  }
236
237
238
  /* ... followed by packet-command: "IN", "NO", "OK", "BY", "CK" and "RS" and "RL" */
239
0
  if ( ( APPLEMIDI_COMMAND_INVITATION            == *command_p ) ||
240
0
       ( APPLEMIDI_COMMAND_INVITATION_REJECTED   == *command_p ) ||
241
0
       ( APPLEMIDI_COMMAND_INVITATION_ACCEPTED   == *command_p ) ||
242
0
       ( APPLEMIDI_COMMAND_ENDSESSION            == *command_p ) ||
243
0
       ( APPLEMIDI_COMMAND_SYNCHRONIZATION       == *command_p ) ||
244
0
       ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK     == *command_p ) ||
245
0
       ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT == *command_p ) )
246
0
    return true;
247
248
0
  return false;
249
0
}
250
251
252
253
/* dissect_applemidi() is called when a packet is seen from a previously identified applemidi conversation */
254
/*  If the packet isn't a valid applemidi packet, assume it's an RTP-MIDI packet.                          */
255
256
static int
257
0
dissect_applemidi( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_ ) {
258
0
  uint16_t    command;
259
260
0
  if ( test_applemidi( tvb, &command, true ) )
261
0
    dissect_applemidi_common( tvb, pinfo, tree, command );
262
0
  else
263
0
    call_dissector( rtp_handle, tvb, pinfo, tree );
264
265
0
  return tvb_captured_length(tvb);
266
0
}
267
268
static bool
269
0
dissect_applemidi_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_ ) {
270
271
0
  uint16_t     command;
272
0
  conversation_t  *p_conv;
273
0
  rtp_dyn_payload_t *rtp_dyn_payload;
274
275
0
  if ( tvb_captured_length( tvb ) < 4)
276
0
    return false;  /* not enough bytes to check */
277
278
0
  if ( !test_applemidi( tvb, &command, false ) ) {
279
0
    return false;
280
0
  }
281
282
  /* set dynamic payload-type 97 which is used by Apple for their RTP-MIDI implementation for this
283
     address/port-tuple to cause RTP-dissector to call the RTP-MIDI-dissector for payload-decoding */
284
285
0
  rtp_dyn_payload = rtp_dyn_payload_new();
286
0
  rtp_dyn_payload_insert(rtp_dyn_payload, 97, "rtp-midi", 10000, 1);
287
0
  rtp_add_address( pinfo, PT_UDP, &pinfo->src, pinfo->srcport, 0, APPLEMIDI_DISSECTOR_SHORTNAME,
288
0
       pinfo->num, false, rtp_dyn_payload);
289
290
  /* call dissect_applemidi() from now on for UDP packets on this "connection"
291
     it is important to do this step after calling rtp_add_address, otherwise
292
     all further packets will go directly to the RTP-dissector!                */
293
294
0
  p_conv = find_or_create_conversation(pinfo);
295
0
  conversation_set_dissector( p_conv, applemidi_handle );
296
297
  /* punt to actual decoding */
298
299
0
  dissect_applemidi_common( tvb, pinfo, tree, command );
300
0
  return true;
301
302
0
}
303
304
305
void
306
proto_register_applemidi( void )
307
15
{
308
15
  static hf_register_info hf[] =  {
309
15
    {
310
15
      &hf_applemidi_signature,
311
15
      {
312
15
        "Signature",
313
15
        "applemidi.signature",
314
15
        FT_UINT16,
315
15
        BASE_HEX,
316
15
        NULL,
317
15
        0x0,
318
15
        NULL, HFILL
319
15
      }
320
15
    },
321
15
    {
322
15
      &hf_applemidi_command,
323
15
      {
324
15
        "Command",
325
15
        "applemidi.command",
326
15
        FT_UINT16,
327
15
        BASE_HEX,
328
15
        VALS( applemidi_commands ),
329
15
        0x0,
330
15
        NULL, HFILL
331
15
      }
332
15
    },
333
15
    {
334
15
      &hf_applemidi_protocol_version,
335
15
      {
336
15
        "Protocol Version",
337
15
        "applemidi.protocol_version",
338
15
        FT_UINT32,
339
15
        BASE_DEC,
340
15
        NULL,
341
15
        0x0,
342
15
        NULL, HFILL
343
15
      }
344
15
    },
345
15
    {
346
15
      &hf_applemidi_token,
347
15
      {
348
15
        "Initiator Token",
349
15
        "applemidi.initiator_token",
350
15
        FT_UINT32,
351
15
        BASE_HEX,
352
15
        NULL,
353
15
        0x0,
354
15
        NULL, HFILL
355
15
      }
356
15
    },
357
15
    {
358
15
      &hf_applemidi_ssrc,
359
15
      {
360
15
        "Sender SSRC",
361
15
        "applemidi.sender_ssrc",
362
15
        FT_UINT32,
363
15
        BASE_HEX,
364
15
        NULL,
365
15
        0x0,
366
15
        NULL, HFILL
367
15
      }
368
15
    },
369
15
    {
370
15
      &hf_applemidi_name,
371
15
      {
372
15
        "Name",
373
15
        "applemidi.name",
374
15
        FT_STRING,
375
15
        BASE_NONE,
376
15
        NULL,
377
15
        0x0,
378
15
        NULL, HFILL
379
15
      }
380
15
    },
381
15
    {
382
15
      &hf_applemidi_count,
383
15
      {
384
15
        "Count",
385
15
        "applemidi.count",
386
15
        FT_UINT8,
387
15
        BASE_DEC,
388
15
        NULL,
389
15
        0x0,
390
15
        NULL, HFILL
391
15
      }
392
15
    },
393
15
    {
394
15
      &hf_applemidi_padding,
395
15
      {
396
15
        "Padding",
397
15
        "applemidi.padding",
398
15
        FT_UINT24,
399
15
        BASE_HEX,
400
15
        NULL,
401
15
        0x0,
402
15
        NULL, HFILL
403
15
      }
404
15
    },
405
15
    {
406
15
      &hf_applemidi_timestamp1,
407
15
      {
408
15
        "Timestamp 1",
409
15
        "applemidi.timestamp1",
410
15
        FT_UINT64,
411
15
        BASE_HEX,
412
15
        NULL,
413
15
        0x0,
414
15
        NULL, HFILL
415
15
      }
416
15
    },
417
15
    {
418
15
      &hf_applemidi_timestamp2,
419
15
      {
420
15
        "Timestamp 2",
421
15
        "applemidi.timestamp2",
422
15
        FT_UINT64,
423
15
        BASE_HEX,
424
15
        NULL,
425
15
        0x0,
426
15
        NULL, HFILL
427
15
      }
428
15
    },
429
15
    {
430
15
      &hf_applemidi_timestamp3,
431
15
      {
432
15
        "Timestamp 3",
433
15
        "applemidi.timestamp3",
434
15
        FT_UINT64,
435
15
        BASE_HEX,
436
15
        NULL,
437
15
        0x0,
438
15
        NULL, HFILL
439
15
      }
440
15
    },
441
15
    {
442
15
      &hf_applemidi_sequence_num,
443
15
      {
444
15
        "Sequence Number",
445
15
        "applemidi.sequence_number",
446
15
        FT_UINT32,
447
15
        BASE_HEX,
448
15
        NULL,
449
15
        0x0,
450
15
        NULL, HFILL
451
15
      }
452
15
    },
453
15
    {
454
15
      &hf_applemidi_rtp_sequence_num,
455
15
      {
456
15
        "RTP Sequence Number",
457
15
        "applemidi.rtp_sequence_number",
458
15
        FT_UINT16,
459
15
        BASE_DEC,
460
15
        NULL,
461
15
        0x0,
462
15
        NULL, HFILL
463
15
      }
464
15
    },
465
15
    {
466
15
      &hf_applemidi_rtp_bitrate_limit,
467
15
      {
468
15
        "Bitrate limit",
469
15
        "applemidi.bitrate_limit",
470
15
        FT_UINT32,
471
15
        BASE_DEC,
472
15
        NULL,
473
15
        0x0,
474
15
        NULL, HFILL
475
15
      }
476
15
    },
477
15
    {
478
15
      &hf_applemidi_unknown_data,
479
15
      {
480
15
        "Unknown Data",
481
15
        "applemidi.unknown_data",
482
15
        FT_BYTES,
483
15
        BASE_NONE,
484
15
        NULL,
485
15
        0x00,
486
15
        NULL, HFILL
487
15
      }
488
15
    },
489
15
  };
490
491
492
15
  static int *ett[] = {
493
15
    &ett_applemidi,
494
15
    &ett_applemidi_seq_num
495
15
  };
496
497
15
  proto_applemidi = proto_register_protocol( APPLEMIDI_DISSECTOR_NAME,
498
15
               APPLEMIDI_DISSECTOR_SHORTNAME,
499
15
               APPLEMIDI_DISSECTOR_ABBREVIATION );
500
15
  proto_register_field_array( proto_applemidi, hf, array_length( hf ) );
501
15
  proto_register_subtree_array( ett, array_length( ett ) );
502
503
15
  applemidi_handle = register_dissector( "applemidi", dissect_applemidi, proto_applemidi );
504
15
}
505
506
void
507
15
proto_reg_handoff_applemidi( void ) {
508
  /* If we cannot decode the data it will be RTP-MIDI since the Apple session protocol uses
509
   * two ports: the control-port and the MIDI-port.  On both ports an invitation is being sent.
510
   * The second port is then used for the RTP-MIDI-data. So if we can't find valid AppleMidi
511
   * packets, it will be most likely RTP-MIDI...
512
   */
513
15
  rtp_handle = find_dissector_add_dependency( "rtp", proto_applemidi );
514
15
  heur_dissector_add( "udp", dissect_applemidi_heur, "Apple MIDI over UDP", "applemidi_udp", proto_applemidi, HEURISTIC_DISABLE );
515
15
}
516
517
/*
518
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
519
 *
520
 * Local variables:
521
 * c-basic-offset: 8
522
 * tab-width: 8
523
 * indent-tabs-mode: t
524
 * End:
525
 *
526
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
527
 * :indentSize=8:tabSize=8:noTabs=false:
528
 */