Refactored Controller Structure and Add/Remove/List/Save Functions

Changed fixed-size array `struct Controller[MAX_CONTROLLERS]` to a dynamic linked-list of Controllers.  Re-coded all related functions.  MAX_CONTROLLERS no sets the size of any buffers but is only used to limit the ultimate size of the linked-list.

Saved about 1K of RAM since most of the time there are only 2 Controllers defined (as opposed to the 16 allowed).
This commit is contained in:
Gregg 2023-08-03 22:29:31 -05:00
parent 5a356432b3
commit 3396a5ff96
4 changed files with 164 additions and 212 deletions

View File

@ -80,7 +80,7 @@ void HAPClient::init(){
if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS
nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data
} else {
LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n");
LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n\n");
uint8_t buf[6];
char cBuf[18];
@ -95,21 +95,9 @@ void HAPClient::init(){
nvs_commit(hapNVS); // commit to NVS
}
if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS
nvs_get_blob(hapNVS,"CONTROLLERS",controllers,&len); // retrieve data
} else {
LOG0("Initializing storage for Paired Controllers data...\n\n");
HAPClient::removeControllers(); // clear all Controller data
nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update data
nvs_commit(hapNVS); // commit to NVS
}
if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS
TempBuffer <Controller> tBuf(len/sizeof(Controller));
nvs_get_blob(hapNVS,"CONTROLLERS",tBuf.get(),&len); // retrieve data
Serial.printf("*** SIZE %d ***\n",tBuf.size());
for(int i=0;i<tBuf.size();i++){
if(tBuf.get()[i].allocated)
controllerList.push_back(tBuf.get()[i]);
@ -583,9 +571,6 @@ int HAPClient::postPairSetupURL(){
addController(iosDevicePairingID,iosDeviceLTPK,true); // save Pairing ID and LTPK for this Controller with admin privileges
nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update data
nvs_commit(hapNVS); // commit to NVS
// Now perform the above steps in reverse to securely transmit the AccessoryLTPK to the Controller (HAP Section 5.6.6.2)
uint8_t accessoryX[32];
@ -800,12 +785,14 @@ int HAPClient::postPairVerifyURL(){
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
tlvRespond(); // send response to client
return(0);
};
}
Controller *tPair; // temporary pointer to Controller
if(!(tPair=findController(tlv8.buf(kTLVType_Identifier)))){
LOG0("\n*** ERROR: Unrecognized Controller PairingID\n\n");
LOG0("\n*** ERROR: Unrecognized Controller ID: ");
charPrintRow(tlv8.buf(kTLVType_Identifier),36,2);
LOG0("\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
@ -813,6 +800,10 @@ int HAPClient::postPairVerifyURL(){
return(0);
}
LOG2("\n*** Verifying session with Controller ID: ");
charPrintRow(tPair->ID,36,2);
LOG2("...\n");
size_t iosDeviceInfoLen=32+36+32;
uint8_t iosDeviceInfo[iosDeviceInfoLen];
@ -833,7 +824,7 @@ int HAPClient::postPairVerifyURL(){
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
tlvRespond(); // send response to client (unencrypted since cPair=NULL)
cPair=tPair; // save Controller for this connection slot - connection is not verified and should be encrypted going forward
cPair=tPair; // save Controller for this connection slot - connection is now verified and should be encrypted going forward
hkdf.create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey (HAP Section 6.5.2)
hkdf.create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey (HAP Section 6.5.2)
@ -897,8 +888,6 @@ int HAPClient::postPairingsURL(){
return(0);
}
Controller *newCont;
LOG1("In Post Pairings #");
LOG1(conNum);
LOG1(" (");
@ -913,69 +902,58 @@ int HAPClient::postPairingsURL(){
switch(tlv8.val(kTLVType_Method)){
case 3:
case 3: {
LOG1("Add...\n");
if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Permissions)){
LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
break;
}
tlv8.clear();
tlv8.val(kTLVType_Error,tagError_Unknown);
if(!cPair->admin){
} else if(!cPair->admin){
LOG0("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
break;
tlv8.clear();
tlv8.val(kTLVType_Error,tagError_Authentication);
} else {
tagError err=addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions));
tlv8.clear();
if(err!=tagError_None)
tlv8.val(kTLVType_Error,err);
}
if((newCont=findController(tlv8.buf(kTLVType_Identifier))) && memcmp(tlv8.buf(kTLVType_PublicKey),newCont->LTPK,32)){ // requested Controller already exists, but LTPKs don't match
LOG0("\n*** ERROR: Invalid request to update the LTPK of an exsiting Controller\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown
break;
tlv8.val(kTLVType_State,pairState_M2);
tlvRespond();
return(1);
}
if(!addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false)){
LOG0("\n*** ERROR: Can't pair more than %d Controllers\n\n",MAX_CONTROLLERS);
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers
break;
}
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
break;
case 4:
case 4: {
LOG1("Remove...\n");
uint8_t id[36];
if(!tlv8.len(kTLVType_Identifier)){
LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data)
break;
}
tlv8.clear();
tlv8.val(kTLVType_Error,tagError_Unknown);
if(!cPair->admin){
} else if(!cPair->admin){
LOG0("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication
break;
tlv8.clear();
tlv8.val(kTLVType_Error,tagError_Authentication);
} else {
memcpy(id,tlv8.buf(kTLVType_Identifier),36);
tlv8.clear();
}
removeController(tlv8.buf(kTLVType_Identifier));
tlv8.val(kTLVType_State,pairState_M2);
tlvRespond(); // must send response before removing Controller below
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
break;
if(tlv8.val(kTLVType_Error)==-1)
removeController(id);
return(1);
}
case 5: {
LOG1("List...\n");
@ -992,36 +970,16 @@ int HAPClient::postPairingsURL(){
listControllers(tBuf.get());
sendEncrypted(body,tBuf.get(),tBuf.len());
free(body);
return(1);
}
default:
default: {
LOG0("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
break;
}
nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update Controller data
nvs_commit(hapNVS); // commit to NVS
tlvRespond();
// re-check connections and close any (or all) clients as a result of controllers that were removed above
// must be performed AFTER sending the TLV response, since that connection itself may be terminated below
for(int i=0;i<homeSpan.maxConnections;i++){ // loop over all connection slots
if(hap[i]->client){ // if slot is connected
if(!nAdminControllers() || (hap[i]->cPair && !hap[i]->cPair->allocated)){ // accessory unpaired, OR client connection is verified but points to a newly *unallocated* controller
LOG1("*** Terminating Client #");
LOG1(i);
LOG1("\n");
hap[i]->client.stop();
}
} // if client connected
} // loop over all connection slots
} // switch
return(1);
}
@ -1317,7 +1275,7 @@ int HAPClient::getStatusURL(){
mbedtls_version_get_string_full(mbtlsv);
response+="<tr><td>MbedTLS Version:</td><td>" + String(mbtlsv) + "</td></tr>\n";
response+="<tr><td>HomeKit Status:</td><td>" + String(nAdminControllers()?"PAIRED":"NOT PAIRED") + "</td></tr>\n";
response+="<tr><td>HomeKit Status:</td><td>" + String(HAPClient::nAdminControllers()?"PAIRED":"NOT PAIRED") + "</td></tr>\n";
response+="<tr><td>Max Log Entries:</td><td>" + String(homeSpan.webLog.maxEntries) + "</td></tr>\n";
response+="</table>\n";
response+="<p></p>";
@ -1582,9 +1540,9 @@ void HAPClient::charPrintRow(uint8_t *buf, int n, int minLogLevel){
Controller *HAPClient::findController(uint8_t *id){
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(controllers[i].allocated && !memcmp(controllers[i].ID,id,36)) // found matching ID
return(controllers+i); // return with pointer to matching controller
for(auto it=controllerList.begin();it!=controllerList.end();it++){
if(!memcmp((*it).ID,id,36))
return(&*it);
}
return(NULL); // no match
@ -1592,96 +1550,92 @@ Controller *HAPClient::findController(uint8_t *id){
//////////////////////////////////////
Controller *HAPClient::getFreeController(){
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(!controllers[i].allocated) // found free slot
return(controllers+i); // return with pointer to free slot
}
return(NULL); // no free slots
}
//////////////////////////////////////
Controller *HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
Controller *slot;
if((slot=findController(id))){ // found existing controller
memcpy(slot->LTPK,ltpk,32);
slot->admin=admin;
LOG2("\n*** Updated Controller: ");
charPrintRow(id,36,2);
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
return(slot);
}
if((slot=getFreeController())){ // get slot for new controller, if available
slot->allocated=true;
memcpy(slot->ID,id,36);
memcpy(slot->LTPK,ltpk,32);
slot->admin=admin;
LOG2("\n*** Added Controller: ");
charPrintRow(id,36,2);
LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n");
return(slot);
}
return(NULL);
}
//////////////////////////////////////
int HAPClient::nAdminControllers(){
int n=0;
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
n+=(controllers[i].allocated && controllers[i].admin); // count number of allocated controllers with admin privileges
}
for(auto it=controllerList.begin();it!=controllerList.end();it++)
n+=((*it).admin);
return(n);
}
//////////////////////////////////////
void HAPClient::removeControllers(){
tagError HAPClient::addController(uint8_t *id, uint8_t *ltpk, boolean admin){
for(int i=0;i<MAX_CONTROLLERS;i++)
controllers[i].allocated=false;
Controller *cTemp=findController(id);
tagError err=tagError_None;
if(!cTemp){ // new controller
if(controllerList.size()<MAX_CONTROLLERS){
controllerList.emplace_back(id,ltpk,admin); // create and store data
LOG2("\n*** Added Controller: ");
charPrintRow(id,36,2);
LOG2(admin?" (admin)\n\n":" (regular)\n\n");
saveControllers();
} else {
LOG0("\n*** ERROR: Can't pair more than %d Controllers\n\n",MAX_CONTROLLERS);
err=tagError_MaxPeers;
}
} else if(!memcmp(ltpk,cTemp->LTPK,sizeof(cTemp->LTPK))){ // existing controller with same LTPK
LOG2("\n*** Updated Controller: ");
charPrintRow(id,36,2);
LOG2(" from %s to %s\n\n",cTemp->admin?"(admin)":"(regular)",admin?"(admin)":"(regular)");
cTemp->admin=admin;
saveControllers();
} else {
LOG0("\n*** ERROR: Invalid request to update the LTPK of an existing Controller\n\n");
err=tagError_Unknown;
}
return(err);
}
//////////////////////////////////////
void HAPClient::removeController(uint8_t *id){
Controller *slot;
auto it=std::find_if(controllerList.begin(), controllerList.end(), [id](const Controller& cTemp){return(!memcmp(cTemp.ID,id,sizeof(cTemp.ID)));});
if((slot=findController(id))){ // remove controller if found
LOG2("\n*** Removed Controller: ");
if(it==controllerList.end()){
LOG2("\n*** Request to Remove Controller Ignored - Controller Not Found: ");
charPrintRow(id,36,2);
LOG2(slot->admin?" (admin)\n":" (regular)\n");
slot->allocated=false;
LOG2("\n");
return;
}
LOG1("\n*** Removing Controller: ");
charPrintRow((*it).ID,36,2);
LOG1((*it).admin?" (admin)\n":" (regular)\n");
tearDown((*it).ID); // teardown any connections using this Controller
controllerList.erase(it); // remove Controller
if(!nAdminControllers()){ // no more admin Controllers
if(nAdminControllers()==0){ // if no more admins, remove all controllers
removeControllers();
LOG1("That was last Admin Controller! Removing any remaining Regular Controllers and unpairing Accessory\n");
tearDown(NULL); // teardown all remaining connections
controllerList.clear(); // remove all remaining Controllers
mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8)
STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED)
STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED) // set optional Status LED
if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been un-paired
homeSpan.pairCallback(false);
}
LOG2("\n");
} else {
LOG2("\n*** Request to Remove Controller Ignored - Controller Not Found: ");
charPrintRow(id,36,2);
LOG2("\n");
}
saveControllers();
}
//////////////////////////////////////
void HAPClient::tearDown(uint8_t *id){
for(int i=0;i<homeSpan.maxConnections;i++){ // loop over all connection slots
if(hap[i]->client && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,36)))){
LOG1("*** Terminating Client #%d\n",i);
hap[i]->client.stop();
}
}
}
//////////////////////////////////////
@ -1744,11 +1698,8 @@ void HAPClient::saveControllers(){
return;
}
int n=0;
TempBuffer <Controller> tBuf(controllerList.size()); // create temporary buffer to hold Controller data
for(auto it=controllerList.begin();it!=controllerList.end();it++) // store Controller data in temporary buffer
tBuf.get()[n++]=(*it);
std::copy(controllerList.begin(),controllerList.end(),tBuf.get()); // copy data from linked list to buffer
nvs_set_blob(hapNVS,"CONTROLLERS",tBuf.get(),tBuf.len()); // update data
nvs_commit(hapNVS); // commit to NVS
@ -1792,7 +1743,6 @@ nvs_handle HAPClient::srpNVS;
HKDF HAPClient::hkdf;
pairState HAPClient::pairStatus;
Accessory HAPClient::accessory;
Controller HAPClient::controllers[MAX_CONTROLLERS];
list<Controller> HAPClient::controllerList;
SRP6A HAPClient::srp;
int HAPClient::conNum;

View File

@ -50,10 +50,20 @@ struct Nonce {
// Paired Controller Structure for Permanently-Stored Data
struct Controller {
boolean allocated=false; // slot is allocated with Controller data
boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
boolean admin; // Controller has admin privileges
uint8_t ID[36]; // Pairing ID
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
Controller(){}
Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
allocated=true;
admin=ad;
memcpy(ID,id,sizeof(ID));
memcpy(LTPK,ltpk,sizeof(LTPK));
}
};
/////////////////////////////////////////////////
@ -84,14 +94,13 @@ struct HAPClient {
static pairState pairStatus; // tracks pair-setup status
static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup
static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored
static Controller controllers[MAX_CONTROLLERS]; // Paired Controller IDs and ED25519 long-term public keys - permanently stored
static list<Controller> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored
static int conNum; // connection number - used to keep track of per-connection EV notifications
// individual structures and data defined for each Hap Client connection
WiFiClient client; // handle to client
Controller *cPair; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
// These keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period
@ -136,14 +145,13 @@ struct HAPClient {
static void charPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match)
static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots)
static Controller *addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns pointer to Controller slot on success, else NULL
static int nAdminControllers(); // returns number of admin Controllers stored
static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots)
static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns tagError (if any)
static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level
static int listControllers(uint8_t *tlvBuf); // creates and prints a multi-TLV list of Controllers (HAP Section 5.12)
static void saveControllers(); // saves Controller list in NVS
static int nAdminControllers(); // returns number of admin Controller
static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL
static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client

