Refactored TLV

* Added support for zero-length TLV
* Added SEPARATOR as a formal kTLVType (and updated listControllers() to use)
* Added `uint8_t *buf(tagType tag, uint8_t *src, int len);` to load buffer needing external memcpy (and updated listControllers() to use)
This commit is contained in:
Gregg 2023-07-30 21:37:47 -05:00
parent 17410e825e
commit a84429f930
6 changed files with 126 additions and 71 deletions

View File

@ -106,6 +106,16 @@ 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
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]);
}
}
LOG0("Accessory ID: ");
charPrintRow(accessory.ID,17);
LOG0(" LTPK: ");
@ -114,7 +124,8 @@ void HAPClient::init(){
printControllers();
tlv8.create(kTLVType_State,1,"STATE"); // define the actual TLV records needed for the implementation of HAP; one for each kTLVType needed (HAP Table 5-6)
tlv8.create(kTLVType_Separator,0,"SEPARATOR"); // define the actual TLV records needed for the implementation of HAP; one for each kTLVType needed (HAP Table 5-6)
tlv8.create(kTLVType_State,1,"STATE");
tlv8.create(kTLVType_PublicKey,384,"PUBKEY");
tlv8.create(kTLVType_Method,1,"METHOD");
tlv8.create(kTLVType_Salt,16,"SALT");
@ -476,7 +487,7 @@ int HAPClient::postPairSetupURL(){
case pairState_M5: // 'Exchange Request'
if(!tlv8.buf(kTLVType_EncryptedData)){
if(!tlv8.len(kTLVType_EncryptedData)){
LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
@ -526,7 +537,7 @@ int HAPClient::postPairSetupURL(){
tlv8.print(2); // print decrypted TLV data
LOG2("------- END DECRYPTED TLVS! -------\n");
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Signature)){
if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Signature)){
LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Signature' TLV records for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
@ -679,7 +690,7 @@ int HAPClient::postPairVerifyURL(){
case pairState_M1: // 'Verify Start Request'
if(!tlv8.buf(kTLVType_PublicKey)){
if(!tlv8.len(kTLVType_PublicKey)){
LOG0("\n*** ERROR: Required 'PublicKey' 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>
@ -745,7 +756,7 @@ int HAPClient::postPairVerifyURL(){
case pairState_M3: // 'Verify Finish Request'
if(!tlv8.buf(kTLVType_EncryptedData)){
if(!tlv8.len(kTLVType_EncryptedData)){
LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
@ -782,7 +793,7 @@ int HAPClient::postPairVerifyURL(){
tlv8.print(2); // print decrypted TLV data
LOG2("------- END DECRYPTED TLVS! -------\n");
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_Signature)){
if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_Signature)){
LOG0("\n*** ERROR: One or more of required 'Identifier,' and 'Signature' TLV records for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
@ -905,7 +916,7 @@ int HAPClient::postPairingsURL(){
case 3:
LOG1("Add...\n");
if(!tlv8.buf(kTLVType_Identifier) || !tlv8.buf(kTLVType_PublicKey) || !tlv8.buf(kTLVType_Permissions)){
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>
@ -944,7 +955,7 @@ int HAPClient::postPairingsURL(){
case 4:
LOG1("Remove...\n");
if(!tlv8.buf(kTLVType_Identifier)){
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>
@ -1683,21 +1694,13 @@ int HAPClient::listControllers(uint8_t *tlvBuf){
tlv8.clear();
tlv8.val(kTLVType_State,pairState_M2);
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(controllers[i].allocated){
if(tlv8.val(kTLVType_State)==-1){ // if State is not set then this is not the first controller found
n=tlv8.separate(tlvBuf); // add separator
nBytes+=n;
if(tlvBuf){
tlvBuf+=n;
LOG2("SEPARATOR(0)\n");
}
}
tlv8.val(kTLVType_Permissions,controllers[i].admin);
memcpy(tlv8.buf(kTLVType_Identifier,36),controllers[i].ID,36);
memcpy(tlv8.buf(kTLVType_PublicKey,32),controllers[i].LTPK,32);
for(auto it=controllerList.begin();it!=controllerList.end();it++){
if((*it).allocated){
if(tlv8.val(kTLVType_State)==-1) // if State is not set then this is not the first controller found
tlv8.val(kTLVType_Separator,1);
tlv8.val(kTLVType_Permissions,(*it).admin);
tlv8.buf(kTLVType_Identifier,(*it).ID,36);
tlv8.buf(kTLVType_PublicKey,(*it).LTPK,32);
n=tlv8.pack(tlvBuf);
nBytes+=n;
if(tlvBuf){
@ -1718,23 +1721,40 @@ void HAPClient::printControllers(int minLogLevel){
if(homeSpan.logLevel<minLogLevel)
return;
int n=0;
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots
if(controllers[i].allocated){
Serial.printf("Paired Controller: ");
charPrintRow(controllers[i].ID,36);
Serial.printf("%s LTPK: ",controllers[i].admin?" (admin)":" (regular)");
hexPrintRow(controllers[i].LTPK,32);
Serial.printf("\n");
n++;
}
}
if(n==0)
if(controllerList.empty()){
Serial.printf("No Paired Controllers\n");
return;
}
for(auto it=controllerList.begin();it!=controllerList.end();it++){
Serial.printf("Paired Controller: ");
charPrintRow((*it).ID,36);
Serial.printf("%s LTPK: ",(*it).admin?" (admin)":" (regular)");
hexPrintRow((*it).LTPK,32);
Serial.printf("\n");
}
}
//////////////////////////////////////
void HAPClient::saveControllers(){
if(controllerList.empty()){
nvs_erase_key(hapNVS,"CONTROLLERS");
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);
nvs_set_blob(hapNVS,"CONTROLLERS",tBuf.get(),tBuf.len()); // update data
nvs_commit(hapNVS); // commit to NVS
}
//////////////////////////////////////
Nonce::Nonce(){
@ -1766,13 +1786,14 @@ void Nonce::inc(){
// instantiate all static HAP Client structures and data
TLV<kTLVType,10> HAPClient::tlv8;
TLV<kTLVType,11> HAPClient::tlv8;
nvs_handle HAPClient::hapNVS;
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

@ -77,7 +77,7 @@ struct HAPClient {
static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16)
static const int MAX_ACCESSORIES=41; // maximum number of allowed Acessories (HAP limit=150, but not enough memory in ESP32 to run that many)
static TLV<kTLVType,10> tlv8; // TLV8 structure (HAP Section 14.1) with space for 10 TLV records of type kTLVType (HAP Table 5-6)
static TLV<kTLVType,11> tlv8; // TLV8 structure (HAP Section 14.1) with space for 11 TLV records of type kTLVType (HAP Table 5-6)
static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data
static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data
static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string
@ -85,6 +85,7 @@ struct HAPClient {
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
@ -142,6 +143,7 @@ struct HAPClient {
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 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

@ -554,6 +554,14 @@ void Span::processSerialCommand(const char *c){
switch(c[0]){
case 'Z': {
TempBuffer <uint8_t> tBuf(HAPClient::listControllers(NULL));
HAPClient::listControllers(tBuf.get());
Serial.printf("SIZE = %d\n",tBuf.len());
HAPClient::hexPrintRow(tBuf.get(),tBuf.len());
break;
}
case 's': {
LOG0("\n*** HomeSpan Status ***\n\n");

View File

@ -38,6 +38,7 @@
#include <unordered_map>
#include <vector>
#include <unordered_set>
#include <list>
#include <nvs.h>
#include <ArduinoOTA.h>
#include <esp_now.h>
@ -55,6 +56,7 @@
using std::vector;
using std::unordered_map;
using std::unordered_set;
using std::list;
enum {
GET_AID=1,

View File

@ -55,11 +55,11 @@ public:
int val(tagType tag, uint8_t val); // sets and returns VAL for TLV with matching TAG (or -1 if no match)
uint8_t *buf(tagType tag); // returns VAL Buffer for TLV with matching TAG (or NULL if no match)
uint8_t *buf(tagType tag, int len); // set length and returns VAL Buffer for TLV with matching TAG (or NULL if no match or if LEN>MAX)
uint8_t *buf(tagType tag, uint8_t *src, int len); // copies len bytes of src into VAL buffer, and sets length to len, for TLV with matching TAG; returns VAL Buffer on success, or NULL if no match or if LEN>MAX
int len(tagType tag); // returns LEN for TLV matching TAG (or 0 if TAG is found but LEN not yet set; -1 if no match at all)
void print(int minLogLevel=0); // prints all defined TLVs (those with length>0), subject to specified minimum log level
int unpack(uint8_t *tlvBuf, int nBytes); // unpacks nBytes of TLV content from single byte buffer into individual TLV records (return 1 on success, 0 if fail)
int pack(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte chunks. Returns number of bytes (that would be) stored in buffer
int separate(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs a TLV separator into a single byte buffer. Returns number of bytes (that would be) stored in buffer
}; // TLV
@ -129,8 +129,12 @@ int TLV<tagType, maxTags>::val(tagType tag){
tlv_t *tlv=find(tag);
if(tlv && tlv->len>0)
if(tlv && tlv->len>=0){
if(tlv->maxLen>0)
return(tlv->val[0]);
else
return(0);
}
return(-1);
}
@ -144,8 +148,9 @@ int TLV<tagType, maxTags>::val(tagType tag, uint8_t val){
tlv_t *tlv=find(tag);
if(tlv){
if(tlv->maxLen>0)
tlv->val[0]=val;
tlv->len=1;
tlv->len=(tlv->maxLen>0);
cLen+=tlv->len+2;
return(val);
}
@ -188,6 +193,28 @@ uint8_t *TLV<tagType, maxTags>::buf(tagType tag, int len){
return(NULL);
}
//////////////////////////////////////
// TLV buf(tag, src, len)
template<class tagType, int maxTags>
uint8_t *TLV<tagType, maxTags>::buf(tagType tag, uint8_t *src, int len){
tlv_t *tlv=find(tag);
if(tlv && len<=tlv->maxLen){
tlv->len=len;
cLen+=tlv->len;
for(int i=0;i<tlv->len;i+=255)
cLen+=2;
memcpy(tlv->val,src,len);
return(tlv->val);
}
return(NULL);
}
//////////////////////////////////////
// TLV print()
@ -199,7 +226,7 @@ void TLV<tagType, maxTags>::print(int minLogLevel){
for(int i=0;i<numTags;i++){
if(tlv[i].len>0){
if(tlv[i].len>=0){
Serial.printf("%s(%d) ",tlv[i].name,tlv[i].len);
for(int j=0;j<tlv[i].len;j++)
@ -211,20 +238,6 @@ void TLV<tagType, maxTags>::print(int minLogLevel){
} // loop over all TLVs
}
//////////////////////////////////////
// TLV separate(tlvBuf)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::separate(uint8_t *tlvBuf){
if(tlvBuf){ // load separator
*tlvBuf++=kTLVType_Separator;
*tlvBuf=0;
}
return(2);
}
//////////////////////////////////////
// TLV pack(tlvBuf)
@ -236,17 +249,21 @@ int TLV<tagType, maxTags>::pack(uint8_t *tlvBuf){
for(int i=0;i<numTags;i++){
if((nBytes=tlv[i].len)>0){
for(int j=0;j<tlv[i].len;j+=255,nBytes-=255){
if((nBytes=tlv[i].len)>=0){
int j=0;
do{
int wBytes=nBytes>255?255:nBytes;
if(tlvBuf!=NULL){
*tlvBuf++=tlv[i].tag;
*tlvBuf++=nBytes>255?255:nBytes;
memcpy(tlvBuf,tlv[i].val+j,nBytes>255?255:nBytes);
tlvBuf+=nBytes>255?255:nBytes;
*tlvBuf++=wBytes;
memcpy(tlvBuf,tlv[i].val+j,wBytes);
tlvBuf+=wBytes;
}
n+=(nBytes>255?255:nBytes)+2;
} // j-loop
} // len>0
n+=wBytes+2;
j+=wBytes;
nBytes-=wBytes;
} while(nBytes>0);
} // len>=0
} // loop over all TLVs

View File

@ -48,11 +48,12 @@ class TempBuffer {
bufType *buf;
int nBytes;
int nElements;
public:
TempBuffer(size_t len){
nBytes=len*sizeof(bufType);
TempBuffer(int _nElements) : nElements(_nElements) {
nBytes=nElements*sizeof(bufType);
buf=(bufType *)malloc(nBytes);
if(buf==NULL){
Serial.print("\n\n*** FATAL ERROR: Requested allocation of ");
@ -70,6 +71,10 @@ class TempBuffer {
return(nBytes);
}
int size(){
return(nElements);
}
bufType *get(){
return(buf);
}