1/*-
2 * Copyright (c) 2008 Sam Leffler, Errno Consulting
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include <sys/types.h>
27#include <sys/errno.h>
28#include <sys/param.h>
29#include <sys/mman.h>
30#include <sys/sbuf.h>
31#include <sys/stat.h>
32
33#include <stdio.h>
34#include <string.h>
35#include <ctype.h>
36#include <fcntl.h>
37#include <err.h>
38#include <unistd.h>
39
40#include <bsdxml.h>
41
42#include "lib80211_regdomain.h"
43
44#include <net80211/_ieee80211.h>
45
46#define	MAXLEVEL	20
47
48struct mystate {
49	XML_Parser		parser;
50	struct regdata		*rdp;
51	struct regdomain	*rd;		/* current domain */
52	struct netband		*netband;	/* current netband */
53	struct freqband		*freqband;	/* current freqband */
54	struct country		*country;	/* current country */
55	netband_head		*curband;	/* current netband list */
56	int			level;
57	struct sbuf		*sbuf[MAXLEVEL];
58	int			nident;
59};
60
61struct ident {
62	const void *id;
63	void *p;
64	enum { DOMAIN, COUNTRY, FREQBAND } type;
65};
66
67static void
68start_element(void *data, const char *name, const char **attr)
69{
70#define	iseq(a,b)	(strcasecmp(a,b) == 0)
71	struct mystate *mt;
72	const void *id, *ref, *mode;
73	int i;
74
75	mt = data;
76	if (++mt->level == MAXLEVEL) {
77		/* XXX force parser to abort */
78		return;
79	}
80	mt->sbuf[mt->level] = sbuf_new_auto();
81	id = ref = mode = NULL;
82	for (i = 0; attr[i] != NULL; i += 2) {
83		if (iseq(attr[i], "id")) {
84			id = attr[i+1];
85		} else if (iseq(attr[i], "ref")) {
86			ref = attr[i+1];
87		} else if (iseq(attr[i], "mode")) {
88			mode = attr[i+1];
89		} else
90			printf("%*.*s[%s = %s]\n", mt->level + 1,
91			    mt->level + 1, "", attr[i], attr[i+1]);
92	}
93	if (iseq(name, "rd") && mt->rd == NULL) {
94		if (mt->country == NULL) {
95			mt->rd = calloc(1, sizeof(struct regdomain));
96			mt->rd->name = strdup(id);
97			mt->nident++;
98			LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next);
99		} else
100			mt->country->rd = (void *)strdup(ref);
101		return;
102	}
103	if (iseq(name, "defcc") && mt->rd != NULL) {
104		mt->rd->cc = (void *)strdup(ref);
105		return;
106	}
107	if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) {
108		if (mode == NULL) {
109			warnx("no mode for netband at line %ld",
110			    XML_GetCurrentLineNumber(mt->parser));
111			return;
112		}
113		if (iseq(mode, "11b"))
114			mt->curband = &mt->rd->bands_11b;
115		else if (iseq(mode, "11g"))
116			mt->curband = &mt->rd->bands_11g;
117		else if (iseq(mode, "11a"))
118			mt->curband = &mt->rd->bands_11a;
119		else if (iseq(mode, "11ng"))
120			mt->curband = &mt->rd->bands_11ng;
121		else if (iseq(mode, "11na"))
122			mt->curband = &mt->rd->bands_11na;
123		else if (iseq(mode, "11ac"))
124			mt->curband = &mt->rd->bands_11ac;
125		else if (iseq(mode, "11acg"))
126			mt->curband = &mt->rd->bands_11acg;
127		else
128			warnx("unknown mode \"%s\" at line %ld",
129			    __DECONST(char *, mode),
130			    XML_GetCurrentLineNumber(mt->parser));
131		return;
132	}
133	if (iseq(name, "band") && mt->netband == NULL) {
134		if (mt->curband == NULL) {
135			warnx("band without enclosing netband at line %ld",
136			    XML_GetCurrentLineNumber(mt->parser));
137			return;
138		}
139		mt->netband = calloc(1, sizeof(struct netband));
140		LIST_INSERT_HEAD(mt->curband, mt->netband, next);
141		return;
142	}
143	if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) {
144		/* XXX handle inlines and merge into table? */
145		if (mt->netband->band != NULL) {
146			warnx("duplicate freqband at line %ld ignored",
147			    XML_GetCurrentLineNumber(mt->parser));
148			/* XXX complain */
149		} else
150			mt->netband->band = (void *)strdup(ref);
151		return;
152	}
153
154	if (iseq(name, "country") && mt->country == NULL) {
155		mt->country = calloc(1, sizeof(struct country));
156		mt->country->isoname = strdup(id);
157		mt->country->code = NO_COUNTRY;
158		mt->nident++;
159		LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next);
160		return;
161	}
162
163	if (iseq(name, "freqband") && mt->freqband == NULL) {
164		mt->freqband = calloc(1, sizeof(struct freqband));
165		mt->freqband->id = strdup(id);
166		mt->nident++;
167		LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next);
168		return;
169	}
170#undef iseq
171}
172
173static int
174decode_flag(struct mystate *mt, const char *p, int len)
175{
176#define	iseq(a,b)	(strcasecmp(a,b) == 0)
177	static const struct {
178		const char *name;
179		int len;
180		uint32_t value;
181	} flags[] = {
182#define	FLAG(x)	{ #x, sizeof(#x)-1, x }
183		FLAG(IEEE80211_CHAN_A),
184		FLAG(IEEE80211_CHAN_B),
185		FLAG(IEEE80211_CHAN_G),
186		FLAG(IEEE80211_CHAN_HT20),
187		FLAG(IEEE80211_CHAN_HT40),
188		FLAG(IEEE80211_CHAN_VHT20),
189		FLAG(IEEE80211_CHAN_VHT40),
190		FLAG(IEEE80211_CHAN_VHT80),
191		FLAG(IEEE80211_CHAN_VHT160),
192		/*
193		 * XXX VHT80P80? This likely should be done by
194		 * 80MHz chan logic in net80211 / ifconfig.
195		 */
196		FLAG(IEEE80211_CHAN_ST),
197		FLAG(IEEE80211_CHAN_TURBO),
198		FLAG(IEEE80211_CHAN_PASSIVE),
199		FLAG(IEEE80211_CHAN_DFS),
200		FLAG(IEEE80211_CHAN_CCK),
201		FLAG(IEEE80211_CHAN_OFDM),
202		FLAG(IEEE80211_CHAN_2GHZ),
203		FLAG(IEEE80211_CHAN_5GHZ),
204		FLAG(IEEE80211_CHAN_DYN),
205		FLAG(IEEE80211_CHAN_GFSK),
206		FLAG(IEEE80211_CHAN_GSM),
207		FLAG(IEEE80211_CHAN_STURBO),
208		FLAG(IEEE80211_CHAN_HALF),
209		FLAG(IEEE80211_CHAN_QUARTER),
210		FLAG(IEEE80211_CHAN_HT40U),
211		FLAG(IEEE80211_CHAN_HT40D),
212		FLAG(IEEE80211_CHAN_4MSXMIT),
213		FLAG(IEEE80211_CHAN_NOADHOC),
214		FLAG(IEEE80211_CHAN_NOHOSTAP),
215		FLAG(IEEE80211_CHAN_11D),
216		FLAG(IEEE80211_CHAN_FHSS),
217		FLAG(IEEE80211_CHAN_PUREG),
218		FLAG(IEEE80211_CHAN_108A),
219		FLAG(IEEE80211_CHAN_108G),
220#undef FLAG
221		{ "ECM",	3,	REQ_ECM },
222		{ "INDOOR",	6,	REQ_INDOOR },
223		{ "OUTDOOR",	7,	REQ_OUTDOOR },
224	};
225	unsigned int i;
226
227	for (i = 0; i < nitems(flags); i++)
228		if (len == flags[i].len && iseq(p, flags[i].name))
229			return flags[i].value;
230	warnx("unknown flag \"%.*s\" at line %ld ignored",
231	    len, p, XML_GetCurrentLineNumber(mt->parser));
232	return 0;
233#undef iseq
234}
235
236static void
237end_element(void *data, const char *name)
238{
239#define	iseq(a,b)	(strcasecmp(a,b) == 0)
240	struct mystate *mt;
241	int len;
242	char *p;
243
244	mt = data;
245	sbuf_finish(mt->sbuf[mt->level]);
246	p = sbuf_data(mt->sbuf[mt->level]);
247	len = sbuf_len(mt->sbuf[mt->level]);
248
249	/* <freqband>...</freqband> */
250	if (iseq(name, "freqstart") && mt->freqband != NULL) {
251		mt->freqband->freqStart = strtoul(p, NULL, 0);
252		goto done;
253	}
254	if (iseq(name, "freqend") && mt->freqband != NULL) {
255		mt->freqband->freqEnd = strtoul(p, NULL, 0);
256		goto done;
257	}
258	if (iseq(name, "chanwidth") && mt->freqband != NULL) {
259		mt->freqband->chanWidth = strtoul(p, NULL, 0);
260		goto done;
261	}
262	if (iseq(name, "chansep") && mt->freqband != NULL) {
263		mt->freqband->chanSep = strtoul(p, NULL, 0);
264		goto done;
265	}
266	if (iseq(name, "flags")) {
267		if (mt->freqband != NULL)
268			mt->freqband->flags |= decode_flag(mt, p, len);
269		else if (mt->netband != NULL)
270			mt->netband->flags |= decode_flag(mt, p, len);
271		else {
272			warnx("flags without freqband or netband at line %ld ignored",
273			    XML_GetCurrentLineNumber(mt->parser));
274		}
275		goto done;
276	}
277
278	/* <rd> ... </rd> */
279	if (iseq(name, "name") && mt->rd != NULL) {
280		mt->rd->name = strdup(p);
281		goto done;
282	}
283	if (iseq(name, "sku") && mt->rd != NULL) {
284		mt->rd->sku = strtoul(p, NULL, 0);
285		goto done;
286	}
287	if (iseq(name, "netband") && mt->rd != NULL) {
288		mt->curband = NULL;
289		goto done;
290	}
291
292	/* <band> ... </band> */
293	if (iseq(name, "freqband") && mt->netband != NULL) {
294		/* XXX handle inline freqbands */
295		goto done;
296	}
297	if (iseq(name, "maxpower") && mt->netband != NULL) {
298		mt->netband->maxPower = strtoul(p, NULL, 0);
299		goto done;
300	}
301	if (iseq(name, "maxpowerdfs") && mt->netband != NULL) {
302		mt->netband->maxPowerDFS = strtoul(p, NULL, 0);
303		goto done;
304	}
305	if (iseq(name, "maxantgain") && mt->netband != NULL) {
306		mt->netband->maxAntGain = strtoul(p, NULL, 0);
307		goto done;
308	}
309
310	/* <country>...</country> */
311	if (iseq(name, "isocc") && mt->country != NULL) {
312		mt->country->code = strtoul(p, NULL, 0);
313		goto done;
314	}
315	if (iseq(name, "name") && mt->country != NULL) {
316		mt->country->name = strdup(p);
317		goto done;
318	}
319
320	if (len != 0) {
321		warnx("unexpected XML token \"%s\" data \"%s\" at line %ld",
322		    name, p, XML_GetCurrentLineNumber(mt->parser));
323		/* XXX goto done? */
324	}
325	/* </freqband> */
326	if (iseq(name, "freqband") && mt->freqband != NULL) {
327		/* XXX must have start/end frequencies */
328		/* XXX must have channel width/sep */
329		mt->freqband = NULL;
330		goto done;
331	}
332	/* </rd> */
333	if (iseq(name, "rd") && mt->rd != NULL) {
334		mt->rd = NULL;
335		goto done;
336	}
337	/* </band> */
338	if (iseq(name, "band") && mt->netband != NULL) {
339		if (mt->netband->band == NULL) {
340			warnx("no freqbands for band at line %ld",
341			   XML_GetCurrentLineNumber(mt->parser));
342		}
343		if (mt->netband->maxPower == 0) {
344			warnx("no maxpower for band at line %ld",
345			   XML_GetCurrentLineNumber(mt->parser));
346		}
347		/* default max power w/ DFS to max power */
348		if (mt->netband->maxPowerDFS == 0)
349			mt->netband->maxPowerDFS = mt->netband->maxPower;
350		mt->netband = NULL;
351		goto done;
352	}
353	/* </netband> */
354	if (iseq(name, "netband") && mt->netband != NULL) {
355		mt->curband = NULL;
356		goto done;
357	}
358	/* </country> */
359	if (iseq(name, "country") && mt->country != NULL) {
360		/* XXX NO_COUNTRY should be in the net80211 country enum */
361		if ((int) mt->country->code == NO_COUNTRY) {
362			warnx("no ISO cc for country at line %ld",
363			   XML_GetCurrentLineNumber(mt->parser));
364		}
365		if (mt->country->name == NULL) {
366			warnx("no name for country at line %ld",
367			   XML_GetCurrentLineNumber(mt->parser));
368		}
369		if (mt->country->rd == NULL) {
370			warnx("no regdomain reference for country at line %ld",
371			   XML_GetCurrentLineNumber(mt->parser));
372		}
373		mt->country = NULL;
374		goto done;
375	}
376done:
377	sbuf_delete(mt->sbuf[mt->level]);
378	mt->sbuf[mt->level--] = NULL;
379#undef iseq
380}
381
382static void
383char_data(void *data, const XML_Char *s, int len)
384{
385	struct mystate *mt;
386	const char *b, *e;
387
388	mt = data;
389
390	b = s;
391	e = s + len-1;
392	for (; isspace(*b) && b < e; b++)
393		;
394	for (; isspace(*e) && e > b; e++)
395		;
396	if (e != b || (*b != '\0' && !isspace(*b)))
397		sbuf_bcat(mt->sbuf[mt->level], b, e-b+1);
398}
399
400static void *
401findid(struct regdata *rdp, const void *id, int type)
402{
403	struct ident *ip;
404
405	for (ip = rdp->ident; ip->id != NULL; ip++)
406		if ((int) ip->type == type && strcasecmp(ip->id, id) == 0)
407			return ip->p;
408	return NULL;
409}
410
411/*
412 * Parse an regdomain XML configuration and build the internal representation.
413 */
414int
415lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len)
416{
417	struct mystate *mt;
418	struct regdomain *dp;
419	struct country *cp;
420	struct freqband *fp;
421	struct netband *nb;
422	const void *id;
423	int i, errors;
424
425	memset(rdp, 0, sizeof(struct regdata));
426	mt = calloc(1, sizeof(struct mystate));
427	if (mt == NULL)
428		return ENOMEM;
429	/* parse the XML input */
430	mt->rdp = rdp;
431	mt->parser = XML_ParserCreate(NULL);
432	XML_SetUserData(mt->parser, mt);
433	XML_SetElementHandler(mt->parser, start_element, end_element);
434	XML_SetCharacterDataHandler(mt->parser, char_data);
435	if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) {
436		warnx("%s: %s at line %ld", __func__,
437		   XML_ErrorString(XML_GetErrorCode(mt->parser)),
438		   XML_GetCurrentLineNumber(mt->parser));
439		return -1;
440	}
441	XML_ParserFree(mt->parser);
442
443	/* setup the identifer table */
444	rdp->ident = calloc(sizeof(struct ident), mt->nident + 1);
445	if (rdp->ident == NULL)
446		return ENOMEM;
447	free(mt);
448
449	errors = 0;
450	i = 0;
451	LIST_FOREACH(dp, &rdp->domains, next) {
452		rdp->ident[i].id = dp->name;
453		rdp->ident[i].p = dp;
454		rdp->ident[i].type = DOMAIN;
455		i++;
456	}
457	LIST_FOREACH(fp, &rdp->freqbands, next) {
458		rdp->ident[i].id = fp->id;
459		rdp->ident[i].p = fp;
460		rdp->ident[i].type = FREQBAND;
461		i++;
462	}
463	LIST_FOREACH(cp, &rdp->countries, next) {
464		rdp->ident[i].id = cp->isoname;
465		rdp->ident[i].p = cp;
466		rdp->ident[i].type = COUNTRY;
467		i++;
468	}
469
470	/* patch references */
471	LIST_FOREACH(dp, &rdp->domains, next) {
472		if (dp->cc != NULL) {
473			id = dp->cc;
474			dp->cc = findid(rdp, id, COUNTRY);
475			if (dp->cc == NULL) {
476				warnx("undefined country \"%s\"",
477				    __DECONST(char *, id));
478				errors++;
479			}
480			free(__DECONST(char *, id));
481		}
482		LIST_FOREACH(nb, &dp->bands_11b, next) {
483			id = findid(rdp, nb->band, FREQBAND);
484			if (id == NULL) {
485				warnx("undefined 11b band \"%s\"",
486				    __DECONST(char *, nb->band));
487				errors++;
488			}
489			nb->band = id;
490		}
491		LIST_FOREACH(nb, &dp->bands_11g, next) {
492			id = findid(rdp, nb->band, FREQBAND);
493			if (id == NULL) {
494				warnx("undefined 11g band \"%s\"",
495				    __DECONST(char *, nb->band));
496				errors++;
497			}
498			nb->band = id;
499		}
500		LIST_FOREACH(nb, &dp->bands_11a, next) {
501			id = findid(rdp, nb->band, FREQBAND);
502			if (id == NULL) {
503				warnx("undefined 11a band \"%s\"",
504				    __DECONST(char *, nb->band));
505				errors++;
506			}
507			nb->band = id;
508		}
509		LIST_FOREACH(nb, &dp->bands_11ng, next) {
510			id = findid(rdp, nb->band, FREQBAND);
511			if (id == NULL) {
512				warnx("undefined 11ng band \"%s\"",
513				    __DECONST(char *, nb->band));
514				errors++;
515			}
516			nb->band = id;
517		}
518		LIST_FOREACH(nb, &dp->bands_11na, next) {
519			id = findid(rdp, nb->band, FREQBAND);
520			if (id == NULL) {
521				warnx("undefined 11na band \"%s\"",
522				    __DECONST(char *, nb->band));
523				errors++;
524			}
525			nb->band = id;
526		}
527		LIST_FOREACH(nb, &dp->bands_11ac, next) {
528			id = findid(rdp, nb->band, FREQBAND);
529			if (id == NULL) {
530				warnx("undefined 11ac band \"%s\"",
531				    __DECONST(char *, nb->band));
532				errors++;
533			}
534			nb->band = id;
535		}
536		LIST_FOREACH(nb, &dp->bands_11acg, next) {
537			id = findid(rdp, nb->band, FREQBAND);
538			if (id == NULL) {
539				warnx("undefined 11acg band \"%s\"",
540				    __DECONST(char *, nb->band));
541				errors++;
542			}
543			nb->band = id;
544		}
545	}
546	LIST_FOREACH(cp, &rdp->countries, next) {
547		id = cp->rd;
548		cp->rd = findid(rdp, id, DOMAIN);
549		if (cp->rd == NULL) {
550			warnx("undefined country \"%s\"",
551			    __DECONST(char *, id));
552			errors++;
553		}
554		free(__DECONST(char *, id));
555	}
556
557	return errors ? EINVAL : 0;
558}
559
560static void
561cleanup_bands(netband_head *head)
562{
563	struct netband *nb;
564
565	for (;;) {
566		nb = LIST_FIRST(head);
567		if (nb == NULL)
568			break;
569		LIST_REMOVE(nb, next);
570		free(nb);
571	}
572}
573
574/*
575 * Cleanup state/resources for a previously parsed regdomain database.
576 */
577void
578lib80211_regdomain_cleanup(struct regdata *rdp)
579{
580
581	free(rdp->ident);
582	rdp->ident = NULL;
583	for (;;) {
584		struct regdomain *dp = LIST_FIRST(&rdp->domains);
585		if (dp == NULL)
586			break;
587		LIST_REMOVE(dp, next);
588		cleanup_bands(&dp->bands_11b);
589		cleanup_bands(&dp->bands_11g);
590		cleanup_bands(&dp->bands_11a);
591		cleanup_bands(&dp->bands_11ng);
592		cleanup_bands(&dp->bands_11na);
593		cleanup_bands(&dp->bands_11ac);
594		cleanup_bands(&dp->bands_11acg);
595		if (dp->name != NULL)
596			free(__DECONST(char *, dp->name));
597	}
598	for (;;) {
599		struct country *cp = LIST_FIRST(&rdp->countries);
600		if (cp == NULL)
601			break;
602		LIST_REMOVE(cp, next);
603		if (cp->name != NULL)
604			free(__DECONST(char *, cp->name));
605		free(cp);
606	}
607	for (;;) {
608		struct freqband *fp = LIST_FIRST(&rdp->freqbands);
609		if (fp == NULL)
610			break;
611		LIST_REMOVE(fp, next);
612		free(fp);
613	}
614}
615
616struct regdata *
617lib80211_alloc_regdata(void)
618{
619	struct regdata *rdp;
620	struct stat sb;
621	void *xml;
622	int fd;
623
624	rdp = calloc(1, sizeof(struct regdata));
625
626	fd = open(_PATH_REGDOMAIN, O_RDONLY);
627	if (fd < 0) {
628#ifdef DEBUG
629		warn("%s: open(%s)", __func__, _PATH_REGDOMAIN);
630#endif
631		free(rdp);
632		return NULL;
633	}
634	if (fstat(fd, &sb) < 0) {
635#ifdef DEBUG
636		warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN);
637#endif
638		close(fd);
639		free(rdp);
640		return NULL;
641	}
642	xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
643	if (xml == MAP_FAILED) {
644#ifdef DEBUG
645		warn("%s: mmap", __func__);
646#endif
647		close(fd);
648		free(rdp);
649		return NULL;
650	}
651	if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) {
652#ifdef DEBUG
653		warn("%s: error reading regulatory database", __func__);
654#endif
655		munmap(xml, sb.st_size);
656		close(fd);
657		free(rdp);
658		return NULL;
659	}
660	munmap(xml, sb.st_size);
661	close(fd);
662
663	return rdp;
664}
665
666void
667lib80211_free_regdata(struct regdata *rdp)
668{
669	lib80211_regdomain_cleanup(rdp);
670	free(rdp);
671}
672
673/*
674 * Lookup a regdomain by SKU.
675 */
676const struct regdomain *
677lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku)
678{
679	const struct regdomain *dp;
680
681	LIST_FOREACH(dp, &rdp->domains, next) {
682		if (dp->sku == sku)
683			return dp;
684	}
685	return NULL;
686}
687
688/*
689 * Lookup a regdomain by name.
690 */
691const struct regdomain *
692lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name)
693{
694	const struct regdomain *dp;
695
696	LIST_FOREACH(dp, &rdp->domains, next) {
697		if (strcasecmp(dp->name, name) == 0)
698			return dp;
699	}
700	return NULL;
701}
702
703/*
704 * Lookup a country by ISO country code.
705 */
706const struct country *
707lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc)
708{
709	const struct country *cp;
710
711	LIST_FOREACH(cp, &rdp->countries, next) {
712		if (cp->code == cc)
713			return cp;
714	}
715	return NULL;
716}
717
718/*
719 * Lookup a country by ISO/long name.
720 */
721const struct country *
722lib80211_country_findbyname(const struct regdata *rdp, const char *name)
723{
724	const struct country *cp;
725	int len;
726
727	len = strlen(name);
728	LIST_FOREACH(cp, &rdp->countries, next) {
729		if (strcasecmp(cp->isoname, name) == 0)
730			return cp;
731	}
732	LIST_FOREACH(cp, &rdp->countries, next) {
733		if (strncasecmp(cp->name, name, len) == 0)
734			return cp;
735	}
736	return NULL;
737}
738