blob: dd5066f0cc00872e68206c607398aa2909761f74 [file] [log] [blame]
/******************************************************************************
* 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);
}
}