Bluetooth: Mesh: Fix IV Update duration tracking

When the IV Update state enters Normal operation or IV Update in
Progress, we need to keep track of how many hours has passed in the
state, since the specification requires us to remain in the state at
least for 96 hours (Update in Progress has an additional upper limit
of 144 hours).

In order to fulfil the above requirement, even if the node might be
powered off once in a while, we need to store persistently how many
hours the node has been in the state. This doesn't necessarily need to
happen every hour (thanks to the flexible duration range). The exact
cadence will depend a lot on the ways that the node will be used and
what kind of power source it has.

Since there is no single optimal answer, this patch adds a new
configuration option, which allows specifying a divider, i.e. how many
intervals the 96 hour minimum gets split into. After each interval the
duration that the node has been in the current state gets stored to
flash. E.g. the default value of 4 means that the state is saved every
24 hours (96 / 4).

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
Johan Hedberg 2018-05-15 14:35:07 +03:00 committed by Johan Hedberg
commit 2b73c97d68
6 changed files with 90 additions and 71 deletions

View file

@ -162,6 +162,32 @@ config BT_MESH_ADV_BUF_COUNT
which leaves 56 bytes for application layer data using a which leaves 56 bytes for application layer data using a
4-byte MIC and 52 bytes using an 8-byte MIC. 4-byte MIC and 52 bytes using an 8-byte MIC.
config BT_MESH_IVU_DIVIDER
int "Divider for IV Update state refresh timer"
default 4
range 2 96
help
When the IV Update state enters Normal operation or IV Update
in Progress, we need to keep track of how many hours has passed
in the state, since the specification requires us to remain in
the state at least for 96 hours (Update in Progress has an
additional upper limit of 144 hours).
In order to fulfil the above requirement, even if the node might
be powered off once in a while, we need to store persistently
how many hours the node has been in the state. This doesn't
necessarily need to happen every hour (thanks to the flexible
duration range). The exact cadence will depend a lot on the
ways that the node will be used and what kind of power source it
has.
Since there is no single optimal answer, this configuration
option allows specifying a divider, i.e. how many intervals
the 96 hour minimum gets split into. After each interval the
duration that the node has been in the current state gets
stored to flash. E.g. the default value of 4 means that the
state is saved every 24 hours (96 / 4).
config BT_MESH_TX_SEG_MSG_COUNT config BT_MESH_TX_SEG_MSG_COUNT
int "Maximum number of simultaneous outgoing segmented messages" int "Maximum number of simultaneous outgoing segmented messages"
default 1 default 1

View file

@ -65,7 +65,7 @@ int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx,
BT_DBG("Storing network information persistently"); BT_DBG("Storing network information persistently");
bt_mesh_store_net(); bt_mesh_store_net();
bt_mesh_store_subnet(&bt_mesh.sub[0]); bt_mesh_store_subnet(&bt_mesh.sub[0]);
bt_mesh_store_iv(); bt_mesh_store_iv(false);
} }
bt_mesh_net_start(); bt_mesh_net_start();
@ -86,10 +86,10 @@ void bt_mesh_reset(void)
bt_mesh.iv_update = 0; bt_mesh.iv_update = 0;
bt_mesh.pending_update = 0; bt_mesh.pending_update = 0;
bt_mesh.valid = 0; bt_mesh.valid = 0;
bt_mesh.last_update = 0; bt_mesh.ivu_duration = 0;
bt_mesh.ivu_initiator = 0; bt_mesh.ivu_initiator = 0;
k_delayed_work_cancel(&bt_mesh.ivu_complete); k_delayed_work_cancel(&bt_mesh.ivu_timer);
bt_mesh_cfg_reset(); bt_mesh_cfg_reset();

View file

