Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
EddystoneService.cpp
00001 /* mbed Microcontroller Library 00002 * Copyright (c) 2006-2015 ARM Limited 00003 * 00004 * Licensed under the Apache License, Version 2.0 (the "License"); 00005 * you may not use this file except in compliance with the License. 00006 * You may obtain a copy of the License at 00007 * 00008 * http://www.apache.org/licenses/LICENSE-2.0 00009 * 00010 * Unless required by applicable law or agreed to in writing, software 00011 * distributed under the License is distributed on an "AS IS" BASIS, 00012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00013 * See the License for the specific language governing permissions and 00014 * limitations under the License. 00015 */ 00016 00017 #include "EddystoneService.h" 00018 #include "EntropySource/EntropySource.h" 00019 00020 /* Use define zero for production, 1 for testing to allow connection at any time */ 00021 #define DEFAULT_REMAIN_CONNECTABLE 0x01 00022 00023 const char * const EddystoneService::slotDefaultUrls[] = EDDYSTONE_DEFAULT_SLOT_URLS; 00024 00025 // Static timer used as time since boot 00026 Timer EddystoneService::timeSinceBootTimer; 00027 00028 /* 00029 * CONSTRUCTOR #1 Used on 1st boot (after reflash) 00030 */ 00031 EddystoneService::EddystoneService(BLE &bleIn, 00032 const PowerLevels_t &advTxPowerLevelsIn, 00033 const PowerLevels_t &radioTxPowerLevelsIn, 00034 event_queue_t &evQ, 00035 uint32_t advConfigIntervalIn) : 00036 ble(bleIn), 00037 operationMode(EDDYSTONE_MODE_NONE), 00038 uidFrame(), 00039 urlFrame(), 00040 tlmFrame(), 00041 eidFrame(), 00042 tlmBatteryVoltageCallback(NULL), 00043 tlmBeaconTemperatureCallback(NULL), 00044 radioManagerCallbackHandle(NULL), 00045 deviceName(DEFAULT_DEVICE_NAME), 00046 eventQueue(evQ), 00047 nextEidSlot(0) 00048 { 00049 LOG(("1st Boot: ")); 00050 LOG((BUILD_VERSION_STR)); 00051 if (advConfigIntervalIn != 0) { 00052 if (advConfigIntervalIn < ble.gap().getMinAdvertisingInterval()) { 00053 advConfigInterval = ble.gap().getMinAdvertisingInterval(); 00054 } else if (advConfigIntervalIn > ble.gap().getMaxAdvertisingInterval()) { 00055 advConfigInterval = ble.gap().getMaxAdvertisingInterval(); 00056 } else { 00057 advConfigInterval = advConfigIntervalIn; 00058 } 00059 } 00060 memcpy(radioTxPowerLevels, radioTxPowerLevelsIn, sizeof(PowerLevels_t)); 00061 memcpy(advTxPowerLevels, advTxPowerLevelsIn, sizeof(PowerLevels_t)); 00062 00063 // 1st Boot so reset everything to factory values 00064 doFactoryReset(); // includes genBeaconKeys 00065 00066 LOG(("After FactoryReset in 1st Boot Init: genBeaconKeyRC=%d\r\n", genBeaconKeyRC)); 00067 /* TODO: Note that this timer is started from the time EddystoneService 00068 * is initialised and NOT from when the device is booted. 00069 */ 00070 timeSinceBootTimer.start(); 00071 00072 /* Set the device name at startup */ 00073 ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName)); 00074 00075 } 00076 00077 /* 00078 * Constuctor #2: Used on 2nd+ boot: EddystoneService parameters derived from persistent storage 00079 */ 00080 EddystoneService::EddystoneService(BLE &bleIn, 00081 EddystoneParams_t ¶msIn, 00082 const PowerLevels_t &radioTxPowerLevelsIn, 00083 event_queue_t &evQ, 00084 uint32_t advConfigIntervalIn) : 00085 ble(bleIn), 00086 operationMode(EDDYSTONE_MODE_NONE), 00087 uidFrame(), 00088 urlFrame(), 00089 tlmFrame(), 00090 eidFrame(), 00091 tlmBatteryVoltageCallback(NULL), 00092 tlmBeaconTemperatureCallback(NULL), 00093 radioManagerCallbackHandle(NULL), 00094 deviceName(DEFAULT_DEVICE_NAME), 00095 eventQueue(evQ), 00096 nextEidSlot(0) 00097 { 00098 LOG(("2nd (>=) Boot: ")); 00099 LOG((BUILD_VERSION_STR)); 00100 memcpy(capabilities, paramsIn.capabilities, sizeof(Capability_t)); 00101 activeSlot = paramsIn.activeSlot; 00102 memcpy(radioTxPowerLevels, radioTxPowerLevelsIn, sizeof(PowerLevels_t)); 00103 memcpy(slotRadioTxPowerLevels, paramsIn.slotRadioTxPowerLevels, sizeof(SlotTxPowerLevels_t)); 00104 memcpy(advTxPowerLevels, paramsIn.advTxPowerLevels, sizeof(PowerLevels_t)); 00105 memcpy(slotAdvTxPowerLevels, paramsIn.slotAdvTxPowerLevels, sizeof(SlotTxPowerLevels_t)); 00106 memcpy(slotAdvIntervals, paramsIn.slotAdvIntervals, sizeof(SlotAdvIntervals_t)); 00107 lockState = paramsIn.lockState; 00108 memcpy(unlockKey, paramsIn.unlockKey, sizeof(Lock_t)); 00109 memcpy(unlockToken, paramsIn.unlockToken, sizeof(Lock_t)); 00110 memcpy(challenge, paramsIn.challenge, sizeof(Lock_t)); 00111 memset(slotCallbackHandles, 0, sizeof(SlotCallbackHandles_t)); 00112 memcpy(slotStorage, paramsIn.slotStorage, sizeof(SlotStorage_t)); 00113 memcpy(slotFrameTypes, paramsIn.slotFrameTypes, sizeof(SlotFrameTypes_t)); 00114 memcpy(slotEidRotationPeriodExps, paramsIn.slotEidRotationPeriodExps, sizeof(SlotEidRotationPeriodExps_t)); 00115 memcpy(slotEidIdentityKeys, paramsIn.slotEidIdentityKeys, sizeof(SlotEidIdentityKeys_t)); 00116 remainConnectable = paramsIn.remainConnectable; 00117 00118 if (advConfigIntervalIn != 0) { 00119 if (advConfigIntervalIn < ble.gap().getMinAdvertisingInterval()) { 00120 advConfigInterval = ble.gap().getMinAdvertisingInterval(); 00121 } else if (advConfigIntervalIn > ble.gap().getMaxAdvertisingInterval()) { 00122 advConfigInterval = ble.gap().getMaxAdvertisingInterval(); 00123 } else { 00124 advConfigInterval = advConfigIntervalIn; 00125 } 00126 } 00127 00128 // Generate fresh private and public ECDH keys for EID 00129 genEIDBeaconKeys(); 00130 00131 // Recompute EID Slot Data 00132 for (int slot = 0; slot < MAX_ADV_SLOTS; slot++) { 00133 uint8_t* frame = slotToFrame(slot); 00134 switch (slotFrameTypes[slot]) { 00135 case EDDYSTONE_FRAME_EID: 00136 eidFrame.update(frame, slotEidIdentityKeys[slot], slotEidRotationPeriodExps[slot], timeSinceBootTimer.read_ms() / 1000); 00137 eidFrame.setAdvTxPower(frame, slotAdvTxPowerLevels[slot]); 00138 break; 00139 default: ; 00140 } 00141 } 00142 00143 00144 /* TODO: Note that this timer is started from the time EddystoneService 00145 * is initialised and NOT from when the device is booted. 00146 */ 00147 timeSinceBootTimer.start(); 00148 00149 /* Set the device name at startup */ 00150 ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceName)); 00151 } 00152 00153 // Regenerate the beacon keys 00154 void EddystoneService::genEIDBeaconKeys(void) { 00155 genBeaconKeyRC = -1; 00156 #ifdef GEN_BEACON_KEYS_AT_INIT 00157 memset(privateEcdhKey, 0, 32); 00158 memset(publicEcdhKey, 0, 32); 00159 genBeaconKeyRC = eidFrame.genBeaconKeys(privateEcdhKey, publicEcdhKey); 00160 swapEndianArray(publicEcdhKey, publicEcdhKeyLE, 32); 00161 #endif 00162 } 00163 00164 /** 00165 * Factory reset all parmeters: used at initial boot, and activated from Char 11 00166 */ 00167 void EddystoneService::doFactoryReset(void) 00168 { 00169 memset(slotCallbackHandles, 0, sizeof(SlotCallbackHandles_t)); 00170 radioManagerCallbackHandle = NULL; 00171 memcpy(capabilities, CAPABILITIES_DEFAULT, CAP_HDR_LEN); 00172 // Line above leaves powerlevels blank; Line below fills them in 00173 memcpy(capabilities + CAP_HDR_LEN, radioTxPowerLevels, sizeof(PowerLevels_t)); 00174 activeSlot = DEFAULT_SLOT; 00175 // Intervals 00176 uint16_t buf1[] = EDDYSTONE_DEFAULT_SLOT_INTERVALS; 00177 for (int i = 0; i < MAX_ADV_SLOTS; i++) { 00178 // Ensure all slot periods are in range 00179 buf1[i] = correctAdvertisementPeriod(buf1[i]); 00180 } 00181 memcpy(slotAdvIntervals, buf1, sizeof(SlotAdvIntervals_t)); 00182 // Radio and Adv TX Power 00183 int8_t buf2[] = EDDYSTONE_DEFAULT_SLOT_TX_POWERS; 00184 for (int i = 0; i< MAX_ADV_SLOTS; i++) { 00185 slotRadioTxPowerLevels[i] = buf2[i]; 00186 slotAdvTxPowerLevels[i] = advTxPowerLevels[radioTxPowerToIndex(buf2[i])]; 00187 } 00188 // Lock 00189 lockState = UNLOCKED; 00190 uint8_t defKeyBuf[] = EDDYSTONE_DEFAULT_UNLOCK_KEY; 00191 memcpy(unlockKey, defKeyBuf, sizeof(Lock_t)); 00192 memset(unlockToken, 0, sizeof(Lock_t)); 00193 memset(challenge, 0, sizeof(Lock_t)); // NOTE: challenge is randomized on first unlockChar read; 00194 00195 // Generate ECDH Beacon Key Pair (Private/Public) 00196 genEIDBeaconKeys(); 00197 00198 memcpy(slotEidIdentityKeys, slotDefaultEidIdentityKeys, sizeof(SlotEidIdentityKeys_t)); 00199 uint8_t buf4[] = EDDYSTONE_DEFAULT_SLOT_EID_ROTATION_PERIOD_EXPS; 00200 memcpy(slotEidRotationPeriodExps, buf4, sizeof(SlotEidRotationPeriodExps_t)); 00201 memset(slotEidNextRotationTimes, 0, sizeof(SlotEidNextRotationTimes_t)); 00202 // Slot Data Type Defaults 00203 uint8_t buf3[] = EDDYSTONE_DEFAULT_SLOT_TYPES; 00204 memcpy(slotFrameTypes, buf3, sizeof(SlotFrameTypes_t)); 00205 // Initialize Slot Data Defaults 00206 int eidSlot; 00207 for (int slot = 0; slot < MAX_ADV_SLOTS; slot++) { 00208 uint8_t* frame = slotToFrame(slot); 00209 switch (slotFrameTypes[slot]) { 00210 case EDDYSTONE_FRAME_UID: 00211 uidFrame.setData(frame, slotAdvTxPowerLevels[slot], reinterpret_cast<const uint8_t*>(slotDefaultUids[slot])); 00212 break; 00213 case EDDYSTONE_FRAME_URL: 00214 urlFrame.setUnencodedUrlData(frame, slotAdvTxPowerLevels[slot], slotDefaultUrls[slot]); 00215 break; 00216 case EDDYSTONE_FRAME_TLM: 00217 tlmFrame.setTLMData(TLMFrame::DEFAULT_TLM_VERSION); 00218 tlmFrame.setData(frame); 00219 eidSlot = getEidSlot(); 00220 if (eidSlot != NO_EID_SLOT_SET) { 00221 LOG(("EID slot Set in FactoryReset\r\n")); 00222 tlmFrame.encryptData(frame, slotEidIdentityKeys[eidSlot], slotEidRotationPeriodExps[eidSlot], timeSinceBootTimer.read_ms() / 1000); 00223 } 00224 break; 00225 case EDDYSTONE_FRAME_EID: 00226 nextEidSlot = slot; 00227 eidFrame.setData(frame, slotAdvTxPowerLevels[slot], reinterpret_cast<const uint8_t*>(allSlotsDefaultEid)); 00228 eidFrame.update(frame, slotEidIdentityKeys[slot], slotEidRotationPeriodExps[slot], timeSinceBootTimer.read_ms() / 1000); 00229 break; 00230 } 00231 } 00232 #ifdef DONT_REMAIN_CONNECTABLE 00233 remainConnectable = REMAIN_CONNECTABLE_UNSET; 00234 #else 00235 remainConnectable = REMAIN_CONNECTABLE_SET; 00236 #endif 00237 factoryReset = false; 00238 } 00239 00240 /* Setup callback to update BatteryVoltage in TLM frame */ 00241 void EddystoneService::onTLMBatteryVoltageUpdate(TlmUpdateCallback_t tlmBatteryVoltageCallbackIn) 00242 { 00243 tlmBatteryVoltageCallback = tlmBatteryVoltageCallbackIn; 00244 } 00245 00246 /* Setup callback to update BeaconTemperature in TLM frame */ 00247 void EddystoneService::onTLMBeaconTemperatureUpdate(TlmUpdateCallback_t tlmBeaconTemperatureCallbackIn) 00248 { 00249 tlmBeaconTemperatureCallback = tlmBeaconTemperatureCallbackIn; 00250 } 00251 00252 EddystoneService::EddystoneError_t EddystoneService::startEddystoneBeaconAdvertisements(void) 00253 { 00254 stopEddystoneBeaconAdvertisements(); 00255 00256 bool intervalValidFlag = false; 00257 for (int i = 0; i < MAX_ADV_SLOTS; i++) { 00258 if (slotAdvIntervals[i] != 0) { 00259 intervalValidFlag = true; 00260 } 00261 } 00262 00263 if (!intervalValidFlag) { 00264 /* Nothing to do, the period is 0 for all frames */ 00265 return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL; 00266 } 00267 00268 // In case left over from Config Adv Mode 00269 ble.gap().clearScanResponse(); 00270 00271 operationMode = EDDYSTONE_MODE_BEACON; 00272 00273 /* Configure advertisements initially at power of active slot*/ 00274 ble.gap().setTxPower(slotRadioTxPowerLevels[activeSlot]); 00275 00276 if (remainConnectable) { 00277 ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); 00278 } else { 00279 ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED); 00280 } 00281 ble.gap().setAdvertisingInterval(ble.gap().getMaxAdvertisingInterval()); 00282 00283 /* Make sure the queue is currently empty */ 00284 advFrameQueue.reset(); 00285 /* Setup callbacks to periodically add frames to be advertised to the queue and 00286 * add initial frame so that we have something to advertise on startup */ 00287 for (int slot = 0; slot < MAX_ADV_SLOTS; slot++) { 00288 uint8_t* frame = slotToFrame(slot); 00289 if (slotAdvIntervals[slot] && testValidFrame(frame)) { 00290 advFrameQueue.push(slot); 00291 slotCallbackHandles[slot] = eventQueue.post_every( 00292 &EddystoneService::enqueueFrame, this, slot, 00293 slotAdvIntervals[slot] /* ms */ 00294 ); 00295 } 00296 } 00297 /* Start advertising */ 00298 manageRadio(); 00299 00300 return EDDYSTONE_ERROR_NONE; 00301 } 00302 00303 ble_error_t EddystoneService::setCompleteDeviceName(const char *deviceNameIn) 00304 { 00305 /* Make sure the device name is safe */ 00306 ble_error_t error = ble.gap().setDeviceName(reinterpret_cast<const uint8_t *>(deviceNameIn)); 00307 if (error == BLE_ERROR_NONE) { 00308 deviceName = deviceNameIn; 00309 if (operationMode == EDDYSTONE_MODE_CONFIG) { 00310 /* Need to update the advertising packets to the new name */ 00311 setupEddystoneConfigScanResponse(); 00312 } 00313 } 00314 00315 return error; 00316 } 00317 00318 /* It is not the responsibility of the Eddystone implementation to store 00319 * the configured parameters in persistent storage since this is 00320 * platform-specific. So we provide this function that returns the 00321 * configured values that need to be stored and the main application 00322 * takes care of storing them. 00323 */ 00324 void EddystoneService::getEddystoneParams(EddystoneParams_t ¶ms) 00325 { 00326 // Capabilities 00327 memcpy(params.capabilities, capabilities, sizeof(Capability_t)); 00328 // Active Slot 00329 params.activeSlot = activeSlot; 00330 // Intervals 00331 memcpy(params.slotAdvIntervals, slotAdvIntervals, sizeof(SlotAdvIntervals_t)); 00332 // Power Levels 00333 memcpy(params.radioTxPowerLevels, radioTxPowerLevels, sizeof(PowerLevels_t)); 00334 memcpy(params.advTxPowerLevels, advTxPowerLevels, sizeof(PowerLevels_t)); 00335 // Slot Power Levels 00336 memcpy(params.slotRadioTxPowerLevels, slotRadioTxPowerLevels, sizeof(MAX_ADV_SLOTS)); 00337 memcpy(params.slotAdvTxPowerLevels, slotAdvTxPowerLevels, sizeof(MAX_ADV_SLOTS)); 00338 // Lock 00339 params.lockState = lockState; 00340 memcpy(params.unlockKey, unlockKey, sizeof(Lock_t)); 00341 memcpy(params.unlockToken, unlockToken, sizeof(Lock_t)); 00342 memcpy(params.challenge, challenge, sizeof(Lock_t)); 00343 // Slots 00344 memcpy(params.slotFrameTypes, slotFrameTypes, sizeof(SlotFrameTypes_t)); 00345 memcpy(params.slotStorage, slotStorage, sizeof(SlotStorage_t)); 00346 memcpy(params.slotEidRotationPeriodExps, slotEidRotationPeriodExps, sizeof(SlotEidRotationPeriodExps_t)); 00347 memcpy(params.slotEidIdentityKeys, slotEidIdentityKeys, sizeof(SlotEidIdentityKeys_t)); 00348 // Testing and Management 00349 params.remainConnectable = remainConnectable; 00350 } 00351 00352 void EddystoneService::swapAdvertisedFrame(int slot) 00353 { 00354 uint8_t* frame = slotToFrame(slot); 00355 uint8_t frameType = slotFrameTypes[slot]; 00356 uint32_t timeSecs = timeSinceBootTimer.read_ms() / 1000; 00357 switch (frameType) { 00358 case EDDYSTONE_FRAME_UID: 00359 updateAdvertisementPacket(uidFrame.getAdvFrame(frame), uidFrame.getAdvFrameLength(frame)); 00360 break; 00361 case EDDYSTONE_FRAME_URL: 00362 updateAdvertisementPacket(urlFrame.getAdvFrame(frame), urlFrame.getAdvFrameLength(frame)); 00363 break; 00364 case EDDYSTONE_FRAME_TLM: 00365 updateRawTLMFrame(frame); 00366 updateAdvertisementPacket(tlmFrame.getAdvFrame(frame), tlmFrame.getAdvFrameLength(frame)); 00367 break; 00368 case EDDYSTONE_FRAME_EID: 00369 // only update the frame if the rotation period is due 00370 if (timeSecs >= slotEidNextRotationTimes[slot]) { 00371 eidFrame.update(frame, slotEidIdentityKeys[slot], slotEidRotationPeriodExps[slot], timeSecs); 00372 slotEidNextRotationTimes[slot] = timeSecs + (1 << slotEidRotationPeriodExps[slot]); 00373 setRandomMacAddress(); // selects a new MAC address so the beacon is not trackable 00374 LOG(("EID ROTATED: Time=%lu\r\n", timeSecs)); 00375 } 00376 updateAdvertisementPacket(eidFrame.getAdvFrame(frame), eidFrame.getAdvFrameLength(frame)); 00377 break; 00378 default: 00379 //Some error occurred 00380 error("Frame to swap in does not specify a valid type"); 00381 break; 00382 } 00383 ble.gap().setTxPower(slotRadioTxPowerLevels[slot]); 00384 } 00385 00386 00387 /* Helper function that calls user-defined functions to update Battery Voltage and Temperature (if available), 00388 * then updates the raw frame data and finally updates the actual advertised packet. This operation must be 00389 * done fairly often because the TLM frame TimeSinceBoot must have a 0.1 secs resolution according to the 00390 * Eddystone specification. 00391 */ 00392 void EddystoneService::updateRawTLMFrame(uint8_t* frame) 00393 { 00394 if (tlmBeaconTemperatureCallback != NULL) { 00395 tlmFrame.updateBeaconTemperature((*tlmBeaconTemperatureCallback)(tlmFrame.getBeaconTemperature())); 00396 } 00397 if (tlmBatteryVoltageCallback != NULL) { 00398 tlmFrame.updateBatteryVoltage((*tlmBatteryVoltageCallback)(tlmFrame.getBatteryVoltage())); 00399 } 00400 tlmFrame.updateTimeSinceBoot(timeSinceBootTimer.read_ms()); 00401 tlmFrame.setData(frame); 00402 int slot = getEidSlot(); 00403 LOG(("TLMHelper Method slot=%d\r\n", slot)); 00404 if (slot != NO_EID_SLOT_SET) { 00405 LOG(("TLMHelper: Before Encrypting TLM\r\n")); 00406 tlmFrame.encryptData(frame, slotEidIdentityKeys[slot], slotEidRotationPeriodExps[slot], timeSinceBootTimer.read_ms() / 1000); 00407 LOG(("TLMHelper: Before Encrypting TLM\r\n")); 00408 } 00409 } 00410 00411 void EddystoneService::updateAdvertisementPacket(const uint8_t* rawFrame, size_t rawFrameLength) 00412 { 00413 ble.gap().clearAdvertisingPayload(); 00414 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); 00415 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, EDDYSTONE_UUID, sizeof(EDDYSTONE_UUID)); 00416 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, rawFrame, rawFrameLength); 00417 } 00418 00419 uint8_t* EddystoneService::slotToFrame(int slot) 00420 { 00421 return reinterpret_cast<uint8_t *>(&slotStorage[slot * sizeof(Slot_t)]); 00422 } 00423 00424 void EddystoneService::enqueueFrame(int slot) 00425 { 00426 advFrameQueue.push(slot); 00427 if (!radioManagerCallbackHandle) { 00428 /* Advertising stopped and there is not callback posted in the event queue. Just 00429 * execute the manager to resume advertising */ 00430 manageRadio(); 00431 } 00432 } 00433 00434 void EddystoneService::manageRadio(void) 00435 { 00436 uint8_t slot; 00437 uint32_t startTimeManageRadio = timeSinceBootTimer.read_ms(); 00438 00439 /* Signal that there is currently no callback posted */ 00440 radioManagerCallbackHandle = NULL; 00441 00442 if (advFrameQueue.pop(slot)) { 00443 /* We have something to advertise */ 00444 if (ble.gap().getState().advertising) { 00445 ble.gap().stopAdvertising(); 00446 } 00447 swapAdvertisedFrame(slot); 00448 ble.gap().startAdvertising(); 00449 00450 /* Increase the advertised packet count in TLM frame */ 00451 tlmFrame.updatePduCount(); 00452 00453 /* Post a callback to itself to stop the advertisement or pop the next 00454 * frame from the queue. However, take into account the time taken to 00455 * swap in this frame. */ 00456 radioManagerCallbackHandle = eventQueue.post_in( 00457 &EddystoneService::manageRadio, this, 00458 ble.gap().getMinNonConnectableAdvertisingInterval() - (timeSinceBootTimer.read_ms() - startTimeManageRadio) /* ms */ 00459 ); 00460 } else if (ble.gap().getState().advertising) { 00461 /* Nothing else to advertise, stop advertising and do not schedule any callbacks */ 00462 ble.gap().stopAdvertising(); 00463 } 00464 } 00465 00466 void EddystoneService::startEddystoneConfigService(void) 00467 { 00468 uint16_t beAdvInterval = swapEndian(slotAdvIntervals[activeSlot]); 00469 int8_t radioTxPower = slotRadioTxPowerLevels[activeSlot]; 00470 int8_t advTxPower = slotAdvTxPowerLevels[activeSlot]; 00471 uint8_t* slotData = slotToFrame(activeSlot) + 1; 00472 aes128Encrypt(unlockKey, slotEidIdentityKeys[activeSlot], encryptedEidIdentityKey); 00473 00474 capabilitiesChar = new ReadOnlyArrayGattCharacteristic<uint8_t, sizeof(Capability_t)>(UUID_CAPABILITIES_CHAR, capabilities); 00475 activeSlotChar = new ReadWriteGattCharacteristic<uint8_t>(UUID_ACTIVE_SLOT_CHAR, &activeSlot); 00476 advIntervalChar = new ReadWriteGattCharacteristic<uint16_t>(UUID_ADV_INTERVAL_CHAR, &beAdvInterval); 00477 radioTxPowerChar = new ReadWriteGattCharacteristic<int8_t>(UUID_RADIO_TX_POWER_CHAR, &radioTxPower); 00478 advTxPowerChar = new ReadWriteGattCharacteristic<int8_t>(UUID_ADV_TX_POWER_CHAR, &advTxPower); 00479 lockStateChar = new GattCharacteristic(UUID_LOCK_STATE_CHAR, &lockState, sizeof(uint8_t), sizeof(LockState_t), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); 00480 unlockChar = new ReadWriteArrayGattCharacteristic<uint8_t, sizeof(Lock_t)>(UUID_UNLOCK_CHAR, unlockToken); 00481 publicEcdhKeyChar = new GattCharacteristic(UUID_PUBLIC_ECDH_KEY_CHAR, publicEcdhKey, 0, sizeof(PublicEcdhKey_t), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ); 00482 eidIdentityKeyChar = new GattCharacteristic(UUID_EID_IDENTITY_KEY_CHAR, encryptedEidIdentityKey, 0, sizeof(EidIdentityKey_t), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ); 00483 advSlotDataChar = new GattCharacteristic(UUID_ADV_SLOT_DATA_CHAR, slotData, 0, 34, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE); 00484 factoryResetChar = new WriteOnlyGattCharacteristic<uint8_t>(UUID_FACTORY_RESET_CHAR, &factoryReset); 00485 remainConnectableChar = new ReadWriteGattCharacteristic<uint8_t>(UUID_REMAIN_CONNECTABLE_CHAR, &remainConnectable); 00486 00487 // CHAR-1 capabilities (READ ONLY) 00488 capabilitiesChar->setReadAuthorizationCallback(this, &EddystoneService::readBasicTestLockAuthorizationCallback); 00489 // CHAR-2 Active Slot 00490 activeSlotChar->setReadAuthorizationCallback(this, &EddystoneService::readBasicTestLockAuthorizationCallback); 00491 activeSlotChar->setWriteAuthorizationCallback(this, &EddystoneService::writeActiveSlotAuthorizationCallback<uint8_t>); 00492 // CHAR-3 Adv Interval 00493 advIntervalChar->setReadAuthorizationCallback(this, &EddystoneService::readAdvIntervalAuthorizationCallback); 00494 advIntervalChar->setWriteAuthorizationCallback(this, &EddystoneService::writeBasicAuthorizationCallback<uint16_t>); 00495 // CHAR-4 Radio TX Power 00496 radioTxPowerChar->setReadAuthorizationCallback(this, &EddystoneService::readRadioTxPowerAuthorizationCallback); 00497 radioTxPowerChar->setWriteAuthorizationCallback(this, &EddystoneService::writeBasicAuthorizationCallback<uint8_t>); 00498 // CHAR-5 00499 advTxPowerChar->setReadAuthorizationCallback(this, &EddystoneService::readAdvTxPowerAuthorizationCallback); 00500 advTxPowerChar->setWriteAuthorizationCallback(this, &EddystoneService::writeBasicAuthorizationCallback<uint8_t>); 00501 // CHAR-6 Lock State 00502 lockStateChar->setWriteAuthorizationCallback(this, &EddystoneService::writeLockStateAuthorizationCallback); 00503 // CHAR-7 Unlock 00504 unlockChar->setReadAuthorizationCallback(this, &EddystoneService::readUnlockAuthorizationCallback); 00505 unlockChar->setWriteAuthorizationCallback(this, &EddystoneService::writeUnlockAuthorizationCallback); 00506 // CHAR-8 Public Ecdh Key (READ ONLY) 00507 publicEcdhKeyChar->setReadAuthorizationCallback(this, &EddystoneService::readPublicEcdhKeyAuthorizationCallback); 00508 // CHAR-9 EID Identity Key (READ ONLY) 00509 eidIdentityKeyChar->setReadAuthorizationCallback(this, &EddystoneService::readEidIdentityAuthorizationCallback); 00510 // CHAR-10 Adv Slot Data 00511 advSlotDataChar->setReadAuthorizationCallback(this, &EddystoneService::readDataAuthorizationCallback); 00512 advSlotDataChar->setWriteAuthorizationCallback(this, &EddystoneService::writeVarLengthDataAuthorizationCallback); 00513 // CHAR-11 Factory Reset 00514 factoryResetChar->setReadAuthorizationCallback(this, &EddystoneService::readBasicTestLockAuthorizationCallback); 00515 factoryResetChar->setWriteAuthorizationCallback(this, &EddystoneService::writeBasicAuthorizationCallback<bool>); 00516 // CHAR-12 Remain Connectable 00517 remainConnectableChar->setReadAuthorizationCallback(this, &EddystoneService::readBasicTestLockAuthorizationCallback); 00518 remainConnectableChar->setWriteAuthorizationCallback(this, &EddystoneService::writeBasicAuthorizationCallback<bool>); 00519 00520 // Create pointers to all characteristics in the GATT service 00521 charTable[0] = capabilitiesChar; 00522 charTable[1] = activeSlotChar; 00523 charTable[2] = advIntervalChar; 00524 charTable[3] = radioTxPowerChar; 00525 charTable[4] = advTxPowerChar; 00526 charTable[5] = lockStateChar; 00527 charTable[6] = unlockChar; 00528 charTable[7] = publicEcdhKeyChar; 00529 charTable[8] = eidIdentityKeyChar; 00530 charTable[9] = advSlotDataChar; 00531 charTable[10] = factoryResetChar; 00532 charTable[11] = remainConnectableChar; 00533 00534 GattService configService(UUID_ES_BEACON_SERVICE, charTable, sizeof(charTable) / sizeof(GattCharacteristic *)); 00535 00536 ble.gattServer().addService(configService); 00537 ble.gattServer().onDataWritten(this, &EddystoneService::onDataWrittenCallback); 00538 updateCharacteristicValues(); 00539 } 00540 00541 00542 void EddystoneService::freeConfigCharacteristics(void) 00543 { 00544 delete capabilitiesChar; 00545 delete activeSlotChar; 00546 delete advIntervalChar; 00547 delete radioTxPowerChar; 00548 delete advTxPowerChar; 00549 delete lockStateChar; 00550 delete unlockChar; 00551 delete publicEcdhKeyChar; 00552 delete eidIdentityKeyChar; 00553 delete advSlotDataChar; 00554 delete factoryResetChar; 00555 delete remainConnectableChar; 00556 } 00557 00558 void EddystoneService::stopEddystoneBeaconAdvertisements(void) 00559 { 00560 /* Unschedule callbacks */ 00561 00562 for (int slot = 0; slot < MAX_ADV_SLOTS; slot++) { 00563 if (slotCallbackHandles[slot]) { 00564 eventQueue.cancel(slotCallbackHandles[slot]); 00565 slotCallbackHandles[slot] = NULL; 00566 } 00567 } 00568 00569 if (radioManagerCallbackHandle) { 00570 eventQueue.cancel(radioManagerCallbackHandle); 00571 radioManagerCallbackHandle = NULL; 00572 } 00573 00574 /* Stop any current Advs (ES Config or Beacon) */ 00575 BLE::Instance().gap().stopAdvertising(); 00576 } 00577 00578 /* 00579 * Internal helper function used to update the GATT database following any 00580 * change to the internal state of the service object. 00581 */ 00582 void EddystoneService::updateCharacteristicValues(void) 00583 { 00584 // Init variables for update 00585 uint16_t beAdvInterval = swapEndian(slotAdvIntervals[activeSlot]); 00586 int8_t radioTxPower = slotRadioTxPowerLevels[activeSlot]; 00587 int8_t advTxPower = slotAdvTxPowerLevels[activeSlot]; 00588 uint8_t* frame = slotToFrame(activeSlot); 00589 uint8_t slotLength = 0; 00590 uint8_t* slotData = NULL; 00591 memset(encryptedEidIdentityKey, 0, sizeof(encryptedEidIdentityKey)); 00592 00593 switch(slotFrameTypes[activeSlot]) { 00594 case EDDYSTONE_FRAME_UID: 00595 slotLength = uidFrame.getDataLength(frame); 00596 slotData = uidFrame.getData(frame); 00597 break; 00598 case EDDYSTONE_FRAME_URL: 00599 slotLength = urlFrame.getDataLength(frame); 00600 slotData = urlFrame.getData(frame); 00601 break; 00602 case EDDYSTONE_FRAME_TLM: 00603 updateRawTLMFrame(frame); 00604 slotLength = tlmFrame.getDataLength(frame); 00605 slotData = tlmFrame.getData(frame); 00606 break; 00607 case EDDYSTONE_FRAME_EID: 00608 slotLength = eidFrame.getDataLength(frame); 00609 slotData = eidFrame.getData(frame); 00610 aes128Encrypt(unlockKey, slotEidIdentityKeys[activeSlot], encryptedEidIdentityKey); 00611 break; 00612 } 00613 00614 ble.gattServer().write(capabilitiesChar->getValueHandle(), reinterpret_cast<uint8_t *>(capabilities), sizeof(Capability_t)); 00615 ble.gattServer().write(activeSlotChar->getValueHandle(), &activeSlot, sizeof(uint8_t)); 00616 ble.gattServer().write(advIntervalChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beAdvInterval), sizeof(uint16_t)); 00617 ble.gattServer().write(radioTxPowerChar->getValueHandle(), reinterpret_cast<uint8_t *>(&radioTxPower), sizeof(int8_t)); 00618 ble.gattServer().write(advTxPowerChar->getValueHandle(), reinterpret_cast<uint8_t *>(&advTxPower), sizeof(int8_t)); 00619 ble.gattServer().write(lockStateChar->getValueHandle(), &lockState, sizeof(uint8_t)); 00620 ble.gattServer().write(unlockChar->getValueHandle(), unlockToken, sizeof(Lock_t)); 00621 ble.gattServer().write(publicEcdhKeyChar->getValueHandle(), reinterpret_cast<uint8_t *>(publicEcdhKey), sizeof(PublicEcdhKey_t)); 00622 ble.gattServer().write(eidIdentityKeyChar->getValueHandle(), reinterpret_cast<uint8_t *>(encryptedEidIdentityKey), sizeof(EidIdentityKey_t)); 00623 ble.gattServer().write(advSlotDataChar->getValueHandle(), slotData, slotLength); 00624 ble.gattServer().write(factoryResetChar->getValueHandle(), &factoryReset, sizeof(uint8_t)); 00625 ble.gattServer().write(remainConnectableChar->getValueHandle(), &remainConnectable, sizeof(uint8_t)); 00626 } 00627 00628 EddystoneService::EddystoneError_t EddystoneService::startEddystoneConfigAdvertisements(void) 00629 { 00630 stopEddystoneBeaconAdvertisements(); 00631 00632 if (advConfigInterval == 0) { 00633 // Nothing to do, the advertisement interval is 0 00634 return EDDYSTONE_ERROR_INVALID_ADVERTISING_INTERVAL; 00635 } 00636 00637 operationMode = EDDYSTONE_MODE_CONFIG; 00638 00639 ble.gap().clearAdvertisingPayload(); 00640 00641 /* Accumulate the new payload */ 00642 ble.gap().accumulateAdvertisingPayload( 00643 GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE 00644 ); 00645 /* UUID is in different order in the ADV frame (!) */ 00646 uint8_t reversedServiceUUID[sizeof(UUID_ES_BEACON_SERVICE)]; 00647 for (size_t i = 0; i < sizeof(UUID_ES_BEACON_SERVICE); i++) { 00648 reversedServiceUUID[i] = UUID_ES_BEACON_SERVICE[sizeof(UUID_ES_BEACON_SERVICE) - i - 1]; 00649 } 00650 ble.gap().accumulateAdvertisingPayload( 00651 GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, 00652 reversedServiceUUID, 00653 sizeof(reversedServiceUUID) 00654 ); 00655 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_TAG); 00656 setupEddystoneConfigScanResponse(); 00657 00658 ble.gap().setTxPower(radioTxPowerLevels[sizeof(PowerLevels_t)-1]); // Max Power for Config 00659 ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); 00660 ble.gap().setAdvertisingInterval(advConfigInterval); 00661 ble.gap().startAdvertising(); 00662 00663 return EDDYSTONE_ERROR_NONE; 00664 } 00665 00666 void EddystoneService::setupEddystoneConfigScanResponse(void) 00667 { 00668 ble.gap().clearScanResponse(); 00669 ble.gap().accumulateScanResponse( 00670 GapAdvertisingData::COMPLETE_LOCAL_NAME, 00671 reinterpret_cast<const uint8_t *>(deviceName), 00672 strlen(deviceName) 00673 ); 00674 ble.gap().accumulateScanResponse( 00675 GapAdvertisingData::TX_POWER_LEVEL, 00676 reinterpret_cast<uint8_t *>(&advTxPowerLevels[sizeof(PowerLevels_t)-1]), 00677 sizeof(uint8_t) 00678 ); 00679 } 00680 00681 /* WRITE AUTHORIZATION */ 00682 00683 void EddystoneService::writeUnlockAuthorizationCallback(GattWriteAuthCallbackParams *authParams) 00684 { 00685 if (lockState == UNLOCKED) { 00686 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; 00687 } else if (authParams->len != sizeof(Lock_t)) { 00688 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; 00689 } else if (authParams->offset != 0) { 00690 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; 00691 } else if (memcmp(authParams->data, unlockToken, sizeof(Lock_t)) != 0) { 00692 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; 00693 } else { 00694 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00695 } 00696 } 00697 00698 void EddystoneService::writeVarLengthDataAuthorizationCallback(GattWriteAuthCallbackParams *authParams) 00699 { 00700 if (lockState == LOCKED) { 00701 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; 00702 } else if ((authParams->len > 34) || (authParams->len == 0)) { 00703 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; 00704 } else { 00705 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00706 } 00707 } 00708 00709 00710 void EddystoneService::writeLockStateAuthorizationCallback(GattWriteAuthCallbackParams *authParams) 00711 { 00712 if (lockState == LOCKED) { 00713 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; 00714 } else if ((authParams->len != sizeof(uint8_t)) && (authParams->len != (sizeof(uint8_t) + sizeof(Lock_t)))) { 00715 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; 00716 } else if (authParams->offset != 0) { 00717 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; 00718 } else { 00719 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00720 } 00721 } 00722 00723 template <typename T> 00724 void EddystoneService::writeBasicAuthorizationCallback(GattWriteAuthCallbackParams *authParams) 00725 { 00726 if (lockState == LOCKED) { 00727 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; 00728 } else if (authParams->len != sizeof(T)) { 00729 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; 00730 } else if (authParams->offset != 0) { 00731 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; 00732 } else { 00733 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00734 } 00735 } 00736 00737 template <typename T> 00738 void EddystoneService::writeActiveSlotAuthorizationCallback(GattWriteAuthCallbackParams *authParams) 00739 { 00740 if (lockState == LOCKED) { 00741 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_WRITE_NOT_PERMITTED; 00742 } else if (authParams->len != sizeof(T)) { 00743 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; 00744 } else if (*(authParams->data) > MAX_ADV_SLOTS -1) { 00745 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_ATT_VAL_LENGTH; 00746 } else if (authParams->offset != 0) { 00747 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_INVALID_OFFSET; 00748 } else { 00749 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00750 } 00751 } 00752 00753 /* READ AUTHORIZTION */ 00754 00755 void EddystoneService::readBasicTestLockAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00756 { 00757 LOG(("\r\nDO READ BASIC TEST LOCK slot=%d\r\n", activeSlot)); 00758 if (lockState == LOCKED) { 00759 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00760 } else { 00761 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00762 } 00763 } 00764 00765 void EddystoneService::readEidIdentityAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00766 { 00767 LOG(("\r\nDO READ EID IDENTITY slot=%d\r\n", activeSlot)); 00768 aes128Encrypt(unlockKey, slotEidIdentityKeys[activeSlot], encryptedEidIdentityKey); 00769 int sum = 0; 00770 // Test if the IdentityKey is all zeros for this slot 00771 for (uint8_t i = 0; i < sizeof(EidIdentityKey_t); i++) { 00772 sum = sum + slotEidIdentityKeys[activeSlot][i]; 00773 } 00774 ble.gattServer().write(eidIdentityKeyChar->getValueHandle(), encryptedEidIdentityKey, sizeof(EidIdentityKey_t)); 00775 00776 // When the array is all zeros, the key has not been set, so return fault 00777 if ((lockState == LOCKED) || (sum == 0)) { 00778 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00779 } else { 00780 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00781 } 00782 } 00783 00784 void EddystoneService::readPublicEcdhKeyAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00785 { 00786 LOG(("\r\nDO READ BEACON PUBLIC ECDH KEY (LE) slot=%d\r\n", activeSlot)); 00787 00788 ble.gattServer().write(publicEcdhKeyChar->getValueHandle(), publicEcdhKeyLE, sizeof(PublicEcdhKey_t)); 00789 00790 // When the array is all zeros, the key has not been set, so return fault 00791 if (lockState == LOCKED) { 00792 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00793 } else { 00794 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00795 } 00796 } 00797 00798 void EddystoneService::readDataAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00799 { 00800 LOG(("\r\nDO READ ADV-DATA : slot=%d\r\n", activeSlot)); 00801 uint8_t frameType = slotFrameTypes[activeSlot]; 00802 uint8_t* frame = slotToFrame(activeSlot); 00803 uint8_t slotLength = 1; 00804 uint8_t buf[14] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 00805 uint8_t* slotData = buf; 00806 00807 if (lockState == LOCKED) { 00808 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00809 return; 00810 } 00811 LOG(("IN READ ADV-DATA AFTER LOCK TEST frameType=%d\r\n", frameType)); 00812 if (testValidFrame(frame) ) { // Check the frame has valid data before proceeding 00813 switch(frameType) { 00814 case EDDYSTONE_FRAME_UID: 00815 LOG(("READ ADV-DATA UID SLOT DATA slot=%d\r\n", activeSlot)); 00816 slotLength = uidFrame.getDataLength(frame); 00817 slotData = uidFrame.getData(frame); 00818 break; 00819 case EDDYSTONE_FRAME_URL: 00820 LOG(("READ ADV-DATA URL SLOT DATA slot=%d\r\n", activeSlot)); 00821 slotLength = urlFrame.getDataLength(frame); 00822 slotData = urlFrame.getData(frame); 00823 break; 00824 case EDDYSTONE_FRAME_TLM: 00825 LOG(("READ ADV-DATA TLM SLOT DATA slot=%d\r\n", activeSlot)); 00826 updateRawTLMFrame(frame); 00827 slotLength = tlmFrame.getDataLength(frame); 00828 slotData = tlmFrame.getData(frame); 00829 LOG(("READ ADV-DATA AFTER T/E TLM length=%d\r\n", slotLength)); 00830 LOG(("Data=")); logPrintHex(slotData, 18); 00831 break; 00832 case EDDYSTONE_FRAME_EID: 00833 LOG(("READ ADV-DATA EID SLOT DATA slot=%d\r\n", activeSlot)); 00834 slotLength = 14; 00835 buf[0] = EIDFrame::FRAME_TYPE_EID; 00836 buf[1] = slotEidRotationPeriodExps[activeSlot]; 00837 // Add time as a big endian 32 bit number 00838 uint32_t timeSecs = timeSinceBootTimer.read_ms() / 1000; 00839 buf[2] = (timeSecs >> 24) & 0xff; 00840 buf[3] = (timeSecs >> 16) & 0xff; 00841 buf[4] = (timeSecs >> 8) & 0xff; 00842 buf[5] = timeSecs & 0xff; 00843 memcpy(buf + 6, eidFrame.getEid(frame), 8); 00844 slotData = buf; 00845 break; 00846 } 00847 } 00848 LOG(("IN READ ADV-DATA AFTER FRAME PROCESSING slot=%d\r\n", activeSlot)); 00849 ble.gattServer().write(advSlotDataChar->getValueHandle(), slotData, slotLength); 00850 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00851 } 00852 00853 bool EddystoneService::testValidFrame(uint8_t* frame) { 00854 return (frame[0] != 0 ) ? true : false; 00855 } 00856 00857 void EddystoneService::readUnlockAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00858 { 00859 LOG(("\r\nDO READ UNLOCK slot=%d\r\n", activeSlot)); 00860 if (lockState == UNLOCKED) { 00861 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00862 return; 00863 } 00864 // Update the challenge ready for the characteristic read 00865 generateRandom(challenge, sizeof(Lock_t)); 00866 aes128Encrypt(unlockKey, challenge, unlockToken); 00867 ble.gattServer().write(unlockChar->getValueHandle(), reinterpret_cast<uint8_t *>(challenge), sizeof(Lock_t)); 00868 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00869 } 00870 00871 void EddystoneService::readAdvIntervalAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00872 { 00873 LOG(("\r\nDO READ ADV INTERVAL slot=%d\r\n", activeSlot)); 00874 if (lockState == LOCKED) { 00875 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00876 return; 00877 } 00878 uint16_t beAdvInterval = swapEndian(slotAdvIntervals[activeSlot]); 00879 ble.gattServer().write(advIntervalChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beAdvInterval), sizeof(uint16_t)); 00880 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00881 } 00882 00883 void EddystoneService::readRadioTxPowerAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00884 { 00885 LOG(("\r\nDO READ RADIO TXPOWER slot=%d\r\n", activeSlot)); 00886 if (lockState == LOCKED) { 00887 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00888 return; 00889 } 00890 int8_t radioTxPower = slotRadioTxPowerLevels[activeSlot]; 00891 ble.gattServer().write(radioTxPowerChar->getValueHandle(), reinterpret_cast<uint8_t *>(&radioTxPower), sizeof(int8_t)); 00892 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00893 } 00894 00895 void EddystoneService::readAdvTxPowerAuthorizationCallback(GattReadAuthCallbackParams *authParams) 00896 { 00897 LOG(("\r\nDO READ ADV TXPOWER slot=%d\r\n", activeSlot)); 00898 if (lockState == LOCKED) { 00899 authParams->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED; 00900 return; 00901 } 00902 int8_t advTxPower = slotAdvTxPowerLevels[activeSlot]; 00903 ble.gattServer().write(advTxPowerChar->getValueHandle(), reinterpret_cast<uint8_t *>(&advTxPower), sizeof(int8_t)); 00904 authParams->authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS; 00905 } 00906 00907 /* 00908 * This callback is invoked when a GATT client attempts to modify any of the 00909 * characteristics of this service. Attempts to do so are also applied to 00910 * the internal state of this service object. 00911 */ 00912 void EddystoneService::onDataWrittenCallback(const GattWriteCallbackParams *writeParams) 00913 { 00914 uint16_t handle = writeParams->handle; 00915 LOG(("\r\nDO WRITE: Handle=%d Len=%d\r\n", handle, writeParams->len)); 00916 // CHAR-1 CAPABILITIES 00917 /* capabilitySlotChar is READ ONLY */ 00918 // CHAR-2 ACTIVE SLOT 00919 if (handle == activeSlotChar->getValueHandle()) { 00920 LOG(("Write: Active Slot Handle=%d\r\n", handle)); 00921 uint8_t slot = *(writeParams->data); 00922 LOG(("Active Slot=%d\r\n", slot)); 00923 // Ensure slot does not exceed limit, or set highest slot 00924 if (slot < MAX_ADV_SLOTS) { 00925 activeSlot = slot; 00926 } 00927 ble.gattServer().write(activeSlotChar->getValueHandle(), &activeSlot, sizeof(uint8_t)); 00928 // CHAR-3 ADV INTERVAL 00929 } else if (handle == advIntervalChar->getValueHandle()) { 00930 LOG(("Write: Interval Handle=%d\r\n", handle)); 00931 uint16_t interval = correctAdvertisementPeriod(swapEndian(*((uint16_t *)(writeParams->data)))); 00932 slotAdvIntervals[activeSlot] = interval; // Store this value for reading 00933 uint16_t beAdvInterval = swapEndian(slotAdvIntervals[activeSlot]); 00934 ble.gattServer().write(advIntervalChar->getValueHandle(), reinterpret_cast<uint8_t *>(&beAdvInterval), sizeof(uint16_t)); 00935 // CHAR-4 RADIO TX POWER 00936 } else if (handle == radioTxPowerChar->getValueHandle()) { 00937 LOG(("Write: RADIO Power Handle=%d\r\n", handle)); 00938 int8_t radioTxPower = *(writeParams->data); 00939 uint8_t index = radioTxPowerToIndex(radioTxPower); 00940 radioTxPower = radioTxPowerLevels[index]; // Power now corrected to nearest allowed power 00941 slotRadioTxPowerLevels[activeSlot] = radioTxPower; // Store by slot number 00942 int8_t advTxPower = advTxPowerLevels[index]; // Determine adv power equivalent 00943 slotAdvTxPowerLevels[activeSlot] = advTxPower; 00944 setFrameTxPower(activeSlot, advTxPower); // Set the actual frame radio TxPower for this slot 00945 ble.gattServer().write(radioTxPowerChar->getValueHandle(), reinterpret_cast<uint8_t *>(&radioTxPower), sizeof(int8_t)); 00946 // CHAR-5 ADV TX POWER 00947 } else if (handle == advTxPowerChar->getValueHandle()) { 00948 LOG(("Write: ADV Power Handle=%d\r\n", handle)); 00949 int8_t advTxPower = *(writeParams->data); 00950 slotAdvTxPowerLevels[activeSlot] = advTxPower; 00951 setFrameTxPower(activeSlot, advTxPower); // Update the actual frame Adv TxPower for this slot 00952 ble.gattServer().write(advTxPowerChar->getValueHandle(), reinterpret_cast<uint8_t *>(&advTxPower), sizeof(int8_t)); 00953 // CHAR-6 LOCK STATE 00954 } else if (handle == lockStateChar->getValueHandle()) { 00955 LOG(("Write: Lock State Handle=%d\r\n", handle)); 00956 uint8_t newLockState = *(writeParams->data); 00957 if ((writeParams->len == sizeof(uint8_t)) || (writeParams->len == sizeof(uint8_t) + sizeof(Lock_t))) { 00958 if ((newLockState == LOCKED) || (newLockState == UNLOCKED) || (newLockState == UNLOCKED_AUTO_RELOCK_DISABLED)) { 00959 lockState = newLockState; 00960 } 00961 } 00962 if ((newLockState == LOCKED) && (writeParams->len == (sizeof(uint8_t) + sizeof(Lock_t))) ) { 00963 // And sets the new secret lock code if present 00964 uint8_t encryptedNewKey[sizeof(Lock_t)]; 00965 uint8_t newKey[sizeof(Lock_t)]; 00966 memcpy(encryptedNewKey, (writeParams->data)+1, sizeof(Lock_t)); 00967 // Decrypt the new key 00968 aes128Decrypt(unlockKey, encryptedNewKey, newKey); 00969 memcpy(unlockKey, newKey, sizeof(Lock_t)); 00970 } 00971 ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(uint8_t)); 00972 // CHAR-7 UNLOCK 00973 } else if (handle == unlockChar->getValueHandle()) { 00974 LOG(("Write: Unlock Handle=%d\r\n", handle)); 00975 // NOTE: Actual comparison with unlock code is done in: 00976 // writeUnlockAuthorizationCallback(...) which is executed before this method call. 00977 lockState = UNLOCKED; 00978 // Regenerate challenge and expected unlockToken for Next unlock operation 00979 generateRandom(challenge, sizeof(Lock_t)); 00980 aes128Encrypt(unlockKey, challenge, unlockToken); 00981 // Update Chars 00982 ble.gattServer().write(unlockChar->getValueHandle(), reinterpret_cast<uint8_t *>(challenge), sizeof(Lock_t)); // Update the challenge 00983 ble.gattServer().write(lockStateChar->getValueHandle(), reinterpret_cast<uint8_t *>(&lockState), sizeof(uint8_t)); // Update the lock 00984 // CHAR-8 PUBLIC ECDH KEY 00985 /* PublicEchdChar is READ ONLY */ 00986 // CHAR-9 EID INDENTITY KEY 00987 /* EidIdentityChar is READ ONLY */ 00988 // CHAR-10 ADV DATA 00989 } else if (handle == advSlotDataChar->getValueHandle()) { 00990 LOG(("Write: Adv Slot DATA Handle=%d\r\n", handle)); 00991 uint8_t* frame = slotToFrame(activeSlot); 00992 int8_t advTxPower = slotAdvTxPowerLevels[activeSlot]; 00993 uint8_t writeFrameFormat = *(writeParams->data); 00994 uint8_t writeFrameLen = (writeParams->len); 00995 uint8_t writeData[34]; 00996 uint8_t serverPublicEcdhKey[32]; 00997 00998 if (writeFrameLen != 0) { 00999 writeFrameLen--; // Remove the Format byte from the count 01000 } else { 01001 writeFrameFormat = UNDEFINED_FRAME_FORMAT; // Undefined format 01002 } 01003 01004 memcpy(writeData, (writeParams->data) + 1, writeFrameLen); 01005 LOG(("ADV Data Write=%d,%d\r\n", writeFrameFormat, writeFrameLen)); 01006 switch(writeFrameFormat) { 01007 case UIDFrame::FRAME_TYPE_UID: 01008 if (writeFrameLen == 16) { 01009 uidFrame.setData(frame, advTxPower,reinterpret_cast<const uint8_t *>((writeParams->data) + 1)); 01010 slotFrameTypes[activeSlot] = EDDYSTONE_FRAME_UID; 01011 } else if (writeFrameLen == 0) { 01012 uidFrame.clearFrame(frame); 01013 } 01014 break; 01015 case URLFrame::FRAME_TYPE_URL: 01016 if (writeFrameLen <= 18) { 01017 urlFrame.setData(frame, advTxPower, reinterpret_cast<const uint8_t*>((writeParams->data) + 1), writeFrameLen ); 01018 slotFrameTypes[activeSlot] = EDDYSTONE_FRAME_URL; 01019 } else if (writeFrameLen == 0) { 01020 urlFrame.clearFrame(frame); 01021 } 01022 break; 01023 case TLMFrame::FRAME_TYPE_TLM: 01024 if (writeFrameLen == 0) { 01025 updateRawTLMFrame(frame); 01026 tlmFrame.setData(frame); 01027 int slot = getEidSlot(); 01028 LOG(("WRITE: Testing if TLM or ETLM=%d\r\n", slot)); 01029 if (slot != NO_EID_SLOT_SET) { 01030 LOG(("WRITE: Configuring ETLM Slot time=%d\r\n", timeSinceBootTimer.read_ms() / 1000)); 01031 tlmFrame.encryptData(frame, slotEidIdentityKeys[slot], slotEidRotationPeriodExps[slot], timeSinceBootTimer.read_ms() / 1000); 01032 } 01033 slotFrameTypes[activeSlot] = EDDYSTONE_FRAME_TLM; 01034 } 01035 break; 01036 case EIDFrame::FRAME_TYPE_EID: 01037 LOG(("EID Len=%d\r\n", writeFrameLen)); 01038 if (writeFrameLen == 17) { 01039 // Least secure 01040 LOG(("EID Insecure branch\r\n")); 01041 aes128Decrypt(unlockKey, writeData, slotEidIdentityKeys[activeSlot]); 01042 slotEidRotationPeriodExps[activeSlot] = writeData[16]; // index 16 is the exponent 01043 ble.gattServer().write(eidIdentityKeyChar->getValueHandle(), reinterpret_cast<uint8_t *>(&writeData), sizeof(EidIdentityKey_t)); 01044 } else if (writeFrameLen == 33 ) { 01045 // Most secure 01046 memcpy(serverPublicEcdhKey, writeData, 32); 01047 ble.gattServer().write(publicEcdhKeyChar->getValueHandle(), reinterpret_cast<uint8_t *>(&serverPublicEcdhKey), sizeof(PublicEcdhKey_t)); 01048 LOG(("ServerPublicEcdhKey=")); logPrintHex(serverPublicEcdhKey, 32); 01049 slotEidRotationPeriodExps[activeSlot] = writeData[32]; // index 32 is the exponent 01050 LOG(("Exponent=%i\r\n", writeData[32])); 01051 LOG(("genBeaconKeyRC=%x\r\n", genBeaconKeyRC)); 01052 LOG(("BeaconPrivateEcdhKey=")); logPrintHex(privateEcdhKey, 32); 01053 LOG(("BeaconPublicEcdhKey=")); logPrintHex(publicEcdhKey, 32); 01054 LOG(("genECDHShareKey\r\n")); 01055 int rc = eidFrame.genEcdhSharedKey(privateEcdhKey, publicEcdhKey, serverPublicEcdhKey, slotEidIdentityKeys[activeSlot]); 01056 LOG(("Gen Keys RC = %x\r\n", rc)); 01057 LOG(("Generated eidIdentityKey=")); logPrintHex(slotEidIdentityKeys[activeSlot], 16); 01058 aes128Encrypt(unlockKey, slotEidIdentityKeys[activeSlot], encryptedEidIdentityKey); 01059 LOG(("encryptedEidIdentityKey=")); logPrintHex(encryptedEidIdentityKey, 16); 01060 ble.gattServer().write(eidIdentityKeyChar->getValueHandle(), reinterpret_cast<uint8_t *>(&encryptedEidIdentityKey), sizeof(EidIdentityKey_t)); 01061 } else if (writeFrameLen == 0) { 01062 // Reset eidFrame 01063 eidFrame.clearFrame(frame); 01064 break; 01065 } else { 01066 break; // Do nothing, this is not a recognized Frame length 01067 } 01068 // Establish the new frame type 01069 slotFrameTypes[activeSlot] = EDDYSTONE_FRAME_EID; 01070 nextEidSlot = activeSlot; // This was the last one updated 01071 LOG(("update Eid Frame\r\n")); 01072 // Generate ADV frame packet from EidIdentity Key 01073 eidFrame.update(frame, slotEidIdentityKeys[activeSlot], slotEidRotationPeriodExps[activeSlot], timeSinceBootTimer.read_ms() / 1000); 01074 LOG(("END update Eid Frame\r\n")); 01075 break; 01076 default: 01077 frame[0] = 0; // Frame format unknown so clear the entire frame by writing 0 to its length 01078 break; 01079 } 01080 // Read takes care of setting the Characteristic Value 01081 // CHAR-11 FACTORY RESET 01082 } else if (handle == factoryResetChar->getValueHandle() && (*((uint8_t *)writeParams->data) != 0)) { 01083 LOG(("Write: Factory Reset: Handle=%d\r\n", handle)); 01084 // Reset params to default values 01085 doFactoryReset(); 01086 // Update all characteristics based on params 01087 updateCharacteristicValues(); 01088 // CHAR-12 REMAIN CONNECTABLE 01089 } else if (handle == remainConnectableChar->getValueHandle()) { 01090 LOG(("Write: Remain Connectable Handle=%d\r\n", handle)); 01091 remainConnectable = *(writeParams->data); 01092 ble.gattServer().write(remainConnectableChar->getValueHandle(), &remainConnectable, sizeof(uint8_t)); 01093 } 01094 01095 } 01096 01097 void EddystoneService::setFrameTxPower(uint8_t slot, int8_t advTxPower) { 01098 uint8_t* frame = slotToFrame(slot); 01099 uint8_t frameType = slotFrameTypes[slot]; 01100 switch (frameType) { 01101 case UIDFrame::FRAME_TYPE_UID: 01102 uidFrame.setAdvTxPower(frame, advTxPower); 01103 break; 01104 case URLFrame::FRAME_TYPE_URL: 01105 urlFrame.setAdvTxPower(frame, advTxPower); 01106 break; 01107 case EIDFrame::FRAME_TYPE_EID: 01108 eidFrame.setAdvTxPower(frame, advTxPower); 01109 break; 01110 } 01111 } 01112 01113 uint8_t EddystoneService::radioTxPowerToIndex(int8_t txPower) { 01114 // NOTE: txPower is an 8-bit signed number 01115 uint8_t size = sizeof(PowerLevels_t); 01116 // Look for the value in range (or next biggest value) 01117 for (uint8_t i = 0; i < size; i++) { 01118 if (txPower <= radioTxPowerLevels[i]) { 01119 return i; 01120 } 01121 } 01122 return size - 1; 01123 } 01124 01125 /** AES128 encrypts a 16-byte input array with a key, resulting in a 16-byte output array */ 01126 void EddystoneService::aes128Encrypt(uint8_t key[], uint8_t input[], uint8_t output[]) { 01127 mbedtls_aes_context ctx; 01128 mbedtls_aes_init(&ctx); 01129 mbedtls_aes_setkey_enc(&ctx, key, 8 * sizeof(Lock_t)); 01130 mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, input, output); 01131 mbedtls_aes_free(&ctx); 01132 } 01133 01134 /** AES128 decrypts a 16-byte input array with a key, resulting in a 16-byte output array */ 01135 void EddystoneService::aes128Decrypt(uint8_t key[], uint8_t input[], uint8_t output[]) { 01136 mbedtls_aes_context ctx; 01137 mbedtls_aes_init(&ctx); 01138 mbedtls_aes_setkey_dec(&ctx, key, 8 * sizeof(Lock_t)); 01139 mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_DECRYPT, input, output); 01140 mbedtls_aes_free(&ctx); 01141 } 01142 01143 01144 01145 #ifdef HARDWARE_RANDOM_NUM_GENERATOR 01146 // Generates a set of random values in byte array[size] based on hardware source 01147 void EddystoneService::generateRandom(uint8_t ain[], int size) { 01148 mbedtls_entropy_context entropy; 01149 mbedtls_entropy_init(&entropy); 01150 // init entropy source 01151 eddystoneRegisterEntropySource(&entropy); 01152 mbedtls_ctr_drbg_context ctr_drbg; 01153 mbedtls_ctr_drbg_init(&ctr_drbg); 01154 mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); 01155 mbedtls_ctr_drbg_random(&ctr_drbg, ain, size); 01156 mbedtls_ctr_drbg_free(&ctr_drbg); 01157 mbedtls_entropy_free(&entropy); 01158 return; 01159 } 01160 #else 01161 // Generates a set of random values in byte array[size] seeded by the clock(ms) 01162 void EddystoneService::generateRandom(uint8_t ain[], int size) { 01163 int i; 01164 // Random seed based on boot time in milliseconds 01165 srand(timeSinceBootTimer.read_ms()); 01166 for (i = 0; i < size; i++) { 01167 ain[i] = rand() % 256; 01168 } 01169 return; 01170 } 01171 #endif 01172 01173 /* 01174 // ALTERNATE Better Random number generator (but has Memory usage issues) 01175 // Generates a set of random values in byte array[size] 01176 01177 */ 01178 01179 /** Reverse Even sized Array endianess: Big to Little or Little to Big */ 01180 void EddystoneService::swapEndianArray(uint8_t ptrIn[], uint8_t ptrOut[], int size) { 01181 int i; 01182 for (i = 0; i < size; i++) { 01183 ptrOut[i] = ptrIn[size - i - 1]; 01184 } 01185 return; 01186 } 01187 01188 /** Reverse endianess: Big to Little or Little to Big */ 01189 uint16_t EddystoneService::swapEndian(uint16_t arg) { 01190 return (arg / 256) + (arg % 256) * 256; 01191 } 01192 01193 uint16_t EddystoneService::correctAdvertisementPeriod(uint16_t beaconPeriodIn) const 01194 { 01195 /* Re-map beaconPeriod to within permissible bounds if necessary. */ 01196 if (beaconPeriodIn != 0) { 01197 if (beaconPeriodIn < ble.gap().getMinNonConnectableAdvertisingInterval()) { 01198 return ble.gap().getMinNonConnectableAdvertisingInterval(); 01199 } else if (beaconPeriodIn > ble.gap().getMaxAdvertisingInterval()) { 01200 return ble.gap().getMaxAdvertisingInterval(); 01201 } 01202 } 01203 return beaconPeriodIn; 01204 } 01205 01206 void EddystoneService::logPrintHex(uint8_t* a, int len) { 01207 for (int i = 0; i < len; i++) { 01208 LOG(("%x%x", a[i] >> 4, a[i] & 0x0f )); 01209 } 01210 LOG(("\r\n")); 01211 } 01212 01213 void EddystoneService::setRandomMacAddress(void) { 01214 #ifdef EID_RANDOM_MAC 01215 uint8_t macAddress[6]; // 48 bit Mac Address 01216 generateRandom(macAddress, 6); 01217 macAddress[5] |= 0xc0; // Ensure upper two bits are 11's for Random Add 01218 ble.setAddress(BLEProtocol::AddressType::RANDOM_STATIC, macAddress); 01219 #endif 01220 } 01221 01222 int EddystoneService::getEidSlot(void) { 01223 int eidSlot = NO_EID_SLOT_SET; // by default; 01224 for (int i = 0; i < MAX_ADV_SLOTS; i++) { 01225 if (slotFrameTypes[nextEidSlot] == EDDYSTONE_FRAME_EID) { 01226 eidSlot = nextEidSlot; 01227 nextEidSlot = (nextEidSlot-1) % MAX_ADV_SLOTS; 01228 break; 01229 } 01230 nextEidSlot = (nextEidSlot-1) % MAX_ADV_SLOTS; // ensure the slot numbers wrap 01231 } 01232 return eidSlot; 01233 } 01234 01235 01236 const uint8_t EddystoneService::slotDefaultUids[MAX_ADV_SLOTS][16] = EDDYSTONE_DEFAULT_SLOT_UIDS; 01237 01238 const uint8_t EddystoneService::slotDefaultEidIdentityKeys[MAX_ADV_SLOTS][16] = EDDYSTONE_DEFAULT_SLOT_EID_IDENTITY_KEYS; 01239 01240 const uint8_t EddystoneService::allSlotsDefaultEid[8] = {0,0,0,0,0,0,0,0};
Generated on Thu Jul 14 2022 09:28:18 by
1.7.2