+static int parse_random_mac_addr(struct nl_msg *msg, char *arg)
+{
+ char *a_addr, *a_mask, *sep;
+ unsigned char addr[ETH_ALEN], mask[ETH_ALEN];
+ char *addrs = arg + 9;
+
+ if (*addrs != '=')
+ return 0;
+
+ addrs++;
+ sep = strchr(addrs, '/');
+ a_addr = addrs;
+
+ if (!sep)
+ return 1;
+
+ *sep = 0;
+ a_mask = sep + 1;
+ if (mac_addr_a2n(addr, a_addr) || mac_addr_a2n(mask, a_mask))
+ return 1;
+
+ NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, addr);
+ NLA_PUT(msg, NL80211_ATTR_MAC_MASK, ETH_ALEN, mask);
+
+ return 0;
+ nla_put_failure:
+ return -ENOBUFS;
+}
+
+int parse_sched_scan(struct nl_msg *msg, int *argc, char ***argv)
+{
+ struct nl_msg *matchset = NULL, *freqs = NULL, *ssids = NULL;
+ struct nl_msg *scan_plans = NULL;
+ struct nlattr *match = NULL, *plan = NULL;
+ enum {
+ ND_TOPLEVEL,
+ ND_MATCH,
+ ND_FREQS,
+ ND_ACTIVE,
+ ND_PLANS,
+ } parse_state = ND_TOPLEVEL;
+ int c = *argc;
+ char *end, **v = *argv;
+ int err = 0, i = 0;
+ unsigned int freq, interval = 0, delay = 0, iterations = 0;
+ bool have_matchset = false, have_freqs = false, have_ssids = false;
+ bool have_active = false, have_passive = false, have_plans = false;
+ uint32_t flags = 0;
+
+ matchset = nlmsg_alloc();
+ if (!matchset) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ freqs = nlmsg_alloc();
+ if (!freqs) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ ssids = nlmsg_alloc();
+ if (!ssids) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ scan_plans = nlmsg_alloc();
+ if (!scan_plans) {
+ err = -ENOBUFS;
+ goto out;
+ }
+
+ while (c) {
+ switch (parse_state) {
+ case ND_TOPLEVEL:
+ if (!strcmp(v[0], "interval")) {
+ c--; v++;
+ if (c == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ if (interval || have_plans) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+ interval = strtoul(v[0], &end, 10);
+ if (*end || !interval) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+ NLA_PUT_U32(msg,
+ NL80211_ATTR_SCHED_SCAN_INTERVAL,
+ interval);
+ } else if (!strcmp(v[0], "scan_plans")) {
+ parse_state = ND_PLANS;
+ if (have_plans || interval) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ have_plans = true;
+ i = 0;
+ } else if (!strcmp(v[0], "delay")) {
+ c--; v++;
+ if (c == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ if (delay) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+ delay = strtoul(v[0], &end, 10);
+ if (*end) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+ NLA_PUT_U32(msg,
+ NL80211_ATTR_SCHED_SCAN_DELAY,
+ delay);
+ } else if (!strcmp(v[0], "matches")) {
+ parse_state = ND_MATCH;
+ if (have_matchset) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ i = 0;
+ } else if (!strcmp(v[0], "freqs")) {
+ parse_state = ND_FREQS;
+ if (have_freqs) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ have_freqs = true;
+ i = 0;
+ } else if (!strcmp(v[0], "active")) {
+ parse_state = ND_ACTIVE;
+ if (have_active || have_passive) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ have_active = true;
+ i = 0;
+ } else if (!strcmp(v[0], "passive")) {
+ if (have_active || have_passive) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ have_passive = true;
+ } else if (!strncmp(v[0], "randomise", 9) ||
+ !strncmp(v[0], "randomize", 9)) {
+ flags |= NL80211_SCAN_FLAG_RANDOM_ADDR;
+ if (c > 0) {
+ err = parse_random_mac_addr(msg, v[0]);
+ if (err)
+ goto nla_put_failure;
+ }
+ } else {
+ /* this element is not for us, so
+ * return to continue parsing.
+ */
+ goto nla_put_failure;
+ }
+ c--; v++;
+
+ break;
+ case ND_MATCH:
+ if (!strcmp(v[0], "ssid")) {
+ c--; v++;
+ if (c == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ /* TODO: for now we can only have an
+ * SSID in the match, so we can start
+ * the match nest here.
+ */
+ match = nla_nest_start(matchset, i);
+ if (!match) {
+ err = -ENOBUFS;
+ goto nla_put_failure;
+ }
+
+ NLA_PUT(matchset,
+ NL80211_SCHED_SCAN_MATCH_ATTR_SSID,
+ strlen(v[0]), v[0]);
+ nla_nest_end(matchset, match);
+ match = NULL;
+
+ have_matchset = true;
+ i++;
+ c--; v++;
+ } else {
+ /* other element that cannot be part
+ * of a match indicates the end of the
+ * match. */
+ /* need at least one match in the matchset */
+ if (i == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ parse_state = ND_TOPLEVEL;
+ }
+
+ break;
+ case ND_FREQS:
+ freq = strtoul(v[0], &end, 10);
+ if (*end) {
+ if (i == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ parse_state = ND_TOPLEVEL;
+ } else {
+ NLA_PUT_U32(freqs, i, freq);
+ i++;
+ c--; v++;
+ }
+ break;
+ case ND_ACTIVE:
+ if (!strcmp(v[0], "ssid")) {
+ c--; v++;
+ if (c == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ NLA_PUT(ssids,
+ NL80211_SCHED_SCAN_MATCH_ATTR_SSID,
+ strlen(v[0]), v[0]);
+
+ have_ssids = true;
+ i++;
+ c--; v++;
+ } else {
+ /* other element that cannot be part
+ * of a match indicates the end of the
+ * active set. */
+ /* need at least one item in the set */
+ if (i == 0) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ parse_state = ND_TOPLEVEL;
+ }
+ break;
+ case ND_PLANS:
+ iterations = 0;
+ interval = strtoul(v[0], &end, 10);
+ if (*end) {
+ char *iter;
+
+ if (*end != ':') {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+
+ iter = ++end;
+ iterations = strtoul(iter, &end, 10);
+ if (*end || !iterations) {
+ err = -EINVAL;
+ goto nla_put_failure;
+ }
+ }
+
+ plan = nla_nest_start(scan_plans, i + 1);
+ if (!plan) {
+ err = -ENOBUFS;
+ goto nla_put_failure;
+ }
+
+ NLA_PUT_U32(scan_plans,
+ NL80211_SCHED_SCAN_PLAN_INTERVAL,
+ interval);
+
+ if (iterations)
+ NLA_PUT_U32(scan_plans,
+ NL80211_SCHED_SCAN_PLAN_ITERATIONS,
+ iterations);
+ else
+ parse_state = ND_TOPLEVEL;
+
+ nla_nest_end(scan_plans, plan);
+ plan = NULL;
+ i++;
+ c--; v++;
+ break;
+ }
+ }
+
+ if (!have_ssids)
+ NLA_PUT(ssids, 1, 0, "");
+ if (!have_passive)
+ nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids);
+ if (have_freqs)
+ nla_put_nested(msg, NL80211_ATTR_SCAN_FREQUENCIES, freqs);
+ if (have_matchset)
+ nla_put_nested(msg, NL80211_ATTR_SCHED_SCAN_MATCH, matchset);
+ if (have_plans)
+ nla_put_nested(msg, NL80211_ATTR_SCHED_SCAN_PLANS, scan_plans);
+ if (flags)
+ NLA_PUT_U32(msg, NL80211_ATTR_SCAN_FLAGS, flags);
+
+nla_put_failure:
+ if (match)
+ nla_nest_end(msg, match);
+ nlmsg_free(freqs);
+ nlmsg_free(matchset);
+ nlmsg_free(scan_plans);
+
+out:
+ *argc = c;
+ *argv = v;
+ return err;
+}
+