esphome_elektromer_han/components/xt211/apdu.c

2073 lines
64 KiB
C

//
// --------------------------------------------------------------------------
// Gurux Ltd
//
//
//
// Filename: $HeadURL$
//
// Version: $Revision$,
// $Date$
// $Author$
//
// Copyright (c) Gurux Ltd
//
//---------------------------------------------------------------------------
//
// DESCRIPTION
//
// This file is a part of Gurux Device Framework.
//
// Gurux Device Framework is Open Source software; you can redistribute it
// and/or modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2 of the License.
// Gurux Device Framework is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// This code is licensed under the GNU General Public License v2.
// Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt
//---------------------------------------------------------------------------
#include "gxignore.h"
#include "gxmem.h"
#include "enums.h"
#include "helpers.h"
#include "apdu.h"
#include "errorcodes.h"
#include "ciphering.h"
#include "serverevents.h"
#ifndef DLMS_IGNORE_CLIENT
/**
* Retrieves the string that indicates the level of authentication, if any.
*/
int apdu_getAuthenticationString(
dlmsSettings* settings,
gxByteBuffer* data)
{
int ret = 0;
gxByteBuffer* callingAuthenticationValue = NULL;
if (settings->authentication != DLMS_AUTHENTICATION_NONE
#ifndef DLMS_IGNORE_HIGH_GMAC
|| settings->cipher.security != DLMS_SECURITY_NONE
#endif //DLMS_IGNORE_HIGH_GMAC
)
{
unsigned char p[] = { 0x60, 0x85, 0x74, 0x05, 0x08, 0x02 };
// Add sender ACSE-requirements field component.
if ((ret = bb_setUInt8(data, (uint16_t)BER_TYPE_CONTEXT | (char)PDU_TYPE_SENDER_ACSE_REQUIREMENTS)) != 0 ||
(ret = bb_setUInt8(data, 2)) != 0 ||
(ret = bb_setUInt8(data, BER_TYPE_BIT_STRING | BER_TYPE_OCTET_STRING)) != 0 ||
(ret = bb_setUInt8(data, 0x80)) != 0 ||
(ret = bb_setUInt8(data, (uint16_t)BER_TYPE_CONTEXT | (char)PDU_TYPE_MECHANISM_NAME)) != 0 ||
// Len
(ret = bb_setUInt8(data, 7)) != 0 ||
// OBJECT IDENTIFIER
(ret = bb_set(data, p, 6)) != 0 ||
(ret = bb_setUInt8(data, settings->authentication)) != 0)
{
//Error code is returned at the end of the function.
}
}
// If authentication is used.
if (settings->authentication != DLMS_AUTHENTICATION_NONE)
{
// Add Calling authentication information.
if (settings->authentication == DLMS_AUTHENTICATION_LOW)
{
if (settings->password.size != 0)
{
callingAuthenticationValue = &settings->password;
}
}
else
{
callingAuthenticationValue = &settings->ctoSChallenge;
}
// 0xAC
if ((ret = bb_setUInt8(data, BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AUTHENTICATION_VALUE)) == 0 &&
// Len
(ret = bb_setUInt8(data, (unsigned char)(2 + bb_size(callingAuthenticationValue)))) == 0 &&
// Add authentication information.
(ret = bb_setUInt8(data, BER_TYPE_CONTEXT)) == 0 &&
// Len.
(ret = bb_setUInt8(data, (unsigned char)bb_size(callingAuthenticationValue))) == 0)
{
if (callingAuthenticationValue != NULL)
{
ret = bb_set(data, callingAuthenticationValue->data, bb_size(callingAuthenticationValue));
}
}
}
return ret;
}
#endif //DLMS_IGNORE_CLIENT
/**
* Code application context name.
*
* @param settings
* DLMS settings->
* @param data
* Byte buffer where data is saved.
* @param cipher
* Is ciphering settings->
*/
int apdu_generateApplicationContextName(
dlmsSettings* settings,
gxByteBuffer* data)
{
int ret;
//ProtocolVersion
if (settings->protocolVersion != 0)
{
if ((ret = bb_setUInt8(data, BER_TYPE_CONTEXT | PDU_TYPE_PROTOCOL_VERSION)) != 0 ||
(ret = bb_setUInt8(data, 2)) != 0 ||
//Un-used bits.
(ret = bb_setUInt8(data, 2)) != 0 ||
(ret = bb_setUInt8(data, settings->protocolVersion)) != 0)
{
return ret;
}
}
unsigned char ciphered;
#ifndef DLMS_IGNORE_HIGH_GMAC
ciphered = isCiphered(&settings->cipher);
#else
ciphered = 0;
#endif //DLMS_IGNORE_HIGH_GMAC
// Application context name tag
if ((ret = bb_setUInt8(data, (BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_APPLICATION_CONTEXT_NAME))) != 0 ||
// Len
(ret = bb_setUInt8(data, 0x09)) != 0 ||
(ret = bb_setUInt8(data, BER_TYPE_OBJECT_IDENTIFIER)) != 0 ||
// Len
(ret = bb_setUInt8(data, 0x07)) != 0 ||
(ret = bb_setUInt8(data, 0x60)) != 0 ||
(ret = bb_setUInt8(data, 0x85)) != 0 ||
(ret = bb_setUInt8(data, 0x74)) != 0 ||
(ret = bb_setUInt8(data, 0x05)) != 0 ||
(ret = bb_setUInt8(data, 0x08)) != 0 ||
(ret = bb_setUInt8(data, 0x01)) != 0)
{
return ret;
}
if (settings->useLogicalNameReferencing)
{
if ((ret = bb_setUInt8(data, ciphered ? 0x03 : 0x01)) != 0)
{
return ret;
}
}
else
{
if ((ret = bb_setUInt8(data, ciphered ? 0x04 : 0x02)) != 0)
{
return ret;
}
}
// Add system title.
#ifndef DLMS_IGNORE_HIGH_GMAC
if (!settings->server && (ciphered || settings->authentication == DLMS_AUTHENTICATION_HIGH_GMAC))
{
#ifndef DLMS_IGNORE_MALLOC
if (settings->cipher.systemTitle.size == 0)
{
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
#endif //DLMS_IGNORE_MALLOC
// Add calling-AP-title
if ((ret = bb_setUInt8(data, (BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | 6))) != 0 ||
// LEN
(ret = bb_setUInt8(data, (unsigned char)(2 + 8))) != 0 ||
(ret = bb_setUInt8(data, BER_TYPE_OCTET_STRING)) != 0 ||
// LEN
(ret = bb_setUInt8(data, (unsigned char)8)) != 0 ||
#ifndef DLMS_IGNORE_MALLOC
(ret = bb_set(data, settings->cipher.systemTitle.data, 8)) != 0)
#else
(ret = bb_set(data, settings->cipher.systemTitle, 8)) != 0)
#endif //DLMS_IGNORE_MALLOC
{
return ret;
}
}
//Add CallingAEInvocationId.
if (!settings->server && settings->userId != -1 && settings->cipher.security != DLMS_SECURITY_NONE)
{
if ((ret = bb_setUInt8(data, (BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AE_INVOCATION_ID))) != 0 ||
//LEN
(ret = bb_setUInt8(data, 3)) != 0 ||
(ret = bb_setUInt8(data, BER_TYPE_INTEGER)) != 0 ||
//LEN
(ret = bb_setUInt8(data, 1)) != 0 ||
(ret = bb_setUInt8(data, (unsigned char)settings->userId)) != 0)
{
return ret;
}
}
#endif //DLMS_IGNORE_HIGH_GMAC
return 0;
}
#ifndef DLMS_IGNORE_HIGH_GMAC
unsigned char useDedicatedKey(dlmsSettings* settings)
{
#ifndef DLMS_IGNORE_MALLOC
if (settings->cipher.dedicatedKey == NULL)
{
return 0;
}
return settings->cipher.dedicatedKey->size == 16;
#else
return memcmp(settings->cipher.dedicatedKey, EMPTY_KEY, sizeof(EMPTY_KEY)) != 0;
#endif //DLMS_IGNORE_MALLOC
}
#endif //DLMS_IGNORE_HIGH_GMAC
// Reserved for internal use.
int apdu_getConformanceFromArray(gxByteBuffer* data, uint32_t* value)
{
int ret;
unsigned char v;
uint32_t tmp;
if ((ret = bb_getUInt8(data, &v)) == 0)
{
*value = hlp_swapBits(v);
if ((ret = bb_getUInt8(data, &v)) == 0)
{
tmp = hlp_swapBits(v);
tmp <<= 8;
*value |= tmp;
if ((ret = bb_getUInt8(data, &v)) == 0)
{
tmp = hlp_swapBits(v);
tmp <<= 16;
*value |= tmp;
}
}
}
return ret;
}
// Reserved for internal use.
int apdu_setConformanceToArray(uint32_t value, gxByteBuffer* data)
{
int ret;
if ((ret = bb_setUInt8(data, hlp_swapBits((unsigned char)(value & 0xFF)))) == 0)
{
value >>= 8;
if ((ret = bb_setUInt8(data, hlp_swapBits((unsigned char)(value & 0xFF)))) == 0)
{
value >>= 8;
ret = bb_setUInt8(data, hlp_swapBits((unsigned char)(value & 0xFF)));
}
}
return ret;
}
/**
* Generate User information initiate request.
*
* @param settings
* DLMS settings->
* @param cipher
* @param data
*/
int apdu_getInitiateRequest(
dlmsSettings* settings,
gxByteBuffer* data)
{
int ret;
// Tag for xDLMS-Initiate request
bb_setUInt8(data, DLMS_COMMAND_INITIATE_REQUEST);
// Usage field for the response allowed component.
#ifdef DLMS_IGNORE_HIGH_GMAC
bb_setUInt8(data, 0);
#else
// Usage field for dedicated-key component.
if (settings->cipher.security == DLMS_SECURITY_NONE || !useDedicatedKey(settings))
{
bb_setUInt8(data, 0);
}
else
{
bb_setUInt8(data, 1);
#ifndef DLMS_IGNORE_MALLOC
hlp_setObjectCount(settings->cipher.dedicatedKey->size, data);
bb_set(data, settings->cipher.dedicatedKey->data, settings->cipher.dedicatedKey->size);
#else
hlp_setObjectCount(8, data);
bb_set(data, settings->cipher.dedicatedKey, 8);
#endif //DLMS_IGNORE_MALLOC
}
#endif //DLMS_IGNORE_HIGH_GMAC
// encoding of the response-allowed component (bool DEFAULT TRUE)
// usage flag (FALSE, default value TRUE conveyed)
bb_setUInt8(data, 0);
// Usage field of the proposed-quality-of-service component.
if (settings->qualityOfService == 0)
{
// Not used
bb_setUInt8(data, 0x00);
}
else
{
bb_setUInt8(data, 0x01);
bb_setUInt8(data, settings->qualityOfService);
}
if ((ret = bb_setUInt8(data, settings->dlmsVersionNumber)) != 0 ||
// Tag for conformance block
(ret = bb_setUInt8(data, 0x5F)) != 0 ||
(ret = bb_setUInt8(data, 0x1F)) != 0 ||
// length of the conformance block
(ret = bb_setUInt8(data, 0x04)) != 0 ||
// encoding the number of unused bits in the bit string
(ret = bb_setUInt8(data, 0x00)) != 0 ||
(ret = apdu_setConformanceToArray(settings->proposedConformance, data)) != 0 ||
(ret = bb_setUInt16(data, settings->maxPduSize)) != 0)
{
return ret;
}
return 0;
}
#ifndef DLMS_IGNORE_CLIENT
/**
* Generate user information.
*
* @param settings
* DLMS settings->
* @param cipher
* @param data
* Generated user information.
*/
int apdu_generateUserInformation(
dlmsSettings* settings,
gxByteBuffer* data)
{
int ret = 0;
bb_setUInt8(data, BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_USER_INFORMATION);
#ifndef DLMS_IGNORE_HIGH_GMAC
if (!isCiphered(&settings->cipher))
#endif //DLMS_IGNORE_HIGH_GMAC
{
// Length for AARQ user field
bb_setUInt8(data, 0x10);
// Coding the choice for user-information (Octet STRING, universal)
bb_setUInt8(data, BER_TYPE_OCTET_STRING);
// Length
bb_setUInt8(data, 0x0E);
if ((ret = apdu_getInitiateRequest(settings, data)) != 0)
{
return ret;
}
}
#ifndef DLMS_IGNORE_HIGH_GMAC
else
{
gxByteBuffer crypted;
#ifndef DLMS_IGNORE_MALLOC
BYTE_BUFFER_INIT(&crypted);
#else
unsigned char tmp[25 + 12];
bb_attach(&crypted, tmp, 0, sizeof(tmp));
#endif //DLMS_IGNORE_MALLOC
if ((ret = apdu_getInitiateRequest(settings, &crypted)) != 0)
{
return ret;
}
#ifndef DLMS_IGNORE_MALLOC
ret = cip_encrypt(
&settings->cipher,
settings->cipher.security,
DLMS_COUNT_TYPE_PACKET,
settings->cipher.invocationCounter,
DLMS_COMMAND_GLO_INITIATE_REQUEST,
settings->cipher.systemTitle.data,
&settings->cipher.blockCipherKey,
&crypted);
#else
ret = cip_encrypt(
&settings->cipher,
settings->cipher.security,
DLMS_COUNT_TYPE_PACKET,
settings->cipher.invocationCounter,
DLMS_COMMAND_GLO_INITIATE_REQUEST,
settings->cipher.systemTitle,
settings->cipher.blockCipherKey,
&crypted);
#endif //DLMS_IGNORE_MALLOC
if (ret == 0)
{
// Length for AARQ user field
if ((ret = bb_setUInt8(data, (unsigned char)(2 + crypted.size))) != 0 ||
// Coding the choice for user-information (Octet string, universal)
(ret = bb_setUInt8(data, BER_TYPE_OCTET_STRING)) != 0 ||
(ret = bb_setUInt8(data, (unsigned char)crypted.size)) != 0 ||
(ret = bb_set2(data, &crypted, 0, crypted.size)) != 0)
{
//Error code is returned at the end of the function.
}
}
#ifndef DLMS_IGNORE_MALLOC
bb_clear(&crypted);
#endif //DLMS_IGNORE_MALLOC
}
#endif //DLMS_IGNORE_HIGH_GMAC
return ret;
}
#endif //DLMS_IGNORE_CLIENT
/**
* Parse User Information from PDU.
*/
int apdu_parseUserInformation(
dlmsSettings* settings,
gxByteBuffer* data,
unsigned char ciphered,
unsigned char* command)
{
int ret;
unsigned char response;
uint16_t pduSize;
unsigned char ch, len, tag;
uint32_t v;
if ((ret = bb_getUInt8(data, &len)) != 0)
{
return ret;
}
if (data->size - data->position < len)
{
return DLMS_ERROR_CODE_OUTOFMEMORY;
}
// Encoding the choice for user information
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
if (tag != 0x4)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(data, &len)) != 0)
{
return ret;
}
// Tag for xDLMS-Initate.response
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
#ifndef DLMS_IGNORE_HIGH_GMAC
DLMS_SECURITY security;
DLMS_SECURITY_SUITE suite;
uint64_t invocationCounter;
if (tag == DLMS_COMMAND_GLO_INITIATE_RESPONSE ||
tag == DLMS_COMMAND_GLO_INITIATE_REQUEST ||
tag == DLMS_COMMAND_DED_INITIATE_RESPONSE ||
tag == DLMS_COMMAND_DED_INITIATE_REQUEST ||
tag == DLMS_COMMAND_GENERAL_GLO_CIPHERING ||
tag == DLMS_COMMAND_GENERAL_DED_CIPHERING)
{
*command = (unsigned char)tag;
data->position = (data->position - 1);
#ifndef DLMS_IGNORE_MALLOC
if ((ret = cip_decrypt(&settings->cipher,
settings->sourceSystemTitle,
&settings->cipher.blockCipherKey,
data,
&security,
&suite,
&invocationCounter)) != 0)
{
return ret;
}
#else
if ((ret = cip_decrypt(&settings->cipher,
settings->sourceSystemTitle,
settings->cipher.blockCipherKey,
data,
&security,
&suite,
&invocationCounter)) != 0)
{
return DLMS_ERROR_CODE_INVALID_DECIPHERING_ERROR;
}
#endif //DLMS_IGNORE_MALLOC
if (settings->expectedSecurityPolicy != 0xFF && security != settings->expectedSecurityPolicy << 4)
{
return DLMS_ERROR_CODE_INVALID_DECIPHERING_ERROR;
}
if (settings->expectedSecuritySuite != 0xFF && suite != settings->expectedSecuritySuite)
{
return DLMS_ERROR_CODE_INVALID_SECURITY_SUITE;
}
if (settings->expectedInvocationCounter != NULL)
{
if (invocationCounter < 1 + *settings->expectedInvocationCounter)
{
return DLMS_ERROR_CODE_INVOCATION_COUNTER_TOO_SMALL;
}
#ifdef DLMS_COSEM_INVOCATION_COUNTER_SIZE64
*settings->expectedInvocationCounter = 1 + invocationCounter;
#else
*settings->expectedInvocationCounter = (uint32_t)(1 + invocationCounter);
#endif //DLMS_COSEM_INVOCATION_COUNTER_SIZE64
}
//If client system title doesn't match.
if (settings->expectedClientSystemTitle != NULL &&
memcmp(settings->expectedClientSystemTitle, EMPTY_SYSTEM_TITLE, 8) != 0 &&
memcmp(settings->sourceSystemTitle, settings->expectedClientSystemTitle, 8) != 0)
{
return DLMS_ERROR_CODE_INVALID_DECIPHERING_ERROR;
}
settings->cipher.security = security;
settings->cipher.suite = suite;
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
}
#endif //DLMS_IGNORE_HIGH_GMAC
response = tag == DLMS_COMMAND_INITIATE_RESPONSE;
if (response)
{
// Optional usage field of the negotiated quality of service
// component
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
if (tag != 0)
{
if ((ret = bb_getUInt8(data, &settings->qualityOfService)) != 0)
{
return ret;
}
}
}
else if (tag == DLMS_COMMAND_INITIATE_REQUEST)
{
// Optional usage field of the negotiated quality of service
// component
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
// Dedicated key.
#ifdef DLMS_IGNORE_HIGH_GMAC
#else
if (tag != 0)
{
//Return error if ciphering is not used.
if (!ciphered || settings->cipher.security != DLMS_SECURITY_AUTHENTICATION_ENCRYPTION)
{
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(data, &len)) != 0)
{
return ret;
}
if (len != 16)
{
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
#ifndef DLMS_IGNORE_MALLOC
if (settings->cipher.dedicatedKey == NULL)
{
settings->cipher.dedicatedKey = gxmalloc(sizeof(gxByteBuffer));
BYTE_BUFFER_INIT(settings->cipher.dedicatedKey);
}
else
{
bb_clear(settings->cipher.dedicatedKey);
}
bb_set2(settings->cipher.dedicatedKey, data, data->position, len);
#else
hlp_setObjectCount(8, data);
bb_get(data, settings->cipher.dedicatedKey, len);
#endif //DLMS_IGNORE_MALLOC
}
else
{
#ifndef DLMS_IGNORE_MALLOC
if (settings->cipher.dedicatedKey != NULL)
{
bb_clear(settings->cipher.dedicatedKey);
gxfree(settings->cipher.dedicatedKey);
settings->cipher.dedicatedKey = NULL;
}
#else
memset(settings->cipher.dedicatedKey, 0, 8);
#endif //DLMS_IGNORE_MALLOC
}
#endif //DLMS_IGNORE_HIGH_GMAC
// Optional usage field of the negotiated quality of service
// component
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
// Skip if used.
if (tag != 0)
{
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
}
// Optional usage field of the proposed quality of service component
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
if (tag != 0)
{
if ((ret = bb_getUInt8(data, &settings->qualityOfService)) != 0)
{
return ret;
}
}
}
else
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
// Get DLMS version number.
if (settings->server)
{
if ((ret = bb_getUInt8(data, &ch)) != 0)
{
return ret;
}
settings->dlmsVersionNumber = ch;
}
else
{
if ((ret = bb_getUInt8(data, &ch)) != 0)
{
return ret;
}
if (ch < 6)
{
//Invalid DLMS version number.
return DLMS_ERROR_CODE_INVALID_VERSION_NUMBER;
}
}
// Tag for conformance block
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
if (tag != 0x5F)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
// Old Way...
tag = data->data[data->position];
if (tag == 0x1F)
{
data->position = (data->position + 1);
}
if ((ret = bb_getUInt8(data, &len)) != 0)
{
return ret;
}
// The number of unused bits in the bit string.
if ((ret = bb_getUInt8(data, &tag)) != 0)
{
return ret;
}
if ((ret = apdu_getConformanceFromArray(data, &v)) != 0)
{
return ret;
}
if (settings->server)
{
settings->negotiatedConformance = (DLMS_CONFORMANCE)(v & settings->proposedConformance);
//Remove general protection if ciphered connection is not used.
if (!ciphered && (settings->negotiatedConformance & DLMS_CONFORMANCE_GENERAL_PROTECTION) != 0)
{
settings->negotiatedConformance &= ~DLMS_CONFORMANCE_GENERAL_PROTECTION;
}
}
else
{
settings->negotiatedConformance = (DLMS_CONFORMANCE)v;
}
if (settings->server)
{
if ((ret = bb_getUInt16(data, &pduSize)) != 0)
{
return ret;
}
//If client asks too high PDU.
if (pduSize > settings->maxServerPDUSize)
{
pduSize = settings->maxServerPDUSize;
}
settings->maxPduSize = pduSize;
}
else
{
if ((ret = bb_getUInt16(data, &pduSize)) != 0)
{
return ret;
}
settings->maxPduSize = pduSize;
}
if (response)
{
// VAA Name
uint16_t vaa;
if ((ret = bb_getUInt16(data, &vaa)) != 0)
{
return ret;
}
if (vaa == 0x0007)
{
// If LN
if (!settings->useLogicalNameReferencing)
{
//Invalid VAA.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
}
else if (vaa == 0xFA00)
{
// If SN
if (settings->useLogicalNameReferencing)
{
//Invalid VAA.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
}
else
{
// Unknown VAA.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
}
return 0;
}
/**
* Parse application context name.
*
* @param settings
* DLMS settings->
* @param buff
* Received data->
*/
int apdu_parseApplicationContextName(
dlmsSettings* settings,
gxByteBuffer* buff,
unsigned char* ciphered)
{
int ret;
unsigned char len, ch;
// Get length.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
return ret;
}
if (buff->size - buff->position < len)
{
//Encoding failed. Not enough data->
return DLMS_ERROR_CODE_OUTOFMEMORY;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x6)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
#ifndef DLMS_IGNORE_HIGH_GMAC
if (settings->server)
{
settings->cipher.security = DLMS_SECURITY_NONE;
}
#endif //DLMS_IGNORE_HIGH_GMAC
// Object ID length.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
return ret;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x60)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x85)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x74)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x05)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x08)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x01)
{
//Encoding failed. Not an Object ID.
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (settings->useLogicalNameReferencing)
{
*ciphered = ch == 3;
if (ch == 1 || *ciphered)
{
return 0;
}
return DLMS_ERROR_CODE_FALSE;
}
*ciphered = ch == 4;
if (ch == 2 || *ciphered)
{
return 0;
}
return DLMS_ERROR_CODE_FALSE;
}
int apdu_validateAare(
dlmsSettings* settings,
gxByteBuffer* buff)
{
int ret;
unsigned char tag;
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
return ret;
}
if (settings->server)
{
if (tag != (BER_TYPE_APPLICATION
| BER_TYPE_CONSTRUCTED
| PDU_TYPE_PROTOCOL_VERSION))
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
}
}
else
{
if (tag != (BER_TYPE_APPLICATION
| BER_TYPE_CONSTRUCTED
| PDU_TYPE_APPLICATION_CONTEXT_NAME))
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
}
}
return ret;
}
int apdu_updatePassword(
dlmsSettings* settings,
gxByteBuffer* buff)
{
int ret;
unsigned char ch, len;
if ((ret = bb_getUInt8(buff, &len)) == 0 &&
// Get authentication information.
(ret = bb_getUInt8(buff, &ch)) == 0)
{
if (ch != 0x80)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
}
else if ((ret = bb_getUInt8(buff, &len)) == 0)
{
if (settings->authentication == DLMS_AUTHENTICATION_LOW)
{
if ((ret = bb_clear(&settings->password)) != 0 ||
(ret = bb_set2(&settings->password, buff, buff->position, len)) != 0)
{
//Error code is returned at the end of the function.
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("updating password failed. "), ret);
#endif //DLMS_DEBUG
}
}
else
{
if (len < 8 || len > 64)
{
ret = DLMS_ERROR_CODE_INVALID_PARAMETER;
}
else
{
if ((ret = bb_clear(&settings->ctoSChallenge)) != 0 ||
(ret = bb_set2(&settings->ctoSChallenge, buff, buff->position, len)) != 0)
{
//Error code is returned at the end of the function.
}
}
}
}
}
return ret;
}
int apdu_updateAuthentication(
dlmsSettings* settings,
gxByteBuffer* buff)
{
int ret;
unsigned char ch;
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x60)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x85)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x74)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x05)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x08)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
if (ch != 0x02)
{
return DLMS_ERROR_CODE_INVALID_TAG;
}
if ((ret = bb_getUInt8(buff, &ch)) != 0)
{
return ret;
}
switch (ch)
{
case DLMS_AUTHENTICATION_NONE:
case DLMS_AUTHENTICATION_LOW:
case DLMS_AUTHENTICATION_HIGH:
#ifndef DLMS_IGNORE_HIGH_MD5
case DLMS_AUTHENTICATION_HIGH_MD5:
#endif //DLMS_IGNORE_HIGH_MD5
#ifndef DLMS_IGNORE_HIGH_SHA1
case DLMS_AUTHENTICATION_HIGH_SHA1:
#endif //DLMS_IGNORE_HIGH_SHA1
#ifndef DLMS_IGNORE_HIGH_SHA256
case DLMS_AUTHENTICATION_HIGH_SHA256:
#endif //DLMS_IGNORE_HIGH_SHA256
#ifndef DLMS_IGNORE_HIGH_GMAC
case DLMS_AUTHENTICATION_HIGH_GMAC:
#endif //DLMS_IGNORE_HIGH_GMAC
break;
default:
return DLMS_ERROR_CODE_INVALID_TAG;
}
settings->authentication = (DLMS_AUTHENTICATION)ch;
return 0;
}
int apdu_getUserInformation(
dlmsSettings* settings,
gxByteBuffer* data,
unsigned char command)
{
int ret = 0;
// Tag for xDLMS-Initiate
if ((ret = bb_setUInt8(data, DLMS_COMMAND_INITIATE_RESPONSE)) != 0)
{
return ret;
}
//NegotiatedQualityOfService
if (settings->qualityOfService == 0)
{
// Not used.
if ((ret = bb_setUInt8(data, 0x00)) != 0)
{
return ret;
}
}
else
{
if ((ret = bb_setUInt8(data, 0x01)) != 0 ||
(ret = bb_setUInt8(data, settings->qualityOfService)) != 0)
{
return ret;
}
}
// DLMS Version Number
if ((ret = bb_setUInt8(data, 06)) != 0 ||
(ret = bb_setUInt8(data, 0x5F)) != 0 ||
(ret = bb_setUInt8(data, 0x1F)) != 0 ||
// length of the conformance block
(ret = bb_setUInt8(data, 0x04)) != 0 ||
// encoding the number of unused bits in the bit string
(ret = bb_setUInt8(data, 0x00)) != 0 ||
(ret = apdu_setConformanceToArray(settings->negotiatedConformance, data)) != 0 ||
(ret = bb_setUInt16(data, settings->maxPduSize)) != 0)
{
return ret;
}
// VAA Name VAA name (0x0007 for LN referencing and 0xFA00 for SN)
if ((ret = bb_setUInt16(data, settings->useLogicalNameReferencing ? 0x0007 : 0xFA00)) != 0)
{
return ret;
}
#ifndef DLMS_IGNORE_HIGH_GMAC
if (isCiphered(&settings->cipher))
{
unsigned char cmd;
if (command == DLMS_COMMAND_GENERAL_DED_CIPHERING)
{
cmd = DLMS_COMMAND_GENERAL_DED_CIPHERING;
}
else if (command == DLMS_COMMAND_GENERAL_GLO_CIPHERING)
{
cmd = DLMS_COMMAND_GENERAL_GLO_CIPHERING;
}
else
{
cmd = DLMS_COMMAND_GLO_INITIATE_RESPONSE;
}
#ifndef DLMS_IGNORE_MALLOC
ret = cip_encrypt(&settings->cipher,
settings->cipher.security,
DLMS_COUNT_TYPE_PACKET,
settings->cipher.invocationCounter,
cmd,
settings->cipher.systemTitle.data,
&settings->cipher.blockCipherKey,
data);
#else
ret = cip_encrypt(&settings->cipher,
settings->cipher.security,
DLMS_COUNT_TYPE_PACKET,
settings->cipher.invocationCounter,
cmd,
settings->cipher.systemTitle,
settings->cipher.blockCipherKey,
data);
#endif //DLMS_IGNORE_MALLOC
}
#endif //DLMS_IGNORE_HIGH_GMAC
return ret;
}
#ifndef DLMS_IGNORE_CLIENT
int apdu_generateAarq(
dlmsSettings* settings,
gxByteBuffer* data)
{
#if defined(GX_DLMS_BYTE_BUFFER_SIZE_32) || (!defined(GX_DLMS_MICROCONTROLLER) && (defined(_WIN32) || defined(_WIN64) || defined(__linux__)))
uint32_t offset;
#else
uint16_t offset;
#endif
int ret;
// Length is updated later.
offset = data->size + 1;
// AARQ APDU Tag
if ((ret = bb_setUInt8(data, BER_TYPE_APPLICATION | BER_TYPE_CONSTRUCTED)) == 0 &&
(ret = bb_setUInt8(data, 0)) == 0 &&
///////////////////////////////////////////
// Add Application context name.
(ret = apdu_generateApplicationContextName(settings, data)) == 0 &&
(ret = apdu_getAuthenticationString(settings, data)) == 0 &&
(ret = apdu_generateUserInformation(settings, data)) == 0)
{
return bb_setUInt8ByIndex(data, offset, (unsigned char)(data->size - offset - 1));
}
return ret;
}
#endif //DLMS_IGNORE_CLIENT
int apdu_handleResultComponent(unsigned char value)
{
int ret;
switch (value)
{
case DLMS_SOURCE_DIAGNOSTIC_NO_REASON_GIVEN:
ret = DLMS_ERROR_CODE_NO_REASON_GIVEN;
break;
case DLMS_SOURCE_DIAGNOSTIC_APPLICATION_CONTEXT_NAME_NOT_SUPPORTED:
ret = DLMS_ERROR_CODE_APPLICATION_CONTEXT_NAME_NOT_SUPPORTED;
break;
case DLMS_SOURCE_DIAGNOSTIC_AUTHENTICATION_MECHANISM_NAME_NOT_RECOGNISED:
ret = DLMS_ERROR_CODE_AUTHENTICATION_MECHANISM_NAME_NOT_RECOGNISED;
break;
case DLMS_SOURCE_DIAGNOSTIC_AUTHENTICATION_MECHANISM_NAME_REQUIRED:
ret = DLMS_ERROR_CODE_AUTHENTICATION_MECHANISM_NAME_REQUIRED;
break;
case DLMS_SOURCE_DIAGNOSTIC_AUTHENTICATION_FAILURE:
ret = DLMS_ERROR_CODE_AUTHENTICATION_FAILURE;
break;
default:
//OK.
ret = 0;
break;
}
return ret;
}
int apdu_parseProtocolVersion(dlmsSettings* settings,
gxByteBuffer* buff)
{
unsigned char cnt, unusedBits, value;
int ret;
if ((ret = bb_getUInt8(buff, &cnt)) != 0)
{
return ret;
}
if ((ret = bb_getUInt8(buff, &unusedBits)) != 0)
{
return ret;
}
if (unusedBits > 8)
{
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
if ((ret = bb_getUInt8(buff, &value)) != 0)
{
return ret;
}
//Protocol version must be 100001.
//This is not checked in client side because some meters are returning wrong value here.
if (settings->server && value != 0x84)
{
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
settings->protocolVersion = value;
return 0;
}
int apdu_parsePDU(
dlmsSettings* settings,
gxByteBuffer* buff,
DLMS_ASSOCIATION_RESULT* result,
unsigned char* diagnostic,
unsigned char* command)
{
unsigned char ciphered = 0;
uint16_t size;
unsigned char len;
unsigned char tag;
int ret;
*result = DLMS_ASSOCIATION_RESULT_ACCEPTED;
*diagnostic = DLMS_SOURCE_DIAGNOSTIC_NONE;
#ifndef DLMS_IGNORE_SERVER
typedef enum
{
DLMS_AFU_MISSING_NONE = 0x0,
DLMS_AFU_MISSING_SENDER_ACSE_REQUIREMENTS = 0x1,
DLMS_AFU_MISSING_MECHANISM_NAME = 0x2,
DLMS_AFU_MISSING_CALLING_AUTHENTICATION_VALUE = 0x4
}
DLMS_AFU_MISSING;
DLMS_AFU_MISSING afu = DLMS_AFU_MISSING_NONE;
#endif //DLMS_IGNORE_SERVER
// Get AARE tag and length
if ((ret = apdu_validateAare(settings, buff)) != 0)
{
return ret;
}
if ((ret = hlp_getObjectCount2(buff, &size)) != 0)
{
return ret;
}
if (size > buff->size - buff->position)
{
//Encoding failed. Not enough data->
return DLMS_ERROR_CODE_OUTOFMEMORY;
}
while (ret == 0 && buff->position < buff->size)
{
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
break;
}
switch (tag)
{
//0xA1
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_APPLICATION_CONTEXT_NAME:
{
if ((ret = apdu_parseApplicationContextName(settings, buff, &ciphered)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("apdu_parseApplicationContextName "), ret);
#endif //DLMS_DEBUG
* diagnostic = DLMS_SOURCE_DIAGNOSTIC_APPLICATION_CONTEXT_NAME_NOT_SUPPORTED;
*result = DLMS_ASSOCIATION_RESULT_PERMANENT_REJECTED;
return 0;
}
#ifndef DLMS_IGNORE_SERVER
if (ciphered)
{
afu = (DLMS_AFU_MISSING)(DLMS_AFU_MISSING_SENDER_ACSE_REQUIREMENTS | DLMS_AFU_MISSING_MECHANISM_NAME | DLMS_AFU_MISSING_CALLING_AUTHENTICATION_VALUE);
}
#endif //DLMS_IGNORE_SERVER
}
break;
// 0xA2
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLED_AP_TITLE:
// Get len.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP title "), -1);
#endif //DLMS_DEBUG
break;
}
if (len != 3)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP title "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
// Choice for result.
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP title "), -1);
#endif //DLMS_DEBUG
break;
}
if (settings->server)
{
//Ignore if client sends CalledAPTitle.
if (tag != BER_TYPE_OCTET_STRING)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP title "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
// Get len.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP title "), -1);
#endif //DLMS_DEBUG
break;
}
buff->position += len;
}
else
{
if (tag != BER_TYPE_INTEGER)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
// Get len.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
break;
}
if (len != 1)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
break;
}
*result = (DLMS_ASSOCIATION_RESULT)tag;
}
break;
// 0xA3 SourceDiagnostic
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLED_AE_QUALIFIER:
if ((ret = bb_getUInt8(buff, &len)) != 0 ||
// ACSE service user tag.
(ret = bb_getUInt8(buff, &tag)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE qualifier. "), -1);
#endif //DLMS_DEBUG
break;
}
if (settings->server)
{
//Ignore if client sends CalledAEQualifier.
if (tag != BER_TYPE_OCTET_STRING)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE qualifier. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
buff->position += len;
}
else
{
// Result source diagnostic component.
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
break;
}
if (tag != BER_TYPE_INTEGER)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
break;
}
if (len != 1)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
break;
}
*diagnostic = (DLMS_SOURCE_DIAGNOSTIC)tag;
}
break;
// 0xA4 Result
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLED_AP_INVOCATION_ID:
// Get len.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP invocationID. "), -1);
#endif //DLMS_DEBUG
break;
}
if (settings->server)
{
if (len != 3)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP invocationID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
// ACSE service user tag.
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP invocationID. "), -1);
#endif //DLMS_DEBUG
break;
}
if (tag != BER_TYPE_INTEGER)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP invocationID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AP invocationID. "), -1);
#endif //DLMS_DEBUG
break;
}
//Ignore if client sends CalledAEQualifier.
buff->position += len;
}
else
{
if (len != 0xA)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
// Choice for result (Universal, Octet string type)
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
break;
}
if (tag != BER_TYPE_OCTET_STRING)
{
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
// responding-AP-title-field
// Get len.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
break;
}
if ((ret = bb_get(buff, settings->sourceSystemTitle, len)) != 0)
{
break;
}
//If system title is invalid.
if (len != 8)
{
memset(settings->sourceSystemTitle, 0, 8);
}
}
break;
// 0xA6 Client system title.
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AP_TITLE:
if ((ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &tag)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid client system title. "), -1);
#endif //DLMS_DEBUG
break;
}
if (ciphered && len != 8)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid client system title. "), -1);
#endif //DLMS_DEBUG
* diagnostic = DLMS_SOURCE_DIAGNOSTIC_CALLING_AP_TITLE_NOT_RECOGNIZED;
*result = DLMS_ASSOCIATION_RESULT_PERMANENT_REJECTED;
return 0;
}
if ((ret = bb_get(buff, settings->sourceSystemTitle, len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid client system title. "), -1);
#endif //DLMS_DEBUG
break;
}
break;
// 0xAA Server system title.
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_SENDER_ACSE_REQUIREMENTS:
if ((ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &tag)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_clear(&settings->stoCChallenge)) != 0 ||
(ret = bb_set2(&settings->stoCChallenge, buff, buff->position, len)) != 0)
{
break;
}
break;
//Client AE Invocation id.
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AE_INVOCATION_ID:
if ((ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &tag)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
break;
//Client CalledAeInvocationId.
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLED_AE_INVOCATION_ID://0xA5
if (settings->server)
{
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
//Invalid length.
if (len != 3)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
//Invalid length.
if (len != 2)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
//Invalid tag length.
if (len != 1)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
//Get value.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
}
else
{
if ((ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &tag)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0)
{
break;
}
if (ciphered)
{
settings->userId = len;
}
}
break;
//Server RespondingAEInvocationId.
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AE_QUALIFIER://0xA7
if ((ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &tag)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0 ||
(ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Responding AE Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
if (ciphered && len == 0)
{
settings->userId = len;
}
break;
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AP_INVOCATION_ID://0xA8
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
if (tag != 3)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
if (len != 2)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
//Invalid tag length.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
return ret;
}
if (len != 1)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
//Get value.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid Calling AP Invocation ID. "), -1);
#endif //DLMS_DEBUG
break;
}
break;
// 0x8A or 0x88
case (uint16_t)BER_TYPE_CONTEXT | (unsigned char)PDU_TYPE_SENDER_ACSE_REQUIREMENTS:
case (uint16_t)BER_TYPE_CONTEXT | (unsigned char)PDU_TYPE_CALLING_AP_INVOCATION_ID:
// Get sender ACSE-requirements field component.
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid sender ACSE-requirements field. "), -1);
#endif //DLMS_DEBUG
break;
}
if (len != 2)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid sender ACSE-requirements field. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid sender ACSE-requirements field. "), -1);
#endif //DLMS_DEBUG
break;
}
if (tag != BER_TYPE_OBJECT_DESCRIPTOR)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid sender ACSE-requirements field. "), -1);
#endif //DLMS_DEBUG
ret = DLMS_ERROR_CODE_INVALID_TAG;
break;
}
//Get only value because client app is sending system title with LOW authentication.
if ((ret = bb_getUInt8(buff, &tag)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid sender ACSE-requirements field. "), -1);
#endif //DLMS_DEBUG
break;
}
#ifndef DLMS_IGNORE_SERVER
if (ciphered && tag == 0x80)
{
afu &= ~DLMS_AFU_MISSING_SENDER_ACSE_REQUIREMENTS;
}
#endif //DLMS_IGNORE_SERVER
break;
// 0x8B or 0x89
case (uint16_t)BER_TYPE_CONTEXT | (unsigned char)PDU_TYPE_MECHANISM_NAME:
case (uint16_t)BER_TYPE_CONTEXT | (unsigned char)PDU_TYPE_CALLING_AE_INVOCATION_ID:
if ((ret = apdu_updateAuthentication(settings, buff)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid mechanism name. "), ret);
#endif //DLMS_DEBUG
break;
}
#ifndef DLMS_IGNORE_HIGH_GMAC
unsigned char invalidSystemTitle;
invalidSystemTitle = memcmp(settings->sourceSystemTitle, EMPTY_SYSTEM_TITLE, 8) == 0;
if (settings->server && settings->authentication == DLMS_AUTHENTICATION_HIGH_GMAC && invalidSystemTitle)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid mechanism name. "), -1);
#endif //DLMS_DEBUG
* diagnostic = DLMS_SOURCE_DIAGNOSTIC_CALLING_AP_TITLE_NOT_RECOGNIZED;
*result = DLMS_ASSOCIATION_RESULT_PERMANENT_REJECTED;
return 0;
}
#endif //DLMS_IGNORE_HIGH_GMAC
#ifndef DLMS_IGNORE_SERVER
if (ciphered)
{
afu &= ~DLMS_AFU_MISSING_MECHANISM_NAME;
}
#endif //DLMS_IGNORE_SERVER
break;
// 0xAC
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AUTHENTICATION_VALUE:
if ((ret = apdu_updatePassword(settings, buff)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid password. "), ret);
#endif //DLMS_DEBUG
break;
}
#ifndef DLMS_IGNORE_SERVER
if (ciphered)
{
afu &= ~DLMS_AFU_MISSING_CALLING_AUTHENTICATION_VALUE;
}
#endif //DLMS_IGNORE_SERVER
break;
// 0xBE
case BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_USER_INFORMATION:
//Check result component. Some meters are returning invalid user-information if connection failed.
if (*result != DLMS_ASSOCIATION_RESULT_ACCEPTED
&& *diagnostic != DLMS_SOURCE_DIAGNOSTIC_NONE)
{
if ((ret = apdu_handleResultComponent(*diagnostic)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Invalid result component. "), ret);
#endif //DLMS_DEBUG
}
return ret;
}
if ((ret = apdu_parseUserInformation(settings, buff, ciphered, command)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("parseUserInformation. "), ret);
#endif //DLMS_DEBUG
if (ret == DLMS_ERROR_CODE_INVOCATION_COUNTER_TOO_SMALL ||
ret == DLMS_ERROR_CODE_INVALID_DECIPHERING_ERROR ||
ret == DLMS_ERROR_CODE_INVALID_SECURITY_SUITE)
{
return ret;
}
if (ciphered)
{
ret = DLMS_ERROR_CODE_INVALID_DECIPHERING_ERROR;
}
else
{
//Return confirmed service error.
ret = DLMS_ERROR_CODE_INVALID_TAG;
}
break;
}
break;
case BER_TYPE_CONTEXT: //0x80
if ((ret = apdu_parseProtocolVersion(settings, buff)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("parseProtocolVersion. "), ret);
#endif //DLMS_DEBUG
* diagnostic = 0x80 | DLMS_ACSE_SERVICE_PROVIDER_NO_COMMON_ACSE_VERSION;
*result = DLMS_ASSOCIATION_RESULT_PERMANENT_REJECTED;
return 0;
}
break;
default:
// Unknown tags.
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("Unknown tag. "), -1);
#endif //DLMS_DEBUG
if (buff->position < buff->size)
{
if ((ret = bb_getUInt8(buff, &len)) != 0)
{
break;
}
buff->position = (buff->position + len);
}
break;
}
}
if (ret == 0)
{
#ifndef DLMS_IGNORE_SERVER
if (settings->server && afu != 0 &&
*result == DLMS_ASSOCIATION_RESULT_ACCEPTED &&
!(
afu == DLMS_AFU_MISSING_CALLING_AUTHENTICATION_VALUE &&
settings->authentication == DLMS_AUTHENTICATION_NONE))
{
#ifdef DLMS_DEBUG
switch (afu)
{
case DLMS_AFU_MISSING_SENDER_ACSE_REQUIREMENTS:
svr_notifyTrace(GET_STR_FROM_EEPROM("Sender ACSE requirements is missing."), -1);
break;
case DLMS_AFU_MISSING_MECHANISM_NAME:
svr_notifyTrace(GET_STR_FROM_EEPROM("Mechanism name is missing."), -1);
break;
case DLMS_AFU_MISSING_CALLING_AUTHENTICATION_VALUE:
svr_notifyTrace(GET_STR_FROM_EEPROM("Calling authentication value is missing."), -1);
break;
case DLMS_AFU_MISSING_NONE:
break;
}
#endif //DLMS_DEBUG
* result = DLMS_ASSOCIATION_RESULT_PERMANENT_REJECTED;
*diagnostic = DLMS_SOURCE_DIAGNOSTIC_AUTHENTICATION_FAILURE;
return 0;
}
#endif //DLMS_IGNORE_SERVER
//All meters don't send user-information if connection is failed.
//For this reason result component is check again.
if ((ret = apdu_handleResultComponent(*diagnostic)) != 0)
{
#ifdef DLMS_DEBUG
svr_notifyTrace(GET_STR_FROM_EEPROM("handleResultComponent."), ret);
#endif //DLMS_DEBUG
}
#ifndef DLMS_IGNORE_HIGH_GMAC
//Check that user is not trying to connect without ciphered connection.
if (ret == 0 && settings->expectedSecurityPolicy != 0xFF)
{
if (settings->cipher.security != settings->expectedSecurityPolicy << 4)
{
return DLMS_ERROR_CODE_INVALID_DECIPHERING_ERROR;
}
}
#endif //DLMS_IGNORE_HIGH_GMAC
}
return ret;
}
#ifndef DLMS_IGNORE_SERVER
int apdu_generateAARE(
dlmsSettings* settings,
gxByteBuffer* data,
DLMS_ASSOCIATION_RESULT result,
unsigned char diagnostic,
gxByteBuffer* errorData,
gxByteBuffer* encryptedData,
unsigned char command)
{
int ret;
#if defined(GX_DLMS_BYTE_BUFFER_SIZE_32) || (!defined(GX_DLMS_MICROCONTROLLER) && (defined(_WIN32) || defined(_WIN64) || defined(__linux__)))
uint32_t offset = data->size;
#else
uint16_t offset = data->size;
#endif
// Set AARE tag and length 0x61
bb_setUInt8(data, BER_TYPE_APPLICATION | BER_TYPE_CONSTRUCTED | PDU_TYPE_APPLICATION_CONTEXT_NAME);
// Length is updated later.
bb_setUInt8(data, 0);
if ((ret = apdu_generateApplicationContextName(settings, data)) != 0)
{
return ret;
}
// Result 0xA2
bb_setUInt8(data, BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | BER_TYPE_INTEGER);
bb_setUInt8(data, 3); // len
// Tag
bb_setUInt8(data, BER_TYPE_INTEGER);
// Choice for result (INTEGER, universal)
bb_setUInt8(data, 1); // Len
// ResultValue
bb_setUInt8(data, result);
// SourceDiagnostic
bb_setUInt8(data, 0xA3);
// len
bb_setUInt8(data, 5);
// Tag
if ((diagnostic & 0x80) == 0)
{
bb_setUInt8(data, 0xA1);
}
else
{
bb_setUInt8(data, 0xA2);
diagnostic &= ~0x80;
}
bb_setUInt8(data, 3); // len
bb_setUInt8(data, 2); // Tag
// Choice for result (INTEGER, universal)
bb_setUInt8(data, 1); // Len
// diagnostic
bb_setUInt8(data, diagnostic);
// SystemTitle
#ifndef DLMS_IGNORE_HIGH_GMAC
if (settings->authentication == DLMS_AUTHENTICATION_HIGH_GMAC
|| isCiphered(&settings->cipher))
{
bb_setUInt8(data, BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLED_AP_INVOCATION_ID);
bb_setUInt8(data, 2 + 8);
bb_setUInt8(data, BER_TYPE_OCTET_STRING);
bb_setUInt8(data, 8);
#ifndef DLMS_IGNORE_MALLOC
bb_set(data, settings->cipher.systemTitle.data, 8);
#else
bb_set(data, settings->cipher.systemTitle, 8);
#endif //DLMS_IGNORE_MALLOC
}
#endif //LMS_IGNORE_HIGH_GMAC
//Add CalledAEInvocationId.
#ifndef DLMS_IGNORE_HIGH_GMAC
if (settings->userId != -1 && settings->cipher.security != DLMS_SECURITY_NONE)
#else
if (settings->userId != -1)
#endif
{
bb_setUInt8(data, BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_CALLING_AE_QUALIFIER);
//LEN
bb_setUInt8(data, 3);
bb_setUInt8(data, BER_TYPE_INTEGER);
//LEN
bb_setUInt8(data, 1);
bb_setUInt8(data, (unsigned char)settings->userId);
}
if (settings->authentication > DLMS_AUTHENTICATION_LOW)
{
if (settings->stoCChallenge.size == 0)
{
return DLMS_ERROR_CODE_INVALID_PARAMETER;
}
// Add server ACSE-requirenents field component.
if ((ret = bb_setUInt8(data, 0x88)) != 0 ||
(ret = bb_setUInt8(data, 0x02)) != 0 || // Len.
(ret = bb_setUInt16(data, 0x0780)) != 0 ||
// Add tag.
(ret = bb_setUInt8(data, 0x89)) != 0 ||
(ret = bb_setUInt8(data, 0x07)) != 0 || // Len
(ret = bb_setUInt8(data, 0x60)) != 0 ||
(ret = bb_setUInt8(data, 0x85)) != 0 ||
(ret = bb_setUInt8(data, 0x74)) != 0 ||
(ret = bb_setUInt8(data, 0x05)) != 0 ||
(ret = bb_setUInt8(data, 0x08)) != 0 ||
(ret = bb_setUInt8(data, 0x02)) != 0 ||
(ret = bb_setUInt8(data, settings->authentication)) != 0 ||
// Add tag.
(ret = bb_setUInt8(data, 0xAA)) != 0 ||
(ret = bb_setUInt8(data, (unsigned char)(2 + settings->stoCChallenge.size))) != 0 || // Len
(ret = bb_setUInt8(data, BER_TYPE_CONTEXT)) != 0 ||
(ret = bb_setUInt8(data, (unsigned char)settings->stoCChallenge.size)) != 0 ||
(ret = bb_set(data, settings->stoCChallenge.data, settings->stoCChallenge.size)) != 0)
{
return ret;
}
}
#ifndef DLMS_IGNORE_HIGH_GMAC
if (result == DLMS_ASSOCIATION_RESULT_ACCEPTED || !isCiphered(&settings->cipher))
#endif //DLMS_IGNORE_HIGH_GMAC
{
gxByteBuffer tmp;
unsigned char buff[200];
// Add User Information. Tag 0xBE
bb_setUInt8(data, BER_TYPE_CONTEXT | BER_TYPE_CONSTRUCTED | PDU_TYPE_USER_INFORMATION);
bb_attach(&tmp, buff, 0, sizeof(buff));
#ifndef DLMS_IGNORE_HIGH_GMAC
if (encryptedData != NULL && encryptedData->size != 0)
{
bb_capacity(&tmp, 2 + encryptedData->size);
bb_setUInt8(&tmp, DLMS_COMMAND_GLO_INITIATE_RESPONSE);
hlp_setObjectCount(encryptedData->size, &tmp);
bb_set2(&tmp, encryptedData, 0, encryptedData->size);
}
else
#endif //DLMS_IGNORE_HIGH_GMAC
{
if (errorData != NULL && errorData->size != 0)
{
bb_set2(&tmp, errorData, 0, errorData->size);
}
else
{
if ((ret = apdu_getUserInformation(settings, &tmp, command)) != 0)
{
bb_clear(&tmp);
return ret;
}
}
}
if ((ret = bb_setUInt8(data, (unsigned char)(2 + tmp.size))) != 0 ||
// Coding the choice for user-information (Octet STRING, universal)
(ret = bb_setUInt8(data, BER_TYPE_OCTET_STRING)) != 0 ||
// Length
(ret = bb_setUInt8(data, (unsigned char)tmp.size)) != 0 ||
(ret = bb_set2(data, &tmp, 0, tmp.size)) != 0)
{
return ret;
}
}
return bb_setUInt8ByIndex(data, (offset + 1), (unsigned char)(data->size - offset - 2));
}
#endif //DLMS_IGNORE_SERVER