/* packet-srvloc.c
 * Routines for SRVLOC (Service Location Protocol) packet dissection
 * Copyright 1999, James Coe <jammer@cin.net>
 *
 * NOTE: This is Alpha software not all features have been verified yet.
 *       In particular I have not had an opportunity to see how it 
 *       responds to SRVLOC over TCP.
 *
 * $Id: packet-srvloc.c,v 1.25 2001/07/15 19:14:00 guy Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@ethereal.com>
 * Copyright 1998 Gerald Combs
 *
 * Service Location Protocol is RFC 2165
 *
 * 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 <stdio.h>
#include <stdlib.h>

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#include <string.h>
#include <time.h>
#include <glib.h>

#ifdef NEED_SNPRINTF_H
# include "snprintf.h"
#endif

#include "packet.h"
#include "strutil.h"

static int proto_srvloc = -1;
static int hf_srvloc_version = -1;
static int hf_srvloc_function = -1;
static int hf_srvloc_flags = -1;
static int hf_srvloc_error = -1;

static gint ett_srvloc = -1;
static gint ett_srvloc_flags = -1;

#define TCP_PORT_SRVLOC	427
#define UDP_PORT_SRVLOC	427

/* Define function types */

#define SRVREQ		1
#define SRVRPLY		2
#define SRVREG		3
#define SRVDEREG	4
#define SRVACK		5
#define ATTRRQST	6
#define ATTRRPLY	7
#define DAADVERT	8
#define SRVTYPERQST	9
#define	SRVTYPERPLY	10

/* Create protocol header structure */

struct srvloc_hdr {
    guint8	version;
    guint8	function;
    guint16	length;
    guint8	flags;
    guint8	dialect;
    u_char	language[2];
    guint16	encoding;
    guint16	xid;
};

/* List to resolve function numbers to names */

static const value_string srvloc_functions[] = {
    { SRVREQ, "Service Request" }, 
    { SRVRPLY, "Service Reply" }, 
    { SRVREG, "Service Registration" }, 
    { SRVDEREG, "Service Deregister" }, 
    { SRVACK, "Service Acknowledge" }, 
    { ATTRRQST, "Attribute Request" }, 
    { ATTRRPLY, "Attribute Reply" }, 
    { DAADVERT, "DA Advertisement" }, 
    { SRVTYPERQST, "Service Type Request" }, 
    { SRVTYPERPLY, "Service Type Reply" }, 
    { 0, NULL }
};

/* List to resolve flag values to names */


/* Define flag masks */

#define FLAG_O		0x80
#define FLAG_M		0x40
#define FLAG_U		0x20
#define FLAG_A		0x10
#define FLAG_F		0x08

/* Define Error Codes */

#define SUCCESS		0
#define LANG_NOT_SPTD	1
#define PROT_PARSE_ERR	2
#define INVLD_REG	3
#define SCOPE_NOT_SPTD	4
#define CHRSET_NOT_UND	5
#define AUTH_ABSENT	6
#define AUTH_FAILED	7

/* List to resolve error codes to names */

static const value_string srvloc_errs[] = {
    { SUCCESS, "No Error" },
    { LANG_NOT_SPTD, "Language not supported" },
    { PROT_PARSE_ERR, "Protocol parse error" },
    { INVLD_REG, "Invalid registration" },
    { SCOPE_NOT_SPTD, "Scope not supported" },
    { CHRSET_NOT_UND, "Character set not understood" },
    { AUTH_ABSENT, "Authentication absent" },
    { AUTH_FAILED, "Authentication failed" },
    { 0, NULL }
};

/*
 * Character encodings.
 * This is a small subset of what's in
 *
 *	http://www.isi.edu/in-notes/iana/assignments/character-sets
 *
 * XXX - we should do something useful with this, i.e. properly
 * handle strings based on the character set they're in.
 *
 * XXX - what does "properly handle strings" mean?  How do we know
 * what character set the terminal can handle (for tty-based code)
 * or the GUI can handle (for GUI code)?
 *
 * XXX - the Ethereal core really should be what does all the
 * character set handling for strings, and it should be stuck with
 * the task of figuring out how to properly handle them.
 */
