mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
Add ua2json tool
This commit is contained in:
parent
b864e0e03c
commit
f9d8afdbdd
@ -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
tools/ua2json/CMakeLists.txt
Normal file
9
tools/ua2json/CMakeLists.txt
Normal file
@ -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
tools/ua2json/README.md
Normal file
51
tools/ua2json/README.md
Normal file
@ -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
tools/ua2json/examples/datavalue.bin
Normal file
11
tools/ua2json/examples/datavalue.bin
Normal file
@ -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
tools/ua2json/examples/datavalue.json
Normal file
11
tools/ua2json/examples/datavalue.json
Normal file
@ -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
|
||||
}
|
||||
}
|
BIN
tools/ua2json/examples/readrequest.bin
Normal file
BIN
tools/ua2json/examples/readrequest.bin
Normal file
Binary file not shown.
27
tools/ua2json/examples/readrequest.json
Normal file
27
tools/ua2json/examples/readrequest.json
Normal file
@ -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}
|
||||
}
|
||||
]
|
||||
}
|
BIN
tools/ua2json/examples/variant.bin
Normal file
BIN
tools/ua2json/examples/variant.bin
Normal file
Binary file not shown.
5
tools/ua2json/examples/variant.json
Normal file
5
tools/ua2json/examples/variant.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Type": 3,
|
||||
"Body": [1,2,3,4,5,6,7,8],
|
||||
"Dimension": [2, 4]
|
||||
}
|
258
tools/ua2json/ua2json.c
Normal file
258
tools/ua2json/ua2json.c
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user