/* 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: packet-dis-fields.c 26047 2008-08-20 06:32:08Z jake $
 *
 * 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;

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