Hello Nordic team,
I'm developing a proprietary radio protocol and I'm using CCM peripheral for on-the-fly encryption. The packets seem to be correctly encrypted and decrypted at receiving site. However the MICSTATUS register is almost always set on 0. CRC is always correct. I succeeded to receive packet with valid MIC only when I decrease MAXPACKETSIZE to small values for tests, even when packet was trimmed to smaller size MIC was correct, but this looks like coincidence.
I'm using SEGGER Embedded Studio v6.32a with SEGGER compiler without softdevice. Hardware is nRF52840 Dongles
I have example arrays of data which i'm transmitting in loop:
uint8_t buffer1[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sollicitudin varius nulla, sed accumsan turpis tristique eget. In vestibulum ante sit amet urna pharetra consectetur. Praesent pulvinar sollicitudin augue at pellentesque. Integer lao.";
uint8_t buffer2[] = "Solaris was originally developed as proprietary software, but Sun Microsystems was an early commercial proponent of open source software and in June 2005 released most of the Solaris codebase under the CDDL license.";
uint8_t buffer3[] = "ABCDE";
uint8_t buffer4[] = "Bluetooth 5, IEEE 802.15.4-2006, 2.4 GHz transceiver";
uint8_t buffer5[] = "Why this is so hard?";
uint8_t buffer6[] = "Deoxyribonucleic acid is a polymer composed of two polynucleotide chains that coil around each other to form a double helix.";
Results vary depending on MAXPACKETSIZE register value.
MAXPACKETSIZE = 240
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 243): Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sollicitudin varius nulla, sed accumsan turpis tristique eget. In vestibulum ante sit amet urna pharetra consectetur. Praesent pulvinar sollicitudin augue at pellentesque. I
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 216): Solaris was originally developed as proprietary software, but Sun Microsystems was an early commercial proponent of open source software and in June 2005 released most of the Solaris codebase under the CDDL license.
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 6): ABCDE
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 53): Bluetooth 5, IEEE 802.15.4-2006, 2.4 GHz transceiver
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 21): Why this is so hard?
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 125): Deoxyribonucleic acid is a polymer composed of two polynucleotide chains that coil around each other to form a double helix.
MAXPACKETSIZE = 150
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 243): Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sollicitudin varius nulla, sed accumsan turpis tristique eget. In vestibulum ante s
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 216): Solaris was originally developed as proprietary software, but Sun Microsystems was an early commercial proponent of open source software and in J
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 6): ABCDE
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 53): Bluetooth 5, IEEE 802.15.4-2006, 2.4 GHz transceiver
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 21): Why this is so hard?
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 125): Deoxyribonucleic acid is a polymer composed of two polynucleotide chains that coil around each other to form a double helix.
MAXPACKETSIZE = 50 (all packet has valid MIC, but they are cut to max size)
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 243): Lorem ipsum dolor sit amet, consectetur adipi
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 216): Solaris was originally developed as proprieta
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 6): ABCDE
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 53): Bluetooth 5, IEEE 802.15.4-2006, 2.4 GHz tran
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 21): Why this is so hard?
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 125): Deoxyribonucleic acid is a polymer composed o
MAXPACKETSIZE = 25 (this is the best, only one packet always has invalid MIC)
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 243): Lorem ipsum dolor si
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 216): Solaris was original
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 6): ABCDE
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 53): Bluetooth 5, IEEE 80
Packet received from [5BF942E2][MIC: 0][CRC: 1] (Length: 21): Why this is so hard?
Packet received from [5BF942E2][MIC: 1][CRC: 1] (Length: 125): Deoxyribonucleic aci
I'm using this method to begin transmission (after setModeTx() is code responsible for receiving ACK):
int Radio::transmitPacket(uint32_t radioAddress, const uint8_t *buffer, uint8_t length, uint32_t &timeToAck) {
int returnValue;
// Current packet ID
const uint8_t pid = this->nextPid++;
// Limit payload size to max. packet size (minus headers)
if (length > 243) {
length = 243;
}
returnValue = length;
// Packet type
this->txBuffer[0] = PacketType::DATA;
// Packet length (+ headers length)
this->txBuffer[1] = length + 5;
// Packet ID
this->txBuffer[2] = pid;
// Sender address and prefix is set once in constructor
// Header CRC
//this->txBuffer[8] = this->getCRC(txBuffer, 8);
// Copy data to send
memcpy(&this->txBuffer[8], buffer, length);
// Configure recipient address
NRF_RADIO->BASE0 = radioAddress;
NRF_RADIO->PREFIX0 = (0xAA << RADIO_PREFIX0_AP0_Pos) | (0x00 << RADIO_PREFIX0_AP1_Pos) | (this->broadcastGroup << RADIO_PREFIX0_AP2_Pos);
// Set pointer to unencrypted data buffer for encryption (max 257 bytes)
NRF_CCM->INPTR = (uint32_t)&this->txBuffer;
// Set pointer to encrypted data buffer for transmission
NRF_CCM->OUTPTR = (uint32_t)&this->txBufferEncrypted;
// Configure AES encryption
NRF_CCM->MODE = CCM_MODE_MODE_Encryption << CCM_MODE_MODE_Pos | CCM_MODE_DATARATE_2Mbit << CCM_MODE_DATARATE_Pos | CCM_MODE_LENGTH_Extended << CCM_MODE_LENGTH_Pos;
// Enable shortcut between event ENDKSGEN and task CRYPT
NRF_CCM->SHORTS = (CCM_SHORTS_ENDKSGEN_CRYPT_Enabled << CCM_SHORTS_ENDKSGEN_CRYPT_Pos);
// Start keystream generation
NRF_CCM->TASKS_KSGEN = CCM_TASKS_KSGEN_TASKS_KSGEN_Trigger;
// Set pointer to encrypted data buffer for transmission
NRF_RADIO->PACKETPTR = (uint32_t)&this->txBufferEncrypted;
// Set ACK timeout timer to 2ms
NRF_TIMER2->CC[0] = 2000;
// Clear events
NRF_TIMER2->EVENTS_COMPARE[0] = 0;
NRF_EGU0->EVENTS_TRIGGERED[0] = 0;
NRF_EGU0->EVENTS_TRIGGERED[1] = 0;
// Start transmission
this->setModeTx();
// Start ACK timeout timer
NRF_TIMER2->TASKS_START = 1;
// Listening mode will be called in interrupt after transmission END
// Wait for response or timeout
while (NRF_TIMER2->EVENTS_COMPARE[0] == 0 && NRF_EGU0->EVENTS_TRIGGERED[0] == 0 && NRF_EGU0->EVENTS_TRIGGERED[1] == 0);
//ccmData.counter++;
NRF_TIMER2->TASKS_CAPTURE[0] = 1;
timeToAck = NRF_TIMER2->CC[0];
NRF_TIMER2->TASKS_CLEAR = 1;
// ACK timeout, retry
if (NRF_TIMER2->EVENTS_COMPARE[0] == 1) {
this->setModeIdle();
return RadioError::ERROR_TIMEOUT;
}
// Check packet PID
uint8_t *radioPacket = this->peekCurrentRxPacket();
if (radioPacket[2] != pid) {
return RadioError::ERROR_PID_MISMATCH;
}
// NACK received
if (NRF_EGU0->EVENTS_TRIGGERED[1] == 1) {
return RadioError::ERROR_NACK;
}
// ACK received
if (NRF_EGU0->EVENTS_TRIGGERED[0] == 1) {
return RadioError::ERROR_OK;
}
return returnValue;
}
inline void Radio::setModeIdle() {
if (NRF_RADIO->STATE != RADIO_STATE_STATE_Disabled) {
NRF_RADIO->TASKS_DISABLE = 1;
}
this->radioState = RadioState::IDLE;
while(NRF_RADIO->STATE != RADIO_STATE_STATE_Disabled);
}
inline void Radio::setModeRx() {
if (NRF_RADIO->STATE != RADIO_STATE_STATE_Disabled) {
this->setModeIdle();
}
// Set pointer to encrypted data buffer for decryption
NRF_CCM->INPTR = (uint32_t)&this->rxBufferEncrypted;
// Set pointer to unencrypted data buffer to store
NRF_CCM->OUTPTR = (uint32_t)&this->rxBuffer[this->rxBufferHead];
// Configure AES decryption
NRF_CCM->MODE = CCM_MODE_MODE_Decryption << CCM_MODE_MODE_Pos | CCM_MODE_DATARATE_2Mbit << CCM_MODE_DATARATE_Pos | CCM_MODE_LENGTH_Extended << CCM_MODE_LENGTH_Pos;
// Disable shortcut between event ENDKSGEN and task CRYPT
NRF_CCM->SHORTS = (CCM_SHORTS_ENDKSGEN_CRYPT_Disabled << CCM_SHORTS_ENDKSGEN_CRYPT_Pos);
// Enable PPI channel between EVENTS_ADDRESS and TASKS_CRYPT
NRF_PPI->CHENSET = (PPI_CHENSET_CH25_Set << PPI_CHENSET_CH25_Pos);
// Set pointer to encrypted data buffer for receiving
NRF_RADIO->PACKETPTR = (uint32_t)&this->rxBufferEncrypted;
// Start keystream generation
NRF_CCM->TASKS_KSGEN = CCM_TASKS_KSGEN_TASKS_KSGEN_Trigger;
// Configure device address
NRF_RADIO->BASE0 = this->radioAddress;
NRF_RADIO->PREFIX0 = (0xAA << RADIO_PREFIX0_AP0_Pos) | (0x00 << RADIO_PREFIX0_AP1_Pos) | (this->broadcastGroup << RADIO_PREFIX0_AP2_Pos);
// Enable RADIO in RX mode
NRF_RADIO->TASKS_RXEN = 1;
this->radioState = RadioState::RX;
}
inline void Radio::setModeTx() {
if (NRF_RADIO->STATE != RADIO_STATE_STATE_Disabled) {
this->setModeIdle();
}
// Disable PPI channel between EVENTS_ADDRESS and TASKS_CRYPT
NRF_PPI->CHENCLR = (PPI_CHENSET_CH25_Set << PPI_CHENSET_CH25_Pos);
// Enable RADIO in TX mode
NRF_RADIO->TASKS_TXEN = 1;
this->radioState = RadioState::TX;
}
Radio and CCM init:
Radio::Radio(const uint32_t radioAddress, const BroadcastGroup broadcastGroup): radioAddress(radioAddress), broadcastAddress(0xFFAAFF55), broadcastGroup(broadcastGroup) {
// The peripheral and its registers will be reset to its initial state by switching the peripheral off and then back on again.
NRF_RADIO->POWER = RADIO_POWER_POWER_Disabled << RADIO_POWER_POWER_Pos;
NRF_RADIO->POWER = RADIO_POWER_POWER_Enabled << RADIO_POWER_POWER_Pos;
// Configure radio with 2Mbit Nordic proprietary mode
NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_2Mbit << RADIO_MODE_MODE_Pos;
// Initialize whitening value
NRF_RADIO->DATAWHITEIV = 0x55;
// Configure device address Base0 + Prefix0
NRF_RADIO->BASE0 = radioAddress;
NRF_RADIO->PREFIX0 = (0xAA << RADIO_PREFIX0_AP0_Pos) | (0x00 << RADIO_PREFIX0_AP1_Pos) | (this->broadcastGroup << RADIO_PREFIX0_AP2_Pos);
// Configure broadcast address Base0 + Prefix0
NRF_RADIO->BASE1 = this->broadcastAddress;
// Use logical address 0 (BASE0 + PREFIX0[0])
NRF_RADIO->TXADDRESS = 0 << RADIO_TXADDRESS_TXADDRESS_Pos;
// Use logical address 0 as RX address, and logical address 1 and 2 as broadcast RX address
if (broadcastGroup == 0) {
NRF_RADIO->RXADDRESSES = (RADIO_RXADDRESSES_ADDR0_Enabled << RADIO_RXADDRESSES_ADDR0_Pos) | (RADIO_RXADDRESSES_ADDR1_Enabled << RADIO_RXADDRESSES_ADDR1_Pos);
} else {
NRF_RADIO->RXADDRESSES = (RADIO_RXADDRESSES_ADDR0_Enabled << RADIO_RXADDRESSES_ADDR0_Pos) | (RADIO_RXADDRESSES_ADDR1_Enabled << RADIO_RXADDRESSES_ADDR1_Pos) | (RADIO_RXADDRESSES_ADDR2_Enabled << RADIO_RXADDRESSES_ADDR2_Pos);
}
// Initialize CRC (two bytes)
NRF_RADIO->CRCCNF = (RADIO_CRCCNF_LEN_Two << RADIO_CRCCNF_LEN_Pos) | (RADIO_CRCCNF_SKIPADDR_Skip << RADIO_CRCCNF_SKIPADDR_Pos);
NRF_RADIO->CRCPOLY = 0x0000AAAA;
NRF_RADIO->CRCINIT = 0x12345678;
// Enable fast rampup
NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_DTX_Center << RADIO_MODECNF0_DTX_Pos) | (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
// +8dBm output power, sending packets at 2400MHz
NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos8dBm << RADIO_TXPOWER_TXPOWER_Pos;
// Configure packet with 8-bit S0 and S1, 8-bit length fields and 8-bit preamble.
NRF_RADIO->PCNF0 = (1 << RADIO_PCNF0_S0LEN_Pos) | (8 << RADIO_PCNF0_S1LEN_Pos) | (RADIO_PCNF0_S1INCL_Include << RADIO_PCNF0_S1INCL_Pos) | (8 << RADIO_PCNF0_LFLEN_Pos) | (RADIO_PCNF0_CRCINC_Include << RADIO_PCNF0_CRCINC_Pos) | (RADIO_PCNF0_PLEN_8bit << RADIO_PCNF0_PLEN_Pos);
// Configure maximum payload length of 255 bytes, 5 bytes address, little endian with whitening enabled.
NRF_RADIO->PCNF1 = (255 << RADIO_PCNF1_MAXLEN_Pos) | (0 << RADIO_PCNF1_STATLEN_Pos) | (4 << RADIO_PCNF1_BALEN_Pos) | (RADIO_PCNF1_ENDIAN_Little << RADIO_PCNF1_ENDIAN_Pos) | (RADIO_PCNF1_WHITEEN_Enabled << RADIO_PCNF1_WHITEEN_Pos);
// Configure shortcuts to:
// - start as soon as READY event is received
// - disable radio as soon RX or TX operation is END
// - start RRSI measure on ADDESS event
// - stop RRSI measure as soon RX or TX operation is END
NRF_RADIO->SHORTS = (RADIO_SHORTS_READY_START_Enabled << RADIO_SHORTS_READY_START_Pos) |
(RADIO_SHORTS_END_DISABLE_Enabled << RADIO_SHORTS_END_DISABLE_Pos) |
(RADIO_SHORTS_ADDRESS_RSSISTART_Enabled << RADIO_SHORTS_ADDRESS_RSSISTART_Pos) |
(RADIO_SHORTS_DISABLED_RSSISTOP_Enabled << RADIO_SHORTS_DISABLED_RSSISTOP_Pos);
// Timer 2 in timer mode and 32 bit resolution
NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;
NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
// Configure shortcuts to CLEAR and STOP timer on COMPARE event
NRF_TIMER2->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos) | (TIMER_SHORTS_COMPARE0_STOP_Enabled << TIMER_SHORTS_COMPARE0_STOP_Pos);
// Timer 2 clock is set to 1 MHz (f = 16 MHz / (2 ^ PRESCALER)). Interval between timer tick would be 1 / 1 MHz = 1us
NRF_TIMER2->PRESCALER = 4;
// Confdigure Radio interrupts
NRF_RADIO->INTENSET = (RADIO_INTENSET_END_Enabled << RADIO_INTENSET_END_Pos); // | (RADIO_INTENSET_CRCOK_Enabled << RADIO_INTENSET_CRCOK_Pos);
// Set sender address in tx buffer
this->txBuffer[3] = (this->radioAddress & 0xFF);
this->txBuffer[4] = ((this->radioAddress >> 8) & 0xFF);
this->txBuffer[5] = ((this->radioAddress >> 16) & 0xFF);
this->txBuffer[6] = ((this->radioAddress >> 24) & 0xFF);
// Set sender prefix in tx buffer
this->txBuffer[7] = 0xAA;
// Set last packet id
this->nextPid = 0;
// Init RX queue circular buffer
this->rxBufferHead = 0;
this->rxBufferTail = 0;
this->rxBufferDataSize = 0;
// Set pointer to data buffer
NRF_RADIO->PACKETPTR = (uint32_t)&this->rxBuffer[this->rxBufferHead];
// Build CRC table
this->buildCRCTable();
// Enable encryption module
NRF_CCM->ENABLE = CCM_ENABLE_ENABLE_Enabled << CCM_ENABLE_ENABLE_Pos;
NRF_CCM->MAXPACKETSIZE = 25;
// Pointer to data area used for temporary storage
NRF_CCM->SCRATCHPTR = (uint32_t)&ccmScratchArea;
// Init CCM data
const char key[] = {0x1D, 0xF9, 0x2F, 0xA7, 0x9F, 0x2A, 0x1C, 0xF8, 0xBE, 0x3D, 0x8F, 0xE3, 0xD2, 0x53, 0xDA, 0x07};
const char iv[] = {0x47, 0xC7, 0xCE, 0xD3, 0x2E, 0x92, 0xA4, 0x26};
ccmData.counter = 0;
ccmData.direction = 0;
memcpy(ccmData.key, key, sizeof(ccmData.key));
memcpy(ccmData.iv, iv, sizeof(ccmData.iv));
// Pointer to data structure holding the AES key and the NONCE vector
NRF_CCM->CNFPTR = (uint32_t)&ccmData;
// Enable Radio interrupts
NVIC_SetPriority(RADIO_IRQn, 1);
NVIC_ClearPendingIRQ(RADIO_IRQn);
NVIC_EnableIRQ(RADIO_IRQn);
}
Receiving part:
extern "C" void RADIO_IRQHandler(void) {
if (NRF_RADIO->EVENTS_END) {
// Packet received
if (radio.getRadioState() == RadioState::RX) {
// Packet header
uint8_t *radioPacket = radio.peekCurrentRxPacket();
const PacketType packetType = (PacketType)*radioPacket;
const uint8_t packetLength = *(radioPacket + 1) - 5;
const uint8_t packetId = *(radioPacket + 2);
const uint32_t senderAddress = (*(radioPacket + 6) << 24) | (*(radioPacket + 5) << 16) | (*(radioPacket + 4) << 8) | *(radioPacket + 3);
//const uint8_t senderPrefix = *(radioPacket + 7);
const uint8_t *payloadBuffer = (radioPacket + 8);
//const uint32_t rssi = NRF_RADIO->RSSISAMPLE;
// Calculate header CRC
//const uint8_t crcCheck = radio.getCRC(radioPacket, 8);
const bool crcOk = (/*crcCheck == headerCrc &&*/ NRF_RADIO->CRCSTATUS == RADIO_CRCSTATUS_CRCSTATUS_CRCOk);
if(NRF_CCM->EVENTS_ERROR) logger.error("Decrypt error");
// Get Message integrity check status
const bool integrityCheck = (NRF_CCM->MICSTATUS == (CCM_MICSTATUS_MICSTATUS_CheckPassed << CCM_MICSTATUS_MICSTATUS_Pos));
if (integrityCheck && crcOk) {
logger.info("Packet received from [%X][MIC: %d][CRC: %d] (Length: %d): %s", senderAddress, integrityCheck, crcOk, packetLength, payloadBuffer);
} else {
logger.warning("Packet received from [%X][MIC: %d][CRC: %d] (Length: %d): %s", senderAddress, integrityCheck, crcOk, packetLength, payloadBuffer);
}
// Add RSSI value to packet
*(radioPacket + 256) = (uint8_t)(NRF_RADIO->RSSISAMPLE & 0xFF);
// Add "broadcast" flag to packet
*(radioPacket + 257) = (bool)(NRF_RADIO->RXMATCH != 0);
// Free fields
//*(radioPacket + 258) = 0x00;
//*(radioPacket + 259) = 0x00;
radio.sendAck(senderAddress, packetId);
return;
// If packet was send to device address, not brodcast
if (NRF_RADIO->RXMATCH == 0) {
if (packetType == PacketType::ACK) {
NRF_EGU0->TASKS_TRIGGER[0] = EGU_TASKS_TRIGGER_TASKS_TRIGGER_Trigger;
} else if (packetType == PacketType::NACK) {
NRF_EGU0->TASKS_TRIGGER[1] = EGU_TASKS_TRIGGER_TASKS_TRIGGER_Trigger;
} else if (packetType == PacketType::DATA) {
if (radio.isRxBufferFull()) {
// Send NACK if process queue is full
radio.sendNack(senderAddress, NackReason::DEVICE_BUSY, packetId);
} else if (!crcOk) {
// Send NACK if packet has incorrect CRC
radio.sendNack(senderAddress, NackReason::CRC_ERROR, packetId);
} else if (!integrityCheck) {
// Send NACK if packet has failed integrity check
radio.sendNack(senderAddress, NackReason::MIC_ERROR, packetId);
} else {
// Add packet to process queue
radio.advanceRxBufferPointer();
// Send ACK if packet is correct
radio.sendAck(senderAddress, packetId);
}
}
} else {
if (crcOk && integrityCheck && radio.isRxBufferFull() == false) {
// Add packet to process queue
radio.advanceRxBufferPointer();
}
radio.startReceiving();
}
// Packet sended
} else if (radio.getRadioState() == RadioState::TX) {
// Set TX end event
NRF_EGU0->EVENTS_TRIGGERED[2] = EGU_TASKS_TRIGGER_TASKS_TRIGGER_Trigger;
// Return to receiving mode
radio.startReceiving();
}
NRF_RADIO->EVENTS_END = 0;
}
}
What can cause such behavior? How I can get correct MIC status? I will be very grateful for every hint.