#define CHARSET_ASCII		3
#define CHARSET_ISO_10646_UTF_1	27
#define CHARSET_ISO_646_BASIC	28
#define CHARSET_ISO_646_IRV	30
#define CHARSET_ISO_8859_1	4
#define CHARSET_ISO_10646_UCS_2	1000	/* a/k/a Unicode */
#define CHARSET_UTF_7		1012
#define CHARSET_UTF_8		106

static const value_string charsets[] = {
	{ CHARSET_ASCII, "US-ASCII" },
	{ CHARSET_ISO_10646_UTF_1, "ISO 10646 UTF-1" },
	{ CHARSET_ISO_646_BASIC, "ISO 646 basic:1983" },
	{ CHARSET_ISO_646_IRV, "ISO 646 IRV:1983" },
	{ CHARSET_ISO_8859_1, "ISO 8859-1" },
	{ CHARSET_ISO_10646_UCS_2, "Unicode" },
	{ CHARSET_UTF_7, "UTF-7" },
	{ CHARSET_UTF_8, "UTF-8" },
	{ 0, NULL }
};

static int
dissect_authblk(tvbuff_t *tvb, int offset, proto_tree *tree)
{
    struct tm *stamp;
    time_t seconds;
    double floatsec;
    guint16 length;
    
    seconds = tvb_get_ntohl(tvb, offset) - 2208988800ul;
    stamp = gmtime(&seconds);
    if (stamp != NULL) {
      floatsec = stamp->tm_sec + tvb_get_ntohl(tvb, offset + 4) / 4294967296.0;
      proto_tree_add_text(tree, tvb, offset, 8,
                          "Timestamp: %04d-%02d-%02d %02d:%02d:%07.4f UTC",
                          stamp->tm_year + 1900, stamp->tm_mon + 1,
                          stamp->tm_mday, stamp->tm_hour, stamp->tm_min,
                          floatsec);
    } else {
      proto_tree_add_text(tree, tvb, offset, 8, "Timestamp not representable");
    }
    proto_tree_add_text(tree, tvb, offset + 8, 2, "Block Structure Desciptor: %u",
			tvb_get_ntohs(tvb, offset + 8));
    length = tvb_get_ntohs(tvb, offset + 10);
    proto_tree_add_text(tree, tvb, offset + 10, 2, "Authenticator length: %u",
			length);
    offset += 12;
    proto_tree_add_text(tree, tvb, offset, length, "Authentication block: %s",
			tvb_format_text(tvb, offset, length));
    offset += length;
    return offset;
}

/* Packet dissection routine called by tcp & udp when port 427 detected */

