From 306d4fa6f9f480fb70b099c7d6440d70aac38952 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Thu, 1 Aug 2013 16:30:12 -0700 Subject: [PATCH 01/13] Pluginizing the DNP3 analyzer, plus a basic script logging requests and replies. Almost ready, but now 1 test fails again ... [Note I broke git history by copying over the files into a new branch.] --- scripts/base/init-default.bro | 1 + scripts/base/protocols/dnp3/__load__.bro | 3 + scripts/base/protocols/dnp3/consts.bro | 49 + scripts/base/protocols/dnp3/dpd.sig | 15 + scripts/base/protocols/dnp3/main.bro | 73 + src/analyzer/protocol/CMakeLists.txt | 1 + src/analyzer/protocol/dnp3/CMakeLists.txt | 10 + src/analyzer/protocol/dnp3/DNP3.cc | 375 +++++ src/analyzer/protocol/dnp3/DNP3.h | 56 + src/analyzer/protocol/dnp3/Plugin.cc | 10 + src/analyzer/protocol/dnp3/dnp3-analyzer.pac | 969 +++++++++++ src/analyzer/protocol/dnp3/dnp3-objects.pac | 1451 +++++++++++++++++ src/analyzer/protocol/dnp3/dnp3-protocol.pac | 257 +++ src/analyzer/protocol/dnp3/dnp3.pac | 16 + src/analyzer/protocol/dnp3/events.bif | 241 +++ .../coverage | 1 + .../dnp3.log | 10 + .../output | 7 + .../coverage | 1 + .../dnp3.log | 10 + .../output | 7 + .../coverage | 1 + .../dnp3.log | 10 + .../output | 9 + .../coverage | 1 + .../dnp3.log | 14 + .../output | 45 + .../coverage | 1 + .../dnp3.log | 12 + .../output | 29 + .../coverage | 1 + .../dnp3.log | 10 + .../output | 88 + .../coverage | 1 + .../dnp3.log | 10 + .../output | 4 + .../coverage | 1 + .../dnp3.log | 11 + .../output | 22 + .../coverage | 1 + .../dnp3.log | 10 + .../output | 6 + .../coverage | 1 + .../scripts.base.protocols.dnp3.events/output | 574 +++++++ testing/btest/Traces/dnp3/dnp3.trace | Bin 0 -> 18790 bytes .../btest/Traces/dnp3/dnp3_del_measure.pcap | Bin 0 -> 804 bytes testing/btest/Traces/dnp3/dnp3_en_spon.pcap | Bin 0 -> 807 bytes testing/btest/Traces/dnp3/dnp3_file_del.pcap | Bin 0 -> 920 bytes testing/btest/Traces/dnp3/dnp3_file_read.pcap | Bin 0 -> 3489 bytes .../btest/Traces/dnp3/dnp3_file_write.pcap | Bin 0 -> 2868 bytes testing/btest/Traces/dnp3/dnp3_read.pcap | Bin 0 -> 928 bytes .../btest/Traces/dnp3/dnp3_read_p20001.pcap | Bin 0 -> 928 bytes testing/btest/Traces/dnp3/dnp3_rec_time.pcap | Bin 0 -> 798 bytes .../Traces/dnp3/dnp3_select_operate.pcap | Bin 0 -> 1120 bytes testing/btest/Traces/dnp3/dnp3_write.pcap | Bin 0 -> 808 bytes .../base/protocols/dnp3/dnp3_del_measure.bro | 9 + .../base/protocols/dnp3/dnp3_en_spon.bro | 9 + .../base/protocols/dnp3/dnp3_file_del.bro | 9 + .../base/protocols/dnp3/dnp3_file_read.bro | 9 + .../base/protocols/dnp3/dnp3_file_write.bro | 9 + .../scripts/base/protocols/dnp3/dnp3_read.bro | 9 + .../base/protocols/dnp3/dnp3_rec_time.bro | 9 + .../protocols/dnp3/dnp3_select_operate.bro | 9 + .../base/protocols/dnp3/dnp3_write.bro | 9 + .../scripts/base/protocols/dnp3/events.bro | 266 +++ 65 files changed, 4772 insertions(+) create mode 100644 scripts/base/protocols/dnp3/__load__.bro create mode 100644 scripts/base/protocols/dnp3/consts.bro create mode 100644 scripts/base/protocols/dnp3/dpd.sig create mode 100644 scripts/base/protocols/dnp3/main.bro create mode 100644 src/analyzer/protocol/dnp3/CMakeLists.txt create mode 100644 src/analyzer/protocol/dnp3/DNP3.cc create mode 100644 src/analyzer/protocol/dnp3/DNP3.h create mode 100644 src/analyzer/protocol/dnp3/Plugin.cc create mode 100644 src/analyzer/protocol/dnp3/dnp3-analyzer.pac create mode 100644 src/analyzer/protocol/dnp3/dnp3-objects.pac create mode 100644 src/analyzer/protocol/dnp3/dnp3-protocol.pac create mode 100644 src/analyzer/protocol/dnp3/dnp3.pac create mode 100644 src/analyzer/protocol/dnp3/events.bif create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_write/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_write/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_write/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_read/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_read/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_read/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_rec_time/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_rec_time/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_rec_time/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_select_operate/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_select_operate/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_select_operate/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_write/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_write/dnp3.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_write/output create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.events/coverage create mode 100644 testing/btest/Baseline/scripts.base.protocols.dnp3.events/output create mode 100644 testing/btest/Traces/dnp3/dnp3.trace create mode 100644 testing/btest/Traces/dnp3/dnp3_del_measure.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_en_spon.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_file_del.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_file_read.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_file_write.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_read.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_read_p20001.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_rec_time.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_select_operate.pcap create mode 100644 testing/btest/Traces/dnp3/dnp3_write.pcap create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_del_measure.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_en_spon.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_file_del.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_file_read.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_file_write.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_read.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_rec_time.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_select_operate.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/dnp3_write.bro create mode 100644 testing/btest/scripts/base/protocols/dnp3/events.bro diff --git a/scripts/base/init-default.bro b/scripts/base/init-default.bro index 6e348cfffd..749bd0a024 100644 --- a/scripts/base/init-default.bro +++ b/scripts/base/init-default.bro @@ -39,6 +39,7 @@ @load base/frameworks/tunnels @load base/protocols/conn +@load base/protocols/dnp3 @load base/protocols/dns @load base/protocols/ftp @load base/protocols/http diff --git a/scripts/base/protocols/dnp3/__load__.bro b/scripts/base/protocols/dnp3/__load__.bro new file mode 100644 index 0000000000..0f41578f8a --- /dev/null +++ b/scripts/base/protocols/dnp3/__load__.bro @@ -0,0 +1,3 @@ +@load ./main + +@load-sigs ./dpd.sig diff --git a/scripts/base/protocols/dnp3/consts.bro b/scripts/base/protocols/dnp3/consts.bro new file mode 100644 index 0000000000..1b2a6206ef --- /dev/null +++ b/scripts/base/protocols/dnp3/consts.bro @@ -0,0 +1,49 @@ + +module DNP3; + +export { + ## Standard defined Modbus function codes. + const function_codes = { + # Requests. + [0x00] = "CONFIRM", + [0x01] = "READ", + [0x02] = "WRITE", + [0x03] = "SELECT", + [0x04] = "OPERATE", + [0x05] = "DIRECT_OPERATE", + [0x06] = "DIRECT_OPERATE_NR", + [0x07] = "IMMED_FREEZE", + [0x08] = "IMMED_FREEZE_NR", + [0x09] = "FREEZE_CLEAR", + [0x0a] = "FREEZE_CLEAR_NR", + [0x0b] = "FREEZE_AT_TIME", + [0x0c] = "FREEZE_AT_TIME_NR", + [0x0d] = "COLD_RESTART", + [0x0e] = "WARM_RESTART", + [0x0f] = "INITIALIZE_DATA", + [0x10] = "INITIALIZE_APPL", + [0x11] = "START_APPL", + [0x12] = "STOP_APPL", + [0x13] = "SAVE_CONFIG", + [0x14] = "ENABLE_UNSOLICITED", + [0x15] = "DISABLE_UNSOLICITED", + [0x16] = "ASSIGN_CLASS", + [0x17] = "DELAY_MEASURE", + [0x18] = "RECORD_CURRENT_TIME", + [0x19] = "OPEN_FILE", + [0x1a] = "CLOSE_FILE", + [0x1b] = "DELETE_FILE", + [0x1c] = "GET_FILE_INFO", + [0x1d] = "AUTHENTICATE_FILE", + [0x1e] = "ABORT_FILE", + [0x1f] = "ACTIVATE_CONFIG", + [0x20] = "AUTHENTICATE_REQ", + [0x21] = "AUTHENTICATE_ERR", + + # Responses. + [0x81] = "RESPONSE", + [0x82] = "UNSOLICITED_RESPONSE", + [0x83] = "AUTHENTICATE_RESP", + } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; +} + diff --git a/scripts/base/protocols/dnp3/dpd.sig b/scripts/base/protocols/dnp3/dpd.sig new file mode 100644 index 0000000000..691c47d0a9 --- /dev/null +++ b/scripts/base/protocols/dnp3/dpd.sig @@ -0,0 +1,15 @@ + +signature dpd_dnp3_client { + ip-proto == tcp + # dnp3 packets always starts with 0x05 0x64 . + payload /\x05\0x64/ + tcp-state originator +} + +signature dpd_dnp3_server { + ip-proto == tcp + # dnp3 packets always starts with 0x05 0x64 . + payload /\x05\x64/ + tcp-state responder + enable "dnp3" +} diff --git a/scripts/base/protocols/dnp3/main.bro b/scripts/base/protocols/dnp3/main.bro new file mode 100644 index 0000000000..2b014bcc67 --- /dev/null +++ b/scripts/base/protocols/dnp3/main.bro @@ -0,0 +1,73 @@ +##! A very basic DNP3 analysis script that just logs requests and replies. + +module DNP3; + +@load ./consts + +export { + redef enum Log::ID += { LOG }; + + type Info: record { + ## Time of the request. + ts: time &log; + ## Unique identifier for the connnection. + uid: string &log; + ## Identifier for the connection. + id: conn_id &log; + ## The name of the function message in the request. + fc_request: string &log &optional; + ## The name of the function message in the reply. + fc_reply: string &log &optional; + ## The response's "internal indication number". + iin: count &log &optional; + }; + + ## Event that can be handled to access the DNP3 record as it is sent on + ## to the logging framework. + global log_dnp3: event(rec: Info); +} + +redef record connection += { + dnp3: Info &optional; +}; + +const ports = { 502/tcp }; +redef likely_server_ports += { ports }; + +event bro_init() &priority=5 + { + Log::create_stream(DNP3::LOG, [$columns=Info, $ev=log_dnp3]); + Analyzer::register_for_ports(Analyzer::ANALYZER_DNP3, ports); + } + +event dnp3_application_request_header(c: connection, is_orig: bool, fc: count) + { + if ( ! c?$dnp3 ) + c$dnp3 = [$ts=network_time(), $uid=c$uid, $id=c$id]; + + c$dnp3$ts = network_time(); + c$dnp3$fc_request = function_codes[fc]; + } + +event dnp3_application_response_header(c: connection, is_orig: bool, fc: count, iin: count) + { + if ( ! c?$dnp3 ) + c$dnp3 = [$ts=network_time(), $uid=c$uid, $id=c$id]; + + c$dnp3$ts = network_time(); + c$dnp3$fc_reply = function_codes[fc]; + c$dnp3$iin = iin; + + Log::write(LOG, c$dnp3); + + delete c$dnp3; + } + +event connection_state_remove(c: connection) &priority=-5 + { + if ( ! c?$dnp3 ) + return; + + Log::write(LOG, c$dnp3); + delete c$dnp3; + } diff --git a/src/analyzer/protocol/CMakeLists.txt b/src/analyzer/protocol/CMakeLists.txt index a4e170f52b..fc63aa4b66 100644 --- a/src/analyzer/protocol/CMakeLists.txt +++ b/src/analyzer/protocol/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(bittorrent) add_subdirectory(conn-size) add_subdirectory(dce-rpc) add_subdirectory(dhcp) +add_subdirectory(dnp3) add_subdirectory(dns) add_subdirectory(file) add_subdirectory(finger) diff --git a/src/analyzer/protocol/dnp3/CMakeLists.txt b/src/analyzer/protocol/dnp3/CMakeLists.txt new file mode 100644 index 0000000000..b1c0f0b760 --- /dev/null +++ b/src/analyzer/protocol/dnp3/CMakeLists.txt @@ -0,0 +1,10 @@ + +include(BroPlugin) + +include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +bro_plugin_begin(Bro DNP3) +bro_plugin_cc(DNP3.cc Plugin.cc) +bro_plugin_bif(events.bif) +bro_plugin_pac(dnp3.pac dnp3-analyzer.pac dnp3-protocol.pac dnp3-objects.pac) +bro_plugin_end() diff --git a/src/analyzer/protocol/dnp3/DNP3.cc b/src/analyzer/protocol/dnp3/DNP3.cc new file mode 100644 index 0000000000..ec0b02ba37 --- /dev/null +++ b/src/analyzer/protocol/dnp3/DNP3.cc @@ -0,0 +1,375 @@ +// +// DNP3 was initially used over serial links; it defined its own application +// layer, transport layer, and data link layer. This hierarchy cannot be +// mapped to the TCP/IP stack directly. As a result, all three DNP3 layers +// are packed together as a single application layer payload over the TCP +// layer. Each DNP3 packet in the application layer may look like this DNP3 +// Packet: +// +// DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer +// +// (This hierarchy can be viewed in the Wireshark visually.) +// +// === Background on DNP3 +// +// 1. Basic structure of DNP3 Protocol over serial links. This information +// can be found in detail in +// +// DNP3 Specification Volume 2, Part 1 Basic, Application Layer +// DNP3 Specification Volume 4, Data Link Layer +// +// Traditionally, the DNP3 Application Layer in serial links contains a +// "DNP3 Application Layer Fragment". The data that is parsed by the end +// device and then executed. As the "DNP3 Application Layer Fragment" can +// be long (>255 bytes), it may be trunkcated and carried in different +// DNP3 Application Layer of more than one DNP3 packets. +// +// So we may find a long DNP3 Application Layer Fragment to be transmitted in the following format +// +// DNP3 Packet #1 : DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer #1 +// DNP3 Packet #2 : DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer #2 +// .... +// DNP3 Packet #n : DNP3 Link Layer | DNP3 Transport Layer | DNP3 Application Layer #n +// +// So to get the whole DNP3 application layer fragment, we concatenate +// each DNP3 Application Layer Data into a logic DNP3 Application Layer +// Fragment: +// +// DNP3 Application Layer #1 + DNP3 Application Layer #2 + ... + DNP3 Application Layer #n +// +// 2. Packing DNP3 Network Packet into TCP/IP stack +// +// We will call the original DNP3 Link Layer, Transport Layer and Application +// Layer used in serial link as Pseudo Link Layer, Pseudo Transport Layer and +// Pseudo Application Layer. +// +// For a long DNP3 application layer fragment, we may find it tramistted +// over IP network in the following format: +// +// Network Packet #1 : TCP Header | DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer #1 +// Network Packet #2 : TCP Header | DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer #2 +// .... +// Network Packet #n : TCP Header | DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer #n +// +// === Challenges of Writing DNP3 Analyzer on Binpac === +// +// The detailed structure of the DNP3 Link Layer is: +// +// 0x05 0x64 Len Ctrl Dest_LSB Dest_MSB Src_LSB Src_MSB CRC_LSB CRC_MSB +// +// Each field is a byte; LSB: least significant byte; MSB: most significatn byte. +// +// "Len" indicates the length of the byte stream right after this field +// (excluding CRC fields) in the current DNP3 packet. +// +// Since "Len" is of size one byte, the largest length it can represent is +// 255 bytes. The larget DNP3 Application Layer size is "255 - 5 + size of +// all CRC fields". "minus 5" is coming from the 5 bytes after "Len" field in +// the DNP3 Link Layer, i.e. Ctrl Dest_LSB Dest_MSB Src_LSB Src_MSB Hence, +// the largest size of a DNP3 Packet (DNP3 Data Link Layer : DNP3 Transport +// Layer : DNP3 Application Layer) can only be 292 bytes. +// +// The "Len" field indicates the length of of a single chunk of DNP3 Psuedo +// Application Layer data instead of the whole DNP3 Application Layer +// Fragment. However, we can not know the whole length of the DNP3 +// Application Layer Fragment (which Binpac would normally need) until all +// chunks of Pseudo Application Layer Data are received. +// +// We hence exploit the internal flow_buffer class used in Binpac to buffer +// the application layer data until all chunk are received. The trick that I +// used require in-depth understanding on how Binpac parse the application +// layer data and perform incremental parsing. The codes that exploits +// flow_buffer class to buffer the application layer data is included in +// DNP3_ProcessData class. +// +// The binpac analyzer parses the DNP3 Application Layer Fragment. However, +// we manually add the original Pseudo Link Layer data as an additional +// header before the DNP3 Application Fragment. This helps to know how many +// bytes are in the current chunk of DNP3 application layer data (not the +// whole Application Layer Fragment). +// +// Graphically, the procedure is: +// +// DNP3 Packet : DNP3 Pseudo Data Link Layer : DNP3 Pseudo Transport Layer : DNP3 Pseudo Application Layer +// || || +// || (length field) || (original paylad byte stream) +// \/ \/ +// DNP3 Additional Header : Reassembled DNP3 Pseudo Application Layer Data +// || +// \/ +// Binpac DNP3 Analyzer + +#include "DNP3.h" +#include "analyzer/protocol/tcp/TCP_Reassembler.h" +#include "events.bif.h" + +using namespace analyzer::dnp3; + +const unsigned int PSEUDO_LENGTH_INDEX = 2; // index of len field of DNP3 Pseudo Link Layer +const unsigned int PSEUDO_CONTROL_FIELD_INDEX = 3; // index of ctrl field of DNP3 Pseudo Link Layer +const unsigned int PSEUDO_TRANSPORT_INDEX = 10; // index of DNP3 Pseudo Transport Layer +const unsigned int PSEUDO_APP_LAYER_INDEX = 11; // index of first DNP3 app-layer byte. +const unsigned int PSEUDO_TRANSPORT_LEN = 1; // length of DNP3 Transport Layer +const unsigned int PSEUDO_LINK_LAYER_LEN = 8; // length of DNP3 Pseudo Link Layer + +bool DNP3_Analyzer::crc_table_initialized = false; +unsigned int DNP3_Analyzer::crc_table[256]; + +DNP3_Analyzer::DNP3_Analyzer(Connection* c) : TCP_ApplicationAnalyzer("DNP3", c) + { + interp = new binpac::DNP3::DNP3_Conn(this); + + ClearEndpointState(true); + ClearEndpointState(false); + + if ( ! crc_table_initialized ) + PrecomputeCRCTable(); + } + +DNP3_Analyzer::~DNP3_Analyzer() + { + delete interp; + } + +void DNP3_Analyzer::Done() + { + TCP_ApplicationAnalyzer::Done(); + + interp->FlowEOF(true); + interp->FlowEOF(false); + } + +void DNP3_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + + try + { + if ( ! ProcessData(len, data, orig) ) + SetSkip(1); + } + + catch ( const binpac::Exception& e ) + { + SetSkip(1); + throw; + } + + } + +void DNP3_Analyzer::Undelivered(int seq, int len, bool orig) + { + TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + interp->NewGap(orig, len); + } + +void DNP3_Analyzer::EndpointEOF(tcp::TCP_Reassembler* endp) + { + TCP_ApplicationAnalyzer::EndpointEOF(endp); + interp->FlowEOF(endp->IsOrig()); + } + +bool DNP3_Analyzer::ProcessData(int len, const u_char* data, bool orig) + { + Endpoint* endp = orig ? &orig_state : &resp_state; + + while ( len ) + { + if ( endp->in_hdr ) + { + // We're parsing the DNP3 header and link layer, get that in full. + if ( ! AddToBuffer(endp, PSEUDO_APP_LAYER_INDEX, &data, &len) ) + return true; + + // The first two bytes must always be 0x0564. + if( endp->buffer[0] != 0x05 || endp->buffer[1] != 0x64 ) + { + Weird("dnp3_header_lacks_magic"); + return false; + } + + // Make sure header checksum is correct. + if ( ! CheckCRC(PSEUDO_LINK_LAYER_LEN, endp->buffer, endp->buffer + PSEUDO_LINK_LAYER_LEN, "header") ) + { + ProtocolViolation("broken_checksum"); + return false; + } + + // If the checksum works out, we're pretty certainly DNP3. + ProtocolConfirmation(); + + // Double check the direction in case the first + // received packet is a response. + u_char ctrl = endp->buffer[PSEUDO_CONTROL_FIELD_INDEX]; + + if ( orig != (bool)(ctrl & 0x80) ) + Weird("dnp3_unexpected_flow_direction"); + + // Update state. + endp->pkt_length = endp->buffer[PSEUDO_LENGTH_INDEX]; + endp->tpflags = endp->buffer[PSEUDO_TRANSPORT_INDEX]; + endp->in_hdr = false; // Now parsing application layer. + + // For the first packet, we submit the header to + // BinPAC. + if ( ++endp->pkt_cnt == 1 ) + interp->NewData(orig, endp->buffer, endp->buffer + PSEUDO_LINK_LAYER_LEN); + + } + + if ( ! endp->in_hdr ) + { + assert(endp->pkt_length); + + // We're parsing the DNP3 application layer, get that + // in full now as well. We calculate the number of + // raw bytes the application layer consists of from + // the packet length by determining how much 16-byte + // chunks fit in there, and then add 2 bytes CRC for + // each. + int n = PSEUDO_APP_LAYER_INDEX + (endp->pkt_length - 5) + ((endp->pkt_length - 5) / 16) * 2 + 2 - 1; + + if ( ! AddToBuffer(endp, n, &data, &len) ) + return true; + + // Parse the the application layer data. + if ( ! ParseAppLayer(endp) ) + return false; + + // Done with this packet, prepare for next. + endp->buffer_len = 0; + endp->in_hdr = true; + } + } + + return true; + } + +bool DNP3_Analyzer::AddToBuffer(Endpoint* endp, int target_len, const u_char** data, int* len) + { + if ( ! target_len ) + return true; + + int to_copy = min(*len, target_len - endp->buffer_len); + + memcpy(endp->buffer + endp->buffer_len, *data, to_copy); + *data += to_copy; + *len -= to_copy; + endp->buffer_len += to_copy; + + return endp->buffer_len == target_len; + } + +bool DNP3_Analyzer::ParseAppLayer(Endpoint* endp) + { + bool orig = (endp == &orig_state); + binpac::DNP3::DNP3_Flow* flow = orig ? interp->upflow() : interp->downflow(); + + u_char* data = endp->buffer + PSEUDO_TRANSPORT_INDEX; // The transport layer byte counts as app-layer it seems. + int len = endp->pkt_length - 5; + + //// DNP3 Packet : DNP3 Pseudo Link Layer | DNP3 Pseudo Transport Layer | DNP3 Pseudo Application Layer + //// DNP3 Serial Transport Layer data is always 1 byte. + //// Get FIN FIR seq field in transport header + //// FIR indicate whether the following DNP3 Serial Application Layer is first chunk of bytes or not + //// FIN indicate whether the following DNP3 Serial Application Layer is last chunk of bytes or not + + //// Get FIR and FIN field from the DNP3 Pseudo Transport Layer + + int is_first = (endp->tpflags & 0x40) >> 6; // Initial chunk of data in this packet. + int is_last = (endp->tpflags & 0x80) >> 7; // Last chunk of data in this packet. + + int transport = PSEUDO_TRANSPORT_LEN; + + int i = 0; + while ( len > 0 ) + { + int n = min(len, 16); + + // Make sure chunk has a correct checksum. + if ( ! CheckCRC(n, data, data + n, "app_chunk") ) + return false; + + // Pass on to BinPAC. + assert(data + n < endp->buffer + endp->buffer_len); + flow->flow_buffer()->BufferData(data + transport, data + n); + transport = 0; + + data += n + 2; + len -= n; + } + + if ( is_first ) + endp->encountered_first_chunk = true; + + if ( ! is_first && ! endp->encountered_first_chunk ) + { + // We lost the first chunk. + Weird("dnp3_first_application_layer_chunk_missing"); + return false; + } + + if ( is_last ) + { + flow->flow_buffer()->FinishBuffer(); + flow->FlowEOF(); + ClearEndpointState(orig); + } + + return true; + } + +void DNP3_Analyzer::ClearEndpointState(bool orig) + { + Endpoint* endp = orig ? &orig_state : &resp_state; + binpac::DNP3::DNP3_Flow* flow = orig ? interp->upflow() : interp->downflow(); + + endp->in_hdr = true; + endp->encountered_first_chunk = false; + endp->buffer_len = 0; + endp->pkt_length = 0; + endp->tpflags = 0; + endp->pkt_cnt = 0; + } + +bool DNP3_Analyzer::CheckCRC(int len, const u_char* data, const u_char* crc16, const char* where) + { + unsigned int crc = CalcCRC(len, data); + + if ( crc16[0] == (crc & 0xff) && crc16[1] == (crc & 0xff00) >> 8 ) + return true; + + Weird(fmt("dnp3_corrupt_%s_checksum", where)); + return false; + } + +void DNP3_Analyzer::PrecomputeCRCTable() + { + for( unsigned int i = 0; i < 256; i++) + { + unsigned int crc = i; + + for ( unsigned int j = 0; j < 8; ++j ) + { + if ( crc & 0x0001 ) + crc = (crc >> 1) ^ 0xA6BC; // Generating polynomial. + else + crc >>= 1; + } + + crc_table[i] = crc; + } + } + +unsigned int DNP3_Analyzer::CalcCRC(int len, const u_char* data) + { + unsigned int crc = 0x0000; + + for ( int i = 0; i < len; i++ ) + { + unsigned int index = (crc ^ data[i]) & 0xFF; + crc = crc_table[index] ^ (crc >> 8); + } + + return ~crc & 0xFFFF; + } diff --git a/src/analyzer/protocol/dnp3/DNP3.h b/src/analyzer/protocol/dnp3/DNP3.h new file mode 100644 index 0000000000..d758c2e35a --- /dev/null +++ b/src/analyzer/protocol/dnp3/DNP3.h @@ -0,0 +1,56 @@ +#ifndef ANALYZER_PROTOCOL_DNP3_DNP3_H +#define ANALYZER_PROTOCOL_DNP3_DNP3_H + +#include "analyzer/protocol/tcp/TCP.h" +#include "dnp3_pac.h" + +namespace analyzer { namespace dnp3 { + +class DNP3_Analyzer : public tcp::TCP_ApplicationAnalyzer { +public: + DNP3_Analyzer(Connection* conn); + virtual ~DNP3_Analyzer(); + + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(int seq, int len, bool orig); + virtual void EndpointEOF(tcp::TCP_Reassembler* endp); + + static Analyzer* InstantiateAnalyzer(Connection* conn) + { return new DNP3_Analyzer(conn); } + +private: + static const int MAX_BUFFER_SIZE = 300; + + struct Endpoint + { + u_char buffer[MAX_BUFFER_SIZE]; + int buffer_len; + bool in_hdr; + int tpflags; + int pkt_length; + int pkt_cnt; + bool encountered_first_chunk; + }; + + bool ProcessData(int len, const u_char* data, bool orig); + void ClearEndpointState(bool orig); + bool AddToBuffer(Endpoint* endp, int target_len, const u_char** data, int* len); + bool ParseAppLayer(Endpoint* endp); + bool CheckCRC(int len, const u_char* data, const u_char* crc16, const char* where); + unsigned int CalcCRC(int len, const u_char* data); + + binpac::DNP3::DNP3_Conn* interp; + + Endpoint orig_state; + Endpoint resp_state; + + static void PrecomputeCRCTable(); + + static bool crc_table_initialized; + static unsigned int crc_table[256]; +}; + +} } // namespace analyzer::* + +#endif diff --git a/src/analyzer/protocol/dnp3/Plugin.cc b/src/analyzer/protocol/dnp3/Plugin.cc new file mode 100644 index 0000000000..d86413b388 --- /dev/null +++ b/src/analyzer/protocol/dnp3/Plugin.cc @@ -0,0 +1,10 @@ + +#include "plugin/Plugin.h" + +#include "DNP3.h" + +BRO_PLUGIN_BEGIN(Bro, DNP3) + BRO_PLUGIN_DESCRIPTION("DNP3 analyzer"); + BRO_PLUGIN_ANALYZER("DNP3", dnp3::DNP3_Analyzer); + BRO_PLUGIN_BIF_FILE(events); +BRO_PLUGIN_END diff --git a/src/analyzer/protocol/dnp3/dnp3-analyzer.pac b/src/analyzer/protocol/dnp3/dnp3-analyzer.pac new file mode 100644 index 0000000000..2ae783c82e --- /dev/null +++ b/src/analyzer/protocol/dnp3/dnp3-analyzer.pac @@ -0,0 +1,969 @@ + +connection DNP3_Conn(bro_analyzer: BroAnalyzer) { + upflow = DNP3_Flow(true); + downflow = DNP3_Flow(false); +}; + +flow DNP3_Flow(is_orig: bool) { + flowunit = DNP3_PDU(is_orig) withcontext (connection, this); + + function get_dnp3_header_block(start: uint16, len: uint16, ctrl: uint8, dest_addr: uint16, src_addr: uint16): bool + %{ + if ( ::dnp3_header_block ) + { + BifEvent::generate_dnp3_header_block( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), start, len, ctrl, dest_addr, src_addr); + } + + return true; + %} + + function get_dnp3_application_request_header(fc: uint8): bool + %{ + if ( ::dnp3_application_request_header ) + { + BifEvent::generate_dnp3_application_request_header( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), + fc + ); + } + return true; + %} + + function get_dnp3_application_response_header(fc: uint8, iin: uint16): bool + %{ + if ( ::dnp3_application_response_header ) + { + BifEvent::generate_dnp3_application_response_header( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), + fc, + iin + ); + } + return true; + %} + + function get_dnp3_object_header(obj_type: uint16, qua_field: uint8, number: uint32, rf_low: uint32, rf_high: uint32 ): bool + %{ + if ( ::dnp3_object_header ) + { + BifEvent::generate_dnp3_object_header( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), obj_type, qua_field, number, rf_low, rf_high); + } + + return true; + %} + + function get_dnp3_object_prefix(prefix_value: uint32): bool + %{ + if ( ::dnp3_object_prefix ) + { + BifEvent::generate_dnp3_object_prefix( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), prefix_value); + } + + return true; + %} + + function get_dnp3_response_data_object(data_value: uint8): bool + %{ + if ( ::dnp3_response_data_object ) + { + BifEvent::generate_dnp3_response_data_object( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), data_value); + } + + return true; + %} + + #g0 + function get_dnp3_attribute_common(data_type_code: uint8, leng: uint8, attribute_obj: const_bytestring): bool + %{ + if ( ::dnp3_attribute_common ) + { + BifEvent::generate_dnp3_attribute_common( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), data_type_code, leng, bytestring_to_val(attribute_obj) ); + } + + return true; + %} + + #g12v1 + function get_dnp3_crob(control_code: uint8, count8: uint8, on_time: uint32, off_time: uint32, status_code: uint8): bool + %{ + if ( ::dnp3_crob ) + { + BifEvent::generate_dnp3_crob( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), control_code, count8, on_time, off_time, status_code); + } + + return true; + %} + + #g12v2 + function get_dnp3_pcb(control_code: uint8, count8: uint8, on_time: uint32, off_time: uint32, status_code: uint8): bool + %{ + if ( ::dnp3_pcb ) + { + BifEvent::generate_dnp3_pcb( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), control_code, count8, on_time, off_time, status_code); + } + + return true; + %} + + # g20v1 + function get_dnp3_counter_32wFlag(flag: uint8, count_value: uint32): bool + %{ + if ( ::dnp3_counter_32wFlag ) + { + BifEvent::generate_dnp3_counter_32wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, count_value); + } + + return true; + %} + + # g20v2 + function get_dnp3_counter_16wFlag(flag: uint8, count_value: uint16): bool + %{ + if ( ::dnp3_counter_16wFlag ) + { + BifEvent::generate_dnp3_counter_16wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, count_value); + } + + return true; + %} + + # g20v5 + function get_dnp3_counter_32woFlag(count_value: uint32): bool + %{ + if ( ::dnp3_counter_32woFlag ) + { + BifEvent::generate_dnp3_counter_32woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), count_value); + } + + return true; + %} + + # g20v6 + function get_dnp3_counter_16woFlag(count_value: uint16): bool + %{ + if ( ::dnp3_counter_16woFlag ) + { + BifEvent::generate_dnp3_counter_16woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), count_value); + } + + return true; + %} + + # g21v1 + function get_dnp3_frozen_counter_32wFlag(flag: uint8, count_value: uint32): bool + %{ + if ( ::dnp3_frozen_counter_32wFlag ) + { + BifEvent::generate_dnp3_frozen_counter_32wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, count_value); + } + + return true; + %} + + # g21v2 + function get_dnp3_frozen_counter_16wFlag(flag: uint8, count_value: uint16): bool + %{ + if ( ::dnp3_frozen_counter_16wFlag ) + { + BifEvent::generate_dnp3_frozen_counter_16wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, count_value); + } + + return true; + %} + + # g21v5 + function get_dnp3_frozen_counter_32wFlagTime(flag: uint8, count_value: uint32, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_counter_32wFlagTime ) + { + BifEvent::generate_dnp3_frozen_counter_32wFlagTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, count_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g21v6 + function get_dnp3_frozen_counter_16wFlagTime(flag: uint8, count_value: uint16, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_counter_16wFlagTime ) + { + BifEvent::generate_dnp3_frozen_counter_16wFlagTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, count_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g21v9 + function get_dnp3_frozen_counter_32woFlag(count_value: uint32): bool + %{ + if ( ::dnp3_frozen_counter_32woFlag ) + { + BifEvent::generate_dnp3_frozen_counter_32woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), count_value); + } + + return true; + %} + + # g21v10 + function get_dnp3_frozen_counter_16woFlag(count_value: uint16): bool + %{ + if ( ::dnp3_frozen_counter_16woFlag ) + { + BifEvent::generate_dnp3_frozen_counter_16woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), count_value); + } + + return true; + %} + + # g30v1 + function get_dnp3_analog_input_32wFlag(flag: uint8, value: int32): bool + %{ + if ( ::dnp3_analog_input_32wFlag ) + { + BifEvent::generate_dnp3_analog_input_32wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value); + } + + return true; + %} + + # g30v2 + function get_dnp3_analog_input_16wFlag(flag: uint8, value: int16): bool + %{ + if ( ::dnp3_analog_input_16wFlag ) + { + BifEvent::generate_dnp3_analog_input_16wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value); + } + + return true; + %} + + # g30v3 + function get_dnp3_analog_input_32woFlag(value: int32): bool + %{ + if ( ::dnp3_analog_input_32woFlag ) + { + BifEvent::generate_dnp3_analog_input_32woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), value); + } + + return true; + %} + + #g30v4 + function get_dnp3_analog_input_16woFlag(value: int16): bool + %{ + if ( ::dnp3_analog_input_16woFlag ) + { + BifEvent::generate_dnp3_analog_input_16woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), value); + } + + return true; + %} + + # g30v5 + function get_dnp3_analog_input_SPwFlag(flag: uint8, value: uint32): bool + %{ + if ( ::dnp3_analog_input_SPwFlag ) + { + BifEvent::generate_dnp3_analog_input_SPwFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value); + } + + return true; + %} + + # g30v6 + function get_dnp3_analog_input_DPwFlag(flag: uint8, value_low: uint32, value_high: uint32): bool + %{ + if ( ::dnp3_analog_input_DPwFlag ) + { + BifEvent::generate_dnp3_analog_input_DPwFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value_low, value_high); + } + + return true; + %} + + # g31v1 + function get_dnp3_frozen_analog_input_32wFlag(flag: uint8, frozen_value: int32): bool + %{ + if ( ::dnp3_frozen_analog_input_32wFlag ) + { + BifEvent::generate_dnp3_frozen_analog_input_32wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value); + } + + return true; + %} + + # g31v2 + function get_dnp3_frozen_analog_input_16wFlag(flag: uint8, frozen_value: int16): bool + %{ + if ( ::dnp3_frozen_analog_input_16wFlag ) + { + BifEvent::generate_dnp3_frozen_analog_input_16wFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value); + } + + return true; + %} + + # g31v3 + function get_dnp3_frozen_analog_input_32wTime(flag: uint8, frozen_value: int32, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_analog_input_32wTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_32wTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g31v4 + function get_dnp3_frozen_analog_input_16wTime(flag: uint8, frozen_value: int16, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_analog_input_16wTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_16wTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g31v5 + function get_dnp3_frozen_analog_input_32woFlag(frozen_value: int32): bool + %{ + if ( ::dnp3_frozen_analog_input_32woFlag ) + { + BifEvent::generate_dnp3_frozen_analog_input_32woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), frozen_value); + } + + return true; + %} + + # g31v6 + function get_dnp3_frozen_analog_input_16woFlag(frozen_value: int16): bool + %{ + if ( ::dnp3_frozen_analog_input_16woFlag ) + { + BifEvent::generate_dnp3_frozen_analog_input_16woFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), frozen_value); + } + + return true; + %} + + # g31v7 + function get_dnp3_frozen_analog_input_SPwFlag(flag: uint8, frozen_value: uint32): bool + %{ + if ( ::dnp3_frozen_analog_input_SPwFlag ) + { + BifEvent::generate_dnp3_frozen_analog_input_SPwFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value); + } + + return true; + %} + + # g31v8 + function get_dnp3_frozen_analog_input_DPwFlag(flag: uint8, frozen_value_low: uint32, frozen_value_high: uint32): bool + %{ + if ( ::dnp3_frozen_analog_input_DPwFlag ) + { + BifEvent::generate_dnp3_frozen_analog_input_DPwFlag( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value_low, frozen_value_high); + } + + return true; + %} + + # g32v1 + function get_dnp3_analog_input_event_32woTime(flag: uint8, value: int32): bool + %{ + if ( ::dnp3_analog_input_event_32woTime ) + { + BifEvent::generate_dnp3_analog_input_event_32woTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value); + } + + return true; + %} + + # g32v2 + function get_dnp3_analog_input_event_16woTime(flag: uint8, value: int16): bool + %{ + if ( ::dnp3_analog_input_event_16woTime ) + { + BifEvent::generate_dnp3_analog_input_event_16woTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value); + } + + return true; + %} + + # g32v3 + function get_dnp3_analog_input_event_32wTime(flag: uint8, value: int32, time48: const_bytestring): bool + %{ + if ( ::dnp3_analog_input_event_32wTime ) + { + BifEvent::generate_dnp3_analog_input_event_32wTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value, bytestring_to_val(time48)); + } + + return true; + %} + + # g32v4 + function get_dnp3_analog_input_event_16wTime(flag: uint8, value: int16, time48: const_bytestring): bool + %{ + if ( ::dnp3_analog_input_event_16wTime ) + { + BifEvent::generate_dnp3_analog_input_event_16wTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value, bytestring_to_val(time48)); + } + + return true; + %} + + # g32v5 + function get_dnp3_analog_input_event_SPwoTime(flag: uint8, value: uint32): bool + %{ + if ( ::dnp3_analog_input_event_SPwoTime ) + { + BifEvent::generate_dnp3_analog_input_event_SPwoTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value); + } + + return true; + %} + + # g32v6 + function get_dnp3_analog_input_event_DPwoTime(flag: uint8, value_low: uint32, value_high: uint32): bool + %{ + if ( ::dnp3_analog_input_event_DPwoTime ) + { + BifEvent::generate_dnp3_analog_input_event_DPwoTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value_low, value_high); + } + + return true; + %} + + # g32v7 + function get_dnp3_analog_input_event_SPwTime(flag: uint8, value: uint32, time48: const_bytestring): bool + %{ + if ( ::dnp3_analog_input_event_SPwTime ) + { + BifEvent::generate_dnp3_analog_input_event_SPwTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value, bytestring_to_val(time48)); + } + + return true; + %} + + # g32v8 + function get_dnp3_analog_input_event_DPwTime(flag: uint8, value_low: uint32, value_high: uint32, time48: const_bytestring): bool + %{ + if ( ::dnp3_analog_input_event_DPwTime ) + { + BifEvent::generate_dnp3_analog_input_event_DPwTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, value_low, value_high, bytestring_to_val(time48)); + } + + return true; + %} + + # g33v1 + function get_dnp3_frozen_analog_input_event_32woTime(flag: uint8, frozen_value: int32): bool + %{ + if ( ::dnp3_frozen_analog_input_event_32woTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_32woTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value); + } + + return true; + %} + + # g33v2 + function get_dnp3_frozen_analog_input_event_16woTime(flag: uint8, frozen_value: int16): bool + %{ + if ( ::dnp3_frozen_analog_input_event_16woTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_16woTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value); + } + + return true; + %} + + # g33v3 + function get_dnp3_frozen_analog_input_event_32wTime(flag: uint8, frozen_value: int32, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_analog_input_event_32wTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_32wTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g33v4 + function get_dnp3_frozen_analog_input_event_16wTime(flag: uint8, frozen_value: int16, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_analog_input_event_16wTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_16wTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g33v5 + function get_dnp3_frozen_analog_input_event_SPwoTime(flag: uint8, frozen_value: uint32): bool + %{ + if ( ::dnp3_frozen_analog_input_event_SPwoTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_SPwoTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value); + } + + return true; + %} + + # g33v6 + function get_dnp3_frozen_analog_input_event_DPwoTime(flag: uint8, frozen_value_low: uint32, frozen_value_high: uint32): bool + %{ + if ( ::dnp3_frozen_analog_input_event_DPwoTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_DPwoTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value_low, frozen_value_high); + } + + return true; + %} + + # g33v7 + function get_dnp3_frozen_analog_input_event_SPwTime(flag: uint8, frozen_value: uint32, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_analog_input_event_SPwTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_SPwTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value, bytestring_to_val(time48)); + } + + return true; + %} + + # g33v8 + function get_dnp3_frozen_analog_input_event_DPwTime(flag: uint8, frozen_value_low: uint32, frozen_value_high: uint32, time48: const_bytestring): bool + %{ + if ( ::dnp3_frozen_analog_input_event_DPwTime ) + { + BifEvent::generate_dnp3_frozen_analog_input_event_DPwTime( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), flag, frozen_value_low, frozen_value_high, bytestring_to_val(time48)); + } + + return true; + %} + + # g70v5 + function get_dnp3_file_transport(file_handle: uint32, block_num: uint32, file_data: const_bytestring): bool + %{ + if ( ::dnp3_file_transport ) + { + BifEvent::generate_dnp3_file_transport( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), file_handle, block_num, bytestring_to_val(file_data)); + } + + return true; + %} + +#### for debug use or unknown data types used in "case" + function get_dnp3_debug_byte(debug: const_bytestring): bool + %{ + if ( ::dnp3_debug_byte ) + { + BifEvent::generate_dnp3_debug_byte ( + connection()->bro_analyzer(), + connection()->bro_analyzer()->Conn(), + is_orig(), bytestring_to_val(debug)); + } + + return true; + %} + +}; + +refine typeattr Header_Block += &let { + get_header: bool = $context.flow.get_dnp3_header_block(start, len, ctrl, dest_addr, src_addr); +}; + +refine typeattr DNP3_Application_Request_Header += &let { + process_request: bool = $context.flow.get_dnp3_application_request_header(function_code); +}; + +refine typeattr DNP3_Application_Response_Header += &let { + process_request: bool = $context.flow.get_dnp3_application_response_header(function_code, internal_indications); +}; + +refine typeattr Object_Header += &let { + process_request: bool = $context.flow.get_dnp3_object_header(object_type_field, qualifier_field, number_of_item, rf_value_low, rf_value_high); +}; + +refine typeattr Prefix_Type += &let { + prefix_called: bool = $context.flow.get_dnp3_object_prefix(prefix_value); +}; + +refine typeattr Response_Data_Object += &let { + process_request: bool = $context.flow.get_dnp3_response_data_object(data_value); +}; + +# g0 +refine typeattr AttributeCommon += &let { + process_request: bool = $context.flow.get_dnp3_attribute_common(data_type_code, leng, attribute_obj); +}; + +# g12v1 +refine typeattr CROB += &let { + process_request: bool = $context.flow.get_dnp3_crob(control_code, count, on_time, off_time, status_code); +}; + +# g12v2 +refine typeattr PCB += &let { + process_request: bool = $context.flow.get_dnp3_pcb(control_code, count, on_time, off_time, status_code); +}; + +# g20v1 +refine typeattr Counter32wFlag += &let { + process_request: bool = $context.flow.get_dnp3_counter_32wFlag(flag, count_value); +}; + +# g20v2 +refine typeattr Counter16wFlag += &let { + process_request: bool = $context.flow.get_dnp3_counter_16wFlag(flag, count_value); +}; + +# g20v5 +refine typeattr Counter32woFlag += &let { + process_request: bool = $context.flow.get_dnp3_counter_32woFlag(count_value); +}; + +# g20v6 +refine typeattr Counter16woFlag += &let { + process_request: bool = $context.flow.get_dnp3_counter_16woFlag(count_value); +}; + +# g21v1 +refine typeattr FrozenCounter32wFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_counter_32wFlag(flag, count_value); +}; + +# g21v2 +refine typeattr FrozenCounter16wFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_counter_16wFlag(flag, count_value); +}; +# g21v5 +refine typeattr FrozenCounter32wFlagTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_counter_32wFlagTime(flag, count_value, time48); +}; + +# g21v6 +refine typeattr FrozenCounter16wFlagTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_counter_16wFlagTime(flag, count_value, time48); +}; + +# g21v9 +refine typeattr FrozenCounter32woFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_counter_32woFlag(count_value); +}; + +# g21v10 +refine typeattr FrozenCounter16woFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_counter_16woFlag(count_value); +}; + +# g30v1 +refine typeattr AnalogInput32wFlag += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_32wFlag(flag, value); +}; + +# g30v2 +refine typeattr AnalogInput16wFlag += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_16wFlag(flag, value); +}; + +# g30v3 +refine typeattr AnalogInput32woFlag += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_32woFlag(value); +}; + +# g30v4 +refine typeattr AnalogInput16woFlag += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_16woFlag(value); +}; + +# g30v5 +refine typeattr AnalogInputSPwFlag += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_SPwFlag(flag, value); +}; + +# g30v6 +refine typeattr AnalogInputDPwFlag += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_DPwFlag(flag, value_low, value_high); +}; + +# g31v1 +refine typeattr FrozenAnalogInput32wFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_32wFlag(flag, frozen_value); +}; + +# g31v2 +refine typeattr FrozenAnalogInput16wFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_16wFlag(flag, frozen_value); +}; + +# g31v3 +refine typeattr FrozenAnalogInput32wTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_32wTime(flag, frozen_value, time48); +}; + +# g31v4 +refine typeattr FrozenAnalogInput16wTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_16wTime(flag, frozen_value, time48); +}; + +# g31v5 +refine typeattr FrozenAnalogInput32woFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_32woFlag(frozen_value); +}; + +# g31v6 +refine typeattr FrozenAnalogInput16woFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_16woFlag(frozen_value); +}; + +# g31v7 +refine typeattr FrozenAnalogInputSPwFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_SPwFlag(flag, frozen_value); +}; + +# g31v8 +refine typeattr FrozenAnalogInputDPwFlag += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_DPwFlag(flag, frozen_value_low, frozen_value_high); +}; + +# g32v1 +refine typeattr AnalogInput32woTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_32woTime(flag, value); +}; + +# g32v2 +refine typeattr AnalogInput16woTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_16woTime(flag, value); +}; + +# g32v3 +refine typeattr AnalogInput32wTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_32wTime(flag, value, time48); +}; + +# g32v4 +refine typeattr AnalogInput16wTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_16wTime(flag, value, time48); +}; + +# g32v5 +refine typeattr AnalogInputSPwoTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_SPwoTime(flag, value); +}; + +# g32v6 +refine typeattr AnalogInputDPwoTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_DPwoTime(flag, value_low, value_high); +}; + +# g32v7 +refine typeattr AnalogInputSPwTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_SPwTime(flag, value, time48); +}; + +# g32v8 +refine typeattr AnalogInputDPwTime += &let { + process_request: bool = $context.flow.get_dnp3_analog_input_event_DPwTime(flag, value_low, value_high, time48); +}; + +# g33v1 +refine typeattr FrozenAnaInputEve32woTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_32woTime(flag, f_value); +}; + +# g33v2 +refine typeattr FrozenAnaInputEve16woTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_16woTime(flag, f_value); +}; + +# g33v3 +refine typeattr FrozenAnaInputEve32wTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_32wTime(flag, f_value, time48); +}; + +# g33v4 +refine typeattr FrozenAnaInputEve16wTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_16wTime(flag, f_value, time48); +}; + +# g33v5 +refine typeattr FrozenAnaInputEveSPwoTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_SPwoTime(flag, f_value); +}; + +# g33v6 +refine typeattr FrozenAnaInputEveDPwoTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_DPwoTime(flag, f_value_low, f_value_high); +}; + +# g33v7 +refine typeattr FrozenAnaInputEveSPwTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_SPwTime(flag, f_value, time48); +}; + +# g33v8 +refine typeattr FrozenAnaInputEveDPwTime += &let { + process_request: bool = $context.flow.get_dnp3_frozen_analog_input_event_DPwTime(flag, f_value_low, f_value_high, time48); +}; + +# g70v5 +refine typeattr File_Transport += &let { + result: bool = $context.flow.get_dnp3_file_transport(file_handle, block_num, file_data); +}; + +refine typeattr Debug_Byte += &let { + process_request: bool = $context.flow.get_dnp3_debug_byte(debug); +}; + diff --git a/src/analyzer/protocol/dnp3/dnp3-objects.pac b/src/analyzer/protocol/dnp3/dnp3-objects.pac new file mode 100644 index 0000000000..35d0d42b92 --- /dev/null +++ b/src/analyzer/protocol/dnp3/dnp3-objects.pac @@ -0,0 +1,1451 @@ +##### moved from dnp3-protocol.pac + +type Prefix_Type(qualifier_field: uint8) = record { + prefix: case ( qualifier_field & 0xf0 ) of { + 0x00 -> none: empty &check(qualifier_field == 0x01 || + qualifier_field == 0x02 || + qualifier_field == 0x03 || + qualifier_field == 0x04 || + qualifier_field == 0x05 || + qualifier_field == 0x06 || + qualifier_field == 0x07 || + qualifier_field == 0x08 || + qualifier_field == 0x09 ); + 0x10 -> prefix8: uint8 &check(qualifier_field == 0x17 || + qualifier_field == 0x18 || + qualifier_field == 0x19 ); + 0x20 -> prefix16: uint16 &check(qualifier_field == 0x27 || + qualifier_field == 0x28 || + qualifier_field == 0x29 ); + 0x30 -> prefix32: uint32 &check(qualifier_field == 0x37 || + qualifier_field == 0x38 || + qualifier_field == 0x39 ); + 0x40 -> object_size8: uint8 &check(qualifier_field == 0x4B); + 0x50 -> object_size16: uint16 &check(qualifier_field == 0x5B); + 0x60 -> object_size32: uint32 &check(qualifier_field == 0x6B); + default -> unknownprefix: empty; + }; +} &let{ + prefix_value: uint32 = case (qualifier_field & 0xf0) of { + 0x00 -> 0; + 0x10 -> prefix8; + 0x20 -> prefix16; + 0x30 -> prefix32; + 0x40 -> object_size8; + 0x50 -> object_size16; + 0x60 -> object_size32; + default -> 0x0; + }; +} &byteorder = littleendian; + +type Request_Data_Object(function_code: uint8, qualifier_field: uint8, object_type_field: uint16) = record { + prefix: Prefix_Type(qualifier_field); + data: case (object_type_field) of { + # device attributes g0 + 0x00D3 -> attrib211: AttributeCommon; + 0x00D4 -> attrib212: AttributeCommon; + 0x00D5 -> attrib213: AttributeCommon; + 0x00D6 -> attrib214: AttributeCommon; + 0x00D7 -> attrib215: AttributeCommon; + 0x00D8 -> attrib216: AttributeCommon; + 0x00D9 -> attrib217: AttributeCommon; + 0x00DA -> attrib218: AttributeCommon; + 0x00DB -> attrib219: AttributeCommon; + 0x00DC -> attrib220: AttributeCommon; + 0x00DD -> attrib221: AttributeCommon; + 0x00DE -> attrib222: AttributeCommon; + 0x00DF -> attrib223: AttributeCommon; + 0x00E0 -> attrib224: AttributeCommon; + 0x00E1 -> attrib225: AttributeCommon; + 0x00E2 -> attrib226: AttributeCommon; + 0x00E3 -> attrib227: AttributeCommon; + 0x00E4 -> attrib228: AttributeCommon; + 0x00E5 -> attrib229: AttributeCommon; + 0x00E6 -> attrib230: AttributeCommon; + 0x00E7 -> attrib231: AttributeCommon; + 0x00E8 -> attrib232: AttributeCommon; + 0x00E9 -> attrib233: AttributeCommon; + 0x00EA -> attrib234: AttributeCommon; + 0x00EB -> attrib235: AttributeCommon; + 0x00EC -> attrib236: AttributeCommon; + 0x00ED -> attrib237: AttributeCommon; + 0x00EE -> attrib238: AttributeCommon; + 0x00EF -> attrib239: AttributeCommon; + 0x00F0 -> attrib240: AttributeCommon; + 0x00F1 -> attrib241: AttributeCommon; + 0x00F2 -> attrib242: AttributeCommon; + 0x00F3 -> attrib243: AttributeCommon; + 0x00F5 -> attrib245: AttributeCommon; + 0x00F6 -> attrib246: AttributeCommon; + 0x00F7 -> attrib247: AttributeCommon; + 0x00F8 -> attrib248: AttributeCommon; + 0x00F9 -> attrib249: AttributeCommon; + 0x00FA -> attrib250: AttributeCommon; + 0x00FC -> attrib252: AttributeCommon; + 0x00FE -> attrib254: AttributeCommon; + 0x00FF -> attrib255: AttributeCommon; + + # binary input g1 + 0x0100 -> bi_default: empty; + 0x0101 -> bi_packed: empty; + 0x0102 -> bi_flag: empty; + + # binary input event g2 + 0x0200 -> biedefault: empty; + 0x0201 -> biewotime: empty; + 0x0202 -> biewatime: empty; + 0x0203 -> biewrtime: empty; + + # double-bit Binary Input g3 + 0x0300 -> dbiDefault: empty; + 0x0301 -> dbibytes: empty; + 0x0302 -> dbiflag: empty; + + # double-bit Binary Input Event g4 + 0x0400 -> dbieDefault: empty; + 0x0401 -> dbieatime: empty; + 0x0402 -> dbiertime: empty; + + # binary output g10 + 0x0a00 -> boDefault: empty; + 0x0a01 -> bowoflag: empty; # warning: returning integer index? + 0x0a02 -> bowflag: empty; # warning: only flag? + + # binary output event g11 + 0x0b00 -> bowDefault: empty; + 0x0b01 -> boewflag: empty; + 0x0b02 -> boewatime: empty; + + # binary output command g12 + 0x0c01 -> bocmd_CROB: CROB &check (function_code == SELECT || function_code == OPERATE || + function_code == DIRECT_OPERATE || function_code == DIRECT_OPERATE_NR ); + 0x0c02 -> bocmd_PCB: PCB &check (function_code == SELECT || function_code == OPERATE || + function_code == DIRECT_OPERATE || function_code == DIRECT_OPERATE_NR || function_code == WRITE ); + 0x0c03 -> bocmd_PM: uint8; + + # binary output command event g13 + 0x0d00 -> boceDefault: empty; + 0x0d01 -> boceFlag: empty; + 0x0d02 -> boceAtime: empty; + + # counter ; g20 + 0x1400 -> counter_default: empty; + 0x1401 -> counter_32_wflag: empty; + 0x1402 -> counter_16_wflag: empty; + 0x1403 -> counter_32_wflag_delta: empty &check (0); # obsolete situation; generate warning + 0x1404 -> counter_16_wflag_delta: empty &check (0); # obsolete situations; generate warning + 0x1405 -> counter_32_woflag: empty; + 0x1406 -> counter_16_woflag: empty; + 0x1407 -> counter_32_woflag_delta: empty &check (0); # obsolete + 0x1408 -> counter_16_woflag_delta: empty &check (0); # obsolete + # frozen counter ; g21 + 0x1500 -> f_counter_default: empty; + 0x1501 -> f_counter_32_wflag: empty; + 0x1502 -> f_counter_16_wflag: empty; + 0x1503 -> f_counter_32_wflag_delta: empty &check (0); # obsolete situation; generate warning + 0x1504 -> f_counter_16_wflag_delta: empty &check (0); # obsolete situations; generate warning + 0x1505 -> f_counter_32_wflag_time: empty; + 0x1506 -> f_counter_16_wflag_time: empty; + 0x1507 -> f_counter_32_wflag_time_delta: empty &check (0); # obsolete + 0x1508 -> f_counter_16_wflag_time_delta: empty &check (0); # obsolete + 0x1509 -> f_counter_32_woflag: empty; + 0x150a -> f_counter_16_woflag: empty; + 0x150b -> f_counter_32_woflag_delta: empty &check (0); # obsolete + 0x150c -> f_counter_16_woflag_delta: empty &check (0); # obsolete + + # counter event g22 + 0x1600 -> counter_event_default: empty; + 0x1601 -> counter_event_32_wflag: empty; + 0x1602 -> counter_event_16_wflag: empty; + 0x1603 -> counter_event_32_wflag_delta: empty &check(0); + 0x1604 -> counter_event_16_wflag_delta: empty &check(0); + 0x1605 -> counter_event_32_wflag_time: empty; + 0x1606 -> counter_event_16_wflag_time: empty; + 0x1607 -> counter_event_32_wflag_time_delta: empty &check(0); + 0x1608 -> counter_event_16_wflag_time_delat: empty &check(0); + + # counter event g23 + 0x1700 -> f_counter_event_default: empty; + 0x1701 -> f_counter_event_32_wflag: empty; + 0x1702 -> f_counter_event_16_wflag: empty; + 0x1703 -> f_counter_event_32_wflag_delta: empty &check(0); + 0x1704 -> f_counter_event_16_wflag_delta: empty &check(0); + 0x1705 -> f_counter_event_32_wflag_time: empty; + 0x1706 -> f_counter_event_16_wflag_time: empty; + 0x1707 -> f_counter_event_32_wflag_time_delta: empty &check(0); + 0x1708 -> f_counter_event_16_wflag_time_delat: empty &check(0); + + #analog input g30 + 0x1e00 -> ai_default: empty; + 0x1e01 -> ai_32_wflag: empty; + 0x1e02 -> ai_16_wflag: empty; + 0x1e03 -> ai_32_woflag: empty; + 0x1e04 -> ai_16_woflag: empty; + 0x1e05 -> ai_sp_wflag: empty; + 0x1e06 -> ai_dp_wflag: empty; + + #frozen analog input g31 + 0x1f00 -> f_ai_default: empty; + 0x1f01 -> f_ai_32_wflag: empty; + 0x1f02 -> f_ai_16_wflag: empty; + 0x1f03 -> f_ai_32_wtime: empty; + 0x1f04 -> f_ai_16_wtime: empty; + 0x1f05 -> f_ai_32_woflag: empty; + 0x1f06 -> f_ai_16_woflag: empty; + 0x1f07 -> f_ai_sp_wflag: empty; + 0x1f08 -> f_ai_dp_wflag: empty; + + #analog input event g32 + 0x2000 -> aie_default: empty; + 0x2001 -> ai32wotime: empty; + 0x2002 -> ai16wotime: empty; + 0x2003 -> ai32wtime: empty; + 0x2004 -> ai16wtime: empty; + 0x2005 -> aispwotime: empty; + 0x2006 -> aidpwotime: empty; + 0x2007 -> aispwtime: empty; + 0x2008 -> aidpwtime: empty; + + # frozen analog input g33 + 0x2100 -> f_aie_default: empty; + 0x2101 -> f_aie_32_wotime: empty; + 0x2102 -> f_aie_16_wotime: empty; + 0x2103 -> f_aie_32_wtime: empty; + 0x2104 -> f_aie_16_wtime: empty; + 0x2105 -> f_aie_sp_wotime: empty; + 0x2106 -> f_aie_dp_wotime: empty; + 0x2107 -> f_aie_sp_wtime: empty; + 0x2108 -> f_aie_dp_wtime: empty; + + # analog input deadband g34 + 0x2200 -> ai_dead: empty; + 0x2201 -> ai_dead_16: empty; + 0x2202 -> ai_dead_32: empty; + 0x2203 -> ai_dead_sp: empty; + + # analog ouput status g40 + 0x2800 -> aos_default: empty; + 0x2801 -> aos_32: empty; + 0x2802 -> aos_16: empty; + 0x2803 -> aos_sp: empty; + 0x2804 -> aos_dp: empty; + + # analog ouput g41 + 0x2901 -> ao_32: AnaOut32; + 0x2902 -> ao_16: AnaOut16; + 0x2903 -> ao_sp: AnaOutSP; + 0x2904 -> ao_dp: AnaOutDP; + + # analog output event g42 + 0x2a00 -> aoe_default: empty; + 0x2a01 -> aoe32wotime: empty; + 0x2a02 -> aoe16wotime: empty; + 0x2a03 -> aoe32wtime: empty; + 0x2a04 -> aoe16wtime: empty; + 0x2a05 -> aoespwotime: empty; + 0x2a06 -> aoedpwotime: empty; + 0x2a07 -> aoespwtime: empty; + 0x2a08 -> aoedpwtime: empty; + + # analog output command event g43 + 0x2b00 -> aoce_default: empty; + 0x2b01 -> aoce32wotime: empty; + 0x2b02 -> aoce16wotime: empty; + 0x2b03 -> aoce32wtime: empty; + 0x2b04 -> aoce16wtime: empty; + 0x2b05 -> aocespwotime: empty; + 0x2b06 -> aocedpwotime: empty; + 0x2b07 -> aocespwtime: empty; + 0x2b08 -> aocedpwtime: empty; + + # time data interval data object g50 + 0x3200 -> time_default: empty; + 0x3201 -> time_abs: AbsTime; + 0x3202 -> time_interval: AbsTimeInterval; + 0x3203 -> time_abs_last: Last_AbsTime; + + # Time and Date Common Time-of-Occurrence g51 + 0x3301 -> time_abs_sync: AbsTime; + 0x3302 -> time_abs_unsync: AbsTime; + + # time delay g52 + 0x3401 -> time_coarse: uint16; + 0x3402 -> time_fine: uint16; + + # class objects g60 + 0x3C01 -> class0data: empty &check(object_header.qualifier_field == 0x06); + #0x3C02 -> class1data: uint8 &check(object_header.qualifier_field == 0x06); + 0x3C02 -> class1data: empty &check(object_header.qualifier_field == 0x06 || + object_header.qualifier_field == 0x07 || object_header.qualifier_field == 0x08); + 0x3C03 -> class2data: empty &check(object_header.qualifier_field == 0x06 || + object_header.qualifier_field == 0x07 || object_header.qualifier_field == 0x08); + 0x3C04 -> class3data: empty &check(object_header.qualifier_field == 0x06 || + object_header.qualifier_field == 0x07 || object_header.qualifier_field == 0x08); + # file control g70 + 0x4601 -> file_control_id: File_Control_ID &check(0); + 0x4602 -> file_control_auth: File_Control_Auth_Wrap(function_code); + 0x4603 -> file_control_cmd: File_Control_Cmd &check( file_control_cmd.op_mode == 0 || file_control_cmd.op_mode == 1 || + file_control_cmd.op_mode == 2 || file_control_cmd.op_mode == 3 ); + #0x4604 -> file_control_cmd_status: File_Control_Cmd_Status_Wrap(function_code, prefix.prefix_value); # example shown in P66 + 0x4604 -> file_control_cmd_status: File_Control_Cmd_Status(prefix.prefix_value); # example shown in P66 + 0x4605 -> file_trans: File_Transport(prefix.prefix_value); + 0x4606 -> file_trans_status: File_Transport_Status(prefix.prefix_value); + #0x4607 -> file_desc: File_Desc_Wrap(function_code); + 0x4607 -> file_desc: File_Desc; + + # internal indication g80 + #0x5001 -> iin: uint16; + 0x5001 -> iin: bytestring &restofdata; # confusion from the real traffic + + # device storage g81 + 0x5101 -> dev_store: empty; + + # device storage g82 + 0x5201 -> dev_profile: empty; + + # device storage g83 + 0x5301 -> priregobj: PrivRegObj; + 0x5302 -> priregobjdesc: PrivRegObjDesc; + + # private data set g85 + 0x5501 -> desc_ele: DescEle; + + # data descriptor table g86 + 0x5601 -> desc_ele86: DescEle; + 0x5602 -> cha: uint8; + 0x5603 -> point_index_attr: Debug_Byte; + + # Data set g87 + 0x5701 -> present_value: Debug_Byte; + + # Data set event g88 + 0x5801 -> snapshot: Debug_Byte; + + # application identification object g90 + #0x5A01 -> app_id: App_Id(qualifier_field, object_size16); + #0x5A01 -> app_id: App_Id(qualifier_field, prefix.prefix_value); + + # status of request operation g91 + 0x5b01 -> activate_conf: ActivateConf; + + # bcd value g101 + 0x6501 -> bcd_small: uint16; + 0x6502 -> bcd_medium: uint32; + 0x6503 -> bcd_large: BCD_Large; + + # unsigned integer g102 + 0x6601 -> unsigned_integer: uint8; + + # authentication challenge g120 + 0x7801 -> challenge: AuthChallenge(prefix.prefix_value); + 0x7802 -> reply: AuthRely(prefix.prefix_value); + 0x7803 -> aggrRequest: AuthAggrRequest(prefix.prefix_value); + 0x7804 -> seesionKeyRequest: uint8; + 0x7805 -> status: AuthSessionKeyStatus(prefix.prefix_value); + 0x7806 -> keyChange: AuthSessionKeyChange(prefix.prefix_value); + 0x7807 -> error: AuthError(prefix.prefix_value); + + default -> unmatched: Default_Wrap(object_type_field); + }; +}; + + +type Response_Data_Object(function_code: uint8, qualifier_field: uint8, object_type_field: uint16) = record { + prefix: Prefix_Type(qualifier_field); + data: case (object_type_field) of { + # device attributes g0 + 0x00D3 -> attrib211: AttributeCommon; + 0x00D4 -> attrib212: AttributeCommon; + 0x00D5 -> attrib213: AttributeCommon; + 0x00D6 -> attrib214: AttributeCommon; + 0x00D7 -> attrib215: AttributeCommon; + 0x00D8 -> attrib216: AttributeCommon; + 0x00D9 -> attrib217: AttributeCommon; + 0x00DA -> attrib218: AttributeCommon; + 0x00DB -> attrib219: AttributeCommon; + 0x00DC -> attrib220: AttributeCommon; + 0x00DD -> attrib221: AttributeCommon; + 0x00DE -> attrib222: AttributeCommon; + 0x00DF -> attrib223: AttributeCommon; + 0x00E0 -> attrib224: AttributeCommon; + 0x00E1 -> attrib225: AttributeCommon; + 0x00E2 -> attrib226: AttributeCommon; + 0x00E3 -> attrib227: AttributeCommon; + 0x00E4 -> attrib228: AttributeCommon; + 0x00E5 -> attrib229: AttributeCommon; + 0x00E6 -> attrib230: AttributeCommon; + 0x00E7 -> attrib231: AttributeCommon; + 0x00E8 -> attrib232: AttributeCommon; + 0x00E9 -> attrib233: AttributeCommon; + 0x00EA -> attrib234: AttributeCommon; + 0x00EB -> attrib235: AttributeCommon; + 0x00EC -> attrib236: AttributeCommon; + 0x00ED -> attrib237: AttributeCommon; + 0x00EE -> attrib238: AttributeCommon; + 0x00EF -> attrib239: AttributeCommon; + 0x00F0 -> attrib240: AttributeCommon; + 0x00F1 -> attrib241: AttributeCommon; + 0x00F2 -> attrib242: AttributeCommon; + 0x00F3 -> attrib243: AttributeCommon; + 0x00F5 -> attrib245: AttributeCommon; + 0x00F6 -> attrib246: AttributeCommon; + 0x00F7 -> attrib247: AttributeCommon; + 0x00F8 -> attrib248: AttributeCommon; + 0x00F9 -> attrib249: AttributeCommon; + 0x00FA -> attrib250: AttributeCommon; + 0x00FC -> attrib252: AttributeCommon; + 0x00FE -> attrib254: AttributeCommon; + 0x00FF -> attrib255: AttributeCommon; + + # binary input g1 + 0x0101 -> biwoflag: uint8; # warning: returning integer index? + 0x0102 -> biwflag: uint8; # warning: only flag? + + # binary input event g2 + 0x0201 -> biewoflag: uint8; + 0x0202 -> biewatime: BinInEveAtime; + 0x0203 -> biewrtime: BinInEveRtime; + + # double-bit Binary Input g3 + 0x0301 -> dbibytes: bytestring &restofdata; # don;t quit understand specification + 0x0302 -> dbiflag: uint8; + + # double-bit Binary Input Event g4 + 0x0401 -> dbieatime: DoubleInEveAtime; + 0x0402 -> dbiertime: DoubleInEveRtime; + + # binary output g10 + 0x0a01 -> bowoflag: uint8; # warning: returning integer index? + 0x0a02 -> bowflag: uint8; # warning: only flag? + + # binary output event g11 + 0x0b01 -> boewflag: uint8; + 0x0b02 -> boewatime: BinOutEveAtime; + + # binary output command g12 + 0x0c01 -> bocmd_CROB: CROB &check (function_code == SELECT || function_code == OPERATE || + function_code == DIRECT_OPERATE || function_code == DIRECT_OPERATE_NR ); + 0x0c02 -> bocmd_PCB: PCB &check (function_code == SELECT || function_code == OPERATE || + function_code == DIRECT_OPERATE || function_code == DIRECT_OPERATE_NR || function_code == WRITE ); + 0x0c03 -> bocmd_PM: uint8; + + # binary output command event g13 + 0x0d01 -> boceFlag: uint8; + 0x0d02 -> boceAtime: BinOutCmdEveAtime; + + # counter ; g20 + 0x1401 -> counter_32_wflag: Counter32wFlag; + 0x1402 -> counter_16_wflag: Counter16wFlag; + 0x1403 -> counter_32_wflag_delta: Debug_Byte &check (0); # obsolete situation; generate warning + 0x1404 -> counter_16_wflag_delta: Debug_Byte &check (0); # obsolete situations; generate warning + 0x1405 -> counter_32_woflag: Counter32woFlag; + 0x1406 -> counter_16_woflag: Counter16woFlag; + 0x1407 -> counter_32_woflag_delta: Debug_Byte &check (0); # obsolete + 0x1408 -> counter_16_woflag_delta: Debug_Byte &check (0); # obsolete + # frozen counter ; g21 + #0x1500 -> f_counter_default: empty; + 0x1501 -> f_counter_32_wflag: FrozenCounter32wFlag; + 0x1502 -> f_counter_16_wflag: FrozenCounter16wFlag; + 0x1503 -> f_counter_32_wflag_delta: Debug_Byte &check (0); # obsolete situation; generate warning + 0x1504 -> f_counter_16_wflag_delta: Debug_Byte &check (0); # obsolete situations; generate warning + 0x1505 -> f_counter_32_wflag_time: FrozenCounter32wFlagTime; + 0x1506 -> f_counter_16_wflag_time: FrozenCounter16wFlagTime; + 0x1507 -> f_counter_32_wflag_time_delta: Debug_Byte &check (0); # obsolete + 0x1508 -> f_counter_16_wflag_time_delta: Debug_Byte &check (0); # obsolete + 0x1509 -> f_counter_32_woflag: FrozenCounter32woFlag &check (f_counter_32_woflag.count_value == 23); + 0x150a -> f_counter_16_woflag: FrozenCounter16woFlag; + 0x150b -> f_counter_32_woflag_delta: Debug_Byte &check (0); # obsolete + 0x150c -> f_counter_16_woflag_delta: Debug_Byte &check (0); # obsolete + + # counter event g22 + 0x1601 -> counter_event_32_wflag: CounterEve32wFlag; + 0x1602 -> counter_event_16_wflag: CounterEve16wFlag; + 0x1603 -> counter_event_32_wflag_delta: Debug_Byte &check(0); + 0x1604 -> counter_event_16_wflag_delta: Debug_Byte &check(0); + 0x1605 -> counter_event_32_wflag_time: CounterEve32wFlagTime; + 0x1606 -> counter_event_16_wflag_time: CounterEve16wFlagTime; + 0x1607 -> counter_event_32_wflag_time_delta: Debug_Byte &check(0); + 0x1608 -> counter_event_16_wflag_time_delat: Debug_Byte &check(0); + + # counter event g23 + 0x1701 -> f_counter_event_32_wflag: CounterEve32wFlag; + 0x1702 -> f_counter_event_16_wflag: CounterEve16wFlag; + 0x1703 -> f_counter_event_32_wflag_delta: Debug_Byte &check(0); + 0x1704 -> f_counter_event_16_wflag_delta: Debug_Byte &check(0); + 0x1705 -> f_counter_event_32_wflag_time: CounterEve32wFlagTime; + 0x1706 -> f_counter_event_16_wflag_time: CounterEve16wFlagTime; + 0x1707 -> f_counter_event_32_wflag_time_delta: Debug_Byte &check(0); + 0x1708 -> f_counter_event_16_wflag_time_delat: Debug_Byte &check(0); + + # analog input g30 + 0x1e01 -> ai_32_wflag: AnalogInput32wFlag; + 0x1e02 -> ai_16_wflag: AnalogInput16wFlag; + 0x1e03 -> ai_32_woflag: AnalogInput32woFlag; + 0x1e04 -> ai_16_woflag: AnalogInput16woFlag; + 0x1e05 -> ai_sp_wflag: AnalogInputSPwFlag; + 0x1e06 -> ai_dp_wflag: AnalogInputDPwFlag; + + # frozen analog input g31 + 0x1f01 -> f_ai_32_wflag: FrozenAnalogInput32wFlag; + 0x1f02 -> f_ai_16_wflag: FrozenAnalogInput16wFlag; + 0x1f03 -> f_ai_32_wtime: FrozenAnalogInput32wTime; + 0x1f04 -> f_ai_16_wtime: FrozenAnalogInput16wTime; + 0x1f05 -> f_ai_32_woflag: AnalogInput32woFlag; + 0x1f06 -> f_ai_16_woflag: AnalogInput16woFlag; + 0x1f07 -> f_ai_sp_wflag: AnalogInputSPwFlag; + 0x1f08 -> f_ai_dp_wflag: AnalogInputDPwFlag; + + # analog input event g32 + 0x2001 -> ai32wotime: AnalogInput32woTime; + 0x2002 -> ai16wotime: AnalogInput16woTime; + 0x2003 -> ai32wtime: AnalogInput32wTime; + 0x2004 -> ai16wtime: AnalogInput16wTime; + 0x2005 -> aispwotime: AnalogInputSPwoTime; + 0x2006 -> aidpwotime: AnalogInputDPwoTime; + 0x2007 -> aispwtime: AnalogInputSPwTime; + 0x2008 -> aidpwtime: AnalogInputDPwTime; + + # frozen analog input event g33 + 0x2101 -> faie_32_wotime: FrozenAnaInputEve32woTime; + 0x2102 -> faie_16_wotime: FrozenAnaInputEve16woTime; + 0x2103 -> faie_32_wtime: FrozenAnaInputEve32wTime; + 0x2104 -> faie_16_wtime: FrozenAnaInputEve16wTime; + 0x2105 -> faie_sp_wotime: FrozenAnaInputEveSPwoTime; + 0x2106 -> faie_dp_wotime: FrozenAnaInputEveDPwoTime; + 0x2107 -> faie_sp_wtime: FrozenAnaInputEveSPwTime; + 0x2108 -> faie_dp_wtime: FrozenAnaInputEveDPwTime; + + # analog input deadband g34 + 0x2201 -> ai_dead_16: uint16; + 0x2202 -> ai_dead_32: uint32; + 0x2203 -> ai_dead_sp: uint32; + + # analog ouput status g40 + 0x2801 -> aos_32: AnaOutStatus32; + 0x2802 -> aos_16: AnaOutStatus16; + 0x2803 -> aos_sp: AnaOutStatusSP; + 0x2804 -> aos_dp: AnaOutStatusDP; + + # analog ouput g41 + 0x2901 -> ao_32: AnaOut32; + 0x2902 -> ao_16: AnaOut16; + 0x2903 -> ao_sp: AnaOutSP; + 0x2904 -> ao_dp: AnaOutDP; + + # analog output event g42 + 0x2a01 -> aoe32wotime: AnaOutEve32woTime; + 0x2a02 -> aoe16wotime: AnaOutEve16woTime; + 0x2a03 -> aoe32wtime: AnaOutEve32wTime; + 0x2a04 -> aoe16wtime: AnaOutEve16wTime; + 0x2a05 -> aoespwotime: AnaOutEveSPwoTime; + 0x2a06 -> aoedpwotime: AnaOutEveDPwoTime; + 0x2a07 -> aoespwtime: AnaOutEveSPwTime; + 0x2a08 -> aoedpwtime: AnaOutEveDPwTime; + + # analog output command event g43 + 0x2b01 -> aoce32wotime: AnaOutEve32woTime; + 0x2b02 -> aoce16wotime: AnaOutEve16woTime; + 0x2b03 -> aoce32wtime: AnaOutEve32wTime; + 0x2b04 -> aoce16wtime: AnaOutEve16wTime; + 0x2b05 -> aocespwotime: AnaOutEveSPwoTime; + 0x2b06 -> aocedpwotime: AnaOutEveDPwoTime; + 0x2b07 -> aocespwtime: AnaOutEveSPwTime; + 0x2b08 -> aocedpwtime: AnaOutEveDPwTime; + + # time data interval data object g50 + 0x3201 -> time_abs: AbsTime; + 0x3202 -> time_interval: AbsTimeInterval; + 0x3203 -> time_abs_last: Last_AbsTime; + + # Time and Date Common Time-of-Occurrence g51 + 0x3301 -> time_abs_sync: AbsTime; + 0x3302 -> time_abs_unsync: AbsTime; + + # time delay g52 + 0x3401 -> time_coarse: uint16; + 0x3402 -> time_fine: uint16; + + # file control g70 + 0x4601 -> file_control_id: File_Control_ID &check(0); + 0x4602 -> file_control_auth: File_Control_Auth &check(file_control_auth.usr_name_size == 0 && file_control_auth.pwd_size == 0); + 0x4603 -> file_control_cmd: File_Control_Cmd &check(file_control_cmd.name_size == 0 && + ( file_control_cmd.op_mode == 0 || file_control_cmd.op_mode == 1 || + file_control_cmd.op_mode == 2 || file_control_cmd.op_mode == 3) ); + 0x4604 -> file_control_cmd_status: File_Control_Cmd_Status(prefix.prefix_value); + 0x4605 -> file_trans: File_Transport(prefix.prefix_value); + 0x4606 -> file_trans_status: File_Transport_Status(prefix.prefix_value); + #0x4607 -> file_desc: File_Desc_Wrap(function_code); + 0x4607 -> file_desc: File_Desc; + + # internal indication g80 + 0x5001 -> iin: uint16; + # device storage g81 + 0x5101 -> dev_store: Dev_Store; + + # device storage g82 + 0x5201 -> dev_profile: Dev_Profile; + + # device storage g83 + 0x5301 -> priregobj: PrivRegObj; + 0x5302 -> priregobjdesc: PrivRegObjDesc; + + # device storage g85 + 0x5501 -> desc_ele: DescEle; + + # data descriptor table g86 + 0x5601 -> desc_ele86: DescEle; + 0x5602 -> cha: uint8; + 0x5603 -> point_index_attr: Debug_Byte; + + # Data set g87 + 0x5701 -> present_value: Debug_Byte; + + # Data set event g88 + 0x5801 -> snapshot: Debug_Byte; + + # status of request operation g91 + 0x5b01 -> activate_conf: ActivateConf; + + # bcd value g101 + 0x6501 -> bcd_small: uint16; + 0x6502 -> bcd_medium: uint32; + 0x6503 -> bcd_large: BCD_Large; + + # unsigned integer g102 + 0x6601 -> unsigned_integer: uint8; + + # authentication challenge g120 + 0x7801 -> challenge: AuthChallenge(prefix.prefix_value); + 0x7802 -> reply: AuthRely(prefix.prefix_value); + 0x7803 -> aggrRequest: AuthAggrRequest(prefix.prefix_value); + 0x7804 -> seesionKeyRequest: uint8; + 0x7805 -> status: AuthSessionKeyStatus(prefix.prefix_value); + 0x7806 -> keyChange: AuthSessionKeyChange(prefix.prefix_value); + 0x7807 -> error: AuthError(prefix.prefix_value); + + #default -> unkonwndata: Debug_Byte &check( T ); + default -> unmatched: Default_Wrap(object_type_field); + }; +} + &let{ + data_value: uint8 = case (object_type_field) of { # this data_value is used for the Bro Event + 0x0101 -> biwoflag; + 0x0102 -> biwflag; + 0x0a01 -> bowoflag; + 0x0a02 -> bowflag; + default -> 0xff; + }; + } +; + + +###### +# this Default_Wrap is created when dealing with g110. Only Group type matters and variations can be all. So too much coding +type Default_Wrap(obj_type: uint32) = record { + unresolved: case (obj_type & 0xFF00) of { + 0x6E00 -> oct_str: bytestring &length = (obj_type & 0x00FF) ; + 0x6F00 -> oct_str_eve: bytestring &length = (obj_type & 0x00FF) ; + 0x7000 -> vir_ter_out_blk: bytestring &length = (obj_type & 0x00FF) ; + 0x7100 -> vir_ter_eve: bytestring &length = (obj_type & 0x00FF) ; + + #default -> unknown: bytestring &restofdata; + default -> unknown: Debug_Byte; + }; +}; + +# contains different objects format +# corresponding to the DNP3Spec-V6-Part2-Objects + +# g0: group 0 objects are used to retrieve substation attributes; +# all variations including variation 249 255, share the same structure; +type AttributeCommon = record { + data_type_code: uint8; + leng: uint8; + attribute_obj: bytestring &length=leng; +} &byteorder = littleendian; + + +# g2v2 +type BinInEveAtime = record { + flag: uint8; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g2v3 +type BinInEveRtime = record { + flag: uint8; + time16: uint16; +} &byteorder = littleendian; + +# g4v2 +type DoubleInEveAtime = record { + flag: uint8; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g4v3 +type DoubleInEveRtime = record { + flag: uint8; + time16: uint16; +} &byteorder = littleendian; + +# g11v2 +type BinOutEveAtime = record { + flag: uint8; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g12v1 group: 12; variation: 1 +type CROB = record { + control_code: uint8 &check ( (control_code & 0xCF) == 0x00 || (control_code & 0xCF) == 0x01 || (control_code & 0xCF) == 0x03 || (control_code & 0xCF) == 0x04 || + (control_code & 0xCF) == 0x41 || (control_code & 0xCF) == 0x81 ); + count: uint8; + on_time: uint32; + off_time: uint32; + status_code: uint8; # contains the reserved bit +} &byteorder = littleendian; + +# g12v2; same as g12v1 +type PCB = record { + control_code: uint8 &check ( (control_code & 0xCF) == 0x00 || (control_code & 0xCF) == 0x01 || (control_code & 0xCF) == 0x03 || (control_code & 0xCF) == 0x04 || + (control_code & 0xCF) == 0x41 || (control_code & 0xCF) == 0x81 ); + count: uint8; + on_time: uint32; + off_time: uint32; + status_code: uint8; # contains the reserved bit +} &byteorder = littleendian; + +# g13v2 +type BinOutCmdEveAtime = record { + flag: uint8; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g20v1; group: 20, variation 1 +type Counter32wFlag = record { + flag: uint8; + count_value: uint32; +} &byteorder = littleendian; + +# g20v2 +type Counter16wFlag = record { + flag: uint8; + count_value: uint16; +} &byteorder = littleendian; + +# g20v3 and g20v4 are obsolete + +# g20v5 +type Counter32woFlag = record { + count_value: uint32; +} &byteorder = littleendian; + +# g20v6 +type Counter16woFlag = record { + count_value: uint16; +} &byteorder = littleendian; + +# g20v7 and g20v8 are obsolete + +# g21v1 +type FrozenCounter32wFlag = record { + flag: uint8; + count_value: uint32; +} &byteorder = littleendian; + +# g21v2 +type FrozenCounter16wFlag = record { + flag: uint8; + count_value: uint16; +} &byteorder = littleendian; + +# g21v3 and g21v4 are obsolete + +# g21v5 +type FrozenCounter32wFlagTime = record { + flag: uint8; + count_value: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g21v6 +type FrozenCounter16wFlagTime = record { + flag: uint8; + count_value: uint16; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g21v7 and g21v8 are obsolete + +# g21v9 +type FrozenCounter32woFlag = record { + count_value: uint32; +} &byteorder = littleendian; + +# g21v10 +type FrozenCounter16woFlag = record { + count_value: uint16; +} &byteorder = littleendian; + +# g21v11 and g21v12 are obsolete + +# Conter event g22 + +# g22v1 +type CounterEve32wFlag = record { + flag: uint8; + count_value: uint32; +} &byteorder = littleendian; + +# g22v2 +type CounterEve16wFlag = record { + flag: uint8; + count_value: uint16; +} &byteorder = littleendian; + +# g22v3 and g22v4 obsolete + +# g22v5 +type CounterEve32wFlagTime = record { + flag: uint8; + count_value: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g22v6 +type CounterEve16wFlagTime = record { + flag: uint8; + count_value: uint16; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g22v7 g22v8 obsolete + +# Conter event g23 + +# g23v1 +type FrozenCounterEve32wFlag = record { + flag: uint8; + count_value: uint32; +} &byteorder = littleendian; + +# g23v2 +type FrozenCounterEve16wFlag = record { + flag: uint8; + count_value: uint16; +} &byteorder = littleendian; + +# g23v3 and g23v4 obsolete + +# g23v5 +type FrozenCounterEve32wFlagTime = record { + flag: uint8; + count_value: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g23v6 +type FrozenCounterEve16wFlagTime = record { + flag: uint8; + count_value: uint16; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g23v7 g23v8 obsolete + +# group: 30; variation: 1 g30v1 +type AnalogInput32wFlag = record { + flag: uint8; + value: int32; +} &byteorder = littleendian; + +# group: 30; variation: 2 +type AnalogInput16wFlag = record { + flag: uint8; + value: int16; +} &byteorder = littleendian; + +# group: 30; variation: 3 +type AnalogInput32woFlag = record { + value: int32; +} &byteorder = littleendian; + +# group: 30; variation: 4 +type AnalogInput16woFlag = record { + value: int16; +} &byteorder = littleendian; + +# group: 30; variation: 5; singple precision 32 bit +type AnalogInputSPwFlag = record { + flag: uint8; + value: uint32; +} &byteorder = littleendian; + +# group: 30; variation: 6; double precision 64 bit +type AnalogInputDPwFlag = record { + flag: uint8; + value_low: uint32; + value_high: uint32; +} &byteorder = littleendian; + +# g31v1 +type FrozenAnalogInput32wFlag = record { + flag: uint8; + frozen_value: int32; +} &byteorder = littleendian; + +# g31v2 +type FrozenAnalogInput16wFlag = record { + flag: uint8; + frozen_value: int16; +} &byteorder = littleendian; + +# g31v3 +type FrozenAnalogInput32wTime = record { + flag: uint8; + frozen_value: int32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g31v4 +type FrozenAnalogInput16wTime = record { + flag: uint8; + frozen_value: int16; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g31v5 +type FrozenAnalogInput32woFlag = record { + frozen_value: int32; +} &byteorder = littleendian; + +# g31v6 +type FrozenAnalogInput16woFlag = record { + frozen_value: uint16; +} &byteorder = littleendian; + +# g31v7 +type FrozenAnalogInputSPwFlag = record { + flag: uint8; + frozen_value: uint32; +} &byteorder = littleendian; + +# g31v8 +type FrozenAnalogInputDPwFlag = record { + flag: uint8; + frozen_value_low: uint32; + frozen_value_high: uint32; +} &byteorder = littleendian; + +# group: 32; variation: 1 g32v1 +type AnalogInput32woTime = record { + flag: uint8; + value: int32; +} &byteorder = littleendian; + +# group: 32; variation: 2 +type AnalogInput16woTime = record { + flag: uint8; + value: int16; +} &byteorder = littleendian; + +# group: 32; variation: 3 +type AnalogInput32wTime = record { + flag: uint8; + value: int32; + #time: uint8[6]; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# group: 32; variation: 4 +type AnalogInput16wTime = record { + flag: uint8; + value: int16; + #time: uint8[6]; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# group: 32; variation: 5; singple precision 32 bit +type AnalogInputSPwoTime = record { + flag: uint8; + value: uint32; +} &byteorder = littleendian; + +# group: 32; variation: 6; double precision 64 bit +type AnalogInputDPwoTime = record { + flag: uint8; + value_low: uint32; + value_high: uint32; +} &byteorder = littleendian; + +# group: 32; variation: 7 +type AnalogInputSPwTime = record { + flag: uint8; + value: uint32; + #time: uint8[6]; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# group: 32; variation: 8 +type AnalogInputDPwTime = record { + flag: uint8; + value_low: uint32; + value_high: uint32; + #time: uint8[6]; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g33v1 +type FrozenAnaInputEve32woTime = record { + flag: uint8; + f_value: int32; +} &byteorder = littleendian; + +# g33v2 +type FrozenAnaInputEve16woTime = record { + flag: uint8; + f_value: int16; +} &byteorder = littleendian; + +# g33v3 +type FrozenAnaInputEve32wTime = record { + flag: uint8; + f_value: int32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g33v4 +type FrozenAnaInputEve16wTime = record { + flag: uint8; + f_value: int32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g33v5 +type FrozenAnaInputEveSPwoTime = record { + flag: uint8; + f_value: uint32; +} &byteorder = littleendian; + +# g33v6 +type FrozenAnaInputEveDPwoTime = record { + flag: uint8; + f_value_low: uint32; + f_value_high: uint32; +} &byteorder = littleendian; + +# g33v7 +type FrozenAnaInputEveSPwTime = record { + flag: uint8; + f_value: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g33v8 +type FrozenAnaInputEveDPwTime = record { + flag: uint8; + f_value_low: uint32; + f_value_high: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# analog output status g40 + +# g40v1 +type AnaOutStatus32 = record { + flag: uint8; + status: int32; +} &byteorder = littleendian; + +# g40v2 +type AnaOutStatus16 = record { + flag: uint8; + status: int16; +} &byteorder = littleendian; + +# g40v3 +type AnaOutStatusSP = record { + flag: uint8; + status: uint32; +} &byteorder = littleendian; + +# g40v4 +type AnaOutStatusDP = record { + flag: uint8; + status_low: uint32; + status_high: uint32; +} &byteorder = littleendian; + +# analog output g41 + +# g41v1 +type AnaOut32 = record { + value: int32; + con_status: uint8; +} &byteorder = littleendian; + +# g41v2 +type AnaOut16 = record { + value: int16; + con_status: uint8; +} &byteorder = littleendian; + +# g41v3 +type AnaOutSP = record { + value: uint32; + con_status: uint8; +} &byteorder = littleendian; + +# g41v4 +type AnaOutDP = record { + value_low: uint32; + value_high: uint32; + con_status: uint8; +} &byteorder = littleendian; + +# analog output event g42 + +# g42v1 +type AnaOutEve32woTime = record { + flag: uint8; + value: int32; +} &byteorder = littleendian; + +# g42v2 +type AnaOutEve16woTime = record { + flag: uint8; + value: int16; +} &byteorder = littleendian; + +# g42v3 +type AnaOutEve32wTime = record { + flag: uint8; + value: int32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g42v4 +type AnaOutEve16wTime = record { + flag: uint8; + value: int16; + time48: bytestring &length = 6; +} &byteorder = littleendian; +# g42v5 +type AnaOutEveSPwoTime = record { + flag: uint8; + value: uint32; +} &byteorder = littleendian; + +# g42v6 +type AnaOutEveDPwoTime = record { + flag: uint8; + value_low: uint32; + value_high: uint32; +} &byteorder = littleendian; + +# g42v7 +type AnaOutEveSPwTime = record { + flag: uint8; + value: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g42v8 +type AnaOutEveDPwTime = record { + flag: uint8; + value_low: uint32; + value_high: uint32; + time48: bytestring &length = 6; +} &byteorder = littleendian; + +## g43 data format is exacatly same as g42 so use g42 directly + +# g50v1 +type AbsTime = record { + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g50v2 +type AbsTimeInterval = record { + time48: bytestring &length = 6; + interval: uint32; +} &byteorder = littleendian; + +# g50v3 +type Last_AbsTime = record { + time48: bytestring &length = 6; +} &byteorder = littleendian; + +# g51v1 and g51v2 are the same structure of g50v1. so reuse it + +# g70v1 +type Record_Obj = record { + record_size: uint16; + record_oct: bytestring &length = record_size; +} &byteorder = littleendian; + +type File_Control_ID = record { + name_size: uint16; + type_code: uint8; + attr_code: uint8; + start_rec: uint16; + end_rec: uint16; + file_size: uint32; + time_create: bytestring &length = 6; + permission: uint16 &check ( (permission & 0xFE00 ) == 0x0000); + file_id: uint32; + owner_id: uint32; + group_id: uint32; + function_code: uint8; + status_code: uint8; + file_name: bytestring &length = name_size; + records: Record_Obj[]; +} &byteorder = littleendian; + +# g70v2 +type File_Control_Auth_Wrap(fc: uint8) = record { + data: case(fc) of { + AUTHENTICATE_FILE -> auth_file: File_Control_Auth &check(auth_file.auth_key == 0) ; + default -> null: empty; + }; +}; + +type File_Control_Auth = record { + usr_name_offset: uint16; + usr_name_size: uint16; + pwd_offset: uint16; + pwd_size: uint16; + auth_key: uint32; + usr_name: bytestring &length = usr_name_size; + pwd: bytestring &length = pwd_size; +} &byteorder = littleendian; + +# g70v3 +type File_Control_Cmd_Wrap(function_code: uint8) = record { + data_obj: case (function_code) of { + OPEN_FILE -> fc_cmd_open: File_Control_Cmd; + DELETE_FILE -> fc_cmd_del: File_Control_Cmd &check( fc_cmd_del.op_mode == 0 && fc_cmd_del.name_size == 0 && fc_cmd_del.time_create == 0x0); + default -> null: empty; + }; + +}; + +type File_Control_Cmd = record { + name_offset: uint16; + name_size: uint16; + time_create: bytestring &length = 6; + permission: uint16 &check ( (permission & 0xFE00 ) == 0x0000); + auth_key: uint32; + file_size: uint32; + op_mode: uint16; + max_block_size: uint16; + req_id: uint16; + file_name: bytestring &length = name_size; +} &byteorder = littleendian; + +# g70v4 +type File_Control_Cmd_Status_Wrap(function_code: uint8, obj_size: uint32) = record { + data_obj: case (function_code) of { + ABORT_FILE -> abort: File_Control_Cmd_Status(obj_size) &check(abort.file_size == 0 && abort.max_block_size ==0 && abort.status_code ==0 ); + RESPONSE -> fc_cmd_status: File_Control_Cmd_Status(obj_size); + default -> null: empty; + }; +}; + +type File_Control_Cmd_Status(obj_size: uint32) = record { + file_handle: uint32; + file_size: uint32; + max_block_size: uint16; + req_id: uint16 ; + status_code: uint8; + #opt_text: bytestring &restofdata; + opt_text: bytestring &length = (obj_size - 8 - 4 - 1); +} &byteorder = littleendian; + +# g70v5 +type File_Transport(obj_size: uint32) = record { + file_handle: uint32; + block_num: uint32; + # file_data: bytestring &restofdata; + file_data: bytestring &length = (obj_size - 8); +} &byteorder = littleendian; + +# g70v6 +type File_Transport_Status(obj_size: uint32) = record { + file_handle: uint32; + block_num: uint32; + status: uint8; + #file_data: bytestring &restofdata; + opt_text: bytestring &length = (obj_size - 4 - 4 - 1); +} &byteorder = littleendian; + +# g70v7 +type File_Desc_Wrap(function_code: uint8) = record { + data: case(function_code) of { + GET_FILE_INFO -> get_file_info: File_Desc &check(get_file_info.type ==0 && + get_file_info.f_size == 0 && + get_file_info.time_create_low == 0 && + get_file_info.time_create_high == 0 && + get_file_info.permission == 0); + default -> null: empty; + }; +} &byteorder = littleendian; + +type File_Desc = record { + name_offset: uint16; + name_size: uint16; + type: uint16; + f_size: uint32; + time_create_low: uint32; + time_create_high: uint16; + permission: uint16 &check ( (permission & 0xFE00 ) == 0x0000); + req_id: uint16; + f_name: bytestring &length = name_size; +} &byteorder = littleendian; + +# g70v8 +type File_Spec_Str = record { + f_spec: bytestring &restofdata; +} &byteorder = littleendian; + +# device storage g81 +# g81v1 +type Dev_Store = record { + overflow: uint8; + obj_group: uint8; + variatiion: uint8; +} &byteorder = littleendian; + +# device profile g82 +# g82v1 +type Dev_Profile = record { + fc_support_low: uint32; + fc_support_high: uint32; + count: uint16; + dev_headers: Dev_Profile_OH[count]; +} &byteorder = littleendian; + +type Dev_Profile_OH = record { + group: uint8; + variation: uint8; + qualifier: uint8; + range: uint8; +} &byteorder = littleendian; + +# data set g983 + +# g83v1 +type PrivRegObj = record { + vendor: uint32; + obj_id: uint16; + len: uint16; + data_objs: bytestring &length = len; +} &byteorder = littleendian; + +# g83v2 +type PrivRegObjDesc = record { + vendor: uint32; + obj_id: uint16; + count: uint16; + data_objs: ObjDescSpec[count]; +} &byteorder = littleendian; + +type ObjDescSpec = record { + obj_quantity: uint16; + obj_group: uint8; + obj_variation: uint8; +} &byteorder = littleendian; + +# data set prototype g85 + +# g85v1 only one descriptor element is defined. number of n is defined by number-of-item +type DescEle = record { + len: uint8; + desc_code: uint8; + data_type: uint8; + max_len: uint8; + ancillary: uint8; +} &byteorder = littleendian; + +# data descriptor element g86 + +# g86v1 is the same structure of DescEle + +# g86v3 does not quite understant specification description + +# g87 doest not quite understand specfication description + +# g88 doest not quite understand specfication description + +# g90v1 +type App_Id(qualifier_field: uint8, object_size16: uint16) = record { + app_id: case (qualifier_field) of { + 0x5B -> app_name: bytestring &length = object_size16; + 0x06 -> all_app: empty; + default -> illegal: empty; + }; +} &byteorder = littleendian; + +# status of request operation g91 +type ActivateConf = record { + time_delay: uint32; + count: uint8; + elements: StatusEle[count]; +} &byteorder = littleendian; + +type StatusEle = record { + len: uint8; + status_code: uint8; + ancillary: bytestring &length = ( len - 1 ); +} &byteorder = littleendian; + +# BCD values + +# g101v3 +type BCD_Large = record { + value_low: uint32; + vlaue_high: uint32; +} &byteorder = littleendian; + +# authentication g120 + +# g120v1 +type AuthChallenge(prefix: uint16) = record { + csqUsr: uint32; + hal: uint8; + reason: uint8; + chan_data: bytestring &length = (prefix - 10); +} &byteorder = littleendian; + +# g120v2 +type AuthRely(prefix: uint16) = record { + csqUsr: uint32; + chan_data: bytestring &length = (prefix - 4); +} &byteorder = littleendian; + +# g120v3 +type AuthAggrRequest(prefix: uint16) = record { + csqUsr: uint32; + chan_data: bytestring &length = (prefix - 4); +} &byteorder = littleendian; + +# g120v5 +type AuthSessionKeyStatus(prefix: uint16) = record { + csqUsr: uint32; + key_alg: uint8; + key_status: uint8; + chan_data: bytestring &length = (prefix - 10); +} &byteorder = littleendian; + +# g120v6 +type AuthSessionKeyChange(prefix: uint16) = record { + csqUsr: uint32; + key_wrap_data: bytestring &length = (prefix - 5); +} &byteorder = littleendian; + +# g120v7 +type AuthError(prefix: uint16) = record { + csqUsr: uint32; + error_code: uint8; + key_wrap_data: bytestring &length = (prefix - 6); +} &byteorder = littleendian; diff --git a/src/analyzer/protocol/dnp3/dnp3-protocol.pac b/src/analyzer/protocol/dnp3/dnp3-protocol.pac new file mode 100644 index 0000000000..02fbd678cc --- /dev/null +++ b/src/analyzer/protocol/dnp3/dnp3-protocol.pac @@ -0,0 +1,257 @@ +# +# This is Binpac code for DNP3 analyzer by Hui Lin. +# + +type DNP3_PDU(is_orig: bool) = case is_orig of { + true -> request: DNP3_Request; + false -> response: DNP3_Response; +} &byteorder = bigendian; + +type Header_Block = record { + start: uint16 &check(start == 0x0564); + len: uint8; + ctrl: uint8; + dest_addr: uint16; + src_addr: uint16; +} &byteorder = littleendian; + +type DNP3_Request = record { + addin_header: Header_Block; ## added by Hui Lin in Bro code + app_header: DNP3_Application_Request_Header; + data: case ( app_header.function_code ) of { + CONFIRM -> none_coonfirm: empty; + READ -> read_requests: Request_Objects(app_header.function_code)[]; + WRITE -> write_requests: Request_Objects(app_header.function_code)[]; + SELECT -> select_requests: Request_Objects(app_header.function_code)[]; + OPERATE -> operate_requests: Request_Objects(app_header.function_code)[]; + DIRECT_OPERATE -> direct_operate_requests: Request_Objects(app_header.function_code)[]; + DIRECT_OPERATE_NR -> direct_operate_nr_requests: Request_Objects(app_header.function_code)[]; + IMMED_FREEZE -> immed_freeze_requests: Request_Objects(app_header.function_code)[]; + IMMED_FREEZE_NR -> immed_freeze_nr_requests: Request_Objects(app_header.function_code)[]; + FREEZE_CLEAR -> freeze_clear_requests: Request_Objects(app_header.function_code)[]; + FREEZE_CLEAR_NR -> freeze_clear_nr_requests: Request_Objects(app_header.function_code)[]; + FREEZE_AT_TIME -> freeze_time_requests: Request_Objects(app_header.function_code)[]; + FREEZE_AT_TIME_NR -> freeze_time_nr_requests: Request_Objects(app_header.function_code)[]; + COLD_RESTART -> cold_restart: empty; + WARM_RESTART -> warm_restart: empty; + INITIALIZE_DATA -> initilize_data: empty &check(0); # obsolete + INITIALIZE_APPL -> initilize_appl: Request_Objects(app_header.function_code)[]; + START_APPL -> start_appl: Request_Objects(app_header.function_code)[]; + STOP_APPL -> stop_appl: Request_Objects(app_header.function_code)[]; + SAVE_CONFIG -> save_config: empty &check(0); # depracated + ENABLE_UNSOLICITED -> enable_unsolicited: Request_Objects(app_header.function_code)[]; + DISABLE_UNSOLICITED -> disable_unsolicited: Request_Objects(app_header.function_code)[]; + ASSIGN_CLASS -> assign_class: Request_Objects(app_header.function_code)[]; + DELAY_MEASURE -> delay_measure: empty; + RECORD_CURRENT_TIME -> record_cur_time: empty; + OPEN_FILE -> open_file: Request_Objects(app_header.function_code)[]; + CLOSE_FILE -> close_file: Request_Objects(app_header.function_code)[]; + DELETE_FILE -> delete_file: Request_Objects(app_header.function_code)[]; + ABORT_FILE -> abort_file: Request_Objects(app_header.function_code)[]; + GET_FILE_INFO -> get_file_info: Request_Objects(app_header.function_code)[]; + AUTHENTICATE_FILE -> auth_file: Request_Objects(app_header.function_code)[]; + ACTIVATE_CONFIG -> active_config: Request_Objects(app_header.function_code)[]; + AUTHENTICATE_REQ -> auth_req: Request_Objects(app_header.function_code)[]; + AUTHENTICATE_ERR -> auth_err: Request_Objects(app_header.function_code)[]; + default -> unknown: bytestring &restofdata; + }; +} &byteorder = bigendian + &length= 9 + addin_header.len - 5 - 1; + +type Debug_Byte = record { + debug: bytestring &restofdata; +}; + +type DNP3_Response = record { + addin_header: Header_Block; + app_header: DNP3_Application_Response_Header; + data: case ( app_header.function_code ) of { + RESPONSE -> response_objects: Response_Objects(app_header.function_code)[]; + UNSOLICITED_RESPONSE -> unsolicited_response_objects: Response_Objects(app_header.function_code)[]; + AUTHENTICATE_RESP -> auth_response: Response_Objects(app_header.function_code)[]; + default -> unknown: Debug_Byte; + }; +} &byteorder = bigendian + &length= 9 + addin_header.len - 5 - 1' + +type DNP3_Application_Request_Header = record { + empty: bytestring &length = 0; # Work-around BinPAC problem. + application_control : uint8; + function_code : uint8 ; +} &length = 2; + +type DNP3_Application_Response_Header = record { + empty: bytestring &length = 0; # Work-around BinPAC problem. + application_control : uint8; + function_code : uint8; + internal_indications : uint16; +} &length = 4; + +type Request_Objects(function_code: uint8) = record { + object_header: Object_Header(function_code); + data: case (object_header.object_type_field) of { + 0x0c03 -> bocmd_PM: Request_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ ( object_header.number_of_item / 8 ) + 1 ]; + 0x3202 -> time_interval_ojbects: Request_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ object_header.number_of_item] + &check( object_header.qualifer_field == 0x0f && object_header.number_of_item == 0x01); + default -> ojbects: Request_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ object_header.number_of_item]; + }; + # dump_data is always empty; I intend to use it for checking some conditions; + # However, in the current binpac implementation, &check is not implemented + dump_data: case (function_code) of { + OPEN_FILE -> open_file_dump: empty &check(object_header.object_type_field == 0x4603); + CLOSE_FILE -> close_file_dump: empty &check(object_header.object_type_field == 0x4604); + DELETE_FILE -> delete_file_dump: empty &check(object_header.object_type_field == 0x4603); + ABORT_FILE -> abort_file_dump: empty &check(object_header.object_type_field == 0x4604); + GET_FILE_INFO -> get_file_info: empty &check(object_header.object_type_field == 0x4607); + AUTHENTICATE_FILE -> auth_file: empty &check(object_header.object_type_field == 0x4602); + ACTIVATE_CONFIG -> active_config: empty &check(object_header.object_type_field == 0x4608 || (object_header.object_type_field & 0xFF00) == 0x6E00); + default -> default_dump: empty; + }; +}; + +type Response_Objects(function_code: uint8) = record { + object_header: Object_Header(function_code); + data: case (object_header.object_type_field) of { + 0x0101 -> biwoflag: Response_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ ( object_header.number_of_item / 8 ) ]; + 0x0301 -> diwoflag: Response_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ ( object_header.number_of_item / 8 ) ]; + 0x0a01 -> bowoflag: Response_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ ( object_header.number_of_item / 8 ) ]; + 0x0c03 -> bocmd_PM: Response_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ ( object_header.number_of_item / 8 ) ]; + default -> ojbects: Response_Data_Object(function_code, object_header.qualifier_field, object_header.object_type_field )[ object_header.number_of_item]; + }; +}; + +type Object_Header(function_code: uint8) = record { + object_type_field: uint16 ; + qualifier_field: uint8 ; + range_field: case ( qualifier_field & 0x0f ) of { + 0 -> range_field_0: Range_Field_0 &check(range_field_0.stop_index >= range_field_0.start_index); + 1 -> range_field_1: Range_Field_1 &check(range_field_1.stop_index >= range_field_1.start_index); + 2 -> range_field_2: Range_Field_2 &check(range_field_2.stop_index >= range_field_2.start_index); + 3 -> range_field_3: Range_Field_3; + 4 -> range_field_4: Range_Field_4; + 5 -> range_field_5: Range_Field_5; + 6 -> range_field_6: empty; + 7 -> range_field_7: uint8; + 8 -> range_field_8: uint16; + 9 -> range_field_9: uint32; + 0x0b -> range_field_b: uint8; + default -> unknown: bytestring &restofdata &check(0); + }; + # dump_data is always empty; used to check dependency bw object_type_field and qualifier_field + dump_data: case ( object_type_field & 0xff00 ) of { + 0x3C00 -> dump_3c: empty &check( (object_type_field == 0x3C01 || object_type_field == 0x3C02 || object_type_field == 0x3C03 || object_type_field == 0x3C04) && ( qualifier_field == 0x06 ) ); + default -> dump_def: empty; + }; +} + &let{ + number_of_item: int = case (qualifier_field & 0x0f) of { + 0 -> (range_field_0.stop_index - range_field_0.start_index + 1); + 1 -> (range_field_1.stop_index - range_field_1.start_index + 1); + 2 -> (range_field_2.stop_index - range_field_2.start_index + 1); + 7 -> range_field_7; + 8 -> ( range_field_8 & 0x0ff )* 0x100 + ( range_field_8 / 0x100 ) ; + 9 -> ( range_field_9 & 0x000000ff )* 0x1000000 + (range_field_9 & 0x0000ff00) * 0x100 + (range_field_9 & 0x00ff0000) / 0x100 + (range_field_9 & 0xff000000) / 0x1000000 ; + 0x0b -> range_field_b; + default -> 0; + }; + rf_value_low: int = case (qualifier_field & 0x0f) of { + 0 -> 0 + range_field_0.start_index; + 1 -> range_field_1.start_index; + 2 -> range_field_2.start_index; + 3 -> range_field_3.start_addr; + 4 -> range_field_4.start_addr; + 5 -> range_field_5.start_addr; + 6 -> 0xffff; + 7 -> range_field_7; + 8 -> range_field_8; + 9 -> range_field_9; + 0x0b -> range_field_b; + default -> 0 ; + }; + rf_value_high: int = case (qualifier_field & 0x0f) of { + 0 -> 0 + range_field_0.stop_index; + 1 -> range_field_1.stop_index; + 2 -> range_field_2.stop_index; + 3 -> range_field_3.stop_addr; + 4 -> range_field_4.stop_addr; + 5 -> range_field_5.stop_addr; + 6 -> 0xffff; + default -> 0 ; + }; +}; + +type Range_Field_0 = record { + start_index: uint8; + stop_index: uint8; +}; + +type Range_Field_1 = record { + start_index: uint16; + stop_index: uint16; +} + &byteorder = littleendian; + +type Range_Field_2 = record { + start_index: uint32; + stop_index: uint32; +} + &byteorder = littleendian; + +type Range_Field_3 = record { + start_addr: uint8; + stop_addr: uint8; +}; + +type Range_Field_4 = record { + start_addr: uint16; + stop_addr: uint16; +}; + +type Range_Field_5 = record { + start_addr: uint32; + stop_addr: uint32; +}; + +enum function_codes_value { + CONFIRM = 0x00, + READ = 0x01, + WRITE = 0x02, + SELECT = 0x03, + OPERATE = 0x04, + DIRECT_OPERATE = 0x05, + DIRECT_OPERATE_NR = 0x06, + IMMED_FREEZE = 0x07, + IMMED_FREEZE_NR = 0x08, + FREEZE_CLEAR = 0x09, + FREEZE_CLEAR_NR = 0x0a, + FREEZE_AT_TIME = 0x0b, + FREEZE_AT_TIME_NR = 0x0c, + COLD_RESTART = 0x0d, + WARM_RESTART = 0x0e, + INITIALIZE_DATA = 0x0f, + INITIALIZE_APPL = 0x10, + START_APPL = 0x11, + STOP_APPL = 0x12, + SAVE_CONFIG = 0x13, + ENABLE_UNSOLICITED = 0x14, + DISABLE_UNSOLICITED = 0x15, + ASSIGN_CLASS = 0x16, + DELAY_MEASURE = 0x17, + RECORD_CURRENT_TIME = 0x18, + OPEN_FILE = 0x19, + CLOSE_FILE = 0x1a, + DELETE_FILE = 0x1b, + GET_FILE_INFO = 0x1c, + AUTHENTICATE_FILE = 0x1d, + ABORT_FILE = 0x1e, + ACTIVATE_CONFIG = 0x1f, + AUTHENTICATE_REQ = 0x20, + AUTHENTICATE_ERR = 0x21, +# reserved + RESPONSE = 0x81, + UNSOLICITED_RESPONSE = 0x82, + AUTHENTICATE_RESP = 0x83, +# reserved +}; + +%include dnp3-objects.pac diff --git a/src/analyzer/protocol/dnp3/dnp3.pac b/src/analyzer/protocol/dnp3/dnp3.pac new file mode 100644 index 0000000000..57005fd8e1 --- /dev/null +++ b/src/analyzer/protocol/dnp3/dnp3.pac @@ -0,0 +1,16 @@ + +%include binpac.pac +%include bro.pac + +%extern{ +#include "events.bif.h" +%} + +analyzer DNP3 withcontext { + connection: DNP3_Conn; + flow: DNP3_Flow; +}; + +%include dnp3-protocol.pac +%include dnp3-analyzer.pac + diff --git a/src/analyzer/protocol/dnp3/events.bif b/src/analyzer/protocol/dnp3/events.bif new file mode 100644 index 0000000000..b537d2f8a5 --- /dev/null +++ b/src/analyzer/protocol/dnp3/events.bif @@ -0,0 +1,241 @@ + +### event handler that is used to analyze network packets based on DNP3 protocol +### starts with dnp3_ +### In src/DNP3.cc, we include detailed descriptions on how DNP3 Pseudo Link Layer, +### DNP3 Pseudo Transport Layer, DNP3 Pseudo Application Layer are packed into application +### layer payload over TCP +### The event handlers defined for binpac DNP3 analyzer are used to analyze fields +### of the DNP3 Pseudo Application Layer. +### we have tried our best to name the event handler by the field names that is described +### in DNP3 Specification Volum 2, Part 1 Basic, Application Layer, DNP3 Specification +### Volum 4, Data Link Layer, DNP3 Specification Volum 6, Part 1 Basic, DNP3 OBJECT LIBRARY, +### DNP3 Specification Volum 6, Part 2 Objects, DNP3 OBJECT LIBRARY + +event dnp3_debug_bufferBytes%(c: connection , is_orig: bool, buffer_bytes: count%); + + +## Generated for the request header in Pseudo Application Layer. +## The request header contains two fields: +## fc: function code. +event dnp3_application_request_header%(c: connection, is_orig: bool, fc: count%); + +## Generated for the response header in Pseudo Application Layer. +## The response header contains three fields: +## app_control: application control field. +## fc: function code. +## iin: internal indication number +event dnp3_application_response_header%(c: connection, is_orig: bool, fc: count, iin: count%); + +## Generated for the object header found in both DNP3 requests and responses +## obj_type: type of object, which is classified based on an 8-bit group number and an 8-bit variation number +## qua_field: qualifier field +## rf_low, rf_high: the structure of the range field depends on the qualified field. In some cases, range field +## contain only one logic part, e.g., number of objects, so only rf_low contains the useful values; in some +## cases, range field contain two logic parts, e.g., start index and stop index, so rf_low contains the start +## index while rf_high contains the stop index +event dnp3_object_header%(c: connection, is_orig: bool, obj_type: count, qua_field: count, number: count, rf_low: count, rf_high: count%); + +## Generated for the prefix before each object. +## the structure and the meaning of the prefix are defined by the qualifier field +event dnp3_object_prefix%(c: connection, is_orig: bool, prefix_value: count%); + +## Generated for the additional header that is added by the DNP3.cc; +## the reason to add this header is found in DNP3.cc +## start: the first two bytes of the DNP3 Pseudo Link Layer; its value is fixed as 0x0564 +## len: the "length" field in the DNP3 Pseudo Link Layer +## ctrl: the "control" field in the DNP3 Pseudo Link Layer +## dest_addr: the "destination" field in the DNP3 Pseudo Link Layer +## src_addr: the "source" field in the DNP3 Pseudo Link Layer +event dnp3_header_block%(c: connection, is_orig: bool, start: count, len: count, ctrl: count, dest_addr: count, src_addr: count%); + +## Generated for "Response_Data_Object" +## the "Response_Data_Object" contains two parts: object prefix and objects data. +## In most cases, objects data are defined by new record types. But in a few +## cases, objects data are directly basic types, such as int16, or int8; thus we use +## a additional data_value to record the values of those object data. +event dnp3_response_data_object%(c: connection, is_orig: bool, data_value: count%); + +## Different from most binpac scripts, which consists only two pac files: *-analyzer.pac +## and *-protocol.pac. I use a separate pac files, i.e., dnp3-objects.pac to contain +## different types of object data. +## The following event handlers are all generated for the different object data types. + +event dnp3_attribute_common%(c: connection, is_orig: bool, data_type_code: count, leng: count, attribute_obj: string%); + +## Generated for the object with the group number 12 and variation number 1 +## CROB: control relay output block +event dnp3_crob%(c: connection, is_orig: bool, control_code: count, count8: count, on_time: count, off_time: count, status_code: count%); + +## Generated for the object with the group number 12 and variation number 2 +## PCB: Pattern Control Block +event dnp3_pcb%(c: connection, is_orig: bool, control_code: count, count8: count, on_time: count, off_time: count, status_code: count%); + +## Generated for the object with the group number 20 and variation number 1 +## counter 32 bit with flag +event dnp3_counter_32wFlag%(c: connection, is_orig: bool, flag: count, count_value: count%); + +## Generated for the object with the group number 20 and variation number 2 +## counter 16 bit with flag +event dnp3_counter_16wFlag%(c: connection, is_orig: bool, flag: count, count_value: count%); + +## Generated for the object with the group number 20 and variation number 5 +## counter 32 bit without flag +event dnp3_counter_32woFlag%(c: connection, is_orig: bool, count_value: count%); + +## Generated for the object with the group number 20 and variation number 6 +## counter 16 bit without flag +event dnp3_counter_16woFlag%(c: connection, is_orig: bool, count_value: count%); + +## Generated for the object with the group number 21 and variation number 1 +## frozen counter 32 bit with flag +event dnp3_frozen_counter_32wFlag%(c: connection, is_orig: bool, flag:count, count_value: count%); + +## Generated for the object with the group number 21 and variation number 2 +## frozen counter 16 bit with flag +event dnp3_frozen_counter_16wFlag%(c: connection, is_orig: bool, flag:count, count_value: count%); + +## Generated for the object with the group number 21 and variation number 5 +## frozen counter 32 bit with flag and time +event dnp3_frozen_counter_32wFlagTime%(c: connection, is_orig: bool, flag:count, count_value: count, time48: string%); + +## Generated for the object with the group number 21 and variation number 6 +## frozen counter 16 bit with flag and time +event dnp3_frozen_counter_16wFlagTime%(c: connection, is_orig: bool, flag:count, count_value: count, time48: string%); + +## Generated for the object with the group number 21 and variation number 9 +## frozen counter 32 bit without flag +event dnp3_frozen_counter_32woFlag%(c: connection, is_orig: bool, count_value: count%); + +## Generated for the object with the group number 21 and variation number 10 +## frozen counter 16 bit without flag +event dnp3_frozen_counter_16woFlag%(c: connection, is_orig: bool, count_value: count%); + +## Generated for the object with the group number 30 and variation number 1 +## analog input 32 bit with flag +event dnp3_analog_input_32wFlag%(c: connection, is_orig: bool, flag: count, value: count%); + +## Generated for the object with the group number 30 and variation number 2 +## analog input 16 bit with flag +event dnp3_analog_input_16wFlag%(c: connection, is_orig: bool, flag: count, value: count%); + +## Generated for the object with the group number 30 and variation number 3 +## analog input 32 bit without flag +event dnp3_analog_input_32woFlag%(c: connection, is_orig: bool, value: count%); + +## Generated for the object with the group number 30 and variation number 4 +## analog input 16 bit without flag +event dnp3_analog_input_16woFlag%(c: connection, is_orig: bool, value: count%); + +## Generated for the object with the group number 30 and variation number 5 +## analog input single precision, float point with flag +event dnp3_analog_input_SPwFlag%(c: connection, is_orig: bool, flag: count, value: count%); + +## Generated for the object with the group number 30 and variation number 6 +## analog input double precision, float point with flag +event dnp3_analog_input_DPwFlag%(c: connection, is_orig: bool, flag: count, value_low: count, value_high: count%); + +## Generated for the object with the group number 31 and variation number 1 +## frozen analog input 32 bit with flag +event dnp3_frozen_analog_input_32wFlag%(c: connection, is_orig: bool, flag: count, frozen_value: count%); + +## Generated for the object with the group number 31 and variation number 2 +## frozen analog input 16 bit with flag +event dnp3_frozen_analog_input_16wFlag%(c: connection, is_orig: bool, flag: count, frozen_value: count%); + +## Generated for the object with the group number 31 and variation number 3 +## frozen analog input 32 bit with time-of-freeze +event dnp3_frozen_analog_input_32wTime%(c: connection, is_orig: bool, flag: count, frozen_value: count, time48: string%); + +## Generated for the object with the group number 31 and variation number 4 +## frozen analog input 16 bit with time-of-freeze +event dnp3_frozen_analog_input_16wTime%(c: connection, is_orig: bool, flag: count, frozen_value: count, time48: string%); + +## Generated for the object with the group number 31 and variation number 5 +## frozen analog input 32 bit without flag +event dnp3_frozen_analog_input_32woFlag%(c: connection, is_orig: bool, frozen_value: count%); + +## Generated for the object with the group number 31 and variation number 6 +## frozen analog input 16 bit without flag +event dnp3_frozen_analog_input_16woFlag%(c: connection, is_orig: bool, frozen_value: count%); + +## Generated for the object with the group number 31 and variation number 7 +## frozen analog input single-precision, float point with flag +event dnp3_frozen_analog_input_SPwFlag%(c: connection, is_orig: bool, flag: count, frozen_value: count%); + +## Generated for the object with the group number 31 and variation number 8 +## frozen analog input double-precision, float point with flag +event dnp3_frozen_analog_input_DPwFlag%(c: connection, is_orig: bool, flag: count, frozen_value_low: count, frozen_value_high: count%); + +## Generated for the object with the group number 32 and variation number 1 +## analog input event 32 bit without time +event dnp3_analog_input_event_32woTime%(c: connection, is_orig: bool, flag: count, value: count%); + +## Generated for the object with the group number 32 and variation number 2 +## analog input event 16 bit without time +event dnp3_analog_input_event_16woTime%(c: connection, is_orig: bool, flag: count, value: count%); + +## Generated for the object with the group number 32 and variation number 3 +## analog input event 32 bit with time +event dnp3_analog_input_event_32wTime%(c: connection, is_orig: bool, flag: count, value: count, time48: string%); + +## Generated for the object with the group number 32 and variation number 4 +## analog input event 16 bit with time +event dnp3_analog_input_event_16wTime%(c: connection, is_orig: bool, flag: count, value: count, time48: string%); + +## Generated for the object with the group number 32 and variation number 5 +## analog input event single-precision float point without time +event dnp3_analog_input_event_SPwoTime%(c: connection, is_orig: bool, flag: count, value: count%); + +## Generated for the object with the group number 32 and variation number 6 +## analog input event double-precision float point without time +event dnp3_analog_input_event_DPwoTime%(c: connection, is_orig: bool, flag: count, value_low: count, value_high: count%); + +## Generated for the object with the group number 32 and variation number 7 +## analog input event single-precision float point with time +event dnp3_analog_input_event_SPwTime%(c: connection, is_orig: bool, flag: count, value: count, time48: string%); + +## Generated for the object with the group number 32 and variation number 8 +## analog input event double-precisiion float point with time +event dnp3_analog_input_event_DPwTime%(c: connection, is_orig: bool, flag: count, value_low: count, value_high: count, time48: string%); + +## Generated for the object with the group number 33 and variation number 1 +## frozen analog input event 32 bit without time +event dnp3_frozen_analog_input_event_32woTime%(c: connection, is_orig: bool, flag: count, frozen_value: count%); + +## Generated for the object with the group number 33 and variation number 2 +## frozen analog input event 16 bit without time +event dnp3_frozen_analog_input_event_16woTime%(c: connection, is_orig: bool, flag: count, frozen_value: count%); + +## Generated for the object with the group number 33 and variation number 3 +## frozen analog input event 32 bit with time +event dnp3_frozen_analog_input_event_32wTime%(c: connection, is_orig: bool, flag: count, frozen_value: count, time48: string%); + +## Generated for the object with the group number 33 and variation number 4 +## frozen analog input event 16 bit with time +event dnp3_frozen_analog_input_event_16wTime%(c: connection, is_orig: bool, flag: count, frozen_value: count, time48: string%); + +## Generated for the object with the group number 33 and variation number 5 +## frozen analog input event single-precision float point without time +event dnp3_frozen_analog_input_event_SPwoTime%(c: connection, is_orig: bool, flag: count, frozen_value: count%); + +## Generated for the object with the group number 33 and variation number 6 +## frozen analog input event double-precision float point without time +event dnp3_frozen_analog_input_event_DPwoTime%(c: connection, is_orig: bool, flag: count, frozen_value_low: count, frozen_value_high: count%); + +## Generated for the object with the group number 33 and variation number 7 +## frozen analog input event single-precision float point with time +event dnp3_frozen_analog_input_event_SPwTime%(c: connection, is_orig: bool, flag: count, frozen_value: count, time48: string%); + +## Generated for the object with the group number 34 and variation number 8 +## frozen analog input event double-precision float point with time +event dnp3_frozen_analog_input_event_DPwTime%(c: connection, is_orig: bool, flag: count, frozen_value_low: count, frozen_value_high: count, time48: string%); + +## g70 +event dnp3_file_transport%(c: connection, is_orig: bool, file_handle: count, block_num: count, file_data: string%); + +## Generated for the "Debug_Byte" for the binpac analyzer +## This event handler is left for the debug usage. +## For example, in the binpac analyzer, a unknown "case" generated +## this event; the user can base on the debug the byte string to check +## what cause the malformed network packets +event dnp3_debug_byte%(c: connection, is_orig: bool, debug: string%); diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/coverage b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/coverage new file mode 100644 index 0000000000..62a0937c09 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/coverage @@ -0,0 +1 @@ +6 of 52 events triggered by trace diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/dnp3.log b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/dnp3.log new file mode 100644 index 0000000000..2d702295dc --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/dnp3.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dnp3 +#open 2013-08-02-00-08-47 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p fc_request fc_reply iin +#types time string addr port addr port string string count +1324503054.884183 UWkUyAuUGXf 130.126.142.250 49413 130.126.140.229 20000 DELAY_MEASURE RESPONSE 0 +#close 2013-08-02-00-08-47 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/output b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/output new file mode 100644 index 0000000000..5bd7d932bc --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_del_measure/output @@ -0,0 +1,7 @@ +dnp3_header_block, T, 25605, 8, 196, 2, 3 +dnp3_application_request_header, T, 23 +dnp3_header_block, F, 25605, 16, 68, 3, 2 +dnp3_application_response_header, F, 129, 0 +dnp3_object_header, F, 13314, 7, 1, 1, 0 +dnp3_object_prefix, F, 0 +dnp3_response_data_object, F, 255 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/coverage b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/coverage new file mode 100644 index 0000000000..8535338ace --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/coverage @@ -0,0 +1 @@ +4 of 52 events triggered by trace diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/dnp3.log b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/dnp3.log new file mode 100644 index 0000000000..58127c313d --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/dnp3.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dnp3 +#open 2013-08-02-00-08-48 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p fc_request fc_reply iin +#types time string addr port addr port string string count +1324916729.150101 UWkUyAuUGXf 130.126.142.250 50059 130.126.140.229 20000 ENABLE_UNSOLICITED RESPONSE 0 +#close 2013-08-02-00-08-48 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/output b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/output new file mode 100644 index 0000000000..16491bb3a5 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_en_spon/output @@ -0,0 +1,7 @@ +dnp3_header_block, T, 25605, 17, 196, 2, 3 +dnp3_application_request_header, T, 20 +dnp3_object_header, T, 15362, 6, 0, 65535, 65535 +dnp3_object_header, T, 15363, 6, 0, 65535, 65535 +dnp3_object_header, T, 15364, 6, 0, 65535, 65535 +dnp3_header_block, F, 25605, 10, 68, 3, 2 +dnp3_application_response_header, F, 129, 0 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/coverage b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/coverage new file mode 100644 index 0000000000..62a0937c09 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/coverage @@ -0,0 +1 @@ +6 of 52 events triggered by trace diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/dnp3.log b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/dnp3.log new file mode 100644 index 0000000000..c60080fee0 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/dnp3.log @@ -0,0 +1,10 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dnp3 +#open 2013-08-02-00-08-48 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p fc_request fc_reply iin +#types time string addr port addr port string string count +1325044377.992570 UWkUyAuUGXf 130.126.142.250 50301 130.126.140.229 20000 DELETE_FILE RESPONSE 0 +#close 2013-08-02-00-08-48 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/output b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/output new file mode 100644 index 0000000000..37ccbc5bc9 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_del/output @@ -0,0 +1,9 @@ +dnp3_header_block, T, 25605, 99, 196, 4, 3 +dnp3_application_request_header, T, 27 +dnp3_object_header, T, 17923, 91, 1, 1, 0 +dnp3_object_prefix, T, 85 +dnp3_header_block, F, 25605, 29, 68, 3, 4 +dnp3_application_response_header, F, 129, 0 +dnp3_object_header, F, 17924, 91, 1, 1, 0 +dnp3_object_prefix, F, 13 +dnp3_response_data_object, F, 255 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/coverage b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/coverage new file mode 100644 index 0000000000..c71c9033a1 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/coverage @@ -0,0 +1 @@ +9 of 52 events triggered by trace diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/dnp3.log b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/dnp3.log new file mode 100644 index 0000000000..80cfa7caec --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/dnp3.log @@ -0,0 +1,14 @@ +#separator \x09 +#set_separator , +#empty_field (empty) +#unset_field - +#path dnp3 +#open 2013-08-02-00-08-49 +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p fc_request fc_reply iin +#types time string addr port addr port string string count +1325036012.621691 UWkUyAuUGXf 130.126.142.250 50276 130.126.140.229 20000 OPEN_FILE RESPONSE 4096 +1325036016.729050 UWkUyAuUGXf 130.126.142.250 50276 130.126.140.229 20000 READ RESPONSE 4096 +1325036019.765502 UWkUyAuUGXf 130.126.142.250 50276 130.126.140.229 20000 WRITE RESPONSE 0 +1325036022.292689 UWkUyAuUGXf 130.126.142.250 50276 130.126.140.229 20000 WRITE RESPONSE 0 +1325036024.820857 UWkUyAuUGXf 130.126.142.250 50276 130.126.140.229 20000 CLOSE_FILE RESPONSE 0 +#close 2013-08-02-00-08-49 diff --git a/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/output b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/output new file mode 100644 index 0000000000..1a4971a9e3 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.dnp3.dnp3_file_read/output @@ -0,0 +1,45 @@ +dnp3_header_block, T, 25605, 50, 196, 4, 3 +dnp3_application_request_header, T, 25 +dnp3_object_header, T, 17923, 91, 1, 1, 0 +dnp3_object_prefix, T, 36 +dnp3_header_block, F, 25605, 29, 68, 3, 4 +dnp3_application_response_header, F, 129, 4096 +dnp3_object_header, F, 17924, 91, 1, 1, 0 +dnp3_object_prefix, F, 13 +dnp3_response_data_object, F, 255 +dnp3_header_block, T, 25605, 22, 196, 4, 3 +dnp3_application_request_header, T, 1 +dnp3_object_header, T, 17925, 91, 1, 1, 0 +dnp3_object_prefix, T, 8 +dnp3_file_transport, T, 305419896, 0 + ^J +dnp3_header_block, F, 25605, 255, 68, 3, 4 +dnp3_application_response_header, F, 129, 4096 +dnp3_object_header, F, 17925, 91, 1, 1, 0 +dnp3_object_prefix, F, 838 +dnp3_file_transport, F, 305419896, 2147483648 +0000 ef bb bf 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e .......^J0150 0d 0a 20 20 3c 21 2d 2d 44 6f 63 75 6d 65 6e 74 ..