dhcpvendortax: genus & species support.

We've converged on a format of:
   taxonomy: type label Short Name;Exact Model
in the other taxonomy mechanisms. Implement that support in
dhcpvendortax as well, which was really only outputting the
short name.

This involved converting the substring matchers to regex, so
they could extract model information from the string. That in
turn means that the regex cases previously handled in check_specials
can be lumped in with the other regexes now.

Add a test case for every regexp.

Also convert the gperf file to use | as its delimeter, as the
other taxonomic gperf files now do.

Change-Id: I9596bbfda030d983cc5bc41de03594d9aa3d7f9c
diff --git a/cmds/Makefile b/cmds/Makefile
index 069e6ff..38557fa 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -227,7 +227,7 @@
 host-wifi_files_test: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
 dhcpvendortax: dhcpvendortax.o dhcpvendorlookup.o
 dhcpvendorlookup.c: dhcpvendorlookup.gperf
-	$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class \
+	$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class --delimiters="|" \
 		--includes --output-file=dhcpvendorlookup.c dhcpvendorlookup.gperf
 dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
 host-dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
diff --git a/cmds/dhcpvendorlookup.gperf b/cmds/dhcpvendorlookup.gperf
index b4b0ee1..884625a 100644
--- a/cmds/dhcpvendorlookup.gperf
+++ b/cmds/dhcpvendorlookup.gperf
@@ -18,50 +18,51 @@
 %}
 struct string_match {
   char *vendor_class;
+  char *genus;
   char *species;
 };
 %%