static void
dissect_srvloc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    int offset = 0;
    proto_item *ti, *tf;
    proto_tree *srvloc_tree, *srvloc_flags;
    guint8 version;
    guint8 function;
    guint16 encoding;
    guint16 length;
    guint8 flags;
    guint32 count;

    if (check_col(pinfo->fd, COL_PROTOCOL))
        col_set_str(pinfo->fd, COL_PROTOCOL, "SRVLOC");
    
    if (check_col(pinfo->fd, COL_INFO))
        col_clear(pinfo->fd, COL_INFO);

    version = tvb_get_guint8(tvb, offset);
    function = tvb_get_guint8(tvb, offset + 1);

    if (check_col(pinfo->fd, COL_INFO))
        col_add_str(pinfo->fd, COL_INFO,
            val_to_str(function, srvloc_functions, "Unknown Function (%u)"));
        
    if (tree) {
        ti = proto_tree_add_item(tree, proto_srvloc, tvb, offset,
                                 tvb_length(tvb), FALSE);
        srvloc_tree = proto_item_add_subtree(ti, ett_srvloc);
    
        proto_tree_add_uint(srvloc_tree, hf_srvloc_version, tvb, offset, 1,
                            version);
        proto_tree_add_uint(srvloc_tree, hf_srvloc_function, tvb, offset + 1, 1,
                            function);
        length = tvb_get_ntohs(tvb, offset + 2);
        proto_tree_add_text(srvloc_tree, tvb, offset + 2, 2, "Length: %u",
                            length);
        flags = tvb_get_guint8(tvb, offset + 4);
        tf = proto_tree_add_uint(srvloc_tree, hf_srvloc_flags, tvb, offset + 4, 1,
                                 flags);
        srvloc_flags = proto_item_add_subtree(tf, ett_srvloc_flags);
        proto_tree_add_text(srvloc_flags, tvb, offset + 4, 0, "Overflow                          %d... .xxx", (flags & FLAG_O) >> 7 );
        proto_tree_add_text(srvloc_flags, tvb, offset + 4, 0, "Monolingual                       .%d.. .xxx", (flags & FLAG_M) >> 6 );
        proto_tree_add_text(srvloc_flags, tvb, offset + 4, 0, "URL Authentication Present        ..%d. .xxx", (flags & FLAG_U) >> 5 );
        proto_tree_add_text(srvloc_flags, tvb, offset + 4, 0, "Attribute Authentication Present  ...%d .xxx", (flags & FLAG_A) >> 4 );
        proto_tree_add_text(srvloc_flags, tvb, offset + 4, 0, "Fresh Service Entry               .... %dxxx", (flags & FLAG_F) >> 3 );
        proto_tree_add_text(srvloc_tree, tvb, offset + 5, 1, "Dialect: %u",
                            tvb_get_guint8(tvb, offset + 5));
        proto_tree_add_text(srvloc_tree, tvb, offset + 6, 2, "Language: %s",
                            tvb_format_text(tvb, offset + 6, 2));
        encoding = tvb_get_ntohs(tvb, offset + 8);
        proto_tree_add_text(srvloc_tree, tvb, offset + 8, 2, "Encoding: %u (%s)",
                            encoding,
                            val_to_str(encoding, charsets, "Unknown"));
        proto_tree_add_text(srvloc_tree, tvb, offset + 10, 2, "Transaction ID: %u",
                            tvb_get_ntohs(tvb, offset + 10));
        offset += 12;
        
        switch (function) {
            case SRVREQ:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Request");
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Previous Response List Length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Previous Response List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Predicate length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Predicate: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
            break;
            
            case SRVRPLY:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Reply");
                proto_tree_add_item(srvloc_tree, hf_srvloc_error, tvb, offset, 2, FALSE);
                offset += 2;
                count = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL Count: %u",
                                    count);
                offset += 2;
                while (count > 0) {
                    proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL lifetime: %u",
                                        tvb_get_ntohs(tvb, offset));
                    offset += 2;
                    length = tvb_get_ntohs(tvb, offset);
                    proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL length: %u",
                                        length);
                    offset += 2;
                    proto_tree_add_text(srvloc_tree, tvb, offset, length, "Service URL: %s",
                                        tvb_format_text(tvb, offset, length));
                    offset += length;
                    if ( (flags & FLAG_U) == FLAG_U ) 
                        offset = dissect_authblk(tvb, offset, srvloc_tree);
                    count--;
                };
            break;

            case SRVREG:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Registration");
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL lifetime: %u",
                                    tvb_get_ntohs(tvb, offset));
                offset += 2;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Service URL: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                if ( (flags & FLAG_U) == FLAG_U ) 
                    offset = dissect_authblk(tvb, offset, srvloc_tree);
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Attribute List length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Attribute List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                if ( (flags & FLAG_A) == FLAG_A ) 
                    offset = dissect_authblk(tvb, offset, srvloc_tree);
            break;

            case SRVDEREG:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Deregister");
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Service URL: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                if ( (flags & FLAG_U) == FLAG_U ) 
                    offset = dissect_authblk(tvb, offset, srvloc_tree);
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Attribute List length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Attribute List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                if ( (flags & FLAG_A) == FLAG_A ) 
                    offset = dissect_authblk(tvb, offset, srvloc_tree);
            break;
            
            case SRVACK:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Acknowledge");
                proto_tree_add_item(srvloc_tree, hf_srvloc_error, tvb, offset, 2, FALSE);
                offset += 2;
            break;

            case ATTRRQST:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Attribute Request");
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Previous Response List Length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Previous Response List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Service URL: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Scope List Length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Scope Response List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Attribute List length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Attribute List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
            break;
            
            case ATTRRPLY:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Attribute Reply");
                proto_tree_add_item(srvloc_tree, hf_srvloc_error, tvb, offset, 2, FALSE);
                offset += 2;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Attribute List length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Attribute List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                if ( (flags & FLAG_A) == FLAG_A ) 
                    offset = dissect_authblk(tvb, offset, srvloc_tree);
            break;
            
            case DAADVERT:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "DA Advertisement");
                proto_tree_add_item(srvloc_tree, hf_srvloc_error, tvb, offset, 2, FALSE);
                offset += 2;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "URL length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Service URL: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Scope List Length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Scope Response List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
            break;

            case SRVTYPERQST:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Type Request");
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Previous Response List Length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Previous Response List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Naming Authority List length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Naming Authority List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
                length = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Scope List Length: %u",
                                    length);
                offset += 2;
                proto_tree_add_text(srvloc_tree, tvb, offset, length, "Scope Response List: %s",
                                    tvb_format_text(tvb, offset, length));
                offset += length;
            break;

            case SRVTYPERPLY:
                proto_tree_add_text(srvloc_tree, tvb, offset, 0, "Service Type Reply");
                proto_tree_add_item(srvloc_tree, hf_srvloc_error, tvb, offset, 2, FALSE);
                offset += 2;
                count = tvb_get_ntohs(tvb, offset);
                proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Service Type Count: %u",
                                    count);
                offset += 2;
                while (count > 0) {
                    length = tvb_get_ntohs(tvb, offset);
                    proto_tree_add_text(srvloc_tree, tvb, offset, 2, "Service Type List length: %u",
                                        length);
                    offset += 2;
                    proto_tree_add_text(srvloc_tree, tvb, offset, length, "Service Type List: %s",
                                        tvb_format_text(tvb, offset, length));
                    offset += length;
                    count--;
                };
            break;

            default:
                proto_tree_add_text(srvloc_tree, tvb, offset, tvb_length_remaining(tvb, offset), "Unknown Function Type");
        };
    };
}

