Add observed login path commands
This commit is contained in:
1 parent
2ce59b58ed
commit
da774e9e75
8 files changed
+302
-24
No files matched your search
+11
-1
@@ -1550,7 +1550,9 @@ The pin number is the Arduino pin number used by that target (the normal GPIO nu
|
||||
#### View or set the direct path override for the current remote client
|
||||
**Usage:**
|
||||
- `get outpath`
|
||||
- `get outpath path`
|
||||
- `set outpath <hop1_hex,hop2_hex,...>`
|
||||
- `set outpath path`
|
||||
- `set outpath direct`
|
||||
- `set outpath clear`
|
||||
- `set outpath flood`
|
||||
@@ -1560,8 +1562,16 @@ The pin number is the Arduino pin number used by that target (the normal GPIO nu
|
||||
|
||||
**Notes:**
|
||||
- These commands require remote client context and update the caller's ACL entry.
|
||||
- `get outpath path` reports the reciprocal `PAYLOAD_TYPE_PATH` received after
|
||||
the caller's latest flood login without changing the selected output route.
|
||||
Because that packet is asynchronous, an immediate query can report
|
||||
`> path pending`; retry shortly. The observation window expires after one
|
||||
minute, and the captured login path is not automatically selected.
|
||||
- `set outpath path` copies that observed route to `outpath` and saves it. It
|
||||
returns an error without changing `outpath` if no route was received.
|
||||
- `direct` selects a zero-hop route for a directly reachable caller.
|
||||
- `clear` forgets the override so normal path discovery can repopulate it.
|
||||
- `clear` forgets the override, replies `> outpath cleared`, and allows normal
|
||||
path discovery to repopulate it.
|
||||
- `flood` forces replies to use flood packets until the client logs in again.
|
||||
|
||||
---
|
||||
|
||||
@@ -195,7 +195,9 @@ can be copied directly into another `set outpath` or `set altpath` command.
|
||||
|
||||
```text
|
||||
get outpath
|
||||
get outpath path
|
||||
set outpath A1B2C3,D4E5F6
|
||||
set outpath path
|
||||
set outpath direct
|
||||
set outpath clear
|
||||
set outpath flood
|
||||
@@ -214,10 +216,18 @@ For example, both `set altpath 600000,0d2784,f8dada` and
|
||||
The first CLI word is case-insensitive (`set`, `Set`, and `SET` are the same,
|
||||
as are `get`, `Get`, and the other command verbs). Argument case is preserved.
|
||||
|
||||
`get outpath path` reports the reciprocal path received after the latest flood
|
||||
login without changing `outpath`. That packet arrives asynchronously, so retry
|
||||
shortly if the first query reports `> path pending`; the observation window
|
||||
expires after one minute. The login path is not automatically selected.
|
||||
`set outpath path` copies the observed route into `outpath` and saves it; it
|
||||
leaves the current route unchanged when no observed route is available.
|
||||
|
||||
`set outpath direct` sets a zero-hop direct route for a client reachable without
|
||||
repeaters. `set outpath clear` forgets the override and lets normal path
|
||||
discovery fill it again. `set outpath flood` forces replies to use flood packets
|
||||
until the client logs in again.
|
||||
repeaters. `set outpath clear` forgets the override, replies
|
||||
`> outpath cleared`, and lets normal path discovery fill it again.
|
||||
`set outpath flood` forces replies to use flood packets until the client logs in
|
||||
again.
|
||||
|
||||
When `outpath` is a valid direct path and `altpath` is also a valid, different
|
||||
direct path, repeater DM replies send two packets: one on `outpath` and one on
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/ClockSyncUtils.h>
|
||||
#include <helpers/ClientLoginPersistence.h>
|
||||
#include <helpers/ClientPathObservation.h>
|
||||
#include <helpers/ClientPathPersistence.h>
|
||||
#include <helpers/DatagramPayloadLimits.h>
|
||||
#include <helpers/FloodFilterPolicy.h>
|
||||
@@ -94,6 +95,8 @@ extern "C" caddr_t _sbrk(int increment);
|
||||
|
||||
#define FIRMWARE_VER_LEVEL 2
|
||||
|
||||
static constexpr uint32_t LOGIN_PATH_OBSERVATION_TIMEOUT_MS = 60000UL;
|
||||
|
||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
@@ -582,6 +585,9 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
*client, client_existed, perms, PERM_ACL_ROLE_MASK,
|
||||
secret, sender_timestamp,
|
||||
getRTCClock()->getCurrentTime(), reset_out_path, OUT_PATH_UNKNOWN);
|
||||
// A flood login arms observation only after its PATH reply is successfully
|
||||
// allocated below. Clear any result from an earlier login in either case.
|
||||
mesh::clearObservedClientPath(*client, OUT_PATH_UNKNOWN);
|
||||
|
||||
const bool persistence_needed =
|
||||
mesh::successfulClientLoginNeedsPersistence(
|
||||
@@ -914,7 +920,7 @@ bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[])
|
||||
return n >= max_counters[hash_size];
|
||||
}
|
||||
|
||||
void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) {
|
||||
bool MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) {
|
||||
TransportKey req_scope;
|
||||
bool is_wildcard = recv_pkt_region != NULL && recv_pkt_region->isWildcard();
|
||||
bool req_scope_known = recv_pkt_region != NULL && !is_wildcard
|
||||
@@ -922,17 +928,15 @@ void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, ui
|
||||
|
||||
switch (mesh::chooseReplyScope(req_scope_known, is_wildcard, !default_scope.isNull())) {
|
||||
case mesh::REPLY_SCOPE_REQUEST:
|
||||
sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request
|
||||
break;
|
||||
return sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request
|
||||
case mesh::REPLY_SCOPE_DEFAULT:
|
||||
// requester's scope is unknown: DIRECT request (no transport codes), or code matched no Region.
|
||||
// un-scoped would be dropped at hop 0 by repeaters running flood.max.unscoped=0
|
||||
sendFloodScoped(default_scope, packet, delay_millis, path_hash_size);
|
||||
break;
|
||||
return sendFloodScoped(default_scope, packet, delay_millis, path_hash_size);
|
||||
case mesh::REPLY_SCOPE_NONE:
|
||||
sendFlood(packet, delay_millis, path_hash_size); // send un-scoped
|
||||
break;
|
||||
return sendFlood(packet, delay_millis, path_hash_size); // send un-scoped
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool directPathsEqual(const uint8_t* a_path, uint8_t a_len, const uint8_t* b_path, uint8_t b_len) {
|
||||
@@ -2576,7 +2580,15 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
if (path) {
|
||||
const bool login_path_sent = sendFloodReply(
|
||||
path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
if (client != NULL && login_path_sent) {
|
||||
mesh::beginObservedClientPath(
|
||||
*client, OUT_PATH_UNKNOWN,
|
||||
futureMillis(LOGIN_PATH_OBSERVATION_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3161,16 +3173,26 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
||||
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
||||
auto client = acl.getClientByIdx(i);
|
||||
|
||||
// A flood login's reciprocal PATH is direct and has no embedded payload
|
||||
// (decoded as the reserved 0x0F extra type). Retain that one independently
|
||||
// so the operator can inspect or explicitly select it.
|
||||
const bool captured_login_path = packet->isRouteDirect()
|
||||
&& extra_type == 0x0F
|
||||
&& mesh::captureObservedClientPath(
|
||||
*client, path, path_len, futureMillis(0));
|
||||
|
||||
// PATH packets authenticate the sender and are de-duplicated in RAM, but
|
||||
// the protocol carries no timestamp or request nonce. Keep a learned route
|
||||
// useful for this boot without making an arbitrarily old replay durable.
|
||||
const bool persistence_allowed = mesh::clientPathPersistenceAllowed(
|
||||
client->permissions != 0, false /* no durable replay proof */);
|
||||
const mesh::ClientPathUpdateResult path_update =
|
||||
mesh::applyReceivedClientPath(
|
||||
*client, path, path_len, persistence_allowed,
|
||||
OUT_PATH_FORCE_FLOOD);
|
||||
(void)path_update;
|
||||
if (!captured_login_path) {
|
||||
const bool persistence_allowed = mesh::clientPathPersistenceAllowed(
|
||||
client->permissions != 0, false /* no durable replay proof */);
|
||||
const mesh::ClientPathUpdateResult path_update =
|
||||
mesh::applyReceivedClientPath(
|
||||
*client, path, path_len, persistence_allowed,
|
||||
OUT_PATH_FORCE_FLOOD);
|
||||
(void)path_update;
|
||||
}
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
||||
@@ -10609,7 +10631,11 @@ static __attribute__((always_inline)) ClientPathCommand classifyClientPathComman
|
||||
}
|
||||
|
||||
if (is_get) {
|
||||
if (path[7] != 0) return CLIENT_PATH_NONE;
|
||||
if (path[7] != 0
|
||||
&& ((result & CLIENT_PATH_ALT) != 0
|
||||
|| strcmp(path + 7, " path") != 0)) {
|
||||
return CLIENT_PATH_NONE;
|
||||
}
|
||||
} else if (path[7] != 0 && path[7] != ' ') {
|
||||
return CLIENT_PATH_NONE;
|
||||
}
|
||||
@@ -10622,6 +10648,9 @@ bool MyMesh::handleClientPathCommand(ClientInfo* sender, char* command, char* re
|
||||
|
||||
bool is_get = (path_command & CLIENT_PATH_SET) == 0;
|
||||
bool is_alt = (path_command & CLIENT_PATH_ALT) != 0;
|
||||
bool use_observed_path = !is_alt
|
||||
&& (strcmp(command, "get outpath path") == 0
|
||||
|| strcmp(command, "set outpath path") == 0);
|
||||
if (sender == NULL) {
|
||||
strcpy(reply, "Err - command needs remote client context");
|
||||
return true;
|
||||
@@ -10630,7 +10659,14 @@ bool MyMesh::handleClientPathCommand(ClientInfo* sender, char* command, char* re
|
||||
uint8_t* stored_path = is_alt ? sender->alt_path : sender->out_path;
|
||||
uint8_t* stored_path_len = is_alt ? &sender->alt_path_len : &sender->out_path_len;
|
||||
if (is_get) {
|
||||
formatPathReply(stored_path, *stored_path_len, reply, 160);
|
||||
if (use_observed_path
|
||||
&& mesh::isObservedClientPathPending(*sender, futureMillis(0))) {
|
||||
strcpy(reply, "> path pending");
|
||||
} else if (use_observed_path) {
|
||||
formatPathReply(sender->observed_path, sender->observed_path_len, reply, 160);
|
||||
} else {
|
||||
formatPathReply(stored_path, *stored_path_len, reply, 160);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10640,7 +10676,21 @@ bool MyMesh::handleClientPathCommand(ClientInfo* sender, char* command, char* re
|
||||
uint8_t path[MAX_PATH_SIZE];
|
||||
uint8_t path_len = OUT_PATH_UNKNOWN;
|
||||
const char* err = NULL;
|
||||
if (!parsePathCommand(spec, path, path_len, err)) {
|
||||
if (use_observed_path
|
||||
&& mesh::isObservedClientPathPending(*sender, futureMillis(0))) {
|
||||
strcpy(reply, "Err - path pending");
|
||||
return true;
|
||||
}
|
||||
if (use_observed_path) {
|
||||
if (!mesh::isValidObservedClientPathLength(
|
||||
sender->observed_path_len, sizeof(sender->observed_path))) {
|
||||
strcpy(reply, "Err - no path received");
|
||||
return true;
|
||||
}
|
||||
path_len = sender->observed_path_len;
|
||||
const size_t path_bytes = mesh::observedClientPathByteLength(path_len);
|
||||
if (path_bytes != 0) memcpy(path, sender->observed_path, path_bytes);
|
||||
} else if (!parsePathCommand(spec, path, path_len, err)) {
|
||||
strcpy(reply, err ? err : "Err - invalid path");
|
||||
return true;
|
||||
}
|
||||
@@ -10672,7 +10722,11 @@ bool MyMesh::handleClientPathCommand(ClientInfo* sender, char* command, char* re
|
||||
}
|
||||
mesh::resetLazyPersistenceAfterSuccess(
|
||||
dirty_contacts_expiry, contacts_save_failures);
|
||||
formatPathReply(stored_path, *stored_path_len, reply, 160);
|
||||
if (!is_alt && path_len == OUT_PATH_UNKNOWN) {
|
||||
strcpy(reply, "> outpath cleared");
|
||||
} else {
|
||||
formatPathReply(stored_path, *stored_path_len, reply, 160);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -893,7 +893,7 @@ protected:
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
// OTA mesh-integration is centralized in mesh::Mesh (no per-example onOtaRecv / send adapter / tick).
|
||||
|
||||
void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
|
||||
bool sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
|
||||
void sendClientReply(ClientInfo* client, mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
|
||||
|
||||
public:
|
||||
|
||||
@@ -461,6 +461,7 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
|
||||
|
||||
memset(&c, 0, sizeof(c));
|
||||
c.alt_path_len = OUT_PATH_UNKNOWN;
|
||||
c.observed_path_len = OUT_PATH_UNKNOWN;
|
||||
|
||||
bool success = (file.read(pub_key, 32) == 32);
|
||||
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
||||
@@ -694,6 +695,7 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) {
|
||||
c->out_path_len = OUT_PATH_UNKNOWN;
|
||||
c->out_path_is_persistable = true;
|
||||
c->alt_path_len = OUT_PATH_UNKNOWN;
|
||||
c->observed_path_len = OUT_PATH_UNKNOWN;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ struct ClientInfo {
|
||||
bool out_path_is_persistable; // live route may replace, but not erase, durable route
|
||||
uint8_t alt_path_len;
|
||||
uint8_t alt_path[MAX_PATH_SIZE];
|
||||
uint8_t observed_path_len; // reciprocal login PATH (transient)
|
||||
uint8_t observed_path[MAX_PATH_SIZE];
|
||||
bool observed_path_pending;
|
||||
uint32_t observed_path_expiry;
|
||||
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||
uint32_t last_timestamp; // by THEIR clock (exact live floor; reserved ceiling after load)
|
||||
uint32_t last_activity; // by OUR clock (transient)
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
inline size_t observedClientPathByteLength(uint8_t encoded_path_len) {
|
||||
const size_t hash_count = encoded_path_len & 63U;
|
||||
const size_t hash_size = (encoded_path_len >> 6) + 1U;
|
||||
return hash_count * hash_size;
|
||||
}
|
||||
|
||||
inline bool isValidObservedClientPathLength(uint8_t encoded_path_len,
|
||||
size_t capacity) {
|
||||
const size_t hash_size = (encoded_path_len >> 6) + 1U;
|
||||
return hash_size != 4U
|
||||
&& observedClientPathByteLength(encoded_path_len) <= capacity;
|
||||
}
|
||||
|
||||
template <typename Client>
|
||||
void clearObservedClientPath(Client& client, uint8_t unknown_path) {
|
||||
memset(client.observed_path, 0, sizeof(client.observed_path));
|
||||
client.observed_path_len = unknown_path;
|
||||
client.observed_path_pending = false;
|
||||
client.observed_path_expiry = 0;
|
||||
}
|
||||
|
||||
template <typename Client>
|
||||
void beginObservedClientPath(Client& client, uint8_t unknown_path,
|
||||
uint32_t expiry) {
|
||||
clearObservedClientPath(client, unknown_path);
|
||||
client.observed_path_pending = true;
|
||||
client.observed_path_expiry = expiry;
|
||||
}
|
||||
|
||||
template <typename Client>
|
||||
bool isObservedClientPathPending(Client& client, uint32_t now) {
|
||||
if (!client.observed_path_pending) return false;
|
||||
if (static_cast<int32_t>(now - client.observed_path_expiry) < 0) return true;
|
||||
|
||||
client.observed_path_pending = false;
|
||||
client.observed_path_expiry = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep the most recently received, authenticated PAYLOAD_TYPE_PATH separate
|
||||
// from the selected output route so CLI reads never need to mutate out_path.
|
||||
template <typename Client>
|
||||
bool captureObservedClientPath(Client& client, const uint8_t* path,
|
||||
uint8_t encoded_path_len, uint32_t now) {
|
||||
if (!isObservedClientPathPending(client, now)) return false;
|
||||
|
||||
const size_t path_bytes = observedClientPathByteLength(encoded_path_len);
|
||||
if (!isValidObservedClientPathLength(
|
||||
encoded_path_len, sizeof(client.observed_path))
|
||||
|| (path_bytes != 0 && path == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(client.observed_path, 0, sizeof(client.observed_path));
|
||||
if (path_bytes != 0) {
|
||||
memcpy(client.observed_path, path, path_bytes);
|
||||
}
|
||||
client.observed_path_len = encoded_path_len;
|
||||
client.observed_path_pending = false;
|
||||
client.observed_path_expiry = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Promote a previously observed PATH to the selected output route. Invalid
|
||||
// sentinels (including UNKNOWN/FORCE_FLOOD) cannot be promoted.
|
||||
template <typename Client>
|
||||
bool promoteObservedClientPath(Client& client) {
|
||||
const uint8_t encoded_path_len = client.observed_path_len;
|
||||
if (!isValidObservedClientPathLength(
|
||||
encoded_path_len, sizeof(client.observed_path))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t path_bytes = observedClientPathByteLength(encoded_path_len);
|
||||
if (path_bytes > sizeof(client.out_path)) return false;
|
||||
|
||||
memset(client.out_path, 0, sizeof(client.out_path));
|
||||
if (path_bytes != 0) {
|
||||
memcpy(client.out_path, client.observed_path, path_bytes);
|
||||
}
|
||||
client.out_path_len = encoded_path_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
@@ -0,0 +1,105 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <helpers/ClientPathObservation.h>
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr uint8_t UNKNOWN_PATH = 0xFF;
|
||||
|
||||
struct ClientState {
|
||||
uint8_t out_path_len = UNKNOWN_PATH;
|
||||
uint8_t out_path[64] = {};
|
||||
uint8_t observed_path_len = UNKNOWN_PATH;
|
||||
uint8_t observed_path[64] = {};
|
||||
bool observed_path_pending = false;
|
||||
uint32_t observed_path_expiry = 0;
|
||||
};
|
||||
|
||||
TEST(ClientPathObservation, CaptureDoesNotChangeSelectedOutputPath) {
|
||||
ClientState client;
|
||||
client.out_path_len = 1;
|
||||
client.out_path[0] = 0xAA;
|
||||
const uint8_t received[] = {0x10, 0x20, 0x30, 0x40};
|
||||
|
||||
mesh::beginObservedClientPath(client, UNKNOWN_PATH, 1000);
|
||||
ASSERT_TRUE(mesh::captureObservedClientPath(
|
||||
client, received, 0x42, 500)); // two 2-byte hashes
|
||||
EXPECT_EQ(client.observed_path_len, 0x42);
|
||||
EXPECT_EQ(memcmp(client.observed_path, received, sizeof(received)), 0);
|
||||
EXPECT_EQ(client.out_path_len, 1);
|
||||
EXPECT_EQ(client.out_path[0], 0xAA);
|
||||
}
|
||||
|
||||
TEST(ClientPathObservation, PromoteCopiesObservedPathToOutputPath) {
|
||||
ClientState client;
|
||||
const uint8_t received[] = {0x10, 0x20, 0x30, 0x40};
|
||||
mesh::beginObservedClientPath(client, UNKNOWN_PATH, 1000);
|
||||
ASSERT_TRUE(mesh::captureObservedClientPath(client, received, 0x42, 500));
|
||||
|
||||
ASSERT_TRUE(mesh::promoteObservedClientPath(client));
|
||||
EXPECT_EQ(client.out_path_len, 0x42);
|
||||
EXPECT_EQ(memcmp(client.out_path, received, sizeof(received)), 0);
|
||||
}
|
||||
|
||||
TEST(ClientPathObservation, MissingObservedPathCannotReplaceOutputPath) {
|
||||
ClientState client;
|
||||
client.out_path_len = 1;
|
||||
client.out_path[0] = 0xAA;
|
||||
|
||||
EXPECT_FALSE(mesh::promoteObservedClientPath(client));
|
||||
EXPECT_EQ(client.out_path_len, 1);
|
||||
EXPECT_EQ(client.out_path[0], 0xAA);
|
||||
}
|
||||
|
||||
TEST(ClientPathObservation, ZeroHopDirectPathCanBeObservedAndPromoted) {
|
||||
ClientState client;
|
||||
mesh::beginObservedClientPath(client, UNKNOWN_PATH, 1000);
|
||||
ASSERT_TRUE(mesh::captureObservedClientPath(client, nullptr, 0, 500));
|
||||
ASSERT_TRUE(mesh::promoteObservedClientPath(client));
|
||||
EXPECT_EQ(client.out_path_len, 0);
|
||||
}
|
||||
|
||||
TEST(ClientPathObservation, NewLoginClearsPreviousObservation) {
|
||||
ClientState client;
|
||||
client.observed_path_len = 1;
|
||||
client.observed_path[0] = 0x55;
|
||||
|
||||
mesh::clearObservedClientPath(client, UNKNOWN_PATH);
|
||||
EXPECT_EQ(client.observed_path_len, UNKNOWN_PATH);
|
||||
EXPECT_FALSE(client.observed_path_pending);
|
||||
EXPECT_EQ(client.observed_path_expiry, 0U);
|
||||
for (uint8_t byte : client.observed_path) EXPECT_EQ(byte, 0);
|
||||
}
|
||||
|
||||
TEST(ClientPathObservation, NewFloodLoginWaitsForOneReciprocalPath) {
|
||||
ClientState client;
|
||||
const uint8_t first[] = {0x11};
|
||||
const uint8_t later[] = {0x22};
|
||||
|
||||
mesh::beginObservedClientPath(client, UNKNOWN_PATH, 1000);
|
||||
EXPECT_TRUE(client.observed_path_pending);
|
||||
ASSERT_TRUE(mesh::captureObservedClientPath(client, first, 1, 500));
|
||||
EXPECT_FALSE(client.observed_path_pending);
|
||||
EXPECT_EQ(client.observed_path[0], 0x11);
|
||||
|
||||
EXPECT_FALSE(mesh::captureObservedClientPath(client, later, 1, 600));
|
||||
EXPECT_EQ(client.observed_path[0], 0x11);
|
||||
}
|
||||
|
||||
TEST(ClientPathObservation, PendingLoginPathExpires) {
|
||||
ClientState client;
|
||||
const uint8_t received[] = {0x11};
|
||||
|
||||
mesh::beginObservedClientPath(client, UNKNOWN_PATH, 1000);
|
||||
EXPECT_TRUE(mesh::isObservedClientPathPending(client, 999));
|
||||
EXPECT_FALSE(mesh::captureObservedClientPath(client, received, 1, 1000));
|
||||
EXPECT_FALSE(client.observed_path_pending);
|
||||
EXPECT_EQ(client.observed_path_len, UNKNOWN_PATH);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in new issue
Block a user