View File

@ -52,6 +52,7 @@ typedef enum {
// HAP Error Codes (HAP Table 5-5)
typedef enum {
tagError_None=0x00,
tagError_Unknown=0x01,
tagError_Authentication=0x02,
tagError_Backoff=0x03,

View File

@ -260,7 +260,7 @@ void Span::pollTask() {
homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context
if(!hap[i]->client){ // client disconnected by server
LOG1("** Disconnecting Client #");
LOG1("** Disconnected Client #");
LOG1(i);
LOG1(" (");
LOG1(millis()/1000);
@ -555,6 +555,8 @@ void Span::processSerialCommand(const char *c){
switch(c[0]){
case 'Z': {
HAPClient::saveControllers();
break;
TempBuffer <uint8_t> tBuf(HAPClient::listControllers(NULL));
HAPClient::listControllers(tBuf.get());
Serial.printf("SIZE = %d\n",tBuf.len());
@ -688,19 +690,10 @@ void Span::processSerialCommand(const char *c){
case 'U': {
HAPClient::removeControllers(); // clear all Controller data
nvs_set_blob(HAPClient::hapNVS,"CONTROLLERS",HAPClient::controllers,sizeof(HAPClient::controllers)); // update data
nvs_commit(HAPClient::hapNVS); // commit to NVS
HAPClient::controllerList.clear(); // clear all Controller data
HAPClient::saveControllers();
LOG0("\n*** HomeSpan Pairing Data DELETED ***\n\n");
for(int i=0;i<maxConnections;i++){ // loop over all connection slots
if(hap[i]->client){ // if slot is connected
LOG1("*** Terminating Client #");
LOG1(i);
LOG1("\n");
hap[i]->client.stop();
}
}
HAPClient::tearDown(NULL); // tear down all verified connections
LOG0("\nDEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8)
@ -1016,12 +1009,10 @@ void Span::processSerialCommand(const char *c){
TempBuffer<char> tBuf(256);
mbedtls_base64_encode((uint8_t *)tBuf.get(),256,&olen,(uint8_t *)&HAPClient::accessory,sizeof(struct Accessory));
LOG0("Accessory data: %s\n",tBuf.get());
for(int i=0;i<HAPClient::MAX_CONTROLLERS;i++){
if(HAPClient::controllers[i].allocated){
mbedtls_base64_encode((uint8_t *)tBuf.get(),256,&olen,(uint8_t *)(HAPClient::controllers+i),sizeof(struct Controller));
for(const auto &cont : HAPClient::controllerList){
mbedtls_base64_encode((uint8_t *)tBuf.get(),256,&olen,(uint8_t *)(&cont),sizeof(struct Controller));
LOG0("Controller data: %s\n",tBuf.get());
}
}
LOG0("\n*** End Pairing Data\n\n");
}
break;
@ -1048,21 +1039,24 @@ void Span::processSerialCommand(const char *c){
LOG0("\n");
}
for(int i=0;i<HAPClient::MAX_CONTROLLERS;i++){
HAPClient::controllerList.clear();
Controller tCont;
while(HAPClient::controllerList.size()<16){
tBuf.get()[0]='\0';
LOG0(">>> Controller data: ");
readSerial(tBuf.get(),199);
if(strlen(tBuf.get())==0){
LOG0("(done)\n");
while(i<HAPClient::MAX_CONTROLLERS) // clear data from remaining controller slots
HAPClient::controllers[i++].allocated=false;
break;
} else {
mbedtls_base64_decode((uint8_t *)(HAPClient::controllers+i),sizeof(struct Controller),&olen,(uint8_t *)tBuf.get(),strlen(tBuf.get()));
mbedtls_base64_decode((uint8_t *)(&tCont),sizeof(struct Controller),&olen,(uint8_t *)tBuf.get(),strlen(tBuf.get()));
if(olen!=sizeof(struct Controller)){
LOG0("\n*** Error in size of Controller data - cloning cancelled. Restarting...\n\n");
reboot();
} else {
HAPClient::charPrintRow(HAPClient::controllers[i].ID,36);
HAPClient::controllerList.push_back(tCont);
HAPClient::charPrintRow(tCont.ID,36);
LOG0("\n");
}
}
@ -1076,8 +1070,7 @@ void Span::processSerialCommand(const char *c){
if(qSave[0]=='y'){
LOG0("(yes)\nData saved! Rebooting...");
nvs_set_blob(HAPClient::hapNVS,"ACCESSORY",&HAPClient::accessory,sizeof(HAPClient::accessory)); // update data
nvs_set_blob(HAPClient::hapNVS,"CONTROLLERS",HAPClient::controllers,sizeof(HAPClient::controllers));
nvs_commit(HAPClient::hapNVS); // commit to NVS
HAPClient::saveControllers();
reboot();
} else
if(qSave[0]=='n'){