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 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 * Apple network-midi session establishment is a lightweight protocol for
27 * providing a simple session establishment for MIDI-data sent in the form
28 * of RTP-MIDI (RFC 4695 / 6295). Peers recognize each other using the
29 * Apple Bonjour scheme with the service-name "_apple-midi._udp", establish
30 * a connection using AppleMIDI (no official name, just an abbreviation)
31 * and then send payload using RTP-MIDI. The implementation of this
32 * dissector is based on the Apple implementation summary from May 6th, 2005
33 * and the extension from August 13th, 2010.
36 * - initial version of dissector
38 * - implemented dynamic payloadtype support to automatically punt
39 * the decoding to the RTP-MIDI dissector via the RTP dissector
40 * - added new bitrate receive limit feature
42 * Here are some links:
44 * http://www.cs.berkeley.edu/~lazzaro/rtpmidi/
45 * http://www.faqs.org/rfcs/rfc4695.html
46 * http://www.faqs.org/rfcs/rfc6295.html
52 #include <epan/packet.h>
53 #include <epan/wmem/wmem.h>
54 #include <epan/conversation.h>
56 #include "packet-rtp.h"
58 void proto_register_applemidi(void);
59 void proto_reg_handoff_applemidi(void);
61 /* Definitions for protocol name during dissector-register */
62 #define APPLEMIDI_DISSECTOR_NAME "Apple Network-MIDI Session Protocol"
63 #define APPLEMIDI_DISSECTOR_SHORTNAME "AppleMIDI"
64 #define APPLEMIDI_DISSECTOR_ABBREVIATION "applemidi"
66 /* Signature "Magic Value" for Apple network MIDI session establishment */
67 #define APPLEMIDI_PROTOCOL_SIGNATURE 0xffff
69 /* Apple network MIDI valid commands */
70 #define APPLEMIDI_COMMAND_INVITATION 0x494e /* "IN" */
71 #define APPLEMIDI_COMMAND_INVITATION_REJECTED 0x4e4f /* "NO" */
72 #define APLLEMIDI_COMMAND_INVITATION_ACCEPTED 0x4f4b /* "OK" */
73 #define APPLEMIDI_COMMAND_ENDSESSION 0x4259 /* "BY" */
74 #define APPLEMIDI_COMMAND_SYNCHRONIZATION 0x434b /* "CK" */
75 #define APPLEMIDI_COMMAND_RECEIVER_FEEDBACK 0x5253 /* "RS" */
76 #define APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT 0x524c /* "RL" */
78 static int hf_applemidi_signature = -1;
79 static int hf_applemidi_command = -1;
80 static int hf_applemidi_protocol_version = -1;
81 static int hf_applemidi_token = -1;
82 static int hf_applemidi_ssrc = -1;
83 static int hf_applemidi_name = -1;
84 static int hf_applemidi_count = -1;
85 static int hf_applemidi_padding = -1;
86 static int hf_applemidi_timestamp1 = -1;
87 static int hf_applemidi_timestamp2 = -1;
88 static int hf_applemidi_timestamp3 = -1;
89 static int hf_applemidi_sequence_num = -1;
90 static int hf_applemidi_rtp_sequence_num = -1;
91 static int hf_applemidi_rtp_bitrate_limit = -1;
92 static int hf_applemidi_unknown_data = -1;
95 static gint ett_applemidi = -1;
96 static gint ett_applemidi_seq_num = -1;
99 static const value_string applemidi_commands[] = {
100 { APPLEMIDI_COMMAND_INVITATION, "Invitation" },
101 { APPLEMIDI_COMMAND_INVITATION_REJECTED, "Invitation Rejected" },
102 { APLLEMIDI_COMMAND_INVITATION_ACCEPTED, "Invitation Accepted" },
103 { APPLEMIDI_COMMAND_ENDSESSION, "End Session" },
104 { APPLEMIDI_COMMAND_SYNCHRONIZATION, "Synchronization" },
105 { APPLEMIDI_COMMAND_RECEIVER_FEEDBACK, "Receiver Feedback" },
106 { APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT, "Bitrate Receive Limit" },
111 static int proto_applemidi = -1;
113 static dissector_handle_t applemidi_handle;
114 static dissector_handle_t rtp_handle;
116 static const char applemidi_unknown_command[] = "unknown command: 0x%04x";
119 dissect_applemidi_common( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint16 command ) {
127 proto_tree *applemidi_tree;
128 proto_tree *applemidi_tree_seq_num;
131 col_set_str( pinfo->cinfo, COL_PROTOCOL, APPLEMIDI_DISSECTOR_SHORTNAME );
133 col_add_fstr( pinfo->cinfo, COL_INFO, "%s", val_to_str( command, applemidi_commands, applemidi_unknown_command ) );
137 ti = proto_tree_add_item( tree, proto_applemidi, tvb, 0, -1, ENC_NA );
138 applemidi_tree = proto_item_add_subtree( ti, ett_applemidi );
140 proto_tree_add_item( applemidi_tree, hf_applemidi_signature, tvb, offset, 2, ENC_BIG_ENDIAN );
143 proto_tree_add_item( applemidi_tree, hf_applemidi_command, tvb, offset, 2, ENC_BIG_ENDIAN );
146 /* the format of packets for "IN", "NO", "OK" and "BY" is identical and contains
147 * the protocol version, a random number generated by the initiator of the session,
148 * the SSRC that is used by the respective sides RTP-entity and optionally the
149 * name of the participant */
150 if ( ( APPLEMIDI_COMMAND_INVITATION == command ) ||
151 ( APPLEMIDI_COMMAND_INVITATION_REJECTED == command ) ||
152 ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED == command ) ||
153 ( APPLEMIDI_COMMAND_ENDSESSION == command ) ) {
155 proto_tree_add_item( applemidi_tree, hf_applemidi_protocol_version, tvb, offset, 4, ENC_BIG_ENDIAN );
158 proto_tree_add_item( applemidi_tree, hf_applemidi_token, tvb, offset, 4, ENC_BIG_ENDIAN );
161 proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
164 len = tvb_reported_length(tvb) - offset;
166 /* Name is optional */
168 name = tvb_get_string_enc( wmem_packet_scope(), tvb, offset, len, ENC_UTF_8|ENC_NA );
169 string_size = (gint)( strlen( name ) + 1 );
170 proto_tree_add_item( applemidi_tree, hf_applemidi_name, tvb, offset, string_size, ENC_UTF_8|ENC_NA );
171 col_append_fstr( pinfo->cinfo, COL_INFO, ": peer = \"%s\"", name );
172 offset += string_size;
175 /* the synchronization packet contains three 64bit timestamps, and a value to define how
176 * many of the timestamps transmitted are valid */
177 } else if ( APPLEMIDI_COMMAND_SYNCHRONIZATION == command ) {
178 proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
181 count = tvb_get_guint8( tvb, offset );
182 proto_tree_add_item( applemidi_tree, hf_applemidi_count, tvb, offset, 1, ENC_BIG_ENDIAN );
183 col_append_fstr( pinfo->cinfo, COL_INFO, ": count = %u", count );
186 proto_tree_add_item( applemidi_tree, hf_applemidi_padding, tvb, offset, 3, ENC_BIG_ENDIAN );
189 proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp1, tvb, offset, 8, ENC_BIG_ENDIAN );
192 proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp2, tvb, offset, 8, ENC_BIG_ENDIAN );
195 proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp3, tvb, offset, 8, ENC_BIG_ENDIAN );
197 /* With the receiver feedback packet, the recipient can tell the sender up to what sequence
198 * number in the RTP-stream the packets have been received; this can be used to shorten the
199 * recovery-journal-section in the RTP-session */
200 } else if ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK == command ) {
201 proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
204 ti = proto_tree_add_item( applemidi_tree, hf_applemidi_sequence_num, tvb, offset, 4, ENC_BIG_ENDIAN );
205 /* Apple includes a 32bit sequence-number, but the RTP-packet only specifies 16bit.
206 * this subtree and subitem are added to be able to associate the sequence-number
207 * here easier with the one specified in the corresponding RTP-packet */
208 applemidi_tree_seq_num = proto_item_add_subtree( ti, ett_applemidi_seq_num );
209 seq_num = tvb_get_ntohs( tvb, offset );
210 proto_tree_add_uint( applemidi_tree_seq_num, hf_applemidi_rtp_sequence_num, tvb, offset, 2, seq_num );
213 col_append_fstr( pinfo->cinfo, COL_INFO, ": seq = %u", seq_num );
214 /* With the bitrate receive limit packet, the recipient can tell the sender to limit
215 the transmission to a certain bitrate. This is important if the peer is a gateway
216 to a hardware-device that only supports a certain speed. Like the MIDI 1.0 DIN-cable
217 MIDI-implementation which is limited to 31250. */
218 } else if ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT == command ) {
219 proto_tree_add_item( applemidi_tree, hf_applemidi_ssrc, tvb, offset, 4, ENC_BIG_ENDIAN );
222 proto_tree_add_item( applemidi_tree, hf_applemidi_rtp_bitrate_limit,
223 tvb, offset, 4, ENC_BIG_ENDIAN );
226 /* If there is any remaining data (possibly because an unknown command was encountered),
227 * we just dump it here */
228 len = tvb_length_remaining( tvb, offset );
230 proto_tree_add_item( applemidi_tree, hf_applemidi_unknown_data, tvb, offset, len, ENC_NA );
236 test_applemidi(tvbuff_t *tvb, guint16 *command_p, gboolean conversation_established ) {
240 /* An applemidi session protocol UDP-packet must start with the "magic value" of 0xffff ... */
241 if ( APPLEMIDI_PROTOCOL_SIGNATURE != tvb_get_ntohs( tvb, 0 ) )
244 *command_p = tvb_get_ntohs( tvb, 2 );
246 /* If the conversation is establised (one prior packet with a valid known command)
247 * we won't check the commands anymore - this way we still show new commands
248 * Apple might introduct as "unknown" instead of punting to RTP-dissector */
249 if ( conversation_established ) {
254 /* ... followed by packet-command: "IN", "NO", "OK", "BY", "CK" and "RS" and "RL" */
255 if ( ( APPLEMIDI_COMMAND_INVITATION == *command_p ) ||
256 ( APPLEMIDI_COMMAND_INVITATION_REJECTED == *command_p ) ||
257 ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED == *command_p ) ||
258 ( APPLEMIDI_COMMAND_ENDSESSION == *command_p ) ||
259 ( APPLEMIDI_COMMAND_SYNCHRONIZATION == *command_p ) ||
260 ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK == *command_p ) ||
261 ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT == *command_p ) )
269 /* dissect_applemidi() is called when a packet is seen from a previously identified applemidi conversation */
270 /* If the packet isn't a valid applemidi packet, assume it's an RTP-MIDI packet. */
273 dissect_applemidi( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree ) {
276 if ( test_applemidi( tvb, &command, TRUE ) )
277 dissect_applemidi_common( tvb, pinfo, tree, command );
279 call_dissector( rtp_handle, tvb, pinfo, tree );
283 dissect_applemidi_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_ ) {
286 conversation_t *p_conv;
287 /*struct _rtp_conversation_info *p_conv_data = NULL;*/
288 rtp_dyn_payload_t *rtp_dyn_payload = NULL;
290 if ( tvb_length( tvb ) < 4)
291 return FALSE; /* not enough bytes to check */
293 if ( !test_applemidi( tvb, &command, FALSE ) ) {
297 /* set dynamic payload-type 97 which is used by Apple for their RTP-MIDI implementation for this
298 address/port-tuple to cause RTP-dissector to call the RTP-MIDI-dissector for payload-decoding */
300 rtp_dyn_payload = rtp_dyn_payload_new();
301 rtp_dyn_payload_insert(rtp_dyn_payload, 97, "rtp-midi", 10000);
302 rtp_add_address( pinfo, &pinfo->src, pinfo->srcport, 0, APPLEMIDI_DISSECTOR_SHORTNAME,
303 pinfo->fd->num, FALSE, rtp_dyn_payload);
305 /* call dissect_applemidi() from now on for UDP packets on this "connection"
306 it is important to do this step after calling rtp_add_address, otherwise
307 all further packets will go directly to the RTP-dissector! */
309 p_conv = find_or_create_conversation(pinfo);
310 conversation_set_dissector( p_conv, applemidi_handle );
312 /* punt to actual decoding */
314 dissect_applemidi_common( tvb, pinfo, tree, command );
321 proto_register_applemidi( void )
323 static hf_register_info hf[] = {
325 &hf_applemidi_signature,
328 "applemidi.signature",
337 &hf_applemidi_command,
343 VALS( applemidi_commands ),
349 &hf_applemidi_protocol_version,
352 "applemidi.protocol_version",
364 "applemidi.initiator_token",
376 "applemidi.sender_ssrc",
409 &hf_applemidi_padding,
421 &hf_applemidi_timestamp1,
424 "applemidi.timestamp1",
433 &hf_applemidi_timestamp2,
436 "applemidi.timestamp2",
445 &hf_applemidi_timestamp3,
448 "applemidi.timestamp3",
457 &hf_applemidi_sequence_num,
460 "applemidi.sequence_number",
469 &hf_applemidi_rtp_sequence_num,
471 "RTP Sequence Number",
472 "applemidi.rtp_sequence_number",
481 &hf_applemidi_rtp_bitrate_limit,
484 "applemidi.bitrate_limit",
493 &hf_applemidi_unknown_data,
496 "applemidi.unknown_data",
507 static gint *ett[] = {
509 &ett_applemidi_seq_num
512 proto_applemidi = proto_register_protocol( APPLEMIDI_DISSECTOR_NAME,
513 APPLEMIDI_DISSECTOR_SHORTNAME,
514 APPLEMIDI_DISSECTOR_ABBREVIATION );
515 proto_register_field_array( proto_applemidi, hf, array_length( hf ) );
516 proto_register_subtree_array( ett, array_length( ett ) );
521 proto_reg_handoff_applemidi( void ) {
524 applemidi_handle = create_dissector_handle( dissect_applemidi, proto_applemidi );
526 /* If we cannot decode the data it will be RTP-MIDI since the Apple session protocol uses
527 * two ports: the control-port and the MIDI-port. On both ports an invitation is being sent.
528 * The second port is then used for the RTP-MIDI-data. So if we can't find valid AppleMidi
529 * packets, it will be most likely RTP-MIDI...
531 rtp_handle = find_dissector( "rtp" );
532 heur_dissector_add( "udp", dissect_applemidi_heur, proto_applemidi );