forked from osmocom/wireshark
![Jaap Keuter](/assets/img/avatar_default.png)
Add support for the DIS protocol dissector to more fully interpret the PDU types "Data Query", "Data", and "Set Data". It should interpret the originating and receiving entity IDs, request ID, number of fixed and variable datum fields, and the ID and values of each datum field in the PDU. svn path=/trunk/; revision=28583
577 lines
16 KiB
C
577 lines
16 KiB
C
/* packet-dis-fields.c
|
|
* Routines and definitions for DIS field parsing.
|
|
* Copyright 2005, Scientific Research Corporation
|
|
* Initial implementation by Jeremy Ouellette <jouellet@scires.com>
|
|
*
|
|
* $Id$
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <epan/packet.h>
|
|
#include "packet-dis-fields.h"
|
|
#include "packet-dis-enums.h"
|
|
|
|
guint32 pduType;
|
|
guint32 entityKind;
|
|
guint32 entityDomain;
|
|
guint32 variableDatumLength;
|
|
|
|
DIS_ParserNode DIS_FIELDS_PDU_HEADER[] =
|
|
{
|
|
{ DIS_FIELDTYPE_PROTOCOL_VERSION, "Protocol Version",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT8, "Exercise ID",0,0,0 },
|
|
{ DIS_FIELDTYPE_PDU_TYPE, "PDU Type",0,0,&pduType },
|
|
{ DIS_FIELDTYPE_PROTOCOL_FAMILY, "Protocol Family",0,0,0 },
|
|
{ DIS_FIELDTYPE_TIMESTAMP, "Timestamp",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Length",0,0,0 },
|
|
{ DIS_FIELDTYPE_PAD16, "Padding",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_ENTITY_ID[] =
|
|
{
|
|
{ DIS_FIELDTYPE_UINT16, "Site",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Application",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Entity",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_ENTITY_TYPE[] =
|
|
{
|
|
{ DIS_FIELDTYPE_ENTITY_KIND, "Entity Kind",0,0,&entityKind },
|
|
{ DIS_FIELDTYPE_DOMAIN, "Domain",0,0,&entityDomain },
|
|
{ DIS_FIELDTYPE_COUNTRY, "Country",0,0,0 },
|
|
{ DIS_FIELDTYPE_CATEGORY, "Category",0,0,0 },
|
|
{ DIS_FIELDTYPE_SUBCATEGORY, "Subcategory",0,0,0 },
|
|
{ DIS_FIELDTYPE_SPECIFIC, "Specific",0,0,0 },
|
|
{ DIS_FIELDTYPE_EXTRA, "Extra",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_EVENT_ID[] =
|
|
{
|
|
{ DIS_FIELDTYPE_UINT16, "Site",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Application",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Event Number",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_LINEAR_VELOCITY[] =
|
|
{
|
|
{ DIS_FIELDTYPE_FLOAT32, "X",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT32, "Y",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT32, "Z",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_LOCATION_WORLD[] =
|
|
{
|
|
{ DIS_FIELDTYPE_FLOAT64, "X",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT64, "Y",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT64, "Z",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_LOCATION_ENTITY[] =
|
|
{
|
|
{ DIS_FIELDTYPE_FLOAT32, "X",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT32, "Y",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT32, "Z",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_ORIENTATION[] =
|
|
{
|
|
{ DIS_FIELDTYPE_FLOAT32, "Psi",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT32, "Theta",0,0,0 },
|
|
{ DIS_FIELDTYPE_FLOAT32, "Phi",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_BURST_DESCRIPTOR[] =
|
|
{
|
|
{ DIS_FIELDTYPE_ENTITY_TYPE, "Munition",0,0,0 },
|
|
{ DIS_FIELDTYPE_WARHEAD, "Warhead",0,0,0 },
|
|
{ DIS_FIELDTYPE_FUSE, "Fuse",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Quantity",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Rate",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_ARTICULATION_PARAMETER[] =
|
|
{
|
|
{ DIS_FIELDTYPE_ARTIC_PARAM_TYPE_DESIGNATOR, "Parameter Type Designator",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT8, "Change",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT16, "Part Attached To ID",0,0,0 },
|
|
{ DIS_FIELDTYPE_ARTIC_PARAM_TYPE, "Parameter Type",0,0,0 },
|
|
{ DIS_FIELDTYPE_UINT64, "Parameter Value",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_NONE[] =
|
|
{
|
|
{ DIS_FIELDTYPE_END, NULL, 0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_FIXED_DATUM[] =
|
|
{
|
|
{ DIS_FIELDTYPE_DATUM_ID, "Datum ID",0,0,0 },
|
|
{ DIS_FIELDTYPE_FIXED_DATUM_VALUE, "Datum value",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_ParserNode DIS_FIELDS_VARIABLE_DATUM[] =
|
|
{
|
|
{ DIS_FIELDTYPE_DATUM_ID, "Datum ID",0,0,0 },
|
|
{ DIS_FIELDTYPE_DATUM_LENGTH, "Datum length",0,0,&variableDatumLength },
|
|
{ DIS_FIELDTYPE_VARIABLE_DATUM_VALUE, "Datum value",0,0,0 },
|
|
{ DIS_FIELDTYPE_END, NULL,0,0,0 }
|
|
};
|
|
|
|
DIS_BitMask DIS_APPEARANCE_LANDPLATFORM[] =
|
|
{
|
|
{ 0x00000001, 0, "Paint Scheme", {
|
|
{ 0, "Uniform color" },
|
|
{ 1, "Camouflage" },
|
|
{ 0,0 }
|
|
} },
|
|
{ 0x00000002, 1, "Mobility", {
|
|
{ 0, "No mobility kill" },
|
|
{ 1, "Mobility kill" },
|
|
{ 0,0 }
|
|
} },
|
|
{ 0x00000004, 2, "Fire Power", {
|
|
{ 0, "No fire-power kill" },
|
|
{ 1, "Fire-power kill" },
|
|
{ 0,0 }
|
|
} },
|
|
{ 0x00000018, 3, "Damage", {
|
|
{ 0, "No damage" },
|
|
{ 1, "Slight damage" },
|
|
{ 2, "Moderate damage" },
|
|
{ 3, "Destroyed" },
|
|
{ 0,0 }
|
|
} },
|
|
{ 0, 0, 0, {
|
|
{ 0, 0 }
|
|
} }
|
|
};
|
|
|
|
DIS_BitMask DIS_APPEARANCE_LIFEFORM[] =
|
|
{
|
|
{ 0x00000001, 0, "Paint Scheme", {
|
|
{ 0, "Uniform color" },
|
|
{ 1, "Camouflage" },
|
|
{ 0,0 }
|
|
} },
|
|
{ 0x00000018, 3, "Health", {
|
|
{ 0, "No injury" },
|
|
{ 1, "Slight injury" },
|
|
{ 2, "Moderate injury" },
|
|
{ 3, "Fatal injury" },
|
|
{ 0,0 }
|
|
} },
|
|
{ 0, 0, 0, {
|
|
{ 0, 0 }
|
|
} }
|
|
};
|
|
|
|
/* Adjust an offset variable for proper alignment for a specified field length.
|
|
*/
|
|
static gint alignOffset(gint offset, guint fieldLength)
|
|
{
|
|
gint remainder = offset % fieldLength;
|
|
if (remainder != 0)
|
|
{
|
|
offset += fieldLength - remainder;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/* Parse a field consisting of a specified number of bytes. This field parser
|
|
* doesn't perform any alignment.
|
|
*/
|
|
gint parseField_Bytes(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode, guint numBytes)
|
|
{
|
|
proto_tree_add_text(tree, tvb, offset, numBytes, "%s (%d bytes)",
|
|
parserNode.fieldLabel, numBytes);
|
|
offset += numBytes;
|
|
return offset;
|
|
}
|
|
|
|
/* Parse a bitmask field.
|
|
*/
|
|
gint parseField_Bitmask(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode, guint numBytes)
|
|
{
|
|
DIS_BitMask *bitMask = 0;
|
|
guint64 uintVal = 0;
|
|
|
|
offset = alignOffset(offset, numBytes);
|
|
|
|
switch(numBytes)
|
|
{
|
|
case 1:
|
|
uintVal = tvb_get_guint8(tvb, offset);
|
|
break;
|
|
case 2:
|
|
uintVal = tvb_get_ntohs(tvb, offset);
|
|
break;
|
|
case 4:
|
|
uintVal = tvb_get_ntohl(tvb, offset);
|
|
break;
|
|
case 8:
|
|
uintVal = tvb_get_ntoh64(tvb, offset);
|
|
break;
|
|
default:
|
|
/* assert */
|
|
break;
|
|
}
|
|
|
|
switch(parserNode.fieldType)
|
|
{
|
|
case DIS_FIELDTYPE_APPEARANCE:
|
|
if ((entityKind == DIS_ENTITYKIND_PLATFORM) &&
|
|
(entityDomain == DIS_DOMAIN_LAND))
|
|
{
|
|
bitMask = DIS_APPEARANCE_LANDPLATFORM;
|
|
}
|
|
else if (entityKind == DIS_ENTITYKIND_LIFE_FORM)
|
|
{
|
|
bitMask = DIS_APPEARANCE_LIFEFORM;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (bitMask != 0)
|
|
{
|
|
int maskIndex = 0;
|
|
while (bitMask[maskIndex].maskBits != 0)
|
|
{
|
|
int mapIndex = 0;
|
|
DIS_BitMaskMapping *bitMaskMap = bitMask[maskIndex].bitMappings;
|
|
|
|
while (bitMaskMap[mapIndex].label != 0)
|
|
{
|
|
if (((bitMask[maskIndex].maskBits & uintVal) >> bitMask[maskIndex].shiftBits) ==
|
|
bitMaskMap[mapIndex].value)
|
|
{
|
|
proto_tree_add_text(tree, tvb, offset, numBytes,
|
|
"%s = %s", bitMask[maskIndex].label,
|
|
bitMaskMap[mapIndex].label);
|
|
break;
|
|
}
|
|
++mapIndex;
|
|
}
|
|
++maskIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
proto_tree_add_text(tree, tvb, offset, numBytes,
|
|
"Unknown Appearance Type (%" G_GINT64_MODIFIER "u)", uintVal);
|
|
}
|
|
|
|
offset += numBytes;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse an unsigned integer field of a specified number of bytes.
|
|
*/
|
|
gint parseField_UInt(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode, guint numBytes)
|
|
{
|
|
guint64 uintVal = 0;
|
|
|
|
offset = alignOffset(offset, numBytes);
|
|
|
|
switch(numBytes)
|
|
{
|
|
case 1:
|
|
uintVal = tvb_get_guint8(tvb, offset);
|
|
break;
|
|
case 2:
|
|
uintVal = tvb_get_ntohs(tvb, offset);
|
|
break;
|
|
case 4:
|
|
uintVal = tvb_get_ntohl(tvb, offset);
|
|
break;
|
|
case 8:
|
|
uintVal = tvb_get_ntoh64(tvb, offset);
|
|
break;
|
|
default:
|
|
/* assert */
|
|
break;
|
|
}
|
|
|
|
proto_tree_add_text(tree, tvb, offset, numBytes, "%s = %" G_GINT64_MODIFIER "u",
|
|
parserNode.fieldLabel, uintVal);
|
|
|
|
if (parserNode.outputVar != 0)
|
|
{
|
|
*(parserNode.outputVar) = (guint32)uintVal;
|
|
}
|
|
|
|
offset += numBytes;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse a signed integer field of a specified number of bytes.
|
|
*/
|
|
gint parseField_Int(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode, guint numBytes)
|
|
{
|
|
guint64 uintVal = 0;
|
|
|
|
offset = alignOffset(offset, numBytes);
|
|
|
|
switch(numBytes)
|
|
{
|
|
case 1:
|
|
uintVal = tvb_get_guint8(tvb, offset);
|
|
break;
|
|
case 2:
|
|
uintVal = tvb_get_ntohs(tvb, offset);
|
|
break;
|
|
case 4:
|
|
uintVal = tvb_get_ntohl(tvb, offset);
|
|
break;
|
|
case 8:
|
|
uintVal = tvb_get_ntoh64(tvb, offset);
|
|
break;
|
|
default:
|
|
/* assert */
|
|
break;
|
|
}
|
|
|
|
proto_tree_add_text(tree, tvb, offset, numBytes, "%s = %" G_GINT64_MODIFIER "d",
|
|
parserNode.fieldLabel, uintVal);
|
|
|
|
offset += numBytes;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse a field that explicitly specified a number of pad bytes (vs implicit
|
|
* padding, which occurs whenever padding is inserted to properly align the
|
|
* field.
|
|
*/
|
|
gint parseField_Pad(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode _U_, guint numBytes)
|
|
{
|
|
proto_tree_add_text(tree, tvb, offset, numBytes,
|
|
"Explicit Padding (%d bytes)", numBytes);
|
|
|
|
offset += numBytes;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse an enumerated type field.
|
|
*/
|
|
gint parseField_Enum(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode, guint numBytes)
|
|
{
|
|
const value_string *enumStrings = 0;
|
|
guint32 enumVal = 0;
|
|
const gchar *enumStr = 0;
|
|
|
|
offset = alignOffset(offset, numBytes);
|
|
|
|
switch(parserNode.fieldType)
|
|
{
|
|
case DIS_FIELDTYPE_PROTOCOL_VERSION:
|
|
enumStrings = DIS_PDU_ProtocolVersion_Strings;
|
|
break;
|
|
case DIS_FIELDTYPE_PROTOCOL_FAMILY:
|
|
enumStrings = DIS_PDU_ProtocolFamily_Strings;
|
|
break;
|
|
case DIS_FIELDTYPE_PDU_TYPE:
|
|
enumStrings = DIS_PDU_Type_Strings;
|
|
break;
|
|
case DIS_FIELDTYPE_ENTITY_KIND:
|
|
enumStrings = DIS_PDU_EntityKind_Strings;
|
|
break;
|
|
case DIS_FIELDTYPE_DOMAIN:
|
|
enumStrings = DIS_PDU_Domain_Strings;
|
|
break;
|
|
case DIS_FIELDTYPE_DETONATION_RESULT:
|
|
enumStrings = DIS_PDU_DetonationResult_Strings;
|
|
break;
|
|
case DIS_FIELDTYPE_CATEGORY:
|
|
if (entityKind == DIS_ENTITYKIND_PLATFORM)
|
|
{
|
|
switch(entityDomain)
|
|
{
|
|
case DIS_DOMAIN_LAND:
|
|
enumStrings = DIS_PDU_Category_LandPlatform_Strings;
|
|
break;
|
|
case DIS_DOMAIN_AIR:
|
|
enumStrings = DIS_PDU_Category_AirPlatform_Strings;
|
|
break;
|
|
case DIS_DOMAIN_SURFACE:
|
|
enumStrings = DIS_PDU_Category_SurfacePlatform_Strings;
|
|
break;
|
|
case DIS_DOMAIN_SUBSURFACE:
|
|
enumStrings = DIS_PDU_Category_SubsurfacePlatform_Strings;
|
|
break;
|
|
case DIS_DOMAIN_SPACE:
|
|
enumStrings = DIS_PDU_Category_SpacePlatform_Strings;
|
|
break;
|
|
default:
|
|
enumStrings = 0;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
enumStrings = 0;
|
|
break;
|
|
}
|
|
|
|
switch(numBytes)
|
|
{
|
|
case 1:
|
|
enumVal = tvb_get_guint8(tvb, offset);
|
|
break;
|
|
case 2:
|
|
enumVal = tvb_get_ntohs(tvb, offset);
|
|
break;
|
|
case 4:
|
|
enumVal = tvb_get_ntohl(tvb, offset);
|
|
break;
|
|
default:
|
|
/* assert */
|
|
break;
|
|
}
|
|
|
|
if (enumStrings != 0)
|
|
{
|
|
enumStr = val_to_str(enumVal, enumStrings, "Unknown Enum Value");
|
|
}
|
|
else
|
|
{
|
|
enumStr = "Unknown Enum Type";
|
|
}
|
|
|
|
proto_tree_add_text(tree, tvb, offset, numBytes, "%s = %s",
|
|
parserNode.fieldLabel, enumStr);
|
|
|
|
if (parserNode.outputVar != 0)
|
|
{
|
|
*(parserNode.outputVar) = enumVal;
|
|
}
|
|
|
|
offset += numBytes;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse a 4-byte floating-point value.
|
|
*/
|
|
gint parseField_Float(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode)
|
|
{
|
|
gfloat floatVal;
|
|
|
|
offset = alignOffset(offset, 4);
|
|
floatVal = tvb_get_ntohieee_float(tvb, offset);
|
|
proto_tree_add_text(tree, tvb, offset, 4, "%s = %f",
|
|
parserNode.fieldLabel, floatVal);
|
|
|
|
offset += 4;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse an 8-byte floating-point value.
|
|
*/
|
|
gint parseField_Double(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode)
|
|
{
|
|
gdouble doubleVal;
|
|
|
|
offset = alignOffset(offset, 8);
|
|
doubleVal = tvb_get_ntohieee_double(tvb, offset);
|
|
proto_tree_add_text(tree, tvb, offset, 8, "%s = %lf",
|
|
parserNode.fieldLabel, doubleVal);
|
|
|
|
offset += 8;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/* Parse the Timestamp */
|
|
gint parseField_Timestamp(tvbuff_t *tvb, proto_tree *tree, gint offset, DIS_ParserNode parserNode)
|
|
{
|
|
/* some consts */
|
|
static double MSEC_PER_SECOND = 1000.0;
|
|
static double MSEC_PER_MINUTE = 60.0 * 1000.0 ;
|
|
static double MSEC_PER_HOUR = 60.0 * 60.0 * 1000.0;
|
|
static double FSV = 0x7fffffff;
|
|
/* variables */
|
|
guint isAbsolute = 0;
|
|
guint32 uintVal;
|
|
guint minutes;
|
|
guint seconds;
|
|
guint milliseconds;
|
|
double ms;
|
|
|
|
offset = alignOffset(offset, 4);
|
|
|
|
/* convert to host value */
|
|
uintVal = tvb_get_ntohl(tvb, offset);
|
|
/* determine absolute vis sim time */
|
|
if( uintVal & 1 )
|
|
isAbsolute = 1;
|
|
|
|
/* convert TS to MS */
|
|
ms = (uintVal >> 1) * MSEC_PER_HOUR / FSV;
|
|
ms += 0.5;
|
|
|
|
/* calc minutes and reduce ms */
|
|
minutes = (guint) (ms / MSEC_PER_MINUTE);
|
|
ms -= (minutes * MSEC_PER_MINUTE);
|
|
|
|
/* calc seconds and reduce ms */
|
|
seconds = (guint) (ms / MSEC_PER_SECOND);
|
|
ms -= (seconds * MSEC_PER_SECOND);
|
|
|
|
/* truncate milliseconds */
|
|
milliseconds = (guint) ms;
|
|
|
|
/* push out the values */
|
|
if( isAbsolute )
|
|
{
|
|
proto_tree_add_text(tree, tvb, offset, 4, "%s = %02d:%02d %03d absolute (UTM)",
|
|
parserNode.fieldLabel, minutes, seconds, milliseconds);
|
|
}
|
|
else
|
|
{
|
|
proto_tree_add_text(tree, tvb, offset, 4, "%s = %02d:%02d %03d relative",
|
|
parserNode.fieldLabel, minutes, seconds, milliseconds);
|
|
}
|
|
|
|
offset += 4;
|
|
return offset;
|
|
}
|