@ -482,15 +482,11 @@ int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16],
bt_mesh.iv_index = iv_index; bt_mesh.iv_index = iv_index;
bt_mesh.iv_update = BT_MESH_IV_UPDATE(flags); bt_mesh.iv_update = BT_MESH_IV_UPDATE(flags);
bt_mesh.ivu_duration = 0;
bt_mesh.ivu_unknown = 1;
/* Set initial IV Update procedure state time-stamp */ /* Set a timer to keep refreshing the stored duration */
bt_mesh.last_update = BT_MESH_NET_IVU_UNKNOWN; k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
/* Set a timer to transition back to normal mode */
if (bt_mesh.iv_update) {
k_delayed_work_submit(&bt_mesh.ivu_complete,
BT_MESH_NET_IVU_TIMEOUT);
}
/* Make sure we have valid beacon data to be sent */ /* Make sure we have valid beacon data to be sent */
bt_mesh_net_beacon_update(sub); bt_mesh_net_beacon_update(sub);
@ -586,6 +582,8 @@ void bt_mesh_rpl_reset(void)
void bt_mesh_iv_update_test(bool enable) void bt_mesh_iv_update_test(bool enable)
{ {
bt_mesh.ivu_test = enable; bt_mesh.ivu_test = enable;
/* Clear the "unknown duration" variable - needed for some PTS tests */
bt_mesh.ivu_unknown = 0;
} }
bool bt_mesh_iv_update(void) bool bt_mesh_iv_update(void)
@ -620,13 +618,6 @@ void bt_mesh_net_sec_update(struct bt_mesh_subnet *sub)
} }
} }
static void update_ivu_timestamp(void)
{
if (bt_mesh.last_update == BT_MESH_NET_IVU_UNKNOWN) {
bt_mesh.last_update = k_uptime_get();
}
}
bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update) bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
{ {
int i; int i;
@ -643,7 +634,6 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
if (iv_update) { if (iv_update) {
/* Nothing to do */ /* Nothing to do */
BT_DBG("Already in IV Update in Progress state"); BT_DBG("Already in IV Update in Progress state");
update_ivu_timestamp();
return false; return false;
} }
} else { } else {
@ -651,7 +641,6 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
if (iv_index == bt_mesh.iv_index) { if (iv_index == bt_mesh.iv_index) {
BT_DBG("Same IV Index in normal mode"); BT_DBG("Same IV Index in normal mode");
update_ivu_timestamp();
return false; return false;
} }
@ -678,16 +667,13 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
if (!iv_update) { if (!iv_update) {
/* Nothing to do */ /* Nothing to do */
BT_DBG("Already in Normal state"); BT_DBG("Already in Normal state");
update_ivu_timestamp();
return false; return false;
} }
} }
if (bt_mesh.last_update != BT_MESH_NET_IVU_UNKNOWN && if (!bt_mesh.ivu_unknown &&
!(IS_ENABLED(CONFIG_BT_MESH_IV_UPDATE_TEST) && bt_mesh.ivu_test)) { !(IS_ENABLED(CONFIG_BT_MESH_IV_UPDATE_TEST) && bt_mesh.ivu_test)) {
s64_t delta = k_uptime_get() - bt_mesh.last_update; if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) {
if (delta < K_HOURS(96)) {
BT_WARN("IV Update before minimum duration"); BT_WARN("IV Update before minimum duration");
return false; return false;
} }
@ -702,6 +688,8 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
do_update: do_update:
bt_mesh.iv_update = iv_update; bt_mesh.iv_update = iv_update;
bt_mesh.ivu_duration = 0;
bt_mesh.ivu_unknown = 0;
if (bt_mesh.iv_update) { if (bt_mesh.iv_update) {
bt_mesh.iv_index = iv_index; bt_mesh.iv_index = iv_index;
@ -709,21 +697,12 @@ do_update:
bt_mesh.iv_index); bt_mesh.iv_index);
bt_mesh_rpl_reset(); bt_mesh_rpl_reset();
k_delayed_work_submit(&bt_mesh.ivu_complete,
BT_MESH_NET_IVU_TIMEOUT);
} else { } else {
BT_DBG("Normal mode entered"); BT_DBG("Normal mode entered");
bt_mesh.seq = 0; bt_mesh.seq = 0;
k_delayed_work_cancel(&bt_mesh.ivu_complete);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
bt_mesh_store_seq();
}
} }
/* Store time-stamp of the IV procedure state change */ k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
bt_mesh.last_update = k_uptime_get();
for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
if (bt_mesh.sub[i].net_idx != BT_MESH_KEY_UNUSED) { if (bt_mesh.sub[i].net_idx != BT_MESH_KEY_UNUSED) {
@ -732,7 +711,7 @@ do_update:
} }
if (IS_ENABLED(CONFIG_BT_SETTINGS)) { if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
bt_mesh_store_iv(); bt_mesh_store_iv(false);
} }
return true; return true;
@ -1358,12 +1337,29 @@ void bt_mesh_net_recv(struct net_buf_simple *data, s8_t rssi,
} }
} }
static void ivu_complete(struct k_work *work) static void ivu_refresh(struct k_work *work)
{ {
BT_DBG(""); bt_mesh.ivu_duration += BT_MESH_IVU_HOURS;
bt_mesh_beacon_ivu_initiator(true); BT_DBG("%s for %u hour%s",
bt_mesh_net_iv_update(bt_mesh.iv_index, false); bt_mesh.iv_update ? "IVU in Progress" : "IVU Normal mode",
bt_mesh.ivu_duration, bt_mesh.ivu_duration == 1 ? "" : "s");
if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) {
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
bt_mesh_store_iv(true);
}
k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
return;
}
if (bt_mesh.iv_update) {
bt_mesh_beacon_ivu_initiator(true);
bt_mesh_net_iv_update(bt_mesh.iv_index, false);
} else if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
bt_mesh_store_iv(true);
}
} }
void bt_mesh_net_start(void) void bt_mesh_net_start(void)
@ -1400,7 +1396,7 @@ void bt_mesh_net_start(void)
void bt_mesh_net_init(void) void bt_mesh_net_init(void)
{ {
k_delayed_work_init(&bt_mesh.ivu_complete, ivu_complete); k_delayed_work_init(&bt_mesh.ivu_timer, ivu_refresh);
k_work_init(&bt_mesh.local_work, bt_mesh_net_local); k_work_init(&bt_mesh.local_work, bt_mesh_net_local);
} }

View file

@ -17,18 +17,11 @@
#define BT_MESH_IV_UPDATE(flags) ((flags >> 1) & 0x01) #define BT_MESH_IV_UPDATE(flags) ((flags >> 1) & 0x01)
#define BT_MESH_KEY_REFRESH(flags) (flags & 0x01) #define BT_MESH_KEY_REFRESH(flags) (flags & 0x01)
/* Special time-stamp to indicate that we don't know when the last IV /* How many hours in between updating IVU duration */
* Update happened. #define BT_MESH_IVU_MIN_HOURS 96
*/ #define BT_MESH_IVU_HOURS (BT_MESH_IVU_MIN_HOURS / \
#define BT_MESH_NET_IVU_UNKNOWN -1 CONFIG_BT_MESH_IVU_DIVIDER)
#define BT_MESH_IVU_TIMEOUT K_HOURS(BT_MESH_IVU_HOURS)
#if defined(CONFIG_BT_MESH_IV_UPDATE_TEST)
/* Small test timeout for IV Update Procedure testing */
#define BT_MESH_NET_IVU_TIMEOUT K_SECONDS(120)
#else
/* Maximum time to stay in IV Update mode (96 < time < 144) */
#define BT_MESH_NET_IVU_TIMEOUT K_HOURS(120)
#endif /* CONFIG_BT_MESH_IV_UPDATE_TEST */
struct bt_mesh_app_key { struct bt_mesh_app_key {
u16_t net_idx; u16_t net_idx;
@ -218,11 +211,10 @@ struct bt_mesh_net {
iv_update:1, /* 1 if IV Update in Progress */ iv_update:1, /* 1 if IV Update in Progress */
ivu_initiator:1, /* IV Update initiated by us */ ivu_initiator:1, /* IV Update initiated by us */
ivu_test:1, /* IV Update test mode */ ivu_test:1, /* IV Update test mode */
ivu_unknown:1, /* Set to 1 right after provisioning */
pending_update:1, /* Update blocked by SDU in progress */ pending_update:1, /* Update blocked by SDU in progress */
valid:1; /* 0 if unused */ valid:1; /* 0 if unused */
s64_t last_update; /* Time since last IV Update change */
ATOMIC_DEFINE(flags, BT_MESH_FLAG_COUNT); ATOMIC_DEFINE(flags, BT_MESH_FLAG_COUNT);
/* Local network interface */ /* Local network interface */
@ -238,8 +230,11 @@ struct bt_mesh_net {
struct bt_mesh_lpn lpn; /* Low Power Node state */ struct bt_mesh_lpn lpn; /* Low Power Node state */
#endif #endif
/* Timer to transition IV Update in Progress state */ /* Number of hours in current IV Update state */
struct k_delayed_work ivu_complete; u8_t ivu_duration;
/* Timer to track duration in current IV Update state */
struct k_delayed_work ivu_timer;
u8_t dev_key[16]; u8_t dev_key[16];

View file

@ -82,7 +82,8 @@ struct cfg_val {
/* IV Index & IV Update storage */ /* IV Index & IV Update storage */
struct iv_val { struct iv_val {
u32_t iv_index; u32_t iv_index;
bool iv_update; u8_t iv_update:1,
iv_duration:7;
} __packed; } __packed;
/* Replay Protection List storage */ /* Replay Protection List storage */
@ -184,9 +185,11 @@ static int iv_set(int argc, char **argv, char *val)
bt_mesh.iv_index = iv.iv_index; bt_mesh.iv_index = iv.iv_index;
bt_mesh.iv_update = iv.iv_update; bt_mesh.iv_update = iv.iv_update;
bt_mesh.ivu_duration = iv.iv_duration;
bt_mesh.ivu_unknown = 0;
BT_DBG("IV Index 0x%04x (IV Update Flag %u)", bt_mesh.iv_index, BT_DBG("IV Index 0x%04x (IV Update Flag %u) duration %u hours",
bt_mesh.iv_update); bt_mesh.iv_index, bt_mesh.iv_update, bt_mesh.ivu_duration);
return 0; return 0;
} }
@ -804,13 +807,8 @@ static int mesh_commit(void)
} }
} }
/* Set initial IV Update procedure state time-stamp */ if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) {
bt_mesh.last_update = BT_MESH_NET_IVU_UNKNOWN; k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
/* Set a timer to transition back to normal mode */
if (bt_mesh.iv_update) {
k_delayed_work_submit(&bt_mesh.ivu_complete,
BT_MESH_NET_IVU_TIMEOUT);
} }
bt_mesh_model_foreach(commit_mod, NULL); bt_mesh_model_foreach(commit_mod, NULL);
@ -912,6 +910,7 @@ static void store_pending_iv(void)
iv.iv_index = bt_mesh.iv_index; iv.iv_index = bt_mesh.iv_index;
iv.iv_update = bt_mesh.iv_update; iv.iv_update = bt_mesh.iv_update;
iv.iv_duration = bt_mesh.ivu_duration;
str = settings_str_from_bytes(&iv, sizeof(iv), buf, sizeof(buf)); str = settings_str_from_bytes(&iv, sizeof(iv), buf, sizeof(buf));
if (!str) { if (!str) {
@ -923,11 +922,14 @@ static void store_pending_iv(void)
settings_save_one("bt/mesh/IV", str); settings_save_one("bt/mesh/IV", str);
} }
void bt_mesh_store_iv(void) void bt_mesh_store_iv(bool only_duration)
{ {
schedule_store(BT_MESH_IV_PENDING); schedule_store(BT_MESH_IV_PENDING);
/* Always update Seq whenever IV changes */
schedule_store(BT_MESH_SEQ_PENDING); if (!only_duration) {
/* Always update Seq whenever IV changes */
schedule_store(BT_MESH_SEQ_PENDING);
}
} }
static void store_pending_seq(void) static void store_pending_seq(void)

View file

@ -5,7 +5,7 @@
*/ */
void bt_mesh_store_net(void); void bt_mesh_store_net(void);
void bt_mesh_store_iv(void); void bt_mesh_store_iv(bool only_duration);
void bt_mesh_store_seq(void); void bt_mesh_store_seq(void);
void bt_mesh_store_rpl(struct bt_mesh_rpl *rpl); void bt_mesh_store_rpl(struct bt_mesh_rpl *rpl);
void bt_mesh_store_subnet(struct bt_mesh_subnet *sub); void bt_mesh_store_subnet(struct bt_mesh_subnet *sub);