summaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-nmea0183.c
diff options
context:
space:
mode:
Diffstat (limited to 'epan/dissectors/packet-nmea0183.c')
-rw-r--r--epan/dissectors/packet-nmea0183.c1535
1 files changed, 1535 insertions, 0 deletions
diff --git a/epan/dissectors/packet-nmea0183.c b/epan/dissectors/packet-nmea0183.c
new file mode 100644
index 00000000..e0e8cfa8
--- /dev/null
+++ b/epan/dissectors/packet-nmea0183.c
@@ -0,0 +1,1535 @@
+/* packet-nmea0183.c
+ * Routines for NMEA 0183 protocol dissection
+ * Copyright 2024 Casper Meijn <casper@meijn.net>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+#include <epan/packet.h>
+#include <epan/expert.h>
+
+static int hf_nmea0183_talker_id;
+static int hf_nmea0183_sentence_id;
+static int hf_nmea0183_unknown_field;
+static int hf_nmea0183_checksum;
+static int hf_nmea0183_checksum_calculated;
+
+static int hf_nmea0183_dpt_depth;
+static int hf_nmea0183_dpt_offset;
+static int hf_nmea0183_dpt_max_range;
+
+static int hf_nmea0183_hdt_heading;
+static int hf_nmea0183_hdt_unit;
+
+static int hf_nmea0183_gga_time;
+static int hf_nmea0183_gga_time_hour;
+static int hf_nmea0183_gga_time_minute;
+static int hf_nmea0183_gga_time_second;
+static int hf_nmea0183_gga_latitude;
+static int hf_nmea0183_gga_latitude_degree;
+static int hf_nmea0183_gga_latitude_minute;
+static int hf_nmea0183_gga_latitude_direction;
+static int hf_nmea0183_gga_longitude;
+static int hf_nmea0183_gga_longitude_degree;
+static int hf_nmea0183_gga_longitude_minute;
+static int hf_nmea0183_gga_longitude_direction;
+static int hf_nmea0183_gga_quality;
+static int hf_nmea0183_gga_number_satellites;
+static int hf_nmea0183_gga_horizontal_dilution;
+static int hf_nmea0183_gga_altitude;
+static int hf_nmea0183_gga_altitude_unit;
+static int hf_nmea0183_gga_geoidal_separation;
+static int hf_nmea0183_gga_geoidal_separation_unit;
+static int hf_nmea0183_gga_age_dgps;
+static int hf_nmea0183_gga_dgps_station;
+
+static int hf_nmea0183_gll_latitude;
+static int hf_nmea0183_gll_latitude_degree;
+static int hf_nmea0183_gll_latitude_minute;
+static int hf_nmea0183_gll_latitude_direction;
+static int hf_nmea0183_gll_longitude;
+static int hf_nmea0183_gll_longitude_degree;
+static int hf_nmea0183_gll_longitude_minute;
+static int hf_nmea0183_gll_longitude_direction;
+static int hf_nmea0183_gll_time;
+static int hf_nmea0183_gll_time_hour;
+static int hf_nmea0183_gll_time_minute;
+static int hf_nmea0183_gll_time_second;
+static int hf_nmea0183_gll_status;
+static int hf_nmea0183_gll_mode;
+
+static int hf_nmea0183_rot_rate_of_turn;
+static int hf_nmea0183_rot_valid;
+
+static int hf_nmea0183_vhw_true_heading;
+static int hf_nmea0183_vhw_true_heading_unit;
+static int hf_nmea0183_vhw_magnetic_heading;
+static int hf_nmea0183_vhw_magnetic_heading_unit;
+static int hf_nmea0183_vhw_water_speed_knot;
+static int hf_nmea0183_vhw_water_speed_knot_unit;
+static int hf_nmea0183_vhw_water_speed_kilometer;
+static int hf_nmea0183_vhw_water_speed_kilometer_unit;
+
+static int hf_nmea0183_zda_time;
+static int hf_nmea0183_zda_time_hour;
+static int hf_nmea0183_zda_time_minute;
+static int hf_nmea0183_zda_time_second;
+static int hf_nmea0183_zda_date_day;
+static int hf_nmea0183_zda_date_month;
+static int hf_nmea0183_zda_date_year;
+static int hf_nmea0183_zda_local_zone_hour;
+static int hf_nmea0183_zda_local_zone_minute;
+
+static int ett_nmea0183;
+static int ett_nmea0183_checksum;
+static int ett_nmea0183_sentence;
+static int ett_nmea0183_zda_time;
+static int ett_nmea0183_gga_time;
+static int ett_nmea0183_gga_latitude;
+static int ett_nmea0183_gga_longitude;
+static int ett_nmea0183_gll_time;
+static int ett_nmea0183_gll_latitude;
+static int ett_nmea0183_gll_longitude;
+
+static expert_field ei_nmea0183_invalid_first_character;
+static expert_field ei_nmea0183_missing_checksum_character;
+static expert_field ei_nmea0183_invalid_end_of_line;
+static expert_field ei_nmea0183_checksum_incorrect;
+static expert_field ei_nmea0183_sentence_too_long;
+static expert_field ei_nmea0183_field_time_too_short;
+static expert_field ei_nmea0183_field_latitude_too_short;
+static expert_field ei_nmea0183_field_longitude_too_short;
+static expert_field ei_nmea0183_field_missing;
+static expert_field ei_nmea0183_gga_altitude_unit_incorrect;
+static expert_field ei_nmea0183_gga_geoidal_separation_unit_incorrect;
+static expert_field ei_nmea0183_hdt_unit_incorrect;
+static expert_field ei_nmea0183_vhw_true_heading_unit_incorrect;
+static expert_field ei_nmea0183_vhw_magnetic_heading_unit_incorrect;
+static expert_field ei_nmea0183_vhw_water_speed_knot_unit_incorrect;
+static expert_field ei_nmea0183_vhw_water_speed_kilometer_unit_incorrect;
+
+static int proto_nmea0183;
+
+static dissector_handle_t nmea0183_handle;
+
+// List of known Talker IDs (Source: NMEA Revealed by Eric S. Raymond, https://gpsd.gitlab.io/gpsd/NMEA.html, retrieved 2023-01-26)
+static const string_string known_talker_ids[] = {
+ {"AB", "Independent AIS Base Station"},
+ {"AD", "Dependent AIS Base Station"},
+ {"AG", "Autopilot - General"},
+ {"AI", "Mobile AIS Station"},
+ {"AN", "AIS Aid to Navigation"},
+ {"AP", "Autopilot - Magnetic"},
+ {"AR", "AIS Receiving Station"},
+ {"AT", "AIS Transmitting Station"},
+ {"AX", "AIS Simplex Repeater"},
+ {"BD", "BeiDou (China)"},
+ {"BI", "Bilge System"},
+ {"BN", "Bridge navigational watch alarm system"},
+ {"CA", "Central Alarm"},
+ {"CC", "Computer - Programmed Calculator (obsolete)"},
+ {"CD", "Communications - Digital Selective Calling (DSC)"},
+ {"CM", "Computer - Memory Data (obsolete)"},
+ {"CR", "Data Receiver"},
+ {"CS", "Communications - Satellite"},
+ {"CT", "Communications - Radio-Telephone (MF/HF)"},
+ {"CV", "Communications - Radio-Telephone (VHF)"},
+ {"CX", "Communications - Scanning Receiver"},
+ {"DE", "DECCA Navigation (obsolete)"},
+ {"DF", "Direction Finder"},
+ {"DM", "Velocity Sensor, Speed Log, Water, Magnetic"},
+ {"DP", "Dynamiv Position"},
+ {"DU", "Duplex repeater station"},
+ {"EC", "Electronic Chart Display & Information System (ECDIS)"},
+ {"EP", "Emergency Position Indicating Beacon (EPIRB)"},
+ {"ER", "Engine Room Monitoring Systems"},
+ {"FD", "Fire Door"},
+ {"FS", "Fire Sprinkler"},
+ {"GA", "Galileo Positioning System"},
+ {"GB", "BeiDou (China)"},
+ {"GI", "NavIC, IRNSS (India)"},
+ {"GL", "GLONASS, according to IEIC 61162-1"},
+ {"GN", "Combination of multiple satellite systems (NMEA 1083)"},
+ {"GP", "Global Positioning System receiver"},
+ {"GQ", "QZSS regional GPS augmentation system (Japan)"},
+ {"HC", "Heading - Magnetic Compass"},
+ {"HD", "Hull Door"},
+ {"HE", "Heading - North Seeking Gyro"},
+ {"HF", "Heading - Fluxgate"},
+ {"HN", "Heading - Non North Seeking Gyro"},
+ {"HS", "Hull Stress"},
+ {"II", "Integrated Instrumentation"},
+ {"IN", "Integrated Navigation"},
+ {"JA", "Alarm and Monitoring"},
+ {"JB", "Water Monitoring"},
+ {"JC", "Power Management"},
+ {"JD", "Propulsion Control"},
+ {"JE", "Engine Control"},
+ {"JF", "Propulsion Boiler"},
+ {"JG", "Aux Boiler"},
+ {"JH", "Engine Governor"},
+ {"LA", "Loran A (obsolete)"},
+ {"LC", "Loran C (obsolete)"},
+ {"MP", "Microwave Positioning System (obsolete)"},
+ {"MX", "Multiplexer"},
+ {"NL", "Navigation light controller"},
+ {"OM", "OMEGA Navigation System (obsolete)"},
+ {"OS", "Distress Alarm System (obsolete)"},
+ {"P ", "Vendor specific"},
+ {"QZ", "QZSS regional GPS augmentation system (Japan)"},
+ {"RA", "RADAR and/or ARPA"},
+ {"RB", "Record Book"},
+ {"RC", "Propulsion Machinery"},
+ {"RI", "Rudder Angle Indicator"},
+ {"SA", "Physical Shore AUS Station"},
+ {"SD", "Depth Sounder"},
+ {"SG", "Steering Gear"},
+ {"SN", "Electronic Positioning System, other/general"},
+ {"SS", "Scanning Sounder"},
+ {"ST", "Skytraq debug output"},
+ {"TC", "Track Control"},
+ {"TI", "Turn Rate Indicator"},
+ {"TR", "TRANSIT Navigation System"},
+ {"U0", "User Configured 0"},
+ {"U1", "User Configured 1"},
+ {"U2", "User Configured 2"},
+ {"U3", "User Configured 3"},
+ {"U4", "User Configured 4"},
+ {"U5", "User Configured 5"},
+ {"U6", "User Configured 6"},
+ {"U7", "User Configured 7"},
+ {"U8", "User Configured 8"},
+ {"U9", "User Configured 9"},
+ {"UP", "Microprocessor controller"},
+ {"VA", "VHF Data Exchange System (VDES), ASM"},
+ {"VD", "Velocity Sensor, Doppler, other/general"},
+ {"VM", "Velocity Sensor, Speed Log, Water, Magnetic"},
+ {"VR", "Voyage Data recorder"},
+ {"VS", "VHF Data Exchange System (VDES), Satellite"},
+ {"VT", "VHF Data Exchange System (VDES), Terrestrial"},
+ {"VW", "Velocity Sensor, Speed Log, Water, Mechanical"},
+ {"WD", "Watertight Door"},
+ {"WI", "Weather Instruments"},
+ {"WL", "Water Level"},
+ {"YC", "Transducer - Temperature (obsolete)"},
+ {"YD", "Transducer - Displacement, Angular or Linear (obsolete)"},
+ {"YF", "Transducer - Frequency (obsolete)"},
+ {"YL", "Transducer - Level (obsolete)"},
+ {"YP", "Transducer - Pressure (obsolete)"},
+ {"YR", "Transducer - Flow Rate (obsolete)"},
+ {"YT", "Transducer - Tachometer (obsolete)"},
+ {"YV", "Transducer - Volume (obsolete)"},
+ {"YX", "Transducer"},
+ {"ZA", "Timekeeper - Atomic Clock"},
+ {"ZC", "Timekeeper - Chronometer"},
+ {"ZQ", "Timekeeper - Quartz"},
+ {"ZV", "Timekeeper - Radio Update, WWV or WWVH"},
+ {NULL, NULL}};
+
+// List of known Sentence IDs (Source: NMEA Revealed by Eric S. Raymond, https://gpsd.gitlab.io/gpsd/NMEA.html, retrieved 2023-01-26)
+static const string_string known_sentence_ids[] = {
+ {"AAM", "Waypoint Arrival Alarm"},
+ {"ACK", "Alarm Acknowledgement"},
+ {"ADS", "Automatic Device Status"},
+ {"AKD", "Acknowledge Detail Alarm Condition"},
+ {"ALA", "Set Detail Alarm Condition"},
+ {"ALM", "GPS Almanac Data"},
+ {"APA", "Autopilot Sentence A"},
+ {"APB", "Autopilot Sentence B"},
+ {"ASD", "Autopilot System Data"},
+ {"BEC", "Bearing & Distance to Waypoint - Dead Reckoning"},
+ {"BER", "Bearing & Distance to Waypoint, Dead Reckoning, Rhumb Line"},
+ {"BOD", "Bearing - Waypoint to Waypoint"},
+ {"BPI", "Bearing & Distance to Point of Interest"},
+ {"BWC", "Bearing & Distance to Waypoint - Great Circle"},
+ {"BWR", "Bearing and Distance to Waypoint - Rhumb Line"},
+ {"BWW", "Bearing - Waypoint to Waypoint"},
+ {"CEK", "Configure Encryption Key Command"},
+ {"COP", "Configure the Operational Period, Command"},
+ {"CUR", "Water Current Layer"},
+ {"DBK", "Depth Below Keel"},
+ {"DBS", "Depth Below Surface"},
+ {"DBT", "Depth below transducer"},
+ {"DCN", "DECCA Position"},
+ {"DCR", "Device Capability Report"},
+ {"DDC", "Display Dimming Control"},
+ {"DOR", "Door Status Detection"},
+ {"DPT", "Depth of Water"},
+ {"DRU", "Dual Doppler Auxiliary Data"},
+ {"DSC", "Digital Selective Calling Information"},
+ {"DSE", "Extended DSC"},
+ {"DSI", "DSC Transponder Initiate"},
+ {"DSR", "DSC Transponder Response"},
+ {"DTM", "Datum Reference"},
+ {"ETL", "Engine Telegraph Operation Status"},
+ {"EVE", "General Event Message"},
+ {"FIR", "Fire Detection"},
+ {"FSI", "Frequency Set Information"},
+ {"GBS", "GPS Satellite Fault Detection"},
+ {"GDA", "Dead Reckoning Positions"},
+ {"GGA", "Global Positioning System Fix Data"},
+ {"GLa", "Loran-C Positions"},
+ {"GLC", "Geographic Position, Loran-C"},
+ {"GLL", "Geographic Position - Latitude/Longitude"},
+ {"GNS", "Fix data"},
+ {"GOA", "OMEGA Positions"},
+ {"GRS", "GPS Range Residuals"},
+ {"GSA", "GPS DOP and active satellites"},
+ {"GST", "GPS Pseudorange Noise Statistics"},
+ {"GSV", "Satellites in view"},
+ {"GTD", "Geographic Location in Time Differences"},
+ {"GXA", "TRANSIT Position"},
+ {"HCC", "Compass Heading"},
+ {"HCD", "Heading and Deviation"},
+ {"HDG", "Heading - Deviation & Variation"},
+ {"HDM", "Heading - Magnetic"},
+ {"HDT", "Heading - True"},
+ {"HFB", "Trawl Headrope to Footrope and Bottom"},
+ {"HSC", "Heading Steering Command"},
+ {"HVD", "Magnetic Variation, Automatic"},
+ {"HVM", "Magnetic Variation, Manually Set"},
+ {"IMA", "Vessel Identification"},
+ {"ITS", "Trawl Door Spread 2 Distance"},
+ {"LCD", "Loran-C Signal Data"},
+ {"MDA", "Meteorological Composite"},
+ {"MHU", "Humidity"},
+ {"MMB", "Barometer"},
+ {"MSK", "Control for a Beacon Receiver"},
+ {"MSS", "Beacon Receiver Status"},
+ {"MTA", "Air Temperature"},
+ {"MTW", "Mean Temperature of Water"},
+ {"MWD", "Wind Direction & Speed"},
+ {"MWH", "Wave Height"},
+ {"MWS", "Wind & Sea State"},
+ {"MWV", "Wind Speed and Angle"},
+ {"OLN", "Omega Lane Numbers"},
+ {"OLW", "Omega Lane Width"},
+ {"OMP", "Omega Position"},
+ {"OSD", "Own Ship Data"},
+ {"OZN", "Omega Zone Number"},
+ {"R00", "Waypoints in active route"},
+ {"RLM", "Return Link Message"},
+ {"RMA", "Recommended Minimum Navigation Information"},
+ {"RMB", "Recommended Minimum Navigation Information"},
+ {"RMC", "Recommended Minimum Navigation Information"},
+ {"Rnn", "Routes"},
+ {"ROT", "Rate Of Turn"},
+ {"RPM", "Revolutions"},
+ {"RSA", "Rudder Sensor Angle"},
+ {"RSD", "RADAR System Data"},
+ {"RTE", "Routes"},
+ {"SBK", "Loran-C Blink Status"},
+ {"SCD", "Loran-C ECDs"},
+ {"SCY", "Loran-C Cycle Lock Status"},
+ {"SDB", "Loran-C Signal Strength"},
+ {"SFI", "Scanning Frequency Information"},
+ {"SGD", "Position Accuracy Estimate"},
+ {"SGR", "Loran-C Chain Identifier"},
+ {"SIU", "Loran-C Stations in Use"},
+ {"SLC", "Loran-C Status"},
+ {"SNC", "Navigation Calculation Basis"},
+ {"SNU", "Loran-C SNR Status"},
+ {"SPS", "Loran-C Predicted Signal Strength"},
+ {"SSF", "Position Correction Offset"},
+ {"STC", "Time Constant"},
+ {"STN", "Multiple Data ID"},
+ {"STR", "Tracking Reference"},
+ {"SYS", "Hybrid System Configuration"},
+ {"TDS", "Trawl Door Spread Distance"},
+ {"TEC", "TRANSIT Satellite Error Code & Doppler Count"},
+ {"TEP", "TRANSIT Satellite Predicted Elevation"},
+ {"TFI", "Trawl Filling Indicator"},
+ {"TGA", "TRANSIT Satellite Antenna & Geoidal Heights"},
+ {"TIF", "TRANSIT Satellite Initial Flag"},
+ {"TLB", "Target Label"},
+ {"TLL", "Target Latitude and Longitude"},
+ {"TPC", "Trawl Position Cartesian Coordinates"},
+ {"TPR", "Trawl Position Relative Vessel"},
+ {"TPT", "Trawl Position True"},
+ {"TRF", "TRANSIT Fix Data"},
+ {"TRP", "TRANSIT Satellite Predicted Direction of Rise"},
+ {"TRS", "TRANSIT Satellite Operating Status"},
+ {"TTM", "Tracked Target Message"},
+ {"VBW", "Dual Ground/Water Speed"},
+ {"VCD", "Current at Selected Depth"},
+ {"VDR", "Set and Drift"},
+ {"VHW", "Water speed and heading"},
+ {"VLW", "Distance Traveled through Water"},
+ {"VPE", "Speed, Dead Reckoned Parallel to True Wind"},
+ {"VPW", "Speed - Measured Parallel to Wind"},
+ {"VTA", "Actual Track"},
+ {"VTG", "Track made good and Ground speed"},
+ {"VTI", "Intended Track"},
+ {"VWE", "Wind Track Efficiency"},
+ {"VWR", "Relative Wind Speed and Angle"},
+ {"VWT", "True Wind Speed and Angle"},
+ {"WCV", "Waypoint Closure Velocity"},
+ {"WDC", "Distance to Waypoint - Great Circle"},
+ {"WDR", "Distance to Waypoint - Rhumb Line"},
+ {"WFM", "Route Following Mode"},
+ {"WNC", "Distance - Waypoint to Waypoint"},
+ {"WNR", "Waypoint-to-Waypoint Distance, Rhumb Line"},
+ {"WPL", "Waypoint Location"},
+ {"XDR", "Transducer Measurement"},
+ {"XTE", "Cross-Track Error, Measured"},
+ {"XTR", "Cross Track Error - Dead Reckoning"},
+ {"YWP", "Water Propagation Speed"},
+ {"YWS", "Water Profile"},
+ {"ZAA", "Time, Elapsed/Estimated"},
+ {"ZCD", "Timer"},
+ {"ZDA", "Time & Date - UTC, day, month, year and local time zone"},
+ {"ZDL", "Time and Distance to Variable Point"},
+ {"ZEV", "Event Timer"},
+ {"ZFO", "UTC & Time from origin Waypoint"},
+ {"ZLZ", "Time of Day"},
+ {"ZTG", "UTC & Time to Destination Waypoint"},
+ {"ZZU", "Time, UTC"},
+ {NULL, NULL}};
+
+// List of GPS Quality Indicator (Source: NMEA Revealed by Eric S. Raymond, https://gpsd.gitlab.io/gpsd/NMEA.html, retrieved 2023-01-26)
+static const string_string known_gps_quality_indicators[] = {
+ {"0", "Fix not available"},
+ {"1", "GPS fix"},
+ {"2", "Differential GPS fix"},
+ {"3", "PPS fix"},
+ {"4", "Real Time Kinematic"},
+ {"5", "Float Real Time Kinematic"},
+ {"6", "Estimated (dead reckoning)"},
+ {"7", "Manual input mode"},
+ {"8", "Simulation mode"},
+ {NULL, NULL}};
+
+// List of status indicators (Source: NMEA Revealed by Eric S. Raymond, https://gpsd.gitlab.io/gpsd/NMEA.html, retrieved 2024-04-19)
+static const string_string known_status_indicators[] = {
+ {"A", "Valid/Active"},
+ {"V", "Invalid/Void"},
+ {NULL, NULL}};
+
+// List of FAA Mode Indicator (Source: NMEA Revealed by Eric S. Raymond, https://gpsd.gitlab.io/gpsd/NMEA.html, retrieved 2024-04-19)
+static const string_string known_faa_mode_indicators[] = {
+ {"A", "Autonomous mode"},
+ {"C", "Quectel Querk, Caution"},
+ {"D", "Differential Mode"},
+ {"E", "Estimated (dead-reckoning) mode"},
+ {"F", "RTK Float mode"},
+ {"M", "Manual Input Mode"},
+ {"N", "Data Not Valid"},
+ {"P", "Precise"},
+ {"R", "RTK Integer mode"},
+ {"S", "Simulated Mode"},
+ {"U", "Quectel Querk, Unsafe"},
+ {NULL, NULL}};
+
+static uint8_t calculate_checksum(tvbuff_t *tvb, const int start, const int length)
+{
+ uint8_t checksum = 0;
+ for (int i = start; i < start + length; i++)
+ {
+ checksum ^= tvb_get_uint8(tvb, i);
+ }
+ return checksum;
+}
+
+/* Find first occurrence of a field separator in tvbuff, starting at offset. Searches
+ * to end of tvbuff.
+ * Returns the offset of the found separator.
+ * If separator is not found, return the offset of end of tvbuff.
+ * If offset is out of bounds, return the offset of end of tvbuff.
+ **/
+static int
+tvb_find_end_of_nmea0183_field(tvbuff_t *tvb, const int offset)
+{
+ if (tvb_captured_length_remaining(tvb, offset) == 0)
+ {
+ return tvb_captured_length(tvb);
+ }
+
+ int end_of_field_offset = tvb_find_uint8(tvb, offset, -1, ',');
+ if (end_of_field_offset == -1)
+ {
+ return tvb_captured_length(tvb);
+ }
+ return end_of_field_offset;
+}
+
+/* Add a zero length item which indicates an expected but missing field */
+static proto_item *
+proto_tree_add_missing_field(proto_tree *tree, packet_info *pinfo, int hf, tvbuff_t *tvb,
+ const int offset)
+{
+ proto_item *ti = NULL;
+ ti = proto_tree_add_item(tree, hf, tvb, offset, 0, ENC_ASCII);
+ proto_item_append_text(ti, "[missing]");
+ expert_add_info(pinfo, ti, &ei_nmea0183_field_missing);
+ return ti;
+}
+
+/* Dissect a time field. The field is split into a tree with hour, minute and second elements.
+ * Returns length including separator
+ **/
+static int
+dissect_nmea0183_field_time(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
+ int hf_time, int hf_hour, int hf_minute, int hf_second, int ett_time)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf_time, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ ti = proto_tree_add_item(tree, hf_time, tvb, offset, end_of_field_offset - offset, ENC_ASCII);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, ": [empty]");
+ }
+ else if (end_of_field_offset - offset >= 6)
+ {
+ const uint8_t *hour = NULL;
+ const uint8_t *minute = NULL;
+ const uint8_t *second = NULL;
+ proto_tree *time_subtree = proto_item_add_subtree(ti, ett_time);
+
+ proto_tree_add_item_ret_string(time_subtree, hf_hour,
+ tvb, offset, 2, ENC_ASCII,
+ pinfo->pool, &hour);
+
+ proto_tree_add_item_ret_string(time_subtree, hf_minute,
+ tvb, offset + 2, 2, ENC_ASCII,
+ pinfo->pool, &minute);
+
+ proto_tree_add_item_ret_string(time_subtree, hf_second,
+ tvb, offset + 4, end_of_field_offset - offset - 4,
+ ENC_ASCII, pinfo->pool, &second);
+
+ proto_item_append_text(ti, ": %s:%s:%s", hour, minute, second);
+ }
+ else
+ {
+ expert_add_info(pinfo, ti, &ei_nmea0183_field_time_too_short);
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a single field containing a dimensionless value. Returns length including separator */
+static int
+dissect_nmea0183_field(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int hf, const char *suffix)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ ti = proto_tree_add_item(tree, hf, tvb, offset, end_of_field_offset - offset, ENC_ASCII);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+ }
+ else if (suffix != NULL)
+ {
+ proto_item_append_text(ti, " %s", suffix);
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a latitude/longitude direction field.
+ * Returns length including separator
+ **/
+static int
+dissect_nmea0183_field_latlong_direction(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
+ int offset, int hf,
+ wmem_allocator_t *scope, const uint8_t **retval)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ proto_item *ti = proto_tree_add_item_ret_string(tree, hf,
+ tvb, offset, end_of_field_offset - offset, ENC_ASCII,
+ scope, retval);
+ if (end_of_field_offset - offset == 0)
+ {
+ if (retval == NULL)
+ {
+ proto_item_append_text(ti, "[empty]");
+ }
+ else
+ {
+ proto_item_append_text(ti, "[missing]");
+ expert_add_info(pinfo, ti, &ei_nmea0183_field_missing);
+ }
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a latitude field + direction field. The fields are split into a tree with degree, minute and direction elements.
+ * Returns length including separator
+ **/
+static int
+dissect_nmea0183_field_latitude(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
+ int hf_latitude, int hf_degree, int hf_minute, int hf_direction, int ett_latitude)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf_latitude, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ ti = proto_tree_add_item(tree, hf_latitude, tvb, offset, end_of_field_offset - offset, ENC_ASCII);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+
+ end_of_field_offset += dissect_nmea0183_field_latlong_direction(tvb, pinfo, tree, end_of_field_offset + 1, hf_direction, NULL, NULL);
+ }
+ else if (end_of_field_offset - offset >= 4)
+ {
+ const uint8_t *degree = NULL;
+ const uint8_t *minute = NULL;
+ const uint8_t *direction = NULL;
+ proto_tree *latitude_subtree = proto_item_add_subtree(ti, ett_latitude);
+
+ proto_tree_add_item_ret_string(latitude_subtree, hf_degree,
+ tvb, offset, 2,
+ ENC_ASCII, pinfo->pool, &degree);
+
+ proto_tree_add_item_ret_string(latitude_subtree, hf_minute,
+ tvb, offset + 2, end_of_field_offset - offset - 2,
+ ENC_ASCII, pinfo->pool, &minute);
+
+ end_of_field_offset += dissect_nmea0183_field_latlong_direction(tvb, pinfo, latitude_subtree, end_of_field_offset + 1, hf_direction, pinfo->pool, &direction);
+
+ proto_item_append_text(ti, ": %s° %s' %s", degree, minute, direction);
+ }
+ else
+ {
+ expert_add_info(pinfo, ti, &ei_nmea0183_field_latitude_too_short);
+
+ end_of_field_offset += dissect_nmea0183_field_latlong_direction(tvb, pinfo, tree, end_of_field_offset + 1, hf_direction, NULL, NULL);
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a longitude field + direction field. The fields are split into a tree with degree, minute and direction elements.
+ * Returns length including separator
+ **/
+static int
+dissect_nmea0183_field_longitude(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
+ int hf_longitude, int hf_degree, int hf_minute, int hf_direction, int ett_latitude)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf_longitude, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ ti = proto_tree_add_item(tree, hf_longitude, tvb, offset, end_of_field_offset - offset, ENC_ASCII);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+
+ end_of_field_offset += dissect_nmea0183_field_latlong_direction(tvb, pinfo, tree, end_of_field_offset + 1, hf_direction, NULL, NULL);
+ }
+ else if (end_of_field_offset - offset >= 5)
+ {
+ const uint8_t *degree = NULL;
+ const uint8_t *minute = NULL;
+ const uint8_t *direction = NULL;
+ proto_tree *longitude_subtree = proto_item_add_subtree(ti, ett_latitude);
+
+ proto_tree_add_item_ret_string(longitude_subtree, hf_degree,
+ tvb, offset, 3,
+ ENC_ASCII, pinfo->pool, &degree);
+
+ proto_tree_add_item_ret_string(longitude_subtree, hf_minute,
+ tvb, offset + 3, end_of_field_offset - offset - 3,
+ ENC_ASCII, pinfo->pool, &minute);
+
+ end_of_field_offset += dissect_nmea0183_field_latlong_direction(tvb, pinfo, longitude_subtree, end_of_field_offset + 1, hf_direction, pinfo->pool, &direction);
+
+ proto_item_append_text(ti, ": %s° %s' %s", degree, minute, direction);
+ }
+ else
+ {
+ expert_add_info(pinfo, ti, &ei_nmea0183_field_longitude_too_short);
+
+ end_of_field_offset += dissect_nmea0183_field_latlong_direction(tvb, pinfo, tree, end_of_field_offset + 1, hf_direction, NULL, NULL);
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a required gps quality field. Returns length including separator */
+static int
+dissect_nmea0183_field_gps_quality(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int hf)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ const uint8_t *quality = NULL;
+ ti = proto_tree_add_item_ret_string(tree, hf,
+ tvb, offset, end_of_field_offset - offset, ENC_ASCII,
+ pinfo->pool, &quality);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[missing]");
+ expert_add_info(pinfo, ti, &ei_nmea0183_field_missing);
+ }
+ else
+ {
+ proto_item_append_text(ti, " (%s)", str_to_str(quality, known_gps_quality_indicators, "Unknown quality"));
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a single field containing a fixed text.
+ The text of the field must match the `expected_text` or expert info `invalid_ei` is
+ added to the field. An empty field is allowed. Returns length including separator */
+static int
+dissect_nmea0183_field_fixed_text(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int hf,
+ const uint8_t *expected_text, expert_field *invalid_ei)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ const uint8_t *text = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ ti = proto_tree_add_item_ret_string(tree, hf,
+ tvb, offset, end_of_field_offset - offset, ENC_ASCII,
+ pinfo->pool, &text);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+ }
+ else if (g_ascii_strcasecmp(text, expected_text) != 0)
+ {
+ expert_add_info(pinfo, ti, invalid_ei);
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a optional FAA mode indicator field. Returns length including separator */
+static int
+dissect_nmea0183_field_faa_mode(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int hf)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ const uint8_t *mode = NULL;
+ ti = proto_tree_add_item_ret_string(tree, hf,
+ tvb, offset, end_of_field_offset - offset, ENC_ASCII,
+ pinfo->pool, &mode);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+ }
+ else
+ {
+ proto_item_append_text(ti, " (%s)", str_to_str(mode, known_faa_mode_indicators, "Unknown FAA mode"));
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a optional A/V status field. Returns length including separator */
+static int
+dissect_nmea0183_field_status(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, int hf)
+{
+ if (offset > (int)tvb_captured_length(tvb))
+ {
+ proto_tree_add_missing_field(tree, pinfo, hf, tvb, tvb_captured_length(tvb));
+ return 0;
+ }
+
+ proto_item *ti = NULL;
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ const uint8_t *mode = NULL;
+ ti = proto_tree_add_item_ret_string(tree, hf,
+ tvb, offset, end_of_field_offset - offset, ENC_ASCII,
+ pinfo->pool, &mode);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+ }
+ else
+ {
+ proto_item_append_text(ti, " (%s)", str_to_str(mode, known_status_indicators, "Unknown status"));
+ }
+ return end_of_field_offset - offset + 1;
+}
+
+/* Dissect a DPT sentence. */
+static int
+dissect_nmea0183_sentence_dpt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence, NULL, "DPT sentence - Depth of Water");
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_dpt_depth, "meter");
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_dpt_offset, "meter");
+
+ dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_dpt_max_range, "meter");
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a GGA sentence. The time, latitude and longitude fields is split into individual parts. */
+static int
+dissect_nmea0183_sentence_gga(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "GGA sentence - Global Positioning System Fix");
+
+ offset += dissect_nmea0183_field_time(tvb, pinfo, subtree, offset, hf_nmea0183_gga_time,
+ hf_nmea0183_gga_time_hour, hf_nmea0183_gga_time_minute,
+ hf_nmea0183_gga_time_second, ett_nmea0183_gga_time);
+
+ offset += dissect_nmea0183_field_latitude(tvb, pinfo, subtree, offset, hf_nmea0183_gga_latitude,
+ hf_nmea0183_gga_latitude_degree, hf_nmea0183_gga_latitude_minute,
+ hf_nmea0183_gga_latitude_direction, ett_nmea0183_gga_latitude);
+
+ offset += dissect_nmea0183_field_longitude(tvb, pinfo, subtree, offset, hf_nmea0183_gga_longitude,
+ hf_nmea0183_gga_longitude_degree, hf_nmea0183_gga_longitude_minute,
+ hf_nmea0183_gga_longitude_direction, ett_nmea0183_gga_longitude);
+
+ offset += dissect_nmea0183_field_gps_quality(tvb, pinfo, subtree, offset, hf_nmea0183_gga_quality);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_gga_number_satellites, NULL);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_gga_horizontal_dilution, "meter");
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_gga_altitude, "meter");
+
+ offset += dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_gga_altitude_unit,
+ "M", &ei_nmea0183_gga_altitude_unit_incorrect);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_gga_geoidal_separation, "meter");
+
+ offset += dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_gga_geoidal_separation_unit,
+ "M", &ei_nmea0183_gga_geoidal_separation_unit_incorrect);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_gga_age_dgps, "second");
+
+ dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_gga_dgps_station, NULL);
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a GLL sentence. The latitude, longitude and time fields is split into individual parts. */
+static int
+dissect_nmea0183_sentence_gll(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "GLL sentence - Geographic Position");
+
+ offset += dissect_nmea0183_field_latitude(tvb, pinfo, subtree, offset, hf_nmea0183_gll_latitude,
+ hf_nmea0183_gll_latitude_degree, hf_nmea0183_gll_latitude_minute,
+ hf_nmea0183_gll_latitude_direction, ett_nmea0183_gll_latitude);
+
+ offset += dissect_nmea0183_field_longitude(tvb, pinfo, subtree, offset, hf_nmea0183_gll_longitude,
+ hf_nmea0183_gll_longitude_degree, hf_nmea0183_gll_longitude_minute,
+ hf_nmea0183_gll_longitude_direction, ett_nmea0183_gll_longitude);
+
+ offset += dissect_nmea0183_field_time(tvb, pinfo, subtree, offset, hf_nmea0183_gll_time,
+ hf_nmea0183_gll_time_hour, hf_nmea0183_gll_time_minute,
+ hf_nmea0183_gll_time_second, ett_nmea0183_gll_time);
+
+ offset += dissect_nmea0183_field_status(tvb, pinfo, subtree, offset, hf_nmea0183_gll_status);
+
+ dissect_nmea0183_field_faa_mode(tvb, pinfo, subtree, offset, hf_nmea0183_gll_mode);
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a HDT sentence. */
+static int
+dissect_nmea0183_sentence_hdt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "HDT sentence - True Heading");
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_hdt_heading, "degree");
+
+ dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_hdt_unit,
+ "T", &ei_nmea0183_hdt_unit_incorrect);
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a ROT sentence. */
+static int
+dissect_nmea0183_sentence_rot(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "ROT sentence - Rate Of Turn");
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_rot_rate_of_turn, "degree per minute");
+
+ dissect_nmea0183_field_status(tvb, pinfo, subtree, offset, hf_nmea0183_rot_valid);
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a VHW sentence. */
+static int
+dissect_nmea0183_sentence_vhw(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "VHW sentence - Water speed and heading");
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_true_heading, "degree");
+
+ offset += dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_true_heading_unit,
+ "T", &ei_nmea0183_vhw_true_heading_unit_incorrect);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_magnetic_heading, "degree");
+
+ offset += dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_magnetic_heading_unit,
+ "M", &ei_nmea0183_vhw_magnetic_heading_unit_incorrect);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_water_speed_knot, "knot");
+
+ offset += dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_water_speed_knot_unit,
+ "N", &ei_nmea0183_vhw_water_speed_knot_unit_incorrect);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_water_speed_kilometer, "kilometer per hour");
+
+ dissect_nmea0183_field_fixed_text(tvb, pinfo, subtree, offset, hf_nmea0183_vhw_water_speed_kilometer_unit,
+ "K", &ei_nmea0183_vhw_water_speed_kilometer_unit_incorrect);
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a ZDA (Time & Date) sentence. The time field is split into individual parts. */
+static int
+dissect_nmea0183_sentence_zda(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ int offset = 0;
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "ZDA sentence - Time & Date");
+
+ offset += dissect_nmea0183_field_time(tvb, pinfo, subtree, offset, hf_nmea0183_zda_time,
+ hf_nmea0183_zda_time_hour, hf_nmea0183_zda_time_minute,
+ hf_nmea0183_zda_time_second, ett_nmea0183_zda_time);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_zda_date_day, NULL);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_zda_date_month, NULL);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_zda_date_year, NULL);
+
+ offset += dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_zda_local_zone_hour, NULL);
+
+ dissect_nmea0183_field(tvb, pinfo, subtree, offset, hf_nmea0183_zda_local_zone_minute, NULL);
+
+ return tvb_captured_length(tvb);
+}
+
+/* Dissect a sentence where the sentence id is unknown. Each field is shown as an generic field. */
+static int
+dissect_nmea0183_sentence_unknown(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree)
+{
+ int offset = 0;
+
+ proto_tree *subtree = proto_tree_add_subtree(tree, tvb, offset,
+ tvb_captured_length(tvb), ett_nmea0183_sentence,
+ NULL, "Unknown sentence");
+
+ /* In an unknown sentence, the name of each field is unknown. Find all field by splitting at a comma. */
+ while (tvb_captured_length_remaining(tvb, offset) > 0)
+ {
+ int end_of_field_offset = tvb_find_end_of_nmea0183_field(tvb, offset);
+ proto_item *ti = proto_tree_add_item(subtree, hf_nmea0183_unknown_field,
+ tvb, offset, end_of_field_offset - offset, ENC_ASCII);
+ if (end_of_field_offset - offset == 0)
+ {
+ proto_item_append_text(ti, "[empty]");
+ }
+ offset = end_of_field_offset + 1;
+ }
+ return tvb_captured_length(tvb);
+}
+
+static int
+dissect_nmea0183(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
+{
+ int offset = 0;
+ int start_checksum_offset = 0;
+ const uint8_t *talker_id = NULL;
+ const uint8_t *sentence_id = NULL;
+ const uint8_t *checksum = NULL;
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "NMEA 0183");
+ /* Clear the info column */
+ col_clear(pinfo->cinfo, COL_INFO);
+
+ proto_item *ti = proto_tree_add_item(tree, proto_nmea0183, tvb, 0, -1, ENC_NA);
+ proto_tree *nmea0183_tree = proto_item_add_subtree(ti, ett_nmea0183);
+
+ /* Start delimiter */
+ if (tvb_get_uint8(tvb, offset) != '$')
+ {
+ expert_add_info(pinfo, nmea0183_tree, &ei_nmea0183_invalid_first_character);
+ }
+ offset += 1;
+
+ /* Talker id */
+ ti = proto_tree_add_item_ret_string(nmea0183_tree, hf_nmea0183_talker_id,
+ tvb, offset, 2, ENC_ASCII,
+ pinfo->pool, &talker_id);
+
+ proto_item_append_text(ti, " (%s)", str_to_str(talker_id, known_talker_ids, "Unknown talker ID"));
+
+ col_append_fstr(pinfo->cinfo, COL_INFO, "Talker %s", talker_id);
+
+ offset += 2;
+
+ /* Sentence id */
+ ti = proto_tree_add_item_ret_string(nmea0183_tree, hf_nmea0183_sentence_id,
+ tvb, offset, 3, ENC_ASCII,
+ pinfo->pool, &sentence_id);
+
+ proto_item_append_text(ti, " (%s)", str_to_str(sentence_id, known_sentence_ids, "Unknown sentence ID"));
+
+ col_append_fstr(pinfo->cinfo, COL_INFO, ", Sentence %s", sentence_id);
+
+ offset += 3;
+
+ /* Start of checksum */
+ start_checksum_offset = tvb_find_uint8(tvb, offset, -1, '*');
+ if (start_checksum_offset == -1)
+ {
+ expert_add_info(pinfo, nmea0183_tree, &ei_nmea0183_missing_checksum_character);
+ return tvb_captured_length(tvb);
+ }
+
+ /* Data */
+ offset += 1;
+ tvbuff_t *data_tvb = tvb_new_subset_length(tvb, offset, start_checksum_offset - offset);
+ if (g_ascii_strcasecmp(sentence_id, "DPT") == 0)
+ {
+ offset += dissect_nmea0183_sentence_dpt(data_tvb, pinfo, nmea0183_tree);
+ }
+ else if (g_ascii_strcasecmp(sentence_id, "GGA") == 0)
+ {
+ offset += dissect_nmea0183_sentence_gga(data_tvb, pinfo, nmea0183_tree);
+ }
+ else if (g_ascii_strcasecmp(sentence_id, "GLL") == 0)
+ {
+ offset += dissect_nmea0183_sentence_gll(data_tvb, pinfo, nmea0183_tree);
+ }
+ else if (g_ascii_strcasecmp(sentence_id, "HDT") == 0)
+ {
+ offset += dissect_nmea0183_sentence_hdt(data_tvb, pinfo, nmea0183_tree);
+ }
+ else if (g_ascii_strcasecmp(sentence_id, "ROT") == 0)
+ {
+ offset += dissect_nmea0183_sentence_rot(data_tvb, pinfo, nmea0183_tree);
+ }
+ else if (g_ascii_strcasecmp(sentence_id, "VHW") == 0)
+ {
+ offset += dissect_nmea0183_sentence_vhw(data_tvb, pinfo, nmea0183_tree);
+ }
+ else if (g_ascii_strcasecmp(sentence_id, "ZDA") == 0)
+ {
+ offset += dissect_nmea0183_sentence_zda(data_tvb, pinfo, nmea0183_tree);
+ }
+ else
+ {
+ offset += dissect_nmea0183_sentence_unknown(data_tvb, pinfo, nmea0183_tree);
+ }
+
+ /* Checksum */
+ offset += 1;
+ ti = proto_tree_add_item_ret_string(nmea0183_tree, hf_nmea0183_checksum,
+ tvb, offset, 2, ENC_ASCII,
+ pinfo->pool, &checksum);
+
+ uint8_t received_checksum = (uint8_t)strtol(checksum, NULL, 16);
+ uint8_t calculated_checksum = calculate_checksum(tvb, 1, offset - 2);
+ if (received_checksum == calculated_checksum)
+ {
+ proto_item_append_text(ti, " [correct]");
+ }
+ else
+ {
+ proto_item_append_text(ti, " [INCORRECT]");
+ expert_add_info(pinfo, ti, &ei_nmea0183_checksum_incorrect);
+ }
+
+ // Calculated checksum highlights 2 bytes, which is the ascii hex value of a 1 byte checksum
+ proto_item *checksum_tree = proto_item_add_subtree(ti, ett_nmea0183_checksum);
+ ti = proto_tree_add_uint(checksum_tree, hf_nmea0183_checksum_calculated,
+ tvb, offset, 2, calculated_checksum);
+ proto_item_set_generated(ti);
+
+ offset += 2;
+
+ /* End of line */
+ if (tvb_captured_length_remaining(tvb, offset) < 2 ||
+ tvb_get_uint8(tvb, offset) != '\r' ||
+ tvb_get_uint8(tvb, offset + 1) != '\n')
+ {
+ expert_add_info(pinfo, nmea0183_tree, &ei_nmea0183_invalid_end_of_line);
+ }
+ offset += 2;
+
+ /* Check sentence length */
+ if (offset > 82)
+ {
+ expert_add_info(pinfo, nmea0183_tree, &ei_nmea0183_sentence_too_long);
+ }
+
+ return tvb_captured_length(tvb);
+}
+
+void proto_register_nmea0183(void)
+{
+ expert_module_t *expert_nmea0183;
+
+ static hf_register_info hf[] = {
+ {&hf_nmea0183_talker_id,
+ {"Talker ID", "nmea0183.talker",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 Talker ID", HFILL}},
+ {&hf_nmea0183_sentence_id,
+ {"Sentence ID", "nmea0183.sentence",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 Sentence ID", HFILL}},
+ {&hf_nmea0183_unknown_field,
+ {"Field", "nmea0183.unknown_field",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 Unknown field", HFILL}},
+ {&hf_nmea0183_checksum,
+ {"Checksum", "nmea0183.checksum",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 Checksum", HFILL}},
+ {&hf_nmea0183_checksum_calculated,
+ {"Calculated checksum", "nmea0183.checksum_calculated",
+ FT_UINT8, BASE_HEX,
+ NULL, 0x0,
+ "NMEA 0183 Calculated checksum", HFILL}},
+ {&hf_nmea0183_dpt_depth,
+ {"Water depth", "nmea0183.dpt_depth",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 DPT Water depth relative to transducer", HFILL}},
+ {&hf_nmea0183_dpt_offset,
+ {"Offset", "nmea0183.dpt_offset",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 DPT Offset from transducer, positive means distance from transducer to water line, negative means distance from transducer to keel", HFILL}},
+ {&hf_nmea0183_dpt_max_range,
+ {"Maximum range", "nmea0183.dpt_max_range",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 DPT Maximum range scale in use (NMEA 3.0 and above)", HFILL}},
+ {&hf_nmea0183_gga_time,
+ {"UTC Time of position", "nmea0183.gga_time",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA UTC Time field", HFILL}},
+ {&hf_nmea0183_gga_time_hour,
+ {"Hour", "nmea0183.gga_time_hour",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA UTC hour", HFILL}},
+ {&hf_nmea0183_gga_time_minute,
+ {"Minute", "nmea0183.gga_time_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA UTC minute", HFILL}},
+ {&hf_nmea0183_gga_time_second,
+ {"Second", "nmea0183.gga_time_second",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA UTC second", HFILL}},
+ {&hf_nmea0183_gga_latitude,
+ {"Latitude", "nmea0183.gga_latitude",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Latitude field", HFILL}},
+ {&hf_nmea0183_gga_latitude_degree,
+ {"Degree", "nmea0183.gga_latitude_degree",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Latitude Degree", HFILL}},
+ {&hf_nmea0183_gga_latitude_minute,
+ {"Minute", "nmea0183.gga_latitude_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Latitude Minute", HFILL}},
+ {&hf_nmea0183_gga_latitude_direction,
+ {"Direction", "nmea0183.gga_latitude_direction",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Latitude Direction", HFILL}},
+ {&hf_nmea0183_gga_longitude,
+ {"Longitude", "nmea0183.gga_longitude",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Longitude field", HFILL}},
+ {&hf_nmea0183_gga_longitude_degree,
+ {"Degree", "nmea0183.gga_longitude_degree",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Longitude Degree", HFILL}},
+ {&hf_nmea0183_gga_longitude_minute,
+ {"Minute", "nmea0183.gga_longitude_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Longitude Minute", HFILL}},
+ {&hf_nmea0183_gga_longitude_direction,
+ {"Direction", "nmea0183.gga_longitude_direction",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Longitude Direction", HFILL}},
+ {&hf_nmea0183_gga_quality,
+ {"Quality indicator", "nmea0183.gga_quality",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Quality indicator", HFILL}},
+ {&hf_nmea0183_gga_number_satellites,
+ {"Number of satellites", "nmea0183.gga_number_satellites",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Number of satellites in use", HFILL}},
+ {&hf_nmea0183_gga_horizontal_dilution,
+ {"Horizontal Dilution", "nmea0183.gga_horizontal_dilution",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Horizontal Dilution of precision", HFILL}},
+ {&hf_nmea0183_gga_altitude,
+ {"Altitude", "nmea0183.gga_altitude",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Antenna Altitude above mean-sea-level", HFILL}},
+ {&hf_nmea0183_gga_altitude_unit,
+ {"Altitude unit", "nmea0183.gga_altitude_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Units of antenna altitude", HFILL}},
+ {&hf_nmea0183_gga_geoidal_separation,
+ {"Geoidal separation", "nmea0183.gga_geoidal_separation",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Geoidal separation, the difference between the WGS-84 earth ellipsoid and mean-sea-level", HFILL}},
+ {&hf_nmea0183_gga_geoidal_separation_unit,
+ {"Geoidal separation unit", "nmea0183.gga_geoidal_separation_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Units of geoidal separation, meters", HFILL}},
+ {&hf_nmea0183_gga_age_dgps,
+ {"Age of differential GPS", "nmea0183.gga_age_dgps",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Age of differential GPS data in seconds", HFILL}},
+ {&hf_nmea0183_gga_dgps_station,
+ {"Differential GPS station id", "nmea0183.gga_dgps_station",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GGA Differential reference station ID", HFILL}},
+ {&hf_nmea0183_gll_latitude,
+ {"Latitude", "nmea0183.gll_latitude",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Latitude field", HFILL}},
+ {&hf_nmea0183_gll_latitude_degree,
+ {"Degree", "nmea0183.gll_latitude_degree",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Latitude Degree", HFILL}},
+ {&hf_nmea0183_gll_latitude_minute,
+ {"Minute", "nmea0183.gll_latitude_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Latitude Minute", HFILL}},
+ {&hf_nmea0183_gll_latitude_direction,
+ {"Direction", "nmea0183.gll_latitude_direction",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Latitude Direction", HFILL}},
+ {&hf_nmea0183_gll_longitude,
+ {"Longitude", "nmea0183.gll_longitude",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Longitude field", HFILL}},
+ {&hf_nmea0183_gll_longitude_degree,
+ {"Degree", "nmea0183.gll_longitude_degree",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Longitude Degree", HFILL}},
+ {&hf_nmea0183_gll_longitude_minute,
+ {"Minute", "nmea0183.gll_longitude_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Longitude Minute", HFILL}},
+ {&hf_nmea0183_gll_longitude_direction,
+ {"Direction", "nmea0183.gll_longitude_direction",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Longitude Direction", HFILL}},
+ {&hf_nmea0183_gll_time,
+ {"UTC Time of position", "nmea0183.gll_time",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL UTC Time field", HFILL}},
+ {&hf_nmea0183_gll_time_hour,
+ {"Hour", "nmea0183.gll_time_hour",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL UTC hour", HFILL}},
+ {&hf_nmea0183_gll_time_minute,
+ {"Minute", "nmea0183.gll_time_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL UTC minute", HFILL}},
+ {&hf_nmea0183_gll_time_second,
+ {"Second", "nmea0183.gll_time_second",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL UTC second", HFILL}},
+ {&hf_nmea0183_gll_status,
+ {"Status", "nmea0183.gll_status",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL Status", HFILL}},
+ {&hf_nmea0183_gll_mode,
+ {"FAA mode", "nmea0183.gll_mode",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 GLL FAA mode indicator (NMEA 2.3 and later)", HFILL}},
+ {&hf_nmea0183_hdt_heading,
+ {"True heading", "nmea0183.hdt_heading",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 HDT Heading, degrees True", HFILL}},
+ {&hf_nmea0183_hdt_unit,
+ {"Heading unit", "nmea0183.hdt_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 HDT Heading unit, must be T", HFILL}},
+ {&hf_nmea0183_rot_rate_of_turn,
+ {"Rate of turn", "nmea0183.rot_rate_of_turn",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ROT Rate Of Turn, degrees per minute, negative value means bow turns to port", HFILL}},
+ {&hf_nmea0183_rot_valid,
+ {"Validity", "nmea0183.rot_valid",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ROT Status, A means data is valid", HFILL}},
+ {&hf_nmea0183_vhw_true_heading,
+ {"True heading", "nmea0183.vhw_true_heading",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Heading, degrees True", HFILL}},
+ {&hf_nmea0183_vhw_true_heading_unit,
+ {"Heading unit", "nmea0183.vhw_true_heading_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Heading unit, must be T", HFILL}},
+ {&hf_nmea0183_vhw_magnetic_heading,
+ {"Magnetic heading", "nmea0183.vhw_magnetic_heading",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Heading, degrees Magnetic", HFILL}},
+ {&hf_nmea0183_vhw_magnetic_heading_unit,
+ {"Heading unit", "nmea0183.vhw_magnetic_heading_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Heading unit, must be M", HFILL}},
+ {&hf_nmea0183_vhw_water_speed_knot,
+ {"Water speed", "nmea0183.vhw_water_speed_knot",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Water speed, knots", HFILL}},
+ {&hf_nmea0183_vhw_water_speed_knot_unit,
+ {"Speed unit", "nmea0183.vhw_water_speed_knot_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Water speed unit, must be N", HFILL}},
+ {&hf_nmea0183_vhw_water_speed_kilometer,
+ {"Water speed", "nmea0183.vhw_water_speed_kilometer",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Water speed, kilometers per hour", HFILL}},
+ {&hf_nmea0183_vhw_water_speed_kilometer_unit,
+ {"Speed unit", "nmea0183.vhw_water_speed_kilometer_unit",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 VHW Water speed unit, must be K", HFILL}},
+ {&hf_nmea0183_zda_time,
+ {"UTC Time", "nmea0183.zda_time",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA UTC Time field", HFILL}},
+ {&hf_nmea0183_zda_time_hour,
+ {"Hour", "nmea0183.zda_time_hour",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA UTC hour", HFILL}},
+ {&hf_nmea0183_zda_time_minute,
+ {"Minute", "nmea0183.zda_time_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA UTC minute", HFILL}},
+ {&hf_nmea0183_zda_time_second,
+ {"Second", "nmea0183.zda_time_second",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA UTC second", HFILL}},
+ {&hf_nmea0183_zda_date_day,
+ {"Day", "nmea0183.zda_date_day",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA Day field", HFILL}},
+ {&hf_nmea0183_zda_date_month,
+ {"Month", "nmea0183.zda_date_month",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA Month field", HFILL}},
+ {&hf_nmea0183_zda_date_year,
+ {"Year", "nmea0183.zda_date_year",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA Year field", HFILL}},
+ {&hf_nmea0183_zda_local_zone_hour,
+ {"Local zone hour", "nmea0183.zda_local_zone_hour",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA Local zone hour field", HFILL}},
+ {&hf_nmea0183_zda_local_zone_minute,
+ {"Local zone minute", "nmea0183.zda_local_zone_minute",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ "NMEA 0183 ZDA Local zone minute field", HFILL}}};
+
+ /* Setup protocol subtree array */
+ static int *ett[] = {
+ &ett_nmea0183,
+ &ett_nmea0183_checksum,
+ &ett_nmea0183_sentence,
+ &ett_nmea0183_zda_time,
+ &ett_nmea0183_gga_time,
+ &ett_nmea0183_gga_latitude,
+ &ett_nmea0183_gga_longitude,
+ &ett_nmea0183_gll_time,
+ &ett_nmea0183_gll_latitude,
+ &ett_nmea0183_gll_longitude};
+
+ static ei_register_info ei[] = {
+ {&ei_nmea0183_invalid_first_character,
+ {"nmea0183.invalid_first_character", PI_PROTOCOL, PI_WARN,
+ "First character should be '$'", EXPFILL}},
+ {&ei_nmea0183_missing_checksum_character,
+ {"nmea0183.missing_checksum_character", PI_MALFORMED, PI_ERROR,
+ "Missing begin of checksum character '*'", EXPFILL}},
+ {&ei_nmea0183_invalid_end_of_line,
+ {"nmea0183.invalid_end_of_line", PI_PROTOCOL, PI_WARN,
+ "Sentence should end with <CR><LF>", EXPFILL}},
+ {&ei_nmea0183_checksum_incorrect,
+ {"nmea0183.checksum_incorrect", PI_CHECKSUM, PI_WARN,
+ "Incorrect checksum", EXPFILL}},
+ {&ei_nmea0183_sentence_too_long,
+ {"nmea0183.sentence_too_long", PI_PROTOCOL, PI_WARN,
+ "Sentence is too long. Maximum is 82 bytes including $ and <CR><LF>", EXPFILL}},
+ {&ei_nmea0183_field_time_too_short,
+ {"nmea0183.field_time_too_short", PI_PROTOCOL, PI_WARN,
+ "Field containing time is too short. Field should be at least 6 characters", EXPFILL}},
+ {&ei_nmea0183_field_latitude_too_short,
+ {"nmea0183.field_latitude_too_short", PI_PROTOCOL, PI_WARN,
+ "Field containing latitude is too short. Field should be at least 4 characters", EXPFILL}},
+ {&ei_nmea0183_field_longitude_too_short,
+ {"nmea0183.field_longitude_too_short", PI_PROTOCOL, PI_WARN,
+ "Field containing longitude is too short. Field should be at least 5 characters", EXPFILL}},
+ {&ei_nmea0183_field_missing,
+ {"nmea0183.field_missing", PI_PROTOCOL, PI_WARN,
+ "Field expected, but not found", EXPFILL}},
+ {&ei_nmea0183_gga_altitude_unit_incorrect,
+ {"nmea0183.gga_altitude_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect altitude unit (should be 'M')", EXPFILL}},
+ {&ei_nmea0183_gga_geoidal_separation_unit_incorrect,
+ {"nmea0183.gga_geoidal_separation_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect geoidal separation unit (should be 'M')", EXPFILL}},
+ {&ei_nmea0183_hdt_unit_incorrect,
+ {"nmea0183.hdt_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect heading unit (should be 'T')", EXPFILL}},
+ {&ei_nmea0183_vhw_true_heading_unit_incorrect,
+ {"nmea0183.vhw_true_heading_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect heading unit (should be 'T')", EXPFILL}},
+ {&ei_nmea0183_vhw_magnetic_heading_unit_incorrect,
+ {"nmea0183.vhw_magnetic_heading_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect heading unit (should be 'M')", EXPFILL}},
+ {&ei_nmea0183_vhw_water_speed_knot_unit_incorrect,
+ {"nmea0183.vhw_water_speed_knot_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect speed unit (should be 'N')", EXPFILL}},
+ {&ei_nmea0183_vhw_water_speed_kilometer_unit_incorrect,
+ {"nmea0183.vhw_water_speed_kilometer_unit_incorrect", PI_PROTOCOL, PI_WARN,
+ "Incorrect speed unit (should be 'K')", EXPFILL}}};
+
+ proto_nmea0183 = proto_register_protocol("NMEA 0183 protocol", "NMEA 0183", "nmea0183");
+
+ proto_register_field_array(proto_nmea0183, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+ expert_nmea0183 = expert_register_protocol(proto_nmea0183);
+ expert_register_field_array(expert_nmea0183, ei, array_length(ei));
+
+ nmea0183_handle = register_dissector("nmea0183", dissect_nmea0183, proto_nmea0183);
+}
+
+void proto_reg_handoff_nmea0183(void)
+{
+ dissector_add_for_decode_as_with_preference("udp.port", nmea0183_handle);
+}