| /****************************************************************************** |
| * Copyright (c) 2016 Ericsson AB |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Mate Kovacs - initial implementation and initial documentation |
| * Antal Wu-Hen Chang |
| * Naum Spaseski |
| ******************************************************************************/ |
| // |
| // File: CoAP_EncDec.cc |
| // Rev: R1A |
| // Prodnr: CNL 113 829 |
| |
| #include "CoAP_Types.hh" |
| |
| namespace CoAP__Types { |
| |
| int getOptionCode(CoAP__Options option); |
| int getOptionLength(CoAP__Options option); |
| int getIntegerLength(const long long int value, int mode); |
| void encodeInteger(TTCN_Buffer &stream, const long long int option, const int length); |
| long long int decodeInteger(OCTETSTRING const &str, const int position, const int length); |
| int getBlockLengthForNum(const long long int num); |
| void encodeBlock(TTCN_Buffer &stream, const BlockOption option, const int length); |
| void decodeBlock(BlockOption& block, OCTETSTRING const &str, const int position, const int length); |
| |
| INTEGER |
| f__CoAP__dec(OCTETSTRING const &str, CoAP__Message &msg) |
| { |
| if(TTCN_Logger::log_this_event(TTCN_DEBUG)){ |
| TTCN_Logger::begin_event(TTCN_DEBUG); |
| TTCN_Logger::log_event("Decoding CoAP message: "); |
| str.log(); |
| TTCN_Logger::end_event(); |
| } |
| |
| if(str.lengthof() >= 4){ |
| const unsigned char* str_ptr = (const unsigned char*) str; |
| unsigned char chr; |
| int position = 0; |
| int token_length = 0; |
| int actual_option_code = 0; |
| int count_of_options = 0; |
| bool payload_marker = false; |
| |
| //version, message type and token length |
| chr = str_ptr[0]; |
| msg.msg().header().version() = chr >> 6; |
| msg.msg().header().msg__type() = (chr >> 4) & 3; |
| token_length = chr & 15; |
| //code |
| chr = str_ptr[1]; |
| msg.msg().header().code().class_() = (chr >> 5) & 7; |
| msg.msg().header().code().detail() = chr & 31; |
| //message ID |
| msg.msg().header().message__id() = str_ptr[2] * 256 + str_ptr[3]; |
| |
| if(str.lengthof() > 4 && msg.msg().header().code().class_() == 0 && msg.msg().header().code().detail() == 0){ |
| msg.raw__message() = str; |
| return 1; |
| } |
| |
| position = 4; |
| //token |
| if(token_length > 0){ |
| if(str.lengthof() >= position + token_length){ |
| msg.msg().token() = OCTETSTRING(token_length, &str_ptr[position]); |
| position += token_length; |
| }else{ |
| msg.raw__message() = str; |
| return 1; |
| } |
| }else{ |
| msg.msg().token() = OCTETSTRING(0, 0); |
| } |
| |
| //options |
| msg.msg().options() = OMIT_VALUE; |
| if(str.lengthof() > 4){ |
| while(!payload_marker && str.lengthof() > position){ |
| int delta = str_ptr[position] >> 4; |
| int length = str_ptr[position] & 15; |
| if(delta == 15 && length != 15){ |
| msg.raw__message() = str; |
| return 1; |
| } |
| position++; |
| if(delta == 15 && length == 15){ |
| payload_marker = true; |
| }else{ |
| //optional delta |
| if(delta == 13){ |
| delta = str_ptr[position] + 13; |
| position++; |
| }else if(delta == 14){ |
| delta = (str_ptr[position] << 8) + str_ptr[position+1] + 269; |
| position += 2; |
| } |
| //optional length |
| if(length == 13){ |
| length = str_ptr[position] + 13; |
| position++; |
| }else if(length == 14){ |
| length = (str_ptr[position] << 8) + str_ptr[position+1] + 269; |
| position += 2; |
| }else if(length == 15){ |
| msg.raw__message() = str; |
| return 1; |
| } |
| |
| //value |
| actual_option_code += delta; |
| switch(actual_option_code){ |
| case 1: |
| msg.msg().options()()[count_of_options].if__match() = OCTETSTRING(length, &str_ptr[position]); |
| break; |
| case 3: |
| msg.msg().options()()[count_of_options].uri__host().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 4: |
| msg.msg().options()()[count_of_options].etag() = OCTETSTRING(length, &str_ptr[position]); |
| break; |
| case 5: |
| msg.msg().options()()[count_of_options].if__none__match() = OCTETSTRING(0, 0); |
| break; |
| case 6: |
| msg.msg().options()()[count_of_options].observe().set_long_long_val(decodeInteger(str, position, length)); |
| break; |
| case 7: |
| msg.msg().options()()[count_of_options].uri__port().set_long_long_val(decodeInteger(str, position, length)); |
| break; |
| case 8: |
| msg.msg().options()()[count_of_options].location__path().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 11: |
| msg.msg().options()()[count_of_options].uri__path().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 12: |
| msg.msg().options()()[count_of_options].content__format().set_long_long_val(decodeInteger(str, position, length)); |
| break; |
| case 14: |
| msg.msg().options()()[count_of_options].max__age().set_long_long_val(decodeInteger(str, position, length)); |
| break; |
| case 15: |
| msg.msg().options()()[count_of_options].uri__query().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 17: |
| msg.msg().options()()[count_of_options].accept().set_long_long_val(decodeInteger(str, position, length)); |
| break; |
| case 20: |
| msg.msg().options()()[count_of_options].location__query().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 23: // Block2 RFC 7959 |
| decodeBlock(msg.msg().options()()[count_of_options].block2(), str, position, length); |
| break; |
| case 27: // Block1 RFC 7959 |
| decodeBlock(msg.msg().options()()[count_of_options].block1(), str, position, length); |
| break; |
| case 35: |
| msg.msg().options()()[count_of_options].proxy__uri().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 39: |
| msg.msg().options()()[count_of_options].proxy__scheme().decode_utf8(length, &str_ptr[position]); |
| break; |
| case 60: |
| msg.msg().options()()[count_of_options].size1().set_long_long_val(decodeInteger(str, position, length)); |
| break; |
| default: |
| msg.msg().options()()[count_of_options].unknown__option().option__code() = actual_option_code; |
| msg.msg().options()()[count_of_options].unknown__option().option__value() = OCTETSTRING(length, &str_ptr[position]); |
| break; |
| } |
| position += length; |
| count_of_options++; |
| } |
| } |
| } |
| |
| //payload |
| if(str.lengthof() > position){ |
| msg.msg().payload() = OCTETSTRING(str.lengthof() - position, &str_ptr[position]); |
| if(msg.msg().payload()().lengthof() == 0){ |
| msg.raw__message() = str; |
| return 1; |
| } |
| }else{ |
| if(payload_marker){ |
| msg.raw__message() = str; |
| return 1; |
| }else{ |
| msg.msg().payload() = OMIT_VALUE; |
| } |
| } |
| }else{ |
| msg.raw__message() = str; |
| return 1; |
| } |
| |
| if(TTCN_Logger::log_this_event(TTCN_DEBUG)){ |
| TTCN_Logger::begin_event(TTCN_DEBUG); |
| TTCN_Logger::log_event("Decoded CoAP message: "); |
| msg.log(); |
| TTCN_Logger::end_event(); |
| } |
| |
| return 0; |
| } |
| |
| INTEGER |
| f__CoAP__enc(CoAP__Message const &msg, OCTETSTRING &str) |
| { |
| TTCN_Buffer stream; |
| unsigned char chr = 0; |
| |
| if(TTCN_Logger::log_this_event(TTCN_DEBUG)){ |
| TTCN_Logger::begin_event(TTCN_DEBUG); |
| TTCN_Logger::log_event("Encoding CoAP message: "); |
| msg.log(); |
| TTCN_Logger::end_event(); |
| } |
| |
| switch(msg.get_selection()){ |
| case CoAP__Message::ALT_raw__message: |
| stream.put_os(msg.raw__message()); |
| stream.get_string(str); |
| return 0; |
| break; |
| case CoAP__Message::ALT_msg: |
| default: |
| break; |
| } |
| |
| //HEADER |
| //version |
| if(0 <= msg.msg().header().version() && msg.msg().header().version() <= 3){ |
| chr = msg.msg().header().version() << 6; |
| }else{ |
| return 1; |
| } |
| //message type |
| chr += msg.msg().header().msg__type() << 4; |
| //tkl (token length) |
| if(msg.msg().token().lengthof() >= 0 && msg.msg().token().lengthof() <= 8){ |
| chr += msg.msg().token().lengthof(); |
| }else{ |
| return 1; |
| } |
| stream.put_c(chr); |
| //code |
| if(msg.msg().header().code().class_() >= 0 && msg.msg().header().code().class_() <= 7){ |
| chr = msg.msg().header().code().class_() << 5; |
| }else{ |
| return 1; |
| } |
| if(msg.msg().header().code().detail() >= 0 && msg.msg().header().code().detail() <= 31){ |
| chr += msg.msg().header().code().detail(); |
| }else{ |
| return 1; |
| } |
| stream.put_c(chr); |
| //message ID |
| if(msg.msg().header().message__id() >= 0 && msg.msg().header().message__id() <= 65535){ |
| encodeInteger(stream, msg.msg().header().message__id(), 2); |
| }else{ |
| return 1; |
| } |
| |
| //TOKEN |
| stream.put_os(msg.msg().token()); |
| |
| //OPTIONS |
| if(msg.msg().options().ispresent() && msg.msg().options()().size_of() > 0){ |
| int optionOrder[msg.msg().options()().size_of()]; |
| int temp; |
| for(int i = 0; i < msg.msg().options()().size_of(); i++){ |
| optionOrder[i] = i; |
| } |
| |
| //determine the order of options |
| for(int j = 0; j < msg.msg().options()().size_of()-1; j++){ |
| int minOptionCode = getOptionCode(msg.msg().options()()[optionOrder[j]]); |
| int minOptionIndex = j; |
| for(int i = j+1; i < msg.msg().options()().size_of(); i++){ |
| if(getOptionCode(msg.msg().options()()[optionOrder[i]]) < minOptionCode){ |
| minOptionCode = getOptionCode(msg.msg().options()()[optionOrder[i]]); |
| minOptionIndex = i; |
| } |
| } |
| temp = optionOrder[minOptionIndex]; |
| optionOrder[minOptionIndex] = optionOrder[j]; |
| optionOrder[j] = temp; |
| } |
| |
| //encode options |
| int previousOptionCode = 0; |
| for(int i = 0; i < msg.msg().options()().size_of(); i++){ |
| int delta = getOptionCode(msg.msg().options()()[optionOrder[i]]) - previousOptionCode; |
| int length = getOptionLength(msg.msg().options()()[optionOrder[i]]); |
| |
| previousOptionCode += delta; |
| //first byte |
| if(delta >= 0 && delta <= 12){ |
| chr = delta << 4; |
| }else if(delta >= 13 && delta <= 268){ |
| chr = 13 << 4; |
| }else if(delta >= 269 && delta <= 65535){ |
| chr = 14 << 4; |
| }else{ |
| return 1; |
| } |
| if(length >= 0 && length <= 12){ |
| chr += length; |
| }else if(length >= 13 && length <= 268){ |
| chr += 13; |
| }else if(length >= 269 && length <= 65535){ |
| chr += 14; |
| }else{ |
| return 1; |
| } |
| stream.put_c(chr); |
| |
| //optional delta |
| if(delta >= 13 && delta <= 268){ |
| chr = delta - 13; |
| stream.put_c(chr); |
| }else if(delta >= 269 && delta <= 65535){ |
| chr = (delta - 269) >> 8; |
| stream.put_c(chr); |
| chr = (delta - 269) & 255; |
| stream.put_c(chr); |
| } |
| //optional length |
| if(length >= 13 && length <= 268){ |
| chr = length - 13; |
| stream.put_c(chr); |
| }else if(length >= 269 && length <= 65535){ |
| chr = (length - 269) >> 8; |
| stream.put_c(chr); |
| chr = (length - 269) & 255; |
| stream.put_c(chr); |
| } |
| //option value |
| if(length > 0){ |
| switch(msg.msg().options()()[optionOrder[i]].get_selection()){ |
| case CoAP__Options::ALT_if__match: |
| stream.put_os(msg.msg().options()()[optionOrder[i]].if__match()); |
| break; |
| case CoAP__Options::ALT_uri__host: |
| msg.msg().options()()[optionOrder[i]].uri__host().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_etag: |
| stream.put_os(msg.msg().options()()[optionOrder[i]].etag()); |
| break; |
| case CoAP__Options::ALT_if__none__match: |
| stream.put_os(msg.msg().options()()[optionOrder[i]].if__none__match()); |
| break; |
| case CoAP__Options::ALT_observe: |
| encodeInteger(stream, msg.msg().options()()[optionOrder[i]].observe(), length); |
| break; |
| case CoAP__Options::ALT_uri__port: |
| encodeInteger(stream, msg.msg().options()()[optionOrder[i]].uri__port(), length); |
| break; |
| case CoAP__Options::ALT_location__path: |
| msg.msg().options()()[optionOrder[i]].location__path().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_uri__path: |
| msg.msg().options()()[optionOrder[i]].uri__path().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_content__format: |
| encodeInteger(stream, msg.msg().options()()[optionOrder[i]].content__format(), length); |
| break; |
| case CoAP__Options::ALT_max__age: |
| encodeInteger(stream, msg.msg().options()()[optionOrder[i]].max__age(), length); |
| break; |
| case CoAP__Options::ALT_uri__query: |
| msg.msg().options()()[optionOrder[i]].uri__query().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_accept: |
| encodeInteger(stream, msg.msg().options()()[optionOrder[i]].accept(), length); |
| break; |
| case CoAP__Options::ALT_location__query: |
| msg.msg().options()()[optionOrder[i]].location__query().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_block1: |
| encodeBlock(stream, msg.msg().options()()[optionOrder[i]].block1(), length); |
| break; |
| case CoAP__Options::ALT_block2: |
| encodeBlock(stream, msg.msg().options()()[optionOrder[i]].block2(), length); |
| break; |
| case CoAP__Options::ALT_proxy__uri: |
| msg.msg().options()()[optionOrder[i]].proxy__uri().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_proxy__scheme: |
| msg.msg().options()()[optionOrder[i]].proxy__scheme().encode_utf8(stream, false); |
| break; |
| case CoAP__Options::ALT_size1: |
| encodeInteger(stream, msg.msg().options()()[optionOrder[i]].size1(), length); |
| break; |
| case CoAP__Options::ALT_unknown__option: |
| stream.put_os(msg.msg().options()()[optionOrder[i]].unknown__option().option__value()); |
| break; |
| case CoAP__Options::UNBOUND_VALUE: |
| default: |
| return 8; |
| break; |
| } |
| } |
| } |
| } |
| |
| //PAYLOAD |
| if(msg.msg().payload().ispresent()){ |
| stream.put_c(255); //payload marker |
| stream.put_os(msg.msg().payload()()); |
| } |
| |
| TTCN_Logger::begin_event(TTCN_DEBUG); |
| TTCN_Logger::log_event("Encoded CoAP message: "); |
| stream.log(); |
| TTCN_Logger::end_event(); |
| |
| stream.get_string(str); |
| |
| return 0; |
| } |
| |
| //helper functions |
| int getOptionCode(CoAP__Options option){ |
| switch(option.get_selection()){ |
| case CoAP__Options::ALT_if__match: |
| return 1; |
| break; |
| case CoAP__Options::ALT_uri__host: |
| return 3; |
| break; |
| case CoAP__Options::ALT_etag: |
| return 4; |
| break; |
| case CoAP__Options::ALT_if__none__match: |
| return 5; |
| break; |
| case CoAP__Options::ALT_observe: |
| return 6; |
| break; |
| case CoAP__Options::ALT_uri__port: |
| return 7; |
| break; |
| case CoAP__Options::ALT_location__path: |
| return 8; |
| break; |
| case CoAP__Options::ALT_uri__path: |
| return 11; |
| break; |
| case CoAP__Options::ALT_content__format: |
| return 12; |
| break; |
| case CoAP__Options::ALT_max__age: |
| return 14; |
| break; |
| case CoAP__Options::ALT_uri__query: |
| return 15; |
| break; |
| case CoAP__Options::ALT_accept: |
| return 17; |
| break; |
| case CoAP__Options::ALT_location__query: |
| return 20; |
| break; |
| case CoAP__Options::ALT_block1: // RFC 7959 |
| return 27; |
| break; |
| case CoAP__Options::ALT_block2: // RFC 7959 |
| return 23; |
| break; |
| case CoAP__Options::ALT_proxy__uri: |
| return 35; |
| break; |
| case CoAP__Options::ALT_proxy__scheme: |
| return 39; |
| break; |
| case CoAP__Options::ALT_size1: |
| return 60; |
| break; |
| case CoAP__Options::ALT_unknown__option: |
| return option.unknown__option().option__code(); |
| break; |
| case CoAP__Options::UNBOUND_VALUE: |
| default: |
| TTCN_error("CoAP: Error at getOptionCode!"); |
| break; |
| } |
| } |
| |
| int getOptionLength(CoAP__Options option){ |
| switch(option.get_selection()){ |
| case CoAP__Options::ALT_if__match: |
| return option.if__match().lengthof(); |
| break; |
| case CoAP__Options::ALT_uri__host: |
| return option.uri__host().lengthof(); |
| break; |
| case CoAP__Options::ALT_etag: |
| return option.etag().lengthof(); |
| break; |
| case CoAP__Options::ALT_if__none__match: |
| return option.if__none__match().lengthof(); |
| break; |
| case CoAP__Options::ALT_observe: |
| return getIntegerLength(option.observe().get_long_long_val(), 3); |
| break; |
| case CoAP__Options::ALT_uri__port: |
| return getIntegerLength(option.uri__port().get_long_long_val(), 2); |
| break; |
| case CoAP__Options::ALT_location__path: |
| return option.location__path().lengthof(); |
| break; |
| case CoAP__Options::ALT_uri__path: |
| return option.uri__path().lengthof(); |
| break; |
| case CoAP__Options::ALT_content__format: |
| return getIntegerLength(option.content__format().get_long_long_val(), 2); |
| break; |
| case CoAP__Options::ALT_max__age: |
| return getIntegerLength(option.max__age().get_long_long_val(), 4); |
| break; |
| case CoAP__Options::ALT_uri__query: |
| return option.uri__query().lengthof();; |
| break; |
| case CoAP__Options::ALT_accept: |
| return getIntegerLength(option.accept().get_long_long_val(), 2); |
| break; |
| case CoAP__Options::ALT_location__query: |
| return option.location__query().lengthof(); |
| break; |
| case CoAP__Options::ALT_block1: |
| return getBlockLengthForNum(option.block1().num().get_long_long_val()); |
| break; |
| case CoAP__Options::ALT_block2: |
| return getBlockLengthForNum(option.block2().num().get_long_long_val()); |
| break; |
| case CoAP__Options::ALT_proxy__uri: |
| return option.proxy__uri().lengthof(); |
| break; |
| case CoAP__Options::ALT_proxy__scheme: |
| return option.proxy__scheme().lengthof(); |
| break; |
| case CoAP__Options::ALT_size1: |
| return getIntegerLength(option.size1().get_long_long_val(), 4); |
| break; |
| case CoAP__Options::ALT_unknown__option: |
| return option.unknown__option().option__value().lengthof(); |
| break; |
| case CoAP__Options::UNBOUND_VALUE: |
| default: |
| TTCN_error("CoAP: Error at getOptionLength!"); |
| break; |
| } |
| } |
| |
| int getIntegerLength(const long long int value, int mode){ |
| if(value == 0){ |
| return 0; |
| }else if(value / 269 == 0){ |
| return 1; |
| }else if(value / 65805 == 0){ |
| return 2; |
| }else if(mode == 4 && value / 16777485 == 0){ |
| return 3; |
| }else if(mode == 4 && value / 4294967296 == 0){ |
| return 4; |
| }else{ |
| TTCN_error("CoAP: Error at getIntegerLength!"); |
| } |
| } |
| |
| void encodeInteger(TTCN_Buffer &stream, const long long int option, const int length){ |
| unsigned char chr; |
| |
| for(int i = length-1; i > 0; i--){ |
| chr = (option >> (8 * i)) & 255; |
| stream.put_c(chr); |
| } |
| chr = option & 255; |
| stream.put_c(chr); |
| } |
| |
| long long int decodeInteger(OCTETSTRING const &str, const int position, const int length){ |
| int value = 0; |
| |
| for(int i = 0; i < length; i++){ |
| value += (str[position+i].get_octet() << (8 * (length-i-1))); |
| } |
| return value; |
| } |
| |
| // RFC 7959 |
| int getBlockLengthForNum(const long long int num) { |
| if (num < 0 || num > 1048575) |
| TTCN_error("CoAP: Error at getBlockLengthForNum: num must larger than 0 and smaller than 1048576!"); |
| |
| if (num <= 15) { // 2^4-1 |
| return 1; |
| } |
| else if (num <= 1023) { // 2^10-1 |
| return 2; |
| } |
| else if (num <= 1048575) { // 2^20-1 |
| return 3; |
| } |
| return 0; |
| } |
| |
| // RFC 7959 |
| void encodeBlock(TTCN_Buffer &stream, const BlockOption option, const int length) { |
| |
| unsigned char chr; |
| |
| long long int num_val = option.num().get_long_long_val(); |
| |
| char szx; |
| szx = option.szx().get_long_long_val() & 0x07; // szx_val & 00000111b |
| |
| char m; |
| if ((bool) option.m()) |
| m = 0x08; // 00001000b |
| else |
| m = 0x00; // 00000000b |
| |
| char num; |
| for(int i = length-1; i > 0; i--){ |
| num = (num_val >> (8 * i - 4)) & 0xff; |
| stream.put_c(num); |
| } |
| num = (num_val << 4) & 0xf0; // num_val & 11110000b |
| |
| chr = num | m | szx; |
| stream.put_c(chr); |
| } |
| |
| void decodeBlock(BlockOption& block, OCTETSTRING const &str, const int position, const int length) { |
| |
| int num = 0; |
| for(int i = 0; i < length-1; i++) { |
| num += (str[position+i].get_octet() << (8 * (length-i-1) - 4)); |
| } |
| num += (str[position+length-1].get_octet() >> 4) & 0x0f; |
| |
| int szx = 0; |
| szx = str[position+length-1].get_octet() & 0x07; |
| |
| int m = 0; |
| m = str[position+length-1].get_octet() & 0x08; |
| |
| block.num() = INTEGER(num); |
| block.szx() = INTEGER(szx); |
| block.m() = BOOLEAN(m > 0); |
| } |
| |
| } |