mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-29 21:40:50 +00:00
233 lines
7.3 KiB
C++
233 lines
7.3 KiB
C++
#include "Arduino.h"
|
|
#include "hdlc.h"
|
|
#include "crc.h"
|
|
#include "lwip/def.h"
|
|
#if defined(ESP8266)
|
|
#include "bearssl/bearssl.h"
|
|
#elif defined(ESP32)
|
|
#include "mbedtls/gcm.h"
|
|
#endif
|
|
|
|
void mbus_hexdump(const uint8_t* buf, int len) {
|
|
printf("\nDUMP (%db) [ ", len);
|
|
for(const uint8_t* p = buf; p-buf < len; ++p)
|
|
printf("%02X ", *p);
|
|
printf("]\n");
|
|
}
|
|
|
|
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp) {
|
|
if(length < 10)
|
|
return HDLC_FRAME_INCOMPLETE;
|
|
|
|
int len;
|
|
int headersize = 3;
|
|
int footersize = 1;
|
|
HDLCHeader* h = (HDLCHeader*) d;
|
|
uint8_t* ptr = (uint8_t*) &h[1];
|
|
// Frame format type 3
|
|
if((h->format & 0xF0) == 0xA0) {
|
|
// Length field (11 lsb of format)
|
|
len = (ntohs(h->format) & 0x7FF) + 2;
|
|
if(len > length)
|
|
return HDLC_FRAME_INCOMPLETE;
|
|
|
|
HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);
|
|
footersize = sizeof *f;
|
|
|
|
// First and last byte should be MBUS_HAN_TAG
|
|
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
|
return HDLC_BOUNDRY_FLAG_MISSING;
|
|
|
|
// Verify FCS
|
|
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
|
return HDLC_FCS_ERROR;
|
|
|
|
// Skip destination address, LSB marks last byte
|
|
while(((*ptr) & 0x01) == 0x00) {
|
|
ptr++;
|
|
headersize++;
|
|
}
|
|
headersize++;
|
|
ptr++;
|
|
|
|
// Skip source address, LSB marks last byte
|
|
while(((*ptr) & 0x01) == 0x00) {
|
|
ptr++;
|
|
headersize++;
|
|
}
|
|
headersize++;
|
|
ptr++;
|
|
|
|
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
|
headersize += 3;
|
|
|
|
// Verify HCS
|
|
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
|
return HDLC_HCS_ERROR;
|
|
|
|
ptr += sizeof *t3;
|
|
} else if((h->format & 0xF0) == 0x00) {
|
|
// First byte should be start byte
|
|
if(h->flag != MBUS_START)
|
|
return HDLC_BOUNDRY_FLAG_MISSING;
|
|
|
|
// TODO: Check that the two next bytes are identical
|
|
|
|
// Ignore: Flag + Control field + Address
|
|
ptr += 3;
|
|
headersize += 3;
|
|
}
|
|
|
|
// Extract LLC
|
|
HDLCLLC* llc = (HDLCLLC*) ptr;
|
|
ptr += sizeof *llc;
|
|
headersize += 3;
|
|
|
|
if(((*ptr) & 0xFF) == 0x0F) {
|
|
// Unencrypted APDU
|
|
int i = 0;
|
|
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
|
ptr += sizeof *adpu;
|
|
|
|
// ADPU timestamp
|
|
CosemData* dateTime = (CosemData*) ptr;
|
|
if(dateTime->base.type == CosemTypeOctetString) {
|
|
if(dateTime->base.length == 0x0C) {
|
|
memcpy(timestamp, ptr+1, dateTime->base.length);
|
|
}
|
|
ptr += 2 + dateTime->base.length;
|
|
} else if(dateTime->base.type == CosemTypeNull) {
|
|
timestamp = 0;
|
|
ptr++;
|
|
} else if(dateTime->base.type == CosemTypeDateTime) {
|
|
memcpy(timestamp, ptr, dateTime->base.length);
|
|
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
|
memcpy(timestamp, ptr, 0x0C);
|
|
ptr += 13;
|
|
} else {
|
|
return -99;
|
|
}
|
|
|
|
return ptr-d;
|
|
} else if(((*ptr) & 0xFF) == 0xDB) {
|
|
if(length < headersize + 18)
|
|
return HDLC_FRAME_INCOMPLETE;
|
|
|
|
uint8_t preheadersize = headersize;
|
|
|
|
ptr++;
|
|
// Encrypted APDU
|
|
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
|
if(config == NULL)
|
|
return HDLC_ENCRYPTION_CONFIG_MISSING;
|
|
|
|
uint8_t systemTitleLength = *ptr;
|
|
ptr++;
|
|
memcpy(config->system_title, ptr, systemTitleLength);
|
|
memcpy(config->initialization_vector, config->system_title, systemTitleLength);
|
|
|
|
headersize += 2 + systemTitleLength;
|
|
ptr += systemTitleLength;
|
|
if(((*ptr) & 0xFF) == 0x81) {
|
|
ptr++;
|
|
len = *ptr;
|
|
// 1-byte payload length
|
|
ptr++;
|
|
} else if(((*ptr) & 0xFF) == 0x82) {
|
|
HDLCHeader* h = (HDLCHeader*) d;
|
|
|
|
// Length field (11 lsb of format)
|
|
len = (ntohs(h->format) & 0xFFFF);
|
|
|
|
ptr += 3;
|
|
headersize += 3;
|
|
}
|
|
len += preheadersize;
|
|
if(len > length)
|
|
return HDLC_FRAME_INCOMPLETE;
|
|
|
|
// TODO: FCS
|
|
|
|
memcpy(config->additional_authenticated_data, ptr, 1);
|
|
|
|
// Security tag
|
|
uint8_t sec = *ptr;
|
|
ptr++;
|
|
headersize++;
|
|
|
|
// Frame counter
|
|
memcpy(config->initialization_vector + 8, ptr, 4);
|
|
ptr += 4;
|
|
headersize += 4;
|
|
|
|
// Authentication enabled
|
|
uint8_t authkeylen = 0, aadlen = 0;
|
|
if((sec & 0x10) == 0x10) {
|
|
authkeylen = 12;
|
|
aadlen = 17;
|
|
footersize += authkeylen;
|
|
memcpy(config->additional_authenticated_data + 1, config->authentication_key, 16);
|
|
memcpy(config->authentication_tag, d + len - footersize, authkeylen);
|
|
}
|
|
|
|
#if defined(ESP8266)
|
|
br_gcm_context gcmCtx;
|
|
br_aes_ct_ctr_keys bc;
|
|
br_aes_ct_ctr_init(&bc, config->encryption_key, 16);
|
|
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
|
|
br_gcm_reset(&gcmCtx, config->initialization_vector, sizeof(config->initialization_vector));
|
|
if(authkeylen > 0) {
|
|
br_gcm_aad_inject(&gcmCtx, config->additional_authenticated_data, aadlen);
|
|
}
|
|
br_gcm_flip(&gcmCtx);
|
|
br_gcm_run(&gcmCtx, 0, (void*) ptr, (len - headersize - footersize));
|
|
if(authkeylen > 0 && br_gcm_check_tag_trunc(&gcmCtx, config->authentication_tag, authkeylen) != 1) {
|
|
return -91;
|
|
}
|
|
#elif defined(ESP32)
|
|
uint8_t cipher_text[len - headersize - footersize];
|
|
memcpy(cipher_text, ptr, len - headersize - footersize);
|
|
|
|
mbedtls_gcm_context m_ctx;
|
|
mbedtls_gcm_init(&m_ctx);
|
|
int success = mbedtls_gcm_setkey(&m_ctx, MBEDTLS_CIPHER_ID_AES, config->encryption_key, 128);
|
|
if (0 != success ) {
|
|
return -92;
|
|
}
|
|
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), config->initialization_vector, sizeof(config->initialization_vector),
|
|
config->additional_authenticated_data, aadlen, config->authentication_tag, authkeylen,
|
|
cipher_text, (unsigned char*)(ptr));
|
|
if (0 != success) {
|
|
return -91;
|
|
}
|
|
mbedtls_gcm_free(&m_ctx);
|
|
#endif
|
|
|
|
ptr += 5; // TODO: Come to this number in a proper way...
|
|
|
|
// ADPU timestamp
|
|
CosemData* dateTime = (CosemData*) ptr;
|
|
if(dateTime->base.type == CosemTypeOctetString) {
|
|
if(dateTime->base.length == 0x0C) {
|
|
memcpy(timestamp, ptr+1, dateTime->base.length);
|
|
}
|
|
ptr += 2 + dateTime->base.length;
|
|
} else if(dateTime->base.type == CosemTypeNull) {
|
|
timestamp = 0;
|
|
ptr++;
|
|
} else if(dateTime->base.type == CosemTypeDateTime) {
|
|
memcpy(timestamp, ptr, dateTime->base.length);
|
|
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
|
memcpy(timestamp, ptr, 0x0C);
|
|
ptr += 13;
|
|
} else {
|
|
return -99;
|
|
}
|
|
|
|
return ptr-d;
|
|
}
|
|
|
|
// Unknown payload
|
|
return 0;
|
|
}
|