text: /* packet-aprs.c
   *
   * Routines for Amateur Packet Radio protocol dissection
   * Copyright 2007,2008,2009,2010,2012 R.W. Stearn <richard@rns-stearn.demon.co.uk>
   *
   * Wireshark - Network traffic analyzer
   * By Gerald Combs <gerald@wireshark.org>
   * Copyright 1998 Gerald Combs
   *
   * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   */
  
  /*
   * This dissector is for APRS (Automatic Packet Reporting System)
   *
   * Information was drawn from:
   *    http://www.aprs.org/
   *
   * Inspiration on how to build the dissector drawn from
   *   packet-sdlc.c
   *   packet-x25.c
   *   packet-lapb.c
   *   paket-gprs-llc.c
   *   xdlc.c
   * with the base file built from README.developers.
   */
  
  #include "config.h"
  
  #include <math.h>
  
  #include <epan/packet.h>
  #include <epan/prefs.h>
  #include <epan/to_str.h>
  
  #define STRLEN	100
  
  void proto_register_aprs(void);
  
  /* Initialize the protocol and registered fields */
  static int proto_aprs			= -1;
  
  /* aprs timestamp items */
  static int hf_aprs_dhm			= -1;
  static int hf_aprs_hms			= -1;
  static int hf_aprs_mdhm			= -1;
  static int hf_aprs_tz			= -1;
  
  /* aprs position items */
  /* static int hf_aprs_position		= -1; */
  static int hf_aprs_lat			= -1;
  static int hf_aprs_long			= -1;
  
  /* aprs msg items */
  static int hf_aprs_msg			= -1;
  static int hf_aprs_msg_rng		= -1;
  static int hf_aprs_msg_cse		= -1;
  static int hf_aprs_msg_spd		= -1;
  static int hf_aprs_msg_dir		= -1;
  static int hf_aprs_msg_brg		= -1;
  static int hf_aprs_msg_nrq		= -1;
  
  /* aprs compression type items */
  static int hf_aprs_compression_type	= -1;
  static int hf_aprs_ct_gps_fix		= -1;
  static int hf_aprs_ct_nmea_src		= -1;
  static int hf_aprs_ct_origin		= -1;
  
  /* phg msg items */
  static int hf_aprs_msg_phg_p		= -1;
  static int hf_aprs_msg_phg_h		= -1;
  static int hf_aprs_msg_phg_g		= -1;
  static int hf_aprs_msg_phg_d		= -1;
  
  /* dfs msg items */
  static int hf_aprs_msg_dfs_s		= -1;
  static int hf_aprs_msg_dfs_h		= -1;
  static int hf_aprs_msg_dfs_g		= -1;
  static int hf_aprs_msg_dfs_d		= -1;
  
  /* weather items */
  static int hf_aprs_weather_dir		= -1;
  static int hf_aprs_weather_spd		= -1;
  static int hf_aprs_weather_peak		= -1;
  static int hf_aprs_weather_temp		= -1;
  static int hf_aprs_weather_rain_1	= -1;
  static int hf_aprs_weather_rain_24	= -1;
  static int hf_aprs_weather_rain		= -1;
  static int hf_aprs_weather_humidty	= -1;
  static int hf_aprs_weather_press	= -1;
  static int hf_aprs_weather_luminosity	= -1;
  static int hf_aprs_weather_snow		= -1;
  static int hf_aprs_weather_raw_rain	= -1;
  static int hf_aprs_weather_software	= -1;
  static int hf_aprs_weather_unit		= -1;
  
  /* aod msg items */
  static int hf_aprs_msg_aod_t		= -1;
  static int hf_aprs_msg_aod_c		= -1;
  
  /* mic-e msg items */
  static int hf_aprs_mic_e_dst		= -1;
  static int hf_aprs_mic_e_long_d		= -1;
  static int hf_aprs_mic_e_long_m		= -1;
  static int hf_aprs_mic_e_long_h		= -1;
  static int hf_aprs_mic_e_spd_sp		= -1;
  static int hf_aprs_mic_e_spd_dc		= -1;
  static int hf_aprs_mic_e_spd_se		= -1;
  static int hf_aprs_mic_e_telemetry	= -1;
  static int hf_aprs_mic_e_status		= -1;
  
  /* Storm items */
  static int hf_aprs_storm_dir		= -1;
  static int hf_aprs_storm_spd		= -1;
  static int hf_aprs_storm_type		= -1;
  static int hf_aprs_storm_sws		= -1;
  static int hf_aprs_storm_pwg		= -1;
  static int hf_aprs_storm_cp		= -1;
  static int hf_aprs_storm_rhw		= -1;
  static int hf_aprs_storm_rtsw		= -1;
  static int hf_aprs_storm_rwg		= -1;
  
  /* aprs sundry items */
  static int hf_aprs_dti			= -1;
  static int hf_aprs_sym_id		= -1;
  static int hf_aprs_sym_code		= -1;
  static int hf_aprs_comment		= -1;
  static int hf_aprs_storm		= -1;
  
  /* aprs main catgories items */
  static int hf_ultimeter_2000		= -1;
  static int hf_aprs_status		= -1;
  static int hf_aprs_object		= -1;
  static int hf_aprs_item			= -1;
  static int hf_aprs_query		= -1;
  static int hf_aprs_telemetry		= -1;
  static int hf_aprs_raw			= -1;
  static int hf_aprs_station		= -1;
  static int hf_aprs_message		= -1;
  static int hf_aprs_agrelo		= -1;
  static int hf_aprs_maidenhead		= -1;
  static int hf_aprs_weather		= -1;
  static int hf_aprs_invalid_test		= -1;
  static int hf_aprs_user_defined		= -1;
  static int hf_aprs_third_party		= -1;
  static int hf_aprs_mic_e_0_current	= -1;
  static int hf_aprs_mic_e_0_old		= -1;
  static int hf_aprs_mic_e_old		= -1;
  static int hf_aprs_mic_e_current	= -1;
  static int hf_aprs_peet_1		= -1;
  static int hf_aprs_peet_2		= -1;
  static int hf_aprs_map_feature		= -1;
  static int hf_aprs_shelter_data		= -1;
  static int hf_aprs_space_weather	= -1;
  
  
  /* Global preferences */
  static gboolean gPREF_APRS_LAX = FALSE;
  
  /* Initialize the subtree pointers */
  static gint ett_aprs		= -1;
  static gint ett_aprs_msg	= -1;
  static gint ett_aprs_ct		= -1;
  static gint ett_aprs_weather	= -1;
  static gint ett_aprs_storm	= -1;
  static gint ett_aprs_mic_e	= -1;
  
  
  const value_string ctype_vals[] = {
  	{ 0,   "Compressed" },
  	{ 1,   "TNC BText" },
  	{ 2,   "Software (DOS/Mac/Win/+SA)" },
  	{ 3,   "[tbd]" },
  	{ 4,   "KPC3" },
  	{ 5,   "Pico" },
  	{ 6,   "Other tracker [tbd]" },
  	{ 7,   "Digipeater conversion" },
  	{ 0,            NULL }
  };
  
  const value_string nmea_vals[] = {
  	{ 0,   "other" },
  	{ 1,   "GLL" },
  	{ 2,   "GGA" },
  	{ 3,   "RMC" },
  	{ 0,            NULL }
  };
  
  const value_string gps_vals[] = {
  	{ 0,   "old (last)" },
  	{ 1,   "current" },
  	{ 0,            NULL }
  };
  
  /* sorted */
  static const value_string aprs_description[] = {
  	{ 0x1c, "Current Mic-E Data (Rev 0 beta)" },
  	{ 0x1d, "Old Mic-E Data (Rev 0 beta)" },
  	{ '#',  "Peet Bros U-II Weather Station" },
  	{ '$',  "Raw GPS data or Ultimeter 2000" },
  	{ '%',  "Agrelo DFJr / MicroFinder" },
  	{ '&',  "[Reserved - Map Feature]" },
  	{ '\'', "Old Mic-E Data (current data for TM-D700)" },
  	{ ')',  "Item" },
  	{ '*',  "Peet Bros U-II Weather Station" },
  	{ '+',  "[Reserved - Shelter data with time]" },
  	{ ',',  "Invalid data or test data" },
  	{ '.',  "[Reserved - Space weather]" },
  	{ '/',  "Position + timestamp" },
  	{ ':',  "Message" },
  	{ ';',  "Object" },
  	{ '<',  "Station Capabilities" },
  	{ '=',  "Position + APRS data extension" },
  	{ '>',  "Status" },
  	{ '?',  "Query" },
  	{ '@',  "Position + timestamp + APRS data extension" },
  	{ 'T',  "Telemetry data" },
  	{ '[',  "Maidenhead grid locator beacon (obsolete)" },
  	{ '_',  "Weather Report (without position)" },
  	{ '`',  "Current Mic-E Data (not used in TM-D700)" },
  	{ '{',  "User-Defined APRS packet format" },
  	{ '}',  "Third-party traffic" },
  	{ 0,            NULL }
  };
  static value_string_ext aprs_description_ext = VALUE_STRING_EXT_INIT(aprs_description);
  
  /*
   * Structure containing pointers to hf_ values for various subfields of
   * the compression type field.
   */
  typedef struct {
  	int	*hf_ct_gps_fix;
  	int	*hf_ct_nmea_src;
  	int	*hf_ct_origin;
  } ct_items_s;
  
  static const ct_items_s ct_items_gbl = {
  	/* all items are FT_UINT8 */
  	&hf_aprs_ct_gps_fix,
  	&hf_aprs_ct_nmea_src,
  	&hf_aprs_ct_origin
  };
  
  typedef struct {
  	int	*hf_msg_phg_p;
  	int	*hf_msg_phg_h;
  	int	*hf_msg_phg_g;
  	int	*hf_msg_phg_d;
  	int	*hf_msg_rng;
  	int	*hf_msg_dfs_p;
  	int	*hf_msg_dfs_h;
  	int	*hf_msg_dfs_g;
  	int	*hf_msg_dfs_d;
  	int	*hf_msg_aod_t;
  	int	*hf_msg_aod_c;
  	int	*hf_msg_cse;
  	int	*hf_msg_spd;
  	int	*hf_msg_dir;
  	int	*hf_msg_brg;
  	int	*hf_msg_nrq;
  } msg_items_s;
  
  static const msg_items_s msg_items_gbl = {
  	/* (All items are FT_STRING */
  	&hf_aprs_msg_phg_p,
  	&hf_aprs_msg_phg_h,
  	&hf_aprs_msg_phg_g,
  	&hf_aprs_msg_phg_d,
  	&hf_aprs_msg_rng,
  	&hf_aprs_msg_dfs_s,
  	&hf_aprs_msg_dfs_h,
  	&hf_aprs_msg_dfs_g,
  	&hf_aprs_msg_dfs_d,
  	&hf_aprs_msg_aod_t,
  	&hf_aprs_msg_aod_c,
  	&hf_aprs_msg_cse,
  	&hf_aprs_msg_spd,
  	&hf_aprs_msg_dir,
  	&hf_aprs_msg_brg,
  	&hf_aprs_msg_nrq
  };
  
  typedef struct {
  	int	*hf_weather_dir;
  	int	*hf_weather_spd;
  	int	*hf_weather_peak;
  	int	*hf_weather_temp;
  	int	*hf_weather_rain_1;
  	int	*hf_weather_rain_24;
  	int	*hf_weather_rain;
  	int	*hf_weather_humidty;
  	int	*hf_weather_press;
  	int	*hf_weather_luminosity;
  	int	*hf_weather_snow;
  	int	*hf_weather_raw_rain;
  	int	*hf_weather_software;
  	int	*hf_weather_unit;
  } weather_items_s;
  
  static const weather_items_s weather_items_gbl = {
  	/* (All items are FT_STRING */
  	&hf_aprs_weather_dir,
  	&hf_aprs_weather_spd,
  	&hf_aprs_weather_peak,
  	&hf_aprs_weather_temp,
  	&hf_aprs_weather_rain_1,
  	&hf_aprs_weather_rain_24,
  	&hf_aprs_weather_rain,
  	&hf_aprs_weather_humidty,
  	&hf_aprs_weather_press,
  	&hf_aprs_weather_luminosity,
  	&hf_aprs_weather_snow,
  	&hf_aprs_weather_raw_rain,
  	&hf_aprs_weather_software,
  	&hf_aprs_weather_unit
  };
  
  typedef struct {
  	int	*hf_mic_e_dst;
  	int	*hf_mic_e_long_d;
  	int	*hf_mic_e_long_m;
  	int	*hf_mic_e_long_h;
  	int	*hf_mic_e_spd_sp;
  	int	*hf_mic_e_spd_dc;
  	int	*hf_mic_e_spd_se;
  	int	*hf_mic_e_sym_code;
  	int	*hf_mic_e_sym_id;
  	int	*hf_mic_e_telemetry;
  	int	*hf_mic_e_status;
  } mic_e_items_s;
  
  static const mic_e_items_s mic_e_items_gbl = {
  	&hf_aprs_mic_e_dst,        /* FT_STRING */
  	&hf_aprs_mic_e_long_d,     /* FT_GUINT8 */
  	&hf_aprs_mic_e_long_m,     /* FT_GUINT8 */
  	&hf_aprs_mic_e_long_h,     /* FT_GUINT8 */
  	&hf_aprs_mic_e_spd_sp,     /* FT_GUINT8 */
  	&hf_aprs_mic_e_spd_dc,     /* FT_GUINT8 */
  	&hf_aprs_mic_e_spd_se,     /* FT_GUINT8 */
  	&hf_aprs_sym_code,         /* FT_STRING */
  	&hf_aprs_sym_id,           /* FT_STRING */
  	&hf_aprs_mic_e_telemetry,  /* FT_BYTES  */
  	&hf_aprs_mic_e_status      /* FT_STRING */
  };
  
  typedef struct {
  	int	*hf_aprs_storm_dir;
  	int	*hf_aprs_storm_spd;
  	int	*hf_aprs_storm_type;
  	int	*hf_aprs_storm_sws;
  	int	*hf_aprs_storm_pwg;
  	int	*hf_aprs_storm_cp;
  	int	*hf_aprs_storm_rhw;
  	int	*hf_aprs_storm_rtsw;
  	int	*hf_aprs_storm_rwg;
  } storm_items_s;
  
  /*** XXX: hf[] entries for the following are missing ***/
  static const storm_items_s storm_items_gbl = {
  	&hf_aprs_storm_dir,
  	&hf_aprs_storm_spd,
  	&hf_aprs_storm_type,
  	&hf_aprs_storm_sws,
  	&hf_aprs_storm_pwg,
  	&hf_aprs_storm_cp,
  	&hf_aprs_storm_rhw,
  	&hf_aprs_storm_rtsw,
  	&hf_aprs_storm_rwg
  };
  
  /* MIC-E destination field code table */
  typedef struct
  	{
  	guint8 key;
  	char   digit;
  	int    msg;
  	char   n_s;
  	int    long_offset;
  	char   w_e;
  	} mic_e_dst_code_table_s;
  
  static const mic_e_dst_code_table_s dst_code[] =
  	{
  	{ '0' << 1, '0', 0, 'S',   0, 'E' },
  	{ '1' << 1, '1', 0, 'S',   0, 'E' },
  	{ '2' << 1, '2', 0, 'S',   0, 'E' },
  	{ '3' << 1, '3', 0, 'S',   0, 'E' },
  	{ '4' << 1, '4', 0, 'S',   0, 'E' },
  	{ '5' << 1, '5', 0, 'S',   0, 'E' },
  	{ '6' << 1, '6', 0, 'S',   0, 'E' },
  	{ '7' << 1, '7', 0, 'S',   0, 'E' },
  	{ '8' << 1, '8', 0, 'S',   0, 'E' },
  	{ '9' << 1, '9', 0, 'S',   0, 'E' },
  	{ 'A' << 1, '0', 1, '?',   0, '?' },
  	{ 'B' << 1, '1', 1, '?',   0, '?' },
  	{ 'C' << 1, '2', 1, '?',   0, '?' },
  	{ 'D' << 1, '3', 1, '?',   0, '?' },
  	{ 'E' << 1, '4', 1, '?',   0, '?' },
  	{ 'F' << 1, '5', 1, '?',   0, '?' },
  	{ 'G' << 1, '6', 1, '?',   0, '?' },
  	{ 'H' << 1, '7', 1, '?',   0, '?' },
  	{ 'I' << 1, '8', 1, '?',   0, '?' },
  	{ 'J' << 1, '9', 1, '?',   0, '?' },
  	{ 'K' << 1, ' ', 1, '?',   0, '?' },
  	{ 'L' << 1, ' ', 0, 'S',   0, 'E' },
  	{ 'P' << 1, '0', 1, 'N', 100, 'W' },
  	{ 'Q' << 1, '1', 1, 'N', 100, 'W' },
  	{ 'R' << 1, '2', 1, 'N', 100, 'W' },
  	{ 'S' << 1, '3', 1, 'N', 100, 'W' },
  	{ 'T' << 1, '4', 1, 'N', 100, 'W' },
  	{ 'U' << 1, '5', 1, 'N', 100, 'W' },
  	{ 'V' << 1, '6', 1, 'N', 100, 'W' },
  	{ 'W' << 1, '7', 1, 'N', 100, 'W' },
  	{ 'X' << 1, '8', 1, 'N', 100, 'W' },
  	{ 'Y' << 1, '9', 1, 'N', 100, 'W' },
  	{ 'Z' << 1, ' ', 1, 'N', 100, 'W' },
  	{        0, '_', 3, '?',   3, '?' },
  	};
  
  
  /* MIC-E message table */
  typedef struct
  	{
  	const char *std;
  	const char *custom;
  	} mic_e_msg_table_s;
  
  static const mic_e_msg_table_s mic_e_msg_table[] =
  	{
  	{ "Emergency",  "Emergency" },
  	{ "Priority",   "Custom 6" },
  	{ "Special",    "Custom 5" },
  	{ "Committed",  "Custom 4" },
  	{ "Returning",  "Custom 3" },
  	{ "In Service", "Custom 2" },
  	{ "En Route",   "Custom 1" },
  	{ "Off Duty",   "Custom 0" }
  	};
  
  /* Code to actually dissect the packets */
  
  static int
  dissect_aprs_compression_type(	tvbuff_t	 *tvb,
  				int		  offset,
  				proto_tree	 *parent_tree,
  				int		  hf_aprs_ctype,
  				gint		  ett_aprs_ctype,
  				const ct_items_s *ct_items
  				)
  {
  	proto_tree *tc;
  	proto_tree *compression_tree;
  	int	    new_offset;
  	int	    data_len;
  	guint8	    compression_type;
  
  
  	data_len = 1;
  	new_offset = offset + data_len;
  
  	if ( parent_tree )
  		{
  		compression_type = tvb_get_guint8( tvb, offset ) - 33;
  
  		tc = proto_tree_add_uint( parent_tree, hf_aprs_ctype, tvb, offset, data_len,
  					  compression_type );
  		compression_tree = proto_item_add_subtree( tc, ett_aprs_ctype );
  
  		proto_tree_add_item( compression_tree, *ct_items->hf_ct_gps_fix,  tvb, offset, data_len, ENC_BIG_ENDIAN );
  		proto_tree_add_item( compression_tree, *ct_items->hf_ct_nmea_src, tvb, offset, data_len, ENC_BIG_ENDIAN );
  		proto_tree_add_item( compression_tree, *ct_items->hf_ct_origin,   tvb, offset, data_len, ENC_BIG_ENDIAN );
  		}
  
  	return new_offset;
  }
  
  static int
  dissect_aprs_msg(	tvbuff_t	  *tvb,
  			int		   offset,
  			proto_tree	  *parent_tree,
  			int		   hf_aprs_msg_idx,
  			gint		   ett_aprs_msg_idx,
  			const msg_items_s *msg_items, /* Assumption: all referenced hf[] entries are FT_STRING */
  			int		   wind,
  			int 		   brg_nrq
  			)
  {
  	proto_tree *msg_tree = NULL;
  	guint8	    ch;
  
  
  	if ( parent_tree )
  		{
  		proto_tree *tc;
  		tc = proto_tree_add_item( parent_tree, hf_aprs_msg_idx, tvb, offset, 7, ENC_ASCII|ENC_NA );
  		msg_tree = proto_item_add_subtree( tc, ett_aprs_msg_idx );
  		}
  
  	ch = tvb_get_guint8( tvb, offset );
  
  	if ( g_ascii_isdigit( ch ) )
  		{
  		if ( wind )
  			proto_tree_add_item( msg_tree, *msg_items->hf_msg_dir, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		else
  			proto_tree_add_item( msg_tree, *msg_items->hf_msg_cse, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		offset += 3;
  		/* verify the separator */
  		offset += 1;
  		proto_tree_add_item( msg_tree, *msg_items->hf_msg_spd, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		offset += 3;
  		}
  	else
  		{
  		switch ( ch )
  			{
  			case 'D' :	/* dfs */
  				offset += 3;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_dfs_p, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_dfs_h, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_dfs_g, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_dfs_d, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				break;
  			case 'P' :	/* phgd */
  				offset += 3;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_phg_p, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_phg_h, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_phg_g, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_phg_d, tvb, offset, 1, ENC_ASCII|ENC_NA );
  				break;
  			case 'R' :	/* rng */
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_rng, tvb, offset, 7, ENC_ASCII|ENC_NA );
  				break;
  			case 'T' :	/* aod */
  				offset += 1;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_aod_t, tvb, offset, 2, ENC_ASCII|ENC_NA );
  				offset += 2;
  				/* step over the /C */
  				offset += 2;
  				proto_tree_add_item( msg_tree, *msg_items->hf_msg_aod_c, tvb, offset, 2, ENC_ASCII|ENC_NA );
  				break;
  			default  :	/* wtf */
  				break;
  			}
  		}
  	if ( brg_nrq )
  		{
  		proto_tree_add_item( msg_tree, *msg_items->hf_msg_brg, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		offset += 3;
  		/* verify the separator */
  		offset += 1;
  		proto_tree_add_item( msg_tree, *msg_items->hf_msg_nrq, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		offset += 3;
  		}
  
  	return offset;
  }
  
  static int
  dissect_aprs_compressed_msg(	tvbuff_t *tvb,
  				int offset,
  				proto_tree *parent_tree,
  				int hf_msg_type_idx,
  				gint ett_aprs_msg_idx,
  				const msg_items_s *msg_items
  				)
  {
  	proto_tree *tc;
  	proto_tree *msg_tree;
  	int	    new_offset;
  	int	    data_len;
  	guint8	    ch;
  	guint8	    course;
  	double	    speed;
  	double	    range;
  	gchar	   *info_buffer;
  
  
  	data_len = 2;
  	new_offset = offset + data_len;
  
  	if ( parent_tree )
  		{
  		tc = proto_tree_add_item( parent_tree, hf_msg_type_idx, tvb, offset, data_len, ENC_ASCII|ENC_NA );
  		msg_tree = proto_item_add_subtree( tc, ett_aprs_msg_idx );
  
  		ch = tvb_get_guint8( tvb, offset );
  		if ( ch != ' ' )
  			{
  			if ( ch == '{' )
  				{ /* Pre-Calculated Radio Range */
  				offset += 1;
  				ch = tvb_get_guint8( tvb, offset );
  				range = exp( log( 1.08 ) * (ch - 33) );
  				info_buffer = wmem_strdup_printf( wmem_packet_scope(), "%7.2f", range );
  				proto_tree_add_string( msg_tree, *msg_items->hf_msg_rng, tvb, offset, 1, info_buffer );
  				}
  			else
  				if ( ch >= '!' && ch <= 'z' )
  					{ /* Course/Speed */
  					course = (ch - 33) * 4;
  					info_buffer = wmem_strdup_printf( wmem_packet_scope(), "%d", course );
  					proto_tree_add_string( msg_tree, *msg_items->hf_msg_cse,
  							       tvb, offset, 1, info_buffer );
  					offset += 1;
  					ch = tvb_get_guint8( tvb, offset );
  					speed = exp( log( 1.08 ) * (ch - 33) );
  					info_buffer = wmem_strdup_printf( wmem_packet_scope(), "%7.2f", speed );
  					proto_tree_add_string( msg_tree, *msg_items->hf_msg_spd,
  							       tvb, offset, 1, info_buffer );
  					}
  
  			}
  		}
  
  	return new_offset;
  }
  
  
  static const mic_e_dst_code_table_s *
  dst_code_lookup( guint8 ch )
  {
  	guint indx;
  
  	indx = 0;
  	while ( indx < ( sizeof( dst_code ) / sizeof( mic_e_dst_code_table_s ) )
  			&& dst_code[ indx ].key != ch
  			&& dst_code[ indx ].key > 0 )
  		indx++;
  	return &( dst_code[ indx ] );
  }
  
  static int
  d28_to_deg( guint8 code, int long_offset )
  {
  	int value;
  
  	value = code - 28 + long_offset;
  	if ( value >= 180 && value <= 189 )
  		value -= 80;
  	else
  		if ( value >= 190 && value <= 199 )
  			value -= 190;
  	return value;
  }
  
  static int
  d28_to_min( guint8 code )
  {
  	int value;
  
  	value = code - 28;
  	if ( value >= 60 )
  		value -= 60;
  	return value;
  }
  
  static int
  dissect_mic_e(	tvbuff_t    *tvb,
  		int	     offset,
  		packet_info *pinfo,
  		proto_tree  *parent_tree,
  		int	     hf_mic_e_idx,
  		gint	     ett_mic_e_idx,
  		const mic_e_items_s *mic_e_items
  		)
  {
  	proto_tree *tc;
  	proto_tree *mic_e_tree;
  	int	    new_offset;
  	int	    data_len;
  	char    *info_buffer;
  	char    latitude[7] = { '?', '?', '?', '?', '.', '?', '?' };
  	int	    msg_a;
  	int	    msg_b;
  	int	    msg_c;
  	char    n_s;
  	int	    long_offset;
  	char    w_e;
  	int	    cse;
  	int	    spd;
  	guint8  ssid;
  	const guint8 *addr;
  	const mic_e_dst_code_table_s *dst_code_entry;
  
  	data_len    = tvb_reported_length_remaining( tvb, offset );
  	new_offset  = offset + data_len;
  
  	info_buffer = (char *)wmem_alloc( wmem_packet_scope(), STRLEN );
  
  	msg_a = 0;
  	msg_b = 0;
  	msg_c = 0;
  
  	n_s = '?';
  	long_offset = 0;
  	w_e = '?';
  	ssid = 0;
  
  	if ( pinfo->dst.type == AT_AX25 && pinfo->dst.len == AX25_ADDR_LEN )
  		{
  		/* decode the AX.25 destination address */
  		addr = (const guint8 *)pinfo->dst.data;
  
  		dst_code_entry = dst_code_lookup( addr[ 0 ] );
  		latitude[ 0 ] = dst_code_entry->digit;
  		msg_a = dst_code_entry->msg & 0x1;
  
  		dst_code_entry = dst_code_lookup( addr[ 1 ] );
  		latitude[ 1 ] = dst_code_entry->digit;
  		msg_b = dst_code_entry->msg & 0x1;
  
  		dst_code_entry = dst_code_lookup( addr[ 2 ] );
  		latitude[ 2 ] = dst_code_entry->digit;
  		msg_c = dst_code_entry->msg & 0x1;
  
  		dst_code_entry = dst_code_lookup( addr[ 3 ] );
  		latitude[ 3 ] = dst_code_entry->digit;
  		n_s = dst_code_entry->n_s;
  
  		latitude[ 4 ] = '.';
  
  		dst_code_entry = dst_code_lookup( addr[ 4 ] );
  		latitude[ 5 ] = dst_code_entry->digit;
  		long_offset = dst_code_entry->long_offset;
  
  		dst_code_entry = dst_code_lookup( addr[ 5 ] );
  		latitude[ 6 ] = dst_code_entry->digit;
  		w_e = dst_code_entry->w_e;
  
  		ssid = (addr[ 6 ] >> 1) & 0x0f;
  		}
  
  	/* decode the mic-e info fields */
  	spd = ((tvb_get_guint8( tvb, offset + 3 ) - 28) * 10) + ((tvb_get_guint8( tvb, offset + 4 ) - 28) / 10);
  	if ( spd >= 800 )
  		spd -= 800;
  
  	cse = (((tvb_get_guint8( tvb, offset + 4 ) - 28) % 10) * 100) + ((tvb_get_guint8( tvb, offset + 5 ) - 28) * 10);
  	if ( cse >= 400 )
  		cse -= 400;
  
  	g_snprintf( info_buffer, STRLEN,
  				"Lat: %7.7s%c Long: %03d%02d.%02d%c, Cse: %d, Spd: %d, SSID: %d, Msg %s",
  				latitude,
  				n_s,
  				d28_to_deg( tvb_get_guint8( tvb, offset ), long_offset ),
  				d28_to_min( tvb_get_guint8( tvb, offset + 1 ) ),
  				tvb_get_guint8( tvb, offset + 2 ) - 28,
  				w_e,
  				cse,
  				spd,
  				ssid,
  				mic_e_msg_table[ (msg_a << 2) + (msg_b << 1) + msg_c ].std
  				);
  
  	col_set_str( pinfo->cinfo, COL_INFO, "MIC-E " );
  	col_append_str( pinfo->cinfo, COL_INFO, info_buffer );
  
  	if ( parent_tree )
  		{
  		tc = proto_tree_add_string( parent_tree, hf_mic_e_idx, tvb, offset, data_len, info_buffer );
  		mic_e_tree = proto_item_add_subtree( tc, ett_mic_e_idx );
  
  		g_snprintf( info_buffer, STRLEN,
  				"Lat %7.7s, Msg A %d, Msg B %d, Msg C %d, N/S %c, Long off %3d, W/E %c, SSID %d",
  				latitude,
  				msg_a,
  				msg_b,
  				msg_c,
  				n_s,
  				long_offset,
  				w_e,
  				ssid
  				);
  
  		proto_tree_add_string( mic_e_tree, *mic_e_items->hf_mic_e_dst,     tvb,      0, 0, info_buffer ); /* ?? */
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_long_d,    tvb, offset, 1, ENC_BIG_ENDIAN );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_long_m,    tvb, offset, 1, ENC_BIG_ENDIAN );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_long_h,    tvb, offset, 1, ENC_BIG_ENDIAN );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_spd_sp,    tvb, offset, 1, ENC_BIG_ENDIAN );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_spd_dc,    tvb, offset, 1, ENC_BIG_ENDIAN );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_spd_se,    tvb, offset, 1, ENC_BIG_ENDIAN );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_sym_code,  tvb, offset, 1, ENC_ASCII|ENC_NA );
  		offset += 1;
  
  		proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_sym_id,    tvb, offset, 1, ENC_ASCII|ENC_NA );
  		offset += 1;
  
  		if ( offset < new_offset )
  			{
  			guint8 c = tvb_get_guint8(tvb, offset);
  			if ( (c == ',') || (c == 0x1d) )
  				proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_telemetry,
  						     tvb, offset, -1, ENC_NA );
  			else
  				proto_tree_add_item( mic_e_tree, *mic_e_items->hf_mic_e_status,
  						     tvb, offset, -1, ENC_ASCII|ENC_NA );
  			}
  
  		}
  
  	return new_offset;
  }
  
  static int
  dissect_aprs_storm(	tvbuff_t   *tvb,
  			int	    offset,
  			proto_tree *parent_tree,
  			int	    hf_aprs_storm_idx,
  			gint	    ett_aprs_storm_idx,
  			const storm_items_s *storm_items
  			)
  {
  	proto_tree  *storm_tree;
  	proto_tree *tc;
  
  	tc = proto_tree_add_item( parent_tree, hf_aprs_storm_idx, tvb, offset, -1, ENC_ASCII|ENC_NA );
  	storm_tree = proto_item_add_subtree( tc, ett_aprs_storm_idx );
  
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_dir,  tvb, offset, 3, ENC_BIG_ENDIAN );
  	offset += 3;
  	offset += 1;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_spd,  tvb, offset, 3, ENC_BIG_ENDIAN );
  	offset += 3;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_type, tvb, offset, 3, ENC_BIG_ENDIAN );
  	offset += 3;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_sws,  tvb, offset, 4, ENC_BIG_ENDIAN );
  	offset += 4;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_pwg,  tvb, offset, 4, ENC_BIG_ENDIAN );
  	offset += 4;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_cp,   tvb, offset, 5, ENC_BIG_ENDIAN );
  	offset += 5;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_rhw,  tvb, offset, 4, ENC_BIG_ENDIAN );
  	offset += 4;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_rtsw, tvb, offset, 4, ENC_BIG_ENDIAN );
  	offset += 4;
  	proto_tree_add_item( storm_tree, *storm_items->hf_aprs_storm_rwg,  tvb, offset, 4, ENC_BIG_ENDIAN );
  	offset += 4;
  
  	return offset;
  }
  
  static int
  dissect_aprs_weather(	tvbuff_t   *tvb,
  			int	    offset,
  			proto_tree *parent_tree,
  			int	    hf_aprs_weather_idx,
  			gint	    ett_aprs_weather_idx,
  			const weather_items_s *weather_items
  			)
  {
  	proto_tree  *tc;
  	proto_tree  *weather_tree;
  	int	     new_offset;
  	int	     data_len;
  	guint8	     ch;
  
  
  	data_len    = tvb_reported_length_remaining( tvb, offset );
  	new_offset  = offset + data_len;
  
  	tc = proto_tree_add_item( parent_tree, hf_aprs_weather_idx, tvb, offset, data_len, ENC_ASCII|ENC_NA );
  	weather_tree = proto_item_add_subtree( tc, ett_aprs_weather_idx );
  
  	ch = tvb_get_guint8( tvb, offset );
  	if ( g_ascii_isdigit( ch ) )
  		{
  		proto_tree_add_item( weather_tree, *weather_items->hf_weather_dir, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		offset += 3;
  		/* verify the separator */
  		offset += 1;
  		proto_tree_add_item( weather_tree, *weather_items->hf_weather_spd, tvb, offset, 3, ENC_ASCII|ENC_NA );
  		offset += 3;
  		}
  
  	if ( parent_tree )
  		{
  		while ( offset < new_offset )
  			{
  			ch = tvb_get_guint8( tvb, offset );
  			switch ( ch )
  				{
  				case 'c' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_dir,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 's' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_spd,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 'g' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_peak,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 't' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_temp,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 'r' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_rain_1,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 'P' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_rain_24,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 'p' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_rain,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 'h' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_humidty,
  						tvb, offset, 3, ENC_ASCII|ENC_NA );
  					offset += 3;
  					break;
  				case 'b' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_press,
  						tvb, offset, 6, ENC_ASCII|ENC_NA );
  					offset += 6;
  					break;
  				case 'l' :
  				case 'L' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_luminosity,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case 'S' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_snow,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				case '#' :
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_raw_rain,
  						tvb, offset, 4, ENC_ASCII|ENC_NA );
  					offset += 4;
  					break;
  				default  : {
  					gint lr;
  					/* optional: software type/unit: see if present */
  					lr = new_offset - offset;
  #if 0 /* fcn'al change: defer */
  					/*
  					 * XXX - ASCII or UTF-8?
  					 * See http://www.aprs.org/aprs12/utf-8.txt
  					 */
  					if ( ((lr < 3) || (lr > 5)) ||
  						( lr != strspn( tvb_get_string_enc( wmem_packet_scope(), tvb, offset, lr, ENC_ASCII|ENC_NA ), "a-zA-Z0-9-_" ) ) )
  						{
  						new_offset = offset;  /* Assume rest is a comment: force exit from while */
  						break;  /* from switch */
  						}
  #endif
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_software,
  						tvb, offset, 1, ENC_ASCII|ENC_NA );
  					offset += 1;
  					proto_tree_add_item( weather_tree, *weather_items->hf_weather_unit,
  						tvb, offset, lr-1, ENC_ASCII|ENC_NA );
  					offset = new_offset;
  					break;
  					}
  				} /* switch */
  			} /* while */
  		} /* if (parent_tree) */
  	return new_offset;
  }
  
  static int
  aprs_timestamp( proto_tree *aprs_tree, tvbuff_t *tvb, int offset )
  {
  	int	data_len;
  	const char *tzone;
  	guint8  ch;
  
  	data_len = 8;
  	tzone = "zulu";
  
  	ch= tvb_get_guint8( tvb, offset + 6 );
  	if ( g_ascii_isdigit( ch ) )
  		{ /* MDHM */
  		proto_tree_add_item( aprs_tree, hf_aprs_mdhm, tvb, offset, data_len, ENC_ASCII|ENC_NA );
  		proto_tree_add_string( aprs_tree, hf_aprs_tz, tvb, offset, data_len, tzone );
  		}
  	else
  		{
  		data_len -= 1;
  		if ( ch  == 'h' )
  			{ /* HMS */
  			proto_tree_add_item( aprs_tree, hf_aprs_hms,  tvb, offset, data_len, ENC_ASCII|ENC_NA );
  			proto_tree_add_string( aprs_tree, hf_aprs_tz, tvb, offset, data_len, tzone );
  			}
  		else
  			{ /* DHM */
  			switch ( ch )
  				{
  				case 'z' : tzone = "zulu";    break;
  				case '/' : tzone = "local";   break;
  				default  : tzone = "unknown"; break;
  				}
  			proto_tree_add_item( aprs_tree, hf_aprs_dhm,  tvb, offset, data_len, ENC_ASCII|ENC_NA );
  			proto_tree_add_string( aprs_tree, hf_aprs_tz, tvb, offset + 6, 1, tzone );
  			}
  		}
  
  	return offset + data_len;
  }
  
  static int
  aprs_latitude_compressed( proto_tree *aprs_tree, tvbuff_t *tvb, int offset )
  {
  	if ( aprs_tree )
  		{
  		char *info_buffer;
  		int   temp;
  
  		info_buffer = (char *)wmem_alloc( wmem_packet_scope(), STRLEN );
  
  		temp = ( tvb_get_guint8( tvb, offset + 0 ) - 33 );
  		temp = ( tvb_get_guint8( tvb, offset + 1 ) - 33 ) + ( temp * 91 );
  		temp = ( tvb_get_guint8( tvb, offset + 2 ) - 33 ) + ( temp * 91 );
  		temp = ( tvb_get_guint8( tvb, offset + 3 ) - 33 ) + ( temp * 91 );
  
  		g_snprintf( info_buffer, STRLEN, "%6.2f", 90.0 - (temp / 380926.0) );
  		proto_tree_add_string( aprs_tree, hf_aprs_lat, tvb, offset, 4, info_buffer );
  		}
  	return offset + 4;
  }
  
  static int
  aprs_longitude_compressed( proto_tree *aprs_tree, tvbuff_t *tvb, int offset )
  {
  	if ( aprs_tree )
  		{
  		char *info_buffer;
  		int   temp;
  
  		info_buffer = (char *)wmem_alloc( wmem_packet_scope(), STRLEN );
  
  		temp = ( tvb_get_guint8( tvb, offset + 0 ) - 33 );
  		temp = ( tvb_get_guint8( tvb, offset + 1 ) - 33 ) + ( temp * 91 );
  		temp = ( tvb_get_guint8( tvb, offset + 2 ) - 33 ) + ( temp * 91 );
  		temp = ( tvb_get_guint8( tvb, offset + 3 ) - 33 ) + ( temp * 91 );
  
  		g_snprintf( info_buffer, STRLEN, "%7.2f", (temp / 190463.0) - 180.0 );
  		proto_tree_add_string( aprs_tree, hf_aprs_long, tvb, offset, 4, info_buffer );
  		}
  	return offset + 4;
  }
  
  static int
  aprs_status( proto_tree *aprs_tree, tvbuff_t *tvb, int offset )
  {
  	int data_len;
  
  	data_len = tvb_reported_length_remaining( tvb, offset );
  
  	if ( ( data_len > 7 ) && ( tvb_get_guint8( tvb, offset+6 ) == 'z' ) )
  		{
  		proto_tree_add_item( aprs_tree, hf_aprs_dhm, tvb, offset, 6, ENC_ASCII|ENC_NA );
  		offset	 += 6;
  		data_len -= 6;
  		proto_tree_add_string( aprs_tree, hf_aprs_tz, tvb, offset, 1, "zulu" );
  		offset	 += 1;
  		data_len -= 1;
  		}
  	proto_tree_add_item( aprs_tree, hf_aprs_status, tvb, offset, data_len, ENC_ASCII|ENC_NA );
  
  	return offset + data_len;
  }
  
  static int
  aprs_item( proto_tree *aprs_tree, tvbuff_t *tvb, int offset )
  {
  	char *info_buffer;
  	int   data_len;
  	char *ch_ptr;
  
  	data_len    = 10;
  
  	/*
  	 * XXX - ASCII or UTF-8?
  	 * See http://www.aprs.org/aprs12/utf-8.txt
  	 */
  	info_buffer = tvb_get_string_enc( wmem_packet_scope(), tvb, offset, data_len, ENC_ASCII|ENC_NA );
  
  	ch_ptr = strchr( info_buffer, '!' );
  	if ( ch_ptr != NULL )
  		{
  		data_len = (int)(ch_ptr - info_buffer + 1);
  		*ch_ptr = '\0';
  		}
  	else
  		{
  		ch_ptr = strchr( info_buffer, '!' );
  		if ( ch_ptr != NULL )
  			{
  			data_len = (int)(ch_ptr - info_buffer + 1);
  			*ch_ptr = '\0';
  			}
  		}
  	proto_tree_add_string( aprs_tree, hf_aprs_item, tvb, offset, data_len, info_buffer );
  
  	return offset + data_len;
  }
  
  static int
  aprs_3rd_party( proto_tree *aprs_tree, tvbuff_t *tvb, int offset, int data_len )
  {
  	/* If the type of the hf[] entry pointed to by hfindex is FT_BYTES or FT_STRING */
  	/*  then  data_len == -1 is allowed and means "remainder of the tvbuff"         */
  	if ( data_len == -1 )
  		{
  		data_len = tvb_reported_length_remaining( tvb, offset );
  #if 0 /* fcn'al change: defer */
  		if ( data_len <= 0 )
  			return offset;  /* there's no data */
  #endif
  		}
  	proto_tree_add_item( aprs_tree, hf_aprs_third_party, tvb, offset, data_len, ENC_NA );
  	/* tnc-2 */
  	/* aea */
  	return offset + data_len;
  }
  
  static int
  aprs_default_string( proto_tree *aprs_tree, tvbuff_t *tvb, int offset, int data_len, int hfindex )
  {
  	/* Assumption: hfindex points to an hf[] entry with type FT_STRING; should be validated ? */
  	/* If the type of the hf[] entry pointed to by hfindex is FT_STRING      */
  	/*  then  data_len == -1 is allowed and means "remainder of the tvbuff"  */
  	if ( data_len == -1 )
  		{
  		data_len = tvb_reported_length_remaining( tvb, offset );
  #if 0 /* fcn'al change: defer */
  		if ( data_len <= 0 )
  			return offset;  /* there's no data */
  #endif
  		}
  	proto_tree_add_item( aprs_tree, hfindex, tvb, offset, data_len, ENC_ASCII|ENC_NA );
  	return offset + data_len;
  }
  
  static int
  aprs_default_bytes( proto_tree *aprs_tree, tvbuff_t *tvb, int offset, int data_len, int hfindex )
  {
  	/* Assumption: hfindex points to an hf[] entry with type FT_BYTES; should be validated ? */
  	/* If the type of the hf[] entry pointed to by hfindex is FT_BYTES      */
  	/*  then  data_len == -1 is allowed and means "remainder of the tvbuff" */
  	if ( data_len == -1 )
  		{
  		data_len = tvb_reported_length_remaining( tvb, offset );
  #if 0 /* fcn'al change: defer */
  		if ( data_len <= 0 )
  			return offset;  /* there's no data */
  #endif
  		}
  	proto_tree_add_item( aprs_tree, hfindex, tvb, offset, data_len, ENC_NA );
  	return offset + data_len;
  }
  
  static int
  aprs_position( proto_tree *aprs_tree, tvbuff_t *tvb, int offset, gboolean with_msg )
  {
  	guint8	 symbol_table_id    = 0;
  	guint8	 symbol_code	    = 0;
  	gboolean probably_a_msg	    = FALSE;
  	gboolean probably_not_a_msg = FALSE;
  
  	if ( g_ascii_isdigit( tvb_get_guint8( tvb, offset ) ) )
  		{
  		offset		= aprs_default_string( aprs_tree, tvb, offset, 8, hf_aprs_lat );
  		symbol_table_id = tvb_get_guint8( tvb, offset );
  		offset		= aprs_default_string( aprs_tree, tvb, offset, 1, hf_aprs_sym_id );
  		offset		= aprs_default_string( aprs_tree, tvb, offset, 9, hf_aprs_long );
  		symbol_code	= tvb_get_guint8( tvb, offset );
  		offset		= aprs_default_string( aprs_tree, tvb, offset, 1, hf_aprs_sym_code );
  		if ( gPREF_APRS_LAX )
  			{
  			switch ( tvb_get_guint8( tvb, offset ) )
  				{
  				case 'D'	: probably_a_msg = TRUE;     break;
  				case 'P'	: probably_a_msg = TRUE;     break;
  				case 'R'	: probably_a_msg = TRUE;     break;
  				case 'T'	: probably_a_msg = TRUE;     break;
  				default		: probably_not_a_msg = TRUE; break;
  				}
  			}
  		if ( with_msg || probably_a_msg || ! probably_not_a_msg )
  			offset = dissect_aprs_msg(	tvb,
  							offset,
  							aprs_tree,
  							hf_aprs_msg,
  							ett_aprs_msg,
  							&msg_items_gbl,
  							( symbol_code == '_' ),
  							( symbol_table_id == '/' && symbol_code == '\\' )
  							);
  		}
  	else
  		{
  		symbol_table_id = tvb_get_guint8( tvb, offset );
  		offset = aprs_default_string( aprs_tree, tvb, offset, 1, hf_aprs_sym_id );
  		offset = aprs_latitude_compressed( aprs_tree, tvb, offset );
  		offset = aprs_longitude_compressed( aprs_tree, tvb, offset );
  		symbol_code = tvb_get_guint8( tvb, offset );
  		offset = aprs_default_string( aprs_tree, tvb, offset, 1, hf_aprs_sym_code );
  		offset = dissect_aprs_compressed_msg(	tvb,
  							offset,
  							aprs_tree,
  							hf_aprs_msg,
  							ett_aprs_msg,
  							&msg_items_gbl
  							);
  		offset = dissect_aprs_compression_type(	tvb,
  							offset,
  							aprs_tree,
  							hf_aprs_compression_type,
  							ett_aprs_ct,
  							&ct_items_gbl
  							);
  		if ( symbol_table_id == '/' && symbol_code == '\\' )
  			offset = aprs_default_string( aprs_tree, tvb, offset, 8, hf_aprs_msg_brg );
  		}
  
  	if ( symbol_code == '_' )
  		offset = dissect_aprs_weather(	tvb,
  						offset,
  						aprs_tree,
  						hf_aprs_weather,
  						ett_aprs_weather,
  						&weather_items_gbl
  						);
  	if ( ( symbol_table_id == '/' && symbol_code == '@' ) || ( symbol_table_id == '\\' && symbol_code == '@' ) )
  		offset = dissect_aprs_storm(	tvb,
  						offset,
  						aprs_tree,
  						hf_aprs_storm,
  						ett_aprs_storm,
  						&storm_items_gbl
  						);
  
  	return offset;
  }
  
  static int
  dissect_aprs( tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, void *data _U_ )
  {
  	proto_item    *ti;
  	proto_tree    *aprs_tree;
  
  	int	       offset;
  	guint8	       dti;
  	wmem_strbuf_t *sb;
  
  	col_set_str( pinfo->cinfo, COL_PROTOCOL, "APRS" );
  	col_clear( pinfo->cinfo, COL_INFO );
  
  	offset	 = 0;
  
  	dti	 = tvb_get_guint8( tvb, offset );
  
  	sb = wmem_strbuf_new_label(wmem_packet_scope());
  
  	if (dti != '!')
  		wmem_strbuf_append(sb, val_to_str_ext_const(dti, &aprs_description_ext, ""));
  
  	switch ( dti )
  		{
  		case '!':
  			/* Position or Ultimeter 2000 WX Station */
  			if ( tvb_get_guint8( tvb, offset + 1 ) == '!' )
  				{
  				wmem_strbuf_append(sb, "Ultimeter 2000 WX Station");
  				}
  			else
  				{
  				/* Position "without APRS messaging" */
  				wmem_strbuf_append(sb, "Position (");
  				wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1, 8));	/* Lat */
  				wmem_strbuf_append(sb, " ");
  				wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 8 + 1, 9));	/* Long */
  				wmem_strbuf_append(sb, " ");
  				wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 8, 1));	/* Symbol table id */
  				wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 8 + 1 + 9, 1));	/* Symbol Code */
  				}
  			break;
  
  		case '=':
  			/* Position "with APRS messaging" + Ext APRS message */
  			wmem_strbuf_append(sb, " (");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1, 8));	/* Lat */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 8 + 1, 9));	/* Long */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 8, 1));	/* Symbol table id */
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 8 + 1 + 9, 1));	/* Symbol Code */
  			break;
  
  		case '/':
  			/* Position + timestamp "without APRS messaging" */
  			wmem_strbuf_append(sb, " (");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1, 7));	/* Timestamp */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7 + 1, 8));    /*??*/	/* Lat */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7 + 8 + 1, 9));	/* Long */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7, 1));	/* Symbol table id */
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7 + 1 + 9, 1));	/* Symbol Code */
  			break;
  
  		case '@':
  			/* Position + timestamp "with APRS messaging" + Ext APRS message */
  			wmem_strbuf_append(sb, " (");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1, 7));	/* Timestamp */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7 + 1, 8));    /*??*/	/* Lat */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7 + 8 + 1, 9));	/* Long */
  			wmem_strbuf_append(sb, " ");
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7, 1));	/* Symbol table id */
  			wmem_strbuf_append(sb, tvb_format_text(tvb, offset + 1 + 7 + 1 + 9, 1));	/* Symbol Code */
  			break;
  		}
  
  	col_add_str( pinfo->cinfo, COL_INFO, wmem_strbuf_get_str(sb) );
  
  	/* create display subtree for the protocol */
  	ti = proto_tree_add_protocol_format( parent_tree , proto_aprs, tvb, 0, -1, "%s", wmem_strbuf_get_str(sb) );
  	aprs_tree = proto_item_add_subtree( ti, ett_aprs );
  
  	proto_tree_add_item( aprs_tree, hf_aprs_dti, tvb, offset, 1, ENC_ASCII|ENC_NA );
  	offset += 1;
  
  	switch ( dti )
  		{
  		case '<'	: /* Station Capabilities */
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_station );
  			break;
  		case '>'	: /* Status */
  			offset = aprs_status( aprs_tree, tvb, offset );
  			break;
  		case '?'	: /* Query */
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_query );
  			break;
  		case '$'	: /* Raw GPS data or Ultimeter 2000 */
  			if ( tvb_get_guint8( tvb, offset ) == 'U' )
  				offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_ultimeter_2000 );
  			else
  				offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_raw );
  			break;
  		case '%'	: /* Agrelo DFJr / MicroFinder */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_agrelo );
  			break;
  		case 'T'	: /* Telemetry data */
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_telemetry );
  			break;
  		case '['	: /* Maidenhead grid locator beacon (obsolete) */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_maidenhead );
  			break;
  		case '_'	: /* Weather Report (without position) */
  			offset = aprs_timestamp( aprs_tree, tvb, offset );
  			offset = dissect_aprs_weather(	tvb,
  				offset,
  				aprs_tree,
  				hf_aprs_weather,
  				ett_aprs_weather,
  				&weather_items_gbl
  				);
  			break;
  		case ','	: /* Invalid data or test data */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_invalid_test );
  			break;
  		case '{'	: /* User-Defined APRS packet format */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_user_defined );
  			break;
  		case '}'	: /* Third-party traffic */
  			offset = aprs_3rd_party( aprs_tree, tvb, offset, -1 );
  			break;
  		case ':'	: /* Message */
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_message );
  			break;
  		case 0x1c	: /* Current Mic-E Data (Rev 0 beta) */
  			offset = dissect_mic_e(	tvb,
  						offset,
  						pinfo,
  						aprs_tree,
  						hf_aprs_mic_e_0_current,
  						ett_aprs_mic_e,
  						&mic_e_items_gbl
  						);
  			break;
  		case 0x1d	: /* Old Mic-E Data (Rev 0 beta) */
  			offset = dissect_mic_e(	tvb,
  						offset,
  						pinfo,
  						aprs_tree,
  						hf_aprs_mic_e_0_old,
  						ett_aprs_mic_e,
  						&mic_e_items_gbl
  						);
  			break;
  		case '\''	: /* Old Mic-E Data (but Current data for TM-D700) */
  			offset = dissect_mic_e(	tvb,
  						offset,
  						pinfo,
  						aprs_tree,
  						hf_aprs_mic_e_old,
  						ett_aprs_mic_e,
  						&mic_e_items_gbl
  						);
  			break;
  		case '`'	: /* Current Mic-E Data (not used in TM-D700) */
  			offset = dissect_mic_e(	tvb,
  						offset,
  						pinfo,
  						aprs_tree,
  						hf_aprs_mic_e_current,
  						ett_aprs_mic_e,
  						&mic_e_items_gbl
  						);
  			break;
  		case '#'	: /* Peet Bros U-II Weather Station */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_peet_1 );
  			break;
  		case '*'	: /* Peet Bros U-II Weather Station */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_peet_2 );
  			break;
  		case '&'	: /* [Reserved - Map Feature] */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_map_feature );
  			break;
  		case '+'	: /* [Reserved - Shelter data with time] */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_shelter_data );
  			break;
  		case '.'	: /* [Reserved - Space weather] */
  			offset = aprs_default_bytes( aprs_tree, tvb, offset, -1, hf_aprs_space_weather );
  			break;
  		case ')'	: /* Item */
  			offset = aprs_item( aprs_tree, tvb, offset );
  			offset = aprs_position( aprs_tree, tvb, offset, TRUE );
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_comment );
  			break;
  		case ';'	: /* Object */
  			offset = aprs_default_string( aprs_tree, tvb, offset, 10, hf_aprs_object );
  			offset = aprs_timestamp( aprs_tree, tvb, offset );
  			offset = aprs_position( aprs_tree, tvb, offset, TRUE );
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_comment );
  			break;
  		case '!'	: /* Position or Ultimeter 2000 WX Station */
  			if ( tvb_get_guint8( tvb, offset ) == '!' )
  				offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_ultimeter_2000 );
  			else
  				{
  				offset = aprs_position( aprs_tree, tvb, offset, FALSE );
  				offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_comment );
  				}
  			break;
  		case '='	: /* Position + Ext APRS message */
  			offset = aprs_position( aprs_tree, tvb, offset, TRUE );
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_comment );
  			break;
  		case '/'	: /* Position + timestamp */
  			offset = aprs_timestamp( aprs_tree, tvb, offset );
  			offset = aprs_position( aprs_tree, tvb, offset, FALSE );
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_comment );
  			break;
  		case '@'	: /* Position + timestamp + Ext APRS message */
  			offset = aprs_timestamp( aprs_tree, tvb, offset );
  			offset = aprs_position( aprs_tree, tvb, offset, TRUE );
  			offset = aprs_default_string( aprs_tree, tvb, offset, -1, hf_aprs_comment );
  			break;
  		default	: break;
  		}
  	return offset;
  }
  
  void
  proto_register_aprs( void )
  {
  	module_t *aprs_module;
  
  	/* Setup list of header fields */
  	static hf_register_info hf[] = {
  		{ &hf_aprs_dti,
  			{ "DTI",		"aprs.dti",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Data type indicator", HFILL }
  		},
  		{ &hf_aprs_sym_code,
  			{ "Symbol code",		"aprs.sym_code",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_sym_id,
  			{ "Symbol table ID",		"aprs.sym_id",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* Position */
  #if 0
  		{ &hf_aprs_position,
  			{ "Position",		"aprs.position",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  #endif
  		{ &hf_aprs_lat,
  			{ "Latitude",		"aprs.position.lat",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_long,
  			{ "Longitude",		"aprs.position.long",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* APRS Messages */
  		{ &hf_aprs_comment,
  			{ "Comment",			"aprs.comment",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_ultimeter_2000,
  			{ "Ultimeter 2000",		"aprs.ultimeter_2000",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_status,
  			{ "Status",			"aprs.status",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_object,
  			{ "Object",			"aprs.object",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_item,
  			{ "Item",			"aprs.item",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_query,
  			{ "Query",			"aprs.query",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_telemetry,
  			{ "Telemetry",			"aprs.telemetry",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_raw,
  			{ "Raw",			"aprs.raw",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Raw NMEA position report format", HFILL }
  		},
  		{ &hf_aprs_station,
  			{ "Station",			"aprs.station",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Station capabilities", HFILL }
  		},
  		{ &hf_aprs_message,
  			{ "Message",			"aprs.message",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_agrelo,
  			{ "Agrelo",			"aprs.agrelo",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Agrelo DFJr / MicroFinder", HFILL }
  		},
  		{ &hf_aprs_maidenhead,
  			{ "Maidenhead",			"aprs.maidenhead",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Maidenhead grid locator beacon (obsolete)", HFILL }
  		},
  		{ &hf_aprs_invalid_test,
  			{ "Invalid or test",		"aprs.invalid_test",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Invalid data or test data", HFILL }
  		},
  		{ &hf_aprs_user_defined,
  			{ "User-Defined",		"aprs.user_defined",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"User-Defined APRS packet format", HFILL }
  		},
  		{ &hf_aprs_third_party,
  			{ "Third-party",		"aprs.third_party",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Third-party traffic", HFILL }
  		},
  		{ &hf_aprs_peet_1,
  			{ "Peet U-II (1)",		"aprs.peet_1",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Peet Bros U-II Weather Station", HFILL }
  		},
  		{ &hf_aprs_peet_2,
  			{ "Peet U-II (2)",		"aprs.peet_2",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Peet Bros U-II Weather Station", HFILL }
  		},
  		{ &hf_aprs_map_feature,
  			{ "Map Feature",		"aprs.map_feature",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"[Reserved - Map Feature", HFILL }
  		},
  		{ &hf_aprs_shelter_data,
  			{ "Shelter data",		"aprs.shelter_data",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"[Reserved - Shelter data with time]", HFILL }
  		},
  		{ &hf_aprs_space_weather,
  			{ "Space weather",		"aprs.space_weather",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"[Reserved - Space weather]", HFILL }
  		},
  		{ &hf_aprs_storm,
  			{ "Storm",			"aprs.storm",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* Time stamp */
  		{ &hf_aprs_dhm,
  			{ "DHM",		"aprs.dhm",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Day/Hour/Minute", HFILL }
  		},
  		{ &hf_aprs_hms,
  			{ "HMS",		"aprs.hms",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Hour/Minute/Second", HFILL }
  		},
  		{ &hf_aprs_mdhm,
  			{ "MDHM",		"aprs.mdhm",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Month/Day/Hour/Minute", HFILL }
  		},
  		{ &hf_aprs_tz,
  			{ "TZ",		"aprs.tz",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Time zone", HFILL }
  		},
  
  /* Compressed Msg */
  		{ &hf_aprs_compression_type,
  			{ "Compression type",		"aprs.ct",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_ct_gps_fix,
  			{ "GPS fix type",		"aprs.ct.gps_fix",
  			FT_UINT8, BASE_HEX, VALS(gps_vals), 0x20,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_ct_nmea_src,
  			{ "NMEA source",		"aprs.ct.nmea_src",
  			FT_UINT8, BASE_HEX, VALS(nmea_vals), 0x18,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_ct_origin,
  			{ "Compression origin",		"aprs.ct.origin",
  			FT_UINT8, BASE_HEX, VALS(ctype_vals), 0x07,
  			NULL, HFILL }
  		},
  
  /* Ext Msg */
  		{ &hf_aprs_msg,
  			{ "Extended message",			"aprs.msg",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_rng,
  			{ "Range",			"aprs.msg.rng",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Pre-calculated radio range", HFILL }
  		},
  		{ &hf_aprs_msg_cse,
  			{ "Course",			"aprs.msg.cse",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_spd,
  			{ "Speed",			"aprs.msg.spd",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_dir,
  			{ "Wind direction",		"aprs.msg.dir",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_brg,
  			{ "Bearing",			"aprs.msg.brg",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_nrq,
  			{ "NRQ",			"aprs.msg.nrq",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Number/Range/Quality", HFILL }
  		},
  
  /* Msg PHGD */
  		{ &hf_aprs_msg_phg_p,
  			{ "Power",		"aprs.msg.phg.p",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_phg_h,
  			{ "Height",		"aprs.msg.phg.h",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_phg_g,
  			{ "Gain",		"aprs.msg.phg.g",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_phg_d,
  			{ "Directivity",		"aprs.msg.phg.d",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* Msg DFS */
  		{ &hf_aprs_msg_dfs_s,
  			{ "Strength",			"aprs.msg.dfs.s",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_dfs_h,
  			{ "Height",			"aprs.msg.dfs.h",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_dfs_g,
  			{ "Gain",			"aprs.msg.dfs.g",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_dfs_d,
  			{ "Directivity",		"aprs.msg.dfs.d",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* Msg AOD */
  		{ &hf_aprs_msg_aod_t,
  			{ "Type",			"aprs.msg.aod.t",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_msg_aod_c,
  			{ "Colour",			"aprs.msg.aod.c",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* Weather */
  		{ &hf_aprs_weather,
  			{ "Weather",			"aprs.weather",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Weather report", HFILL }
  		},
  		{ &hf_aprs_weather_dir,
  			{ "Wind direction",		"aprs.weather.dir",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_spd,
  			{ "Wind speed",			"aprs.weather.speed",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Wind speed (1 minute)", HFILL }
  		},
  		{ &hf_aprs_weather_peak,
  			{ "Peak wind speed",		"aprs.weather.peak",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_temp,
  			{ "Temperature (F)",		"aprs.weather.temp",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_rain_1,
  			{ "Rain (last 1 hour)",			"aprs.weather.1_hour",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_rain_24,
  			{ "Rain (last 24 hours)",		"aprs.weather.24_hour",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_rain,
  			{ "Rain",			"aprs.weather.rain",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_humidty,
  			{ "Humidity",			"aprs.weather.humidity",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_press,
  			{ "Pressure",			"aprs.weather.pressure",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_luminosity,
  			{ "Luminosity",			"aprs.weather.luminosity",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_snow,
  			{ "Snow",			"aprs.weather.snow",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_raw_rain,
  			{ "Raw rain",			"aprs.weather.raw_rain",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_software,
  			{ "Software",			"aprs.weather.software",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_weather_unit,
  			{ "Unit",			"aprs.weather.unit",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  
  /* MIC-E */
  		{ &hf_aprs_mic_e_0_current,
  			{ "Current Mic-E (Rev 0)",	"aprs.mic_e_0_current",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_mic_e_0_old,
  			{ "Old Mic-E (Rev 0)",		"aprs.mic_e_0_old",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_mic_e_old,
  			{ "Old Mic-E",			"aprs.mic_e_old",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Old Mic-E Data (but Current data for TM-D700)", HFILL }
  		},
  		{ &hf_aprs_mic_e_current,
  			{ "Current Mic-E",		"aprs.mic_e_current",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			"Current Mic-E Data (not used in TM-D700)", HFILL }
  		},
  		{ &hf_aprs_mic_e_dst,
  			{ "Destination Address",		"aprs.mic_e.dst",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_mic_e_long_d,
  			{ "Longitude degrees",		"aprs.mic_e.long_d",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_mic_e_long_m  ,
  			{ "Longitude minutes",		"aprs.mic_e.long_m",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_mic_e_long_h,
  			{ "Longitude hundreths of minutes",	"aprs.mic_e.long_h",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			NULL, HFILL }
  		},
  		{ &hf_aprs_mic_e_spd_sp,
  			{ "Speed (H & T)",		"aprs.mic_e.speed_sp",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			"Speed (hundreds & tens)", HFILL }
  		},
  		{ &hf_aprs_mic_e_spd_dc,
  			{ "Spd (U), Cse (H)",		"aprs.mic_e.speed_dc",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			"Speed (tens), Course (hundreds)", HFILL }
  		},
  		{ &hf_aprs_mic_e_spd_se,
  			{ "Course (T & U)",	"aprs.mic_e.speed_se",
  			FT_UINT8, BASE_HEX, NULL, 0x0,
  			"Course (tens & units)", HFILL }
  		},
  		{ &hf_aprs_mic_e_telemetry,
  			{ "Telmetry",	"aprs.mic_e.telemetry",
  			FT_BYTES, BASE_NONE, NULL, 0x0,
  			"Telemetry", HFILL }
  		},
  		{ &hf_aprs_mic_e_status,
  			{ "Status",	"aprs.mic_e.status",
  			FT_STRING, BASE_NONE, NULL, 0x0,
  			NULL, HFILL }
  		},
  	};
  
  	/* Setup protocol subtree array */
  	static gint *ett[] = {
  		&ett_aprs,
  		&ett_aprs_msg,
  		&ett_aprs_ct,
  		&ett_aprs_weather,
  		&ett_aprs_storm,
  		&ett_aprs_mic_e,
  	};
  
  	/* Register the protocol name and description */
  	proto_aprs = proto_register_protocol("Automatic Position Reporting System", "APRS", "aprs");
  
  	/* Register the dissector */
  	new_register_dissector( "aprs", dissect_aprs, proto_aprs);
  
  	/* Required function calls to register the header fields and subtrees used */
  	proto_register_field_array( proto_aprs, hf, array_length(hf ) );
  	proto_register_subtree_array( ett, array_length( ett ) );
  
  	/* Register preferences module */
  	aprs_module = prefs_register_protocol( proto_aprs, NULL);
  
  	/* Register any preference */
  	prefs_register_bool_preference(aprs_module, "showaprslax",
  		"Allow APRS violations.",
  		"Attempt to display common APRS protocol violations correctly",
  		&gPREF_APRS_LAX );
  
  }
  
  /*
   * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
   *
   * Local variables:
   * c-basic-offset: 8
   * tab-width: 8
   * indent-tabs-mode: t
   * End:
   *
   * vi: set shiftwidth=8 tabstop=8 noexpandtab:
   * :indentSize=8:tabSize=8:noTabs=false:
   */
  