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 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: "); LOG0("Accessory ID: ");
charPrintRow(accessory.ID,17); charPrintRow(accessory.ID,17);
LOG0(" LTPK: "); LOG0(" LTPK: ");
@ -114,7 +124,8 @@ void HAPClient::init(){
printControllers(); 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_PublicKey,384,"PUBKEY");
tlv8.create(kTLVType_Method,1,"METHOD"); tlv8.create(kTLVType_Method,1,"METHOD");
tlv8.create(kTLVType_Salt,16,"SALT"); tlv8.create(kTLVType_Salt,16,"SALT");
@ -476,7 +487,7 @@ int HAPClient::postPairSetupURL(){
case pairState_M5: // 'Exchange Request' 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"); LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6> tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
@ -526,7 +537,7 @@ int HAPClient::postPairSetupURL(){
tlv8.print(2); // print decrypted TLV data tlv8.print(2); // print decrypted TLV data
LOG2("------- END DECRYPTED TLVS! -------\n"); 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"); 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.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M6); // set State=<M6> tlv8.val(kTLVType_State,pairState_M6); // set State=<M6>
@ -679,7 +690,7 @@ int HAPClient::postPairVerifyURL(){
case pairState_M1: // 'Verify Start Request' 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"); LOG0("\n*** ERROR: Required 'PublicKey' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2> tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
@ -745,7 +756,7 @@ int HAPClient::postPairVerifyURL(){
case pairState_M3: // 'Verify Finish Request' 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"); LOG0("\n*** ERROR: Required 'EncryptedData' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4> tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
@ -782,7 +793,7 @@ int HAPClient::postPairVerifyURL(){
tlv8.print(2); // print decrypted TLV data tlv8.print(2); // print decrypted TLV data
LOG2("------- END DECRYPTED TLVS! -------\n"); 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"); 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.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4> tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
@ -905,7 +916,7 @@ int HAPClient::postPairingsURL(){
case 3: case 3:
LOG1("Add...\n"); 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"); 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.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2> tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
@ -944,7 +955,7 @@ int HAPClient::postPairingsURL(){
case 4: case 4:
LOG1("Remove...\n"); 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"); LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n");
tlv8.clear(); // clear TLV records tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2> tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
@ -1683,21 +1694,13 @@ int HAPClient::listControllers(uint8_t *tlvBuf){
tlv8.clear(); tlv8.clear();
tlv8.val(kTLVType_State,pairState_M2); tlv8.val(kTLVType_State,pairState_M2);
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots for(auto it=controllerList.begin();it!=controllerList.end();it++){
if(controllers[i].allocated){ if((*it).allocated){
if(tlv8.val(kTLVType_State)==-1) // if State is not set then this is not the first controller found
if(tlv8.val(kTLVType_State)==-1){ // if State is not set then this is not the first controller found tlv8.val(kTLVType_Separator,1);
n=tlv8.separate(tlvBuf); // add separator tlv8.val(kTLVType_Permissions,(*it).admin);
nBytes+=n; tlv8.buf(kTLVType_Identifier,(*it).ID,36);
if(tlvBuf){ tlv8.buf(kTLVType_PublicKey,(*it).LTPK,32);
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);
n=tlv8.pack(tlvBuf); n=tlv8.pack(tlvBuf);
nBytes+=n; nBytes+=n;
if(tlvBuf){ if(tlvBuf){
@ -1717,24 +1720,41 @@ void HAPClient::printControllers(int minLogLevel){
if(homeSpan.logLevel<minLogLevel) if(homeSpan.logLevel<minLogLevel)
return; return;
int n=0; if(controllerList.empty()){
Serial.printf("No Paired Controllers\n");
return;
}
for(int i=0;i<MAX_CONTROLLERS;i++){ // loop over all controller slots for(auto it=controllerList.begin();it!=controllerList.end();it++){
if(controllers[i].allocated){ Serial.printf("Paired Controller: ");
Serial.printf("Paired Controller: "); charPrintRow((*it).ID,36);
charPrintRow(controllers[i].ID,36); Serial.printf("%s LTPK: ",(*it).admin?" (admin)":" (regular)");
Serial.printf("%s LTPK: ",controllers[i].admin?" (admin)":" (regular)"); hexPrintRow((*it).LTPK,32);
hexPrintRow(controllers[i].LTPK,32); Serial.printf("\n");
Serial.printf("\n"); }
n++; }
}
//////////////////////////////////////
void HAPClient::saveControllers(){
if(controllerList.empty()){
nvs_erase_key(hapNVS,"CONTROLLERS");
return;
} }
if(n==0) int n=0;
Serial.printf("No Paired Controllers\n"); 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(){ Nonce::Nonce(){
@ -1766,13 +1786,14 @@ void Nonce::inc(){
// instantiate all static HAP Client structures and data // 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::hapNVS;
nvs_handle HAPClient::srpNVS; nvs_handle HAPClient::srpNVS;
HKDF HAPClient::hkdf; HKDF HAPClient::hkdf;
pairState HAPClient::pairStatus; pairState HAPClient::pairStatus;
Accessory HAPClient::accessory; Accessory HAPClient::accessory;
Controller HAPClient::controllers[MAX_CONTROLLERS]; Controller HAPClient::controllers[MAX_CONTROLLERS];
list<Controller> HAPClient::controllerList;
SRP6A HAPClient::srp; SRP6A HAPClient::srp;
int HAPClient::conNum; 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_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 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 hapNVS; // handle for non-volatile-storage of HAP data
static nvs_handle srpNVS; // handle for non-volatile-storage of SRP 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 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 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 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 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 static int conNum; // connection number - used to keep track of per-connection EV notifications
// individual structures and data defined for each Hap Client connection // 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 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 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 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 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 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 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]){ 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': { case 's': {
LOG0("\n*** HomeSpan Status ***\n\n"); LOG0("\n*** HomeSpan Status ***\n\n");

View File

@ -38,6 +38,7 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <unordered_set> #include <unordered_set>
#include <list>
#include <nvs.h> #include <nvs.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <esp_now.h> #include <esp_now.h>
@ -55,6 +56,7 @@
using std::vector; using std::vector;
using std::unordered_map; using std::unordered_map;
using std::unordered_set; using std::unordered_set;
using std::list;
enum { enum {
GET_AID=1, 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) 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); // 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, 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) 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 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 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 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 }; // TLV
@ -129,8 +129,12 @@ int TLV<tagType, maxTags>::val(tagType tag){
tlv_t *tlv=find(tag); tlv_t *tlv=find(tag);
if(tlv && tlv->len>0) if(tlv && tlv->len>=0){
return(tlv->val[0]); if(tlv->maxLen>0)
return(tlv->val[0]);
else
return(0);
}
return(-1); return(-1);
} }
@ -144,8 +148,9 @@ int TLV<tagType, maxTags>::val(tagType tag, uint8_t val){
tlv_t *tlv=find(tag); tlv_t *tlv=find(tag);
if(tlv){ if(tlv){
tlv->val[0]=val; if(tlv->maxLen>0)
tlv->len=1; tlv->val[0]=val;
tlv->len=(tlv->maxLen>0);
cLen+=tlv->len+2; cLen+=tlv->len+2;
return(val); return(val);
} }
@ -188,6 +193,28 @@ uint8_t *TLV<tagType, maxTags>::buf(tagType tag, int len){
return(NULL); 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() // TLV print()
@ -199,7 +226,7 @@ void TLV<tagType, maxTags>::print(int minLogLevel){
for(int i=0;i<numTags;i++){ 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); Serial.printf("%s(%d) ",tlv[i].name,tlv[i].len);
for(int j=0;j<tlv[i].len;j++) for(int j=0;j<tlv[i].len;j++)
@ -209,21 +236,7 @@ void TLV<tagType, maxTags>::print(int minLogLevel){
} // len>0 } // len>0
} // loop over all TLVs } // 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) // TLV pack(tlvBuf)
@ -234,19 +247,23 @@ int TLV<tagType, maxTags>::pack(uint8_t *tlvBuf){
int n=0; int n=0;
int nBytes; int nBytes;
for(int i=0;i<numTags;i++){ for(int i=0;i<numTags;i++){
if((nBytes=tlv[i].len)>0){ if((nBytes=tlv[i].len)>=0){
for(int j=0;j<tlv[i].len;j+=255,nBytes-=255){ int j=0;
do{
int wBytes=nBytes>255?255:nBytes;
if(tlvBuf!=NULL){ if(tlvBuf!=NULL){
*tlvBuf++=tlv[i].tag; *tlvBuf++=tlv[i].tag;
*tlvBuf++=nBytes>255?255:nBytes; *tlvBuf++=wBytes;
memcpy(tlvBuf,tlv[i].val+j,nBytes>255?255:nBytes); memcpy(tlvBuf,tlv[i].val+j,wBytes);
tlvBuf+=nBytes>255?255:nBytes; tlvBuf+=wBytes;
} }
n+=(nBytes>255?255:nBytes)+2; n+=wBytes+2;
} // j-loop j+=wBytes;
} // len>0 nBytes-=wBytes;
} while(nBytes>0);
} // len>=0
} // loop over all TLVs } // loop over all TLVs

View File

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