// SPDX-License-Identifier: GPL-2.0-only /* * KUnit tests for element parsing * * Copyright (C) 2023-2025 Intel Corporation */ #include #include "../ieee80211_i.h" MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); static const struct mesh_preq_parse_test_case { const char *desc; u8 len; bool ae_enabled; u8 target_count; bool result; } mesh_preq_parse_cases[] = { { .desc = "shorter than header", .len = 16, .ae_enabled = false, .target_count = 1, .result = false, }, { .desc = "too short non AE, target count is not included", .len = 29, .ae_enabled = false, .target_count = 1, .result = false, }, { .desc = "too short non AE, target count is 1", .len = 36, .ae_enabled = false, .target_count = 1, .result = false, }, { .desc = "too short AE, target count is not included", .len = 35, .ae_enabled = true, .target_count = 1, .result = false, }, { .desc = "too short AE, target count is 1", .len = 42, .ae_enabled = true, .target_count = 1, .result = false, }, { .desc = "target count is zero", .len = 26, .ae_enabled = false, .target_count = 0, .result = false, }, { .desc = "target count is 21", .len = 255, .ae_enabled = false, .target_count = 21, .result = false, }, { .desc = "non AE, target count is 1", .len = 37, .ae_enabled = false, .target_count = 1, .result = true, }, { .desc = "non AE, target count is 20", .len = 246, .ae_enabled = false, .target_count = 20, .result = true, }, { .desc = "AE, target count is 1", .len = 43, .ae_enabled = true, .target_count = 1, .result = true, }, { .desc = "AE, target count is 20", .len = 252, .ae_enabled = true, .target_count = 20, .result = true, }, }; KUNIT_ARRAY_PARAM_DESC(mesh_preq_parse, mesh_preq_parse_cases, desc); static const struct mesh_prep_parse_test_case { const char *desc; u8 len; bool ae_enabled; bool result; } mesh_prep_parse_cases[] = { { .desc = "shorter than header", .len = 12, .ae_enabled = false, .result = false, }, { .desc = "non AE short", .len = 30, .ae_enabled = false, .result = false, }, { .desc = "non AE", .len = 31, .ae_enabled = false, .result = true, }, { .desc = "AE short", .len = 36, .ae_enabled = true, .result = false, }, { .desc = "AE", .len = 37, .ae_enabled = true, .result = true, }, }; KUNIT_ARRAY_PARAM_DESC(mesh_prep_parse, mesh_prep_parse_cases, desc); static const struct mesh_perr_parse_test_case { const char *desc; u8 len; u8 number_of_dst; int ae_enabled_idx; bool result; } mesh_perr_parse_cases[] = { { .desc = "shorter than header", .len = 1, .number_of_dst = 1, .ae_enabled_idx = -1, .result = false, }, { .desc = "number_of_dst is 0", .len = 2, .number_of_dst = 0, .ae_enabled_idx = -1, .result = true, }, { .desc = "number_of_dst is 20", .len = 255, .number_of_dst = 20, .ae_enabled_idx = -1, .result = false, }, { .desc = "number_of_dst is 1, non AE, short", .len = 14, .number_of_dst = 1, .ae_enabled_idx = -1, .result = false, }, { .desc = "number_of_dst is 1, non AE", .len = 15, .number_of_dst = 1, .ae_enabled_idx = -1, .result = true, }, { .desc = "number_of_dst is 1, non AE, extra short dst header", .len = 25, .number_of_dst = 1, .ae_enabled_idx = -1, .result = false, }, { .desc = "number_of_dst is 1, non AE, extra dst header", .len = 26, .number_of_dst = 1, .ae_enabled_idx = -1, .result = false, }, { .desc = "number_of_dst is 1, AE, short", .len = 20, .number_of_dst = 1, .ae_enabled_idx = 0, .result = false, }, { .desc = "number_of_dst is 1, AE", .len = 21, .number_of_dst = 1, .ae_enabled_idx = 0, .result = true, }, { .desc = "number_of_dst is 19, non AE, short", .len = 2 + 13 * 19 - 1, .number_of_dst = 19, .ae_enabled_idx = -1, .result = false, }, { .desc = "number_of_dst is 19, non AE", .len = 2 + 13 * 19, .number_of_dst = 19, .ae_enabled_idx = -1, .result = true, }, { .desc = "number_of_dst is 19, AE, short", .len = 2 + 13 * 19 + 6 - 1, .number_of_dst = 19, .ae_enabled_idx = 18, .result = false, }, { .desc = "number_of_dst is 19, AE", .len = 2 + 13 * 19 + 6, .number_of_dst = 19, .ae_enabled_idx = 18, .result = true, }, }; KUNIT_ARRAY_PARAM_DESC(mesh_perr_parse, mesh_perr_parse_cases, desc); static void mle_defrag(struct kunit *test) { struct ieee80211_elems_parse_params parse_params = { .link_id = 12, .from_ap = true, .mode = IEEE80211_CONN_MODE_EHT, /* type is not really relevant here */ .type = IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_BEACON, }; struct ieee802_11_elems *parsed; struct sk_buff *skb; u8 *len_mle, *len_prof; int i; skb = alloc_skb(1024, GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, skb); if (skb_pad(skb, skb_tailroom(skb))) { KUNIT_FAIL(test, "failed to pad skb"); return; } /* build a multi-link element */ skb_put_u8(skb, WLAN_EID_EXTENSION); len_mle = skb_put(skb, 1); skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK); put_unaligned_le16(IEEE80211_ML_CONTROL_TYPE_BASIC, skb_put(skb, 2)); /* struct ieee80211_mle_basic_common_info */ skb_put_u8(skb, 7); /* includes len field */ skb_put_data(skb, "\x00\x00\x00\x00\x00\x00", ETH_ALEN); /* MLD addr */ /* with a STA profile inside */ skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE); len_prof = skb_put(skb, 1); put_unaligned_le16(IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE | parse_params.link_id, skb_put(skb, 2)); skb_put_u8(skb, 1); /* fake sta_info_len - includes itself */ /* put a bunch of useless elements into it */ for (i = 0; i < 20; i++) { skb_put_u8(skb, WLAN_EID_SSID); skb_put_u8(skb, 20); skb_put(skb, 20); } /* fragment STA profile */ ieee80211_fragment_element(skb, len_prof, IEEE80211_MLE_SUBELEM_FRAGMENT); /* fragment MLE */ ieee80211_fragment_element(skb, len_mle, WLAN_EID_FRAGMENT); parse_params.start = skb->data; parse_params.len = skb->len; parsed = ieee802_11_parse_elems_full(&parse_params); /* should return ERR_PTR or valid, not NULL */ KUNIT_EXPECT_NOT_NULL(test, parsed); if (IS_ERR_OR_NULL(parsed)) goto free_skb; KUNIT_EXPECT_NOT_NULL(test, parsed->ml_basic); KUNIT_EXPECT_EQ(test, parsed->ml_basic_len, 2 /* control */ + 7 /* common info */ + 2 /* sta profile element header */ + 3 /* sta profile header */ + 20 * 22 /* sta profile data */ + 2 /* sta profile fragment element */); KUNIT_EXPECT_NOT_NULL(test, parsed->prof); KUNIT_EXPECT_EQ(test, parsed->sta_prof_len, 3 /* sta profile header */ + 20 * 22 /* sta profile data */); kfree(parsed); free_skb: kfree_skb(skb); } static void mesh_preq_parse(struct kunit *test) { const struct mesh_preq_parse_test_case *params = test->param_value; u8 data[64] = {}; struct ieee80211_mesh_hwmp_preq_top *top = (void *)data; struct ieee80211_mesh_hwmp_preq_bottom *bottom; top->flags = params->ae_enabled ? AE_F : 0; bottom = ieee80211_mesh_hwmp_preq_get_bottom(data); bottom->target_count = params->target_count; KUNIT_EXPECT_EQ(test, ieee80211_mesh_preq_size_ok(data, params->len), params->result); } static void mesh_prep_parse(struct kunit *test) { const struct mesh_prep_parse_test_case *params = test->param_value; u8 data[64] = {}; struct ieee80211_mesh_hwmp_prep_top *top = (void *)data; top->flags = params->ae_enabled ? AE_F : 0; KUNIT_EXPECT_EQ(test, ieee80211_mesh_prep_size_ok(data, params->len), params->result); } static void mesh_perr_parse(struct kunit *test) { const struct mesh_perr_parse_test_case *params = test->param_value; u8 data[256] = {}; struct ieee80211_mesh_hwmp_perr *perr = (void *)data; perr->number_of_dst = params->number_of_dst; if (params->ae_enabled_idx > -1) { struct ieee80211_mesh_hwmp_perr_dst *dst = ieee80211_mesh_hwmp_perr_get_dst( data, params->ae_enabled_idx); dst->flags = AE_F; } KUNIT_EXPECT_EQ(test, ieee80211_mesh_perr_size_ok(data, params->len), params->result); } static struct kunit_case element_parsing_test_cases[] = { KUNIT_CASE(mle_defrag), KUNIT_CASE_PARAM(mesh_preq_parse, mesh_preq_parse_gen_params), KUNIT_CASE_PARAM(mesh_prep_parse, mesh_prep_parse_gen_params), KUNIT_CASE_PARAM(mesh_perr_parse, mesh_perr_parse_gen_params), {} }; static struct kunit_suite element_parsing = { .name = "mac80211-element-parsing", .test_cases = element_parsing_test_cases, }; kunit_test_suite(element_parsing);