#! /usr/bin/env python
#
# Copyright (C) 2015 Open Information Security Foundation
#
# You can copy, redistribute or modify this Program under the terms of
# the GNU General Public License version 2 as published by the Free
# Software Foundation.
#
# 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
# version 2 along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

# This script generates DNP3 related source code based on definitions
# of DNP3 objects (currently the object structs).

from __future__ import print_function

import sys
import re
from cStringIO import StringIO
import yaml
import types

import jinja2

IN_PLACE_START = "/* START GENERATED CODE */"
IN_PLACE_END = "/* END GENERATED CODE */"

util_lua_dnp3_objects_c_template = """/* Copyright (C) 2015 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * 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
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * DO NOT EDIT. THIS FILE IS AUTO-GENERATED.
 *
 * Generated by command:
 *    {{command_line}}
 */

#include "suricata-common.h"

#include "app-layer-dnp3.h"
#include "app-layer-dnp3-objects.h"

#ifdef HAVE_LUA

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include "util-lua.h"
#include "util-lua-dnp3-objects.h"

/**
 * \\brief Push an object point item onto the stack.
 */
void DNP3PushPoint(lua_State *luastate, DNP3Object *object,
    DNP3Point *point)
{
    switch (DNP3_OBJECT_CODE(object->group, object->variation)) {
{% for object in objects %}
        case DNP3_OBJECT_CODE({{object.group}}, {{object.variation}}): {
            DNP3ObjectG{{object.group}}V{{object.variation}} *data = point->data;
{% for field in object.fields %}
{% if is_integer_type(field.type) %}
            lua_pushliteral(luastate, "{{field.name}}");
            lua_pushinteger(luastate, data->{{field.name}});
            lua_settable(luastate, -3);
{% elif field["type"] in ["flt32", "flt64"] %}
            lua_pushliteral(luastate, "{{field.name}}");
            lua_pushnumber(luastate, data->{{field.name}});
            lua_settable(luastate, -3);
{% elif field["type"] == "chararray" %}
            lua_pushliteral(luastate, "{{field.name}}");
            LuaPushStringBuffer(luastate, (uint8_t *)data->{{field.name}},
                strlen(data->{{field.name}}));
            lua_settable(luastate, -3);
{% elif field["type"] == "vstr4" %}
            lua_pushliteral(luastate, "{{field.name}}");
            LuaPushStringBuffer(luastate, (uint8_t *)data->{{field.name}},
                strlen(data->{{field.name}}));
            lua_settable(luastate, -3);
{% elif field.type == "bytearray" %}
            lua_pushliteral(luastate, "{{field.name}}");
            lua_pushlstring(luastate, (const char *)data->{{field.name}},
                data->{{field.len_field}});
            lua_settable(luastate, -3);
{% elif field.type == "bstr8" %}
{% for field in field.fields %}
            lua_pushliteral(luastate, "{{field.name}}");
            lua_pushinteger(luastate, data->{{field.name}});
            lua_settable(luastate, -3);
{% endfor %}
{% else %}
{{ raise("Unhandled datatype: %s" % (field.type)) }}
{% endif %}
{% endfor %}
            break;
        }
{% endfor %}
        default:
            break;
    }
}

#endif /* HAVE_LUA */

"""

output_json_dn
p3_ob
jects_template = """/* Copyright (C) 2015 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * 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
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * DO NOT EDIT. THIS FILE IS AUTO-GENERATED.
 *
 * Generated by command:
 *    {{command_line}}
 */

#include "suricata-common.h"

#include "util-crypt.h"

#include "app-layer-dnp3.h"
#include "app-layer-dnp3-objects.h"
#include "output-json-dnp3-objects.h"

#ifdef HAVE_LIBJANSSON

void OutputJsonDNP3SetItem(json_t *js, DNP3Object *object,
    DNP3Point *point)
{

    switch (DNP3_OBJECT_CODE(object->group, object->variation)) {
{% for object in objects %}
        case DNP3_OBJECT_CODE({{object.group}}, {{object.variation}}): {
            DNP3ObjectG{{object.group}}V{{object.variation}} *data = point->data;
{% for field in object.fields %}
{% if is_integer_type(field.type) %}
            json_object_set_new(js, "{{field.name}}",
                json_integer(data->{{field.name}}));
{% elif field.type in ["flt32", "flt64"] %}
            json_object_set_new(js, "{{field.name}}",
                json_real(data->{{field.name}}));
{% elif field.type == "bytearray" %}
            unsigned long {{field.name}}_b64_len = data->{{field.len_field}} * 2;
            uint8_t {{field.name}}_b64[{{field.name}}_b64_len];
            Base64Encode(data->{{field.name}}, data->{{field.len_field}},
                {{field.name}}_b64, &{{field.name}}_b64_len);
            json_object_set_new(js, "data->{{field.name}}",
                json_string((char *){{field.name}}_b64));
{% elif field.type == "vstr4" %}
            json_object_set_new(js, "data->{{field.name}}", json_string(data->{{field.name}}));
{% elif field.type == "chararray" %}
            if (data->{{field.len_field}} > 0) {
                /* First create a null terminated string as not all versions
                 * of jansson have json_stringn. */
                char tmpbuf[data->{{field.len_field}} + 1];
                memcpy(tmpbuf, data->{{field.name}}, data->{{field.len_field}});
                tmpbuf[data->{{field.len_field}}] = '\\0';
                json_object_set_new(js, "{{field.name}}", json_string(tmpbuf));
            } else {
                json_object_set_new(js, "{{field.name}}", json_string(""));
            }
{% elif field.type == "bstr8" %}
{% for field in field.fields %}
            json_object_set_new(js, "{{field.name}}",
                json_integer(data->{{field.name}}));
{% endfor %}
{% else %}
{{ raise("Unhandled datatype: %s" % (field.type)) }}
{% endif %}
{% endfor %}
            break;
        }
{% endfor %}
        default:
            SCLogDebug("Unknown object: %d:%d", object->group,
                object->variation);
            break;
    }

}

#endif /* HAVE_LIBJANSSON */

"""

def has_freeable_types(fields):
    freeable_types = [
        "bytearray",
    ]
    for field in fields:
        if field["type"] in freeable_types:
            return True
    return False

def is_integer_type(datatype):
    integer_types = [
        "uint64",
        "uint32",
        "uint24",
        "uint16",
        "uint8",
        "int64",
        "int32",
        "int16",
        "int8",
        "dnp3time",
    ]
    return datatype in integer_types

def to_type(datatype):
    type_map = {
        "uint8": "uint8_t",
    }
    if datatype in type_map:
        return type_map[datatype]
    else:
        raise Exception("Unkno