/* * Copyright (c) 2023 Florian Grandel * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_ieee802154_mgmt_test, LOG_LEVEL_DBG); #include #include #include #include #include #include #include #include #include #include extern struct net_pkt *current_pkt; extern struct k_sem driver_lock; extern uint8_t mock_ext_addr_be[8]; static struct net_if *net_iface; static struct net_mgmt_event_callback scan_cb; K_SEM_DEFINE(scan_lock, 0, 1); #define EXPECTED_COORDINATOR_LQI 15U #define EXPECTED_COORDINATOR_PAN_LE 0xcd, 0xab #define EXPECTED_COORDINATOR_PAN_CPU_ORDER 0xabcd #define EXPECTED_COORDINATOR_PAN_STR "43981" #define EXPECTED_COORDINATOR_ADDR_LE 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f #define EXPECTED_COORDINATOR_ADDR_BE 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08 #define EXPECTED_COORDINATOR_ADDR_STR "0f:0e:0d:0c:0b:0a:09:08" #define EXPECTED_COORDINATOR_SHORT_ADDR 0xbbbb #define EXPECTED_ENDDEVICE_EXT_ADDR_LE 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 #define EXPECTED_ENDDEVICE_EXT_ADDR_STR "08:07:06:05:04:03:02:01" #define EXPECTED_ENDDEVICE_SHORT_ADDR 0xaaaa static void scan_result_cb(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, struct net_if *iface) { struct ieee802154_context *ctx = net_if_l2_data(iface); struct ieee802154_req_params *scan_ctx = ctx->scan_ctx; uint8_t expected_coordinator_address[] = {EXPECTED_COORDINATOR_ADDR_BE}; /* No need for scan_ctx locking as we should execute exclusively. */ zassert_not_null(scan_ctx); zassert_equal(scan_ctx->pan_id, EXPECTED_COORDINATOR_PAN_CPU_ORDER, "Scan did not receive correct PAN id."); zassert_equal(scan_ctx->len, IEEE802154_EXT_ADDR_LENGTH, "Scan did not receive correct co-ordinator address length."); zassert_mem_equal(scan_ctx->addr, expected_coordinator_address, IEEE802154_EXT_ADDR_LENGTH); zassert_equal(scan_ctx->lqi, EXPECTED_COORDINATOR_LQI, "Scan did not receive correct link quality indicator."); k_sem_give(&scan_lock); } static void test_beacon_request(struct ieee802154_mpdu *mpdu) { struct ieee802154_command *cmd = mpdu->command; zassert_equal(mpdu->payload_length, 1U, "Beacon request: invalid payload length."); zassert_equal(cmd->cfi, IEEE802154_CFI_BEACON_REQUEST, "Not a beacon request."); zassert_equal(mpdu->mhr.fs->fc.dst_addr_mode, IEEE802154_ADDR_MODE_SHORT, "Beacon request: invalid destination address mode."); zassert_equal(mpdu->mhr.dst_addr->plain.addr.short_addr, IEEE802154_BROADCAST_ADDRESS, "Beacon request: destination address should be broadcast address."); zassert_equal(mpdu->mhr.dst_addr->plain.pan_id, IEEE802154_BROADCAST_PAN_ID, "Beacon request: destination PAN should be broadcast PAN."); } static void test_association_request(struct ieee802154_mpdu *mpdu) { struct ieee802154_command *cmd = mpdu->command; zassert_equal( mpdu->mhr.fs->fc.frame_version, IEEE802154_VERSION_802154_2006, "Association Request: currently only IEEE 802.15.4 2006 frame version supported."); zassert_equal(mpdu->mhr.fs->fc.frame_type, IEEE802154_FRAME_TYPE_MAC_COMMAND, "Association Request: should be a MAC command."); zassert_equal(mpdu->mhr.fs->fc.ar, true, "Association Request: must request ACK."); zassert_equal(mpdu->payload_length, 1U + IEEE802154_CMD_ASSOC_REQ_LENGTH); zassert_equal(cmd->cfi, IEEE802154_CFI_ASSOCIATION_REQUEST, "Association Request: unexpected CFI."); zassert_equal(cmd->assoc_req.ci.alloc_addr, true, "Association Request: should allocate short address."); zassert_equal(cmd->assoc_req.ci.association_type, false, "Association Request: fast association is not supported."); } static void test_disassociation_notification(struct ieee802154_mpdu *mpdu) { struct ieee802154_command *cmd = mpdu->command; zassert_equal(mpdu->mhr.fs->fc.frame_version, IEEE802154_VERSION_802154_2006, "Disassociation Notification: currently only IEEE 802.15.4 2006 frame " "version supported."); zassert_equal(mpdu->mhr.fs->fc.frame_type, IEEE802154_FRAME_TYPE_MAC_COMMAND, "Disassociation Notification: should be a MAC command."); zassert_equal(mpdu->mhr.fs->fc.ar, true, "Disassociation Notification: must request ACK."); zassert_equal(mpdu->payload_length, 1U + IEEE802154_CMD_DISASSOC_NOTE_LENGTH); zassert_equal(cmd->cfi, IEEE802154_CFI_DISASSOCIATION_NOTIFICATION, "Disassociation Notification: unexpected CFI."); zassert_equal( cmd->disassoc_note.reason, IEEE802154_DRF_DEVICE_WISH, "Disassociation Notification: notification should be initiated by the enddevice."); } static void test_scan_shell_cmd(void) { struct ieee802154_mpdu mpdu = {0}; int ret; /* The beacon placed into the RX queue will be received and handled as * soon as this command yields waiting for beacons. */ ret = shell_execute_cmd(NULL, "ieee802154 scan active 11 500"); zassert_equal(0, ret, "Active scan failed: %d", ret); zassert_equal(0, k_sem_take(&scan_lock, K_NO_WAIT), "Active scan: did not receive beacon."); zassert_not_null(current_pkt); if (!ieee802154_validate_frame(net_pkt_data(current_pkt), net_pkt_get_len(current_pkt), &mpdu)) { NET_ERR("*** Could not parse beacon request.\n"); ztest_test_fail(); goto release_frag; } test_beacon_request(&mpdu); release_frag: net_pkt_frag_unref(current_pkt->frags); current_pkt->frags = NULL; } static void test_associate_shell_cmd(struct ieee802154_context *ctx) { uint8_t expected_coord_addr_le[] = {EXPECTED_COORDINATOR_ADDR_LE}; struct ieee802154_mpdu mpdu = {0}; struct net_buf *assoc_req; int ret; /* The association response placed into the RX queue will be received and * handled as soon as this command yields waiting for a response. */ ret = shell_execute_cmd(NULL, "ieee802154 associate " EXPECTED_COORDINATOR_PAN_STR " " EXPECTED_COORDINATOR_ADDR_STR); zassert_equal(0, ret, "Association failed: %d", ret); /* Test that we were associated. */ zassert_equal(ctx->pan_id, EXPECTED_COORDINATOR_PAN_CPU_ORDER, "Association: did not get associated to the expected PAN."); zassert_equal(ctx->short_addr, EXPECTED_ENDDEVICE_SHORT_ADDR, "Association: did not get the expected short address asigned."); zassert_equal(ctx->coord_short_addr, IEEE802154_NO_SHORT_ADDRESS_ASSIGNED, "Association: co-ordinator should not use short address."); zassert_mem_equal( ctx->coord_ext_addr, expected_coord_addr_le, sizeof(ctx->coord_ext_addr), "Association: did not get associated co-ordinator by the expected coordinator."); /* Test the association request that should have been sent out. */ zassert_not_null(current_pkt); assoc_req = current_pkt->frags; zassert_not_null(assoc_req); if (!ieee802154_validate_frame(assoc_req->data, assoc_req->len, &mpdu)) { NET_ERR("*** Could not parse association request.\n"); ztest_test_fail(); goto release_frag; } test_association_request(&mpdu); release_frag: net_pkt_frag_unref(current_pkt->frags); current_pkt->frags = NULL; } ZTEST(ieee802154_l2_shell, test_active_scan) { uint8_t beacon_pkt[] = { 0x00, 0xd0, /* FCF */ 0x11, /* Sequence Number: 17 */ EXPECTED_COORDINATOR_PAN_LE, /* Source PAN */ EXPECTED_COORDINATOR_ADDR_LE, /* Extended Source Address */ 0x00, 0xc0, /* Superframe Specification: PAN coordinator + association permitted */ 0x00, /* GTS */ 0x00, /* Pending Addresses */ 0x00, 0x00 /* Payload */ }; struct net_pkt *pkt; pkt = net_pkt_rx_alloc_with_buffer(net_iface, sizeof(beacon_pkt), AF_UNSPEC, 0, K_FOREVER); if (!pkt) { NET_ERR("*** No buffer to allocate\n"); goto fail; } net_pkt_set_ieee802154_lqi(pkt, EXPECTED_COORDINATOR_LQI); net_buf_add_mem(pkt->buffer, beacon_pkt, sizeof(beacon_pkt)); /* The packet will be placed in the RX queue but not yet handled. */ if (net_recv_data(net_iface, pkt) < 0) { NET_ERR("Recv data failed"); net_pkt_unref(pkt); goto fail; } net_mgmt_init_event_callback(&scan_cb, scan_result_cb, NET_EVENT_IEEE802154_SCAN_RESULT); net_mgmt_add_event_callback(&scan_cb); test_scan_shell_cmd(); net_mgmt_del_event_callback(&scan_cb); return; fail: ztest_test_fail(); } ZTEST(ieee802154_l2_shell, test_associate) { uint8_t coord_addr_le[] = {EXPECTED_COORDINATOR_ADDR_LE}; struct ieee802154_context *ctx = net_if_l2_data(net_iface); struct ieee802154_frame_params params = { .dst = { .len = IEEE802154_EXT_ADDR_LENGTH, .pan_id = EXPECTED_COORDINATOR_PAN_CPU_ORDER, }}; struct ieee802154_command *cmd; struct net_pkt *pkt; sys_memcpy_swap(params.dst.ext_addr, ctx->ext_addr, sizeof(params.dst.ext_addr)); /* Simulate a packet from the coordinator. */ memcpy(ctx->ext_addr, coord_addr_le, sizeof(ctx->ext_addr)); pkt = ieee802154_create_mac_cmd_frame(net_iface, IEEE802154_CFI_ASSOCIATION_RESPONSE, ¶ms); if (!pkt) { NET_ERR("*** Could not create association response\n"); goto fail; } cmd = ieee802154_get_mac_command(pkt); cmd->assoc_res.short_addr = sys_cpu_to_le16(EXPECTED_ENDDEVICE_SHORT_ADDR); cmd->assoc_res.status = IEEE802154_ASF_SUCCESSFUL; ieee802154_mac_cmd_finalize(pkt, IEEE802154_CFI_ASSOCIATION_RESPONSE); /* The packet will be placed in the RX queue but not yet handled. */ if (net_recv_data(net_iface, pkt) < 0) { NET_ERR("Recv assoc resp pkt failed"); net_pkt_unref(pkt); goto fail; } /* Restore the end device's extended address. */ sys_memcpy_swap(ctx->ext_addr, params.dst.ext_addr, sizeof(ctx->ext_addr)); test_associate_shell_cmd(ctx); return; fail: sys_memcpy_swap(ctx->ext_addr, params.dst.ext_addr, sizeof(ctx->ext_addr)); ztest_test_fail(); } ZTEST(ieee802154_l2_shell, test_initiate_disassociation_from_enddevice) { uint8_t expected_coord_addr_le[] = {EXPECTED_COORDINATOR_ADDR_LE}; uint8_t empty_coord_addr[IEEE802154_EXT_ADDR_LENGTH] = {0}; struct ieee802154_context *ctx = net_if_l2_data(net_iface); uint8_t mock_ext_addr_le[IEEE802154_EXT_ADDR_LENGTH]; struct ieee802154_mpdu mpdu = {0}; int ret; /* Simulate an associated device. */ ctx->pan_id = EXPECTED_COORDINATOR_PAN_CPU_ORDER; ctx->short_addr = EXPECTED_ENDDEVICE_SHORT_ADDR; ctx->coord_short_addr = EXPECTED_COORDINATOR_SHORT_ADDR; memcpy(ctx->coord_ext_addr, expected_coord_addr_le, sizeof(ctx->coord_ext_addr)); ret = shell_execute_cmd(NULL, "ieee802154 disassociate"); zassert_equal(0, ret, "Initiating disassociation from the enddevice failed: %d", ret); /* Ensure we've been disassociated. */ zassert_mem_equal(ctx->coord_ext_addr, empty_coord_addr, sizeof(ctx->coord_ext_addr), "Disassociation: coordinator address should be unset."); zassert_equal(ctx->pan_id, IEEE802154_PAN_ID_NOT_ASSOCIATED, "Disassociation: PAN should be unset."); zassert_equal(ctx->short_addr, IEEE802154_SHORT_ADDRESS_NOT_ASSOCIATED, "Disassociation: Short addr should be unset."); sys_memcpy_swap(mock_ext_addr_le, mock_ext_addr_be, sizeof(mock_ext_addr_le)); zassert_mem_equal(ctx->ext_addr, mock_ext_addr_le, sizeof(ctx->ext_addr), "Disassociation: Ext addr should be unaffected."); zassert_not_null(current_pkt); if (!ieee802154_validate_frame(net_pkt_data(current_pkt), net_pkt_get_len(current_pkt), &mpdu)) { NET_ERR("*** Could not parse disassociation notification.\n"); ztest_test_fail(); goto release_frag; } test_disassociation_notification(&mpdu); release_frag: net_pkt_frag_unref(current_pkt->frags); current_pkt->frags = NULL; } ZTEST(ieee802154_l2_shell, test_initiate_disassociation_from_coordinator) { uint8_t expected_coord_addr_le[] = {EXPECTED_COORDINATOR_ADDR_LE}; uint8_t empty_coord_addr[IEEE802154_EXT_ADDR_LENGTH] = {0}; struct ieee802154_context *ctx = net_if_l2_data(net_iface); uint8_t mock_ext_addr_le[IEEE802154_EXT_ADDR_LENGTH]; struct ieee802154_frame_params params = { .dst = { .len = IEEE802154_EXT_ADDR_LENGTH, .pan_id = EXPECTED_COORDINATOR_PAN_CPU_ORDER, }}; struct ieee802154_command *cmd; struct net_pkt *pkt; /* Simulate an associated device. */ sys_memcpy_swap(params.dst.ext_addr, ctx->ext_addr, sizeof(params.dst.ext_addr)); /* Simulate a packet from the coordinator. */ ctx->device_role = IEEE802154_DEVICE_ROLE_PAN_COORDINATOR; ctx->pan_id = EXPECTED_COORDINATOR_PAN_CPU_ORDER; ctx->short_addr = EXPECTED_COORDINATOR_SHORT_ADDR; memcpy(ctx->ext_addr, expected_coord_addr_le, sizeof(ctx->ext_addr)); /* Create and send an incoming disassociation notification. */ pkt = ieee802154_create_mac_cmd_frame(net_iface, IEEE802154_CFI_DISASSOCIATION_NOTIFICATION, ¶ms); if (!pkt) { NET_ERR("*** Could not create association response\n"); goto fail; } cmd = ieee802154_get_mac_command(pkt); cmd->disassoc_note.reason = IEEE802154_DRF_COORDINATOR_WISH; ieee802154_mac_cmd_finalize(pkt, IEEE802154_CFI_DISASSOCIATION_NOTIFICATION); /* Restore the end device's state and simulate an associated device. */ ctx->device_role = IEEE802154_DEVICE_ROLE_ENDDEVICE; ctx->short_addr = EXPECTED_ENDDEVICE_SHORT_ADDR; sys_memcpy_swap(ctx->ext_addr, params.dst.ext_addr, sizeof(ctx->ext_addr)); ctx->coord_short_addr = EXPECTED_COORDINATOR_SHORT_ADDR; memcpy(ctx->coord_ext_addr, expected_coord_addr_le, sizeof(ctx->coord_ext_addr)); if (net_recv_data(net_iface, pkt) < 0) { NET_ERR("Recv assoc resp pkt failed"); net_pkt_unref(pkt); goto fail; } /* We need to yield, so that the packet is actually being received from the RX thread. */ k_yield(); /* We should have received an ACK packet. */ zassert_not_null(current_pkt); zassert_not_null(current_pkt->frags); zassert_equal(net_pkt_get_len(current_pkt), IEEE802154_ACK_PKT_LENGTH, "Did not receive the expected ACK packet."); net_pkt_frag_unref(current_pkt->frags); current_pkt->frags = NULL; /* Ensure we've been disassociated. */ zassert_mem_equal(ctx->coord_ext_addr, empty_coord_addr, sizeof(ctx->coord_ext_addr), "Disassociation: coordinator address should be unset."); zassert_equal(ctx->pan_id, IEEE802154_PAN_ID_NOT_ASSOCIATED, "Disassociation: PAN should be unset."); zassert_equal(ctx->short_addr, IEEE802154_SHORT_ADDRESS_NOT_ASSOCIATED, "Disassociation: Short addr should be unset."); sys_memcpy_swap(mock_ext_addr_le, mock_ext_addr_be, sizeof(mock_ext_addr_le)); zassert_mem_equal(ctx->ext_addr, mock_ext_addr_le, sizeof(ctx->ext_addr), "Disassociation: Ext addr should be unaffected."); return; fail: sys_memcpy_swap(ctx->ext_addr, params.dst.ext_addr, sizeof(ctx->ext_addr)); ztest_test_fail(); } ZTEST(ieee802154_l2_shell, test_set_ext_addr) { uint8_t expected_ext_addr_le[] = {EXPECTED_ENDDEVICE_EXT_ADDR_LE}; struct ieee802154_context *ctx = net_if_l2_data(net_iface); uint8_t initial_ext_addr_le[sizeof(mock_ext_addr_be)]; int ret; sys_memcpy_swap(initial_ext_addr_le, mock_ext_addr_be, sizeof(initial_ext_addr_le)); zassert_equal(ctx->pan_id, IEEE802154_PAN_ID_NOT_ASSOCIATED, "Setting Ext Addr: PAN should not be set initially."); zassert_equal(ctx->short_addr, IEEE802154_SHORT_ADDRESS_NOT_ASSOCIATED, "Setting Ext Addr: Short addr should not be set initially."); zassert_mem_equal(ctx->ext_addr, initial_ext_addr_le, sizeof(ctx->coord_ext_addr), "Setting Ext Addr: Ext addr should be the mock addr initially."); ret = shell_execute_cmd(NULL, "ieee802154 set_ext_addr " EXPECTED_ENDDEVICE_EXT_ADDR_STR); zassert_equal(0, ret, "Setting the external address failed: %d", ret); zassert_mem_equal( ctx->ext_addr, expected_ext_addr_le, sizeof(ctx->coord_ext_addr), "Setting Ext Addr: failed."); memcpy(ctx->ext_addr, initial_ext_addr_le, sizeof(ctx->ext_addr)); } static void reset_fake_driver(void *test_fixture) { struct ieee802154_context *ctx; ARG_UNUSED(test_fixture); __ASSERT_NO_MSG(net_iface); /* Set initial conditions. */ ctx = net_if_l2_data(net_iface); ctx->pan_id = IEEE802154_PAN_ID_NOT_ASSOCIATED; ctx->short_addr = IEEE802154_SHORT_ADDRESS_NOT_ASSOCIATED; ctx->coord_short_addr = IEEE802154_SHORT_ADDRESS_NOT_ASSOCIATED; memset(ctx->coord_ext_addr, 0, sizeof(ctx->coord_ext_addr)); } static void *test_setup(void) { const struct device *dev = device_get_binding("fake_ieee802154"); k_sem_reset(&driver_lock); if (!dev) { NET_ERR("*** Could not get fake device\n"); return NULL; } net_iface = net_if_lookup_by_dev(dev); if (!net_iface) { NET_ERR("*** Could not get fake iface\n"); return NULL; } NET_INFO("Fake IEEE 802.15.4 network interface ready\n"); current_pkt = net_pkt_rx_alloc(K_FOREVER); if (!current_pkt) { NET_ERR("*** No buffer to allocate\n"); return false; } return NULL; } static void test_teardown(void *test_fixture) { ARG_UNUSED(test_fixture); net_pkt_unref(current_pkt); current_pkt = NULL; } ZTEST_SUITE(ieee802154_l2_shell, NULL, test_setup, reset_fake_driver, reset_fake_driver, test_teardown);