2 * Routines for dissection of Apple network-midi session establishment.
3 * Copyright 2006-2012, Tobias Erichsen <t.erichsen@gmx.de>
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * Copied from packet-data.c, README.developer, and various other files.
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 * Apple network-midi session establishment is a lightweight protocol for
29 * providing a simple session establishment for MIDI-data sent in the form
30 * of RTP-MIDI (RFC 4695 / 6295). Peers recognize each other using the
31 * Apple Bonjour scheme with the service-name "_apple-midi._udp", establish
32 * a connection using AppleMIDI (no official name, just an abbreviation)
33 * and then send payload using RTP-MIDI. The implementation of this
34 * dissector is based on the Apple implementation summary from May 6th, 2005
35 * and the extension from August 13th, 2010.
38 * - initial version of dissector
40 * - implemented dynamic payloadtype support to automatically punt
41 * the decoding to the RTP-MIDI dissector via the RTP dissector
42 * - added new bitrate receive limit feature
44 * Here are some links:
46 * http://www.cs.berkeley.edu/~lazzaro/rtpmidi/
47 * http://www.faqs.org/rfcs/rfc4695.html
48 * http://www.faqs.org/rfcs/rfc6295.html
54 #include <epan/packet.h>
55 #include <epan/wmem/wmem.h>
56 #include <epan/conversation.h>
58 #include "packet-rtp.h"
60 void proto_register_applemidi(void);
61 void proto_reg_handoff_applemidi(void);
63 /* Definitions for protocol name during dissector-register */
64 #define APPLEMIDI_DISSECTOR_NAME "Apple Network-MIDI Session Protocol"
65 #define APPLEMIDI_DISSECTOR_SHORTNAME "AppleMIDI"
66 #define APPLEMIDI_DISSECTOR_ABBREVIATION "applemidi"
68 /* Signature "Magic Value" for Apple network MIDI session establishment */
69 #define APPLEMIDI_PROTOCOL_SIGNATURE 0xffff
71 /* Apple network MIDI valid commands */
72 #define APPLEMIDI_COMMAND_INVITATION 0x494e /* "IN" */
73 #define APPLEMIDI_COMMAND_INVITATION_REJECTED 0x4e4f /* "NO" */
74 #define APLLEMIDI_COMMAND_INVITATION_ACCEPTED 0x4f4b /* "OK" */
75 #define APPLEMIDI_COMMAND_ENDSESSION 0x4259 /* "BY" */
76 #define APPLEMIDI_COMMAND_SYNCHRONIZATION 0x434b /* "CK" */
77 #define APPLEMIDI_COMMAND_RECEIVER_FEEDBACK 0x5253 /* "RS" */
78 #define APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT 0x524c /* "RL" */
80 static int hf_applemidi_signature
= -1;
81 static int hf_applemidi_command
= -1;
82 static int hf_applemidi_protocol_version
= -1;
83 static int hf_applemidi_token
= -1;
84 static int hf_applemidi_ssrc
= -1;
85 static int hf_applemidi_name
= -1;
86 static int hf_applemidi_count
= -1;
87 static int hf_applemidi_padding
= -1;
88 static int hf_applemidi_timestamp1
= -1;
89 static int hf_applemidi_timestamp2
= -1;
90 static int hf_applemidi_timestamp3
= -1;
91 static int hf_applemidi_sequence_num
= -1;
92 static int hf_applemidi_rtp_sequence_num
= -1;
93 static int hf_applemidi_rtp_bitrate_limit
= -1;
94 static int hf_applemidi_unknown_data
= -1;
97 static gint ett_applemidi
= -1;
98 static gint ett_applemidi_seq_num
= -1;
101 static const value_string applemidi_commands
[] = {
102 { APPLEMIDI_COMMAND_INVITATION
, "Invitation" },
103 { APPLEMIDI_COMMAND_INVITATION_REJECTED
, "Invitation Rejected" },
104 { APLLEMIDI_COMMAND_INVITATION_ACCEPTED
, "Invitation Accepted" },
105 { APPLEMIDI_COMMAND_ENDSESSION
, "End Session" },
106 { APPLEMIDI_COMMAND_SYNCHRONIZATION
, "Synchronization" },
107 { APPLEMIDI_COMMAND_RECEIVER_FEEDBACK
, "Receiver Feedback" },
108 { APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT
, "Bitrate Receive Limit" },
113 static int proto_applemidi
= -1;
115 static dissector_handle_t applemidi_handle
;
116 static dissector_handle_t rtp_handle
;
118 static const char applemidi_unknown_command
[] = "unknown command: 0x%04x";
121 dissect_applemidi_common( tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, guint16 command
) {
129 proto_tree
*applemidi_tree
;
130 proto_tree
*applemidi_tree_seq_num
;
133 col_set_str( pinfo
->cinfo
, COL_PROTOCOL
, APPLEMIDI_DISSECTOR_SHORTNAME
);
135 col_add_fstr( pinfo
->cinfo
, COL_INFO
, "%s", val_to_str( command
, applemidi_commands
, applemidi_unknown_command
) );
139 ti
= proto_tree_add_item( tree
, proto_applemidi
, tvb
, 0, -1, ENC_NA
);
140 applemidi_tree
= proto_item_add_subtree( ti
, ett_applemidi
);
142 proto_tree_add_item( applemidi_tree
, hf_applemidi_signature
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
145 proto_tree_add_item( applemidi_tree
, hf_applemidi_command
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
148 /* the format of packets for "IN", "NO", "OK" and "BY" is identical and contains
149 * the protocol version, a random number generated by the initiator of the session,
150 * the SSRC that is used by the respective sides RTP-entity and optionally the
151 * name of the participant */
152 if ( ( APPLEMIDI_COMMAND_INVITATION
== command
) ||
153 ( APPLEMIDI_COMMAND_INVITATION_REJECTED
== command
) ||
154 ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED
== command
) ||
155 ( APPLEMIDI_COMMAND_ENDSESSION
== command
) ) {
157 proto_tree_add_item( applemidi_tree
, hf_applemidi_protocol_version
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
160 proto_tree_add_item( applemidi_tree
, hf_applemidi_token
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
163 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
166 len
= tvb_reported_length(tvb
) - offset
;
168 /* Name is optional */
170 name
= tvb_get_string( wmem_packet_scope(), tvb
, offset
, len
);
171 string_size
= (gint
)( strlen( name
) + 1 );
172 proto_tree_add_item( applemidi_tree
, hf_applemidi_name
, tvb
, offset
, string_size
, ENC_UTF_8
|ENC_NA
);
173 col_append_fstr( pinfo
->cinfo
, COL_INFO
, ": peer = \"%s\"", name
);
174 offset
+= string_size
;
177 /* the synchronization packet contains three 64bit timestamps, and a value to define how
178 * many of the timestamps transmitted are valid */
179 } else if ( APPLEMIDI_COMMAND_SYNCHRONIZATION
== command
) {
180 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
183 count
= tvb_get_guint8( tvb
, offset
);
184 proto_tree_add_item( applemidi_tree
, hf_applemidi_count
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
185 col_append_fstr( pinfo
->cinfo
, COL_INFO
, ": count = %u", count
);
188 proto_tree_add_item( applemidi_tree
, hf_applemidi_padding
, tvb
, offset
, 3, ENC_BIG_ENDIAN
);
191 proto_tree_add_item( applemidi_tree
, hf_applemidi_timestamp1
, tvb
, offset
, 8, ENC_BIG_ENDIAN
);
194 proto_tree_add_item( applemidi_tree
, hf_applemidi_timestamp2
, tvb
, offset
, 8, ENC_BIG_ENDIAN
);
197 proto_tree_add_item( applemidi_tree
, hf_applemidi_timestamp3
, tvb
, offset
, 8, ENC_BIG_ENDIAN
);
199 /* With the receiver feedback packet, the recipient can tell the sender up to what sequence
200 * number in the RTP-stream the packets have been received; this can be used to shorten the
201 * recovery-journal-section in the RTP-session */
202 } else if ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK
== command
) {
203 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
206 ti
= proto_tree_add_item( applemidi_tree
, hf_applemidi_sequence_num
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
207 /* Apple includes a 32bit sequence-number, but the RTP-packet only specifies 16bit.
208 * this subtree and subitem are added to be able to associate the sequence-number
209 * here easier with the one specified in the corresponding RTP-packet */
210 applemidi_tree_seq_num
= proto_item_add_subtree( ti
, ett_applemidi_seq_num
);
211 seq_num
= tvb_get_ntohs( tvb
, offset
);
212 proto_tree_add_uint( applemidi_tree_seq_num
, hf_applemidi_rtp_sequence_num
, tvb
, offset
, 2, seq_num
);
215 col_append_fstr( pinfo
->cinfo
, COL_INFO
, ": seq = %u", seq_num
);
216 /* With the bitrate receive limit packet, the recipient can tell the sender to limit
217 the transmission to a certain bitrate. This is important if the peer is a gateway
218 to a hardware-device that only supports a certain speed. Like the MIDI 1.0 DIN-cable
219 MIDI-implementation which is limited to 31250. */
220 } else if ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT
== command
) {
221 proto_tree_add_item( applemidi_tree
, hf_applemidi_ssrc
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
224 proto_tree_add_item( applemidi_tree
, hf_applemidi_rtp_bitrate_limit
,
225 tvb
, offset
, 4, ENC_BIG_ENDIAN
);
228 /* If there is any remaining data (possibly because an unknown command was encountered),
229 * we just dump it here */
230 len
= tvb_length_remaining( tvb
, offset
);
232 proto_tree_add_item( applemidi_tree
, hf_applemidi_unknown_data
, tvb
, offset
, len
, ENC_NA
);
238 test_applemidi(tvbuff_t
*tvb
, guint16
*command_p
, gboolean conversation_established
) {
242 /* An applemidi session protocol UDP-packet must start with the "magic value" of 0xffff ... */
243 if ( APPLEMIDI_PROTOCOL_SIGNATURE
!= tvb_get_ntohs( tvb
, 0 ) )
246 *command_p
= tvb_get_ntohs( tvb
, 2 );
248 /* If the conversation is establised (one prior packet with a valid known command)
249 * we won't check the commands anymore - this way we still show new commands
250 * Apple might introduct as "unknown" instead of punting to RTP-dissector */
251 if ( conversation_established
) {
256 /* ... followed by packet-command: "IN", "NO", "OK", "BY", "CK" and "RS" and "RL" */
257 if ( ( APPLEMIDI_COMMAND_INVITATION
== *command_p
) ||
258 ( APPLEMIDI_COMMAND_INVITATION_REJECTED
== *command_p
) ||
259 ( APLLEMIDI_COMMAND_INVITATION_ACCEPTED
== *command_p
) ||
260 ( APPLEMIDI_COMMAND_ENDSESSION
== *command_p
) ||
261 ( APPLEMIDI_COMMAND_SYNCHRONIZATION
== *command_p
) ||
262 ( APPLEMIDI_COMMAND_RECEIVER_FEEDBACK
== *command_p
) ||
263 ( APPLEMIDI_COMMAND_BITRATE_RECEIVE_LIMIT
== *command_p
) )
271 /* dissect_applemidi() is called when a packet is seen from a previously identified applemidi conversation */
272 /* If the packet isn't a valid applemidi packet, assume it's an RTP-MIDI packet. */
275 dissect_applemidi( tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
) {
278 if ( test_applemidi( tvb
, &command
, TRUE
) )
279 dissect_applemidi_common( tvb
, pinfo
, tree
, command
);
281 call_dissector( rtp_handle
, tvb
, pinfo
, tree
);
285 dissect_applemidi_heur( tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data _U_
) {
288 conversation_t
*p_conv
;
289 /*struct _rtp_conversation_info *p_conv_data = NULL;*/
290 encoding_name_and_rate_t
*encoding_name_and_rate
= NULL
;
291 GHashTable
*rtp_dyn_payload
= NULL
;
294 if ( tvb_length( tvb
) < 4)
295 return FALSE
; /* not enough bytes to check */
297 if ( !test_applemidi( tvb
, &command
, FALSE
) ) {
301 /* set dynamic payload-type 97 which is used by Apple for their RTP-MIDI implementation for this
302 address/port-tuple to cause RTP-dissector to call the RTP-MIDI-dissector for payload-decoding */
304 encoding_name_and_rate
= wmem_new(wmem_file_scope(), encoding_name_and_rate_t
);
305 rtp_dyn_payload
= g_hash_table_new( g_int_hash
, g_int_equal
);
306 encoding_name_and_rate
->encoding_name
= wmem_strdup( wmem_file_scope(), "rtp-midi" );
307 encoding_name_and_rate
->sample_rate
= 10000;
308 key
= wmem_new(wmem_file_scope(), gint
);
310 g_hash_table_insert( rtp_dyn_payload
, key
, encoding_name_and_rate
);
311 rtp_add_address( pinfo
, &pinfo
->src
, pinfo
->srcport
, 0, APPLEMIDI_DISSECTOR_SHORTNAME
,
312 pinfo
->fd
->num
, FALSE
, rtp_dyn_payload
);
314 /* call dissect_applemidi() from now on for UDP packets on this "connection"
315 it is important to do this step after calling rtp_add_address, otherwise
316 all further packets will go directly to the RTP-dissector! */
318 p_conv
= find_or_create_conversation(pinfo
);
319 conversation_set_dissector( p_conv
, applemidi_handle
);
321 /* punt to actual decoding */
323 dissect_applemidi_common( tvb
, pinfo
, tree
, command
);
330 proto_register_applemidi( void )
332 static hf_register_info hf
[] = {
334 &hf_applemidi_signature
,
337 "applemidi.signature",
346 &hf_applemidi_command
,
352 VALS( applemidi_commands
),
358 &hf_applemidi_protocol_version
,
361 "applemidi.protocol_version",
373 "applemidi.initiator_token",
385 "applemidi.sender_ssrc",
418 &hf_applemidi_padding
,
430 &hf_applemidi_timestamp1
,
433 "applemidi.timestamp1",
442 &hf_applemidi_timestamp2
,
445 "applemidi.timestamp2",
454 &hf_applemidi_timestamp3
,
457 "applemidi.timestamp3",
466 &hf_applemidi_sequence_num
,
469 "applemidi.sequence_number",
478 &hf_applemidi_rtp_sequence_num
,
480 "RTP Sequence Number",
481 "applemidi.rtp_sequence_number",
490 &hf_applemidi_rtp_bitrate_limit
,
493 "applemidi.bitrate_limit",
502 &hf_applemidi_unknown_data
,
505 "applemidi.unknown_data",
516 static gint
*ett
[] = {
518 &ett_applemidi_seq_num
521 proto_applemidi
= proto_register_protocol( APPLEMIDI_DISSECTOR_NAME
,
522 APPLEMIDI_DISSECTOR_SHORTNAME
,
523 APPLEMIDI_DISSECTOR_ABBREVIATION
);
524 proto_register_field_array( proto_applemidi
, hf
, array_length( hf
) );
525 proto_register_subtree_array( ett
, array_length( ett
) );
530 proto_reg_handoff_applemidi( void ) {
533 applemidi_handle
= create_dissector_handle( dissect_applemidi
, proto_applemidi
);
535 /* If we cannot decode the data it will be RTP-MIDI since the Apple session protocol uses
536 * two ports: the control-port and the MIDI-port. On both ports an invitation is being sent.
537 * The second port is then used for the RTP-MIDI-data. So if we can't find valid AppleMidi
538 * packets, it will be most likely RTP-MIDI...
540 rtp_handle
= find_dissector( "rtp" );
541 heur_dissector_add( "udp", dissect_applemidi_heur
, proto_applemidi
);