From 240d789c40b51c475da56c2c4a7a67ab2f508809 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Mon, 8 Feb 2016 15:16:01 -0600 Subject: [PATCH] DNP3: dnp3-gen: code generator for repetitive DNP3 code --- scripts/dnp3-gen/dnp3-gen.py | 729 +++++++ scripts/dnp3-gen/dnp3-objects.yaml | 2995 ++++++++++++++++++++++++++++ 2 files changed, 3724 insertions(+) create mode 100755 scripts/dnp3-gen/dnp3-gen.py create mode 100644 scripts/dnp3-gen/dnp3-objects.yaml diff --git a/scripts/dnp3-gen/dnp3-gen.py b/scripts/dnp3-gen/dnp3-gen.py new file mode 100755 index 0000000000..29d7ae2e59 --- /dev/null +++ b/scripts/dnp3-gen/dnp3-gen.py @@ -0,0 +1,729 @@ +#! /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 +#include +#include + +#include "util-lua.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_dnp3_objects_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" + +#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("Unknown datatype: %s" % (datatype)) + +def generate(template, filename, context): + print("Generating %s." % (filename)) + try: + env = jinja2.Environment(trim_blocks=True) + output = env.from_string(template).render(context) + with open(filename, "w") as fileobj: + fileobj.write(output) + except Exception as err: + print("Failed to generate %s: %s" % (filename, err)) + sys.exit(1) + +def raise_helper(msg): + raise Exception(msg) + +def gen_object_structs(context): + """ Generate structs for all the define DNP3 objects. """ + + template = """ + +/* Code generated by: + * {{command_line}} + */ + +{% for object in objects %} +typedef struct DNP3ObjectG{{object.group}}V{{object.variation}}_ { +{% for field in object.fields %} +{% if field.type == "bstr8" %} +{% for field in field.fields %} + uint8_t {{field.name}}:{{field.width}}; +{% endfor %} +{% else %} +{% if field.type == "int16" %} + int16_t {{field.name}}; +{% elif field.type == "int32" %} + int32_t {{field.name}}; +{% elif field.type == "uint8" %} + uint8_t {{field.name}}; +{% elif field.type == "uint16" %} + uint16_t {{field.name}}; +{% elif field.type == "uint24" %} + uint32_t {{field.name}}; +{% elif field.type == "uint32" %} + uint32_t {{field.name}}; +{% elif field.type == "uint64" %} + uint64_t {{field.name}}; +{% elif field.type == "flt32" %} + float {{field.name}}; +{% elif field.type == "flt64" %} + double {{field.name}}; +{% elif field.type == "dnp3time" %} + uint64_t {{field.name}}; +{% elif field.type == "bytearray" %} + uint8_t *{{field.name}}; +{% elif field.type == "vstr4" %} + char {{field.name}}[5]; +{% elif field.type == "chararray" %} + char {{field.name}}[{{field.size}}]; +{% else %} + {{ raise("Unknown datatype type '%s' for object %d:%d" % ( + field.type, object.group, object.variation)) }} +{% endif %} +{% endif %} +{% endfor %} +{% if object.extra_fields %} +{% for field in object.extra_fields %} +{% if field.type == "uint8" %} + uint8_t {{field.name}}; +{% elif field.type == "uint16" %} + uint16_t {{field.name}}; +{% elif field.type == "uint32" %} + uint32_t {{field.name}}; +{% else %} + {{ raise("Unknown datatype: %s" % (field.type)) }} +{% endif %} +{% endfor %} +{% endif %} +} DNP3ObjectG{{object.group}}V{{object.variation}}; + +{% endfor %} +""" + + filename = "src/app-layer-dnp3-objects.h" + try: + env = jinja2.Environment(trim_blocks=True) + code = env.from_string(template).render(context) + content = open(filename).read() + content = re.sub( + "(%s).*(%s)" % (re.escape(IN_PLACE_START), re.escape(IN_PLACE_END)), + r"\1%s\2" % (code), content, 1, re.M | re.DOTALL) + open(filename, "w").write(content) + print("Updated %s." % (filename)) + except Exception as err: + print("Failed to update %s: %s" % (filename, err), file=sys.stderr) + sys.exit(1) + +def gen_object_decoders(context): + """ Generate decoders for all defined DNP3 objects. """ + + template = """ + +/* Code generated by: + * {{command_line}} + */ + +{% for object in objects %} +{% if object.packed %} +static int DNP3DecodeObjectG{{object.group}}V{{object.variation}}(const uint8_t **buf, uint32_t *len, + uint8_t prefix_code, uint32_t start, uint32_t count, + DNP3PointList *points) +{ + DNP3ObjectG{{object.group}}V{{object.variation}} *object = NULL; + int bytes = (count / 8) + 1; + uint32_t prefix = 0; + int index = start; + + if (!DNP3ReadPrefix(buf, len, prefix_code, &prefix)) { + goto error; + } + + for (int i = 0; i < bytes; i++) { + + uint8_t octet; + + if (!DNP3ReadUint8(buf, len, &octet)) { + goto error; + } + + for (int j = 0; j < 8 && count; j = j + {{object.fields[0].width}}) { + + object = SCCalloc(1, sizeof(*object)); + if (unlikely(object == NULL)) { + goto error; + } + +{% if object.fields[0].width == 1 %} + object->{{object.fields[0].name}} = (octet >> j) & 0x1; +{% elif object.fields[0].width == 2 %} + object->{{object.fields[0].name}} = (octet >> j) & 0x3; +{% else %} +#error "Unhandled field width: {{object.fields[0].width}}" +{% endif %} + + if (!DNP3AddPoint(points, object, index, prefix_code, prefix)) { + goto error; + } + + object = NULL; + count--; + index++; + } + + } + + return 1; +error: + if (object != NULL) { + SCFree(object); + } + return 0; +} + +{% else %} +static int DNP3DecodeObjectG{{object.group}}V{{object.variation}}(const uint8_t **buf, uint32_t *len, + uint8_t prefix_code, uint32_t start, uint32_t count, + DNP3PointList *points) +{ + DNP3ObjectG{{object.group}}V{{object.variation}} *object = NULL; + uint32_t prefix = 0; + uint32_t index = start; +{% if object._track_offset %} + uint32_t offset; +{% endif %} +{% if object.constraints %} + +{% for (key, val) in object.constraints.items() %} +{% if key == "require_size_prefix" %} + if (!DNP3PrefixIsSize(prefix_code)) { + goto error; + } +{% elif key == "require_prefix_code" %} + if (prefix_code != {{val}}) { + goto error; + } +{% else %} +{{ raise("Unhandled constraint: %s" % (key)) }} +{% endif %} +{% endfor %} +{% endif %} + + while (count--) { + + object = SCCalloc(1, sizeof(*object)); + if (unlikely(object == NULL)) { + goto error; + } + + if (!DNP3ReadPrefix(buf, len, prefix_code, &prefix)) { + goto error; + } +{% if object._track_offset %} + + offset = *len; +{% endif %} + +{% for field in object.fields %} +{% if field.type == "int16" %} + if (!DNP3ReadUint16(buf, len, (uint16_t *)&object->{{field.name}})) { + goto error; + } +{% elif field.type == "int32" %} + if (!DNP3ReadUint32(buf, len, (uint32_t *)&object->{{field.name}})) { + goto error; + } +{% elif field.type == "uint8" %} + if (!DNP3ReadUint8(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "uint16" %} + if (!DNP3ReadUint16(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "uint24" %} + if (!DNP3ReadUint24(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "uint32" %} + if (!DNP3ReadUint32(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "uint64" %} + if (!DNP3ReadUint64(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "flt32" %} + if (!DNP3ReadFloat32(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "flt64" %} + if (!DNP3ReadFloat64(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "dnp3time" %} + if (!DNP3ReadUint48(buf, len, &object->{{field.name}})) { + goto error; + } +{% elif field.type == "vstr4" %} + if (*len < 4) { + goto error; + } + memcpy(object->{{field.name}}, *buf, 4); + object->{{field.name}}[4] = '\\\\0'; + *buf += 4; + *len -= 4; +{% elif field.type == "bytearray" %} +{% if field.len_from_prefix %} + object->{{field.len_field}} = prefix - (offset - *len); +{% endif %} + if (object->{{field.len_field}} > 0) { + if (*len < object->{{field.len_field}}) { + /* Not enough data. */ + goto error; + } + object->{{field.name}} = SCCalloc(1, object->{{field.len_field}}); + if (unlikely(object->{{field.name}} == NULL)) { + goto error; + } + memcpy(object->{{field.name}}, *buf, object->{{field.len_field}}); + *buf += object->{{field.len_field}}; + *len -= object->{{field.len_field}}; + } +{% elif field.type == "chararray" %} +{% if field.len_from_prefix %} + object->{{field.len_field}} = prefix - (offset - *len); +{% endif %} + if (object->{{field.len_field}} > 0) { + memcpy(object->{{field.name}}, *buf, object->{{field.len_field}}); + *buf += object->{{field.len_field}}; + *len -= object->{{field.len_field}}; + } + object->{{field.name}}[object->{{field.len_field}}] = '\\\\0'; +{% elif field.type == "bstr8" %} + { + uint8_t octet; + if (!DNP3ReadUint8(buf, len, &octet)) { + goto error; + } +{% set shift = 0 %} +{% for field in field.fields %} +{% if field.width == 1 %} + object->{{field.name}} = (octet >> {{shift}}) & 0x1; +{% elif field.width == 2 %} + object->{{field.name}} = (octet >> {{shift}}) & 0x3; +{% elif field.width == 4 %} + object->{{field.name}} = (octet >> {{shift}}) & 0xf; +{% elif field.width == 7 %} + object->{{field.name}} = (octet >> {{shift}}) & 0x7f; +{% else %} +{{ raise("Unhandled width of %d." % (field.width)) }} +{% endif %} +{% set shift = shift + field.width %} +{% endfor %} + } +{% else %} +{{ raise("Unhandled datatype '%s' for object %d:%d." % (field.type, + object.group, object.variation)) }} +{% endif %} +{% endfor %} + + if (!DNP3AddPoint(points, object, index, prefix_code, prefix)) { + goto error; + } + + object = NULL; + index++; + } + + return 1; +error: + if (object != NULL) { + SCFree(object); + } + + return 0; +} + +{% endif %} +{% endfor %} + +void DNP3FreeObjectPoint(int group, int variation, void *point) +{ + switch(DNP3_OBJECT_CODE(group, variation)) { +{% for object in objects %} +{% if f_has_freeable_types(object.fields) %} + case DNP3_OBJECT_CODE({{object.group}}, {{object.variation}}): { + DNP3ObjectG{{object.group}}V{{object.variation}} *object = (DNP3ObjectG{{object.group}}V{{object.variation}} *) point; +{% for field in object.fields %} +{% if field.type == "bytearray" %} + if (object->{{field.name}} != NULL) { + SCFree(object->{{field.name}}); + } +{% endif %} +{% endfor %} + break; + } +{% endif %} +{% endfor %} + default: + break; + } + SCFree(point); +} + +/** + * \\\\brief Decode a DNP3 object. + * + * \\\\retval 0 on success. On failure a positive integer corresponding + * to a DNP3 application layer event will be returned. + */ +int DNP3DecodeObject(int group, int variation, const uint8_t **buf, + uint32_t *len, uint8_t prefix_code, uint32_t start, + uint32_t count, DNP3PointList *points) +{ + int rc = 0; + + switch (DNP3_OBJECT_CODE(group, variation)) { +{% for object in objects %} + case DNP3_OBJECT_CODE({{object.group}}, {{object.variation}}): + rc = DNP3DecodeObjectG{{object.group}}V{{object.variation}}(buf, len, prefix_code, start, count, + points); + break; +{% endfor %} + default: + return DNP3_DECODER_EVENT_UNKNOWN_OBJECT; + } + + return rc ? 0 : DNP3_DECODER_EVENT_MALFORMED; +} + +""" + + try: + filename = "src/app-layer-dnp3-objects.c" + env = jinja2.Environment(trim_blocks=True, lstrip_blocks=True) + code = env.from_string(template).render(context) + content = open(filename).read() + content = re.sub( + "(%s).*(%s)" % (re.escape(IN_PLACE_START), re.escape(IN_PLACE_END)), + r"\1%s\n\2" % (code), content, 1, re.M | re.DOTALL) + open(filename, "w").write(content) + print("Updated %s." % (filename)) + except Exception as err: + print("Failed to update %s: %s" % (filename, err), file=sys.stderr) + sys.exit(1) + +def preprocess_object(obj): + + valid_keys = [ + "group", + "variation", + "constraints", + "extra_fields", + "fields", + "packed", + ] + + valid_field_keys = [ + "type", + "name", + "width", + "len_from_prefix", + "len_field", + "fields", + "size", + ] + + if "unimplemented" in obj: + print("Object not implemented: %s:%s: %s" % ( + str(obj["group"]), str(obj["variation"]), obj["unimplemented"])) + return None + + for key, val in obj.items(): + + if key not in valid_keys: + print("Invalid key '%s' in object %d:%d" % ( + key, obj["group"], obj["variation"]), file=sys.stderr) + sys.exit(1) + + for field in obj["fields"]: + + for key in field.keys(): + if key not in valid_field_keys: + print("Invalid key '%s' in object %d:%d" % ( + key, obj["group"], obj["variation"]), file=sys.stderr) + sys.exit(1) + + if "len_from_prefix" in field and field["len_from_prefix"]: + obj["_track_offset"] = True + break + + if field["type"] == "bstr8": + width = 0 + for subfield in field["fields"]: + width += int(subfield["width"]) + assert(width == 8) + + return obj + +def main(): + + definitions = yaml.load(open("scripts/dnp3-gen/dnp3-objects.yaml")) + print("Loaded %s objects." % (len(definitions["objects"]))) + definitions["objects"] = map(preprocess_object, definitions["objects"]) + + # Filter out unimplemented objects. + definitions["objects"] = [ + obj for obj in definitions["objects"] if obj != None] + + context = { + "raise": raise_helper, + "objects": definitions["objects"], + "is_integer_type": is_integer_type, + "f_to_type": to_type, + "f_has_freeable_types": has_freeable_types, + "command_line": " ".join(sys.argv), + } + + gen_object_structs(context) + gen_object_decoders(context) + generate(util_lua_dnp3_objects_c_template, + "src/util-lua-dnp3-objects.c", + context) + generate(output_json_dnp3_objects_template, + "src/output-json-dnp3-objects.c", + context) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/dnp3-gen/dnp3-objects.yaml b/scripts/dnp3-gen/dnp3-objects.yaml new file mode 100644 index 0000000000..529910f78d --- /dev/null +++ b/scripts/dnp3-gen/dnp3-objects.yaml @@ -0,0 +1,2995 @@ +objects: + + - group: 1 + variation: 1 + packed: true + fields: + - type: uint8 + name: state + width: 1 + + - group: 1 + variation: 2 + fields: + - type: bstr8 + fields: + - type: uint8 + name: online + width: 1 + - type: uint8 + name: restart + width: 1 + - name: comm_lost + type: uint8 + width: 1 + - name: remote_forced + type: uint8 + width: 1 + - name: local_forced + type: uint8 + width: 1 + - name: chatter_filter + type: uint8 + width: 1 + - name: reserved + type: uint8 + width: 1 + - name: state + type: uint8 + width: 1 + + - group: 2 + variation: 1 + fields: + - type: uint8 + name: state + width: 1 + + - group: 2 + variation: 2 + fields: + - type: bstr8 + fields: + - type: uint8 + name: online + width: 1 + - type: uint8 + name: restart + width: 1 + - name: comm_lost + type: uint8 + width: 1 + - name: remote_forced + type: uint8 + width: 1 + - name: local_forced + type: uint8 + width: 1 + - name: chatter_filter + type: uint8 + width: 1 + - name: reserved + type: uint8 + width: 1 + - name: state + type: uint8 + width: 1 + - type: dnp3time + name: timestamp + + - group: 2 + variation: 3 + fields: + - type: bstr8 + fields: + - type: uint8 + name: online + width: 1 + - type: uint8 + name: restart + width: 1 + - name: comm_lost + type: uint8 + width: 1 + - name: remote_forced + type: uint8 + width: 1 + - name: local_forced + type: uint8 + width: 1 + - name: chatter_filter + type: uint8 + width: 1 + - name: reserved + type: uint8 + width: 1 + - name: state + type: uint8 + width: 1 + - type: uint16 + name: timestamp + + - group: 3 + variation: 1 + packed: true + fields: + - type: uint8 + name: state + width: 2 + + - group: 3 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: chatter_filter + width: 1 + - name: state + width: 2 + + - group: 4 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: chatter_filter + width: 1 + - name: state + width: 2 + + - group: 4 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: chatter_filter + width: 1 + - name: state + width: 2 + - type: dnp3time + name: timestamp + + - group: 4 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: chatter_filter + width: 1 + - name: state + width: 2 + - type: uint16 + name: relative_time_ms + + - group: 10 + variation: 1 + packed: true + fields: + - type: uint8 + name: state + width: 1 + + - group: 10 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - name: state + width: 1 + + - group: 11 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - name: state + width: 1 + + - group: 11 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - name: state + width: 1 + - type: dnp3time + name: timestamp + + - group: 12 + variation: 1 + fields: + - type: bstr8 + fields: + - name: op_type + type: uint8 + width: 4 + - name: qu + type: uint8 + width: 1 + - name: cr + type: uint8 + width: 1 + - name: tcc + type: uint8 + width: 2 + - name: count + type: uint8 + - name: ontime + type: uint32 + - name: offtime + type: uint32 + - type: bstr8 + fields: + - name: status_code + type: uint8 + width: 7 + - name: reserved + type: uint32 + width: 1 + + - group: 12 + variation: 2 + fields: + - type: bstr8 + fields: + - name: op_type + type: uint8 + width: 4 + - name: qu + type: uint8 + width: 1 + - name: cr + type: uint8 + width: 1 + - name: tcc + type: uint8 + width: 2 + - name: count + type: uint8 + - name: ontime + type: uint32 + - name: offtime + type: uint32 + - type: bstr8 + fields: + - name: status_code + type: uint8 + width: 7 + - name: reserved + type: uint32 + width: 1 + + - group: 12 + variation: 3 + packed: true + fields: + - type: uint8 + name: point + width: 1 + + - group: 13 + variation: 1 + fields: + - type: bstr8 + fields: + - type: uint8 + width: 7 + name: status_code + - type: uint8 + width: 1 + name: commanded_state + + - group: 13 + variation: 2 + fields: + - type: bstr8 + fields: + - type: uint8 + width: 7 + name: status_code + - type: uint8 + width: 1 + name: commanded_state + - type: dnp3time + name: timestamp + + - group: 20 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint32 + name: count + + - group: 20 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint16 + name: count + + - group: 20 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + + - group: 20 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + + - group: 20 + variation: 5 + fields: + - type: uint32 + name: count + + - group: 20 + variation: 6 + fields: + - type: uint16 + name: count + + - group: 20 + variation: 7 + fields: + - type: uint32 + name: count + + - group: 20 + variation: 8 + fields: + - type: uint16 + name: count + + - group: 21 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint32 + name: count + + - group: 21 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint16 + name: count + + - group: 21 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + + - group: 21 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + + - group: 21 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + - type: dnp3time + name: timestamp + + - group: 21 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + - type: dnp3time + name: timestamp + + - group: 21 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + - type: dnp3time + name: timestamp + + - group: 21 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + - type: dnp3time + name: timestamp + + - group: 21 + variation: 9 + fields: + - type: uint32 + name: count + + - group: 21 + variation: 10 + fields: + - type: uint16 + name: count + + - group: 21 + variation: 11 + fields: + - type: uint32 + name: count + + - group: 21 + variation: 12 + fields: + - type: uint16 + name: count + + - group: 22 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint32 + name: count + + - group: 22 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint16 + name: count + + - group: 22 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + + - group: 22 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + + - group: 22 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + - type: dnp3time + name: timestamp + + - group: 22 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint16 + name: count + - type: dnp3time + name: timestamp + + - group: 22 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + - type: dnp3time + name: timestamp + + - group: 22 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + - type: dnp3time + name: timestamp + + - group: 23 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint32 + name: count + + - group: 23 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + + - group: 23 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + + - group: 23 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + + - group: 23 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint32 + name: count + - type: dnp3time + name: timestamp + + - group: 23 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: discontinuity + width: 1 + - name: reserved0 + width: 1 + - type: uint16 + name: count + - type: dnp3time + name: timestamp + + - group: 23 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint32 + name: count + - type: dnp3time + name: timestamp + + - group: 23 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: rollover + width: 1 + - name: reserved0 + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: count + - type: dnp3time + name: timestamp + + - group: 30 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + + - group: 30 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + + - group: 30 + variation: 3 + fields: + - type: int32 + name: value + + - group: 30 + variation: 4 + fields: + - type: int16 + name: value + + - group: 30 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + + - group: 30 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + + - group: 31 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + + - group: 31 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + + - group: 31 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + - type: dnp3time + name: timestamp + + - group: 31 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + - type: dnp3time + name: timestamp + + - group: 31 + variation: 5 + fields: + - type: int32 + name: value + + - group: 31 + variation: 6 + fields: + - type: int16 + name: value + + - group: 31 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + + - group: 31 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + + - group: 32 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + + - group: 32 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + + - group: 32 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + - type: dnp3time + name: timestamp + + - group: 32 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + - type: dnp3time + name: timestamp + + - group: 32 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + + - group: 32 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + + - group: 32 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + - type: dnp3time + name: timestamp + + - group: 32 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + # XXX Spec says FLT32, pretty sure its FLT64 and OpenDNP uses a 64 bit + # float as well. + - type: flt64 + name: value + - type: dnp3time + name: timestamp + + - group: 33 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + + - group: 33 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + + - group: 33 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + - type: dnp3time + name: timestamp + + - group: 33 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + - type: dnp3time + name: timestamp + + - group: 33 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + + - group: 33 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + + - group: 33 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + - type: dnp3time + name: timestamp + + - group: 33 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + # XXX Spec says FLT32, I think its FLT64. + - type: flt64 + name: value + - type: dnp3time + name: timestamp + + - group: 34 + variation: 1 + fields: + - type: uint16 + name: deadband_value + + - group: 34 + variation: 2 + fields: + - type: uint32 + name: deadband_value + + - group: 34 + variation: 3 + fields: + - type: flt32 + name: deadband_value + + - group: 40 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + + - group: 40 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + + - group: 40 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + + - group: 40 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + + - group: 41 + variation: 1 + fields: + - type: int32 + name: value + - type: uint8 + name: control_status + + - group: 41 + variation: 2 + fields: + - type: int16 + name: value + - type: uint8 + name: control_status + + - group: 41 + variation: 3 + fields: + - type: flt32 + name: value + - type: uint8 + name: control_status + + - group: 41 + variation: 4 + fields: + - type: flt64 + name: value + - type: uint8 + name: control_status + + - group: 42 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + + - group: 42 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + + - group: 42 + variation: 3 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int32 + name: value + - type: dnp3time + name: timestamp + + - group: 42 + variation: 4 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: int16 + name: value + - type: dnp3time + name: timestamp + + - group: 42 + variation: 5 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + + - group: 42 + variation: 6 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + + - group: 42 + variation: 7 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt32 + name: value + - type: dnp3time + name: timestamp + + - group: 42 + variation: 8 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: over_range + width: 1 + - name: reference_err + width: 1 + - name: reserved0 + width: 1 + - type: flt64 + name: value + - type: dnp3time + name: timestamp + + - group: 43 + variation: 1 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: int32 + name: commanded_value + + - group: 43 + variation: 2 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: int16 + name: commanded_value + + - group: 43 + variation: 3 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: int32 + name: commanded_value + - type: dnp3time + name: timestamp + + - group: 43 + variation: 4 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: int16 + name: commanded_value + - type: dnp3time + name: timestamp + + - group: 43 + variation: 5 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: flt32 + name: commanded_value + + - group: 43 + variation: 6 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: flt64 + name: commanded_value + + - group: 43 + variation: 7 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: flt32 + name: commanded_value + - type: dnp3time + name: timestamp + + - group: 43 + variation: 8 + fields: + - type: bstr8 + fields: + - type: uint8 + name: status_code + width: 7 + - type: uint8 + name: reserved0 + width: 1 + - type: flt64 + name: commanded_value + - type: dnp3time + name: timestamp + + - group: 50 + variation: 1 + fields: + - type: dnp3time + name: timestamp + + - group: 50 + variation: 2 + fields: + - type: dnp3time + name: timestamp + - type: uint32 + name: interval + + - group: 50 + variation: 3 + fields: + - type: dnp3time + name: timestamp + + - group: 50 + variation: 4 + fields: + - type: dnp3time + name: timestamp + - type: uint32 + name: interval_count + - type: uint8 + name: interval_units + + - group: 51 + variation: 1 + fields: + - type: dnp3time + name: timestamp + + - group: 51 + variation: 2 + fields: + - type: dnp3time + name: timestamp + + - group: 52 + variation: 1 + fields: + - type: uint16 + name: delay_secs + + - group: 52 + variation: 2 + fields: + - type: uint16 + name: delay_ms + + # 1815-2012: New DNP3 implementation should avoid using this object. + - group: 70 + variation: 1 + fields: + - type: uint16 + name: filename_size + - type: uint8 + name: filetype_code + - type: uint8 + name: attribute_code + - type: uint16 + name: start_record + - type: uint16 + name: end_record + - type: uint32 + name: file_size + - type: dnp3time + name: created_timestamp + # XXX Actually bit flags. + - type: uint16 + name: permission + - type: uint32 + name: file_id + - type: uint32 + name: owner_id + - type: uint32 + name: group_id + - type: uint8 + name: file_function_code + - type: uint8 + name: status_code + - type: chararray + name: filename + size: 65535 + len_field: filename_size + - type: uint16 + name: data_size + - type: chararray + name: data + size: 65535 + len_field: data_size + + - group: 70 + variation: 2 + fields: + - type: uint16 + name: username_offset + - type: uint16 + name: username_size + - type: uint16 + name: password_offset + - type: uint16 + name: password_size + - type: uint32 + name: authentication_key + - type: chararray + name: username + size: 65535 + len_field: username_size + - type: chararray + name: password + size: 65535 + len_field: password_size + + - group: 70 + variation: 3 + fields: + - type: uint16 + name: filename_offset + - type: uint16 + name: filename_size + - type: dnp3time + name: created + # XXX Break out to bit flags. + - type: uint16 + name: permissions + - type: uint32 + name: authentication_key + - type: uint32 + name: file_size + - type: uint16 + name: operational_mode + - type: uint16 + name: maximum_block_size + - type: uint16 + name: request_id + - type: chararray + name: filename + size: 65535 + len_field: filename_size + + # Checked. + - group: 70 + variation: 4 + constraints: + require_size_prefix: true + extra_fields: + - type: uint8 + name: optional_text_len + fields: + - type: uint32 + name: file_handle + - type: uint32 + name: file_size + - type: uint16 + name: maximum_block_size + - type: uint16 + name: request_id + - type: uint8 + name: status_code + - type: chararray + name: optional_text + size: 0xff + len_from_prefix: true + len_field: optional_text_len + + # Checked. + - group: 70 + variation: 5 + constraints: + require_size_prefix: true + extra_fields: + - type: uint8 + name: file_data_len + fields: + - type: uint32 + name: file_handle + - type: uint32 + name: block_number # Includes "last" flag. + - type: chararray + name: file_data + size: 0xff + len_from_prefix: true + len_field: file_data_len + + # Checked. + - group: 70 + variation: 6 + constraints: + require_size_prefix: true + extra_fields: + - type: uint8 + name: optional_text_len + fields: + - type: uint32 + name: file_handle + - type: uint32 + name: block_number # Includes "last" flag. + - type: uint8 + name: status_code + - type: chararray + name: optional_text + size: 0xff + len_from_prefix: true + len_field: optional_text_len + + # Checked. + - group: 70 + variation: 7 + fields: + - type: uint16 + name: filename_offset + - type: uint16 + name: filename_size + - type: uint16 + name: file_type + - type: uint32 + name: file_size + - type: dnp3time + name: created_timestamp + - type: uint16 + name: permissions + - type: uint16 + name: request_id + - type: chararray + name: filename + size: 0xffff + len_field: filename_size + + # Checked. + - group: 70 + variation: 8 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: file_specification_len + fields: + - type: chararray + name: file_specification + size: 0xffff + len_from_prefix: true + len_field: file_specification_len + + # Checked. + - group: 80 + variation: 1 + packed: true + fields: + - type: uint8 + name: state + width: 1 + + # Checked. + - group: 81 + variation: 1 + fields: + - type: bstr8 + fields: + - type: uint8 + width: 7 + name: fill_percentage + - type: uint8 + width: 1 + name: overflow_state + - type: uint8 + name: group + - type: uint8 + name: variation + + - group: 82 + variation: 1 + unimplemented: object header types not yet supported + + - group: 83 + variation: 1 + fields: + - type: vstr4 + name: vendor_code + - type: uint16 + name: object_id + - type: uint16 + name: length + - type: bytearray + name: data_objects + len_field: length + + - group: 83 + variation: 2 + unimplemented: set objects not yet supported + + - group: 85 + variation: 1 + unimplemented: set objects not yet supported + + - group: 86 + variation: 1 + unimplemented: set objects not yet supported + + # Checked. + - group: 86 + variation: 2 + fields: + - type: bstr8 + fields: + - name: rd + width: 1 + - name: wr + width: 1 + - name: st + width: 1 + - name: ev + width: 1 + - name: df + width: 1 + - name: padding0 + width: 1 + - name: padding1 + width: 1 + - name: padding2 + width: 1 + + - group: 86 + variation: 3 + unimplemented: set objects not yet supported + + - group: 87 + variation: 1 + unimplemented: set objects not yet supported + + - group: 88 + variation: 1 + unimplemented: set objects not yet supported + + - group: 90 + variation: 1 + unimplemented: set objects not yet supported + + - group: 91 + variation: 1 + unimplemented: set objects not yet supported + + - group: 100 + variation: "*" + unimplemented: obsolete object type + + - group: 101 + variation: 1 + unimplemented: bcd types not yet supported + + - group: 101 + variation: 2 + unimplemented: bcd types not yet supported + + - group: 101 + variation: 3 + unimplemented: bcd types not yet supported + + # Checked. + - group: 102 + variation: 1 + fields: + - type: uint8 + name: value + + - group: 110 + variation: "*" + unimplemented: variation all not yet supported + + - group: 111 + variation: "*" + unimplemented: variation all not yet supported + + - group: 112 + variation: "*" + unimplemented: variation all not yet supported + + - group: 113 + variation: "*" + unimplemented: variation all not yet supported + + # Checked. + - group: 120 + variation: 1 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: challenge_data_len + fields: + - type: uint32 + name: csq + - type: uint16 + name: usr + - type: uint8 + name: mal + - type: uint8 + name: reason + - type: bytearray + name: challenge_data + len_from_prefix: true + len_field: challenge_data_len + + - group: 120 + variation: 2 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: mac_value_len + fields: + - type: uint32 + name: csq + - type: uint16 + name: usr + - type: bytearray + name: mac_value + len_from_prefix: true + len_field: mac_value_len + + # Checked. + - group: 120 + variation: 3 + fields: + - type: uint32 + name: csq + - type: uint16 + name: user_number + + # Checked. + - group: 120 + variation: 4 + fields: + - type: uint16 + name: user_number + + # Checked. + - group: 120 + variation: 5 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: mac_value_len + fields: + - type: uint32 + name: ksq + - type: uint16 + name: user_number + - type: uint8 + name: key_wrap_alg + - type: uint8 + name: key_status + - type: uint8 + name: mal + - type: uint16 + name: challenge_data_len + - type: bytearray + name: challenge_data + len_field: challenge_data_len + - type: bytearray + name: mac_value + len_from_prefix: true + len_field: mac_value_len + + # Checked. + - group: 120 + variation: 6 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: wrapped_key_data_len + fields: + - type: uint24 + name: ksq + - type: uint16 + name: usr + - type: bytearray + name: wrapped_key_data + len_from_prefix: true + len_field: wrapped_key_data_len + + # Checked. + - group: 120 + variation: 7 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: error_text_len + fields: + - type: uint32 + name: sequence_number + - type: uint16 + name: usr + - type: uint16 + name: association_id + - type: uint8 + name: error_code + - type: dnp3time + name: time_of_error + - type: chararray + name: error_text + size: 65535 + len_from_prefix: true + len_field: error_text_len + + # Checked. + - group: 120 + variation: 8 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: certificate_len + fields: + - type: uint8 + name: key_change_method + - type: uint8 + name: certificate_type + - type: bytearray + name: certificate + len_from_prefix: true + len_field: certificate_len + + # Checked. + - group: 120 + variation: 9 + extra_fields: + - type: uint16 + name: mac_value_len + fields: + - type: bytearray + name: mac_value + len_from_prefix: true + len_field: mac_value_len + + # Checked. + - group: 120 + variation: 10 + constraints: + require_prefix_code: 0x5 + fields: + - type: uint8 + name: key_change_method + - type: uint8 + name: operation + - type: uint32 + name: scs + - type: uint16 + name: user_role + - type: uint16 + name: user_role_expiry_interval + - type: uint16 + name: username_len + - type: uint16 + name: user_public_key_len + - type: uint16 + name: certification_data_len + - type: chararray + name: username + size: 65535 + len_field: username_len + - type: bytearray + name: user_public_key + len_field: user_public_key_len + - type: bytearray + name: certification_data + len_field: certification_data_len + + # Checked. + - group: 120 + variation: 11 + constraints: + require_prefix_code: 0x5 + fields: + - type: uint8 + name: key_change_method + - type: uint16 + name: username_len + - type: uint16 + name: master_challenge_data_len + - type: chararray + size: 65535 + name: username + len_field: username_len + - type: bytearray + name: master_challenge_data + len_field: master_challenge_data_len + + # Checked. + - group: 120 + variation: 12 + constraints: + require_prefix_code: 0x5 + fields: + - type: uint32 + name: ksq + - type: uint16 + name: user_number + - type: uint16 + name: challenge_data_len + - type: bytearray + name: challenge_data + len_field: challenge_data_len + + # Checked. + - group: 120 + variation: 13 + constraints: + require_prefix_code: 0x5 + fields: + - type: uint32 + name: ksq + - type: uint16 + name: user_number + - type: uint16 + name: encrypted_update_key_len + - type: bytearray + name: encrypted_update_key_data + len_field: encrypted_update_key_len + + # Checked. + - group: 120 + variation: 14 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint16 + name: digital_signature_len + fields: + - type: bytearray + name: digital_signature + len_from_prefix: true + len_field: digital_signature_len + + # Checked. + - group: 120 + variation: 15 + constraints: + require_prefix_code: 0x5 + extra_fields: + - type: uint32 + name: mac_len + fields: + - type: bytearray + name: mac + len_field: mac_len + len_from_prefix: true + + # Checked. + - group: 121 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: reserved0 + width: 1 + - name: discontinuity + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: association_id + - type: uint32 + name: count_value + + # Checked. + - group: 122 + variation: 1 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: reserved0 + width: 1 + - name: discontinuity + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: association_id + - type: uint32 + name: count_value + + # Checked. + - group: 122 + variation: 2 + fields: + - type: bstr8 + fields: + - name: online + width: 1 + - name: restart + width: 1 + - name: comm_lost + width: 1 + - name: remote_forced + width: 1 + - name: local_forced + width: 1 + - name: reserved0 + width: 1 + - name: discontinuity + width: 1 + - name: reserved1 + width: 1 + - type: uint16 + name: association_id + - type: uint32 + name: count_value + - type: dnp3time + name: timestamp