/* Register protocol with Ethereal. */

void
proto_register_srvloc(void)
{
    static hf_register_info hf[] = {
        { &hf_srvloc_version,
            { "Version",           "srvloc.version",
            FT_UINT8, BASE_DEC, NULL, 0x0,
            "", HFILL }
        },
      
        {&hf_srvloc_function,
            {"Function", "srvloc.function", 
            FT_UINT8, BASE_DEC, VALS(srvloc_functions), 0x0, 
            "", HFILL }
        },

        {&hf_srvloc_flags,
            {"Flags", "srvloc.flags", 
            FT_UINT8, BASE_HEX, NULL, 0x0, 
            "", HFILL }
        },
        
        {&hf_srvloc_error,
            {"Error Code", "srvloc.err",
            FT_UINT16, BASE_DEC, VALS(srvloc_errs), 0x0,
            "", HFILL }
        },
    };
                  
    static gint *ett[] = {
	&ett_srvloc,
	&ett_srvloc_flags,
    };

    proto_srvloc = proto_register_protocol("Service Location Protocol",
					   "SRVLOC", "srvloc");
    proto_register_field_array(proto_srvloc, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
};

void
proto_reg_handoff_srvloc(void)
{
    dissector_add("tcp.port", TCP_PORT_SRVLOC, dissect_srvloc, proto_srvloc);
    dissector_add("udp.port", UDP_PORT_SRVLOC, dissect_srvloc, proto_srvloc);
}

