#include "LoRaWAN.h" #include #if !defined(RADIOLIB_EXCLUDE_LORAWAN) // flag to indicate whether we have received a downlink static volatile bool downlinkReceived = false; // interrupt service routine to handle downlinks automatically #if defined(ESP8266) || defined(ESP32) IRAM_ATTR #endif static void LoRaWANNodeOnDownlink(void) { downlinkReceived = true; } // flag to indicate whether channel scan operation is complete static volatile bool scanFlag = false; // interrupt service routine to handle downlinks automatically #if defined(ESP8266) || defined(ESP32) IRAM_ATTR #endif static void LoRaWANNodeOnChannelScan(void) { scanFlag = true; } LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) { this->phyLayer = phy; this->band = band; this->FSK = false; } void LoRaWANNode::wipe() { Module* mod = this->phyLayer->getMod(); mod->hal->wipePersistentStorage(); } int16_t LoRaWANNode::begin() { int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); // check the magic value Module* mod = this->phyLayer->getMod(); if(mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) { // the magic value is not set, user will have to do perform the join procedure return(RADIOLIB_ERR_NETWORK_NOT_JOINED); } // pull all needed information from persistent storage this->devAddr = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); return(RADIOLIB_ERR_NONE); } int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) { // check if we actually need to send the join request Module* mod = this->phyLayer->getMod(); if(!force && (mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) { // the device has joined already, we can just pull the data from persistent storage return(this->begin()); } // set the physical layer configuration int16_t state = this->setPhyProperties(); RADIOLIB_ASSERT(state); // get dev nonce from persistent storage and increment it uint16_t devNonce = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID); mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1); // build the join-request message uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; // set the packet fields joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI); LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI); LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce); // add the authentication code uint32_t mic = this->generateMIC(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), nwkKey); LoRaWANNode::hton(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic); // send it state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); RADIOLIB_ASSERT(state); // set the function that will be called when the reply is received this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); // downlink messages are sent with inverted IQ // TODO use downlink() for this if(!this->FSK) { state = this->phyLayer->invertIQ(true); RADIOLIB_ASSERT(state); } // start receiving uint32_t start = mod->hal->millis(); downlinkReceived = false; state = this->phyLayer->startReceive(); RADIOLIB_ASSERT(state); // wait for the reply or timeout while(!downlinkReceived) { if(mod->hal->millis() - start >= RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS + 2000) { downlinkReceived = false; if(!this->FSK) { this->phyLayer->invertIQ(false); } return(RADIOLIB_ERR_RX_TIMEOUT); } } // we have a message, reset the IQ inversion downlinkReceived = false; this->phyLayer->clearPacketReceivedAction(); if(!this->FSK) { state = this->phyLayer->invertIQ(false); RADIOLIB_ASSERT(state); } // build the buffer for the reply data uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; // check received length size_t lenRx = this->phyLayer->getPacketLength(true); if((lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) && (lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) { RADIOLIB_DEBUG_PRINTLN("joinAccept reply length mismatch, expected %luB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, lenRx); return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } // read the packet state = this->phyLayer->readData(joinAcceptMsgEnc, lenRx); // downlink frames are sent without CRC, which will raise error on SX127x // we can ignore that error if(state != RADIOLIB_ERR_LORA_HEADER_DAMAGED) { RADIOLIB_ASSERT(state); } // check reply message type if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) { RADIOLIB_DEBUG_PRINTLN("joinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]); return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } // decrypt the join accept message // this is done by encrypting again in ECB mode // the first byte is the MAC header which is not encrypted uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; joinAcceptMsg[0] = joinAcceptMsgEnc[0]; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]); //Module::hexdump(joinAcceptMsg, lenRx); // check LoRaWAN revision (the MIC verification depends on this) uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS]; if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) { // 1.1 version, first we need to derive the join accept integrity key uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY; LoRaWANNode::hton(&keyDerivationBuff[1], devEUI); RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->jSIntKey); // prepare the buffer for MIC calculation uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE; LoRaWANNode::hton(&micBuff[1], joinEUI); LoRaWANNode::hton(&micBuff[9], devNonce); memcpy(&micBuff[11], joinAcceptMsg, lenRx); //Module::hexdump(micBuff, lenRx + 11); if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) { return(RADIOLIB_ERR_CRC_MISMATCH); } } else { // 1.0 version if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) { return(RADIOLIB_ERR_CRC_MISMATCH); } } // parse the contents uint32_t joinNonce = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); uint32_t homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); this->devAddr = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]); this->rxDelays[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]*1000; if(this->rxDelays[0] == 0) { this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS; } this->rxDelays[1] = this->rxDelays[0] + 1000; // process CFlist if present if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) { if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) { // list of frequencies for(uint8_t i = 0; i < 5; i++) { uint32_t freq = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + 3*i], 3); availableChannelsFreq[i] = (float)freq/10000.0; RADIOLIB_DEBUG_PRINTLN("Channel %d frequency = %f MHz", i, availableChannelsFreq[i]); } } else { // TODO list of masks RADIOLIB_DEBUG_PRINTLN("CFlist masks not supported (yet)"); return(RADIOLIB_ERR_UNSUPPORTED); } } // prepare buffer for key derivation uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3); // check protocol version (1.0 vs 1.1) if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) { // 1.1 version, derive the keys LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI); LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; //Module::hexdump(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE); RadioLibAES128Instance.init(appKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); //Module::hexdump(this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey); //Module::hexdump(this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->sNwkSIntKey); //Module::hexdump(this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey); //Module::hexdump(this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); // enqueue the RekeyInd MAC command to be sent in the next uplink this->rev = 1; LoRaWANMacCommand_t cmd = { .cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, .len = sizeof(uint8_t), .payload = { this->rev }, .repeat = RADIOLIB_LORAWAN_ADR_ACK_LIMIT, }; state = pushMacCommand(&cmd, &this->commandsUp); RADIOLIB_ASSERT(state); } else { // 1.0 version, just derive the keys this->rev = 0; LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3); LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY; RadioLibAES128Instance.init(nwkKey); RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey); memcpy(this->sNwkSIntKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); memcpy(this->nwkSEncKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } // save the device address mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID, this->devAddr); // update the keys mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); // all complete, reset device counters and set the magic number mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, 0); mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC); return(RADIOLIB_ERR_NONE); } int16_t LoRaWANNode::beginAPB(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey) { this->devAddr = addr; memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); memcpy(this->nwkSEncKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE); if(fNwkSIntKey) { this->rev = 1; memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } else { memcpy(this->fNwkSIntKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE); } if(sNwkSIntKey) { memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } // set the physical layer configuration int16_t state = this->setPhyProperties(); return(state); } #if defined(RADIOLIB_BUILD_ARDUINO) int16_t LoRaWANNode::uplink(String& str, uint8_t port) { return(this->uplink(str.c_str(), port)); } #endif int16_t LoRaWANNode::uplink(const char* str, uint8_t port) { return(this->uplink((uint8_t*)str, strlen(str), port)); } int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) { // check destination port if(port > 0xDF) { return(RADIOLIB_ERR_INVALID_PORT); } // check if there are some MAC commands to piggyback size_t foptsLen = 0; if(this->commandsUp.numCommands > 0) { // there are, assume the maximum possible FOpts len for buffer allocation foptsLen = 15; } // check maximum payload len as defined in phy if(len > this->band->payloadLenMax[this->dataRate]) { return(RADIOLIB_ERR_PACKET_TOO_LONG); } // check if sufficient time has elapsed since the last uplink Module* mod = this->phyLayer->getMod(); if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) { // not enough time elapsed since the last uplink, we may still be in an RX window return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); } // build the uplink message // the first 16 bytes are reserved for MIC calculation blocks size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen); #if defined(RADIOLIB_STATIC_ONLY) uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; #else uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen]; #endif // set the packet fields uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); // TODO implement adaptive data rate // foptslen will be added later uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; // get frame counter from persistent storage uint32_t fcnt = mod->hal->getPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1; mod->hal->setPersistentParameter(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt); LoRaWANNode::hton(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt); // check if we have some MAC command to append // TODO implement appending multiple MAC commands LoRaWANMacCommand_t cmd = { 0 }; if(popMacCommand(&cmd, &this->commandsUp) == RADIOLIB_ERR_NONE) { // we do, add it to fopts uint8_t foptsBuff[RADIOLIB_AES128_BLOCK_SIZE]; foptsBuff[0] = cmd.cid; for(size_t i = 1; i < cmd.len; i++) { foptsBuff[i] = cmd.payload[i]; } foptsLen = 1 + cmd.len; uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen); uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen; // encrypt it processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true); } // set the port uplinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(foptsLen)] = port; // select encryption key based on the target port uint8_t* encKey = this->appSKey; if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { encKey = this->nwkSEncKey; } // encrypt the frame payload processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true); // create blocks for MIC calculation uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK; LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt); block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = uplinkMsgLen - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); // TODO implement confirmed frames block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->dataRate; block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->chIndex; //Module::hexdump(uplinkMsg, uplinkMsgLen); // calculate authentication codes memcpy(uplinkMsg, block1, RADIOLIB_AES128_BLOCK_SIZE); uint32_t micS = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->sNwkSIntKey); memcpy(uplinkMsg, block0, RADIOLIB_AES128_BLOCK_SIZE); uint32_t micF = this->generateMIC(uplinkMsg, uplinkMsgLen - sizeof(uint32_t), this->fNwkSIntKey); // check LoRaWAN revision if(this->rev == 1) { uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0); LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic); } else { LoRaWANNode::hton(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF); } //Module::hexdump(uplinkMsg, uplinkMsgLen); // send it (without the MIC calculation blocks) uint32_t txStart = mod->hal->millis(); uint32_t timeOnAir = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000; int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS); #if !defined(RADIOLIB_STATIC_ONLY) delete[] uplinkMsg; #endif RADIOLIB_ASSERT(state); // set the timestamp so that we can measure when to start receiving this->rxDelayStart = txStart + timeOnAir; return(RADIOLIB_ERR_NONE); } #if defined(RADIOLIB_BUILD_ARDUINO) int16_t LoRaWANNode::downlink(String& str) { int16_t state = RADIOLIB_ERR_NONE; // build a temporary buffer // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL size_t length = 0; uint8_t data[251]; // wait for downlink state = this->downlink(data, &length); if(state == RADIOLIB_ERR_NONE) { // add null terminator data[length] = '\0'; // initialize Arduino String class str = String((char*)data); } return(state); } #endif int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) { // check if there are any upcoming Rx windows Module* mod = this->phyLayer->getMod(); const uint32_t scanGuard = 500; if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) { // time since last Tx is greater than RX2 delay + some guard period // we have nothing to downlink return(RADIOLIB_ERR_NO_RX_WINDOW); } // downlink messages are sent with inverted IQ int16_t state = RADIOLIB_ERR_UNKNOWN; if(!this->FSK) { state = this->phyLayer->invertIQ(true); RADIOLIB_ASSERT(state); } // calculate the channel scanning timeout // according to the spec, this must be at least enough time to effectively detect a preamble uint32_t scanTimeout = this->phyLayer->getTimeOnAir(0)/1000; // set up everything for channel scan downlinkReceived = false; scanFlag = false; bool packetDetected = false; this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan); // perform listening in the two Rx windows for(uint8_t i = 0; i < 2; i++) { // wait for the start of the Rx window // the waiting duration is shortened a bit to cover any possible timing errors uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart); if(waitLen > scanGuard) { waitLen -= scanGuard; } mod->hal->delay(waitLen); // wait until we get a preamble uint32_t scanStart = mod->hal->millis(); while((mod->hal->millis() - scanStart) < (scanTimeout + scanGuard)) { // check channel detection timeout state = this->phyLayer->startChannelScan(); RADIOLIB_ASSERT(state); // wait with some timeout, though it should not be hit uint32_t cadStart = mod->hal->millis(); while(!scanFlag) { mod->hal->yield(); if(mod->hal->millis() - cadStart >= 3000) { // timed out, stop waiting break; } } // check the scan result scanFlag = false; state = this->phyLayer->getChannelScanResult(); if((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { packetDetected = true; break; } } // check if we have a packet if(packetDetected) { break; } else if(i == 0) { // nothing in the first window, configure for the second state = this->phyLayer->setFrequency(this->band->backupChannel.freqStart); RADIOLIB_ASSERT(state); DataRate_t datr; findDataRate(RADIOLIB_LORAWAN_DATA_RATE_UNUSED, &datr, &this->band->backupChannel); state = this->phyLayer->setDataRate(datr); RADIOLIB_ASSERT(state); } } // check if we received a packet at all if(!packetDetected) { this->phyLayer->standby(); if(!this->FSK) { this->phyLayer->invertIQ(false); } // restore the original uplink channel this->configureChannel(0, this->dataRate); return(RADIOLIB_ERR_RX_TIMEOUT); } // channel scan is finished, swap the actions this->phyLayer->clearChannelScanAction(); downlinkReceived = false; this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink); // start receiving state = this->phyLayer->startReceive(); RADIOLIB_ASSERT(state); // wait for reception with some timeout uint32_t rxStart = mod->hal->millis(); while(!downlinkReceived) { mod->hal->yield(); // let's hope 30 seconds is long enough timeout if(mod->hal->millis() - rxStart >= 30000) { // timed out this->phyLayer->standby(); if(!this->FSK) { this->phyLayer->invertIQ(false); } return(RADIOLIB_ERR_RX_TIMEOUT); } } // we have a message, clear actions, go to standby and reset the IQ inversion downlinkReceived = false; this->phyLayer->standby(); this->phyLayer->clearPacketReceivedAction(); if(!this->FSK) { state = this->phyLayer->invertIQ(false); RADIOLIB_ASSERT(state); } // get the packet length size_t downlinkMsgLen = this->phyLayer->getPacketLength(); // check the minimum required frame length // an extra byte is subtracted because downlink frames may not have a port if(downlinkMsgLen < RADIOLIB_LORAWAN_FRAME_LEN(0, 0) - 1 - RADIOLIB_AES128_BLOCK_SIZE) { RADIOLIB_DEBUG_PRINTLN("Downlink message too short (%lu bytes)", downlinkMsgLen); return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } // build the buffer for the downlink message // the first 16 bytes are reserved for MIC calculation block #if !defined(RADIOLIB_STATIC_ONLY) uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen]; #else uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; #endif // set the MIC calculation block // TODO implement confirmed frames memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK; downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t); // read the data state = this->phyLayer->readData(&downlinkMsg[RADIOLIB_AES128_BLOCK_SIZE], downlinkMsgLen); // downlink frames are sent without CRC, which will raise error on SX127x // we can ignore that error if(state == RADIOLIB_ERR_LORA_HEADER_DAMAGED) { state = RADIOLIB_ERR_NONE; } // get the frame counter and set it to the MIC calculation block // TODO this will not handle overflow into 32-bits! // TODO cache the ADR bit? uint16_t fcnt = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt); //Module::hexdump(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); if(state != RADIOLIB_ERR_NONE) { #if !defined(RADIOLIB_STATIC_ONLY) delete[] downlinkMsg; #endif return(state); } // check the MIC if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { return(RADIOLIB_ERR_CRC_MISMATCH); } // check the address uint32_t addr = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]); if(addr != this->devAddr) { RADIOLIB_DEBUG_PRINTLN("Device address mismatch, expected 0x%08X, got 0x%08X", this->devAddr, addr); return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } // check fopts len uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; if(foptsLen > 0) { // there are some Fopts, decrypt them uint8_t fopts[RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK]; // according to the specification, the last two arguments should be 0x00 and false, // but that will fail even for LoRaWAN 1.1.0 server processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true); //Module::hexdump(fopts, foptsLen); // process the MAC command(s) int8_t remLen = foptsLen; uint8_t* foptsPtr = fopts; while(remLen > 0) { LoRaWANMacCommand_t cmd = { .cid = *foptsPtr, .len = (uint8_t)(remLen - 1), .payload = { 0 }, }; memcpy(cmd.payload, foptsPtr + 1, cmd.len); // try to process the mac command // TODO how to handle incomplete commands? size_t processedLen = execMacCommand(&cmd) + 1; // processing succeeded, move in the buffer to the next command remLen -= processedLen; foptsPtr += processedLen; } } // fopts are processed or not present, check if there is payload int payLen = downlinkMsgLen - 8 - foptsLen - sizeof(uint32_t); if(payLen <= 0) { // no payload *len = 0; return(RADIOLIB_ERR_NONE); } // there is payload, and so there should be a port too // TODO pass the port? *len = payLen - 1; processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true); #if !defined(RADIOLIB_STATIC_ONLY) delete[] downlinkMsg; #endif return(state); } void LoRaWANNode::setDeviceStatus(uint8_t battLevel) { this->battLevel = battLevel; } void LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span) { uint8_t dataRateBand = span->dataRates[dr]; this->dataRate = dr; if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) { if(span->dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { dataRateBand = span->dataRates[i]; this->dataRate = i; break; } } } if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) { datr->fsk.bitRate = 50; datr->fsk.freqDev = 25; } else { uint8_t bw = dataRateBand & 0x03; switch(bw) { case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ): datr->lora.bandwidth = 125.0; break; case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ): datr->lora.bandwidth = 250.0; break; case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ): datr->lora.bandwidth = 500.0; break; default: datr->lora.bandwidth = 125.0; } datr->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6; } } int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) { // find the span based on the channel ID uint8_t span = 0; uint8_t spanChannelId = 0; bool found = false; for(uint8_t chanCtr = 0; span < this->band->numChannelSpans; span++) { for(; spanChannelId < this->band->defaultChannels[span].numChannels; spanChannelId++) { if(chanCtr >= chan) { found = true; break; } chanCtr++; } if(found) { break; } } if(!found) { return(RADIOLIB_ERR_INVALID_CHANNEL); } this->chIndex = chan; // set the frequency float freq = this->band->defaultChannels[span].freqStart + this->band->defaultChannels[span].freqStep * (float)spanChannelId; int state = this->phyLayer->setFrequency(freq); RADIOLIB_ASSERT(state); // set the data rate DataRate_t datr; findDataRate(dr, &datr, &this->band->defaultChannels[span]); state = this->phyLayer->setDataRate(datr); return(state); } uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { if((msg == NULL) || (len == 0)) { return(0); } RadioLibAES128Instance.init(key); uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE]; RadioLibAES128Instance.generateCMAC(msg, len, cmac); return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24); } bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { if((msg == NULL) || (len < sizeof(uint32_t))) { return(0); } // extract MIC from the message uint32_t micReceived = LoRaWANNode::ntoh(&msg[len - sizeof(uint32_t)]); // calculate the expected value and compare uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key); if(micCalculated != micReceived) { RADIOLIB_DEBUG_PRINTLN("MIC mismatch, expected %08x, got %08x", micCalculated, micReceived); return(false); } return(true); } int16_t LoRaWANNode::setPhyProperties() { // set the physical layer configuration // TODO select channel span based on channel ID // TODO select channel randomly uint8_t channelId = 0; int16_t state = RADIOLIB_ERR_NONE; if(this->FSK) { state = this->phyLayer->setFrequency(this->band->fskFreq); RADIOLIB_ASSERT(state); DataRate_t dr; dr.fsk.bitRate = 50; dr.fsk.freqDev = 25; state = this->phyLayer->setDataRate(dr); RADIOLIB_ASSERT(state); state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); RADIOLIB_ASSERT(state); state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); } else { state = this->configureChannel(channelId, this->band->defaultChannels[0].joinRequestDataRate); } RADIOLIB_ASSERT(state); // set the maximum power supported by both the module and the band int8_t pwr = this->band->powerMax; state = RADIOLIB_ERR_INVALID_OUTPUT_POWER; while(state == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { // go from the highest power in band and lower it until we hit one supported by the module state = this->phyLayer->setOutputPower(pwr--); } RADIOLIB_ASSERT(state); uint8_t syncWord[3] = { 0 }; uint8_t syncWordLen = 0; size_t preLen = 0; if(this->FSK) { preLen = 8*RADIOLIB_LORAWAN_GFSK_PREAMBLE_LEN; syncWord[0] = (uint8_t)(RADIOLIB_LORAWAN_GFSK_SYNC_WORD >> 16); syncWord[1] = (uint8_t)(RADIOLIB_LORAWAN_GFSK_SYNC_WORD >> 8); syncWord[2] = (uint8_t)RADIOLIB_LORAWAN_GFSK_SYNC_WORD; syncWordLen = 3; } else { preLen = RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN; syncWord[0] = RADIOLIB_LORAWAN_LORA_SYNC_WORD; syncWordLen = 1; } state = this->phyLayer->setSyncWord(syncWord, syncWordLen); RADIOLIB_ASSERT(state); state = this->phyLayer->setPreambleLength(preLen); return(state); } int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen) { // build the command size_t macReqLen = 1 + payloadLen; #if !defined(RADIOLIB_STATIC_ONLY) uint8_t* macReqBuff = new uint8_t[macReqLen]; #else uint8_t macReqBuff[RADIOLIB_STATIC_ARRAY_SIZE]; #endif macReqBuff[0] = cid; memcpy(&macReqBuff[1], payload, payloadLen); // uplink it int16_t state = this->uplink(macReqBuff, macReqLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); #if !defined(RADIOLIB_STATIC_ONLY) delete[] macReqBuff; #endif RADIOLIB_ASSERT(state); // build the reply buffer size_t macRplLen = 1 + replyLen; #if !defined(RADIOLIB_STATIC_ONLY) uint8_t* macRplBuff = new uint8_t[this->band->payloadLenMax[this->dataRate]]; #else uint8_t macRplBuff[RADIOLIB_STATIC_ARRAY_SIZE]; #endif // wait for reply from the server size_t rxRplLen = 0; state = this->downlink(macRplBuff, &rxRplLen); if(state != RADIOLIB_ERR_NONE) { #if !defined(RADIOLIB_STATIC_ONLY) delete[] macRplBuff; #endif return(state); } //Module::hexdump(macRplBuff, rxRplLen); // check the length - it may be longer than expected // if the server decided to append more MAC commands, but never shorter // TODO how to handle the additional command(s)? if(rxRplLen < macRplLen) { #if !defined(RADIOLIB_STATIC_ONLY) delete[] macRplBuff; #endif return(RADIOLIB_ERR_DOWNLINK_MALFORMED); } // check the CID if(macRplBuff[0] != cid) { #if !defined(RADIOLIB_STATIC_ONLY) delete[] macRplBuff; #endif return(RADIOLIB_ERR_INVALID_CID); } // copy the data memcpy(reply, &macRplBuff[1], replyLen); #if !defined(RADIOLIB_STATIC_ONLY) delete[] macRplBuff; #endif return(state); } int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue) { if(queue->numCommands >= RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE) { return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); } memcpy(&queue->commands[queue->numCommands], cmd, sizeof(LoRaWANMacCommand_t)); /*RADIOLIB_DEBUG_PRINTLN("push MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ", queue->commands[queue->numCommands - 1].cid, queue->commands[queue->numCommands - 1].len, queue->commands[queue->numCommands - 1].payload[0], queue->commands[queue->numCommands - 1].payload[1], queue->commands[queue->numCommands - 1].payload[2], queue->commands[queue->numCommands - 1].payload[3], queue->commands[queue->numCommands - 1].payload[4], queue->commands[queue->numCommands - 1].repeat);*/ queue->numCommands++; return(RADIOLIB_ERR_NONE); } int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force) { if(queue->numCommands == 0) { return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY); } if(cmd) { /*RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ", queue->commands[queue->numCommands - 1].cid, queue->commands[queue->numCommands - 1].len, queue->commands[queue->numCommands - 1].payload[0], queue->commands[queue->numCommands - 1].payload[1], queue->commands[queue->numCommands - 1].payload[2], queue->commands[queue->numCommands - 1].payload[3], queue->commands[queue->numCommands - 1].payload[4], queue->commands[queue->numCommands - 1].repeat);*/ memcpy(cmd, &queue->commands[queue->numCommands - 1], sizeof(LoRaWANMacCommand_t)); } if((!force) && (queue->commands[queue->numCommands - 1].repeat > 0)) { queue->commands[queue->numCommands - 1].repeat--; } else { queue->commands[queue->numCommands - 1].repeat = 0; queue->numCommands--; } return(RADIOLIB_ERR_NONE); } size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) { //RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len); switch(cmd->cid) { case(RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS): { // set the uplink reply cmd->len = 2; cmd->payload[1] = this->battLevel; int8_t snr = this->phyLayer->getSNR(); cmd->payload[0] = snr & 0x3F; // push it to the uplink queue pushMacCommand(cmd, &this->commandsUp); return(0); } break; case(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND): { // TODO verify the actual server version here // stop sending the ReKey MAC command popMacCommand(NULL, &this->commandsUp, true); return(1); } break; } return(0); } void LoRaWANNode::processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter) { // figure out how many encryption blocks are there size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE; if(len % RADIOLIB_AES128_BLOCK_SIZE) { numBlocks++; } // generate the encryption blocks uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC; encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS] = ctrId; encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = dir; LoRaWANNode::hton(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); LoRaWANNode::hton(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt); //Module::hexdump(uplinkMsg, uplinkMsgLen); // now encrypt the input // on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext size_t remLen = len; for(size_t i = 0; i < numBlocks; i++) { if(counter) { encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1; } // encrypt the buffer RadioLibAES128Instance.init(key); RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer); // now xor the buffer with the input size_t xorLen = remLen; if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) { xorLen = RADIOLIB_AES128_BLOCK_SIZE; } for(uint8_t j = 0; j < xorLen; j++) { out[i*RADIOLIB_AES128_BLOCK_SIZE + j] = in[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j]; } remLen -= xorLen; } } template T LoRaWANNode::ntoh(uint8_t* buff, size_t size) { uint8_t* buffPtr = buff; size_t targetSize = sizeof(T); if(size != 0) { targetSize = size; } T res = 0; for(size_t i = 0; i < targetSize; i++) { res |= (uint32_t)(*(buffPtr++)) << 8*i; } return(res); } template void LoRaWANNode::hton(uint8_t* buff, T val, size_t size) { uint8_t* buffPtr = buff; size_t targetSize = sizeof(T); if(size != 0) { targetSize = size; } for(size_t i = 0; i < targetSize; i++) { *(buffPtr++) = val >> 8*i; } } #endif