浏览代码

Add ua2json tool

Julius Pfrommer 5 年之前
父节点
当前提交
f9d8afdbdd

+ 7 - 0
CMakeLists.txt

@@ -271,6 +271,7 @@ mark_as_advanced(UA_DEBUG_DUMP_PKGS)
 
 # Build Targets
 option(UA_BUILD_EXAMPLES "Build example servers and clients" OFF)
+option(UA_BUILD_TOOLS "Build OPC UA shell tools" OFF)
 option(UA_BUILD_UNIT_TESTS "Build the unit tests" OFF)
 option(UA_BUILD_FUZZING "Build the fuzzing executables" OFF)
 mark_as_advanced(UA_BUILD_FUZZING)
@@ -952,6 +953,12 @@ if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ OR UA_BUILD_FUZZING_CORPUS)
     add_subdirectory(tests/fuzz)
 endif()
 
+if(UA_BUILD_TOOLS)
+    if(UA_ENABLE_JSON_ENCODING)
+        add_subdirectory(tools/ua2json)
+    endif()
+endif()
+
 ############################
 # Linting run (clang-tidy) #
 ############################

+ 9 - 0
tools/ua2json/CMakeLists.txt

@@ -0,0 +1,9 @@
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+include_directories(${PROJECT_SOURCE_DIR}/src)
+
+add_executable(ua2json ua2json.c)
+target_link_libraries(ua2json open62541 ${open62541_LIBRARIES})
+assign_source_group(ua2json)
+add_dependencies(ua2json open62541-object)
+set_target_properties(ua2json PROPERTIES FOLDER "open62541/tools/ua2json")
+set_target_properties(ua2json PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

+ 51 - 0
tools/ua2json/README.md

@@ -0,0 +1,51 @@
+# ua2json
+
+ua2json is a command line tool to translate between OPC UA JSON encoding and OPC
+UA binary encoding. ua2json follows the tradition of shell-based unix-tools and
+provides translation between the binary and JSON encoding formats of OPC UA
+messages as a reusable building block. Input can be piped through ua2json from
+stdin to stdout. File input and output is possible as well.
+
+At the core of the OPC UA protocol lies a type system in which the protocol
+messages are defined. The built-in data types include integers, strings, and so
+on. From these, more complex structures are assembled. For example the
+`ReadRequest` and `ReadResponse` message pair.
+
+## Usage
+
+```
+Usage: ua2json [encode|decode] [-t dataType] [-o outputFile] [inputFile]
+- encode/decode: Translate UA binary input to UA JSON / Translate UA JSON input to UA binary (required)
+- dataType: UA DataType of the input (default: Variant)
+- outputFile: Output target (default: write to stdout)
+- inputFile: Input source (default: read from stdin)
+```
+
+## Examples
+
+Take the following JSON encoding of a Variant data type instance with a 2x4
+matrix of numerical values. Variants can encapsulate scalars and
+multi-dimensional arrays of any data type.
+
+```json
+{
+    "Type": 3,
+    "Body": [1,2,3,4,5,6,7,8],
+    "Dimension": [2, 4]
+}
+```
+
+Piping this JSON-encoding through ua2json (and the xxd tool to print the output
+as hex) yields the binary OPC UA encoding.
+
+```bash
+$ cat variant.json | ua2json decode -t Variant | xxd
+00000000: c308 0000 0001 0203 0405 0607 0802 0000  ................
+00000010: 0002 0000 0004 0000 00                   .........
+```
+
+The inverse transformation returns the original JSON (modulo pretty-printing).
+
+```bash
+$ cat variant.bin | ua2json encode -t Variant
+```

+ 11 - 0
tools/ua2json/examples/datavalue.bin

@@ -0,0 +1,11 @@
+{
+    "ServerPicoseconds": 0,
+    "ServerTimestamp": "1970-01-15T06:56:07Z",
+    "SourcePicoseconds": 0,
+    "SourceTimestamp": "1970-01-15T06:56:07Z",
+    "Status": 2153250816,
+    "Value": {
+        "Type": 1,
+        "Body": true
+    }
+}

+ 11 - 0
tools/ua2json/examples/datavalue.json

@@ -0,0 +1,11 @@
+{
+    "ServerPicoseconds": 0,
+    "ServerTimestamp": "1970-01-15T06:56:07Z",
+    "SourcePicoseconds": 0,
+    "SourceTimestamp": "1970-01-15T06:56:07Z",
+    "Status": 2153250816,
+    "Value": {
+        "Type": 1,
+        "Body": true
+    }
+}

二进制
tools/ua2json/examples/readrequest.bin


+ 27 - 0
tools/ua2json/examples/readrequest.json

@@ -0,0 +1,27 @@
+{
+    "RequestHeader": {
+        "AdditionalHeader": null,
+        "AuditEntryId": null,
+        "AuthenticationToken": {
+            "Id": "D358AA6F-D5C7-4AA9-0CB0-EB0E1491E91E",
+            "IdType": 2,
+            "Namespace": 1
+        },
+        "RequestHandle": 1000059,
+        "ReturnDiagnostics": 0,
+        "TimeoutHint": 5000,
+        "Timestamp": "2019-01-09T21:54:40.179Z"
+    },
+    "TimestampsToReturn": 1,
+    "MaxAge": 0,
+    "NodesToRead": [
+        {
+            "AttributeId": 13,
+            "DataEncoding": {
+                "Name": null
+            },
+            "IndexRange": null,
+            "NodeId": {"Id": 2259}
+        }
+    ]
+}

二进制
tools/ua2json/examples/variant.bin


+ 5 - 0
tools/ua2json/examples/variant.json

@@ -0,0 +1,5 @@
+{
+    "Type": 3,
+    "Body": [1,2,3,4,5,6,7,8],
+    "Dimension": [2, 4]
+}

+ 258 - 0
tools/ua2json/ua2json.c

@@ -0,0 +1,258 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. 
+ *
+ *    Copyright 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
+ */
+
+/* Enable POSIX features */
+#if !defined(_XOPEN_SOURCE)
+# define _XOPEN_SOURCE 600
+#endif
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+/* On older systems we need to define _BSD_SOURCE.
+ * _DEFAULT_SOURCE is an alias for that. */
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+
+#include <stdio.h>
+#include <ua_types.h>
+
+/* Internal headers */
+#include "ua_types_generated.h"
+#include "ua_types_generated_handling.h"
+#include "ua_types_encoding_binary.h"
+#include "ua_types_encoding_json.h"
+
+static UA_StatusCode
+encode(const UA_ByteString *buf, UA_ByteString *out,
+       const UA_DataType *type) {
+    void *data = malloc(type->memSize);
+    if(!data)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    size_t offset = 0;
+    UA_StatusCode retval = UA_decodeBinary(buf, &offset, data, type, NULL);
+    if(retval != UA_STATUSCODE_GOOD) {
+        free(data);
+        return retval;
+    }
+    if(offset != buf->length) {
+        UA_delete(data, type);
+        fprintf(stderr, "Input buffer not completely read\n");
+        return UA_STATUSCODE_BADINTERNALERROR;
+    }
+
+    size_t jsonLength = UA_calcSizeJson(data, type, NULL, 0, NULL, 0, true);
+    retval = UA_ByteString_allocBuffer(out, jsonLength);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_delete(data, type);
+        return retval;
+    }
+
+    uint8_t *bufPos = &out->data[0];
+    const uint8_t *bufEnd = &out->data[out->length];
+    retval = UA_encodeJson(data, type, &bufPos, &bufEnd, NULL, 0, NULL, 0, true);
+    UA_delete(data, type);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_ByteString_deleteMembers(out);
+        return retval;
+    }
+
+    out->length = (size_t)((uintptr_t)bufPos - (uintptr_t)out->data);
+    return UA_STATUSCODE_GOOD;
+}
+
+static UA_StatusCode
+decode(const UA_ByteString *buf, UA_ByteString *out,
+       const UA_DataType *type) {
+    void *data = malloc(type->memSize);
+    if(!data)
+        return UA_STATUSCODE_BADOUTOFMEMORY;
+
+    UA_StatusCode retval = UA_decodeJson(buf, data, type);
+    if(retval != UA_STATUSCODE_GOOD) {
+        free(data);
+        return retval;
+    }
+
+    size_t binLength = UA_calcSizeBinary(data, type);
+    retval = UA_ByteString_allocBuffer(out, binLength);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_delete(data, type);
+        return retval;
+    }
+
+    uint8_t *bufPos = &out->data[0];
+    const uint8_t *bufEnd = &out->data[out->length];
+    retval = UA_encodeBinary(data, type, &bufPos, &bufEnd, NULL, NULL);
+    UA_delete(data, type);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_ByteString_deleteMembers(out);
+        return retval;
+    }
+
+    out->length = (size_t)((uintptr_t)bufPos - (uintptr_t)out->data);
+    return UA_STATUSCODE_GOOD;
+}
+
+static void
+usage(void) {
+    printf("Usage: ua2json [encode|decode] [-t dataType] [-o outputFile] [inputFile]\n"
+           "- encode/decode: Translate UA binary input to UA JSON / "
+             "Translate UA JSON input to UA binary (required)\n"
+           "- dataType: UA DataType of the input (default: Variant)\n"
+           "- outputFile: Output target (default: write to stdout)\n"
+           "- inputFile: Input source (default: read from stdin)\n");
+}
+
+int main(int argc, char **argv) {
+    UA_Boolean encode_option = true;
+    const char *datatype_option = "Variant";
+    const char *input_option = NULL;
+    const char *output_option = NULL;
+    UA_ByteString outbuf = UA_BYTESTRING_NULL;
+    UA_ByteString buf = UA_BYTESTRING_NULL;
+    FILE *in = stdin;
+    FILE *out = stdout;
+    int retcode = -1;
+
+    /* Read the command line options */
+    if(argc < 2) {
+        usage();
+        return 0;
+    }
+
+    if(strcmp(argv[1], "encode") == 0) {
+        encode_option = true;
+    } else if(strcmp(argv[1], "decode") == 0) {
+        encode_option = false;
+    } else {
+        fprintf(stderr, "Error: The first argument must be \"encode\" or \"decode\"\n");
+        return -1;
+    }
+        
+    for(int argpos = 2; argpos < argc; argpos++) {
+        if(strcmp(argv[argpos], "--help") == 0) {
+            usage();
+            return 0;
+        }
+
+        if(strcmp(argv[argpos], "-t") == 0) {
+            if(argpos + 1 == argc) {
+                usage();
+                return -1;
+            }
+            argpos++;
+            datatype_option = argv[argpos];
+            continue;
+        }
+
+        if(strcmp(argv[argpos], "-o") == 0) {
+            if(argpos + 1 == argc) {
+                usage();
+                return -1;
+            }
+            argpos++;
+            output_option = argv[argpos];
+            continue;
+        }
+
+        if(argpos + 1 == argc) {
+            input_option = argv[argpos];
+            continue;
+        }
+
+        usage();
+        return -1;
+    }
+
+    /* Find the data type */
+    const UA_DataType *type = NULL;
+    for(size_t i = 0; i < UA_TYPES_COUNT; ++i) {
+        if(strcmp(datatype_option, UA_TYPES[i].typeName) == 0) {
+            type = &UA_TYPES[i];
+            break;
+        }
+    }
+    if(!type) {
+        fprintf(stderr, "Error: Datatype not found\n");
+        return -1;
+    }
+
+    /* Open files */
+    if(input_option) {
+        in = fopen(input_option, "rb");
+        if(!in) {
+            fprintf(stderr, "Could not open input file %s\n", input_option);
+            goto cleanup;
+        }
+    }
+    if(output_option) {
+        out = fopen(output_option, "wb");
+        if(!out) {
+            fprintf(stderr, "Could not open output file %s\n", output_option);
+            goto cleanup;
+        }
+    }
+
+    /* Read input until EOF */
+    size_t pos = 0;
+    size_t length = 128;
+    while(true) {
+        if(pos >= buf.length) {
+            length = length * 8;
+            UA_Byte *r = (UA_Byte*)realloc(buf.data, length);
+            if(!r) {
+                fprintf(stderr, "Out of memory\n");
+                goto cleanup;
+            }
+            buf.length = length;
+            buf.data = r;
+        }
+
+        ssize_t c = read(fileno(in), &buf.data[pos], length - pos);
+        if(c == 0)
+            break;
+        if(c < 0) {
+            fprintf(stderr, "Reading from input failed\n");
+            goto cleanup;
+        }
+
+        pos += (size_t)c;
+    }
+
+    if(pos == 0) {
+        fprintf(stderr, "No input\n");
+        goto cleanup;
+    }
+    buf.length = pos;
+
+    /* Convert */
+    UA_StatusCode result;
+    if(encode_option)
+        result = encode(&buf, &outbuf, type);
+    else
+        result = decode(&buf, &outbuf, type);
+    if(result != UA_STATUSCODE_GOOD) {
+        fprintf(stderr, "Error: Parsing failed with code %s\n",
+                UA_StatusCode_name(result));
+        goto cleanup;
+    }
+
+    /* Print the output and quit */
+    fwrite(outbuf.data, 1, outbuf.length, out);
+    retcode = 0;
+
+ cleanup:
+    UA_ByteString_deleteMembers(&buf);
+    UA_ByteString_deleteMembers(&outbuf);
+    if(in != stdin)
+        fclose(in);
+    if(out != stdout)
+        fclose(out);
+    return retcode;
+}