Logo Search packages:      
Sourcecode: wireshark version File versions  Download package

packet-applemidi.c

/* packet-applemidi.c
 * Routines for dissection of Apple network-midi session establishment.
 * Copyright 2006-2010, Tobias Erichsen <t.erichsen@gmx.de>
 *
 * $Id: packet-applemidi.c 32790 2010-05-13 18:28:34Z morriss $
 *
 * 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.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 * 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)
 * 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.
 *
 * Here are some links:
 *
 * http://www.cs.berkeley.edu/~lazzaro/rtpmidi/
 * http://www.faqs.org/rfcs/rfc4695.html
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

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

/* 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"   */


static int  hf_applemidi_signature              = -1;
static int  hf_applemidi_command                = -1;
static int  hf_applemidi_protocol_version       = -1;
static int  hf_applemidi_token                  = -1;
static int  hf_applemidi_ssrc             = -1;
static int  hf_applemidi_name             = -1;
static int  hf_applemidi_count                  = -1;
static int  hf_applemidi_padding                = -1;
static int  hf_applemidi_timestamp1             = -1;
static int  hf_applemidi_timestamp2             = -1;
static int  hf_applemidi_timestamp3             = -1;
static int  hf_applemidi_sequence_num           = -1;
static int  hf_applemidi_rtp_sequence_num       = -1;


static gint ett_applemidi                       = -1;
static gint ett_applemidi_seq_num               = -1;


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" },
      { 0,                                NULL },
};


static int              proto_applemidi               = -1;

static dissector_handle_t     applemidi_handle;
static dissector_handle_t     rtp_handle;



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

      guint16            seq_num;
      guint8             count;
      guint8            *name;
      unsigned int       offset                 = 0;
      proto_tree  *applemidi_tree;
      proto_tree  *applemidi_tree_seq_num;


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

      /* Clear out stuff in the info column */
      col_clear( pinfo->cinfo, COL_INFO );

      col_add_fstr( pinfo->cinfo, COL_INFO, "%s", val_to_str( command, applemidi_commands, "Unknown Command: 0x%04x" ) );

      if ( tree ) {
            proto_item *ti;
            ti = proto_tree_add_item( tree, proto_applemidi, tvb, 0, -1, FALSE );
            applemidi_tree = proto_item_add_subtree( ti, ett_applemidi );

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

            proto_tree_add_item( applemidi_tree, hf_applemidi_command, tvb, offset, 2, FALSE );
            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 ) ) {
                  int len;

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

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

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

                  len = tvb_reported_length(tvb) - offset;

                  /* Name is optional */
                  if ( len > 0 ) {
                        name = tvb_get_ephemeral_string( tvb, offset, len );
                        proto_tree_add_item( applemidi_tree, hf_applemidi_name, tvb, offset, len, FALSE );
                        col_append_fstr( pinfo->cinfo, COL_INFO, ": peer = \"%s\"", name );
                  }

            /* 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, FALSE );
                  offset += 4;

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

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

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

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

                  proto_tree_add_item( applemidi_tree, hf_applemidi_timestamp3, tvb, offset, 8, FALSE );
                  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, FALSE );
                  offset += 4;

                  ti = proto_tree_add_item( applemidi_tree, hf_applemidi_sequence_num, tvb, offset, 4, FALSE );
                  /* 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 );
            }
      }
}

static gboolean
test_applemidi(tvbuff_t *tvb, guint16 *command_p) {

      *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 );

      /* ... followed by packet-command: "IN", "NO", "OK", "BY", "CK" and "RS" */
      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 ) )
            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 void
dissect_applemidi( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree ) {
      guint16           command;

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

static gboolean
dissect_applemidi_heur( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree ) {

      guint16            command;
      conversation_t    *p_conv;

      if ( tvb_length( tvb ) < 4)
            return FALSE;  /* not enough bytes to check */

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

      /* call dissect_applemidi() from now on for UDP packets on this "connection" */
      p_conv = find_or_create_conversation(pinfo);
      conversation_set_dissector( p_conv, applemidi_handle );

      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
                  }
            },
      };


      static gint *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 ) );

}

void
proto_reg_handoff_applemidi( void ) {


      applemidi_handle = create_dissector_handle( dissect_applemidi, proto_applemidi );

      /* 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( "rtp" );
      heur_dissector_add( "udp", dissect_applemidi_heur, proto_applemidi );

}

Generated by  Doxygen 1.6.0   Back to index