-6328-2Re, "InnoMedia VoIP adapter"
-AEROHIVE, "Aerohive Wifi AP"
-AirStation Series BUFFALO INC., "Buffalo Wifi AP"
-ArubaAP, "Aruba Wifi AP"
-ArubaInstantAP, "Aruba Wifi AP"
-ccp.avaya.com, "Avaya IP Phone"
-Cisco 802.11n AP Bridge, "Cisco Wifi AP"
-Dell Network Printer, "Dell Printer"
-DUNEHD, "Dune media player"
-ecobee1, "ecobee thermostat"
-HD409N, "ZaapTV"
-Hewlett-Packard JetDirect, "HP Printer"
-Hewlett-Packard LaserJet, "HP LaserJet"
-Hewlett-Packard OfficeJet, "HP OfficeJet"
-iDRAC, "Dell Remote Access Controller"
-ipphone.mitel.com, "Mitel IP Phone"
-IP2061, "Icon IP Phone"
-IWATSUIP, "Icon IP Phone"
-MC361, "Oki Printer"
-MC362, "Oki Printer"
-MERAKI, "Meraki Wifi AP"
-MicroChip Network Stack, "Microchip board"
-Motorola_AP, "Motorola Wifi AP"
-OptiIpPhone, "Siemens IP Phone"
-PS3, "Sony Playstation 3"
-PS4, "Sony Playstation 4"
-PS Vita, "Sony Playstation Vita"
-PS Vita TV, "Sony Playstation Vita"
-Ruckus CPE, "Ruckus Wifi AP"
-SAMSUNG Network Printer, "Samsung Printer"
-SEC_ITP, "Samsung IP Phone"
-ShoreTel IP Phone, "ShoreTel IP Phone"
-SIP-T38G, "Yealink IP Phone"
-SSG5-Serial-WLAN, "Juniper Gateway"
-TOSHIBA IPedge, "Toshiba VoIP adapter"
-ubnt, "Ubiquiti AP"
-VIZIO VIA, "Vizio TV"
-Withings00, "Withings Scale"
-XBOX 1.0, "Xbox"
-Xbox 360, "Xbox 360"
-Xerox Phaser, "Xerox Printer"
-XEROX Network Printer, "Xerox Printer"
-yealink, "Yealink IP Phone"
+6328-2Re| "InnoMedia VoIP adapter", "MTA6328-2Re"
+AEROHIVE| "Aerohive Wifi AP", "Aerohive Wifi AP"
+AirStation Series BUFFALO INC.| "Buffalo Wifi AP", "Buffalo AirStation AP"
+ArubaAP| "Aruba Wifi AP", "Aruba Wifi AP"
+ArubaInstantAP| "Aruba Wifi AP", "Aruba Wifi AP"
+ccp.avaya.com| "Avaya IP Phone", "Avaya IP Phone"
+Cisco 802.11n AP Bridge| "Cisco Wifi AP", "Cisco Wifi AP"
+Dell Network Printer| "Dell Printer", "Dell Printer"
+DUNEHD| "Dune media player", "DuneHD media player"
+ecobee1| "ecobee thermostat", "ecobee thermostat"
+HD409N| "ZaapTV", "Zaap HD409N"
+Hewlett-Packard JetDirect| "HP Printer", "HP Printer"
+Hewlett-Packard LaserJet| "HP Printer", "HP LaserJet"
+Hewlett-Packard OfficeJet| "HP Printer", "HP OfficeJet"
+iDRAC| "Dell Remote Access Controller", "DRAC"
+ipphone.mitel.com| "Mitel IP Phone", "Mitel IP Phone"
+IP2061| "Icon IP Phone", "IP2061"
+IWATSUIP| "Icon IP Phone", "Icon IP Phone"
+MC361| "Oki Printer", "Oki MC361"
+MC362| "Oki Printer", "Oki MC362"
+MERAKI| "Meraki Wifi AP", "Meraki Wifi AP"
+MicroChip Network Stack| "Microchip board", "Microchip board"
+Motorola_AP| "Motorola Wifi AP", "Motorola Wifi AP"
+OptiIpPhone| "Siemens IP Phone", "Siemens IP Phone"
+PS3| "Sony Playstation", "PS3"
+PS4| "Sony Playstation", "PS4"
+PS Vita| "Sony Playstation", "PS-Vita"
+PS Vita TV| "Sony Playstation", "PS-Vita"
+Ruckus CPE| "Ruckus Wifi AP", "Ruckus Wifi AP"
+SAMSUNG Network Printer| "Samsung Printer", "Samsung Printer"
+SEC_ITP| "Samsung IP Phone", "Samsung IP Phone"
+ShoreTel IP Phone| "ShoreTel IP Phone", "ShoreTel IP Phone"
+SIP-T38G| "Yealink IP Phone", "SIP-T38G"
+SSG5-Serial-WLAN| "Juniper Gateway", "SSG5"
+TOSHIBA IPedge| "Toshiba VoIP adapter", "Toshiba VoIP adapter"
+ubnt| "Ubiquiti Wifi AP", "Ubiquiti Wifi AP"
+VIZIO VIA| "Vizio Smart TV", "Vizio Smart TV"
+Withings00| "Withings Scale", "Withings Scale"
+XBOX 1.0| "Xbox", "Xbox"
+Xbox 360| "Xbox", "Xbox 360"
+Xerox Phaser| "Xerox Printer", "Xerox Printer"
+XEROX Network Printer| "Xerox Printer", "Xerox Printer"
+yealink| "Yealink IP Phone", "Yealink IP Phone"
 %%
diff --git a/cmds/dhcpvendortax.c b/cmds/dhcpvendortax.c
index ee0ca24..7c9adac 100644
--- a/cmds/dhcpvendortax.c
+++ b/cmds/dhcpvendortax.c
@@ -25,6 +25,7 @@
 
 struct string_match {
   char *vendor_class;
+  char *genus;
   char *species;
 };
 
@@ -33,39 +34,45 @@
     unsigned int len);
 
 
