2 * Routines for dissection of Apple network-midi session establishment.
3 * Copyright 2006-2012, Tobias Erichsen <t.erichsen@gmx.de>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * Copied from packet-data.c, README.developer, and various other files.
11 * SPDX-License-Identifier: GPL-2.0-or-later
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.
24 * - initial version of dissector
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
30 * Here are some links:
32 * http://www.cs.berkeley.edu/~lazzaro/rtpmidi/
33 * https://tools.ietf.org/html/rfc4695
34 * https://tools.ietf.org/html/rfc6925
39 #include <epan/packet.h>
40 #include <epan/conversation.h>
42 #include "packet-rtp.h"
44 void proto_register_applemidi(void);
45 void proto_reg_handoff_applemidi(void);
47 /* Definitions for protocol name during dissector-register */
48 #define APPLEMIDI_DISSECTOR_NAME "Apple Network-MIDI Session Protocol"
49 #define APPLEMIDI_DISSECTOR_SHORTNAME "AppleMIDI"
50 #define APPLEMIDI_DISSECTOR_ABBREVIATION "applemidi"
52 /* Signature "Magic Value" for Apple network MIDI session establishment */
53 #define APPLEMIDI_PROTOCOL_SIGNATURE 0xffff
55 /* Apple network MIDI valid commands */
56 #define APPLEMIDI_COMMAND_INVITATION 0x494e /* "IN" */
57 #define APPLEMIDI_COMMAND_INVITATION_REJECTED 0x4e4f /* "NO" */
58 #define APLLEMIDI_COMMAND_INVITATION_ACCEPTED 0x4f4b /* "OK" */
59 #define APPLEMIDI_COMMAND_ENDSESSION 0x4259 /* "BY" */
60 #define APPLEMIDI_COMMAND_SYNCHRONIZATION 0x434b /* "CK" */
61 #define APPLEMIDI_COMMAND_RECEIVER_FEEDBACK 0x5253 /* "RS" */
62 #define APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT 0x524c /* "RL" */
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
;
81 static int ett_applemidi
;
82 static int ett_applemidi_seq_num
;
85 static const value_string applemidi_commands
[] = {
86 { APPLEMIDI_COMMAND_INVITATION
, "Invitation" },
87 { APPLEMIDI_COMMAND_INVITATION_REJECTED
, "Invitation Rejected" },
88 { APLLEMIDI_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" },
97 static int proto_applemidi
;
99 static dissector_handle_t applemidi_handle
;
100 static dissector_handle_t rtp_handle
;
102 static const char applemidi_unknown_command
[] = "unknown command: 0x%04x";
105 dissect_applemidi_common( tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, uint16_t command
) {
114 proto_tree
*applemidi_tree
;
115 proto_tree
*applemidi_tree_seq_num
;
118 col_set_str( pinfo
->cinfo
, COL_PROTOCOL
, APPLEMIDI_DISSECTOR_SHORTNAME
);
120 col_add_str( pinfo
->cinfo
, COL_INFO
, val_to_str( command
, applemidi_commands
, applemidi_unknown_command
) );
122 ti
= proto_tree_add_item( tree
, proto_applemidi
, tvb
, 0, -1, ENC_NA
);
123 applemidi_tree
= proto_item_add_subtree( ti
, ett_applemidi
);
125 proto_tree_add_item( applemidi_tree
, hf_applemidi_signature
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
128 proto_tree_add_item( applemidi_tree
, hf_applemidi_command
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
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 if ( ( APPLEMIDI_COMMAND_INVITATION
== command
) ||
136 ( APPLEMIDI_COMMAND_INVITATION_REJECTED
== command
) ||
137 ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED
== command
) ||
138 ( APPLEMIDI_COMMAND_ENDSESSION
== command
) ) {
140 proto_tree_add_item( applemidi_tree
, hf_applemidi_protocol_version
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
143 proto_tree_add_item( applemidi_tree
, hf_applemidi_token
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
146 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
149 len
= tvb_reported_length(tvb
) - offset
;
151 /* Name is optional */
153 name
= tvb_get_string_enc( pinfo
->pool
, tvb
, offset
, len
, ENC_UTF_8
|ENC_NA
);
154 string_size
= (int)( strlen( name
) + 1 );
155 proto_tree_add_item( applemidi_tree
, hf_applemidi_name
, tvb
, offset
, string_size
, ENC_UTF_8
);
156 col_append_fstr( pinfo
->cinfo
, COL_INFO
, ": peer = \"%s\"", name
);
157 offset
+= string_size
;
160 /* the synchronization packet contains three 64bit timestamps, and a value to define how
161 * many of the timestamps transmitted are valid */
162 } else if ( APPLEMIDI_COMMAND_SYNCHRONIZATION
== command
) {
163 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
166 count
= tvb_get_uint8( tvb
, offset
);
167 proto_tree_add_item( applemidi_tree
, hf_applemidi_count
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
168 col_append_fstr( pinfo
->cinfo
, COL_INFO
, ": count = %u", count
);
171 proto_tree_add_item( applemidi_tree
, hf_applemidi_padding
, tvb
, offset
, 3, ENC_BIG_ENDIAN
);
174 proto_tree_add_item( applemidi_tree
, hf_applemidi_timestamp1
, tvb
, offset
, 8, ENC_BIG_ENDIAN
);
177 proto_tree_add_item( applemidi_tree
, hf_applemidi_timestamp2
, tvb
, offset
, 8, ENC_BIG_ENDIAN
);
180 proto_tree_add_item( applemidi_tree
, hf_applemidi_timestamp3
, tvb
, offset
, 8, ENC_BIG_ENDIAN
);
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 } else if ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK
== command
) {
186 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
189 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 applemidi_tree_seq_num
= proto_item_add_subtree( ti
, ett_applemidi_seq_num
);
194 seq_num
= tvb_get_ntohs( tvb
, offset
);
195 proto_tree_add_uint( applemidi_tree_seq_num
, hf_applemidi_rtp_sequence_num
, tvb
, offset
, 2, seq_num
);
198 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 } else if ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT
== command
) {
204 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
207 proto_tree_add_item( applemidi_tree
, hf_applemidi_rtp_bitrate_limit
,
208 tvb
, offset
, 4, ENC_BIG_ENDIAN
);
211 /* If there is any remaining data (possibly because an unknown command was encountered),
212 * we just dump it here */
213 len
= tvb_reported_length_remaining( tvb
, offset
);
215 proto_tree_add_item( applemidi_tree
, hf_applemidi_unknown_data
, tvb
, offset
, len
, ENC_NA
);
220 test_applemidi(tvbuff_t
*tvb
, uint16_t *command_p
, bool conversation_established
) {
224 /* An applemidi session protocol UDP-packet must start with the "magic value" of 0xffff ... */
225 if ( APPLEMIDI_PROTOCOL_SIGNATURE
!= tvb_get_ntohs( tvb
, 0 ) )
228 *command_p
= tvb_get_ntohs( tvb
, 2 );
230 /* If the conversation is establised (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 if ( conversation_established
) {
238 /* ... followed by packet-command: "IN", "NO", "OK", "BY", "CK" and "RS" and "RL" */
239 if ( ( APPLEMIDI_COMMAND_INVITATION
== *command_p
) ||
240 ( APPLEMIDI_COMMAND_INVITATION_REJECTED
== *command_p
) ||
241 ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED
== *command_p
) ||
242 ( APPLEMIDI_COMMAND_ENDSESSION
== *command_p
) ||
243 ( APPLEMIDI_COMMAND_SYNCHRONIZATION
== *command_p
) ||
244 ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK
== *command_p
) ||
245 ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT
== *command_p
) )
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. */
257 dissect_applemidi( tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void* data _U_
) {
260 if ( test_applemidi( tvb
, &command
, true ) )
261 dissect_applemidi_common( tvb
, pinfo
, tree
, command
);
263 call_dissector( rtp_handle
, tvb
, pinfo
, tree
);
265 return tvb_captured_length(tvb
);
269 dissect_applemidi_heur( tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
) {
272 conversation_t
*p_conv
;
273 rtp_dyn_payload_t
*rtp_dyn_payload
;
275 if ( tvb_captured_length( tvb
) < 4)
276 return false; /* not enough bytes to check */
278 if ( !test_applemidi( tvb
, &command
, false ) ) {
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 */
285 rtp_dyn_payload
= rtp_dyn_payload_new();
286 rtp_dyn_payload_insert(rtp_dyn_payload
, 97, "rtp-midi", 10000, 1);
287 rtp_add_address( pinfo
, PT_UDP
, &pinfo
->src
, pinfo
->srcport
, 0, APPLEMIDI_DISSECTOR_SHORTNAME
,
288 pinfo
->num
, false, rtp_dyn_payload
);
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! */
294 p_conv
= find_or_create_conversation(pinfo
);
295 conversation_set_dissector( p_conv
, applemidi_handle
);
297 /* punt to actual decoding */
299 dissect_applemidi_common( tvb
, pinfo
, tree
, command
);
306 proto_register_applemidi( void )
308 static hf_register_info hf
[] = {
310 &hf_applemidi_signature
,
313 "applemidi.signature",
322 &hf_applemidi_command
,
328 VALS( applemidi_commands
),
334 &hf_applemidi_protocol_version
,
337 "applemidi.protocol_version",
349 "applemidi.initiator_token",
361 "applemidi.sender_ssrc",
394 &hf_applemidi_padding
,
406 &hf_applemidi_timestamp1
,
409 "applemidi.timestamp1",
418 &hf_applemidi_timestamp2
,
421 "applemidi.timestamp2",
430 &hf_applemidi_timestamp3
,
433 "applemidi.timestamp3",
442 &hf_applemidi_sequence_num
,
445 "applemidi.sequence_number",
454 &hf_applemidi_rtp_sequence_num
,
456 "RTP Sequence Number",
457 "applemidi.rtp_sequence_number",
466 &hf_applemidi_rtp_bitrate_limit
,
469 "applemidi.bitrate_limit",
478 &hf_applemidi_unknown_data
,
481 "applemidi.unknown_data",
492 static int *ett
[] = {
494 &ett_applemidi_seq_num
497 proto_applemidi
= proto_register_protocol( APPLEMIDI_DISSECTOR_NAME
,
498 APPLEMIDI_DISSECTOR_SHORTNAME
,
499 APPLEMIDI_DISSECTOR_ABBREVIATION
);
500 proto_register_field_array( proto_applemidi
, hf
, array_length( hf
) );
501 proto_register_subtree_array( ett
, array_length( ett
) );
503 applemidi_handle
= register_dissector( "applemidi", dissect_applemidi
, proto_applemidi
);
507 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...
513 rtp_handle
= find_dissector_add_dependency( "rtp", proto_applemidi
);
514 heur_dissector_add( "udp", dissect_applemidi_heur
, "Apple MIDI over UDP", "applemidi_udp", proto_applemidi
, HEURISTIC_ENABLE
);
518 * Editor modelines - https://www.wireshark.org/tools/modelines.html
523 * indent-tabs-mode: t
526 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
527 * :indentSize=8:tabSize=8:noTabs=false: