From: "Gix, Brian" <brian.gix@intel.com>
To: "linux-bluetooth@vger.kernel.org" <linux-bluetooth@vger.kernel.org>
Cc: "michal.lowas-rzechonek@silvair.com"
<michal.lowas-rzechonek@silvair.com>,
"Stotland, Inga" <inga.stotland@intel.com>
Subject: Re: [PATCH BlueZ v4] mesh: Secure Beacon - IV_Index/Key Refresh re-write
Date: Mon, 14 Oct 2019 22:20:39 +0000 [thread overview]
Message-ID: <6a20427ea1b4a238035c263f377f2bc1750ff85f.camel@intel.com> (raw)
In-Reply-To: <20191010225852.5793-1-brian.gix@intel.com>
Applied
On Thu, 2019-10-10 at 15:58 -0700, Brian Gix wrote:
> This is a major rewrite of Secure Network Beacon (SNB) handling
> that includes:
>
> * Seperating Key Refresh from IV_Index handling
>
> This is a clearer handling of the two features. Although both features
> are represented in SNB's, they run independantly.
>
> * Creating a Seperate IV_Index initialization and updating state
> distinct from the current values sent and received in SNBs.
>
> If a restart occured during an IV Update procedure (96 hours long)
> the IVU bit got lost, and Sequence number resetting was not done
> correctly.
>
> * Assuring that all Nodes handled by daemon receive each incoming
> beacon. SNB handling previously stopped after the first node
> successfuly handled it, although the SNB may be valid for many local
> nodes.
> ---
> mesh/mesh-config-json.c | 3 +-
> mesh/net.c | 350 +++++++++++++++-------------------------
> 2 files changed, 131 insertions(+), 222 deletions(-)
>
> diff --git a/mesh/mesh-config-json.c b/mesh/mesh-config-json.c
> index 198fef518..df58cbd7d 100644
> --- a/mesh/mesh-config-json.c
> +++ b/mesh/mesh-config-json.c
> @@ -2057,7 +2057,8 @@ bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq,
> return mesh_config_save(cfg, true, NULL, NULL);
> }
>
> - if (get_int(cfg->jnode, "sequenceNumber", &value))
> + /* If resetting seq to Zero, make sure cached value reset as well */
> + if (seq && get_int(cfg->jnode, "sequenceNumber", &value))
> cached = (uint32_t)value;
>
> /*
> diff --git a/mesh/net.c b/mesh/net.c
> index 2785039db..f07de4d8a 100644
> --- a/mesh/net.c
> +++ b/mesh/net.c
> @@ -41,7 +41,7 @@
>
> #define IV_IDX_DIFF_RANGE 42
>
> -/* #define IV_IDX_UPD_MIN (60) 1 minute for Testing */
> +/*#define IV_IDX_UPD_MIN (5 * 60) * 5 minute for Testing */
> #define IV_IDX_UPD_MIN (60 * 60 * 96) /* 96 Hours - per Spec */
> #define IV_IDX_UPD_HOLD (IV_IDX_UPD_MIN/2)
> #define IV_IDX_UPD_MAX (IV_IDX_UPD_MIN + IV_IDX_UPD_HOLD)
> @@ -257,6 +257,11 @@ struct net_queue_data {
> uint16_t len;
> };
>
> +struct net_beacon_data {
> + const uint8_t *data;
> + uint16_t len;
> +};
> +
> #define FAST_CACHE_SIZE 8
> static struct l_queue *fast_cache;
> static struct l_queue *nets;
> @@ -503,6 +508,7 @@ void mesh_friend_sub_del(struct mesh_net *net, uint16_t lpn,
> uint32_t mesh_net_next_seq_num(struct mesh_net *net)
> {
> uint32_t seq = net->seq_num++;
> +
> node_set_sequence_number(net->node, net->seq_num);
> return seq;
> }
> @@ -568,9 +574,10 @@ static bool create_secure_beacon(struct mesh_net *net,
> struct mesh_subnet *subnet,
> uint8_t *beacon_data)
> {
> + bool kr = (subnet->kr_phase == KEY_REFRESH_PHASE_TWO);
> +
> return net_key_snb_compose(subnet->net_key_tx, net->iv_index,
> - !!subnet->key_refresh, iv_is_updating(net),
> - beacon_data);
> + kr, net->iv_update, beacon_data);
> }
>
> static void send_network_beacon(struct mesh_subnet *subnet,
> @@ -718,6 +725,7 @@ bool mesh_net_set_seq_num(struct mesh_net *net, uint32_t seq)
> return false;
>
> net->seq_num = seq;
> + node_set_sequence_number(net->node, net->seq_num);
>
> return true;
> }
> @@ -1034,7 +1042,7 @@ uint32_t mesh_net_get_iv_index(struct mesh_net *net)
> if (!net)
> return 0xffffffff;
>
> - return net->iv_index - (iv_is_updating(net) ? 1 : 0);
> + return net->iv_index - net->iv_update;
> }
>
> /* TODO: net key index? */
> @@ -2616,10 +2624,15 @@ static void iv_upd_to(struct l_timeout *upd_timeout, void *user_data)
> break;
> }
>
> - l_info("iv_upd_state = IV_UPD_NORMAL_HOLD");
> + l_debug("iv_upd_state = IV_UPD_NORMAL_HOLD");
> net->iv_upd_state = IV_UPD_NORMAL_HOLD;
> l_timeout_modify(net->iv_update_timeout, IV_IDX_UPD_MIN);
> - mesh_net_set_seq_num(net, 0);
> + if (net->iv_update)
> + mesh_net_set_seq_num(net, 0);
> +
> + net->iv_update = false;
> + mesh_config_write_iv_index(node_config_get(net->node),
> + net->iv_index, false);
> l_queue_foreach(net->subnets, set_network_beacon, net);
> mesh_net_flush_msg_queues(net);
> break;
> @@ -2629,8 +2642,12 @@ static void iv_upd_to(struct l_timeout *upd_timeout, void *user_data)
> case IV_UPD_NORMAL:
> l_timeout_remove(upd_timeout);
> net->iv_update_timeout = NULL;
> - l_info("iv_upd_state = IV_UPD_NORMAL");
> + l_debug("iv_upd_state = IV_UPD_NORMAL");
> net->iv_upd_state = IV_UPD_NORMAL;
> + if (net->iv_update)
> + mesh_net_set_seq_num(net, 0);
> +
> + net->iv_update = false;
> if (net->seq_num > IV_UPDATE_SEQ_TRIGGER)
> mesh_net_iv_index_update(net);
> break;
> @@ -2707,39 +2724,41 @@ static int key_refresh_finish(struct mesh_net *net, uint16_t idx)
> return MESH_STATUS_SUCCESS;
> }
>
> -static void update_iv_kr_state(struct mesh_subnet *subnet, uint32_t iv_index,
> - bool iv_update, bool kr_transition,
> - bool rxed_key_refresh, bool lpn)
> +static void update_kr_state(struct mesh_subnet *subnet, bool kr, uint32_t id)
> +{
> + /* Figure out the key refresh phase */
> + if (kr) {
> + if (id == subnet->net_key_upd) {
> + l_debug("Beacon based KR phase 2 change");
> + key_refresh_phase_two(subnet->net, subnet->idx);
> + }
> + } else {
> + if (id == subnet->net_key_upd) {
> + l_debug("Beacon based KR phase 3 change");
> + key_refresh_finish(subnet->net, subnet->idx);
> + }
> + }
> +}
> +
> +static void update_iv_ivu_state(struct mesh_net *net, uint32_t iv_index,
> + bool ivu)
> {
> - struct mesh_net *net = subnet->net;
> - uint8_t local_kr;
> uint32_t local_iv_index;
> - bool local_iv_update;
> + bool local_ivu;
>
> - /* Save original settings to avoid resetting same values,
> - * and secure beacon timer
> - */
> + /* Save original settings to differentiate what has changed */
> local_iv_index = net->iv_index;
> - local_kr = subnet->key_refresh;
> - local_iv_update = iv_is_updating(net);
> -
> - if (iv_index != local_iv_index || kr_transition)
> - l_info("SNB-RX: %8.8x - Key Refresh: %d IV Update: %d",
> - iv_index, rxed_key_refresh, iv_update);
> + local_ivu = net->iv_update;
>
> - if (iv_update && (net->iv_upd_state > IV_UPD_UPDATING)) {
> - if (iv_index != net->iv_index)
> - l_error("Update attempted to0 soon (Normal < MIN)");
> -
> - return;
> + if ((iv_index - ivu) > (local_iv_index - local_ivu)) {
> + /* Don't accept IV_Index changes when performing SAR Out */
> + if (l_queue_length(net->sar_out))
> + return;
> }
>
> + /* If first beacon seen, accept without judgement */
> if (net->iv_upd_state == IV_UPD_INIT) {
> - if (iv_index > net->iv_index)
> - mesh_net_set_seq_num(net, 0);
> - net->iv_index = iv_index;
> -
> - if (iv_update) {
> + if (ivu) {
> /* Other devices will be accepting old or new iv_index,
> * but we don't know how far through update they are.
> * Starting permissive state will allow us maximum
> @@ -2753,164 +2772,89 @@ static void update_iv_kr_state(struct mesh_subnet *subnet, uint32_t iv_index,
> l_info("iv_upd_state = IV_UPD_NORMAL");
> net->iv_upd_state = IV_UPD_NORMAL;
> }
> -
> - mesh_config_write_iv_index(node_config_get(net->node), iv_index,
> - net->iv_upd_state);
> -
> - /* Figure out the key refresh phase */
> - if (kr_transition) {
> - l_debug("Beacon based KR phase change");
> - if (rxed_key_refresh)
> - key_refresh_phase_two(net, subnet->idx);
> - else
> - key_refresh_finish(net, subnet->idx);
> + } else if (ivu) {
> + /* Ignore beacons with IVU if they come too soon */
> + if (!local_ivu && net->iv_upd_state == IV_UPD_NORMAL_HOLD) {
> + l_error("Update attempted too soon");
> + return;
> }
>
> - if (!lpn)
> - set_network_beacon(subnet, net);
> -
> - return;
> - }
> -
> - if (iv_update && !iv_is_updating(net)) {
> - l_info("iv_upd_state = IV_UPD_UPDATING");
> - net->iv_upd_state = IV_UPD_UPDATING;
> - net->iv_update_timeout = l_timeout_create(IV_IDX_UPD_MIN,
> - iv_upd_to, net, NULL);
> - mesh_config_write_iv_index(node_config_get(net->node), iv_index,
> - net->iv_upd_state);
> - } else if (iv_update && iv_index != net->iv_index) {
> - l_error("Update attempted too soon (iv idx already updated)");
> + if (!local_ivu) {
> + l_info("iv_upd_state = IV_UPD_UPDATING");
> + net->iv_upd_state = IV_UPD_UPDATING;
> + net->iv_update_timeout = l_timeout_create(
> + IV_IDX_UPD_MIN, iv_upd_to, net, NULL);
> + }
> + } else if (local_ivu) {
> + l_error("IVU clear attempted too soon");
> return;
> }
>
> - if (iv_index != local_iv_index || kr_transition)
> - l_info("IVindex 0x%8.8x / Key Refresh update received",
> - iv_index);
> + if ((iv_index - ivu) > (local_iv_index - local_ivu))
> + mesh_net_set_seq_num(net, 0);
>
> - if (iv_index > net->iv_index) {
> - l_queue_clear(net->msg_cache, mesh_msg_free);
> - net->iv_index = iv_index;
> - mesh_config_write_iv_index(node_config_get(net->node), iv_index,
> - net->iv_upd_state);
> - }
> + if (ivu != net->iv_update || local_iv_index != net->iv_index) {
> + struct mesh_config *cfg = node_config_get(net->node);
>
> - /* Figure out the key refresh phase */
> - if (kr_transition) {
> - if (rxed_key_refresh)
> - key_refresh_phase_two(net, subnet->idx);
> - else
> - key_refresh_finish(net, subnet->idx);
> + mesh_config_write_iv_index(cfg, iv_index, ivu);
> }
>
> - if (!lpn)
> - return;
> -
> - if (local_kr != subnet->key_refresh ||
> - local_iv_index != net->iv_index ||
> - local_iv_update != iv_is_updating(net))
> - set_network_beacon(subnet, net);
> + net->iv_index = iv_index;
> + net->iv_update = ivu;
> }
>
> -static void process_beacon(void *user_data, const void *data,
> - uint8_t size, int8_t rssi)
> +static void process_beacon(void *net_ptr, void *user_data)
> {
> - struct mesh_net *net = user_data;
> - const uint8_t *buf = data;
> - uint32_t iv_index;
> - bool iv_update, rxed_iv_update, rxed_key_refresh;
> + struct mesh_net *net = net_ptr;
> + const uint8_t *buf = *(uint8_t **)user_data;
> + uint32_t ivi;
> + bool ivu, kr, local_kr;
> struct mesh_subnet *subnet;
> uint32_t key_id;
> - bool kr_transition = false;
> -
> - if (size != 22 || buf[0] != 0x01)
> - return;
> -
> - /* print_packet("Secure Net Beacon RXed", data, size); */
> - rxed_key_refresh = (buf[1] & 0x01) == 0x01;
> - rxed_iv_update = iv_update = (buf[1] & 0x02) == 0x02;
> - iv_index = l_get_be32(buf + 10);
> -
> - l_debug("KR: %d -- IVU: %d -- IV: %8.8x",
> - rxed_key_refresh, rxed_iv_update, iv_index);
>
> - /* Inhibit recognizing iv_update true-->false
> - * if we have outbound SAR messages in flight
> - */
> - if (l_queue_length(net->sar_out)) {
> - if (!iv_update && iv_update != iv_is_updating(net))
> - iv_update = true;
> - }
> + ivi = l_get_be32(buf + 10);
>
> - key_id = net_key_network_id(buf + 2);
> - subnet = l_queue_find(net->subnets, match_key_id,
> - L_UINT_TO_PTR(key_id));
> - if (!subnet || !key_id)
> + /* Ignore out-of-range IV_Index for this network */
> + if ((net->iv_index + IV_IDX_DIFF_RANGE < ivi) || (ivi < net->iv_index))
> return;
>
> - /* Check if Key Refresh flag value is different from
> - * the locally stored one
> - */
> - if (rxed_key_refresh != subnet->key_refresh)
> - kr_transition = true;
> -
> - /* If the local node is a provisioner or there are no new keys,
> - * ignore KR beacon setting
> - */
> - if (net->provisioner)
> - kr_transition = false;
> - else if (!subnet->net_key_upd)
> - kr_transition = false;
> - /* If beacon's key refresh bit is not set and the beacon is encoded
> - * with the "new" network key, this signals transition from
> - * key refresh procedure to normal operation
> - */
> - else if (!rxed_key_refresh && subnet->net_key_upd == key_id)
> - kr_transition = true;
> -
> - if ((net->iv_index + IV_IDX_DIFF_RANGE < iv_index) ||
> - (iv_index < net->iv_index)) {
> - l_info("iv index outside range");
> - return;
> - }
> -
> - /* Don't bother going further if nothing has changed */
> - if (!memcmp(&subnet->snb.beacon[1], buf, size)) {
> - subnet->snb.observed++;
> + /* Ignore Network IDs unknown to this mesh universe */
> + key_id = net_key_network_id(buf + 2);
> + if (!key_id)
> return;
> - }
>
> - if (!rxed_key_refresh && !subnet->key_refresh && !kr_transition)
> - key_id = subnet->net_key_cur;
> - else if (subnet->net_key_upd)
> - key_id = subnet->net_key_upd;
> - else
> + subnet = l_queue_find(net->subnets, match_key_id,
> + L_UINT_TO_PTR(key_id));
> + if (!subnet)
> return;
>
> - if (!net_key_snb_check(key_id, iv_index, rxed_key_refresh,
> - rxed_iv_update,
> - l_get_be64(buf + 14))) {
> - l_error("mesh_crypto_beacon verify failed");
> - return;
> - }
> + /* Get IVU and KR boolean bits from beacon */
> + ivu = !!(buf[1] & 0x02);
> + kr = !!(buf[1] & 0x01);
> + local_kr = !!(subnet->kr_phase != KEY_REFRESH_PHASE_TWO);
>
> - if (iv_index == net->iv_index &&
> - iv_is_updating(net) == iv_update && !kr_transition) {
> - l_info("No change: IV index %4.4x, rxed KR = %d ",
> - iv_index, rxed_key_refresh);
> - if (net->iv_upd_state == IV_UPD_INIT) {
> - l_info("iv_upd_state = IV_UPD_NORMAL");
> - net->iv_upd_state = IV_UPD_NORMAL;
> + if (net->iv_upd_state != IV_UPD_INIT) {
> + /* Ignore beacons that indicate *no change* */
> + if (!memcmp(&subnet->snb.beacon[1], buf, 22)) {
> + subnet->snb.observed++;
> + return;
> }
> + }
>
> - subnet->snb.observed++;
> + /* Validate beacon before accepting */
> + if (!net_key_snb_check(key_id, ivi, kr, ivu, l_get_be64(buf + 14))) {
> + l_error("mesh_crypto_beacon verify failed");
> return;
> }
>
> + /* We have officially *seen* this beacon now */
> subnet->snb.observed++;
>
> - update_iv_kr_state(subnet, iv_index, iv_update, kr_transition,
> - rxed_key_refresh, false);
> + update_iv_ivu_state(net, ivi, ivu);
> + update_kr_state(subnet, kr, key_id);
> +
> + if (ivi != net->iv_index || ivu != net->iv_update || kr != local_kr)
> + set_network_beacon(subnet, net);
> }
>
> static void lpn_process_beacon(void *user_data, const void *data,
> @@ -2918,87 +2862,50 @@ static void lpn_process_beacon(void *user_data, const void *data,
> {
> struct mesh_net *net = user_data;
> const uint8_t *buf = data;
> - uint32_t iv_index;
> - bool iv_update, rxed_key_refresh;
> + uint32_t ivi;
> + bool ivu, kr, local_kr;
> struct mesh_subnet *subnet;
> bool kr_transition = false;
>
> /* print_packet("lpn: Secure Net Beacon RXed", data, size); */
> - rxed_key_refresh = (buf[0] & 0x01) == 0x01;
> - iv_update = (buf[0] & 0x02) == 0x02;
> - iv_index = l_get_be32(buf + 1);
> + kr = !!(buf[0] & 0x01);
> + ivu = !!(buf[0] & 0x02);
> + ivi = l_get_be32(buf + 1);
>
> - /* Inhibit recognizing iv_update true-->false if we have outbound
> - * SAR messages in flight
> - */
> - if (l_queue_length(net->sar_out)) {
> - if (!iv_update && iv_update != iv_is_updating(net))
> - iv_update = true;
> - }
> -
> - l_debug("KR: %d -- IVU: %d -- IV: %8.8x",
> - rxed_key_refresh, iv_update, iv_index);
> + l_debug("KR: %d -- IVU: %d -- IVI: %8.8x", kr, ivu, ivi);
>
> /* TODO: figure out actual network index (i.e., friendship subnet) */
> subnet = get_primary_subnet(net);
> if (!subnet)
> return;
>
> - /* Check if Key Refresh flag value is different from
> - * the locally stored one
> - */
> - if (rxed_key_refresh != subnet->key_refresh)
> - kr_transition = true;
> -
> - /* If the local node is a provisioner or there are no new keys,
> - * ignore KR beacon setting
> - */
> - if (!subnet->net_key_upd)
> - kr_transition = false;
> -
> - if ((net->iv_index + IV_IDX_DIFF_RANGE < iv_index) ||
> - (iv_index < net->iv_index)) {
> - l_info("iv index outside range");
> - return;
> - }
> + local_kr = (subnet->kr_phase == KEY_REFRESH_PHASE_TWO);
>
> /* Don't bother going further if nothing has changed */
> - if (!kr_transition && iv_index == net->iv_index &&
> - iv_update == iv_is_updating(net) &&
> - net->iv_upd_state != IV_UPD_INIT)
> + if (local_kr == kr && ivi == net->iv_index && ivu == net->iv_update &&
> + net->iv_upd_state != IV_UPD_INIT)
> return;
>
> - if (iv_index == net->iv_index &&
> - iv_is_updating(net) == iv_update && !kr_transition) {
> - l_info("No change: IV index %4.4x, rxed KR = %d ",
> - iv_index, rxed_key_refresh);
> - if (net->iv_upd_state == IV_UPD_INIT) {
> - l_info("iv_upd_state = IV_UPD_NORMAL");
> - net->iv_upd_state = IV_UPD_NORMAL;
> - }
> - return;
> - }
> + update_iv_ivu_state(net, ivi, ivu);
>
> - update_iv_kr_state(subnet, iv_index, iv_update, kr_transition,
> - rxed_key_refresh, true);
> + if (kr)
> + update_kr_state(subnet, kr_transition, subnet->net_key_upd);
> + else
> + update_kr_state(subnet, kr_transition, subnet->net_key_cur);
> }
>
> static void beacon_recv(void *user_data, struct mesh_io_recv_info *info,
> const uint8_t *data, uint16_t len)
> {
> - struct mesh_net *net = user_data;
> - int8_t rssi = 0;
> + const uint8_t *ptr = data + 1;
>
> - if (len <= 2 || !net)
> + if (len != 23 || data[1] != 0x01)
> return;
>
> - if (info) {
> - net->instant = info->instant;
> - net->chan = info->chan;
> - rssi = info->rssi;
> - }
> + l_debug("KR: %d -- IVU: %d -- IV: %8.8x",
> + data[2] & 1, !!(data[2] & 2), l_get_be32(data + 11));
>
> - process_beacon(user_data, data + 1, len - 1, rssi);
> + l_queue_foreach(nets, process_beacon, &ptr);
> }
>
> bool mesh_net_set_beacon_mode(struct mesh_net *net, bool enable)
> @@ -3044,7 +2951,7 @@ bool mesh_net_set_key(struct mesh_net *net, uint16_t idx, const uint8_t *key,
> if (!subnet)
> return false;
>
> - if (new_key)
> + if (new_key && phase)
> subnet->net_key_upd = net_key_add(new_key);
>
> /* Preserve key refresh state to generate secure beacon flags*/
> @@ -3085,7 +2992,7 @@ bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io)
>
> l_info("Register io cb");
> mesh_io_register_recv_cb(io, MESH_IO_FILTER_BEACON,
> - beacon_recv, net);
> + beacon_recv, NULL);
> mesh_io_register_recv_cb(io, MESH_IO_FILTER_NET,
> net_msg_recv, NULL);
> l_queue_foreach(net->subnets, start_network_beacon, net);
> @@ -3128,12 +3035,13 @@ bool mesh_net_iv_index_update(struct mesh_net *net)
>
> l_info("iv_upd_state = IV_UPD_UPDATING");
> mesh_net_flush_msg_queues(net);
> - net->iv_upd_state = IV_UPD_UPDATING;
> - net->iv_index++;
> if (!mesh_config_write_iv_index(node_config_get(net->node),
> - net->iv_index, IV_UPD_UPDATING))
> + net->iv_index + 1, true))
> return false;
>
> + net->iv_upd_state = IV_UPD_UPDATING;
> + net->iv_index++;
> + net->iv_update = true;
> l_queue_foreach(net->subnets, set_network_beacon, net);
> net->iv_update_timeout = l_timeout_create(
> IV_IDX_UPD_MIN,
prev parent reply other threads:[~2019-10-14 22:20 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-10-10 22:58 [PATCH BlueZ v4] mesh: Secure Beacon - IV_Index/Key Refresh re-write Brian Gix
2019-10-14 22:20 ` Gix, Brian [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=6a20427ea1b4a238035c263f377f2bc1750ff85f.camel@intel.com \
--to=brian.gix@intel.com \
--cc=inga.stotland@intel.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=michal.lowas-rzechonek@silvair.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).