@@ -92,12 +92,11 @@ void SensorMesh::loadContacts() {
9292 while (!full) {
9393 ContactInfo c;
9494 uint8_t pub_key[32 ];
95- uint8_t unused;
95+ uint8_t unused[ 5 ] ;
9696
9797 bool success = (file.read (pub_key, 32 ) == 32 );
98- success = success && (file.read (&c.type , 1 ) == 1 );
99- success = success && (file.read (&c.flags , 1 ) == 1 );
100- success = success && (file.read (&unused, 1 ) == 1 );
98+ success = success && (file.read ((uint8_t *) &c.permissions , 2 ) == 2 );
99+ success = success && (file.read (unused, 5 ) == 5 );
101100 success = success && (file.read ((uint8_t *)&c.out_path_len , 1 ) == 1 );
102101 success = success && (file.read (c.out_path , 64 ) == 64 );
103102 success = success && (file.read (c.shared_secret , PUB_KEY_SIZE) == PUB_KEY_SIZE);
@@ -121,16 +120,16 @@ void SensorMesh::loadContacts() {
121120void SensorMesh::saveContacts () {
122121 File file = openWrite (_fs, " /s_contacts" );
123122 if (file) {
124- uint8_t unused = 0 ;
123+ uint8_t unused[5 ];
124+ memset (unused, 0 , sizeof (unused));
125125
126126 for (int i = 0 ; i < num_contacts; i++) {
127127 auto c = &contacts[i];
128- if (c->type == 0 ) continue ; // don't persist guest contacts
128+ if (c->permissions == 0 ) continue ; // skip deleted entries
129129
130130 bool success = (file.write (c->id .pub_key , 32 ) == 32 );
131- success = success && (file.write (&c->type , 1 ) == 1 );
132- success = success && (file.write (&c->flags , 1 ) == 1 );
133- success = success && (file.write (&unused, 1 ) == 1 );
131+ success = success && (file.write ((uint8_t *) &c->permissions , 2 ) == 2 );
132+ success = success && (file.write (unused, 5 ) == 5 );
134133 success = success && (file.write ((uint8_t *)&c->out_path_len , 1 ) == 1 );
135134 success = success && (file.write (c->out_path , 64 ) == 64 );
136135 success = success && (file.write (c->shared_secret , PUB_KEY_SIZE) == PUB_KEY_SIZE);
@@ -141,36 +140,34 @@ void SensorMesh::saveContacts() {
141140 }
142141}
143142
144- uint8_t SensorMesh::handleRequest (bool is_admin , uint32_t sender_timestamp, uint8_t req_type, uint8_t * payload, size_t payload_len) {
143+ uint8_t SensorMesh::handleRequest (uint16_t perms , uint32_t sender_timestamp, uint8_t req_type, uint8_t * payload, size_t payload_len) {
145144 memcpy (reply_data, &sender_timestamp, 4 ); // reflect sender_timestamp back in response packet (kind of like a 'tag')
146145
147- switch (req_type) {
148- case REQ_TYPE_GET_TELEMETRY_DATA: {
149- telemetry.reset ();
150- telemetry.addVoltage (TELEM_CHANNEL_SELF, (float )board.getBattMilliVolts () / 1000 .0f );
151- // query other sensors -- target specific
152- sensors.querySensors (0xFF , telemetry); // allow all telemetry permissions for admin or guest
146+ if (req_type == REQ_TYPE_GET_TELEMETRY_DATA && (perms & PERM_GET_TELEMETRY) != 0 ) {
147+ telemetry.reset ();
148+ telemetry.addVoltage (TELEM_CHANNEL_SELF, (float )board.getBattMilliVolts () / 1000 .0f );
149+ // query other sensors -- target specific
150+ sensors.querySensors (0xFF , telemetry); // allow all telemetry permissions for admin or guest
153151
154- uint8_t tlen = telemetry.getSize ();
155- memcpy (&reply_data[4 ], telemetry.getBuffer (), tlen);
156- return 4 + tlen; // reply_len
157- }
158- case REQ_TYPE_GET_AVG_MIN_MAX: {
159- uint32_t start_secs_ago, end_secs_ago;
160- memcpy (&start_secs_ago, &payload[0 ], 4 );
161- memcpy (&end_secs_ago, &payload[4 ], 4 );
162- uint8_t res1 = payload[8 ]; // reserved for future (extra query params)
163- uint8_t res2 = payload[8 ];
164-
165- MinMaxAvg data[8 ];
166- int n;
167- if (res1 == 0 && res2 == 0 ) {
168- n = querySeriesData (start_secs_ago, end_secs_ago, data, 8 );
169- } else {
170- n = 0 ;
171- }
172- return 0 ; // TODO: encode data[0..n)
152+ uint8_t tlen = telemetry.getSize ();
153+ memcpy (&reply_data[4 ], telemetry.getBuffer (), tlen);
154+ return 4 + tlen; // reply_len
155+ }
156+ if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_GET_MIN_MAX_AVG) != 0 ) {
157+ uint32_t start_secs_ago, end_secs_ago;
158+ memcpy (&start_secs_ago, &payload[0 ], 4 );
159+ memcpy (&end_secs_ago, &payload[4 ], 4 );
160+ uint8_t res1 = payload[8 ]; // reserved for future (extra query params)
161+ uint8_t res2 = payload[8 ];
162+
163+ MinMaxAvg data[8 ];
164+ int n;
165+ if (res1 == 0 && res2 == 0 ) {
166+ n = querySeriesData (start_secs_ago, end_secs_ago, data, 8 );
167+ } else {
168+ n = 0 ;
173169 }
170+ return 0 ; // TODO: encode data[0..n)
174171 }
175172 return 0 ; // unknown command
176173}
@@ -209,6 +206,18 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
209206 return c;
210207}
211208
209+ void SensorMesh::applyContactPermissions (const uint8_t * pubkey, uint16_t perms) {
210+ mesh::Identity id (pubkey);
211+ auto c = putContact (id);
212+
213+ if (perms == 0 ) { // no permissions, remove from contacts
214+ memset (c, 0 , sizeof (*c));
215+ } else {
216+ c->permissions = perms; // update their permissions
217+ }
218+ dirty_contacts_expiry = futureMillis (LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
219+ }
220+
212221void SensorMesh::sendAlert (const char * text) {
213222 int text_len = strlen (text);
214223
@@ -283,12 +292,7 @@ int SensorMesh::getAGCResetInterval() const {
283292}
284293
285294uint8_t SensorMesh::handleLoginReq (const mesh::Identity& sender, const uint8_t * secret, uint32_t sender_timestamp, const uint8_t * data) {
286- bool is_admin;
287- if (strcmp ((char *) data, _prefs.password ) == 0 ) { // check for valid password
288- is_admin = true ;
289- } else if (strcmp ((char *) data, _prefs.guest_password ) == 0 ) { // check guest password
290- is_admin = false ;
291- } else {
295+ if (strcmp ((char *) data, _prefs.password ) != 0 ) { // check for valid password
292296 #if MESH_DEBUG
293297 MESH_DEBUG_PRINTLN (" Invalid password: %s" , &data[4 ]);
294298 #endif
@@ -304,46 +308,61 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
304308 MESH_DEBUG_PRINTLN (" Login success!" );
305309 client->last_timestamp = sender_timestamp;
306310 client->last_activity = getRTCClock ()->getCurrentTime ();
307- client->type = is_admin ? 1 : 0 ;
311+ client->permissions = PERM_IS_ADMIN ;
308312 memcpy (client->shared_secret , secret, PUB_KEY_SIZE);
309313
310- if (is_admin) {
311- // only need to saveContacts() if this is an admin
312- dirty_contacts_expiry = futureMillis (LAZY_CONTACTS_WRITE_DELAY);
313- }
314+ dirty_contacts_expiry = futureMillis (LAZY_CONTACTS_WRITE_DELAY);
314315
315316 uint32_t now = getRTCClock ()->getCurrentTimeUnique ();
316317 memcpy (reply_data, &now, 4 ); // response packets always prefixed with timestamp
317318 reply_data[4 ] = RESP_SERVER_LOGIN_OK;
318319 reply_data[5 ] = 0 ; // NEW: recommended keep-alive interval (secs / 16)
319- reply_data[6 ] = client-> type ;
320+ reply_data[6 ] = 1 ; // 1 = is admin
320321 reply_data[7 ] = 0 ; // FUTURE: reserved
321322 getRNG ()->random (&reply_data[8 ], 4 ); // random blob to help packet-hash uniqueness
322323
323324 return 12 ; // reply length
324325}
325326
327+ void SensorMesh::handleCommand (uint32_t sender_timestamp, char * command, char * reply) {
328+ while (*command == ' ' ) command++; // skip leading spaces
329+
330+ if (strlen (command) > 4 && command[2 ] == ' |' ) { // optional prefix (for companion radio CLI)
331+ memcpy (reply, command, 3 ); // reflect the prefix back
332+ reply += 3 ;
333+ command += 3 ;
334+ }
335+
336+ // handle sensor-specific CLI commands
337+ if (memcmp (command, " setperm " , 8 ) == 0 ) { // format: setperm {pubkey-hex} {permissions-int16}
338+ char * hex = &command[8 ];
339+ char * sp = strchr (hex, ' ' ); // look for separator char
340+ if (sp == NULL || sp - hex != PUB_KEY_SIZE*2 ) {
341+ strcpy (reply, " Err - bad pubkey len" );
342+ } else {
343+ *sp++ = 0 ; // replace space with null terminator
344+
345+ uint8_t pubkey[PUB_KEY_SIZE];
346+ if (mesh::Utils::fromHex (pubkey, PUB_KEY_SIZE, hex)) {
347+ uint16_t perms = atoi (sp);
348+ applyContactPermissions (pubkey, perms);
349+ strcpy (reply, " OK" );
350+ } else {
351+ strcpy (reply, " Err - bad pubkey" );
352+ }
353+ }
354+ } else {
355+ _cli.handleCommand (sender_timestamp, command, reply); // common CLI commands
356+ }
357+ }
358+
326359void SensorMesh::onAnonDataRecv (mesh::Packet* packet, const uint8_t * secret, const mesh::Identity& sender, uint8_t * data, size_t len) {
327360 if (packet->getPayloadType () == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
328361 uint32_t timestamp;
329362 memcpy (×tamp, data, 4 );
330363
331364 data[len] = 0 ; // ensure null terminator
332-
333- uint8_t req_code;
334- uint8_t i = 4 ;
335- if (data[4 ] < 32 ) { // non-print char, is a request code
336- req_code = data[i++];
337- } else {
338- req_code = REQ_TYPE_LOGIN;
339- }
340-
341- uint8_t reply_len;
342- if (req_code == REQ_TYPE_LOGIN) {
343- reply_len = handleLoginReq (sender, secret, timestamp, &data[i]);
344- } else {
345- reply_len = handleRequest (false , timestamp, req_code, &data[i], len - i);
346- }
365+ uint8_t reply_len = handleLoginReq (sender, secret, timestamp, &data[4 ]);
347366
348367 if (reply_len == 0 ) return ; // invalid request
349368
@@ -406,7 +425,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
406425 memcpy (×tamp, data, 4 );
407426
408427 if (timestamp > from.last_timestamp ) { // prevent replay attacks
409- uint8_t reply_len = handleRequest (from.isAdmin (), timestamp, data[4 ], &data[5 ], len - 5 );
428+ uint8_t reply_len = handleRequest (from.isAdmin () ? 0xFFFF : from. permissions , timestamp, data[4 ], &data[5 ], len - 5 );
410429 if (reply_len == 0 ) return ; // invalid command
411430
412431 from.last_timestamp = timestamp;
@@ -445,9 +464,9 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
445464 data[len] = 0 ; // need to make a C string again, with null terminator
446465
447466 uint8_t temp[166 ];
448- const char *command = (const char *) &data[5 ];
467+ char *command = (char *) &data[5 ];
449468 char *reply = (char *) &temp[5 ];
450- _cli. handleCommand (sender_timestamp, command, reply);
469+ handleCommand (sender_timestamp, command, reply);
451470
452471 int text_len = strlen (reply);
453472 if (text_len > 0 ) {
@@ -489,8 +508,9 @@ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint
489508 memcpy (from.out_path , path, from.out_path_len = path_len); // store a copy of path, for sendDirect()
490509 from.last_activity = getRTCClock ()->getCurrentTime ();
491510
511+ // REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes??
492512 if (from.isAdmin ()) {
493- // only need to saveContacts() if this is an admin
513+ // only do saveContacts() (of this out_path change ) if this is an admin
494514 dirty_contacts_expiry = futureMillis (LAZY_CONTACTS_WRITE_DELAY);
495515 }
496516
0 commit comments