/* 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 2014, 2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) * Copyright 2014 (c) Florian Palm * Copyright 2017 (c) Stefan Profanter, fortiss GmbH */ #include #include #include "ua_util_internal.h" #include "base64.h" size_t UA_readNumberWithBase(const UA_Byte *buf, size_t buflen, UA_UInt32 *number, UA_Byte base) { UA_assert(buf); UA_assert(number); u32 n = 0; size_t progress = 0; /* read numbers until the end or a non-number character appears */ while(progress < buflen) { u8 c = buf[progress]; if(c >= '0' && c <= '9' && c <= '0' + (base-1)) n = (n * base) + c - '0'; else if(base > 9 && c >= 'a' && c <= 'z' && c <= 'a' + (base-11)) n = (n * base) + c-'a' + 10; else if(base > 9 && c >= 'A' && c <= 'Z' && c <= 'A' + (base-11)) n = (n * base) + c-'A' + 10; else break; ++progress; } *number = n; return progress; } size_t UA_readNumber(const UA_Byte *buf, size_t buflen, UA_UInt32 *number) { return UA_readNumberWithBase(buf, buflen, number, 10); } UA_StatusCode UA_parseEndpointUrl(const UA_String *endpointUrl, UA_String *outHostname, u16 *outPort, UA_String *outPath) { UA_Boolean ipv6 = false; /* Url must begin with "opc.tcp://" or opc.udp:// (if pubsub enabled) */ if(endpointUrl->length < 11) { return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; } if (strncmp((char*)endpointUrl->data, "opc.tcp://", 10) != 0) { #ifdef UA_ENABLE_PUBSUB if (strncmp((char*)endpointUrl->data, "opc.udp://", 10) != 0 && strncmp((char*)endpointUrl->data, "opc.mqtt://", 11) != 0) { return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; } #else return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; #endif } /* Where does the hostname end? */ size_t curr = 10; if(endpointUrl->data[curr] == '[') { /* IPv6: opc.tcp://[2001:0db8:85a3::8a2e:0370:7334]:1234/path */ for(; curr < endpointUrl->length; ++curr) { if(endpointUrl->data[curr] == ']') break; } if(curr == endpointUrl->length) return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; curr++; ipv6 = true; } else { /* IPv4 or hostname: opc.tcp://something.something:1234/path */ for(; curr < endpointUrl->length; ++curr) { if(endpointUrl->data[curr] == ':' || endpointUrl->data[curr] == '/') break; } } /* Set the hostname */ if(ipv6) { /* Skip the ipv6 '[]' container for getaddrinfo() later */ outHostname->data = &endpointUrl->data[11]; outHostname->length = curr - 12; } else { outHostname->data = &endpointUrl->data[10]; outHostname->length = curr - 10; } /* Empty string? */ if(outHostname->length == 0) outHostname->data = NULL; if(curr == endpointUrl->length) return UA_STATUSCODE_GOOD; /* Set the port */ if(endpointUrl->data[curr] == ':') { if(++curr == endpointUrl->length) return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; u32 largeNum; size_t progress = UA_readNumber(&endpointUrl->data[curr], endpointUrl->length - curr, &largeNum); if(progress == 0 || largeNum > 65535) return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; /* Test if the end of a valid port was reached */ curr += progress; if(curr == endpointUrl->length || endpointUrl->data[curr] == '/') *outPort = (u16)largeNum; if(curr == endpointUrl->length) return UA_STATUSCODE_GOOD; } /* Set the path */ UA_assert(curr < endpointUrl->length); if(endpointUrl->data[curr] != '/') return UA_STATUSCODE_BADTCPENDPOINTURLINVALID; if(++curr == endpointUrl->length) return UA_STATUSCODE_GOOD; outPath->data = &endpointUrl->data[curr]; outPath->length = endpointUrl->length - curr; /* Remove trailing slash from the path */ if(endpointUrl->data[endpointUrl->length - 1] == '/') outPath->length--; /* Empty string? */ if(outPath->length == 0) outPath->data = NULL; return UA_STATUSCODE_GOOD; } UA_StatusCode UA_parseEndpointUrlEthernet(const UA_String *endpointUrl, UA_String *target, UA_UInt16 *vid, UA_Byte *pcp) { /* Url must begin with "opc.eth://" */ if(endpointUrl->length < 11) { return UA_STATUSCODE_BADINTERNALERROR; } if(strncmp((char*) endpointUrl->data, "opc.eth://", 10) != 0) { return UA_STATUSCODE_BADINTERNALERROR; } /* Where does the host address end? */ size_t curr = 10; for(; curr < endpointUrl->length; ++curr) { if(endpointUrl->data[curr] == ':') { break; } } /* set host address */ target->data = &endpointUrl->data[10]; target->length = curr - 10; if(curr == endpointUrl->length) { return UA_STATUSCODE_GOOD; } /* Set VLAN */ u32 value = 0; curr++; /* skip ':' */ size_t progress = UA_readNumber(&endpointUrl->data[curr], endpointUrl->length - curr, &value); if(progress == 0 || value > 4096) { return UA_STATUSCODE_BADINTERNALERROR; } curr += progress; if(curr == endpointUrl->length || endpointUrl->data[curr] == '.') { *vid = (UA_UInt16) value; } if(curr == endpointUrl->length) { return UA_STATUSCODE_GOOD; } /* Set priority */ if(endpointUrl->data[curr] != '.') { return UA_STATUSCODE_BADINTERNALERROR; } curr++; /* skip '.' */ progress = UA_readNumber(&endpointUrl->data[curr], endpointUrl->length - curr, &value); if(progress == 0 || value > 7) { return UA_STATUSCODE_BADINTERNALERROR; } curr += progress; if(curr != endpointUrl->length) { return UA_STATUSCODE_BADINTERNALERROR; } *pcp = (UA_Byte) value; return UA_STATUSCODE_GOOD; } UA_StatusCode UA_ByteString_toBase64(const UA_ByteString *byteString, UA_String *str) { UA_String_init(str); if(!byteString || !byteString->data) return UA_STATUSCODE_GOOD; str->data = (UA_Byte*) UA_base64(byteString->data, byteString->length, &str->length); if(!str->data) return UA_STATUSCODE_BADOUTOFMEMORY; return UA_STATUSCODE_GOOD; } UA_StatusCode UA_EXPORT UA_ByteString_fromBase64(UA_ByteString *bs, const UA_String *input) { UA_ByteString_init(bs); if(input->length == 0) return UA_STATUSCODE_GOOD; bs->data = UA_unbase64((const unsigned char*)input->data, input->length, &bs->length); /* TODO: Differentiate between encoding and memory errors */ if(!bs->data) return UA_STATUSCODE_BADINTERNALERROR; return UA_STATUSCODE_GOOD; } /* Key Value Map */ UA_StatusCode UA_KeyValueMap_setQualified(UA_KeyValuePair **map, size_t *mapSize, const UA_QualifiedName *key, const UA_Variant *value) { /* Parameter exists already */ const UA_Variant *v = UA_KeyValueMap_getQualified(*map, *mapSize, key); if(v) { UA_Variant copyV; UA_StatusCode res = UA_Variant_copy(v, ©V); if(res != UA_STATUSCODE_GOOD) return res; UA_Variant *target = (UA_Variant*)(uintptr_t)v; UA_Variant_clear(target); *target = copyV; return UA_STATUSCODE_GOOD; } /* Append to the array */ UA_KeyValuePair pair; pair.key = *key; pair.value = *value; return UA_Array_appendCopy((void**)map, mapSize, &pair, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); } UA_StatusCode UA_KeyValueMap_set(UA_KeyValuePair **map, size_t *mapSize, const char *key, const UA_Variant *value) { UA_QualifiedName qnKey; qnKey.namespaceIndex = 0; qnKey.name = UA_STRING((char*)(uintptr_t)key); return UA_KeyValueMap_setQualified(map, mapSize, &qnKey, value); } const UA_Variant * UA_KeyValueMap_getQualified(UA_KeyValuePair *map, size_t mapSize, const UA_QualifiedName *key) { for(size_t i = 0; i < mapSize; i++) { if(map[i].key.namespaceIndex == key->namespaceIndex && UA_String_equal(&map[i].key.name, &key->name)) return &map[i].value; } return NULL; } const UA_Variant * UA_KeyValueMap_get(UA_KeyValuePair *map, size_t mapSize, const char *key) { UA_QualifiedName qnKey; qnKey.namespaceIndex = 0; qnKey.name = UA_STRING((char*)(uintptr_t)key); return UA_KeyValueMap_getQualified(map, mapSize, &qnKey); } /* Returns NULL if the parameter is not defined or not of the right datatype */ const UA_Variant * UA_KeyValueMap_getScalar(UA_KeyValuePair *map, size_t mapSize, const char *key, const UA_DataType *type) { const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key); if(!v || !UA_Variant_hasScalarType(v, type)) return NULL; return v; } const UA_Variant * UA_KeyValueMap_getArray(UA_KeyValuePair *map, size_t mapSize, const char *key, const UA_DataType *type) { const UA_Variant *v = UA_KeyValueMap_get(map, mapSize, key); if(!v || !UA_Variant_hasArrayType(v, type)) return NULL; return v; } void UA_KeyValueMap_deleteQualified(UA_KeyValuePair **map, size_t *mapSize, const UA_QualifiedName *key) { UA_KeyValuePair *m = *map; size_t s = *mapSize; for(size_t i = 0; i < s; i++) { if(m[i].key.namespaceIndex != key->namespaceIndex || !UA_String_equal(&m[i].key.name, &key->name)) continue; /* Clean the pair */ UA_KeyValuePair_clear(&m[i]); /* Move the last pair to fill the empty slot */ if(s > 1 && i < s - 1) { m[i] = m[s-1]; UA_KeyValuePair_init(&m[s-1]); } UA_StatusCode res = UA_Array_resize((void**)map, mapSize, *mapSize-1, &UA_TYPES[UA_TYPES_KEYVALUEPAIR]); (void)res; *mapSize = s - 1; /* In case resize fails, keep the longer original * array around. Resize never fails when reducing * the size to zero. Reduce the size integer in * any case. */ return; } } void UA_KeyValueMap_delete(UA_KeyValuePair **map, size_t *mapSize, const char *key) { UA_QualifiedName qnKey; qnKey.namespaceIndex = 0; qnKey.name = UA_STRING((char*)(uintptr_t)key); UA_KeyValueMap_deleteQualified(map, mapSize, &qnKey); }