-struct string_match substring_matches[] = {
+struct regex_match {
+  char *regex;
+  char *genus;
+};
+
+
+struct regex_match regex_matches[] = {
   /*
    * Examples:
    *   AastraIPPhone55i
    *   AastraIPPhone57iCT
    *   AastraIPPhone6737i
    */
-  {"AastraIPPhone", "Aastra IP Phone"},
+  {"AastraIPPhone([[:alnum:]]+)", "Aastra IP Phone"},
 
   /* Examples:
    * AXIS,Network Camera,M3006,5.40.13
    * AXIS,Network Camera,P3346,5.20.1
    * AXIS,Thermal Network Camera,Q1931-E,5.55.4.1
    */
-  {"AXIS,Network Camera", "AXIS Network Camera"},
-  {"AXIS,Thermal Network Camera", "AXIS Network Camera"},
+  {"AXIS,Network Camera,([^,]+)", "AXIS Network Camera"},
+  {"AXIS,Thermal Network Camera,([^,]+)", "AXIS Network Camera"},
 
   /* Examples:
    * Canon MF620C Series
    */
-  {"Canon MF", "Canon Printer"},
+  {"Canon (MF[[:alnum:]]+)", "Canon Printer"},
 
   /* Examples:
    * Cisco AP c1200
    * Cisco AP c1240
    */
-  {"Cisco AP", "Cisco Wifi AP"},
+  {"Cisco AP ([[:alnum:]]+)", "Cisco Wifi AP"},
 
   /* Examples:
    *   Cisco Systems, Inc. IP Phone CP-7961G
    *   Cisco Systems, Inc. IP Phone CP-8861
    */
-  {"Cisco Systems, Inc. IP Phone", "Cisco IP Phone"},
+  {"Cisco Systems, Inc. IP Phone ([[:alnum:]-]+)", "Cisco IP Phone"},
 
   /* Examples:
    *   Cisco SPA504G
@@ -73,24 +80,31 @@
    *   CISCO SPA112
    *   ATA186-H6.0|V3.2.0|B041111A
    */
-  {"Cisco SPA", "Cisco IP Phone"},
-  {"CISCO SPA", "Cisco IP Phone"},
-  {"ATA186", "Cisco IP Phone"},
+  {"Cisco (SPA[[:alnum:]]+)", "Cisco IP Phone"},
+  {"CISCO (SPA[[:alnum:]]+)", "Cisco IP Phone"},
+  {"(ATA186)", "Cisco IP Phone"},
 
   /* Examples:
    * CPQRIB3
    */
-  {"CPQRIB", "Compaq Remote Insight"},
+  {"(CPQRIB[[:digit:]]+)", "Compaq Remote Insight"},
 
   /* Examples:
    * Dell Color MFP E525w
    */
-  {"Dell Color MFP", "Dell Printer"},
+  {"Dell Color MFP ([[:alnum:]]+)", "Dell Printer"},
+
+  /* Examples:
+   * Dell C1760nw Color Printer
+   * Dell C2660dn Color Laser
+   * Dell 2155cn Color MFP
+   */
+  {"^Dell ([[:alnum:]]+) Color (Printer|Laser|MFP)", "Dell Printer"},
 
   /* Examples:
    * digium_D40_1_4_2_0_63880
    */
-  {"digium", "Digium IP Phone"},
+  {"digium_([^_]+)", "Digium IP Phone"},
 
   /* Examples:
    * FortiAP-FP321C-AC-Discovery
@@ -98,8 +112,8 @@
    * FortiAP-FP321C
    * FortiWiFi-60D-POE
    */
-  {"FortiAP", "Fortinet Wifi AP"},
-  {"FortiWiFi", "Fortinet Wifi AP"},
+  {"FortiAP-([^-]+)", "Fortinet Wifi AP"},
+  {"FortiWiFi-([[:alnum:]-]+)", "Fortinet Wifi AP"},
 
   /* Examples:
    * Grandstream GXP1405 dslforum.org
@@ -107,41 +121,48 @@
    * Grandstream GXV3275 dslforum.org
    * Grandstream HT702 dslforum.org
    */
-  {"Grandstream GXP", "Grandstream IP Phone"},
-  {"Grandstream GXV", "Grandstream IP Phone"},
-  {"Grandstream HT", "Grandstream VoIP adapter"},
+  {"Grandstream (GX[[:alnum:]]+)", "Grandstream IP Phone"},
+  {"Grandstream (HT[[:alnum:]]+)", "Grandstream VoIP Adapter"},
+
+  /* Grandstream Voice over IP adapters. Examples:
+   * HT500 dslforum.org
+   * HT7XX dslforum.org
+   * DP7XX dslforum.org
+   */
+  {"^(HT[[:alnum:]]+) dslforum.org", "Grandstream VoIP Adapter"},
+  {"^(DP[[:alnum:]]+) dslforum.org", "Grandstream IP Phone"},
 
   /* Examples:
    * iPECS IP Edge 5000i-24G
    */
-  {"iPECS IP Edge", "iPECS IP PHONE"},
+  {"iPECS IP Edge ([[:alnum:]-]+)", "iPECS IP Phone"},
 
   /* Examples:
    * Juniper-ex2200-c-12p-2g
    */
-  {"Juniper-ex", "Juniper router"},
+  {"Juniper-(ex[^-]+)", "Juniper Router"},
 
   /* Examples:
    *   LINKSYS SPA-922
    *   LINKSYS SPA-942
    */
-  {"LINKSYS SPA", "Linksys IP Phone"},
+  {"LINKSYS (SPA-[[:alnum:]]+)", "Linksys IP Phone"},
 
   /* Examples:
    * MotorolaAP.AP7131
    */
-  {"MotorolaAP", "Motorola Wifi AP"},
+  {"MotorolaAP.([[:alnum:]]+)", "Motorola Wifi AP"},
 
   /* Examples:
    * NECDT700
    */
-  {"NECDT", "NEC IP Phone"},
+  {"(NECDT[[:alnum:]]+)", "NEC IP Phone"},
 
   /* Examples:
    *   6=qPolycomSoundPointIP-SPIP_1234567-12345-001
    *   6=tPolycomSoundStationIP-SSIP_12345678-12345-001
    */
-  {"PolycomSoundPointIP", "Polycom IP Phone"},
+  {".*PolycomSoundPointIP-([^-]+)", "Polycom IP Phone"},
 
   /* Examples:
    *   Polycom-SPIP335
@@ -151,26 +172,26 @@
    *   Polycom-VVX500
    *   Polycom-VVX600
    */
-  {"Polycom-SPIP", "Polycom IP Phone"},
-  {"Polycom-SSIP", "Polycom IP Phone"},
-  {"Polycom-VVX", "Polycom IP Phone"},
+  {"Polycom-(SPIP[[:alnum:]]+)", "Polycom IP Phone"},
+  {"Polycom-(SSIP[[:alnum:]]+)", "Polycom IP Phone"},
+  {"Polycom-(VVX[[:alnum:]]+)", "Polycom IP Phone"},
 
   /* Examples:
-   * Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3
    * Rabbit-TCPIP:Z-World:DHCP-Test:1.2.0
+   * Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3
    */
-  {"Rabbit-TCPIP", "Rabbit Microcontroller"},
-  {"Rabbit2000-TCPIP", "Rabbit Microcontroller"},
+  {"(Rabbit)-TCPIP", "Rabbit Microcontroller"},
+  {"(Rabbit2000)-TCPIP", "Rabbit Microcontroller"},
 
   /* Examples:
    * ReadyNet_WRT500
    */
-  {"ReadyNet_WRT", "ReadyNet Wifi AP"},
+  {"ReadyNet_(WRT[[:alnum:]]+)", "ReadyNet Wifi AP"},
 
   /* Examples:
    * SAMSUNG SCX-6x45
    */
-  {"SAMSUNG SCX", "Samsung Network MFP"},
+  {"SAMSUNG (SCX-[[:alnum:]]+)", "Samsung Network MFP"},
 
   /* Examples:
    * SF200-24P
@@ -182,29 +203,30 @@
    * SG200-50P
    * SG300-10
    */
-  {"SF200", "Cisco Managed Switch"},
-  {"SG 200", "Cisco Managed Switch"},
-  {"SG200", "Cisco Managed Switch"},
-  {"SG 300", "Cisco Managed Switch"},
-  {"SG300", "Cisco Managed Switch"},
+  {"(SF200-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG 200-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG200-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG 300-[[:alnum:]]+)", "Cisco Managed Switch"},
+  {"(SG300-[[:alnum:]]+)", "Cisco Managed Switch"},
 
   /* Examples:
    * snom-m3-SIP/02.11//18-Aug-10 15:36
    * snom320
    * snom710
    */
-  {"snom", "Snom IP Phone"},
+  {"(snom[^/]+)", "Snom IP Phone"},
 
   /* Examples:
    * telsey-stb-f8
    */
-  {"telsey-stb", "Telsey Media Player"},
+  {"telsey-stb-([[:alnum:]]+)", "Telsey Media Player"},
 
   {NULL, NULL}
 };
 
 
-/* Copy a string with no funny schtuff allowed; only alphanumerics + space. */
+/* Copy a string with no funny schtuff allowed; only alphanumerics + space
+ * plus a few characters considered safe. */
 static void no_mischief_strncpy(char *dst, const char *src, size_t n)
 {
   size_t i;
@@ -213,10 +235,11 @@
     int is_lower = (s >= 'a' && s <= 'z');
     int is_upper = (s >= 'A' && s <= 'Z');
     int is_digit = (s >= '0' && s <= '9');
+    int is_safe = (s == '_' || s == '-' || s == '.');
     if (s == '\0') {
       dst[i] = '\0';
       break;
-    } else if (is_lower || is_upper || is_digit) {
+    } else if (is_lower || is_upper || is_digit || is_safe) {
       dst[i] = s;
     } else if (s == ' ' || s == '\t') {
       dst[i] = ' ';
@@ -237,8 +260,9 @@
  *   Mfg=Hewlett Packard;Typ=Printer;Mod=HP LaserJet 400 M401n;Ser=ABCDE01234;
  *   mfg=Xerox;typ=MFP;mod=WorkCentre 3220;ser=ABC012345;loc=
  */
-int check_for_printer(const char *vendor_class, char *species,
-    size_t species_len)
+int check_for_printer(const char *vendor_class,
+    char *genus, size_t genus_len,
+    char *species, size_t species_len)
 {
   regex_t r_vendor, r_type, r_model;
   regmatch_t match[2];
@@ -270,9 +294,10 @@
   if (vendor && type) {
     char buf[128];
     snprintf(buf, sizeof(buf), "%s %s", vendor, type);
-    no_mischief_strncpy(species, buf, species_len);
+    no_mischief_strncpy(genus, buf, genus_len);
     rc = 0;
-  } else if (model) {
+  }
+  if (model) {
     no_mischief_strncpy(species, model, species_len);
     rc = 0;
   }
@@ -280,92 +305,49 @@
   if (vendor) free(vendor);
   if (type) free(type);
   if (model) free(model);
+  regfree(&r_vendor);
+  regfree(&r_type);
+  regfree(&r_model);
 
   return(rc);
 }
 
-/*
- * Check a few patterns from common vendors with lots of model
- * numbers.
- */
-int check_specials(const char *vendor_class, char *species,
-    size_t species_len)
-{
-  regex_t r_dellprinter, r_grandstream;
 
-  /*
-   * Dell printers. Examples:
-   * Dell C1760nw Color Printer
-   * Dell C2660dn Color Laser
-   * Dell 2155cn Color MFP
-   */
-  if (regcomp(&r_dellprinter, "^Dell \\S+ Color (Printer|Laser|MFP)",
-        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
-    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
-    exit(1);
-  }
-  if (regexec(&r_dellprinter, vendor_class, 0, NULL, 0) == 0) {
-    snprintf(species, species_len, "Dell Printer");
-    return(0);
-  }
-
-  /*
-   * Grandstream Voice over IP adapters. Examples:
-   * HT500 dslforum.org
-   * HT7XX dslforum.org
-   */
-  if (regcomp(&r_grandstream, "^HT.* dslforum.org",
-        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
-    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
-    exit(1);
-  }
-  if (regexec(&r_grandstream, vendor_class, 0, NULL, 0) == 0) {
-    snprintf(species, species_len, "Grandstream VoIP adapter");
-    return(0);
-  }
-
-  /*
-   * Grandstream IP phones. Examples:
-   * DP7XX dslforum.org
-   */
-  if (regcomp(&r_grandstream, "^DP.* dslforum.org",
-        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
-    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
-    exit(1);
-  }
-  if (regexec(&r_grandstream, vendor_class, 0, NULL, 0) == 0) {
-    snprintf(species, species_len, "Grandstream IP phone");
-    return(0);
-  }
-
-  return(1);
-}
-
-
-int lookup_vc(const char *vendor_class, char *species, size_t species_len)
+int lookup_vc(const char *vendor_class,
+    char *genus, size_t genus_len,
+    char *species, size_t species_len)
 {
   const struct string_match *p;
+  const struct regex_match *r;
   int slen = strlen(vendor_class);
 
   if ((p = exact_match(vendor_class, slen)) != NULL) {
+    no_mischief_strncpy(genus, p->genus, genus_len);
     no_mischief_strncpy(species, p->species, species_len);
     return(0);
   }
 
-  p = &substring_matches[0];
-  while (p->vendor_class != NULL) {
-    if (strstr(vendor_class, p->vendor_class) != NULL) {
-      no_mischief_strncpy(species, p->species, species_len);
+  for (r = &regex_matches[0]; r->regex != NULL; ++r) {
+    regex_t rx;
+    regmatch_t match[2];
+    if (regcomp(&rx, r->regex, REG_EXTENDED | REG_ICASE)) {
+      fprintf(stderr, "%s: regcomp failed!\n", r->regex);
+      exit(1);
+    }
+    if (regexec(&rx, vendor_class, 2, match, 0) == 0) {
+      int len = match[1].rm_eo - match[1].rm_so;
+      char *model = strndup(vendor_class + match[1].rm_so, len);
+      no_mischief_strncpy(species, model, species_len);
+      free(model);
+
+      snprintf(genus, genus_len, "%s", r->genus);
       return(0);
     }
-    p++;
+    regfree(&rx);
   }
 
-  if (check_for_printer(vendor_class, species, species_len) == 0) {
-    return(0);
-  }
-
-  if (check_specials(vendor_class, species, species_len) == 0) {
+  if (check_for_printer(vendor_class,
+        genus, genus_len, species, species_len) == 0) {
     return(0);
   }
 
@@ -390,6 +372,7 @@
   int c;
   const char *label = NULL;
   const char *vendor = NULL;
+  char genus[80];
   char species[80];
 
   setlinebuf(stdout);
@@ -410,9 +393,10 @@
   if (optind < argc || vendor == NULL || label == NULL)
     usage(argv[0]);
 
+  memset(genus, 0, sizeof(genus));
   memset(species, 0, sizeof(species));
-  if (lookup_vc(vendor, species, sizeof(species)) == 0) {
-    printf("dhcpv %s %s\n", label, species);
+  if (lookup_vc(vendor, genus, sizeof(genus), species, sizeof(species)) == 0) {
+    printf("dhcpv %s %s;%s\n", label, genus, species);
   }
   exit(0);
 }
diff --git a/cmds/test-dhcpvendortax.sh b/cmds/test-dhcpvendortax.sh
index 8f8d474..5704ed2 100755
--- a/cmds/test-dhcpvendortax.sh
+++ b/cmds/test-dhcpvendortax.sh
@@ -8,48 +8,101 @@
 
 # Check regex matches
 WVPASS $TAX -l label -v "AastraIPPhone55i" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Aastra IP Phone"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Aastra IP Phone;55i"
+WVPASS $TAX -l label -v "AXIS,Network Camera,M3006,5.40.13" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label AXIS Network Camera;M3006"
+WVPASS $TAX -l label -v "AXIS,Thermal Network Camera,Q1931-E,5.55.4.1" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label AXIS Network Camera;Q1931-E"
+WVPASS $TAX -l label -v "Canon MF620C Series" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Canon Printer;MF620C"
+WVPASS $TAX -l label -v "Cisco AP c1240" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Wifi AP;c1240"
+WVPASS $TAX -l label -v "Cisco Systems, Inc. IP Phone CP-7961G" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco IP Phone;CP-7961G"
+WVPASS $TAX -l label -v "Cisco SPA525G2" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco IP Phone;SPA525G2"
+WVPASS $TAX -l label -v "ATA186-H6.0|V3.2.0|B041111A" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco IP Phone;ATA186"
+WVPASS $TAX -l label -v "CPQRIB3" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Compaq Remote Insight;CPQRIB3"
+WVPASS $TAX -l label -v "Dell Color MFP E525w" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;E525w"
+WVPASS $TAX -l label -v "Dell C1760nw Color Printer" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;C1760nw"
+WVPASS $TAX -l label -v "Dell C2660dn Color Laser" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;C2660dn"
+WVPASS $TAX -l label -v "Dell 2155cn Color MFP" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;2155cn"
+WVPASS $TAX -l label -v "FortiAP-FP321C" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Fortinet Wifi AP;FP321C"
+WVPASS $TAX -l label -v "FortiWiFi-60D-POE" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Fortinet Wifi AP;60D-POE"
+WVPASS $TAX -l label -v "Grandstream GXP1405 dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream IP Phone;GXP1405"
+WVPASS $TAX -l label -v "Grandstream HT702 dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP Adapter;HT702"
+WVPASS $TAX -l label -v "HT500 dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP Adapter;HT500"
+WVPASS $TAX -l label -v "DP7XX dslforum.org" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream IP Phone;DP7XX"
+WVPASS $TAX -l label -v "iPECS IP Edge 5000i-24G" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label iPECS IP Phone;5000i-24G"
+WVPASS $TAX -l label -v "Juniper-ex2200-c-12p-2g" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Juniper Router;ex2200"
+WVPASS $TAX -l label -v "LINKSYS SPA-942" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Linksys IP Phone;SPA-942"
+WVPASS $TAX -l label -v "MotorolaAP.AP7131" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Motorola Wifi AP;AP7131"
+WVPASS $TAX -l label -v "NECDT700" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label NEC IP Phone;NECDT700"
 WVPASS $TAX -l label -v "6=qPolycomSoundPointIP-SPIP_1234567-12345-001" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone;SPIP_1234567"
 WVPASS $TAX -l label -v "Polycom-VVX310" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone;VVX310"
+WVPASS $TAX -l label -v "Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Rabbit Microcontroller;Rabbit2000"
+WVPASS $TAX -l label -v "Rabbit-TCPIP:Z-World:DHCP-Test:1.2.0" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Rabbit Microcontroller;Rabbit"
+WVPASS $TAX -l label -v "ReadyNet_WRT500" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label ReadyNet Wifi AP;WRT500"
+WVPASS $TAX -l label -v "SAMSUNG SCX-6x45" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Samsung Network MFP;SCX-6x45"
+WVPASS $TAX -l label -v "SF200-24P" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SF200-24P"
+WVPASS $TAX -l label -v "SG 200-08" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SG 200-08"
+WVPASS $TAX -l label -v "SG200-26" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SG200-26"
+WVPASS $TAX -l label -v "SG300-10" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Cisco Managed Switch;SG300-10"
+WVPASS $TAX -l label -v "snom-m3-SIP/02.11//18-Aug-10 15:36" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Snom IP Phone;snom-m3-SIP"
+WVPASS $TAX -l label -v "snom320" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Snom IP Phone;snom320"
+WVPASS $TAX -l label -v "telsey-stb-f8" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Telsey Media Player;f8"
 
 # Check exact matches
 WVPASS $TAX -l label -v "Dell Network Printer" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer;Dell Printer"
 WVPASS $TAX -l label -v "Xbox 360" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xbox 360"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xbox;Xbox 360"
 
 # Check model/type/manufacturer handling
 WVPASS $TAX -l label -v "Mfg=DELL;Typ=Printer;Mod=Dell 2330dn Laser Printer;Ser=0123AB5;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer"
-WVPASS $TAX -l label -v "Mfg=DELL;Mod=Dell 2330dn Laser Printer;Ser=0123AB5;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell 2330dn Laser Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer;Dell 2330dn Laser Printer"
 
 # Check case sensitivity
 WVPASS $TAX -l label -v "mFG=DELL;tYP=Printer;mOD=Dell 2330dn Laser Printer;Ser=0123AB5;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label DELL Printer;Dell 2330dn Laser Printer"
 
 # Check some other printer vendor formats
 WVPASS $TAX -l label -v "Mfg=FujiXerox;Typ=AIO;Mod=WorkCentre 6027;Ser=P1A234567" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label FujiXerox AIO"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label FujiXerox AIO;WorkCentre 6027"
 WVPASS $TAX -l label -v "Mfg=Hewlett Packard;Typ=Printer;Mod=HP LaserJet 400 M401n;Ser=ABCDE01234;" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Hewlett Packard Printer"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Hewlett Packard Printer;HP LaserJet 400 M401n"
 WVPASS $TAX -l label -v "mfg=Xerox;typ=MFP;mod=WorkCentre 3220;ser=ABC012345;loc=" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xerox MFP"
-
-# Check specials
-WVPASS $TAX -l label -v "Dell 2155cn Color MFP" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
-WVPASS $TAX -l label -v "Dell C1760nw Color Printer" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
-WVPASS $TAX -l label -v "Dell C2660dn Color Laser" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
-
-WVPASS $TAX -l label -v "HT500 dslforum.org" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP adapter"
-WVPASS $TAX -l label -v "HT7XX dslforum.org" >test1.$pid.tmp
-WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Grandstream VoIP adapter"
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Xerox MFP;WorkCentre 3220"
 
 # check invalid or missing arguments. -l and -v are required.
 WVFAIL $TAX