Program Listing for File secure_application_layer.cpp

Return to documentation for file (src/knx/secure_application_layer.cpp)

#include "config.h"
#ifdef USE_DATASECURE

#include "secure_application_layer.h"
#include "transport_layer.h"
#include "cemi_frame.h"
#include "association_table_object.h"
#include "address_table_object.h"
#include "security_interface_object.h"
#include "device_object.h"
#include "apdu.h"
#include "bau.h"
#include "string.h"
#include "bits.h"

// Select what cipher modes to include. We need AES128-CBC and AES128-CTR modes.
#define CBC 1
#define CTR 1
#define ECB 0
#include "aes.hpp"

static constexpr uint8_t kSecureDataPdu = 0;
static constexpr uint8_t kSecureSyncRequest = 2;
static constexpr uint8_t kSecureSyncResponse = 3;

SecureApplicationLayer::SecureApplicationLayer(DeviceObject& deviceObj, SecurityInterfaceObject& secIfObj, BusAccessUnit& bau):
    ApplicationLayer(bau),
    _secIfObj(secIfObj),
    _deviceObj(deviceObj)
{
}

void SecureApplicationLayer::groupAddressTable(AddressTableObject& addrTable)
{
    _addrTab = &addrTable;
}

/* from transport layer */

void SecureApplicationLayer::dataGroupIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu)
{
    if (_addrTab == nullptr)
        return;

    println("dataGroupIndication");

    if (apdu.type() == SecureService)
    {
        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataGroupIndication(hopType, priority, tsap, plainFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataGroupIndication(hopType, priority, tsap, apdu);
}

void SecureApplicationLayer::dataGroupConfirm(AckType ack, HopCountType hopType, Priority priority,  uint16_t tsap, APDU& apdu, bool status)
{
    println("dataGroupConfirm");

    if (apdu.type() == SecureService)
    {
        // We do not care about confirmations of our sync communication
        if (isSyncService(apdu))
        {
            return;
        }

        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataGroupConfirm(ack, hopType, priority, tsap, plainFrame.apdu(), secCtrl, status);
        }

        return;
    }

    ApplicationLayer::dataGroupConfirm(ack, hopType, priority, tsap, apdu, status);
}

void SecureApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
{
    println("dataBroadcastIndication");

    if (apdu.type() == SecureService)
    {
        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataBroadcastIndication(hopType, priority, source, plainFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataBroadcastIndication(hopType, priority, source, apdu);
}

void SecureApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, bool status)
{
    println("dataBroadcastConfirm");

    if (apdu.type() == SecureService)
    {
        // We do not care about confirmations of our sync communication
        if (isSyncService(apdu))
        {
            return;
        }

        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataBroadcastConfirm(ack, hopType, priority, plainFrame.apdu(), secCtrl, status);
        }

        return;
    }

    ApplicationLayer::dataBroadcastConfirm(ack, hopType, priority, apdu, status);
}

void SecureApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
{
    println("dataSystemBroadcastIndication");

    if (apdu.type() == SecureService)
    {
        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataSystemBroadcastIndication(hopType, priority, source, plainFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataSystemBroadcastIndication(hopType, priority, source, apdu);
}

void SecureApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status)
{
    println("dataSystemBroadcastConfirm");

    if (apdu.type() == SecureService)
    {
        // We do not care about confirmations of our sync communication
        if (isSyncService(apdu))
        {
            return;
        }

        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataSystemBroadcastConfirm(hopType, priority, plainFrame.apdu(), secCtrl, status);
        }

        return;
    }

    ApplicationLayer::dataSystemBroadcastConfirm(hopType, priority, apdu, status);
}

void SecureApplicationLayer::dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
{
    println("dataIndividualIndication");

    if (apdu.type() == SecureService)
    {
        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataIndividualIndication(hopType, priority, source, plainFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataIndividualIndication(hopType, priority, source, apdu);
}

void SecureApplicationLayer::dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t source, APDU& apdu, bool status)
{
    println("dataIndividualConfirm");

    if (apdu.type() == SecureService)
    {
        // We do not care about confirmations of our sync communication
        if (isSyncService(apdu))
        {
            return;
        }

        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataIndividualConfirm(ack, hopType, priority, source, apdu, secCtrl, status);
        }

        return;
    }
    else
    {
        ApplicationLayer::dataIndividualConfirm(ack, hopType, priority, source, apdu, status);
    }
}

void SecureApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu)
{
    println("dataConnectedIndication");

    if (apdu.type() == SecureService)
    {
        // Will be filled in by decodeSecureApdu()
        SecurityControl secCtrl;

        // Decrypt secure APDU
        // Somehow ugly that we need to know the size in advance here at this point
        uint16_t plainApduLength = apdu.length() - 3 - 6 - 4; // secureAdsuLength - sizeof(tpci,apci,scf) - sizeof(seqNum) - sizeof(mac)
        CemiFrame plainFrame(plainApduLength);

        // Decrypt secure APDU
        if (decodeSecureApdu(apdu, plainFrame.apdu(), secCtrl))
        {
            // Process decrypted inner APDU
            ApplicationLayer::dataConnectedIndication(priority, tsap, plainFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataConnectedIndication(priority, tsap, apdu);
}

void SecureApplicationLayer::dataConnectedConfirm(uint16_t tsap)
{
    // Just the confirmation issued by the transport layer in case of T_DATA_CONNECTED
    ApplicationLayer::dataConnectedConfirm(tsap);
}

/* to transport layer */

void SecureApplicationLayer::dataGroupRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, const SecurityControl& secCtrl)
{
    if (_addrTab == nullptr)
        return;

    println("dataGroupRequest");

    if (secCtrl.dataSecurity != DataSecurity::None)
    {
        apdu.frame().sourceAddress(_deviceObj.individualAddress());
        apdu.frame().destinationAddress(_addrTab->getGroupAddress(tsap));
        apdu.frame().addressType(GroupAddress);

        uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
        CemiFrame secureFrame(secureApduLength);

        // create secure APDU
        if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
        {
            ApplicationLayer::dataGroupRequest(ack, hopType, priority, tsap, secureFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataGroupRequest(ack, hopType, priority, tsap, apdu, secCtrl);
}

void SecureApplicationLayer::dataBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl)
{
    println("dataBroadcastRequest");

    if (secCtrl.dataSecurity != DataSecurity::None)
    {
        apdu.frame().sourceAddress(_deviceObj.individualAddress());
        apdu.frame().destinationAddress(0x0000);
        apdu.frame().addressType(GroupAddress);
        apdu.frame().systemBroadcast(Broadcast);

        uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
        CemiFrame secureFrame(secureApduLength);

        // create secure APDU
        if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
        {
            ApplicationLayer::dataBroadcastRequest(ack, hopType, SystemPriority, secureFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl);
}

void SecureApplicationLayer::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, APDU& apdu, const SecurityControl& secCtrl)
{
    println("dataSystemBroadcastRequest");

    if (secCtrl.dataSecurity != DataSecurity::None)
    {
        apdu.frame().sourceAddress(_deviceObj.individualAddress());
        apdu.frame().destinationAddress(0x0000);
        apdu.frame().addressType(GroupAddress);
        apdu.frame().systemBroadcast(SysBroadcast);

        uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
        CemiFrame secureFrame(secureApduLength);

        // create secure APDU
        if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
        {
            ApplicationLayer::dataSystemBroadcastRequest(ack, hopType, SystemPriority, secureFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataSystemBroadcastRequest(ack, hopType, SystemPriority, apdu, secCtrl);
}

void SecureApplicationLayer::dataIndividualRequest(AckType ack, HopCountType hopType, Priority priority, uint16_t destination, APDU& apdu, const SecurityControl& secCtrl)
{
    println("dataIndividualRequest");

    if (secCtrl.dataSecurity != DataSecurity::None)
    {
        apdu.frame().sourceAddress(_deviceObj.individualAddress());
        apdu.frame().destinationAddress(destination);
        apdu.frame().addressType(IndividualAddress);

        uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
        CemiFrame secureFrame(secureApduLength);

        // create secure APDU
        if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
        {
            ApplicationLayer::dataIndividualRequest(ack, hopType, priority, destination, secureFrame.apdu(), secCtrl);
        }

        return;
    }

    ApplicationLayer::dataIndividualRequest(ack, hopType, priority, destination, apdu, secCtrl);
}

void SecureApplicationLayer::dataConnectedRequest(uint16_t tsap, Priority priority, APDU& apdu, const SecurityControl& secCtrl)
{
    println("dataConnectedRequest");

    if (secCtrl.dataSecurity != DataSecurity::None)
    {
        apdu.frame().sourceAddress(_deviceObj.individualAddress());
        apdu.frame().destinationAddress(_transportLayer->getConnectionAddress());
        apdu.frame().addressType(IndividualAddress);

        uint16_t secureApduLength = apdu.length() + 3 + 6 + 4; // 3(TPCI,APCI,SCF) + sizeof(seqNum) + apdu.length() + 4
        CemiFrame secureFrame(secureApduLength);

        // create secure APDU
        if (createSecureApdu(apdu, secureFrame.apdu(), secCtrl))
        {
            ApplicationLayer::dataConnectedRequest(tsap, priority, secureFrame.apdu(), secCtrl);
        }

        return;
    }

    // apdu must be valid until it was confirmed
    ApplicationLayer::dataConnectedRequest(tsap, priority, apdu, secCtrl);
}

void SecureApplicationLayer::encryptAesCbc(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key)
{
    // Use zeroes as IV for first round
    uint8_t zeroIv[16] = {0x00};

    struct AES_ctx ctx;
    AES_init_ctx_iv(&ctx, key, zeroIv);

    // Now encrypt first block B0.
    AES_CBC_encrypt_buffer(&ctx, (uint8_t*) iv, 16);

    // Encrypt remaining buffer
    AES_CBC_encrypt_buffer(&ctx, buffer, bufLen);
}

void SecureApplicationLayer::xcryptAesCtr(uint8_t* buffer, uint16_t bufLen, const uint8_t* iv, const uint8_t* key)
{
    struct AES_ctx ctx;

    AES_init_ctx_iv(&ctx, key, iv);

    AES_CTR_xcrypt_buffer(&ctx, buffer, bufLen);
}

uint32_t SecureApplicationLayer::calcAuthOnlyMac(uint8_t* apdu, uint8_t apduLength, const uint8_t* key, uint8_t* iv, uint8_t* ctr0)
{
    uint16_t bufLen = 2 + apduLength; // 2 bytes for the length field (uint16_t)
    // AES-128 operates on blocks of 16 bytes, add padding
    uint16_t bufLenPadded = (bufLen + 15) / 16 * 16;
    uint8_t buffer[bufLenPadded];
    // Make sure to have zeroes everywhere, because of the padding
    memset(buffer, 0x00, bufLenPadded);

    uint8_t* pBuf = buffer;

    pBuf = pushWord(apduLength, pBuf);
    pBuf = pushByteArray(apdu, apduLength, pBuf);

    encryptAesCbc(buffer, bufLenPadded, iv, key);
    xcryptAesCtr(buffer, 4, ctr0, key); // 4 bytes only for the MAC

    uint32_t mac;
    popInt(mac, &buffer[0]);

    return mac;
}

uint32_t SecureApplicationLayer::calcConfAuthMac(uint8_t* associatedData, uint16_t associatedDataLength,
        uint8_t* apdu, uint8_t apduLength,
        const uint8_t* key, uint8_t* iv)
{
    uint16_t bufLen = 2 + associatedDataLength + apduLength; // 2 bytes for the length field (uint16_t)
    // AES-128 operates on blocks of 16 bytes, add padding
    uint16_t bufLenPadded = (bufLen + 15) / 16 * 16;
    uint8_t buffer[bufLenPadded];
    // Make sure to have zeroes everywhere, because of the padding
    memset(buffer, 0x00, bufLenPadded);

    uint8_t* pBuf = buffer;

    pBuf = pushWord(associatedDataLength, pBuf);
    pBuf = pushByteArray(associatedData, associatedDataLength, pBuf);
    pBuf = pushByteArray(apdu, apduLength, pBuf);

    encryptAesCbc(buffer, bufLenPadded, iv, key);

    uint32_t mac;
    popInt(mac, &buffer[bufLenPadded - 16]); // bufLenPadded has a guaranteed minimum size of 16 bytes

    return mac;
}

void SecureApplicationLayer::block0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t extFrameFormat, uint8_t tpci, uint8_t apci, uint8_t payloadLength)
{
    uint8_t* pBuf = buffer;
    pBuf = pushByteArray(seqNum, 6, pBuf);
    pBuf = pushWord(indSrcAddr, pBuf);
    pBuf = pushWord(dstAddr, pBuf);
    pBuf = pushByte(0x00, pBuf); // FT: frametype
    pBuf = pushByte( (dstAddrIsGroupAddr ? 0x80 : 0x00) | (extFrameFormat & 0xf), pBuf); // AT: address type
    pBuf = pushByte(tpci, pBuf); // TPCI
    pBuf = pushByte(apci, pBuf); // APCI // draft spec shows something different!
    pBuf = pushByte(0x00, pBuf); // Reserved: fixed 0x00 (really?)
    pBuf = pushByte(payloadLength, pBuf); // Payload length
}

void SecureApplicationLayer::blockCtr0(uint8_t* buffer, uint8_t* seqNum, uint16_t indSrcAddr, uint16_t dstAddr)
{
    uint8_t* pBuf = buffer;
    pBuf = pushByteArray(seqNum, 6, pBuf);
    pBuf = pushWord(indSrcAddr, pBuf);
    pBuf = pushWord(dstAddr, pBuf);
    pBuf = pushInt(0x00000000, pBuf);
    pBuf = pushByte(0x01, pBuf);
}

uint16_t SecureApplicationLayer::groupAddressIndex(uint16_t groupAddr)
{
    // Just for safety reasons, we should never get here, because the dataGroupIndication will return already return early without doing anything
    if (_addrTab == nullptr)
        return 0;

    return _addrTab->getTsap(groupAddr);
}

const uint8_t* SecureApplicationLayer::securityKey(uint16_t addr, bool isGroupAddress)
{
    if (isGroupAddress)
    {
        uint16_t gaIndex = groupAddressIndex(addr);

        if (gaIndex > 0)
            return _secIfObj.groupKey(gaIndex);
    }
    else
    {
        uint16_t iaIndex = _secIfObj.indAddressIndex(addr);

        if (iaIndex > 0)
            return _secIfObj.p2pKey(iaIndex);
    }

    return nullptr;
}

// returns next outgoing sequence number for secure communication
uint64_t SecureApplicationLayer::nextSequenceNumber(bool toolAccess)
{
    return toolAccess ? _sequenceNumberToolAccess : _sequenceNumber;
}

// stores next outgoing sequence number for secure communication
void SecureApplicationLayer::updateSequenceNumber(bool toolAccess, uint64_t seqNum)
{
    if (toolAccess)
    {
        _sequenceNumberToolAccess = seqNum;
    }
    else
    {
        _sequenceNumber = seqNum;
    }

    // Also update the properties accordingly
    _secIfObj.setSequenceNumber(toolAccess, seqNum);
}

uint64_t SecureApplicationLayer::lastValidSequenceNumber(bool toolAccess, uint16_t srcAddr)
{
    if (toolAccess)
    {
        // TODO: check if we really have to support multiple tools at the same time
        if (srcAddr == _deviceObj.individualAddress())
            return _sequenceNumberToolAccess;

        return _lastValidSequenceNumberTool;
    }
    else
    {
        return _secIfObj.getLastValidSequenceNumber(srcAddr);
    }

    return 0;
}

void SecureApplicationLayer::updateLastValidSequence(bool toolAccess, uint16_t remoteAddr, uint64_t seqNo)
{
    if (toolAccess)
        // TODO: check if we really have to support multiple tools at the same time
        _lastValidSequenceNumberTool = seqNo;
    else
    {
        _secIfObj.setLastValidSequenceNumber(remoteAddr, seqNo);
    }
}

void SecureApplicationLayer::sendSyncRequest(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, bool systemBcast)
{
    if (secCtrl.dataSecurity != DataSecurity::AuthConf)
    {
        println("sync.req is always sent with auth+conf!");
        return;
    }

    _syncReqBroadcastOutgoing = (dstAddr == 0x0000) && dstAddrIsGroupAddr;

    // use random number in SyncResponse
    uint64_t challenge = getRandomNumber();

    uint8_t asdu[6];
    sixBytesFromUInt64(challenge, &asdu[0]);

    CemiFrame request(2 + 6 + sizeof(asdu) + 4); // 2 bytes (APCI, SCF) + 6 bytes (SeqNum) + 6 bytes (challenge) + 4 bytes (MAC)
    // Note: additional TPCI byte is already handled internally!

    uint8_t tpci = 0;

    if (!_syncReqBroadcastOutgoing)
    {
        if (isConnected())
        {
            tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED)
        }
    }

    print("sendSyncRequest: TPCI: ");
    println(tpci, HEX);

    if (secure(request.data() + APDU_LPDU_DIFF, kSecureSyncRequest, _deviceObj.individualAddress(), dstAddr, dstAddrIsGroupAddr, tpci, asdu, sizeof(asdu), secCtrl, systemBcast))
    {
        println("SyncRequest: ");
        request.apdu().printPDU();

        if (_syncReqBroadcastOutgoing)
        {
            _transportLayer->dataBroadcastRequest(AckType::AckDontCare, HopCountType::NetworkLayerParameter, Priority::SystemPriority, request.apdu());
        }
        else
        {
            // either send on T_DATA_INDIVIDUAL or T_DATA_CONNECTED
            if (isConnected())
            {
                _transportLayer->dataConnectedRequest(getConnectedTsasp(), SystemPriority, request.apdu());
            }
            else
            {
                // Send encrypted SyncResponse message using T_DATA_INDIVIDUAL
                _transportLayer->dataIndividualRequest(AckType::AckDontCare, NetworkLayerParameter, SystemPriority, dstAddr, request.apdu());
            }
        }

        Addr toAddr = _syncReqBroadcastOutgoing ? (Addr)GrpAddr(0) : (Addr)IndAddr(dstAddr);
        _pendingOutgoingSyncRequests.insertOrAssign(toAddr, challenge);
    }
    else
    {
        println("SyncRequest: failure during encryption");
    }
}

void SecureApplicationLayer::sendSyncResponse(uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, uint64_t remoteNextSeqNum, bool systemBcast)
{
    if (secCtrl.dataSecurity != DataSecurity::AuthConf)
    {
        println("sync.res is always sent with auth+conf!");
        return;
    }

    uint64_t ourNextSeqNum = nextSequenceNumber(secCtrl.toolAccess);

    uint8_t asdu[12];
    sixBytesFromUInt64(ourNextSeqNum, &asdu[0]);
    sixBytesFromUInt64(remoteNextSeqNum, &asdu[6]);

    CemiFrame response(2 + 6 + sizeof(asdu) + 4); // 2 bytes (APCI, SCF) + 6 bytes (SeqNum) + 12 bytes + 4 bytes (MAC)
    // Note: additional TPCI byte is already handled internally!

    uint8_t tpci = 0;

    if (!_syncReqBroadcastIncoming)
    {
        if (isConnected())
        {
            tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED)
        }
    }

    print("sendSyncResponse: TPCI: ");
    println(tpci, HEX);

    if (secure(response.data() + APDU_LPDU_DIFF, kSecureSyncResponse, _deviceObj.individualAddress(), dstAddr, dstAddrIsGroupAddr, tpci, asdu, sizeof(asdu), secCtrl, systemBcast))
    {
        _lastSyncRes = millis();

        println("SyncResponse: ");
        response.apdu().printPDU();

        if (_syncReqBroadcastIncoming)
        {
            _transportLayer->dataBroadcastRequest(AckType::AckDontCare, HopCountType::NetworkLayerParameter, Priority::SystemPriority, response.apdu());
        }
        else
        {
            // either send on T_DATA_INDIVIDUAL or T_DATA_CONNECTED
            if (isConnected())
            {
                _transportLayer->dataConnectedRequest(getConnectedTsasp(), SystemPriority, response.apdu());
            }
            else
            {
                // Send encrypted SyncResponse message using T_DATA_INDIVIDUAL
                _transportLayer->dataIndividualRequest(AckType::AckDontCare, NetworkLayerParameter, SystemPriority, dstAddr, response.apdu());
            }
        }
    }
    else
    {
        println("SyncResponse: failure during encryption");
    }
}

void SecureApplicationLayer::receivedSyncRequest(uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, const SecurityControl& secCtrl, uint8_t* seqNum, uint64_t challenge, bool systemBcast)
{
    println("Received SyncRequest:");

    uint64_t nextRemoteSeqNum = sixBytesToUInt64(seqNum);
    uint64_t nextSeqNum = 1 + lastValidSequenceNumber(secCtrl.toolAccess, srcAddr);

    if (nextRemoteSeqNum > nextSeqNum)
    {
        updateLastValidSequence(secCtrl.toolAccess, srcAddr, nextRemoteSeqNum - 1);
        nextSeqNum = nextRemoteSeqNum;
    }

    _syncReqBroadcastIncoming = (dstAddr == 0x0000) && dstAddrIsGroupAddr;

    // Remember challenge for securing the sync.res later
    _pendingIncomingSyncRequests.insertOrAssign(_syncReqBroadcastIncoming ? (Addr) GrpAddr(0) : (Addr) IndAddr(srcAddr), challenge);

    uint16_t toAddr = _syncReqBroadcastIncoming ? dstAddr : srcAddr;
    bool toIsGroupAddress = _syncReqBroadcastIncoming;
    sendSyncResponse(toAddr, toIsGroupAddress, secCtrl, nextSeqNum, systemBcast);
}

void SecureApplicationLayer::receivedSyncResponse(uint16_t remote, const SecurityControl& secCtrl, uint8_t* plainApdu)
{
    println("Received SyncResponse:");

    if (_syncReqBroadcastOutgoing)
    {
        if (_pendingOutgoingSyncRequests.get(GrpAddr(0)) == nullptr)
        {
            println("Cannot handle sync.res without pending sync.req! (broadcast/systembroadcast)");
            return;
        }
    }
    else
    {
        if (_pendingOutgoingSyncRequests.get(IndAddr(remote)) == nullptr)
        {
            println("Cannot handle sync.res without pending sync.req!");
            return;
        }
    }

    // Bytes 0-5 in the "APDU" buffer contain the remote sequence number
    // Bytes 6-11 in the "APDU" buffer contain the local sequence number
    uint64_t remoteSeq = sixBytesToUInt64(plainApdu + 0);
    uint64_t localSeq = sixBytesToUInt64(plainApdu + 6);

    uint64_t last = lastValidSequenceNumber(secCtrl.toolAccess, remote);

    if (remoteSeq - 1 > last)
    {
        //logger.debug("sync.res update {} last valid {} seq -> {}", remote, toolAccess ? "tool access" : "p2p", remoteSeq -1);
        updateLastValidSequence(secCtrl.toolAccess, remote, remoteSeq - 1);
    }

    uint64_t next = nextSequenceNumber(secCtrl.toolAccess);

    if (localSeq > next)
    {
        //logger.debug("sync.res update local next {} seq -> {}", toolAccess ? "tool access" : "p2p", localSeq);
        updateSequenceNumber(secCtrl.toolAccess, localSeq);
    }

    Addr remoteAddr = _syncReqBroadcastOutgoing ? (Addr)GrpAddr(0) : (Addr)IndAddr(remote);
    _pendingOutgoingSyncRequests.erase(remoteAddr);
}

bool SecureApplicationLayer::decrypt(uint8_t* plainApdu, uint16_t plainApduLength, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci, uint8_t* secureAsdu, SecurityControl& secCtrl, bool systemBcast)
{
    const uint8_t* pBuf;
    uint8_t scf;

    pBuf = popByte(scf, secureAsdu);

    bool toolAccess = ((scf & 0x80) == 0x80);
    bool systemBroadcast = ((scf & 0x08) == 0x08);
    uint8_t sai = (scf >> 4) & 0x07; // sai can only be 0x0 (CCM auth only) or 0x1 (CCM with auth+conf), other values are reserved
    bool authOnly = ( sai == 0);
    uint8_t service = (scf & 0x07); // only 0x0 (S-A_Data-PDU), 0x2 (S-A_Sync_Req-PDU) or 0x3 (S-A_Sync_Rsp-PDU) are valid values

    if (systemBroadcast != systemBcast)
    {
        println("SBC flag in SCF does not match actual communication mode!");
    }

    secCtrl.toolAccess = toolAccess;
    secCtrl.dataSecurity = authOnly ? DataSecurity::Auth : DataSecurity::AuthConf;

    bool syncReq = service == kSecureSyncRequest;
    bool syncRes = service == kSecureSyncResponse;

    //const uint8_t* key = dstAddrIsGroupAddr ? securityKey(dstAddr, dstAddrIsGroupAddr) : toolAccess ? toolKey() : securityKey(srcAddr, false);
    const uint8_t* key = dstAddrIsGroupAddr && (dstAddr != 0) ? securityKey(dstAddr, dstAddrIsGroupAddr) : toolAccess ? _secIfObj.toolKey() : securityKey(srcAddr, false);

    if (key == nullptr)
    {
        print("Error: No key found. toolAccess: ");
        println(toolAccess ? "true" : "false");
        return false;
    }

    uint8_t seqNum[6];
    pBuf = popByteArray(seqNum, 6, pBuf);
    uint64_t receivedSeqNumber = sixBytesToUInt64(seqNum);

    // Provide array for KNX serial number if it is a SyncRequest
    // DataService and SyncResponse do not use this variable.
    uint8_t knxSerialNumber[6];

    uint16_t remainingPlainApduLength = plainApduLength;

    if (service == kSecureDataPdu)
    {
        if (srcAddr != _deviceObj.individualAddress())
        {
            uint64_t expectedSeqNumber = lastValidSequenceNumber(toolAccess, srcAddr) + 1;

            if (receivedSeqNumber < expectedSeqNumber)
            {
                // security failure
                print("security failure: received seqNum: ");
                print(receivedSeqNumber, HEX);
                print(" < expected seqNum: ");
                print(expectedSeqNumber, HEX);
                return false;
            }
        }
    }
    else if (syncReq)
    {
        pBuf = popByteArray(knxSerialNumber, 6, pBuf);
        remainingPlainApduLength -= 6;

        // ignore sync.reqs not addressed to us
        if (!memcmp(knxSerialNumber, _deviceObj.propertyData(PID_SERIAL_NUMBER), 6))
        {
            uint8_t emptySerialNumber[6] = {0};

            if (systemBroadcast || dstAddr != _deviceObj.individualAddress() || !memcmp(knxSerialNumber, emptySerialNumber, 6))
                return false;
        }

        // if responded to another request within the last 1 second, ignore
        if ((millis() - _lastSyncRes) < 1000)
        {
            return false;
        }
    }
    else if (syncRes)
    {
        // fetch challenge depending on srcAddr to handle multiple requests
        uint64_t* challenge = _pendingOutgoingSyncRequests.get(IndAddr(srcAddr));

        if (challenge == nullptr)
        {
            println("Cannot find matching challenge for source address!");
            return false;
        }

        uint8_t _challengeSixBytes[6];
        sixBytesFromUInt64(*challenge, _challengeSixBytes);

        // in a sync.res, seq actually contains our challenge from sync.req xored with a random value
        // extract the random value and store it in seqNum to use it for block0 and ctr0
        for (uint8_t i = 0; i < sizeof(seqNum); i++)
        {
            seqNum[i] ^= _challengeSixBytes[i];
        }
    }

    pBuf = popByteArray(plainApdu, remainingPlainApduLength, pBuf);

    // No LTE-HEE for now
    // Data Secure always uses extended frame format with APDU length > 15 octets (long messages), no standard frames
    uint8_t extendedFrameFormat = 0;
    // Clear IV buffer
    uint8_t iv[16] = {0x00};
    // Create first block B0 for AES CBC MAC calculation, used as IV later
    /*
      printHex("seq: ", seqNum, 6);
      printHex("src: ", (uint8_t*) &srcAddr, 2);
      printHex("dst: ", (uint8_t*) &dstAddr, 2);
      print("dstAddrisGroup: ");println(dstAddrIsGroupAddr ? "true" : "false");
    */
    block0(iv, seqNum, srcAddr, dstAddr, dstAddrIsGroupAddr, extendedFrameFormat, tpci | (SecureService >> 8), SecureService & 0x00FF, remainingPlainApduLength);

    // Clear block counter0 buffer
    uint8_t ctr0[16] = {0x00};
    // Create first block for block counter 0
    blockCtr0(ctr0, seqNum, srcAddr, dstAddr);

    uint32_t mac;
    pBuf = popInt(mac, pBuf);

    if (authOnly)
    {
        // APDU is already plain, no decryption needed

        // Only check the MAC
        uint32_t calculatedMac = calcAuthOnlyMac(plainApdu, remainingPlainApduLength, key, iv, ctr0);

        if (calculatedMac != mac)
        {
            // security failure
            print("security failure(auth): calculated MAC: ");
            print(calculatedMac, HEX);
            print(" != received MAC: ");
            print(mac, HEX);
            println("\n");

            return false;
        }

        memcpy(plainApdu, secureAsdu, remainingPlainApduLength);
    }
    else
    {
        // APDU is encrypted and needs decryption

        uint16_t bufLen = 4 + remainingPlainApduLength;
        // AES-128 operates on blocks of 16 bytes, add padding
        uint16_t bufLenPadded = (bufLen + 15) / 16 * 16;
        uint8_t buffer[bufLenPadded];
        //uint8_t buffer[bufLen];
        // Make sure to have zeroes everywhere, because of the padding
        memset(buffer, 0x00, bufLenPadded);

        pushInt(mac, &buffer[0]);
        pushByteArray(plainApdu, remainingPlainApduLength, &buffer[4]); // apdu is still encrypted

        xcryptAesCtr(buffer, bufLenPadded, ctr0, key);
        //xcryptAesCtr(buffer, bufLen, ctr0, key);

        uint32_t decryptedMac;
        popInt(decryptedMac, &buffer[0]);
        popByteArray(plainApdu, remainingPlainApduLength, &buffer[4]); // apdu is now decrypted (overwritten)

        // Do calculations for Auth+Conf
        uint8_t associatedData[syncReq ? 7 : 1];
        associatedData[0] = scf;

        if (syncReq)
        {
            memcpy(&associatedData[1], knxSerialNumber, 6);
        }

        /*
                printHex("APDU--------->", plainApdu, remainingPlainApduLength);
                printHex("Key---------->", key, 16);
                printHex("ASSOC-------->", associatedData, sizeof(associatedData));
        */
        uint32_t calculatedMac = calcConfAuthMac(associatedData, sizeof(associatedData), plainApdu, remainingPlainApduLength, key, iv);

        if (calculatedMac != decryptedMac)
        {
            // security failure
            print("security failure(conf+auth): calculated MAC: ");
            print(calculatedMac, HEX);
            print(" != decrypted MAC: ");
            print(decryptedMac, HEX);
            println("\n");

            return false;
        }

        // prevent a sync.req sent by us to trigger sync notification, this happens if we provide our own tool key
        // for decryption above
        if (syncReq && (srcAddr == _deviceObj.individualAddress()))
            return false;

        if (syncReq)
        {
            uint64_t challenge = sixBytesToUInt64(&plainApdu[0]);
            receivedSyncRequest(srcAddr, dstAddr, dstAddrIsGroupAddr, secCtrl, seqNum, challenge, systemBroadcast);
            return false;
        }
        else if (syncRes)
        {
            receivedSyncResponse(srcAddr, secCtrl, plainApdu);
            return false;
        }
        else
        {
            if (srcAddr == _deviceObj.individualAddress())
            {
                print("Update our next ");
                print(toolAccess ? "tool access" : "");
                print(" seq from ");
                print(srcAddr, HEX);
                print(" -> (+1) ");
                println(receivedSeqNumber, HEX);
                updateSequenceNumber(toolAccess, receivedSeqNumber + 1);
            }
            else
            {
                print("Update last valid ");
                print(toolAccess ? "tool access" : "");
                print(" seq from ");
                print(srcAddr, HEX);
                print(" -> ");
                println(receivedSeqNumber, HEX);
                updateLastValidSequence(toolAccess, srcAddr, receivedSeqNumber);
            }
        }
    }

    return true;
}

bool SecureApplicationLayer::decodeSecureApdu(APDU& secureApdu, APDU& plainApdu, SecurityControl& secCtrl)
{
    // Decode secure APDU

    println("decodeSecureApdu: Secure APDU: ");
    secureApdu.printPDU();

    uint16_t srcAddress = secureApdu.frame().sourceAddress();
    uint16_t dstAddress = secureApdu.frame().destinationAddress();
    bool isDstAddrGroupAddr = secureApdu.frame().addressType() == GroupAddress;
    bool isSystemBroadcast = secureApdu.frame().systemBroadcast();
    uint8_t tpci = secureApdu.frame().data()[TPDU_LPDU_DIFF]; // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI [fixed TPDU_LPDU_DIFF]
    print("decodeSecureApdu: TPCI: ");
    println(tpci, HEX);
    // Note:
    // The TPCI is also included in the MAC calculation to provide authenticity for this field.
    // However, a secure APDU (with a MAC) is only included in transport layer PDUs T_DATA_GROUP, T_DATA_TAG_GROUP, T_DATA_INDIVIDUAL, T_DATA_CONNECTED
    // and not in T_CONNECT, T_DISCONNECT, T_ACK, T_NACK.
    // This means the DATA/CONTROL flag is always 0(=DATA). The flag "NUMBERED" differentiates between T_DATA_INDIVIDUAL and T_DATA_CONNECTED.
    // The seqNumber is only used in T_DATA_CONNECTED and 0 in case of T_DATA_GROUP and T_DATA_GROUP (leaving out T_DATA_TAG_GROUP).
    // Summary: effectively only the "NUMBERED" flag (bit6) and the SeqNumber (bit5-2) are used from transport layer.
    //          In T_DATA_* services the bits1-0 of TPCI octet are used as bits9-8 for APCI type which is fixed to 0x03. SecureService APCI is 0x03F1.

    // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI (fixed APDU_LPDU_DIFF)
    // We are starting from TPCI octet (including): plainApdu.frame().data()+APDU_LPDU_DIFF
    if (decrypt(plainApdu.frame().data() + APDU_LPDU_DIFF, plainApdu.length() + 1, srcAddress, dstAddress, isDstAddrGroupAddr, tpci, secureApdu.data() + 1, secCtrl, isSystemBroadcast))
    {
        println("decodeSecureApdu: Plain APDU: ");
        plainApdu.frame().apdu().printPDU();

        return true;
    }

    return false;
}

bool SecureApplicationLayer::secure(uint8_t* buffer, uint16_t service, uint16_t srcAddr, uint16_t dstAddr, bool dstAddrIsGroupAddr, uint8_t tpci,
                                    uint8_t* apdu, uint16_t apduLength, const SecurityControl& secCtrl, bool systemBcast)
{
    bool toolAccess = secCtrl.toolAccess;
    bool confidentiality = secCtrl.dataSecurity == DataSecurity::AuthConf;

    if (toolAccess)
    {
        if (!confidentiality)
        {
            println("Error: tool access requires auth+conf security");
            return false;
        }

        if (dstAddrIsGroupAddr && dstAddr != 0)
        {
            println("Error: tool access requires individual address");
            return false;
        }
    }

    const uint8_t* key = toolAccess ? _secIfObj.toolKey() : securityKey(dstAddr, dstAddrIsGroupAddr);

    if (key == nullptr)
    {
        print("Error: No key found. toolAccess: ");
        println(toolAccess ? "true" : "false");
        return false;
    }

    bool syncReq = service == kSecureSyncRequest;
    bool syncRes = service == kSecureSyncResponse;

    tpci |= SecureService >> 8; // OR'ing upper two APCI bits
    uint8_t apci = SecureService & 0x00FF;
    uint8_t* pBuf = buffer;
    pBuf = pushByte(tpci, pBuf);                      // TPCI
    pBuf = pushByte(apci, pBuf);                      // APCI

    uint8_t scf;
    scf = service;
    scf |= toolAccess ? 0x80 : 0;
    scf |= confidentiality ? 0x10 : 0;
    scf |= systemBcast ? 0x8 : 0;

    pBuf = pushByte(scf, pBuf);                       // SCF

    uint64_t seqSend = nextSequenceNumber(toolAccess);

    if (seqSend == 0)
        println("0 is not a valid sequence number");

    uint8_t seq[6];
    sixBytesFromUInt64(seqSend, seq);

    if (!syncRes)
        pBuf = pushByteArray(seq, 6, pBuf);          // Sequence Number

    // Prepare associated data depending on service (SyncRequest, SyncResponse or just DataService)
    uint8_t associatedData[syncReq ? 7 : 1];
    associatedData[0] = scf;

    if (syncReq)
    {
        // TODO: NYI lookup serial number of target device for SBC sync.req
        uint8_t remoteSerialNo[6] = {0};

        uint8_t emptySerialNo[6] = {0};
        pBuf = pushByteArray(systemBcast ? remoteSerialNo : emptySerialNo, 6, pBuf);
        pushByteArray(_deviceObj.propertyData(PID_SERIAL_NUMBER), 6, &associatedData[1]);
    }
    else if (syncRes)
    {
        // use random number in SyncResponse
        uint64_t randomNumber = getRandomNumber();
        sixBytesFromUInt64(randomNumber, seq);

        Addr remote = _syncReqBroadcastIncoming ? (Addr)GrpAddr(0) : (Addr)IndAddr(dstAddr);

        // Get challenge from sync.req
        uint64_t* challenge = _pendingIncomingSyncRequests.get(remote);

        if (challenge == nullptr)
        {
            println("Cannot send sync.res without corresponding sync.req");
            return false;
        }
        else
        {
            _pendingIncomingSyncRequests.erase(remote);
        }

        uint8_t challengeSixBytes[6];
        sixBytesFromUInt64(*challenge, challengeSixBytes);
        //printHex("Decrypted challenge: ", challengeSixBytes, 6);

        // Now XOR the new random SeqNum with the challenge from the SyncRequest
        uint8_t rndXorChallenge[6];
        pushByteArray(seq, 6, rndXorChallenge);

        for (uint8_t i = 0; i < sizeof(rndXorChallenge); i++)
        {
            rndXorChallenge[i] ^= challengeSixBytes[i];
        }

        pBuf = pushByteArray(rndXorChallenge, 6, pBuf);
    }

    // No LTE-HEE for now
    // Data Secure always uses extended frame format with APDU length > 15 octets (long messages), no standard frames
    uint8_t extendedFrameFormat = 0;
    // Clear IV buffer
    uint8_t iv[16] = {0x00};
    // Create first block B0 for AES CBC MAC calculation, used as IV later
    /*
        printHex("seq: ", seq, 6);
        printHex("src: ", (uint8_t*) &srcAddr, 2);
        printHex("dst: ", (uint8_t*) &dstAddr, 2);
        print("dstAddrisGroup: ");println(dstAddrIsGroupAddr ? "true" : "false");
    */
    block0(iv, seq, srcAddr, dstAddr, dstAddrIsGroupAddr, extendedFrameFormat, tpci, apci, apduLength);

    // Clear block counter0 buffer
    uint8_t ctr0[16] = {0x00};
    // Create first block for block counter 0
    blockCtr0(ctr0, seq, srcAddr, dstAddr);

    if (confidentiality)
    {
        // Do calculations for Auth+Conf
        /*
                printHex("APDU--------->", apdu, apduLength);
                printHex("Key---------->", key, 16);
                printHex("ASSOC-------->", associatedData, sizeof(associatedData));
        */
        uint32_t mac = calcConfAuthMac(associatedData, sizeof(associatedData), apdu, apduLength, key, iv);

        uint8_t tmpBuffer[4 + apduLength];
        pushInt(mac, tmpBuffer);
        pushByteArray(apdu, apduLength, &tmpBuffer[4]);

        xcryptAesCtr(tmpBuffer, apduLength + 4, ctr0, key); // APDU + MAC (4 bytes)

        pBuf = pushByteArray(tmpBuffer + 4, apduLength, pBuf); // Encrypted APDU
        pBuf = pushByteArray(tmpBuffer + 0, 4, pBuf);          // Encrypted MAC

        //print("MAC(encrypted): ");
        //println(*((uint32_t*)(tmpBuffer + 0)),HEX);
    }
    else
    {
        // Do calculations for AuthOnly
        uint32_t tmpMac = calcAuthOnlyMac(apdu, apduLength, key, iv, ctr0);

        pBuf = pushByteArray(apdu, apduLength, pBuf);          // Plain APDU
        pBuf = pushInt(tmpMac, pBuf);                          // MAC

        print("MAC: ");
        println(tmpMac, HEX);
    }

    return true;
}

bool SecureApplicationLayer::createSecureApdu(APDU& plainApdu, APDU& secureApdu, const SecurityControl& secCtrl)
{
    // Create secure APDU

    println("createSecureApdu: Plain APDU: ");
    plainApdu.printPDU();

    uint16_t srcAddress = plainApdu.frame().sourceAddress();
    uint16_t dstAddress = plainApdu.frame().destinationAddress();
    bool isDstAddrGroupAddr = plainApdu.frame().addressType() == GroupAddress;
    bool isSystemBroadcast = plainApdu.frame().systemBroadcast();
    uint8_t tpci = 0x00;

    if (isConnected())
    {
        tpci |= 0x40 | _transportLayer->getTpciSeqNum(); // get next TPCI sequence number for MAC calculation from TL (T_DATA_CONNECTED)
    }

    print("createSecureApdu: TPCI: ");
    println(tpci, HEX);
    // Note:
    // The TPCI is also included in the MAC calculation to provide authenticity for this field.
    // However, a secure APDU (with a MAC) is only included in transport layer PDUs T_DATA_GROUP, T_DATA_TAG_GROUP, T_DATA_INDIVIDUAL, T_DATA_CONNECTED
    // and not in T_CONNECT, T_DISCONNECT, T_ACK, T_NACK.
    // This means the DATA/CONTROL flag is always 0(=DATA). The flag "NUMBERED" differentiates between T_DATA_INDIVIDUAL and T_DATA_CONNECTED.
    // The seqNumber is only used in T_DATA_CONNECTED and 0 in case of T_DATA_GROUP and T_DATA_GROUP (leaving out T_DATA_TAG_GROUP).
    // Summary: effectively only the "NUMBERED" flag (bit6) and the SeqNumber (bit5-2) are used from transport layer.
    //          In T_DATA_* services the bits1-0 of TPCI octet are used as bits9-8 for APCI type which is fixed to 0x03. SecureService APCI is 0x03F1.

    // FIXME: when cEMI class is refactored, there might be additional info fields in cEMI (fixed APDU_LPDU_DIFF)
    // We are starting from TPCI octet (including): plainApdu.frame().data()+APDU_LPDU_DIFF
    if (secure(secureApdu.frame().data() + APDU_LPDU_DIFF, kSecureDataPdu, srcAddress, dstAddress, isDstAddrGroupAddr, tpci, plainApdu.frame().data() + APDU_LPDU_DIFF, plainApdu.length() + 1, secCtrl, isSystemBroadcast))
    {
        print("Update our next ");
        print(secCtrl.toolAccess ? "tool access" : "");
        print(" seq from ");
        print(srcAddress, HEX);
        print(" -> (+1) ");
        println(nextSequenceNumber(secCtrl.toolAccess), HEX);
        updateSequenceNumber(secCtrl.toolAccess, nextSequenceNumber(secCtrl.toolAccess) + 1);

        println("createSecureApdu: Secure APDU: ");
        secureApdu.frame().apdu().printPDU();

        return true;
    }

    return false;
}

uint64_t SecureApplicationLayer::getRandomNumber()
{
    return 0x000102030405; // TODO: generate random number
}

void SecureApplicationLayer::loop()
{
    // TODO: handle timeout of outgoing sync requests
    //_pendingOutgoingSyncRequests
}

bool SecureApplicationLayer::isSyncService(APDU& secureApdu)
{
    uint8_t scf = *(secureApdu.data() + 1);
    uint8_t service = (scf & 0x07); // only 0x0 (S-A_Data-PDU), 0x2 (S-A_Sync_Req-PDU) or 0x3 (S-A_Sync_Rsp-PDU) are valid values

    if ((service == kSecureSyncRequest) || (service == kSecureSyncResponse))
    {
        return true;
    }

    return false;
}
#endif