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