diff options
author | Jo-Philipp Wich <jo@mein.io> | 2021-09-19 11:20:37 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2021-09-21 20:46:45 +0200 |
commit | bb358d90d3a7de139516e3682843640754a5228a (patch) | |
tree | 9345c2ca715d681dbe94246e019f20c22b3d1e7c /lib/nl80211.c | |
parent | 914f54cc61e6b16005cceb9562289be0c80e401b (diff) |
lib: introduce Linux 802.11 netlink binding
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'lib/nl80211.c')
-rw-r--r-- | lib/nl80211.c | 2418 |
1 files changed, 2418 insertions, 0 deletions
diff --git a/lib/nl80211.c b/lib/nl80211.c new file mode 100644 index 0000000..e2e7c27 --- /dev/null +++ b/lib/nl80211.c @@ -0,0 +1,2418 @@ +/* +Copyright 2021 Jo-Philipp Wich <jo@mein.io> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <limits.h> +#include <math.h> +#include <assert.h> +#include <endian.h> +#include <fcntl.h> +#include <poll.h> + +#include <net/if.h> +#include <netinet/ether.h> +#include <arpa/inet.h> +#include <netlink/msg.h> +#include <netlink/attr.h> +#include <netlink/socket.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/family.h> +#include <netlink/genl/ctrl.h> + +#include <linux/nl80211.h> +#include <linux/ieee80211.h> + +#include "ucode/module.h" + +#define err_return(code, ...) do { set_error(code, __VA_ARGS__); return NULL; } while(0) + +static struct { + int code; + char *msg; +} last_error; + +static void +set_error(int errcode, const char *fmt, ...) { + va_list ap; + + free(last_error.msg); + + last_error.code = errcode; + last_error.msg = NULL; + + if (fmt) { + va_start(ap, fmt); + vasprintf(&last_error.msg, fmt, ap); + va_end(ap); + } +} + +static bool +uc_nl_parse_u32(uc_value_t *val, uint32_t *n) +{ + uc_type_t t; + int64_t i; + double d; + + t = ucv_cast_number(val, &i, &d); + + if (t == UC_DOUBLE) { + if (isnan(d) || d < 0 || d > UINT32_MAX) + return false; + + i = (int64_t)d; + + if (d - i > 0) + return false; + } + else if (errno != 0) { + return false; + } + + if (i < 0 || i > UINT32_MAX) + return false; + + *n = (uint32_t)i; + + return true; +} + +static bool +uc_nl_parse_s32(uc_value_t *val, uint32_t *n) +{ + uc_type_t t; + int64_t i; + double d; + + t = ucv_cast_number(val, &i, &d); + + if (t == UC_DOUBLE) { + if (isnan(d) || d < INT32_MIN || d > INT32_MAX) + return false; + + i = (int64_t)d; + + if (d - i > 0) + return false; + } + else if (errno != 0) { + return false; + } + + if (i < INT32_MIN || i > INT32_MAX) + return false; + + *n = (uint32_t)i; + + return true; +} + +static bool +uc_nl_parse_u64(uc_value_t *val, uint64_t *n) +{ + uc_type_t t; + int64_t i; + double d; + + if (ucv_type(val) == UC_INTEGER) { + *n = ucv_uint64_get(val); + + return true; + } + + t = ucv_cast_number(val, &i, &d); + + if (t == UC_DOUBLE) { + if (isnan(d) || d < 0) + return false; + + i = (int64_t)d; + + if (d - i > 0) + return false; + } + else if (errno != 0) { + return false; + } + + if (i < 0) + return false; + + *n = (uint64_t)i; + + return true; +} + +static bool +uc_nl_parse_ipaddr(uc_vm_t *vm, uc_value_t *val, struct in_addr *in) +{ + char *s = ucv_to_string(vm, val); + bool valid = true; + + if (!s) + return false; + + valid = (inet_pton(AF_INET, s, in) == 1); + + free(s); + + return valid; +} + +typedef enum { + DT_FLAG, + DT_BOOL, + DT_U8, + DT_U16, + DT_U32, + DT_S32, + DT_U64, + DT_STRING, + DT_NETDEV, + DT_LLADDR, + DT_INADDR, + DT_NESTED, + DT_HT_MCS, + DT_HT_CAP, + DT_VHT_MCS, + DT_HE_MCS, + DT_IE, +} uc_nl_attr_datatype_t; + +enum { + DF_NO_SET = (1 << 0), + DF_MULTIPLE = (1 << 1), + DF_AUTOIDX = (1 << 2), + DF_TYPEIDX = (1 << 3), + DF_OFFSET1 = (1 << 4), + DF_ARRAY = (1 << 5), + DF_BINARY = (1 << 6), +}; + +typedef struct uc_nl_attr_spec { + size_t attr; + const char *key; + uc_nl_attr_datatype_t type; + uint32_t flags; + const void *auxdata; +} uc_nl_attr_spec_t; + +typedef struct uc_nl_nested_spec { + size_t headsize; + size_t nattrs; + const uc_nl_attr_spec_t attrs[]; +} uc_nl_nested_spec_t; + +#define SIZE(type) (void *)(uintptr_t)sizeof(struct type) +#define MEMBER(type, field) (void *)(uintptr_t)offsetof(struct type, field) + +static const uc_nl_nested_spec_t nl80211_cqm_nla = { + .headsize = 0, + .nattrs = 5, + .attrs = { + { NL80211_ATTR_CQM_PKT_LOSS_EVENT, "cqm_pkt_loss_event", DT_U32, 0, NULL }, + { NL80211_ATTR_CQM_RSSI_HYST, "cqm_rssi_hyst", DT_U32, 0, NULL }, + { NL80211_ATTR_CQM_RSSI_LEVEL, "cqm_rssi_level", DT_S32, 0, NULL }, + { NL80211_ATTR_CQM_RSSI_THOLD, "cqm_rssi_thold", DT_U32, 0, NULL }, + { NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT, "cqm_rssi_threshold_event", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_ftm_responder_stats_nla = { + .headsize = 0, + .nattrs = 9, + .attrs = { + { NL80211_FTM_STATS_SUCCESS_NUM, "success_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_PARTIAL_NUM, "partial_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_FAILED_NUM, "failed_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_ASAP_NUM, "asap_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_NON_ASAP_NUM, "non_asap_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_TOTAL_DURATION_MSEC, "total_duration_msec", DT_U64, 0, NULL }, + { NL80211_FTM_STATS_UNKNOWN_TRIGGERS_NUM, "unknown_triggers_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_RESCHEDULE_REQUESTS_NUM, "reschedule_requests_num", DT_U32, 0, NULL }, + { NL80211_FTM_STATS_OUT_OF_WINDOW_TRIGGERS_NUM, "out_of_window_triggers_num", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_ifcomb_limit_types_nla = { + .headsize = 0, + .nattrs = 12, + .attrs = { + { 1, "ibss", DT_FLAG, 0, NULL }, + { 2, "managed", DT_FLAG, 0, NULL }, + { 3, "ap", DT_FLAG, 0, NULL }, + { 4, "ap_vlan", DT_FLAG, 0, NULL }, + { 5, "wds", DT_FLAG, 0, NULL }, + { 6, "monitor", DT_FLAG, 0, NULL }, + { 7, "mesh_point", DT_FLAG, 0, NULL }, + { 8, "p2p_client", DT_FLAG, 0, NULL }, + { 9, "p2p_go", DT_FLAG, 0, NULL }, + { 10, "p2p_device", DT_FLAG, 0, NULL }, + { 11, "outside_bss_context", DT_FLAG, 0, NULL }, + { 12, "nan", DT_FLAG, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_ifcomb_limits_nla = { + .headsize = 0, + .nattrs = 2, + .attrs = { + { NL80211_IFACE_LIMIT_TYPES, "types", DT_NESTED, 0, &nl80211_ifcomb_limit_types_nla }, + { NL80211_IFACE_LIMIT_MAX, "max", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_ifcomb_nla = { + .headsize = 0, + .nattrs = 5, + .attrs = { + { NL80211_IFACE_COMB_LIMITS, "limits", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_ifcomb_limits_nla }, + { NL80211_IFACE_COMB_MAXNUM, "maxnum", DT_U32, 0, NULL }, + { NL80211_IFACE_COMB_STA_AP_BI_MATCH, "sta_ap_bi_match", DT_FLAG, 0, NULL }, + { NL80211_IFACE_COMB_NUM_CHANNELS, "num_channels", DT_U32, 0, NULL }, + { NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS, "radar_detect_widths", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_ftm_responder_nla = { + .headsize = 0, + .nattrs = 3, + .attrs = { + { NL80211_FTM_RESP_ATTR_ENABLED, "enabled", DT_FLAG, 0, NULL }, + { NL80211_FTM_RESP_ATTR_LCI, "lci", DT_STRING, DF_BINARY, NULL }, + { NL80211_FTM_RESP_ATTR_CIVICLOC, "civicloc", DT_STRING, DF_BINARY, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_keys_nla = { + .headsize = 0, + .nattrs = 4, + .attrs = { + { NL80211_KEY_DEFAULT, "default", DT_FLAG, 0, NULL }, + { NL80211_KEY_IDX, "idx", DT_U8, 0, NULL }, + { NL80211_KEY_CIPHER, "cipher", DT_U32, 0, NULL }, + { NL80211_KEY_DATA, "data", DT_STRING, DF_BINARY, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_mesh_params_nla = { + .headsize = 0, + .nattrs = 29, + .attrs = { + { NL80211_MESHCONF_RETRY_TIMEOUT, "retry_timeout", DT_U16, 0, NULL }, + { NL80211_MESHCONF_CONFIRM_TIMEOUT, "confirm_timeout", DT_U16, 0, NULL }, + { NL80211_MESHCONF_HOLDING_TIMEOUT, "holding_timeout", DT_U16, 0, NULL }, + { NL80211_MESHCONF_MAX_PEER_LINKS, "max_peer_links", DT_U16, 0, NULL }, + { NL80211_MESHCONF_MAX_RETRIES, "max_retries", DT_U8, 0, NULL }, + { NL80211_MESHCONF_TTL, "ttl", DT_U8, 0, NULL }, + { NL80211_MESHCONF_ELEMENT_TTL, "element_ttl", DT_U8, 0, NULL }, + { NL80211_MESHCONF_AUTO_OPEN_PLINKS, "auto_open_plinks", DT_BOOL, 0, NULL }, + { NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES, "hwmp_max_preq_retries", DT_U8, 0, NULL }, + { NL80211_MESHCONF_PATH_REFRESH_TIME, "path_refresh_time", DT_U32, 0, NULL }, + { NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT, "min_discovery_timeout", DT_U16, 0, NULL }, + { NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT, "hwmp_active_path_timeout", DT_U32, 0, NULL }, + { NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL, "hwmp_preq_min_interval", DT_U16, 0, NULL }, + { NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME, "hwmp_net_diam_trvs_time", DT_U16, 0, NULL }, + { NL80211_MESHCONF_HWMP_ROOTMODE, "hwmp_rootmode", DT_U8, 0, NULL }, + { NL80211_MESHCONF_HWMP_RANN_INTERVAL, "hwmp_rann_interval", DT_U16, 0, NULL }, + { NL80211_MESHCONF_GATE_ANNOUNCEMENTS, "gate_announcements", DT_U8, 0, NULL }, + { NL80211_MESHCONF_FORWARDING, "forwarding", DT_BOOL, 0, NULL }, + { NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR, "sync_offset_max_neighbor", DT_U32, 0, NULL }, + { NL80211_MESHCONF_RSSI_THRESHOLD, "rssi_threshold", DT_S32, 0, NULL }, + { NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT, "hwmp_path_to_root_timeout", DT_U32, 0, NULL }, + { NL80211_MESHCONF_HWMP_ROOT_INTERVAL, "hwmp_root_interval", DT_U16, 0, NULL }, + { NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL, "hwmp_confirmation_interval", DT_U16, 0, NULL }, + { NL80211_MESHCONF_POWER_MODE, "power_mode", DT_U32, 0, NULL }, + { NL80211_MESHCONF_AWAKE_WINDOW, "awake_window", DT_U16, 0, NULL }, + { NL80211_MESHCONF_PLINK_TIMEOUT, "plink_timeout", DT_U32, 0, NULL }, + { NL80211_MESHCONF_CONNECTED_TO_GATE, "connected_to_gate", DT_BOOL, 0, NULL }, + { NL80211_MESHCONF_NOLEARN, "nolearn", DT_BOOL, 0, NULL }, + { NL80211_MESHCONF_CONNECTED_TO_AS, "connected_to_as", DT_BOOL, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_mesh_setup_nla = { + .headsize = 0, + .nattrs = 1, + .attrs = { + { NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC, "enable_vendor_sync", DT_BOOL, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_mntr_flags_nla = { + .headsize = 0, + .nattrs = 6, + .attrs = { + { NL80211_MNTR_FLAG_FCSFAIL, "fcsfail", DT_FLAG, 0, NULL }, + { NL80211_MNTR_FLAG_PLCPFAIL, "plcpfail", DT_FLAG, 0, NULL }, + { NL80211_MNTR_FLAG_CONTROL, "control", DT_FLAG, 0, NULL }, + { NL80211_MNTR_FLAG_OTHER_BSS, "other_bss", DT_FLAG, 0, NULL }, + { NL80211_MNTR_FLAG_COOK_FRAMES, "cook_frames", DT_FLAG, 0, NULL }, + { NL80211_MNTR_FLAG_ACTIVE, "active", DT_FLAG, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_nan_func_srf_nla = { + .headsize = 0, + .nattrs = 4, + .attrs = { + { NL80211_NAN_SRF_INCLUDE, "include", DT_FLAG, 0, NULL }, + { NL80211_NAN_SRF_BF_IDX, "bf_idx", DT_U8, 0, NULL }, + { NL80211_NAN_SRF_BF, "bf", DT_STRING, DF_BINARY, NULL }, + { NL80211_NAN_SRF_MAC_ADDRS, "mac_addrs", DT_LLADDR, DF_MULTIPLE|DF_AUTOIDX, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_nan_func_nla = { + .headsize = 0, + .nattrs = 16, + .attrs = { + { NL80211_NAN_FUNC_TYPE, "type", DT_U8, 0, NULL }, + { NL80211_NAN_FUNC_SERVICE_ID, "service_id", DT_STRING, DF_BINARY, NULL }, + { NL80211_NAN_FUNC_PUBLISH_TYPE, "publish_type", DT_U8, 0, NULL }, + { NL80211_NAN_FUNC_PUBLISH_BCAST, "publish_bcast", DT_FLAG, 0, NULL }, + { NL80211_NAN_FUNC_SUBSCRIBE_ACTIVE, "subscribe_active", DT_FLAG, 0, NULL }, + { NL80211_NAN_FUNC_FOLLOW_UP_ID, "follow_up_id", DT_U8, 0, NULL }, + { NL80211_NAN_FUNC_FOLLOW_UP_REQ_ID, "follow_up_req_id", DT_U8, 0, NULL }, + { NL80211_NAN_FUNC_FOLLOW_UP_DEST, "follow_up_dest", DT_LLADDR, 0, NULL }, + { NL80211_NAN_FUNC_CLOSE_RANGE, "close_range", DT_FLAG, 0, NULL }, + { NL80211_NAN_FUNC_TTL, "ttl", DT_U32, 0, NULL }, + { NL80211_NAN_FUNC_SERVICE_INFO, "service_info", DT_STRING, 0, NULL }, + { NL80211_NAN_FUNC_SRF, "srf", DT_NESTED, 0, &nl80211_nan_func_srf_nla }, + { NL80211_NAN_FUNC_RX_MATCH_FILTER, "rx_match_filter", DT_STRING, DF_MULTIPLE|DF_AUTOIDX, NULL }, + { NL80211_NAN_FUNC_TX_MATCH_FILTER, "tx_match_filter", DT_STRING, DF_MULTIPLE|DF_AUTOIDX, NULL }, + { NL80211_NAN_FUNC_INSTANCE_ID, "instance_id", DT_U8, 0, NULL }, + { NL80211_NAN_FUNC_TERM_REASON, "term_reason", DT_U8, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_peer_measurements_peers_req_data_ftm_nla = { + .headsize = 0, + .nattrs = 13, + .attrs = { + { NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP, "num_bursts_exp", DT_U8, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD, "burst_period", DT_U16, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES, "num_ftmr_retries", DT_U8, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION, "burst_duration", DT_U8, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST, "ftms_per_burst", DT_U8, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_ASAP, "asap", DT_FLAG, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_REQUEST_CIVICLOC, "request_civicloc", DT_FLAG, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI, "request_lci", DT_FLAG, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED, "trigger_based", DT_FLAG, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE, "preamble", DT_U32, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED, "non_trigger_based", DT_FLAG, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK, "lmr_feedback", DT_FLAG, 0, NULL }, + { NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR, "bss_color", DT_U8, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_peer_measurements_peers_req_data_nla = { + .headsize = 0, + .nattrs = 2, + .attrs = { + { NL80211_PMSR_TYPE_FTM, "ftm", DT_NESTED, 0, &nl80211_peer_measurements_peers_req_data_ftm_nla }, + { NL80211_PMSR_REQ_ATTR_GET_AP_TSF, "get_ap_tsf", DT_FLAG, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_peer_measurements_peers_req_nla = { + .headsize = 0, + .nattrs = 1, + .attrs = { + { NL80211_PMSR_REQ_ATTR_DATA, "data", DT_NESTED, 0, &nl80211_peer_measurements_peers_req_data_nla }, + } +}; + +static const uc_nl_nested_spec_t nl80211_peer_measurements_peers_chan_nla = { + .headsize = 0, + .nattrs = 4, + .attrs = { + { NL80211_ATTR_WIPHY_FREQ, "freq", DT_U32, 0, NULL }, + { NL80211_ATTR_CENTER_FREQ1, "center_freq1", DT_U32, 0, NULL }, + { NL80211_ATTR_CENTER_FREQ2, "center_freq2", DT_U32, 0, NULL }, + { NL80211_ATTR_CHANNEL_WIDTH, "channel_width", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_peer_measurements_peers_nla = { + .headsize = 0, + .nattrs = 3, + .attrs = { + { NL80211_PMSR_PEER_ATTR_ADDR, "addr", DT_LLADDR, 0, NULL }, + { NL80211_PMSR_PEER_ATTR_REQ, "req", DT_NESTED, 0, &nl80211_peer_measurements_peers_req_nla }, + { NL80211_PMSR_PEER_ATTR_CHAN, "chan", DT_NESTED, 0, &nl80211_peer_measurements_peers_chan_nla } + } +}; + +static const uc_nl_nested_spec_t nl80211_peer_measurements_nla = { + .headsize = 0, + .nattrs = 1, + .attrs = { + { NL80211_PMSR_ATTR_PEERS, "peers", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_peer_measurements_peers_nla }, + } +}; + +static const uc_nl_nested_spec_t nl80211_reg_rules_nla = { + .headsize = 0, + .nattrs = 7, + .attrs = { + { NL80211_ATTR_REG_RULE_FLAGS, "reg_rule_flags", DT_U32, 0, NULL }, + { NL80211_ATTR_FREQ_RANGE_START, "freq_range_start", DT_U32, 0, NULL }, + { NL80211_ATTR_FREQ_RANGE_END, "freq_range_end", DT_U32, 0, NULL }, + { NL80211_ATTR_FREQ_RANGE_MAX_BW, "freq_range_max_bw", DT_U32, 0, NULL }, + { NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN, "power_rule_max_ant_gain", DT_U32, 0, NULL }, + { NL80211_ATTR_POWER_RULE_MAX_EIRP, "power_rule_max_eirp", DT_U32, 0, NULL }, + { NL80211_ATTR_DFS_CAC_TIME, "dfs_cac_time", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_frame_types_nla = { + .headsize = 0, + .nattrs = 12, + .attrs = { + { 1, "ibss", DT_U16, DF_MULTIPLE, NULL }, + { 2, "managed", DT_U16, DF_MULTIPLE, NULL }, + { 3, "ap", DT_U16, DF_MULTIPLE, NULL }, + { 4, "ap_vlan", DT_U16, DF_MULTIPLE, NULL }, + { 5, "wds", DT_U16, DF_MULTIPLE, NULL }, + { 6, "monitor", DT_U16, DF_MULTIPLE, NULL }, + { 7, "mesh_point", DT_U16, DF_MULTIPLE, NULL }, + { 8, "p2p_client", DT_U16, DF_MULTIPLE, NULL }, + { 9, "p2p_go", DT_U16, DF_MULTIPLE, NULL }, + { 10, "p2p_device", DT_U16, DF_MULTIPLE, NULL }, + { 11, "outside_bss_context", DT_U16, DF_MULTIPLE, NULL }, + { 12, "nan", DT_U16, DF_MULTIPLE, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_sched_scan_match_nla = { + .headsize = 0, + .nattrs = 1, + .attrs = { + { NL80211_SCHED_SCAN_MATCH_ATTR_SSID, "ssid", DT_STRING, DF_BINARY, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_sched_scan_plan_nla = { + .headsize = 0, + .nattrs = 2, + .attrs = { + { NL80211_SCHED_SCAN_PLAN_INTERVAL, "interval", DT_U32, 0, NULL }, + { NL80211_SCHED_SCAN_PLAN_ITERATIONS, "iterations", DT_U32, 0, NULL }, + } +}; + +enum { + HWSIM_TM_ATTR_CMD = 1, + HWSIM_TM_ATTR_PS = 2, +}; + +static const uc_nl_nested_spec_t nl80211_testdata_nla = { + .headsize = 0, + .nattrs = 2, + .attrs = { + { HWSIM_TM_ATTR_CMD, "cmd", DT_U32, 0, NULL }, + { HWSIM_TM_ATTR_PS, "ps", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_tid_config_nla = { + .headsize = 0, + .nattrs = 1, + .attrs = { + { NL80211_TID_CONFIG_ATTR_TIDS, "tids", DT_U16, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wiphy_bands_freqs_wmm_nla = { + .headsize = 0, + .nattrs = 4, + .attrs = { + { NL80211_WMMR_CW_MIN, "cw_min", DT_U16, 0, NULL }, + { NL80211_WMMR_CW_MAX, "cw_max", DT_U16, 0, NULL }, + { NL80211_WMMR_AIFSN, "aifsn", DT_U8, 0, NULL }, + { NL80211_WMMR_TXOP, "txop", DT_U16, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wiphy_bands_freqs_nla = { + .headsize = 0, + .nattrs = 25, + .attrs = { + { NL80211_FREQUENCY_ATTR_FREQ, "freq", DT_U32, 0, NULL }, + { NL80211_FREQUENCY_ATTR_DISABLED, "disabled", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_IR, "no_ir", DT_FLAG, 0, NULL }, + { __NL80211_FREQUENCY_ATTR_NO_IBSS, "no_ibss", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_RADAR, "radar", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_MAX_TX_POWER, "max_tx_power", DT_U32, 0, NULL }, + { NL80211_FREQUENCY_ATTR_DFS_STATE, "dfs_state", DT_U32, 0, NULL }, + { NL80211_FREQUENCY_ATTR_DFS_TIME, "dfs_time", DT_U32, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_HT40_MINUS, "no_ht40_minus", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_HT40_PLUS, "no_ht40_plus", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_80MHZ, "no_80mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_160MHZ, "no_160mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_DFS_CAC_TIME, "dfs_cac_time", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_INDOOR_ONLY, "indoor_only", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_IR_CONCURRENT, "ir_concurrent", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_20MHZ, "no_20mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_NO_10MHZ, "no_10mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_WMM, "wmm", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_wiphy_bands_freqs_wmm_nla }, + { NL80211_FREQUENCY_ATTR_NO_HE, "no_he", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_OFFSET, "offset", DT_U32, 0, NULL }, + { NL80211_FREQUENCY_ATTR_1MHZ, "1mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_2MHZ, "2mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_4MHZ, "4mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_8MHZ, "8mhz", DT_FLAG, 0, NULL }, + { NL80211_FREQUENCY_ATTR_16MHZ, "16mhz", DT_FLAG, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wiphy_bands_rates_nla = { + .headsize = 0, + .nattrs = 2, + .attrs = { + { NL80211_BITRATE_ATTR_RATE, "rate", DT_U32, 0, NULL }, + { NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE, "2ghz_shortpreamble", DT_FLAG, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wiphy_bands_iftype_data_nla = { + .headsize = 0, + .nattrs = 7, + .attrs = { + { NL80211_BAND_IFTYPE_ATTR_IFTYPES, "iftypes", DT_NESTED, 0, &nl80211_ifcomb_limit_types_nla }, + { NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC, "he_cap_mac", DT_U16, DF_ARRAY, NULL }, + { NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY, "he_cap_phy", DT_U16, DF_ARRAY, NULL }, + { NL80211_BAND_IFTYPE_ATTR_HE_CAP_MCS_SET, "he_cap_mcs_set", DT_HE_MCS, 0, NULL }, + { NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE, "he_cap_ppe", DT_U8, DF_ARRAY, NULL }, + { NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA, "he_6ghz_capa", DT_U16, 0, NULL }, + { NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS, "vendor_elems", DT_STRING, DF_BINARY, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wiphy_bands_nla = { + .headsize = 0, + .nattrs = 11, + .attrs = { + { NL80211_BAND_ATTR_FREQS, "freqs", DT_NESTED, DF_MULTIPLE|DF_TYPEIDX, &nl80211_wiphy_bands_freqs_nla }, + { NL80211_BAND_ATTR_RATES, "rates", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_wiphy_bands_rates_nla }, + { NL80211_BAND_ATTR_HT_MCS_SET, "ht_mcs_set", DT_HT_MCS, 0, NULL }, + { NL80211_BAND_ATTR_HT_CAPA, "ht_capa", DT_U16, 0, NULL }, + { NL80211_BAND_ATTR_HT_AMPDU_FACTOR, "ht_ampdu_factor", DT_U8, 0, NULL }, + { NL80211_BAND_ATTR_HT_AMPDU_DENSITY, "ht_ampdu_density", DT_U8, 0, NULL }, + { NL80211_BAND_ATTR_VHT_MCS_SET, "vht_mcs_set", DT_VHT_MCS, 0, NULL }, + { NL80211_BAND_ATTR_VHT_CAPA, "vht_capa", DT_U32, 0, NULL }, + { NL80211_BAND_ATTR_IFTYPE_DATA, "iftype_data", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_wiphy_bands_iftype_data_nla }, + { NL80211_BAND_ATTR_EDMG_CHANNELS, "edmg_channels", DT_U8, 0, NULL }, + { NL80211_BAND_ATTR_EDMG_BW_CONFIG, "edmg_bw_config", DT_U8, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wowlan_triggers_tcp_nla = { + .headsize = 0, + .nattrs = 11, + .attrs = { + { NL80211_WOWLAN_TCP_SRC_IPV4, "src_ipv4", DT_INADDR, 0, NULL }, + { NL80211_WOWLAN_TCP_SRC_PORT, "src_port", DT_U16, 0, NULL }, + { NL80211_WOWLAN_TCP_DST_IPV4, "dst_ipv4", DT_INADDR, 0, NULL }, + { NL80211_WOWLAN_TCP_DST_PORT, "dst_port", DT_U16, 0, NULL }, + { NL80211_WOWLAN_TCP_DST_MAC, "dst_mac", DT_LLADDR, 0, NULL }, + { NL80211_WOWLAN_TCP_DATA_PAYLOAD, "data_payload", DT_STRING, DF_BINARY, NULL }, + { NL80211_WOWLAN_TCP_DATA_INTERVAL, "data_interval", DT_U32, 0, NULL }, + { NL80211_WOWLAN_TCP_WAKE_MASK, "wake_mask", DT_STRING, DF_BINARY, NULL }, + { NL80211_WOWLAN_TCP_WAKE_PAYLOAD, "wake_payload", DT_STRING, DF_BINARY, NULL }, + { NL80211_WOWLAN_TCP_DATA_PAYLOAD_SEQ, "data_payload_seq", DT_U32, DF_ARRAY, NULL }, + { NL80211_WOWLAN_TCP_DATA_PAYLOAD_TOKEN, "data_payload_token", DT_STRING, DF_BINARY, NULL }, /* XXX: struct nl80211_wowlan_tcp_data_token */ + } +}; + +static const uc_nl_nested_spec_t nl80211_pkt_pattern_nla = { + .headsize = 0, + .nattrs = 3, + .attrs = { + { NL80211_PKTPAT_MASK, "mask", DT_STRING, DF_BINARY, NULL }, + { NL80211_PKTPAT_PATTERN, "pattern", DT_STRING, DF_BINARY, NULL }, + { NL80211_PKTPAT_OFFSET, "offset", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_wowlan_triggers_nla = { + .headsize = 0, + .nattrs = 9, + .attrs = { + { NL80211_WOWLAN_TRIG_ANY, "any", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_DISCONNECT, "disconnect", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_MAGIC_PKT, "magic_pkt", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE, "gtk_rekey_failure", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST, "eap_ident_request", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE, "4way_handshake", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_RFKILL_RELEASE, "rfkill_release", DT_FLAG, 0, NULL }, + { NL80211_WOWLAN_TRIG_TCP_CONNECTION, "tcp_connection", DT_NESTED, 0, &nl80211_wowlan_triggers_tcp_nla }, + { NL80211_WOWLAN_TRIG_PKT_PATTERN, "pkt_pattern", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX|DF_OFFSET1, &nl80211_pkt_pattern_nla }, + } +}; + +static const uc_nl_nested_spec_t nl80211_coalesce_rule_nla = { + .headsize = 0, + .nattrs = 3, + .attrs = { + { NL80211_ATTR_COALESCE_RULE_CONDITION, "coalesce_rule_condition", DT_U32, 0, NULL }, + { NL80211_ATTR_COALESCE_RULE_DELAY, "coalesce_rule_delay", DT_U32, 0, NULL }, + { NL80211_ATTR_COALESCE_RULE_PKT_PATTERN, "coalesce_rule_pkt_pattern", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX|DF_OFFSET1, &nl80211_pkt_pattern_nla }, + } +}; + +static const uc_nl_nested_spec_t nl80211_bss_nla = { + .headsize = 0, + .nattrs = 12, + .attrs = { + { NL80211_BSS_BSSID, "bssid", DT_LLADDR, 0, NULL }, + { NL80211_BSS_STATUS, "status", DT_U32, 0, NULL }, + { NL80211_BSS_LAST_SEEN_BOOTTIME, "last_seen_boottime", DT_U64, 0, NULL }, + { NL80211_BSS_TSF, "tsf", DT_U64, 0, NULL }, + { NL80211_BSS_FREQUENCY, "frequency", DT_U32, 0, NULL }, + { NL80211_BSS_BEACON_INTERVAL, "beacon_interval", DT_U16, 0, NULL }, + { NL80211_BSS_CAPABILITY, "capability", DT_U16, 0, NULL }, + { NL80211_BSS_SIGNAL_MBM, "signal_mbm", DT_S32, 0, NULL }, + { NL80211_BSS_SIGNAL_UNSPEC, "signal_unspec", DT_U8, 0, NULL }, + { NL80211_BSS_SEEN_MS_AGO, "seen_ms_ago", DT_S32, 0, NULL }, + { NL80211_BSS_INFORMATION_ELEMENTS, "information_elements", DT_IE, 0, NULL }, + { NL80211_BSS_BEACON_IES, "beacon_ies", DT_IE, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t nl80211_msg = { + .headsize = 0, + .nattrs = 123, + .attrs = { + { NL80211_ATTR_4ADDR, "4addr", DT_U8, 0, NULL }, + { NL80211_ATTR_AIRTIME_WEIGHT, "airtime_weight", DT_U16, 0, NULL }, + { NL80211_ATTR_AKM_SUITES, "akm_suites", DT_U32, 0, NULL }, + { NL80211_ATTR_AUTH_TYPE, "auth_type", DT_U32, 0, NULL }, + { NL80211_ATTR_BANDS, "bands", DT_U32, 0, NULL }, + { NL80211_ATTR_BEACON_HEAD, "beacon_head", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_BEACON_INTERVAL, "beacon_interval", DT_U32, 0, NULL }, + { NL80211_ATTR_BEACON_TAIL, "beacon_tail", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_BSS, "bss", DT_NESTED, 0, &nl80211_bss_nla }, + { NL80211_ATTR_BSS_BASIC_RATES, "bss_basic_rates", DT_U32, DF_ARRAY, NULL }, + { NL80211_ATTR_CENTER_FREQ1, "center_freq1", DT_U32, 0, NULL }, + { NL80211_ATTR_CENTER_FREQ2, "center_freq2", DT_U32, 0, NULL }, + { NL80211_ATTR_CHANNEL_WIDTH, "channel_width", DT_U32, 0, NULL }, + { NL80211_ATTR_CH_SWITCH_BLOCK_TX, "ch_switch_block_tx", DT_FLAG, 0, NULL }, + { NL80211_ATTR_CH_SWITCH_COUNT, "ch_switch_count", DT_U32, 0, NULL }, + { NL80211_ATTR_CIPHER_SUITES, "cipher_suites", DT_U32, DF_ARRAY, NULL }, + { NL80211_ATTR_CIPHER_SUITES_PAIRWISE, "cipher_suites_pairwise", DT_U32, 0, NULL }, + { NL80211_ATTR_CIPHER_SUITE_GROUP, "cipher_suite_group", DT_U32, 0, NULL }, + { NL80211_ATTR_COALESCE_RULE, "coalesce_rule", DT_NESTED, 0, &nl80211_coalesce_rule_nla }, + { NL80211_ATTR_COOKIE, "cookie", DT_U64, 0, NULL }, + { NL80211_ATTR_CQM, "cqm", DT_NESTED, 0, &nl80211_cqm_nla }, + { NL80211_ATTR_DFS_CAC_TIME, "dfs_cac_time", DT_U32, 0, NULL }, + { NL80211_ATTR_DFS_REGION, "dfs_region", DT_U8, 0, NULL }, + { NL80211_ATTR_DTIM_PERIOD, "dtim_period", DT_U32, 0, NULL }, + { NL80211_ATTR_DURATION, "duration", DT_U32, 0, NULL }, + { NL80211_ATTR_FEATURE_FLAGS, "feature_flags", DT_U32, 0, NULL }, + { NL80211_ATTR_FRAME, "frame", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_FRAME_MATCH, "frame_match", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_FRAME_TYPE, "frame_type", DT_U16, 0, NULL }, + { NL80211_ATTR_FREQ_FIXED, "freq_fixed", DT_FLAG, 0, NULL }, + { NL80211_ATTR_FTM_RESPONDER, "ftm_responder", DT_NESTED, 0, &nl80211_ftm_responder_nla }, + { NL80211_ATTR_FTM_RESPONDER_STATS, "ftm_responder_stats", DT_NESTED, 0, &nl80211_ftm_responder_stats_nla }, + { NL80211_ATTR_HIDDEN_SSID, "hidden_ssid", DT_U32, 0, NULL }, + { NL80211_ATTR_HT_CAPABILITY_MASK, "ht_capability_mask", DT_HT_CAP, 0, NULL }, + { NL80211_ATTR_IE, "ie", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_IFINDEX, "dev", DT_NETDEV, 0, NULL }, + { NL80211_ATTR_IFNAME, "ifname", DT_STRING, 0, NULL }, + { NL80211_ATTR_IFTYPE, "iftype", DT_U32, 0, NULL }, + { NL80211_ATTR_INACTIVITY_TIMEOUT, "inactivity_timeout", DT_U16, 0, NULL }, + { NL80211_ATTR_INTERFACE_COMBINATIONS, "interface_combinations", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_ifcomb_nla }, + { NL80211_ATTR_KEYS, "keys", DT_NESTED, DF_AUTOIDX, &nl80211_keys_nla }, + { NL80211_ATTR_KEY_SEQ, "key_seq", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_KEY_TYPE, "key_type", DT_U32, 0, NULL }, + { NL80211_ATTR_LOCAL_MESH_POWER_MODE, "local_mesh_power_mode", DT_U32, 0, NULL }, + { NL80211_ATTR_MAC, "mac", DT_LLADDR, 0, NULL }, + { NL80211_ATTR_MAC_MASK, "mac_mask", DT_LLADDR, 0, NULL }, + { NL80211_ATTR_MCAST_RATE, "mcast_rate", DT_U32, 0, NULL }, + { NL80211_ATTR_MEASUREMENT_DURATION, "measurement_duration", DT_U16, 0, NULL }, + { NL80211_ATTR_MESH_ID, "mesh_id", DT_STRING, 0, NULL }, + { NL80211_ATTR_MESH_PARAMS, "mesh_params", DT_NESTED, 0, &nl80211_mesh_params_nla }, + { NL80211_ATTR_MESH_SETUP, "mesh_setup", DT_NESTED, 0, &nl80211_mesh_setup_nla }, + { NL80211_ATTR_MGMT_SUBTYPE, "mgmt_subtype", DT_U8, 0, NULL }, + { NL80211_ATTR_MNTR_FLAGS, "mntr_flags", DT_NESTED, 0, &nl80211_mntr_flags_nla }, + { NL80211_ATTR_MPATH_NEXT_HOP, "mpath_next_hop", DT_LLADDR, 0, NULL }, + { NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR, "mu_mimo_follow_mac_addr", DT_LLADDR, 0, NULL }, + { NL80211_ATTR_NAN_FUNC, "nan_func", DT_NESTED, 0, &nl80211_nan_func_nla }, + { NL80211_ATTR_NAN_MASTER_PREF, "nan_master_pref", DT_U8, 0, NULL }, + { NL80211_ATTR_NETNS_FD, "netns_fd", DT_U32, 0, NULL }, + { NL80211_ATTR_NOACK_MAP, "noack_map", DT_U16, 0, NULL }, + { NL80211_ATTR_NSS, "nss", DT_U8, 0, NULL }, + { NL80211_ATTR_PEER_MEASUREMENTS, "peer_measurements", DT_NESTED, 0, &nl80211_peer_measurements_nla }, + { NL80211_ATTR_PID, "pid", DT_U32, 0, NULL }, + { NL80211_ATTR_PMK, "pmk", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_PRIVACY, "privacy", DT_FLAG, 0, NULL }, + { NL80211_ATTR_PROTOCOL_FEATURES, "protocol_features", DT_U32, 0, NULL }, + { NL80211_ATTR_PS_STATE, "ps_state", DT_U32, 0, NULL }, + { NL80211_ATTR_RADAR_EVENT, "radar_event", DT_U32, 0, NULL }, + { NL80211_ATTR_REASON_CODE, "reason_code", DT_U16, 0, NULL }, + { NL80211_ATTR_REG_ALPHA2, "reg_alpha2", DT_STRING, 0, NULL }, + { NL80211_ATTR_REG_INITIATOR, "reg_initiator", DT_U32, 0, NULL }, + { NL80211_ATTR_REG_RULES, "reg_rules", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_reg_rules_nla }, + { NL80211_ATTR_REG_TYPE, "reg_type", DT_U8, 0, NULL }, + { NL80211_ATTR_RX_FRAME_TYPES, "rx_frame_types", DT_NESTED, 0, &nl80211_frame_types_nla }, + { NL80211_ATTR_RX_SIGNAL_DBM, "rx_signal_dbm", DT_U32, 0, NULL }, + { NL80211_ATTR_SCAN_FLAGS, "scan_flags", DT_U32, 0, NULL }, + { NL80211_ATTR_SCAN_FREQUENCIES, "scan_frequencies", DT_U32, DF_MULTIPLE|DF_AUTOIDX, NULL }, + { NL80211_ATTR_SCAN_SSIDS, "scan_ssids", DT_STRING, DF_MULTIPLE|DF_AUTOIDX, NULL }, + { NL80211_ATTR_SCHED_SCAN_DELAY, "sched_scan_delay", DT_U32, 0, NULL }, + { NL80211_ATTR_SCHED_SCAN_INTERVAL, "sched_scan_interval", DT_U32, 0, NULL }, + { NL80211_ATTR_SCHED_SCAN_MATCH, "sched_scan_match", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX, &nl80211_sched_scan_match_nla }, + { NL80211_ATTR_SCHED_SCAN_PLANS, "sched_scan_plans", DT_NESTED, DF_MULTIPLE|DF_AUTOIDX|DF_OFFSET1, &nl80211_sched_scan_plan_nla }, + { NL80211_ATTR_SMPS_MODE, "smps_mode", DT_U8, 0, NULL }, + { NL80211_ATTR_SPLIT_WIPHY_DUMP, "split_wiphy_dump", DT_FLAG, 0, NULL }, + { NL80211_ATTR_SSID, "ssid", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_STATUS_CODE, "status_code", DT_U16, 0, NULL }, + { NL80211_ATTR_STA_PLINK_ACTION, "sta_plink_action", DT_U8, 0, NULL }, + { NL80211_ATTR_STA_TX_POWER, "sta_tx_power", DT_U16, 0, NULL }, + { NL80211_ATTR_STA_TX_POWER_SETTING, "sta_tx_power_setting", DT_U8, 0, NULL }, + { NL80211_ATTR_STA_VLAN, "sta_vlan", DT_U32, 0, NULL }, + { NL80211_ATTR_SUPPORTED_COMMANDS, "supported_commands", DT_U32, DF_NO_SET|DF_MULTIPLE|DF_AUTOIDX, NULL }, + { NL80211_ATTR_TESTDATA, "testdata", DT_NESTED, 0, &nl80211_testdata_nla }, + { NL80211_ATTR_TID_CONFIG, "tid_config", DT_NESTED, DF_MULTIPLE, &nl80211_tid_config_nla }, + { NL80211_ATTR_TIMEOUT, "timeout", DT_U32, 0, NULL }, + { NL80211_ATTR_TXQ_LIMIT, "txq_limit", DT_U32, 0, NULL }, + { NL80211_ATTR_TXQ_MEMORY_LIMIT, "txq_memory_limit", DT_U32, 0, NULL }, + { NL80211_ATTR_TXQ_QUANTUM, "txq_quantum", DT_U32, 0, NULL }, + { NL80211_ATTR_TX_FRAME_TYPES, "tx_frame_types", DT_NESTED, 0, &nl80211_frame_types_nla }, + { NL80211_ATTR_USE_MFP, "use_mfp", DT_U32, 0, NULL }, + { NL80211_ATTR_VENDOR_DATA, "vendor_data", DT_STRING, DF_BINARY, NULL }, + { NL80211_ATTR_VENDOR_ID, "vendor_id", DT_U32, 0, NULL }, + { NL80211_ATTR_VENDOR_SUBCMD, "vendor_subcmd", DT_U32, 0, NULL }, + { NL80211_ATTR_WDEV, "wdev", DT_U64, 0, NULL }, + { NL80211_ATTR_WIPHY, "wiphy", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX, "wiphy_antenna_avail_rx", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX, "wiphy_antenna_avail_tx", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_ANTENNA_RX, "wiphy_antenna_rx", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_ANTENNA_TX, "wiphy_antenna_tx", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_BANDS, "wiphy_bands", DT_NESTED, DF_NO_SET|DF_MULTIPLE|DF_TYPEIDX, &nl80211_wiphy_bands_nla }, + { NL80211_ATTR_WIPHY_CHANNEL_TYPE, "wiphy_channel_type", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_COVERAGE_CLASS, "wiphy_coverage_class", DT_U8, 0, NULL }, + { NL80211_ATTR_WIPHY_DYN_ACK, "wiphy_dyn_ack", DT_FLAG, 0, NULL }, + { NL80211_ATTR_WIPHY_FRAG_THRESHOLD, "wiphy_frag_threshold", DT_S32, 0, NULL }, + { NL80211_ATTR_WIPHY_FREQ, "wiphy_freq", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_NAME, "wiphy_name", DT_STRING, 0, NULL }, + { NL80211_ATTR_WIPHY_RETRY_LONG, "wiphy_retry_long", DT_U8, 0, NULL }, + { NL80211_ATTR_WIPHY_RETRY_SHORT, "wiphy_retry_short", DT_U8, 0, NULL }, + { NL80211_ATTR_WIPHY_RTS_THRESHOLD, "wiphy_rts_threshold", DT_S32, 0, NULL }, + { NL80211_ATTR_WIPHY_TX_POWER_LEVEL, "wiphy_tx_power_level", DT_U32, 0, NULL }, + { NL80211_ATTR_WIPHY_TX_POWER_SETTING, "wiphy_tx_power_setting", DT_U32, 0, NULL }, + { NL80211_ATTR_WOWLAN_TRIGGERS, "wowlan_triggers", DT_NESTED, 0, &nl80211_wowlan_triggers_nla }, + { NL80211_ATTR_WPA_VERSIONS, "wpa_versions", DT_U32, 0, NULL }, + { NL80211_ATTR_SUPPORTED_IFTYPES, "supported_iftypes", DT_NESTED, 0, &nl80211_ifcomb_limit_types_nla }, + { NL80211_ATTR_SOFTWARE_IFTYPES, "software_iftypes", DT_NESTED, 0, &nl80211_ifcomb_limit_types_nla }, + } +}; + + +static bool +nla_check_len(struct nlattr *nla, size_t sz) +{ + return (nla && nla_len(nla) >= (ssize_t)sz); +} + +static bool +nla_parse_error(const uc_nl_attr_spec_t *spec, uc_vm_t *vm, uc_value_t *v, const char *msg) +{ + char *s; + + s = ucv_to_string(vm, v); + + set_error(NLE_INVAL, "%s `%s` has invalid value `%s`: %s", + spec->attr ? "attribute" : "field", + spec->key, + s, + msg); + + free(s); + + return false; +} + +static void +uc_nl_put_struct_member(char *base, const void *offset, size_t datalen, void *data) +{ + memcpy(base + (uintptr_t)offset, data, datalen); +} + +static void +uc_nl_put_struct_member_u8(char *base, const void *offset, uint8_t u8) +{ + base[(uintptr_t)offset] = u8; +} + +static void +uc_nl_put_struct_member_u32(char *base, const void *offset, uint32_t u32) +{ + uc_nl_put_struct_member(base, offset, sizeof(u32), &u32); +} + +static void * +uc_nl_get_struct_member(char *base, const void *offset, size_t datalen, void *data) +{ + memcpy(data, base + (uintptr_t)offset, datalen); + + return data; +} + +static uint8_t +uc_nl_get_struct_member_u8(char *base, const void *offset) +{ + return (uint8_t)base[(uintptr_t)offset]; +} + +static uint32_t +uc_nl_get_struct_member_u32(char *base, const void *offset) +{ + uint32_t u32; + + uc_nl_get_struct_member(base, offset, sizeof(u32), &u32); + + return u32; +} + +static void +uc_nl_nla_parse(struct nlattr *tb[], int maxtype, struct nlattr *head, int len) +{ + struct nlattr *nla; + int rem; + + memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1)); + + nla_for_each_attr(nla, head, len, rem) { + int type = nla_type(nla); + + if (type <= maxtype) + tb[type] = nla; + } + + if (rem > 0) + fprintf(stderr, "netlink: %d bytes leftover after parsing attributes.\n", rem); +} + + +static bool +uc_nl_parse_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val, size_t idx); + +static uc_value_t * +uc_nl_convert_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, struct nlattr **tb, uc_vm_t *vm); + +static bool +uc_nl_convert_attrs(struct nl_msg *msg, void *buf, size_t buflen, size_t headsize, const uc_nl_attr_spec_t *attrs, size_t nattrs, uc_vm_t *vm, uc_value_t *obj) +{ + struct nlattr **tb, *nla, *nla_nest; + size_t i, type, maxattr = 0; + uc_value_t *v, *arr; + int rem; + + for (i = 0; i < nattrs; i++) + if (attrs[i].attr > maxattr) + maxattr = attrs[i].attr; + + tb = calloc(maxattr + 1, sizeof(struct nlattr *)); + + if (!tb) + return false; + + uc_nl_nla_parse(tb, maxattr, buf + headsize, buflen - headsize); + + nla_for_each_attr(nla, buf + headsize, buflen - headsize, rem) { + type = nla_type(nla); + + if (type <= maxattr) + tb[type] = nla; + } + + for (i = 0; i < nattrs; i++) { + if (attrs[i].attr != 0 && !tb[attrs[i].attr]) + continue; + + if (attrs[i].flags & DF_MULTIPLE) { + arr = ucv_array_new(vm); + nla_nest = tb[attrs[i].attr]; + + nla_for_each_attr(nla, nla_data(nla_nest), nla_len(nla_nest), rem) { + if (!(attrs[i].flags & (DF_AUTOIDX|DF_TYPEIDX)) && + attrs[i].auxdata && nla_type(nla) != (intptr_t)attrs[i].auxdata) + continue; + + tb[attrs[i].attr] = nla; + + v = uc_nl_convert_attr(&attrs[i], msg, (char *)buf, tb, vm); + + if (!v) + continue; + + if (attrs[i].flags & DF_TYPEIDX) + ucv_array_set(arr, nla_type(nla) - !!(attrs[i].flags & DF_OFFSET1), v); + else + ucv_array_push(arr, v); + } + + if (!ucv_array_length(arr)) { + ucv_put(arr); + + continue; + } + + v = arr; + } + else { + v = uc_nl_convert_attr(&attrs[i], msg, (char *)buf, tb, vm); + + if (!v) + continue; + } + + ucv_object_add(obj, attrs[i].key, v); + } + + free(tb); + + return true; +} + +static bool +uc_nl_parse_attrs(struct nl_msg *msg, char *base, const uc_nl_attr_spec_t *attrs, size_t nattrs, uc_vm_t *vm, uc_value_t *obj) +{ + struct nlattr *nla_nest = NULL; + uc_value_t *v, *item; + size_t i, j, idx; + bool exists; + + for (i = 0; i < nattrs; i++) { + v = ucv_object_get(obj, attrs[i].key, &exists); + + if (!exists) + continue; + + if (attrs[i].flags & DF_MULTIPLE) { + nla_nest = nla_nest_start(msg, attrs[i].attr); + + if (ucv_type(v) == UC_ARRAY) { + for (j = 0; j < ucv_array_length(v); j++) { + item = ucv_array_get(v, j); + + if (!item && (attrs[i].flags & DF_TYPEIDX)) + continue; + + if (!attrs[i].auxdata || (attrs[i].flags & (DF_AUTOIDX|DF_TYPEIDX))) + idx = j + !!(attrs[i].flags & DF_OFFSET1); + else + idx = (uintptr_t)attrs[i].auxdata; + + if (!uc_nl_parse_attr(&attrs[i], msg, base, vm, item, idx)) + return false; + } + } + else { + if (!attrs[i].auxdata || (attrs[i].flags & (DF_AUTOIDX|DF_TYPEIDX))) + idx = !!(attrs[i].flags & DF_OFFSET1); + else + idx = (uintptr_t)attrs[i].auxdata; + + if (!uc_nl_parse_attr(&attrs[i], msg, base, vm, v, idx)) + return false; + } + + nla_nest_end(msg, nla_nest); + } + else if (!uc_nl_parse_attr(&attrs[i], msg, base, vm, v, 0)) { + return false; + } + } + + return true; +} + +static bool +uc_nl_parse_rta_nested(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + const uc_nl_nested_spec_t *nest = spec->auxdata; + struct nlattr *nested_nla; + + nested_nla = nla_reserve(msg, spec->attr, nest->headsize); + + if (!uc_nl_parse_attrs(msg, nla_data(nested_nla), nest->attrs, nest->nattrs, vm, val)) + return false; + + nla_nest_end(msg, nested_nla); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_nested(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + const uc_nl_nested_spec_t *nest = spec->auxdata; + uc_value_t *nested_obj; + bool rv; + + if (!nla_check_len(tb[spec->attr], nest->headsize)) + return NULL; + + nested_obj = ucv_object_new(vm); + + rv = uc_nl_convert_attrs(msg, + nla_data(tb[spec->attr]), nla_len(tb[spec->attr]), nest->headsize, + nest->attrs, nest->nattrs, + vm, nested_obj); + + if (!rv) { + ucv_put(nested_obj); + + return NULL; + } + + return nested_obj; +} + +static uc_value_t * +uc_nl_convert_rta_ht_mcs(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + uc_value_t *mcs_obj, *mcs_idx; + uint16_t max_rate = 0; + uint8_t *mcs; + size_t i; + + if (!nla_check_len(tb[spec->attr], 16)) + return NULL; + + mcs = nla_data(tb[spec->attr]); + mcs_obj = ucv_object_new(vm); + + max_rate = (mcs[10] | ((mcs[11] & 0x3) << 8)); + + if (max_rate) + ucv_object_add(mcs_obj, "rx_highest_data_rate", ucv_uint64_new(max_rate)); + + mcs_idx = ucv_array_new(vm); + + for (i = 0; i <= 76; i++) + if (mcs[i / 8] & (1 << (i % 8))) + ucv_array_push(mcs_idx, ucv_uint64_new(i)); + + ucv_object_add(mcs_obj, "rx_mcs_indexes", mcs_idx); + + ucv_object_add(mcs_obj, "tx_mcs_set_defined", ucv_boolean_new(mcs[12] & (1 << 0))); + ucv_object_add(mcs_obj, "tx_rx_mcs_set_equal", ucv_boolean_new(!(mcs[12] & (1 << 1)))); + ucv_object_add(mcs_obj, "tx_max_spatial_streams", ucv_uint64_new(((mcs[12] >> 2) & 3) + 1)); + ucv_object_add(mcs_obj, "tx_unequal_modulation", ucv_boolean_new(mcs[12] & (1 << 4))); + + return mcs_obj; +} + +static uc_value_t * +uc_nl_convert_rta_ht_cap(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + uc_value_t *cap_obj, *mcs_obj, *rx_mask; + struct ieee80211_ht_cap *cap; + size_t i; + + if (!nla_check_len(tb[spec->attr], sizeof(*cap))) + return NULL; + + cap = nla_data(tb[spec->attr]); + cap_obj = ucv_object_new(vm); + + ucv_object_add(cap_obj, "cap_info", ucv_uint64_new(le16toh(cap->cap_info))); + ucv_object_add(cap_obj, "ampdu_params_info", ucv_uint64_new(cap->ampdu_params_info)); + ucv_object_add(cap_obj, "extended_ht_cap_info", ucv_uint64_new(le16toh(cap->extended_ht_cap_info))); + ucv_object_add(cap_obj, "tx_BF_cap_info", ucv_uint64_new(le32toh(cap->tx_BF_cap_info))); + ucv_object_add(cap_obj, "antenna_selection_info", ucv_uint64_new(cap->antenna_selection_info)); + + mcs_obj = ucv_object_new(vm); + rx_mask = ucv_array_new_length(vm, sizeof(cap->mcs.rx_mask)); + + for (i = 0; i < sizeof(cap->mcs.rx_mask); i++) + ucv_array_push(rx_mask, ucv_uint64_new(cap->mcs.rx_mask[i])); + + ucv_object_add(mcs_obj, "rx_mask", rx_mask); + ucv_object_add(mcs_obj, "rx_highest", ucv_uint64_new(le16toh(cap->mcs.rx_highest))); + ucv_object_add(mcs_obj, "tx_params", ucv_uint64_new(cap->mcs.tx_params)); + + ucv_object_add(cap_obj, "mcs", mcs_obj); + + return cap_obj; +} + +static uc_value_t * +uc_nl_convert_rta_vht_mcs(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + uc_value_t *mcs_obj, *mcs_set, *mcs_entry, *mcs_idx; + size_t i, j, max_idx; + uint16_t u16; + uint8_t *mcs; + + if (!nla_check_len(tb[spec->attr], 16)) + return NULL; + + mcs = nla_data(tb[spec->attr]); + mcs_obj = ucv_object_new(vm); + + u16 = mcs[0] | (mcs[1] << 8); + mcs_set = ucv_array_new(vm); + + for (i = 1; i <= 8; i++) { + switch ((u16 >> ((i - 1) * 2)) & 3) { + case 0: max_idx = 7; break; + case 1: max_idx = 8; break; + case 2: max_idx = 9; break; + case 3: continue; + } + + mcs_idx = ucv_array_new_length(vm, max_idx + 1); + + for (j = 0; j <= max_idx; j++) + ucv_array_push(mcs_idx, ucv_uint64_new(j)); + + mcs_entry = ucv_object_new(vm); + + ucv_object_add(mcs_entry, "streams", ucv_uint64_new(i)); + ucv_object_add(mcs_entry, "mcs_indexes", mcs_idx); + + ucv_array_push(mcs_set, mcs_entry); + } + + ucv_object_add(mcs_obj, "rx_mcs_set", mcs_set); + ucv_object_add(mcs_obj, "rx_highest_data_rate", ucv_uint64_new((mcs[2] | (mcs[3] << 8)) & 0x1fff)); + + u16 = mcs[4] | (mcs[5] << 8); + mcs_set = ucv_array_new(vm); + + for (i = 1; i <= 8; i++) { + switch ((u16 >> ((i - 1) * 2)) & 3) { + case 0: max_idx = 7; break; + case 1: max_idx = 8; break; + case 2: max_idx = 9; break; + case 3: continue; + } + + mcs_idx = ucv_array_new_length(vm, max_idx + 1); + + for (j = 0; j <= max_idx; j++) + ucv_array_push(mcs_idx, ucv_uint64_new(j)); + + mcs_entry = ucv_object_new(vm); + + ucv_object_add(mcs_entry, "streams", ucv_uint64_new(i)); + ucv_object_add(mcs_entry, "mcs_indexes", mcs_idx); + + ucv_array_push(mcs_set, mcs_entry); + } + + ucv_object_add(mcs_obj, "tx_mcs_set", mcs_set); + ucv_object_add(mcs_obj, "tx_highest_data_rate", ucv_uint64_new((mcs[6] | (mcs[7] << 8)) & 0x1fff)); + + return mcs_obj; +} + +static uc_value_t * +uc_nl_convert_rta_he_mcs(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + uint8_t bw_support_mask[] = { (1 << 1) | (1 << 2), (1 << 3), (1 << 4) }; + uc_value_t *mcs_set, *mcs_bw, *mcs_dir, *mcs_entry, *mcs_idx; + uint16_t bw[] = { 80, 160, 8080 }, mcs[16]; + uint16_t u16, phy_cap_0 = 0; + size_t i, j, k, l, max_idx; + + if (!nla_check_len(tb[spec->attr], sizeof(mcs))) + return NULL; + + if (nla_check_len(tb[NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY], sizeof(phy_cap_0))) + phy_cap_0 = nla_get_u16(tb[NL80211_BAND_IFTYPE_ATTR_HE_CAP_PHY]); + + memcpy(mcs, nla_data(tb[spec->attr]), sizeof(mcs)); + + mcs_set = ucv_array_new_length(vm, 3); + + for (i = 0; i < ARRAY_SIZE(bw); i++) { + if (!(phy_cap_0 & (bw_support_mask[i] << 8))) + continue; + + mcs_bw = ucv_object_new(vm); + + for (j = 0; j < 2; j++) { + mcs_dir = ucv_array_new_length(vm, 8); + + for (k = 0; k < 8; k++) { + u16 = mcs[(i * 2) + k]; + u16 >>= k * 2; + u16 &= 0x3; + + switch (u16) { + case 0: max_idx = 7; break; + case 1: max_idx = 8; break; + case 2: max_idx = 9; break; + case 3: continue; + } + + mcs_idx = ucv_array_new_length(vm, max_idx + 1); + + for (l = 0; l <= max_idx; l++) + ucv_array_push(mcs_idx, ucv_uint64_new(l)); + + mcs_entry = ucv_object_new(vm); + + ucv_object_add(mcs_entry, "streams", ucv_uint64_new(k + 1)); + ucv_object_add(mcs_entry, "mcs_indexes", mcs_idx); + + ucv_array_push(mcs_dir, mcs_entry); + } + + if (ucv_array_length(mcs_dir)) + ucv_object_add(mcs_bw, j ? "tx_mcs_set" : "rx_mcs_set", mcs_dir); + else + ucv_put(mcs_dir); + } + + if (ucv_object_length(mcs_bw)) { + ucv_object_add(mcs_bw, "bandwidth", ucv_uint64_new(bw[i])); + ucv_array_push(mcs_set, mcs_bw); + } + else { + ucv_put(mcs_bw); + } + } + + return mcs_set; +} + +static uc_value_t * +uc_nl_convert_rta_ie(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + uc_value_t *ie_arr, *ie_obj; + uint8_t *ie; + size_t len; + + len = nla_len(tb[spec->attr]); + ie = nla_data(tb[spec->attr]); + + if (len < 2) + return NULL; + + ie_arr = ucv_array_new(vm); + + while (len >= 2 && len - 2 >= ie[1]) { + ie_obj = ucv_object_new(vm); + + ucv_object_add(ie_obj, "type", ucv_uint64_new(ie[0])); + ucv_object_add(ie_obj, "data", ucv_string_new_length((char *)&ie[2], ie[1])); + + ucv_array_push(ie_arr, ie_obj); + + len -= ie[1] + 2; + ie += ie[1] + 2; + } + + return ie_arr; +} + + +static bool +uc_nl_parse_numval(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val, void *dst) +{ + uint64_t u64; + uint32_t u32; + uint16_t u16; + uint8_t u8; + + switch (spec->type) { + case DT_U8: + if (!uc_nl_parse_u32(val, &u32) || u32 > 255) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-255"); + + u8 = (uint8_t)u32; + + memcpy(dst, &u8, sizeof(u8)); + break; + + case DT_U16: + if (!uc_nl_parse_u32(val, &u32) || u32 > 65535) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-65535"); + + u16 = (uint16_t)u32; + + memcpy(dst, &u16, sizeof(u16)); + break; + + case DT_S32: + case DT_U32: + if (spec->type == DT_S32 && !uc_nl_parse_s32(val, &u32)) + return nla_parse_error(spec, vm, val, "not an integer or out of range -2147483648-2147483647"); + else if (spec->type == DT_U32 && !uc_nl_parse_u32(val, &u32)) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-4294967295"); + + memcpy(dst, &u32, sizeof(u32)); + break; + + case DT_U64: + if (!uc_nl_parse_u64(val, &u64)) + return nla_parse_error(spec, vm, val, "not an integer or negative"); + + memcpy(dst, &u64, sizeof(u64)); + break; + + default: + return false; + } + + return true; +} + +static const uint8_t dt_sizes[] = { + [DT_U8] = sizeof(uint8_t), + [DT_U16] = sizeof(uint16_t), + [DT_U32] = sizeof(uint32_t), + [DT_S32] = sizeof(int32_t), + [DT_U64] = sizeof(uint64_t), +}; + +static bool +uc_nl_parse_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val, size_t idx) +{ + char buf[sizeof(uint64_t)]; + struct in_addr in = { 0 }; + struct ether_addr *ea; + struct nlattr *nla; + uc_value_t *item; + size_t attr, i; + uint32_t u32; + char *s; + + if (spec->flags & DF_MULTIPLE) + attr = idx; + else + attr = spec->attr; + + switch (spec->type) { + case DT_U8: + case DT_U16: + case DT_U32: + case DT_S32: + case DT_U64: + if (spec->flags & DF_ARRAY) { + assert(spec->attr != 0); + + if (ucv_type(val) != UC_ARRAY) + return nla_parse_error(spec, vm, val, "not an array"); + + nla = nla_reserve(msg, spec->attr, ucv_array_length(val) * dt_sizes[spec->type]); + s = nla_data(nla); + + for (i = 0; i < ucv_array_length(val); i++) { + item = ucv_array_get(val, i); + + if (!uc_nl_parse_numval(spec, msg, base, vm, item, buf)) + return false; + + memcpy(s, buf, dt_sizes[spec->type]); + + s += dt_sizes[spec->type]; + } + } + else { + if (!uc_nl_parse_numval(spec, msg, base, vm, val, buf)) + return false; + + if (spec->attr == 0) + uc_nl_put_struct_member(base, spec->auxdata, dt_sizes[spec->type], buf); + else + nla_put(msg, attr, dt_sizes[spec->type], buf); + } + + break; + + case DT_BOOL: + u32 = (uint32_t)ucv_is_truish(val); + + if (spec->attr == 0) + uc_nl_put_struct_member_u8(base, spec->auxdata, u32); + else + nla_put_u8(msg, attr, u32); + + break; + + case DT_FLAG: + u32 = (uint32_t)ucv_is_truish(val); + + if (spec->attr == 0) + uc_nl_put_struct_member_u8(base, spec->auxdata, u32); + else if (u32 == 1) + nla_put_flag(msg, attr); + + break; + + case DT_STRING: + assert(spec->attr != 0); + + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + nla_put_string(msg, attr, s); + free(s); + + break; + + case DT_NETDEV: + if (ucv_type(val) == UC_INTEGER) { + if (ucv_int64_get(val) < 0 || + ucv_int64_get(val) > UINT32_MAX) + return nla_parse_error(spec, vm, val, "interface index out of range 0-4294967295"); + + u32 = (uint32_t)ucv_int64_get(val); + } + else { + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + u32 = if_nametoindex(s); + + free(s); + } + + if (spec->attr == 0) + uc_nl_put_struct_member_u32(base, spec->auxdata, u32); + else + nla_put_u32(msg, attr, u32); + + break; + + case DT_LLADDR: + assert(spec->attr != 0); + + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + ea = ether_aton(s); + + free(s); + + if (!ea) + return nla_parse_error(spec, vm, val, "invalid MAC address"); + + nla_put(msg, attr, sizeof(*ea), ea); + + break; + + case DT_INADDR: + assert(spec->attr != 0); + + if (!uc_nl_parse_ipaddr(vm, val, &in)) + return nla_parse_error(spec, vm, val, "invalid IP address"); + + nla_put(msg, attr, sizeof(in), &in); + + break; + + case DT_NESTED: + if (!uc_nl_parse_rta_nested(spec, msg, base, vm, val)) + return false; + + break; + + default: + assert(0); + } + + return true; +} + +static uc_value_t * +uc_nl_convert_numval(const uc_nl_attr_spec_t *spec, char *base) +{ + union { uint8_t *u8; uint16_t *u16; uint32_t *u32; uint64_t *u64; char *base; } t = { .base = base }; + + switch (spec->type) { + case DT_U8: + return ucv_uint64_new(t.u8[0]); + + case DT_U16: + return ucv_uint64_new(t.u16[0]); + + case DT_U32: + return ucv_uint64_new(t.u32[0]); + + case DT_S32: + return ucv_int64_new((int32_t)t.u32[0]); + + case DT_U64: + return ucv_uint64_new(t.u64[0]); + + default: + return NULL; + } +} + +static uc_value_t * +uc_nl_convert_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, struct nlattr **tb, uc_vm_t *vm) +{ + union { uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; size_t sz; } t = { 0 }; + char buf[sizeof("FF:FF:FF:FF:FF:FF")]; + struct ether_addr *ea; + uc_value_t *v; + int i; + + switch (spec->type) { + case DT_U8: + case DT_U16: + case DT_U32: + case DT_S32: + case DT_U64: + if (spec->flags & DF_ARRAY) { + assert(spec->attr != 0); + assert((nla_len(tb[spec->attr]) % dt_sizes[spec->type]) == 0); + + v = ucv_array_new_length(vm, nla_len(tb[spec->attr]) / dt_sizes[spec->type]); + + for (i = 0; i < nla_len(tb[spec->attr]); i += dt_sizes[spec->type]) + ucv_array_push(v, uc_nl_convert_numval(spec, nla_data(tb[spec->attr]) + i)); + + return v; + } + else if (nla_check_len(tb[spec->attr], dt_sizes[spec->type])) { + return uc_nl_convert_numval(spec, nla_data(tb[spec->attr])); + } + + return NULL; + + case DT_BOOL: + if (spec->attr == 0) + t.u8 = uc_nl_get_struct_member_u8(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u8))) + t.u8 = nla_get_u8(tb[spec->attr]); + + return ucv_boolean_new(t.u8 != 0); + + case DT_FLAG: + if (spec->attr == 0) + t.u8 = uc_nl_get_struct_member_u8(base, spec->auxdata); + else if (tb[spec->attr] != NULL) + t.u8 = 1; + + return ucv_boolean_new(t.u8 != 0); + + case DT_STRING: + assert(spec->attr != 0); + + if (!nla_check_len(tb[spec->attr], 1)) + return NULL; + + t.sz = nla_len(tb[spec->attr]); + + if (!(spec->flags & DF_BINARY)) + t.sz -= 1; + + return ucv_string_new_length(nla_data(tb[spec->attr]), t.sz); + + case DT_NETDEV: + if (spec->attr == 0) + t.u32 = uc_nl_get_struct_member_u32(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u32))) + t.u32 = nla_get_u32(tb[spec->attr]); + + if (if_indextoname(t.u32, buf)) + return ucv_string_new(buf); + + return NULL; + + case DT_LLADDR: + assert(spec->attr != 0); + + if (!nla_check_len(tb[spec->attr], sizeof(*ea))) + return NULL; + + ea = nla_data(tb[spec->attr]); + + snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", + ea->ether_addr_octet[0], ea->ether_addr_octet[1], + ea->ether_addr_octet[2], ea->ether_addr_octet[3], + ea->ether_addr_octet[4], ea->ether_addr_octet[5]); + + return ucv_string_new(buf); + + case DT_INADDR: + assert(spec->attr != 0); + + if (!nla_check_len(tb[spec->attr], sizeof(struct in_addr)) || + !inet_ntop(AF_INET, nla_data(tb[spec->attr]), buf, sizeof(buf))) + return NULL; + + return ucv_string_new(buf); + + case DT_NESTED: + return uc_nl_convert_rta_nested(spec, msg, tb, vm); + + case DT_HT_MCS: + return uc_nl_convert_rta_ht_mcs(spec, msg, tb, vm); + + case DT_HT_CAP: + return uc_nl_convert_rta_ht_cap(spec, msg, tb, vm); + + case DT_VHT_MCS: + return uc_nl_convert_rta_vht_mcs(spec, msg, tb, vm); + + case DT_HE_MCS: + return uc_nl_convert_rta_he_mcs(spec, msg, tb, vm); + + case DT_IE: + return uc_nl_convert_rta_ie(spec, msg, tb, vm); + + default: + assert(0); + } + + return NULL; +} + + +static struct { + struct nl_sock *sock; + struct nl_sock *evsock; + struct nl_cache *cache; + struct genl_family *nl80211; + struct genl_family *nlctrl; +} nl80211_conn; + +typedef enum { + STATE_UNREPLIED, + STATE_CONTINUE, + STATE_REPLIED, + STATE_ERROR +} reply_state_t; + +typedef struct { + reply_state_t state; + uc_vm_t *vm; + uc_value_t *res; + bool merge; +} request_state_t; + + +static uc_value_t * +uc_nl_error(uc_vm_t *vm, size_t nargs) +{ + uc_stringbuf_t *buf; + const char *s; + + if (last_error.code == 0) + return NULL; + + buf = ucv_stringbuf_new(); + + if (last_error.code == NLE_FAILURE && last_error.msg) { + ucv_stringbuf_addstr(buf, last_error.msg, strlen(last_error.msg)); + } + else { + s = nl_geterror(last_error.code); + + ucv_stringbuf_addstr(buf, s, strlen(s)); + + if (last_error.msg) + ucv_stringbuf_printf(buf, ": %s", last_error.msg); + } + + set_error(0, NULL); + + return ucv_stringbuf_finish(buf); +} + +static int +cb_done(struct nl_msg *msg, void *arg) +{ + request_state_t *s = arg; + + s->state = STATE_REPLIED; + + return NL_STOP; +} + +static void +deep_merge_array(uc_value_t *dest, uc_value_t *src); + +static void +deep_merge_object(uc_value_t *dest, uc_value_t *src); + +static void +deep_merge_array(uc_value_t *dest, uc_value_t *src) +{ + uc_value_t *e, *v; + size_t i; + + if (ucv_type(dest) == UC_ARRAY && ucv_type(src) == UC_ARRAY) { + for (i = 0; i < ucv_array_length(src); i++) { + e = ucv_array_get(dest, i); + v = ucv_array_get(src, i); + + if (!e) + ucv_array_set(dest, i, ucv_get(v)); + else if (ucv_type(v) == UC_ARRAY) + deep_merge_array(e, v); + else if (ucv_type(v) == UC_OBJECT) + deep_merge_object(e, v); + } + } +} + +static void +deep_merge_object(uc_value_t *dest, uc_value_t *src) +{ + uc_value_t *e; + bool exists; + + if (ucv_type(dest) == UC_OBJECT && ucv_type(src) == UC_OBJECT) { + ucv_object_foreach(src, k, v) { + e = ucv_object_get(dest, k, &exists); + + if (!exists) + ucv_object_add(dest, k, ucv_get(v)); + else if (ucv_type(v) == UC_ARRAY) + deep_merge_array(e, v); + else if (ucv_type(v) == UC_OBJECT) + deep_merge_object(e, v); + } + } +} + +static int +cb_reply(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *hdr = nlmsg_hdr(msg); + struct genlmsghdr *gnlh = nlmsg_data(hdr); + request_state_t *s = arg; + uc_value_t *o; + bool rv; + + o = ucv_object_new(s->vm); + + rv = uc_nl_convert_attrs(msg, + genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), + 0, nl80211_msg.attrs, nl80211_msg.nattrs, s->vm, o); + + if (rv) { + if (hdr->nlmsg_flags & NLM_F_MULTI) { + if (!s->res) { + if (s->merge) { + s->res = o; + } + else { + s->res = ucv_array_new(s->vm); + ucv_array_push(s->res, o); + } + } + else { + if (s->merge) { + deep_merge_object(s->res, o); + ucv_put(o); + } + else { + ucv_array_push(s->res, o); + } + } + } + else { + s->res = o; + } + } + else { + ucv_put(o); + } + + if (hdr->nlmsg_flags & NLM_F_MULTI) + s->state = STATE_CONTINUE; + else + s->state = STATE_REPLIED; + + return NL_SKIP; +} + +static bool +uc_nl_connect_sock(struct nl_sock **sk, bool nonblocking) +{ + int err, fd; + + if (*sk) + return true; + + *sk = nl_socket_alloc(); + + if (!*sk) { + set_error(NLE_NOMEM, NULL); + goto err; + } + + err = genl_connect(*sk); + + if (err != 0) { + set_error(err, NULL); + goto err; + } + + fd = nl_socket_get_fd(*sk); + + if (fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC) < 0) { + set_error(NLE_FAILURE, "unable to set FD_CLOEXEC flag on socket: %s", strerror(errno)); + goto err; + } + + if (nonblocking) { + err = nl_socket_set_nonblocking(*sk); + + if (err != 0) { + set_error(err, NULL); + goto err; + } + } + + return true; + +err: + if (*sk) { + nl_socket_free(*sk); + *sk = NULL; + } + + return false; +} + +static int +uc_nl_find_family_id(const char *name) +{ + struct genl_family *fam; + + if (!nl80211_conn.cache && genl_ctrl_alloc_cache(nl80211_conn.sock, &nl80211_conn.cache)) + return -NLE_NOMEM; + + fam = genl_ctrl_search_by_name(nl80211_conn.cache, name); + + if (!fam) + return -NLE_OBJ_NOTFOUND; + + return genl_family_get_id(fam); +} + +static int +cb_errno(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *ret = arg; + + *ret = err->error; + + return NL_STOP; +} + +static int +cb_ack(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + + *ret = 0; + + return NL_STOP; +} + +static int +cb_subscribe(struct nl_msg *msg, void *arg) +{ + struct nlattr *nla, *tb[CTRL_ATTR_MAX + 1], *grp[CTRL_ATTR_MCAST_GRP_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct { int id; const char *group; } *ret = arg; + int rem; + + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return NL_SKIP; + + nla_for_each_nested(nla, tb[CTRL_ATTR_MCAST_GROUPS], rem) { + nla_parse(grp, CTRL_ATTR_MCAST_GRP_MAX, nla_data(nla), nla_len(nla), NULL); + + if (!grp[CTRL_ATTR_MCAST_GRP_NAME] || !grp[CTRL_ATTR_MCAST_GRP_ID]) + continue; + + if (strncmp(nla_data(grp[CTRL_ATTR_MCAST_GRP_NAME]), + ret->group, nla_len(grp[CTRL_ATTR_MCAST_GRP_NAME]))) + continue; + + ret->id = nla_get_u32(grp[CTRL_ATTR_MCAST_GRP_ID]); + + break; + } + + return NL_SKIP; +} + +static bool +uc_nl_subscribe(struct nl_sock *sk, const char *family, const char *group) +{ + struct { int id; const char *group; } grp = { -NLE_OBJ_NOTFOUND, group }; + struct nl_msg *msg; + struct nl_cb *cb; + int id, ret; + + if (!uc_nl_connect_sock(&nl80211_conn.sock, false)) + return NULL; + + msg = nlmsg_alloc(); + + if (!msg) + err_return(NLE_NOMEM, NULL); + + id = uc_nl_find_family_id("nlctrl"); + + if (id < 0) + err_return(-id, NULL); + + genlmsg_put(msg, 0, 0, id, 0, 0, CTRL_CMD_GETFAMILY, 0); + nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family); + + cb = nl_cb_alloc(NL_CB_DEFAULT); + + if (!cb) { + nlmsg_free(msg); + err_return(NLE_NOMEM, NULL); + } + + nl_send_auto_complete(nl80211_conn.sock, msg); + + ret = 1; + + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_ack, &ret); + nl_cb_err(cb, NL_CB_CUSTOM, cb_errno, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_subscribe, &grp); + + while (ret > 0) + nl_recvmsgs(nl80211_conn.sock, cb); + + nlmsg_free(msg); + nl_cb_put(cb); + + if (ret < 0) + err_return(ret, NULL); + + if (grp.id < 0) + err_return(grp.id, NULL); + + ret = nl_socket_add_membership(sk, grp.id); + + if (ret != 0) + err_return(ret, NULL); + + return true; +} + + +struct waitfor_ctx { + uint8_t cmd; + uc_vm_t *vm; + uc_value_t *res; + uint32_t cmds[8]; +}; + +static int +cb_event(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *hdr = nlmsg_hdr(msg); + struct genlmsghdr *gnlh = nlmsg_data(hdr); + struct waitfor_ctx *s = arg; + bool rv, match = true; + uc_value_t *o; + + if (s->cmds[0] || s->cmds[1] || s->cmds[2] || s->cmds[3] || + s->cmds[4] || s->cmds[5] || s->cmds[6] || s->cmds[7]) { + match = (s->cmds[gnlh->cmd / 32] & (1 << (gnlh->cmd % 32))); + } + + if (match) { + o = ucv_object_new(s->vm); + + rv = uc_nl_convert_attrs(msg, + genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), + 0, nl80211_msg.attrs, nl80211_msg.nattrs, s->vm, o); + + if (rv) + s->res = o; + else + ucv_put(o); + + s->cmd = gnlh->cmd; + } + + return NL_SKIP; +} + +static int +cb_seq(struct nl_msg *msg, void *arg) +{ + return NL_OK; +} + +static uc_value_t * +uc_nl_waitfor(uc_vm_t *vm, size_t nargs) +{ + struct pollfd pfd = { .events = POLLIN }; + uc_value_t *cmds = uc_fn_arg(0); + uc_value_t *timeout = uc_fn_arg(1); + uc_value_t *rv = NULL; + struct waitfor_ctx ctx = { .vm = vm }; + struct nl_cb *cb; + int ms = -1, err; + int64_t n; + size_t i; + + if (timeout) { + n = ucv_int64_get(timeout); + + if (ucv_type(timeout) != UC_INTEGER || n < INT32_MIN || n > INT32_MAX) + err_return(NLE_INVAL, "Invalid timeout specified"); + + ms = (int)n; + } + + if (ucv_type(cmds) == UC_ARRAY) { + for (i = 0; i < ucv_array_length(cmds); i++) { + n = ucv_int64_get(ucv_array_get(cmds, i)); + + if (n < 0 || n > 255) + err_return(NLE_INVAL, "Invalid command ID specified"); + + ctx.cmds[n / 32] |= (1 << (n % 32)); + } + } + else if (ucv_type(cmds) == UC_INTEGER) { + n = ucv_int64_get(cmds); + + if (n < 0 || n > 255) + err_return(NLE_INVAL, "Invalid command ID specified"); + + ctx.cmds[n / 32] |= (1 << (n % 32)); + } + + if (!nl80211_conn.evsock) { + if (!uc_nl_connect_sock(&nl80211_conn.evsock, true) || + !uc_nl_subscribe(nl80211_conn.evsock, "nl80211", "config") || + !uc_nl_subscribe(nl80211_conn.evsock, "nl80211", "scan") || + !uc_nl_subscribe(nl80211_conn.evsock, "nl80211", "regulatory") || + !uc_nl_subscribe(nl80211_conn.evsock, "nl80211", "mlme") || + !uc_nl_subscribe(nl80211_conn.evsock, "nl80211", "vendor") || + !uc_nl_subscribe(nl80211_conn.evsock, "nl80211", "nan")) + return NULL; + } + + cb = nl_cb_alloc(NL_CB_DEFAULT); + + if (!cb) + err_return(NLE_NOMEM, NULL); + + err = 0; + + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, cb_seq, NULL); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_event, &ctx); + nl_cb_err(cb, NL_CB_CUSTOM, cb_errno, &err); + + pfd.fd = nl_socket_get_fd(nl80211_conn.evsock); + + if (poll(&pfd, 1, ms) == 1) { + while (err == 0 && ctx.cmd == 0) + nl_recvmsgs(nl80211_conn.evsock, cb); + } + + nl_cb_put(cb); + + if (ctx.cmd) { + rv = ucv_object_new(vm); + + ucv_object_add(rv, "cmd", ucv_int64_new(ctx.cmd)); + ucv_object_add(rv, "msg", ctx.res); + + return rv; + } + else if (err) { + err_return(err, NULL); + } + else { + err_return(NLE_FAILURE, "No event received"); + } +} + +static uc_value_t * +uc_nl_request(uc_vm_t *vm, size_t nargs) +{ + request_state_t st = { .vm = vm, .state = STATE_CONTINUE }; + uc_value_t *cmd = uc_fn_arg(0); + uc_value_t *flags = uc_fn_arg(1); + uc_value_t *payload = uc_fn_arg(2); + uint16_t flagval = 0; + struct nl_msg *msg; + struct nl_cb *cb; + int ret, id; + + if (ucv_type(cmd) != UC_INTEGER || ucv_int64_get(cmd) < 0 || + (flags != NULL && ucv_type(flags) != UC_INTEGER) || + (payload != NULL && ucv_type(payload) != UC_OBJECT)) + err_return(NLE_INVAL, NULL); + + if (flags) { + if (ucv_int64_get(flags) < 0 || ucv_int64_get(flags) > 0xffff) + err_return(NLE_INVAL, NULL); + else + flagval = (uint16_t)ucv_int64_get(flags); + } + + if (!uc_nl_connect_sock(&nl80211_conn.sock, false)) + return NULL; + + msg = nlmsg_alloc(); + + if (!msg) + err_return(NLE_NOMEM, NULL); + + id = uc_nl_find_family_id("nl80211"); + + if (id < 0) + err_return(-id, NULL); + + genlmsg_put(msg, 0, 0, id, 0, flagval, ucv_int64_get(cmd), 0); + + if (!uc_nl_parse_attrs(msg, nlmsg_data(nlmsg_hdr(msg)), nl80211_msg.attrs, nl80211_msg.nattrs, vm, payload)) { + nlmsg_free(msg); + + return NULL; + } + + switch (ucv_int64_get(cmd)) { + case NL80211_CMD_GET_WIPHY: + st.merge = true; + break; + } + + cb = nl_cb_alloc(NL_CB_DEFAULT); + + if (!cb) { + nlmsg_free(msg); + err_return(NLE_NOMEM, NULL); + } + + ret = 1; + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_reply, &st); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_done, &st); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_ack, &ret); + nl_cb_err(cb, NL_CB_CUSTOM, cb_errno, &ret); + + nl_send_auto_complete(nl80211_conn.sock, msg); + + while (ret > 0 && st.state == STATE_CONTINUE) + nl_recvmsgs(nl80211_conn.sock, cb); + + nlmsg_free(msg); + nl_cb_put(cb); + + if (ret < 0) + err_return(nl_syserr2nlerr(ret), NULL); + + switch (st.state) { + case STATE_REPLIED: + return st.res; + + case STATE_UNREPLIED: + return ucv_boolean_new(true); + + default: + set_error(NLE_FAILURE, "Interrupted reply"); + + return ucv_boolean_new(false); + } +} + + +static void +register_constants(uc_vm_t *vm, uc_value_t *scope) +{ + uc_value_t *c = ucv_object_new(vm); + +#define ADD_CONST(x) ucv_object_add(c, #x, ucv_int64_new(x)) + + ADD_CONST(NLM_F_ACK); + ADD_CONST(NLM_F_ACK_TLVS); + ADD_CONST(NLM_F_APPEND); + ADD_CONST(NLM_F_ATOMIC); + ADD_CONST(NLM_F_CAPPED); + ADD_CONST(NLM_F_CREATE); + ADD_CONST(NLM_F_DUMP); + ADD_CONST(NLM_F_DUMP_FILTERED); + ADD_CONST(NLM_F_DUMP_INTR); + ADD_CONST(NLM_F_ECHO); + ADD_CONST(NLM_F_EXCL); + ADD_CONST(NLM_F_MATCH); + ADD_CONST(NLM_F_MULTI); + ADD_CONST(NLM_F_NONREC); + ADD_CONST(NLM_F_REPLACE); + ADD_CONST(NLM_F_REQUEST); + ADD_CONST(NLM_F_ROOT); + + ADD_CONST(NL80211_CMD_GET_WIPHY); + ADD_CONST(NL80211_CMD_SET_WIPHY); + ADD_CONST(NL80211_CMD_NEW_WIPHY); + ADD_CONST(NL80211_CMD_DEL_WIPHY); + ADD_CONST(NL80211_CMD_GET_INTERFACE); + ADD_CONST(NL80211_CMD_SET_INTERFACE); + ADD_CONST(NL80211_CMD_NEW_INTERFACE); + ADD_CONST(NL80211_CMD_DEL_INTERFACE); + ADD_CONST(NL80211_CMD_GET_KEY); + ADD_CONST(NL80211_CMD_SET_KEY); + ADD_CONST(NL80211_CMD_NEW_KEY); + ADD_CONST(NL80211_CMD_DEL_KEY); + ADD_CONST(NL80211_CMD_GET_BEACON); + ADD_CONST(NL80211_CMD_SET_BEACON); + ADD_CONST(NL80211_CMD_START_AP); + ADD_CONST(NL80211_CMD_NEW_BEACON); + ADD_CONST(NL80211_CMD_STOP_AP); + ADD_CONST(NL80211_CMD_DEL_BEACON); + ADD_CONST(NL80211_CMD_GET_STATION); + ADD_CONST(NL80211_CMD_SET_STATION); + ADD_CONST(NL80211_CMD_NEW_STATION); + ADD_CONST(NL80211_CMD_DEL_STATION); + ADD_CONST(NL80211_CMD_GET_MPATH); + ADD_CONST(NL80211_CMD_SET_MPATH); + ADD_CONST(NL80211_CMD_NEW_MPATH); + ADD_CONST(NL80211_CMD_DEL_MPATH); + ADD_CONST(NL80211_CMD_SET_BSS); + ADD_CONST(NL80211_CMD_SET_REG); + ADD_CONST(NL80211_CMD_REQ_SET_REG); + ADD_CONST(NL80211_CMD_GET_MESH_CONFIG); + ADD_CONST(NL80211_CMD_SET_MESH_CONFIG); + ADD_CONST(NL80211_CMD_GET_REG); + ADD_CONST(NL80211_CMD_GET_SCAN); + ADD_CONST(NL80211_CMD_TRIGGER_SCAN); + ADD_CONST(NL80211_CMD_NEW_SCAN_RESULTS); + ADD_CONST(NL80211_CMD_SCAN_ABORTED); + ADD_CONST(NL80211_CMD_REG_CHANGE); + ADD_CONST(NL80211_CMD_AUTHENTICATE); + ADD_CONST(NL80211_CMD_ASSOCIATE); + ADD_CONST(NL80211_CMD_DEAUTHENTICATE); + ADD_CONST(NL80211_CMD_DISASSOCIATE); + ADD_CONST(NL80211_CMD_MICHAEL_MIC_FAILURE); + ADD_CONST(NL80211_CMD_REG_BEACON_HINT); + ADD_CONST(NL80211_CMD_JOIN_IBSS); + ADD_CONST(NL80211_CMD_LEAVE_IBSS); + ADD_CONST(NL80211_CMD_TESTMODE); + ADD_CONST(NL80211_CMD_CONNECT); + ADD_CONST(NL80211_CMD_ROAM); + ADD_CONST(NL80211_CMD_DISCONNECT); + ADD_CONST(NL80211_CMD_SET_WIPHY_NETNS); + ADD_CONST(NL80211_CMD_GET_SURVEY); + ADD_CONST(NL80211_CMD_NEW_SURVEY_RESULTS); + ADD_CONST(NL80211_CMD_SET_PMKSA); + ADD_CONST(NL80211_CMD_DEL_PMKSA); + ADD_CONST(NL80211_CMD_FLUSH_PMKSA); + ADD_CONST(NL80211_CMD_REMAIN_ON_CHANNEL); + ADD_CONST(NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL); + ADD_CONST(NL80211_CMD_SET_TX_BITRATE_MASK); + ADD_CONST(NL80211_CMD_REGISTER_FRAME); + ADD_CONST(NL80211_CMD_REGISTER_ACTION); + ADD_CONST(NL80211_CMD_FRAME); + ADD_CONST(NL80211_CMD_ACTION); + ADD_CONST(NL80211_CMD_FRAME_TX_STATUS); + ADD_CONST(NL80211_CMD_ACTION_TX_STATUS); + ADD_CONST(NL80211_CMD_SET_POWER_SAVE); + ADD_CONST(NL80211_CMD_GET_POWER_SAVE); + ADD_CONST(NL80211_CMD_SET_CQM); + ADD_CONST(NL80211_CMD_NOTIFY_CQM); + ADD_CONST(NL80211_CMD_SET_CHANNEL); + ADD_CONST(NL80211_CMD_SET_WDS_PEER); + ADD_CONST(NL80211_CMD_FRAME_WAIT_CANCEL); + ADD_CONST(NL80211_CMD_JOIN_MESH); + ADD_CONST(NL80211_CMD_LEAVE_MESH); + ADD_CONST(NL80211_CMD_UNPROT_DEAUTHENTICATE); + ADD_CONST(NL80211_CMD_UNPROT_DISASSOCIATE); + ADD_CONST(NL80211_CMD_NEW_PEER_CANDIDATE); + ADD_CONST(NL80211_CMD_GET_WOWLAN); + ADD_CONST(NL80211_CMD_SET_WOWLAN); + ADD_CONST(NL80211_CMD_START_SCHED_SCAN); + ADD_CONST(NL80211_CMD_STOP_SCHED_SCAN); + ADD_CONST(NL80211_CMD_SCHED_SCAN_RESULTS); + ADD_CONST(NL80211_CMD_SCHED_SCAN_STOPPED); + ADD_CONST(NL80211_CMD_SET_REKEY_OFFLOAD); + ADD_CONST(NL80211_CMD_PMKSA_CANDIDATE); + ADD_CONST(NL80211_CMD_TDLS_OPER); + ADD_CONST(NL80211_CMD_TDLS_MGMT); + ADD_CONST(NL80211_CMD_UNEXPECTED_FRAME); + ADD_CONST(NL80211_CMD_PROBE_CLIENT); + ADD_CONST(NL80211_CMD_REGISTER_BEACONS); + ADD_CONST(NL80211_CMD_UNEXPECTED_4ADDR_FRAME); + ADD_CONST(NL80211_CMD_SET_NOACK_MAP); + ADD_CONST(NL80211_CMD_CH_SWITCH_NOTIFY); + ADD_CONST(NL80211_CMD_START_P2P_DEVICE); + ADD_CONST(NL80211_CMD_STOP_P2P_DEVICE); + ADD_CONST(NL80211_CMD_CONN_FAILED); + ADD_CONST(NL80211_CMD_SET_MCAST_RATE); + ADD_CONST(NL80211_CMD_SET_MAC_ACL); + ADD_CONST(NL80211_CMD_RADAR_DETECT); + ADD_CONST(NL80211_CMD_GET_PROTOCOL_FEATURES); + ADD_CONST(NL80211_CMD_UPDATE_FT_IES); + ADD_CONST(NL80211_CMD_FT_EVENT); + ADD_CONST(NL80211_CMD_CRIT_PROTOCOL_START); + ADD_CONST(NL80211_CMD_CRIT_PROTOCOL_STOP); + ADD_CONST(NL80211_CMD_GET_COALESCE); + ADD_CONST(NL80211_CMD_SET_COALESCE); + ADD_CONST(NL80211_CMD_CHANNEL_SWITCH); + ADD_CONST(NL80211_CMD_VENDOR); + ADD_CONST(NL80211_CMD_SET_QOS_MAP); + ADD_CONST(NL80211_CMD_ADD_TX_TS); + ADD_CONST(NL80211_CMD_DEL_TX_TS); + ADD_CONST(NL80211_CMD_GET_MPP); + ADD_CONST(NL80211_CMD_JOIN_OCB); + ADD_CONST(NL80211_CMD_LEAVE_OCB); + ADD_CONST(NL80211_CMD_CH_SWITCH_STARTED_NOTIFY); + ADD_CONST(NL80211_CMD_TDLS_CHANNEL_SWITCH); + ADD_CONST(NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH); + + ucv_object_add(scope, "const", c); +}; + +static const uc_function_list_t global_fns[] = { + { "error", uc_nl_error }, + { "request", uc_nl_request }, + { "waitfor", uc_nl_waitfor }, +}; + + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, global_fns); + + register_constants(vm, scope); +} |