2 * Copyright (c) 2008 Sam Leffler, Errno Consulting
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
26 static const char rcsid[] = "$FreeBSD$";
29 #include <sys/types.h>
30 #include <sys/errno.h>
31 #include <sys/param.h>
45 #include "lib80211_regdomain.h"
47 #include <net80211/_ieee80211.h>
54 struct regdomain *rd; /* current domain */
55 struct netband *netband; /* current netband */
56 struct freqband *freqband; /* current freqband */
57 struct country *country; /* current country */
58 netband_head *curband; /* current netband list */
60 struct sbuf *sbuf[MAXLEVEL];
67 enum { DOMAIN, COUNTRY, FREQBAND } type;
71 start_element(void *data, const char *name, const char **attr)
73 #define iseq(a,b) (strcasecmp(a,b) == 0)
75 const void *id, *ref, *mode;
79 if (++mt->level == MAXLEVEL) {
80 /* XXX force parser to abort */
83 mt->sbuf[mt->level] = sbuf_new_auto();
84 id = ref = mode = NULL;
85 for (i = 0; attr[i] != NULL; i += 2) {
86 if (iseq(attr[i], "id")) {
88 } else if (iseq(attr[i], "ref")) {
90 } else if (iseq(attr[i], "mode")) {
93 printf("%*.*s[%s = %s]\n", mt->level + 1,
94 mt->level + 1, "", attr[i], attr[i+1]);
96 if (iseq(name, "rd") && mt->rd == NULL) {
97 if (mt->country == NULL) {
98 mt->rd = calloc(1, sizeof(struct regdomain));
99 mt->rd->name = strdup(id);
101 LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next);
103 mt->country->rd = (void *)strdup(ref);
106 if (iseq(name, "defcc") && mt->rd != NULL) {
107 mt->rd->cc = (void *)strdup(ref);
110 if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) {
112 warnx("no mode for netband at line %ld",
113 XML_GetCurrentLineNumber(mt->parser));
116 if (iseq(mode, "11b"))
117 mt->curband = &mt->rd->bands_11b;
118 else if (iseq(mode, "11g"))
119 mt->curband = &mt->rd->bands_11g;
120 else if (iseq(mode, "11a"))
121 mt->curband = &mt->rd->bands_11a;
122 else if (iseq(mode, "11ng"))
123 mt->curband = &mt->rd->bands_11ng;
124 else if (iseq(mode, "11na"))
125 mt->curband = &mt->rd->bands_11na;
127 warnx("unknown mode \"%s\" at line %ld",
128 __DECONST(char *, mode),
129 XML_GetCurrentLineNumber(mt->parser));
132 if (iseq(name, "band") && mt->netband == NULL) {
133 if (mt->curband == NULL) {
134 warnx("band without enclosing netband at line %ld",
135 XML_GetCurrentLineNumber(mt->parser));
138 mt->netband = calloc(1, sizeof(struct netband));
139 LIST_INSERT_HEAD(mt->curband, mt->netband, next);
142 if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) {
143 /* XXX handle inlines and merge into table? */
144 if (mt->netband->band != NULL) {
145 warnx("duplicate freqband at line %ld ignored",
146 XML_GetCurrentLineNumber(mt->parser));
149 mt->netband->band = (void *)strdup(ref);
153 if (iseq(name, "country") && mt->country == NULL) {
154 mt->country = calloc(1, sizeof(struct country));
155 mt->country->isoname = strdup(id);
156 mt->country->code = NO_COUNTRY;
158 LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next);
162 if (iseq(name, "freqband") && mt->freqband == NULL) {
163 mt->freqband = calloc(1, sizeof(struct freqband));
164 mt->freqband->id = strdup(id);
166 LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next);
173 decode_flag(struct mystate *mt, const char *p, int len)
175 #define iseq(a,b) (strcasecmp(a,b) == 0)
176 static const struct {
181 #define FLAG(x) { #x, sizeof(#x)-1, x }
182 FLAG(IEEE80211_CHAN_A),
183 FLAG(IEEE80211_CHAN_B),
184 FLAG(IEEE80211_CHAN_G),
185 FLAG(IEEE80211_CHAN_HT20),
186 FLAG(IEEE80211_CHAN_HT40),
187 FLAG(IEEE80211_CHAN_ST),
188 FLAG(IEEE80211_CHAN_TURBO),
189 FLAG(IEEE80211_CHAN_PASSIVE),
190 FLAG(IEEE80211_CHAN_DFS),
191 FLAG(IEEE80211_CHAN_CCK),
192 FLAG(IEEE80211_CHAN_OFDM),
193 FLAG(IEEE80211_CHAN_2GHZ),
194 FLAG(IEEE80211_CHAN_5GHZ),
195 FLAG(IEEE80211_CHAN_DYN),
196 FLAG(IEEE80211_CHAN_GFSK),
197 FLAG(IEEE80211_CHAN_GSM),
198 FLAG(IEEE80211_CHAN_STURBO),
199 FLAG(IEEE80211_CHAN_HALF),
200 FLAG(IEEE80211_CHAN_QUARTER),
201 FLAG(IEEE80211_CHAN_HT40U),
202 FLAG(IEEE80211_CHAN_HT40D),
203 FLAG(IEEE80211_CHAN_4MSXMIT),
204 FLAG(IEEE80211_CHAN_NOADHOC),
205 FLAG(IEEE80211_CHAN_NOHOSTAP),
206 FLAG(IEEE80211_CHAN_11D),
207 FLAG(IEEE80211_CHAN_FHSS),
208 FLAG(IEEE80211_CHAN_PUREG),
209 FLAG(IEEE80211_CHAN_108A),
210 FLAG(IEEE80211_CHAN_108G),
212 { "ECM", 3, REQ_ECM },
213 { "INDOOR", 6, REQ_INDOOR },
214 { "OUTDOOR", 7, REQ_OUTDOOR },
218 for (i = 0; i < nitems(flags); i++)
219 if (len == flags[i].len && iseq(p, flags[i].name))
220 return flags[i].value;
221 warnx("unknown flag \"%.*s\" at line %ld ignored",
222 len, p, XML_GetCurrentLineNumber(mt->parser));
228 end_element(void *data, const char *name)
230 #define iseq(a,b) (strcasecmp(a,b) == 0)
236 sbuf_finish(mt->sbuf[mt->level]);
237 p = sbuf_data(mt->sbuf[mt->level]);
238 len = sbuf_len(mt->sbuf[mt->level]);
240 /* <freqband>...</freqband> */
241 if (iseq(name, "freqstart") && mt->freqband != NULL) {
242 mt->freqband->freqStart = strtoul(p, NULL, 0);
245 if (iseq(name, "freqend") && mt->freqband != NULL) {
246 mt->freqband->freqEnd = strtoul(p, NULL, 0);
249 if (iseq(name, "chanwidth") && mt->freqband != NULL) {
250 mt->freqband->chanWidth = strtoul(p, NULL, 0);
253 if (iseq(name, "chansep") && mt->freqband != NULL) {
254 mt->freqband->chanSep = strtoul(p, NULL, 0);
257 if (iseq(name, "flags")) {
258 if (mt->freqband != NULL)
259 mt->freqband->flags |= decode_flag(mt, p, len);
260 else if (mt->netband != NULL)
261 mt->netband->flags |= decode_flag(mt, p, len);
263 warnx("flags without freqband or netband at line %ld ignored",
264 XML_GetCurrentLineNumber(mt->parser));
270 if (iseq(name, "name") && mt->rd != NULL) {
271 mt->rd->name = strdup(p);
274 if (iseq(name, "sku") && mt->rd != NULL) {
275 mt->rd->sku = strtoul(p, NULL, 0);
278 if (iseq(name, "netband") && mt->rd != NULL) {
283 /* <band> ... </band> */
284 if (iseq(name, "freqband") && mt->netband != NULL) {
285 /* XXX handle inline freqbands */
288 if (iseq(name, "maxpower") && mt->netband != NULL) {
289 mt->netband->maxPower = strtoul(p, NULL, 0);
292 if (iseq(name, "maxpowerdfs") && mt->netband != NULL) {
293 mt->netband->maxPowerDFS = strtoul(p, NULL, 0);
296 if (iseq(name, "maxantgain") && mt->netband != NULL) {
297 mt->netband->maxAntGain = strtoul(p, NULL, 0);
301 /* <country>...</country> */
302 if (iseq(name, "isocc") && mt->country != NULL) {
303 mt->country->code = strtoul(p, NULL, 0);
306 if (iseq(name, "name") && mt->country != NULL) {
307 mt->country->name = strdup(p);
312 warnx("unexpected XML token \"%s\" data \"%s\" at line %ld",
313 name, p, XML_GetCurrentLineNumber(mt->parser));
317 if (iseq(name, "freqband") && mt->freqband != NULL) {
318 /* XXX must have start/end frequencies */
319 /* XXX must have channel width/sep */
324 if (iseq(name, "rd") && mt->rd != NULL) {
329 if (iseq(name, "band") && mt->netband != NULL) {
330 if (mt->netband->band == NULL) {
331 warnx("no freqbands for band at line %ld",
332 XML_GetCurrentLineNumber(mt->parser));
334 if (mt->netband->maxPower == 0) {
335 warnx("no maxpower for band at line %ld",
336 XML_GetCurrentLineNumber(mt->parser));
338 /* default max power w/ DFS to max power */
339 if (mt->netband->maxPowerDFS == 0)
340 mt->netband->maxPowerDFS = mt->netband->maxPower;
345 if (iseq(name, "netband") && mt->netband != NULL) {
350 if (iseq(name, "country") && mt->country != NULL) {
351 /* XXX NO_COUNTRY should be in the net80211 country enum */
352 if ((int) mt->country->code == NO_COUNTRY) {
353 warnx("no ISO cc for country at line %ld",
354 XML_GetCurrentLineNumber(mt->parser));
356 if (mt->country->name == NULL) {
357 warnx("no name for country at line %ld",
358 XML_GetCurrentLineNumber(mt->parser));
360 if (mt->country->rd == NULL) {
361 warnx("no regdomain reference for country at line %ld",
362 XML_GetCurrentLineNumber(mt->parser));
368 sbuf_delete(mt->sbuf[mt->level]);
369 mt->sbuf[mt->level--] = NULL;
374 char_data(void *data, const XML_Char *s, int len)
383 for (; isspace(*b) && b < e; b++)
385 for (; isspace(*e) && e > b; e++)
387 if (e != b || (*b != '\0' && !isspace(*b)))
388 sbuf_bcat(mt->sbuf[mt->level], b, e-b+1);
392 findid(struct regdata *rdp, const void *id, int type)
396 for (ip = rdp->ident; ip->id != NULL; ip++)
397 if ((int) ip->type == type && strcasecmp(ip->id, id) == 0)
403 * Parse an regdomain XML configuration and build the internal representation.
406 lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len)
409 struct regdomain *dp;
416 memset(rdp, 0, sizeof(struct regdata));
417 mt = calloc(1, sizeof(struct mystate));
420 /* parse the XML input */
422 mt->parser = XML_ParserCreate(NULL);
423 XML_SetUserData(mt->parser, mt);
424 XML_SetElementHandler(mt->parser, start_element, end_element);
425 XML_SetCharacterDataHandler(mt->parser, char_data);
426 if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) {
427 warnx("%s: %s at line %ld", __func__,
428 XML_ErrorString(XML_GetErrorCode(mt->parser)),
429 XML_GetCurrentLineNumber(mt->parser));
432 XML_ParserFree(mt->parser);
434 /* setup the identifer table */
435 rdp->ident = calloc(sizeof(struct ident), mt->nident + 1);
436 if (rdp->ident == NULL)
442 LIST_FOREACH(dp, &rdp->domains, next) {
443 rdp->ident[i].id = dp->name;
444 rdp->ident[i].p = dp;
445 rdp->ident[i].type = DOMAIN;
448 LIST_FOREACH(fp, &rdp->freqbands, next) {
449 rdp->ident[i].id = fp->id;
450 rdp->ident[i].p = fp;
451 rdp->ident[i].type = FREQBAND;
454 LIST_FOREACH(cp, &rdp->countries, next) {
455 rdp->ident[i].id = cp->isoname;
456 rdp->ident[i].p = cp;
457 rdp->ident[i].type = COUNTRY;
461 /* patch references */
462 LIST_FOREACH(dp, &rdp->domains, next) {
463 if (dp->cc != NULL) {
465 dp->cc = findid(rdp, id, COUNTRY);
466 if (dp->cc == NULL) {
467 warnx("undefined country \"%s\"",
468 __DECONST(char *, id));
471 free(__DECONST(char *, id));
473 LIST_FOREACH(nb, &dp->bands_11b, next) {
474 id = findid(rdp, nb->band, FREQBAND);
476 warnx("undefined 11b band \"%s\"",
477 __DECONST(char *, nb->band));
482 LIST_FOREACH(nb, &dp->bands_11g, next) {
483 id = findid(rdp, nb->band, FREQBAND);
485 warnx("undefined 11g band \"%s\"",
486 __DECONST(char *, nb->band));
491 LIST_FOREACH(nb, &dp->bands_11a, next) {
492 id = findid(rdp, nb->band, FREQBAND);
494 warnx("undefined 11a band \"%s\"",
495 __DECONST(char *, nb->band));
500 LIST_FOREACH(nb, &dp->bands_11ng, next) {
501 id = findid(rdp, nb->band, FREQBAND);
503 warnx("undefined 11ng band \"%s\"",
504 __DECONST(char *, nb->band));
509 LIST_FOREACH(nb, &dp->bands_11na, next) {
510 id = findid(rdp, nb->band, FREQBAND);
512 warnx("undefined 11na band \"%s\"",
513 __DECONST(char *, nb->band));
519 LIST_FOREACH(cp, &rdp->countries, next) {
521 cp->rd = findid(rdp, id, DOMAIN);
522 if (cp->rd == NULL) {
523 warnx("undefined country \"%s\"",
524 __DECONST(char *, id));
527 free(__DECONST(char *, id));
530 return errors ? EINVAL : 0;
534 cleanup_bands(netband_head *head)
539 nb = LIST_FIRST(head);
547 * Cleanup state/resources for a previously parsed regdomain database.
550 lib80211_regdomain_cleanup(struct regdata *rdp)
556 struct regdomain *dp = LIST_FIRST(&rdp->domains);
559 LIST_REMOVE(dp, next);
560 cleanup_bands(&dp->bands_11b);
561 cleanup_bands(&dp->bands_11g);
562 cleanup_bands(&dp->bands_11a);
563 cleanup_bands(&dp->bands_11ng);
564 cleanup_bands(&dp->bands_11na);
565 if (dp->name != NULL)
566 free(__DECONST(char *, dp->name));
569 struct country *cp = LIST_FIRST(&rdp->countries);
572 LIST_REMOVE(cp, next);
573 if (cp->name != NULL)
574 free(__DECONST(char *, cp->name));
578 struct freqband *fp = LIST_FIRST(&rdp->freqbands);
581 LIST_REMOVE(fp, next);
587 lib80211_alloc_regdata(void)
594 rdp = calloc(1, sizeof(struct regdata));
596 fd = open(_PATH_REGDOMAIN, O_RDONLY);
599 warn("%s: open(%s)", __func__, _PATH_REGDOMAIN);
604 if (fstat(fd, &sb) < 0) {
606 warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN);
612 xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
613 if (xml == MAP_FAILED) {
615 warn("%s: mmap", __func__);
621 if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) {
623 warn("%s: error reading regulatory database", __func__);
625 munmap(xml, sb.st_size);
630 munmap(xml, sb.st_size);
637 lib80211_free_regdata(struct regdata *rdp)
639 lib80211_regdomain_cleanup(rdp);
644 * Lookup a regdomain by SKU.
646 const struct regdomain *
647 lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku)
649 const struct regdomain *dp;
651 LIST_FOREACH(dp, &rdp->domains, next) {
659 * Lookup a regdomain by name.
661 const struct regdomain *
662 lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name)
664 const struct regdomain *dp;
666 LIST_FOREACH(dp, &rdp->domains, next) {
667 if (strcasecmp(dp->name, name) == 0)
674 * Lookup a country by ISO country code.
676 const struct country *
677 lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc)
679 const struct country *cp;
681 LIST_FOREACH(cp, &rdp->countries, next) {
689 * Lookup a country by ISO/long name.
691 const struct country *
692 lib80211_country_findbyname(const struct regdata *rdp, const char *name)
694 const struct country *cp;
698 LIST_FOREACH(cp, &rdp->countries, next) {
699 if (strcasecmp(cp->isoname, name) == 0)
702 LIST_FOREACH(cp, &rdp->countries, next) {
703 if (strncasecmp(cp->name, name, len) == 0)