Merge "Update gpio-mailbox LED driver to support pwm brightness control."
diff --git a/cmds/.gitignore b/cmds/.gitignore
index 358adfb..5189e0e 100644
--- a/cmds/.gitignore
+++ b/cmds/.gitignore
@@ -17,14 +17,17 @@
gsetsid
gstatic
host-*
+http_bouncer
ionice
isoping
isostream
logos
mcastreceive
memwatcher
+mmap
multicast_join
netusage
+randint
randomdata
readubootver
realtime
diff --git a/cmds/Makefile b/cmds/Makefile
index a9800fb..134ca85 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -19,12 +19,12 @@
TARGETS=\
$(PORTABLE_TARGETS) \
alivemonitor \
- asus_hosts \
bsa2bluez \
burnin-flash \
buttonmon \
chg_mod_own \
cpulog \
+ dhcpvendortax \
dhcp-rogue \
dir-monitor \
diskbench \
@@ -49,15 +49,20 @@
LIB_TARGETS=\
stdoutline.so
HOST_TEST_TARGETS=\
- host-asus_hosts_test \
host-netusage_test \
host-utils_test
SCRIPT_TARGETS=\
is-secure-boot
ARCH_TARGETS=\
+ifeq ($(BUILD_ASUS),y)
+TARGETS += asustax
+HOST_TEST_TARGETS += host-asustax_test
+endif
+
ifeq ($(BUILD_SSDP),y)
-TARGETS += ssdp_poll
+TARGETS += ssdptax
+HOST_TEST_TARGETS += host-test-ssdptax.sh
endif
ifeq ($(BUILD_DNSSD),y)
@@ -86,10 +91,12 @@
HOST_CXX ?= g++
HOST_LD ?= cc
HOST_PROTOC ?= $(HOSTDIR)/usr/bin/protoc
+GPERF ?= gperf
CFLAGS += -Wall -Wextra -Wswitch-enum -Werror -Wno-unused-parameter \
-g -O -std=c99 -D_GNU_SOURCE $(EXTRACFLAGS)
CXXFLAGS += -Wall -Wextra -Wswitch-enum -Werror -Wno-unused-parameter \
-g -O -std=gnu++0x -D_GNU_SOURCE $(EXTRACXXFLAGS)
+LDFLAGS += $(EXTRALDFLAGS)
HOST_INCS=-I$(HOSTDIR)/usr/include
HOST_LIBS=-L$(HOSTDIR)/usr/lib -Wl,-rpath=$(HOSTDIR)/usr/lib
INCS=-I../libstacktrace
@@ -188,7 +195,16 @@
http_bouncer: LIBS+=-lcurl $(RT)
http_bouncer: http_bouncer.o
host-utils_test: host-utils_test.o host-utils.o
-ssdp_poll: ssdp_poll.o
+asustax: asustax.o l2utils.o
+asustax: LIBS += -lnl-3 -lstdc++ -lm
+host-asustax: host-asustax.o host-l2utils.o
+host-asustax: LIBS += $(HOST_LIBS) -lnl-3 -lstdc++ -lm
+host-asustax_test: host-asustax_test.o
+host-asustax_test: LIBS += $(HOST_LIBS) -lstdc++ -lm
+ssdptax: ssdptax.o l2utils.o
+ssdptax: LIBS += -lcurl -lnl-3 -lstdc++ -lm
+host-ssdptax: host-ssdptax.o host-l2utils.o
+host-ssdptax: LIBS += $(HOST_LIBS) -lcurl -lnl-3 -lstdc++ -lm
statpitcher.o: device_stats.pb.o
statpitcher: LIBS+=-L$(DESTDIR)$(PREFIX)/usr/lib -lprotobuf-lite -lpthread -lstdc++
statpitcher: device_stats.pb.o statpitcher.o
@@ -212,6 +228,13 @@
wifi_files: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
host-wifi_files_test: host-wifi_files_test.o
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 \
+ --includes --output-file=dhcpvendorlookup.c dhcpvendorlookup.gperf
+dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
+host-dhcpvendorlookup.o: CFLAGS += -Wno-missing-field-initializers
+host-dhcpvendortax: host-dhcpvendortax.o host-dhcpvendorlookup.o
TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py) $(TEST_TARGETS)
ifeq ($(RUN_HOST_TESTS),y)
diff --git a/cmds/asus_hosts.c b/cmds/asustax.cc
similarity index 88%
rename from cmds/asus_hosts.c
rename to cmds/asustax.cc
index 96ba81f..edbcd4f 100644
--- a/cmds/asus_hosts.c
+++ b/cmds/asustax.cc
@@ -24,6 +24,8 @@
#include <sys/socket.h>
#include <unistd.h>
+#include "l2utils.h"
+
#define ASUS_DISCOVERY_PORT 9999
#define PACKET_LENGTH 512
@@ -129,11 +131,17 @@
return dst;
}
-int receive_response(int s, char *response, int responselen)
+int receive_response(int s, L2Map *l2map, char *response, int responselen)
{
struct timeval tv;
fd_set rfds;
+ if (l2map == NULL || response == NULL) {
+ fprintf(stderr, "%s: l2map=%p response=%p\n", __FUNCTION__,
+ l2map, response);
+ exit(1);
+ }
+
memset(&tv, 0, sizeof(tv));
tv.tv_sec = 1;
tv.tv_usec = 0;
@@ -146,7 +154,8 @@
}
if (FD_ISSET(s, &rfds)) {
uint8_t buf[PACKET_LENGTH + 64];
- char addrbuf[16], namebuf[80];
+ char addrbuf[INET_ADDRSTRLEN], namebuf[80];
+ const char *mac;
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
asus_discovery_packet_t *discovery = (asus_discovery_packet_t *)buf;
@@ -174,7 +183,13 @@
id_len = strnlen((char *)discovery->product_id,
sizeof(discovery->product_id));
replace_newlines(discovery->product_id, id_len, namebuf, sizeof(namebuf));
- snprintf(response, responselen, "%s|%s", addrbuf, namebuf);
+ L2Map::iterator ii = l2map->find(std::string(addrbuf));
+ if (ii != l2map->end()) {
+ mac = ii->second.c_str();
+ } else {
+ mac = "00:00:00:00:00:00";
+ }
+ snprintf(response, responselen, "asus %s %s", mac, namebuf);
return 0;
} else {
@@ -193,7 +208,7 @@
int main(int argc, char **argv)
{
int s, opt, i;
- char *ifname = "br0";
+ const char *ifname = "br0";
while ((opt = getopt(argc, argv, "i:")) != -1) {
switch (opt) {
@@ -213,7 +228,9 @@
send_discovery(s);
for (i = 0; i < 128; i++) {
char response[128];
- int rc = receive_response(s, response, sizeof(response));
+ L2Map l2map;
+ get_l2_map(&l2map);
+ int rc = receive_response(s, &l2map, response, sizeof(response));
if (rc < 0) {
break;
} else if (rc == 0) {
diff --git a/cmds/asus_hosts_test.c b/cmds/asustax_test.cc
similarity index 96%
rename from cmds/asus_hosts_test.c
rename to cmds/asustax_test.cc
index 2fe98a7..eab37bf 100644
--- a/cmds/asus_hosts_test.c
+++ b/cmds/asustax_test.cc
@@ -18,7 +18,7 @@
#include <sys/socket.h>
#define UNIT_TESTS
-#include "asus_hosts.c"
+#include "asustax.cc"
/* Taken from a packet capture from an ASUS RT-68U */
static const unsigned char asus_pkt_normal[] = {
@@ -233,8 +233,9 @@
{
int sv[2];
char response[256];
- char *expected;
+ const char *expected;
ssize_t len;
+ L2Map l2map;
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv)) {
perror("socketpair");
@@ -249,7 +250,7 @@
}
response[0] = '\0';
- if (receive_response(sv[1], response, sizeof(response)) != 0) {
+ if (receive_response(sv[1], &l2map, response, sizeof(response)) != 0) {
fprintf(stderr, "receive_response could not parse packet\n");
exit(1);
}
@@ -268,7 +269,7 @@
}
response[0] = '\0';
- if (receive_response(sv[1], response, sizeof(response)) == 0) {
+ if (receive_response(sv[1], &l2map, response, sizeof(response)) == 0) {
fprintf(stderr, "receive_response should not parse packet\n");
exit(1);
}
@@ -286,7 +287,7 @@
}
response[0] = '\0';
- if (receive_response(sv[1], response, sizeof(response)) == 0) {
+ if (receive_response(sv[1], &l2map, response, sizeof(response)) == 0) {
fprintf(stderr, "receive_response should not parse packet\n");
exit(1);
}
@@ -304,7 +305,7 @@
}
response[0] = '\0';
- if (receive_response(sv[1], response, sizeof(response)) != 0) {
+ if (receive_response(sv[1], &l2map, response, sizeof(response)) != 0) {
fprintf(stderr, "receive_response could not parse packet\n");
exit(1);
}
@@ -315,6 +316,5 @@
exit(1);
}
-
exit(0);
}
diff --git a/cmds/dhcp-rogue.c b/cmds/dhcp-rogue.c
index 8d590bf..2441015 100644
--- a/cmds/dhcp-rogue.c
+++ b/cmds/dhcp-rogue.c
@@ -418,9 +418,8 @@
void usage(const char *progname)
{
- fprintf(stderr, "usage: %s [-i br0] [-l]\n", progname);
+ fprintf(stderr, "usage: %s [-i br0]\n", progname);
fprintf(stderr, "\t-i: name of the interface to probe for DHCP servers.\n");
- fprintf(stderr, "\t-l: show a response from localhost\n");
exit(1);
}
diff --git a/cmds/dhcpvendorlookup.gperf b/cmds/dhcpvendorlookup.gperf
new file mode 100644
index 0000000..b4b0ee1
--- /dev/null
+++ b/cmds/dhcpvendorlookup.gperf
@@ -0,0 +1,67 @@
+%{
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+%}
+struct string_match {
+ char *vendor_class;
+ 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"
+%%
diff --git a/cmds/dhcpvendortax.c b/cmds/dhcpvendortax.c
new file mode 100644
index 0000000..a3d4b9d
--- /dev/null
+++ b/cmds/dhcpvendortax.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+struct string_match {
+ char *vendor_class;
+ char *species;
+};
+
+/* Function generated by gperf for the exact_match lookup table. */
+extern const struct string_match *exact_match (const char *str,
+ unsigned int len);
+
+
+struct string_match substring_matches[] = {
+ /*
+ * Examples:
+ * AastraIPPhone55i
+ * AastraIPPhone57iCT
+ * AastraIPPhone6737i
+ */
+ {"AastraIPPhone", "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"},
+
+ /* Examples:
+ * Canon MF620C Series
+ */
+ {"Canon MF", "Canon Printer"},
+
+ /* Examples:
+ * Cisco AP c1200
+ * Cisco AP c1240
+ */
+ {"Cisco AP", "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"},
+
+ /* Examples:
+ * Cisco SPA504G
+ * Cisco SPA525G2
+ * CISCO SPA112
+ * ATA186-H6.0|V3.2.0|B041111A
+ */
+ {"Cisco SPA", "Cisco IP Phone"},
+ {"CISCO SPA", "Cisco IP Phone"},
+ {"ATA186", "Cisco IP Phone"},
+
+ /* Examples:
+ * CPQRIB3
+ */
+ {"CPQRIB", "Compaq Remote Insight"},
+
+ /* Examples:
+ * Dell Color MFP E525w
+ */
+ {"Dell Color MFP", "Dell Printer"},
+
+ /* Examples:
+ * digium_D40_1_4_2_0_63880
+ */
+ {"digium", "Digium IP Phone"},
+
+ /* Examples:
+ * FortiAP-FP321C-AC-Discovery
+ * FortiAP-FP221B-AC-Discovery
+ * FortiAP-FP321C
+ * FortiWiFi-60D-POE
+ */
+ {"FortiAP", "Fortinet Wifi AP"},
+ {"FortiWiFi", "Fortinet Wifi AP"},
+
+ /* Examples:
+ * Grandstream GXP1405 dslforum.org
+ * Grandstream GXP2124 dslforum.org
+ * Grandstream GXV3275 dslforum.org
+ * Grandstream HT702 dslforum.org
+ */
+ {"Grandstream GXP", "Grandstream IP Phone"},
+ {"Grandstream GXV", "Grandstream IP Phone"},
+ {"Grandstream HT", "Grandstream VoIP adapter"},
+
+ /* Examples:
+ * iPECS IP Edge 5000i-24G
+ */
+ {"iPECS IP Edge", "iPECS IP PHONE"},
+
+ /* Examples:
+ * Juniper-ex2200-c-12p-2g
+ */
+ {"Juniper-ex", "Juniper router"},
+
+ /* Examples:
+ * LINKSYS SPA-922
+ * LINKSYS SPA-942
+ */
+ {"LINKSYS SPA", "Linksys IP Phone"},
+
+ /* Examples:
+ * MotorolaAP.AP7131
+ */
+ {"MotorolaAP", "Motorola Wifi AP"},
+
+ /* Examples:
+ * NECDT700
+ */
+ {"NECDT", "NEC IP Phone"},
+
+ /* Examples:
+ * 6=qPolycomSoundPointIP-SPIP_1234567-12345-001
+ * 6=tPolycomSoundStationIP-SSIP_12345678-12345-001
+ */
+ {"PolycomSoundPointIP", "Polycom IP Phone"},
+
+ /* Examples:
+ * Polycom-SPIP335
+ * Polycom-SPIP550
+ * Polycom-SSIP7000
+ * Polycom-VVX310
+ * Polycom-VVX500
+ * Polycom-VVX600
+ */
+ {"Polycom-SPIP", "Polycom IP Phone"},
+ {"Polycom-SSIP", "Polycom IP Phone"},
+ {"Polycom-VVX", "Polycom IP Phone"},
+
+ /* Examples:
+ * Rabbit2000-TCPIP:Z-World:Testfoo:1.1.3
+ * Rabbit-TCPIP:Z-World:DHCP-Test:1.2.0
+ */
+ {"Rabbit-TCPIP", "Rabbit Microcontroller"},
+ {"Rabbit2000-TCPIP", "Rabbit Microcontroller"},
+
+ /* Examples:
+ * ReadyNet_WRT500
+ */
+ {"ReadyNet_WRT", "ReadyNet Wifi AP"},
+
+ /* Examples:
+ * SAMSUNG SCX-6x45
+ */
+ {"SAMSUNG SCX", "Samsung Network MFP"},
+
+ /* Examples:
+ * SF200-24P
+ * SG 200-08
+ * SG 200-26
+ * SG 300-10
+ * SG 300-20
+ * SG200-26
+ * 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"},
+
+ /* Examples:
+ * snom-m3-SIP/02.11//18-Aug-10 15:36
+ * snom320
+ * snom710
+ */
+ {"snom", "Snom IP Phone"},
+
+ /* Examples:
+ * telsey-stb-f8
+ */
+ {"telsey-stb", "Telsey Media Player"},
+
+ {NULL, NULL}
+};
+
+
+/* Copy a string with no funny schtuff allowed; only alphanumerics + space. */
+static void no_mischief_strncpy(char *dst, const char *src, size_t n)
+{
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ unsigned char s = src[i];
+ int is_lower = (s >= 'a' && s <= 'z');
+ int is_upper = (s >= 'A' && s <= 'Z');
+ int is_digit = (s >= '0' && s <= '9');
+ if (s == '\0') {
+ dst[i] = '\0';
+ break;
+ } else if (is_lower || is_upper || is_digit) {
+ dst[i] = s;
+ } else if (s == ' ' || s == '\t') {
+ dst[i] = ' ';
+ } else {
+ dst[i] = '_';
+ }
+ }
+
+ dst[n - 1] = '\0';
+}
+
+/*
+ * Check for vendor options pattern populated by a number of
+ * printer manufacturers:
+ *
+ * Mfg=DELL;Typ=Printer;Mod=Dell 2330dn Laser Printer;Ser=0123AB5;
+ * Mfg=FujiXerox;Typ=AIO;Mod=WorkCentre 6027;Ser=P1A234567
+ * 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)
+{
+ regex_t r_vendor, r_type, r_model;
+ regmatch_t match[2];
+ char *vendor = NULL, *type = NULL, *model = NULL;
+ int rc = 1;
+
+ if (regcomp(&r_vendor, "mfg=([^;]+)", REG_EXTENDED | REG_ICASE) ||
+ regcomp(&r_type, "typ=([^;]+)", REG_EXTENDED | REG_ICASE) ||
+ regcomp(&r_model, "mod=([^;]+)", REG_EXTENDED | REG_ICASE)) {
+ fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
+ exit(1);
+ }
+
+ if (regexec(&r_vendor, vendor_class, 2, match, 0) == 0) {
+ int len = match[1].rm_eo - match[1].rm_so;
+ vendor = strndup(vendor_class + match[1].rm_so, len);
+ }
+
+ if (regexec(&r_type, vendor_class, 2, match, 0) == 0) {
+ int len = match[1].rm_eo - match[1].rm_so;
+ type = strndup(vendor_class + match[1].rm_so, len);
+ }
+
+ if (regexec(&r_model, vendor_class, 2, match, 0) == 0) {
+ int len = match[1].rm_eo - match[1].rm_so;
+ model = strndup(vendor_class + match[1].rm_so, len);
+ }
+
+ if (vendor && type) {
+ char buf[128];
+ snprintf(buf, sizeof(buf), "%s %s", vendor, type);
+ no_mischief_strncpy(species, buf, species_len);
+ rc = 0;
+ } else if (model) {
+ no_mischief_strncpy(species, model, species_len);
+ rc = 0;
+ }
+
+ if (vendor) free(vendor);
+ if (type) free(type);
+ if (model) free(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)
+{
+ const struct string_match *p;
+ int slen = strlen(vendor_class);
+
+ if ((p = exact_match(vendor_class, slen)) != NULL) {
+ 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);
+ return(0);
+ }
+ p++;
+ }
+
+ if (check_for_printer(vendor_class, species, species_len) == 0) {
+ return(0);
+ }
+
+ if (check_specials(vendor_class, species, species_len) == 0) {
+ return(0);
+ }
+
+ return(1);
+}
+
+
+void usage(const char *progname)
+{
+ fprintf(stderr, "usage: %s -v vendor_string -l label\n", progname);
+ exit(1);
+}
+
+
+int main(int argc, char **argv)
+{
+ struct option long_options[] = {
+ {"label", required_argument, 0, 'l'},
+ {"vendor", required_argument, 0, 'v'},
+ {0, 0, 0, 0},
+ };
+ int c;
+ const char *label = NULL;
+ const char *vendor = NULL;
+ char species[80];
+
+ while ((c = getopt_long(argc, argv, "l:v:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'l':
+ label = optarg;
+ break;
+ case 'v':
+ vendor = optarg;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (optind < argc || vendor == NULL || label == NULL)
+ usage(argv[0]);
+
+ memset(species, 0, sizeof(species));
+ if (lookup_vc(vendor, species, sizeof(species)) == 0) {
+ printf("dhcpv %s %s\n", label, species);
+ }
+ exit(0);
+}
diff --git a/cmds/host-test-ssdptax.sh b/cmds/host-test-ssdptax.sh
new file mode 100755
index 0000000..d6796dd
--- /dev/null
+++ b/cmds/host-test-ssdptax.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#
+# Copyright 2016 Google Inc. All Rights Reserved.
+
+. ./wvtest/wvtest.sh
+
+SSDP=./host-ssdptax
+
+WVSTART "ssdptax test"
+WVPASSEQ "$($SSDP -t)" "ssdp 00:01:02:03:04:05 Test Device"
diff --git a/cmds/isostream.c b/cmds/isostream.c
index 8225e11..9407319 100644
--- a/cmds/isostream.c
+++ b/cmds/isostream.c
@@ -107,6 +107,7 @@
"\n"
"Server specific:\n"
" -P <number> limit to this many parallel connections\n"
+ " -C <algo> override TCP congestion control algorithm\n"
"Client specific:\n"
" -b <Mbits/sec> Mbits per second\n"
" -I <interface> set source interface to specified interface\n"
@@ -148,6 +149,24 @@
}
+int set_cong_ctl(int sock, const char *cong_ctl) {
+#ifdef TCP_CONGESTION
+ if (setsockopt(sock, IPPROTO_TCP, TCP_CONGESTION,
+ cong_ctl, strlen(cong_ctl)) != 0) {
+ char buf[128];
+ int e = errno;
+ snprintf(buf, sizeof(buf), "tcp_congestion('%s')", cong_ctl);
+ errno = e;
+ perror(buf);
+ return -1;
+ } else {
+ fprintf(stderr, "tcp_congestion set to '%s'.\n", cong_ctl);
+ }
+#endif
+ return 0;
+}
+
+
static int do_select(int sock, long long usec_timeout) {
fd_set rfds;
FD_ZERO(&rfds);
@@ -499,10 +518,11 @@
double sufficient = 0;
int timeout = 0;
int max_children = MAX_CHILDREN;
+ const char *cong_ctl = NULL;
int c;
char *ifr_name = NULL;
- while ((c = getopt(argc, argv, "b:I:P:s:t:h?")) >= 0) {
+ while ((c = getopt(argc, argv, "b:I:P:C:s:t:h?")) >= 0) {
switch (c) {
case 'b':
megabits_per_sec = atoi(optarg);
@@ -523,6 +543,14 @@
return 99;
}
break;
+ case 'C':
+ cong_ctl = optarg;
+#ifndef TCP_CONGESTION
+ fprintf(stderr, "%s: no support for congestion control overrides.\n",
+ argv[0]);
+ return 99;
+#endif
+ break;
case 's':
sufficient = atof(optarg);
if (sufficient < 1) {
@@ -582,6 +610,9 @@
perror("getsockname");
return 1;
}
+ if (cong_ctl && set_cong_ctl(sock, cong_ctl) != 0) {
+ return 1;
+ }
if (listen(sock, 1)) {
perror("listen");
return 1;
@@ -612,6 +643,9 @@
perror("accept");
continue;
}
+ if (cong_ctl && set_cong_ctl(conn, cong_ctl) != 0) {
+ return 1;
+ }
pid_t pid = fork();
if (pid < 0) {
perror("fork");
@@ -635,6 +669,11 @@
}
} else if (argc - optind == 1) {
fprintf(stderr, "client mode.\n");
+ if (cong_ctl) {
+ fprintf(stderr, "%s: can't set congestion control in client mode.\n",
+ argv[0]);
+ usage_and_die(argv[0]);
+ }
if (!megabits_per_sec) {
fprintf(stderr, "%s: must specify -b in client mode\n", argv[0]);
diff --git a/cmds/l2utils.cc b/cmds/l2utils.cc
new file mode 100644
index 0000000..d0549cb
--- /dev/null
+++ b/cmds/l2utils.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netlink/msg.h>
+#include <netlink/netlink.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "l2utils.h"
+
+void get_l2_map(L2Map *l2map)
+{
+ int s;
+ struct {
+ struct nlmsghdr hdr;
+ struct ndmsg msg;
+ } nlreq;
+ struct sockaddr_nl addr;
+ struct msghdr msg;
+ static uint8_t l2buf[256 * 1024];
+ struct iovec iov = {.iov_base = l2buf, .iov_len = sizeof(l2buf)};
+ struct nlmsghdr *nh;
+ struct ndmsg *ndm;
+ struct nlattr *tb[NDA_MAX+1];
+ int len;
+ int af[] = {AF_INET, AF_INET6};
+ unsigned int i;
+
+ for (i = 0; i < (sizeof(af) / sizeof(af[0])); ++i) {
+ if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
+ perror("socket AF_NETLINK");
+ exit(1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_pid = getpid();
+ addr.nl_groups = 0;
+
+ if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ perror("bind AF_NETLINK");
+ exit(1);
+ }
+
+ memset(&nlreq, 0, sizeof(nlreq));
+ nlreq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(nlreq.msg));
+ nlreq.hdr.nlmsg_type = RTM_GETNEIGH;
+ nlreq.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlreq.msg.ndm_family = af[i];
+
+ if (send(s, &nlreq, nlreq.hdr.nlmsg_len, 0) < 0) {
+ perror("send AF_NETLINK");
+ exit(1);
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_controllen = 0;
+ msg.msg_control = NULL;
+ msg.msg_flags = 0;
+
+ if ((len = recvmsg(s, &msg, 0)) <= 0) {
+ perror("recvmsg AL_NETLINK");
+ exit(1);
+ }
+
+ if (msg.msg_flags & MSG_TRUNC) {
+ fprintf(stderr, "recvmsg AL_NETLINK MSG_TRUNC\n");
+ exit(1);
+ }
+
+ memset(tb, 0, sizeof(tb));
+ nh = (struct nlmsghdr *)l2buf;
+ while (nlmsg_ok(nh, len)) {
+ ndm = (struct ndmsg *)nlmsg_data(nh);
+ if (nlmsg_parse(nh, sizeof(*ndm), tb, NDA_MAX, NULL)) {
+ fprintf(stderr, "nlmsg_parse failed\n");
+ exit(1);
+ }
+
+ if (tb[NDA_DST] && tb[NDA_LLADDR] &&
+ !(ndm->ndm_state & (NUD_INCOMPLETE | NUD_FAILED)) &&
+ (ndm->ndm_family == AF_INET || ndm->ndm_family == AF_INET6)) {
+ char mac[18];
+ char ipaddr[INET6_ADDRSTRLEN];
+ uint8_t *p;
+
+ p = (uint8_t *)nla_data(tb[NDA_LLADDR]);
+ snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
+ p[0], p[1], p[2], p[3], p[4], p[5]);
+
+ p = (uint8_t *)nla_data(tb[NDA_DST]);
+ inet_ntop(ndm->ndm_family, p, ipaddr, sizeof(ipaddr));
+
+ (*l2map)[std::string(ipaddr)] = std::string(mac);
+ }
+
+ nh = nlmsg_next(nh, &len);
+ }
+
+ close(s);
+ }
+}
diff --git a/cmds/l2utils.h b/cmds/l2utils.h
new file mode 100644
index 0000000..6b2385a
--- /dev/null
+++ b/cmds/l2utils.h
@@ -0,0 +1,10 @@
+#include <string>
+#include <tr1/unordered_map>
+
+#ifndef L2UTILS_H
+#define L2UTILS_H
+
+typedef std::tr1::unordered_map<std::string, std::string> L2Map;
+extern void get_l2_map(L2Map *l2map);
+
+#endif // L2UTILS_H
diff --git a/cmds/ssdp_poll.c b/cmds/ssdp_poll.c
deleted file mode 100644
index ef24b94..0000000
--- a/cmds/ssdp_poll.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/* ssdp_poll
- *
- * A client implementing the API described in
- * http://miniupnp.free.fr/minissdpd.html
- *
- * Requests the list of all known SSDP nodes and the
- * services they export, and prints it to stdout in
- * a format which is simple to parse.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-/* Encode length by using 7bit per Byte :
- * Most significant bit of each byte specifies that the
- * following byte is part of the code */
-#define DECODELENGTH(n, p) { \
- n = 0; \
- do { n = (n << 7) | (*p & 0x7f); } \
- while (*(p++)&0x80); \
-}
-
-#define CODELENGTH(n, p) { \
- if(n>=0x10000000) *(p++) = (n >> 28) | 0x80; \
- if(n>=0x200000) *(p++) = (n >> 21) | 0x80; \
- if(n>=0x4000) *(p++) = (n >> 14) | 0x80; \
- if(n>=0x80) *(p++) = (n >> 7) | 0x80; \
- *(p++) = n & 0x7f; \
-}
-
-#define SOCK_PATH "/var/run/minissdpd.sock"
-
-int connect_to_ssdpd()
-{
- struct sockaddr_un addr;
- int s;
-
- s = socket(AF_UNIX, SOCK_STREAM, 0);
- if(s < 0) {
- perror("socket AF_UNIX failed");
- exit(1);
- }
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path));
- if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
- perror("connect to minisspd failed");
- exit(1);
- }
-
- return s;
-}
-
-int main()
-{
- unsigned char *buffer;
- unsigned char *p;
- const char *device = "ssdp:all";
- int device_len = (int)strlen(device);
- int socket = connect_to_ssdpd();
- size_t siz = 65536;
- ssize_t len;
- fd_set readfds;
- struct timeval tv;
-
- if ((buffer = (unsigned char *)malloc(siz)) == NULL) {
- fprintf(stderr, "malloc(%zu) failed\n", siz);
- exit(1);
- }
- memset(buffer, 0, siz);
-
- buffer[0] = 5; /* request type : request all device server IDs */
- p = buffer + 1;
- CODELENGTH(device_len, p);
- memcpy(p, device, device_len);
- p += device_len;
- if (write(socket, buffer, p - buffer) < 0) {
- perror("write to minissdpd failed");
- exit(1);
- }
-
- FD_ZERO(&readfds);
- FD_SET(socket, &readfds);
- memset(&tv, 0, sizeof(tv));
- tv.tv_sec = 2;
-
- if (select(socket + 1, &readfds, NULL, NULL, &tv) < 1) {
- fprintf(stderr, "select failed\n");
- exit(1);
- }
-
- if ((len = read(socket, buffer, siz)) < 0) {
- perror("read from minissdpd failed");
- exit(1);
- }
-
- int num = buffer[0];
- p = buffer + 1;
- while (num-- > 0) {
- size_t copylen, slen;
- char url[256];
- char server[512];
-
- DECODELENGTH(slen, p);
- copylen = (slen >= sizeof(url)) ? sizeof(url) - 1 : slen;
- memcpy(url, p, copylen);
- url[copylen] = '\0';
- p += slen;
-
- DECODELENGTH(slen, p);
- copylen = (slen >= sizeof(server)) ? sizeof(server) - 1 : slen;
- memcpy(server, p, copylen);
- server[copylen] = '\0';
- p += slen;
-
- printf("%s|%s\n", url, server);
- }
-
- free(buffer);
- exit(0);
-}
diff --git a/cmds/ssdptax.cc b/cmds/ssdptax.cc
new file mode 100644
index 0000000..d218c0a
--- /dev/null
+++ b/cmds/ssdptax.cc
@@ -0,0 +1,593 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * ssdptax (SSDP Taxonomy)
+ *
+ * A client implementing the API described in
+ * http://miniupnp.free.fr/minissdpd.html
+ *
+ * Requests the list of all known SSDP nodes, requests
+ * device info from them, and tries to figure out what
+ * they are.
+ */
+
+#include <arpa/inet.h>
+#include <asm/types.h>
+#include <ctype.h>
+#include <curl/curl.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "l2utils.h"
+
+/* Encode length by using 7bit per Byte :
+ * Most significant bit of each byte specifies that the
+ * following byte is part of the code */
+#define DECODELENGTH(n, p) { \
+ n = 0; \
+ do { n = (n << 7) | (*p & 0x7f); } \
+ while (*(p++)&0x80); \
+}
+
+#define CODELENGTH(n, p) { \
+ if(n>=0x10000000) *(p++) = (n >> 28) | 0x80; \
+ if(n>=0x200000) *(p++) = (n >> 21) | 0x80; \
+ if(n>=0x4000) *(p++) = (n >> 14) | 0x80; \
+ if(n>=0x80) *(p++) = (n >> 7) | 0x80; \
+ *(p++) = n & 0x7f; \
+}
+
+#define SOCK_PATH "/var/run/minissdpd.sock"
+
+
+typedef struct {
+ char server[512];
+ char url[512];
+ char friendlyName[64];
+ int failed;
+} ssdp_info_t;
+
+
+/* Unit test support */
+char *get_test_ssdp_data();
+void get_test_l2_map(L2Map *l2map);
+
+static void memcpy_printable(char *dst, const char *src, size_t n)
+{
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ unsigned char s = src[i];
+ if (isspace(s)) {
+ dst[i] = ' '; // deliberately convert newline to space
+ } else if (isprint(s)) {
+ dst[i] = s;
+ } else {
+ dst[i] = '_';
+ }
+ }
+}
+
+
+/*
+ * Send a request to minissdpd. Returns a pointer to a buffer
+ * allocated using malloc(). Caller must free() the buffer when done.
+ */
+char *request_from_ssdpd(int reqtype, const char *device)
+{
+ int s = socket(AF_UNIX, SOCK_STREAM, 0);
+ struct sockaddr_un addr;
+ size_t siz = 256 * 1024;
+ char *buffer, *p;
+ ssize_t len;
+ int device_len = (int)strlen(device);
+ fd_set readfds;
+ struct timeval tv;
+
+ if (s < 0) {
+ perror("socket AF_UNIX failed");
+ exit(1);
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path));
+ if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
+ perror("connect to minisspd failed");
+ exit(1);
+ }
+
+ if ((buffer = (char *)malloc(siz)) == NULL) {
+ fprintf(stderr, "malloc(%zu) failed\n", siz);
+ exit(1);
+ }
+ memset(buffer, 0, siz);
+
+ buffer[0] = reqtype;
+ p = buffer + 1;
+ CODELENGTH(device_len, p);
+ memcpy(p, device, device_len);
+ p += device_len;
+ if (write(s, buffer, p - buffer) < 0) {
+ perror("write to minissdpd failed");
+ exit(1);
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(s, &readfds);
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = 2;
+
+ if (select(s + 1, &readfds, NULL, NULL, &tv) < 1) {
+ fprintf(stderr, "select failed\n");
+ exit(1);
+ }
+
+ if ((len = read(s, buffer, siz)) < 0) {
+ perror("read from minissdpd failed");
+ exit(1);
+ }
+
+ close(s);
+ return(buffer);
+}
+
+
+static void print_responses(const std::string &ipaddr,
+ const ssdp_info_t *info, L2Map *l2map)
+{
+ const char *mac;
+
+ if (info->failed) {
+ /*
+ * We could not fetch information from this client. That often means that
+ * the device was powered off recently. minissdpd still remembers that
+ * it is there, but we cannot contact it.
+ *
+ * Don't print anything for these, as we'd end up calling them "Unknown"
+ * and that is misleading. We only report information about devices which
+ * are active right now.
+ */
+ return;
+ }
+
+ L2Map::const_iterator ii = l2map->find(ipaddr);
+ if (ii != l2map->end()) {
+ mac = ii->second.c_str();
+ } else {
+ mac = "00:00:00:00:00:00";
+ }
+
+ /* taxonomy information to stdout */
+ if (strlen(info->friendlyName)) {
+ printf("ssdp %s %s\n", mac, info->friendlyName);
+ } else {
+ printf("ssdp %s Unknown;%s\n", mac, info->server);
+ }
+}
+
+
+const char *parse_minissdpd_response(const char *response,
+ char *key, size_t key_len,
+ char *url, size_t url_len,
+ char *value, size_t value_len)
+{
+ const char *p = response;
+ size_t copylen, slen;
+ int prefix = 0;
+ struct in6_addr in6;
+ struct in_addr in;
+ char ip[INET6_ADDRSTRLEN];
+
+ key[0] = url[0] = value[0] = '\0';
+
+ DECODELENGTH(slen, p);
+ copylen = (slen >= url_len) ? url_len - 1 : slen;
+ memcpy_printable(url, p, copylen);
+ url[copylen] = '\0';
+ p += slen;
+
+ DECODELENGTH(slen, p);
+ copylen = (slen >= value_len) ? value_len - 1 : slen;
+ memcpy_printable(value, p, copylen);
+ value[copylen] = '\0';
+ p += slen;
+
+ if (strncasecmp(url, "https://[", 9) == 0) prefix = 9;
+ if (strncasecmp(url, "http://[", 8) == 0) prefix = 8;
+ if (strncasecmp(url, "https://", 8) == 0) prefix = 8;
+ if (strncasecmp(url, "http://", 7) == 0) prefix = 7;
+ strncpy(ip, url + prefix, sizeof(ip));
+ strtok(ip, ":/@");
+
+ if (inet_pton(AF_INET6, ip, &in6)) {
+ inet_ntop(AF_INET6, &in6, key, key_len);
+ }
+ if (inet_pton(AF_INET, ip, &in)) {
+ inet_ntop(AF_INET, &in, key, key_len);
+ }
+
+ return p;
+}
+
+
+ssdp_info_t *dupinfo(ssdp_info_t *info)
+{
+ ssdp_info_t *i = (ssdp_info_t *)malloc(sizeof(*info));
+
+ if (i == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+
+ memcpy(i, info, sizeof(*i));
+ return i;
+}
+
+
+const char *findXmlField(const char *ptr, const char *label, ssize_t *len)
+{
+ char openlabel[64], closelabel[64];
+ const char *start, *end;
+
+ snprintf(openlabel, sizeof(openlabel), "<%s>", label);
+ snprintf(closelabel, sizeof(closelabel), "</%s>", label);
+
+ start = strcasestr(ptr, openlabel) + strlen(openlabel);
+ end = strcasestr(ptr, closelabel);
+
+ if ((end - start) > 0) {
+ *len = end - start;
+ return start;
+ }
+
+ return NULL;
+}
+
+
+/*
+ * libcurl calls this function back with the result of the HTTP GET.
+ *
+ * Expected value is an XML blob of
+ * http://upnp.org/specs/basic/UPnP-basic-Basic-v1-Device.pdf
+ *
+ * Like this (a Samsung TV):
+ * <?xml version="1.0"?>
+ * <root xmlns='urn:schemas-upnp-org:device-1-0' ...
+ * <device>
+ * <deviceType>urn:dial-multiscreen-org:device:dialreceiver:1</deviceType>
+ * <friendlyName>[TV]Samsung LED60</friendlyName>
+ * <manufacturer>Samsung Electronics</manufacturer>
+ * <manufacturerURL>http://www.samsung.com/sec</manufacturerURL>
+ * <modelDescription>Samsung TV NS</modelDescription>
+ * <modelName>UN60F6300</modelName>
+ * <modelNumber>1.0</modelNumber>
+ * <modelURL>http://www.samsung.com/sec</modelURL>
+ * ... etc, etc ...
+ */
+size_t callback(const char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ ssdp_info_t *info = (ssdp_info_t *)userdata;
+ const char *p;
+ ssize_t len;
+ ssize_t max = (ssize_t)sizeof(info->friendlyName);
+
+ if ((p = findXmlField(ptr, "friendlyName", &len)) == NULL) {
+ p = findXmlField(ptr, "modelDescription", &len);
+ }
+
+ if (p && (len > 0) && (len < max)) {
+ /* the len < max check ensures there will be a NUL byte at the end */
+ memcpy(info->friendlyName, p, len);
+ }
+
+ return size * nmemb;
+}
+
+
+/*
+ * SSDP returned an endpoint URL, use curl to GET its contents.
+ */
+void fetch_device_info(const char *url, ssdp_info_t *ssdp)
+{
+ CURL *curl = curl_easy_init();
+ int rc;
+
+ if (!curl) {
+ fprintf(stderr, "curl_easy_init failed\n");
+ return;
+ }
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_PATH_AS_IS, 1L);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &callback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, ssdp);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "ssdptax/1.0");
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L);
+ if ((rc = curl_easy_perform(curl)) != CURLE_OK) {
+ ssdp->failed = 1;
+ }
+ curl_easy_cleanup(curl);
+}
+
+
+void usage(char *progname) {
+ printf("usage: %s [-t]\n", progname);
+ printf("\t-t\ttest mode, run a test with fake SSDP data.\n");
+ exit(1);
+}
+
+
+int main(int argc, char **argv)
+{
+ char *buffer;
+ const char *p;
+ typedef std::tr1::unordered_map<std::string, ssdp_info_t*> ResponsesMap;
+ ResponsesMap responses;
+ L2Map l2map;
+ int c, num;
+ int testmode = 0;
+
+ setlinebuf(stdout);
+ if (curl_global_init(CURL_GLOBAL_NOTHING)) {
+ fprintf(stderr, "curl_global_init failed\n");
+ exit(1);
+ }
+
+ while ((c = getopt(argc, argv, "t")) != -1) {
+ switch(c) {
+ case 't': testmode = 1; break;
+ default: usage(argv[0]); break;
+ }
+ }
+
+ if (!testmode) {
+ /* 5 == request all device server IDs */
+ buffer = request_from_ssdpd(5, "ssdp:all");
+ } else {
+ buffer = get_test_ssdp_data();
+ }
+
+ num = buffer[0];
+ p = buffer + 1;
+ while ((num-- > 0) && (p < (buffer + sizeof(buffer)))) {
+ char key[INET6_ADDRSTRLEN];
+ ssdp_info_t info;
+
+ memset(&info, 0, sizeof(info));
+ p = parse_minissdpd_response(p, key, sizeof(key),
+ info.url, sizeof(info.url), info.server, sizeof(info.server));
+ if (strlen(key) && responses.find(std::string(key)) == responses.end()) {
+ if (!testmode) {
+ fetch_device_info(info.url, &info);
+ } else {
+ snprintf(info.friendlyName, sizeof(info.friendlyName), "Test Device");
+ }
+ responses.insert(std::make_pair<std::string,
+ ssdp_info_t*>(std::string(key), dupinfo(&info)));
+ }
+ }
+ free(buffer);
+
+ if (!testmode) {
+ get_l2_map(&l2map);
+ } else {
+ get_test_l2_map(&l2map);
+ }
+
+ for(ResponsesMap::const_iterator ii = responses.begin();
+ ii != responses.end(); ++ii) {
+ print_responses(ii->first, ii->second, &l2map);
+ }
+
+ curl_global_cleanup();
+ exit(0);
+}
+
+
+/*
+ * data for a unit test, response from a single SSDP
+ * client.
+ */
+uint8_t test_ssdp_data[] = {
+ 0x12, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38,
+ 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a,
+ 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70,
+ 0x5f, 0x32, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50,
+ 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31,
+ 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73,
+ 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50,
+ 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30,
+ 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e,
+ 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37,
+ 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f,
+ 0x32, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e,
+ 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75,
+ 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20,
+ 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
+ 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34,
+ 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36,
+ 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32,
+ 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30,
+ 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e,
+ 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53,
+ 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39,
+ 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32,
+ 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37,
+ 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32, 0x39,
+ 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c,
+ 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44,
+ 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+ 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e,
+ 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36,
+ 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x39, 0x5f,
+ 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50,
+ 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20,
+ 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b,
+ 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e,
+ 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31,
+ 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f,
+ 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x39, 0x5f, 0x23,
+ 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53,
+ 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f,
+ 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31,
+ 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32,
+ 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73,
+ 0x6d, 0x70, 0x5f, 0x31, 0x39, 0x5f, 0x23, 0x53,
+ 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50,
+ 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61,
+ 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50,
+ 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31,
+ 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36,
+ 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35,
+ 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d,
+ 0x70, 0x5f, 0x31, 0x39, 0x5f, 0x23, 0x53, 0x48,
+ 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f,
+ 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d,
+ 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e,
+ 0x30, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38,
+ 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a,
+ 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70,
+ 0x5f, 0x31, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50,
+ 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31,
+ 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73,
+ 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50,
+ 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30,
+ 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e,
+ 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37,
+ 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f,
+ 0x31, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e,
+ 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75,
+ 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20,
+ 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
+ 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34,
+ 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36,
+ 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31,
+ 0x31, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30,
+ 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e,
+ 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53,
+ 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39,
+ 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32,
+ 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37,
+ 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x31,
+ 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c,
+ 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44,
+ 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+ 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e,
+ 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36,
+ 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x31, 0x5f,
+ 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50,
+ 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20,
+ 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b,
+ 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e,
+ 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31,
+ 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f,
+ 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x31, 0x5f, 0x23,
+ 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53,
+ 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f,
+ 0x31, 0x2e, 0x30, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31,
+ 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32,
+ 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73,
+ 0x6d, 0x70, 0x5f, 0x32, 0x5f, 0x23, 0x53, 0x48,
+ 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f,
+ 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d,
+ 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e,
+ 0x30, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38,
+ 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a,
+ 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70,
+ 0x5f, 0x32, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e,
+ 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75,
+ 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20,
+ 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x21,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
+ 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34,
+ 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36,
+ 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32,
+ 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c,
+ 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44,
+ 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x21, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+ 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e,
+ 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36,
+ 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32, 0x5f, 0x23,
+ 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53,
+ 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f,
+ 0x31, 0x2e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+
+char *get_test_ssdp_data()
+{
+ size_t len = sizeof(test_ssdp_data);
+ char *buffer = (char *)malloc(len);
+
+ if (buffer == NULL) {
+ perror("malloc failed");
+ exit(1);
+ }
+
+ memcpy(buffer, test_ssdp_data, len);
+ return buffer;
+}
+
+
+void get_test_l2_map(L2Map *l2map)
+{
+ (*l2map)[std::string("192.168.42.125")] = std::string("00:01:02:03:04:05");
+}
diff --git a/cmds/test-dhcpvendortax.sh b/cmds/test-dhcpvendortax.sh
new file mode 100755
index 0000000..8f8d474
--- /dev/null
+++ b/cmds/test-dhcpvendortax.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+. ./wvtest/wvtest.sh
+
+pid=$$
+TAX=./host-dhcpvendortax
+
+WVSTART "dhcpvendortax test"
+
+# Check regex matches
+WVPASS $TAX -l label -v "AastraIPPhone55i" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Aastra IP Phone"
+WVPASS $TAX -l label -v "6=qPolycomSoundPointIP-SPIP_1234567-12345-001" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone"
+WVPASS $TAX -l label -v "Polycom-VVX310" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Polycom IP Phone"
+
+# Check exact matches
+WVPASS $TAX -l label -v "Dell Network Printer" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label Dell Printer"
+WVPASS $TAX -l label -v "Xbox 360" >test1.$pid.tmp
+WVPASSEQ "$(cat test1.$pid.tmp)" "dhcpv label 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"
+
+# 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"
+
+# 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"
+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"
+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"
+
+# check invalid or missing arguments. -l and -v are required.
+WVFAIL $TAX
+WVFAIL $TAX -l label
+WVFAIL $TAX -v vendor
+
+rm -f *.$pid.tmp
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 0f21e11..d25b40b 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -124,7 +124,21 @@
"""Validate as PA power level."""
def __init__(self):
- super(VPower, self).__init__(0, 2000000) # TODO(edjames)
+ super(VPower, self).__init__(0, 2000)
+
+
+class VGain(VRange):
+ """Validate as gain level."""
+
+ def __init__(self):
+ super(VGain, self).__init__(0, 63)
+
+
+class VGainIndex(VRange):
+ """Validate as gain index."""
+
+ def __init__(self):
+ super(VGainIndex, self).__init__(0, 5)
class VDict(Validator):
@@ -206,7 +220,6 @@
"""Handle a JSON request to glaukusd."""
url = 'http://localhost:8080' + self.api
payload = self.fmt % self.validator.config
- # TODO(edjames)
print 'Glaukus: ', url, payload
try:
fd = urllib2.urlopen(url, payload)
@@ -239,33 +252,41 @@
'peer_ipaddr': PtpConfig(VSlash, 'peer_ipaddr'),
'vlan_inband': PtpConfig(VVlan, 'vlan_inband'),
+ 'vlan_ooband': PtpConfig(VVlan, 'vlan_ooband'),
'vlan_peer': PtpConfig(VVlan, 'vlan_peer'),
'craft_ipaddr_activate': PtpActivate(VTrueFalse, 'craft_ipaddr'),
'link_ipaddr_activate': PtpActivate(VTrueFalse, 'local_ipaddr'),
'peer_ipaddr_activate': PtpActivate(VTrueFalse, 'peer_ipaddr'),
+
'vlan_inband_activate': PtpActivate(VTrueFalse, 'vlan_inband'),
+ 'vlan_ooband_activate': PtpActivate(VTrueFalse, 'vlan_ooband'),
'vlan_peer_activate': PtpActivate(VTrueFalse, 'vlan_peer'),
'freq_hi': Glaukus(VFreqHi, '/api/radio/frequency', '{"hiFrequency":%s}'),
'freq_lo': Glaukus(VFreqLo, '/api/radio/frequency', '{"loFrequency":%s}'),
'mode_hi': Glaukus(VTx, '/api/radio/hiTransceiver/mode', '%s'),
'tx_powerlevel': Glaukus(VPower, '/api/radio/tx/paPowerSet', '%s'),
- 'tx_on': Glaukus(VTrueFalse, '/api/radio/paLnaPowerEnabled', '%s'),
+ 'tx_gain': Glaukus(VGain, '/api/radio/tx/vgaGain', '%s'),
+ 'rx_gainindex': Glaukus(VGainIndex, '/api/radio/rx/agcDigitalGainIndex',
+ '%s'),
+ 'palna_on': Glaukus(VTrueFalse, '/api/radio/paLnaPowerEnabled', '%s'),
+ 'transceivers_on': Glaukus(VTrueFalse,
+ '/api/radio/transceiversPowerEnabled', '%s'),
'reboot': Reboot(VTrueFalse)
}
ifmap = {
'craft0': 'craft',
'br0': 'bridge',
- 'eth1.outofband': 'outofband',
- 'eth1.inband': 'inband',
- 'eth1.peer': 'link',
+ 'sw0.ooband': 'ooband',
+ 'sw0.inband': 'inband',
+ 'sw0.peer': 'link',
}
ifvlan = [
- 'eth1.outofband',
- 'eth1.inband',
- 'eth1.peer'
+ 'sw0.ooband',
+ 'sw0.inband',
+ 'sw0.peer'
]
stats = [
'multicast',
@@ -409,7 +430,7 @@
data['link_ipaddr'] = self.ReadFile(sim + cs + 'local_ipaddr')
data['peer_ipaddr'] = self.ReadFile(sim + cs + 'peer_ipaddr')
data['vlan_inband'] = self.ReadFile(sim + cs + 'vlan_inband')
- data['vlan_outofband'] = self.ReadFile(sim + cs + 'vlan_outofband')
+ data['vlan_ooband'] = self.ReadFile(sim + cs + 'vlan_ooband')
data['vlan_link'] = self.ReadFile(sim + cs + 'vlan_peer')
self.AddIpAddr(data)
self.AddInterfaceStats(data)
diff --git a/craftui/sim.tgz b/craftui/sim.tgz
index c0293f9..136253f 100644
--- a/craftui/sim.tgz
+++ b/craftui/sim.tgz
Binary files differ
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index ccfc835..ddca423 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -27,7 +27,7 @@
<input type="radio" id="tab-1" name="tab-group-1" checked>
<label for="tab-1">Site Configuration</label>
<div class="content">
- <b>Platfrom Parameters:</b>
+ <b>Platform Parameters:</b>
<table>
<tr>
<td align=center><b>Parameter
@@ -86,15 +86,15 @@
<tr>
<td><b>Out-of-band Management VLAN
- <td align=right><span id="platform/active_outofband_vlan">...</span>
+ <td align=right><span id="platform/active_ooband_vlan">...</span>
<td align=right>
- <span id="platform/vlan_outofband">...</span>
- <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_outofband', 1)">
+ <span id="platform/vlan_ooband">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_ooband', 1)">
<td>
- <input id=vlan_outofband type=text value="">
- <input type=submit value=Configure onclick="CraftUI.config('vlan_outofband')">
+ <input id=vlan_ooband type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('vlan_ooband')">
<td>
- <span id=vlan_outofband_result>...</span>
+ <span id=vlan_ooband_result>...</span>
<tr>
<td><b>Link VLAN (to peer)
@@ -145,7 +145,7 @@
<span id=mode_hi_result>...</span>
<tr>
- <td><b>Power Level
+ <td><b>Transmit Power (dB x 100)
<td align=right><span id="radio/tx/paPowerSet">...</span>
<td>
<input id=tx_powerlevel type=text value="">
@@ -154,13 +154,40 @@
<span id=tx_powerlevel_result>...</span>
<tr>
- <td><b>Power Enabled
+ <td><b>Transmit VGA Gain
+ <td align=right><span id="radio/tx/vgaGain">...</span>
+ <td>
+ <input id=tx_gain type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('tx_gain')">
+ <td>
+ <span id=tx_gain_result>...</span>
+
+ <tr>
+ <td><b>Receiver AGC Digital Gain Index
+ <td align=right><span id="radio/rx/agcDigitalGainIndex">...</span>
+ <td>
+ <input id=rx_gainindex type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('rx_gainindex')">
+ <td>
+ <span id=rx_gainindex_result>...</span>
+
+ <tr>
+ <td><b>Receiver PA/LNA Power Enabled
<td align=right><span id="radio/paLnaPowerEnabled">...</span>
<td>
- <input id=tx_on type=text value="">
- <input type=submit value=Configure onclick="CraftUI.config('tx_on')">
+ <input id=palna_on type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('palna_on')">
<td>
- <span id=tx_on_result>...</span>
+ <span id=palna_on_result>...</span>
+
+ <tr>
+ <td><b>Transceivers Power Enabled
+ <td align=right><span id="radio/transceiversPowerEnabled">...</span>
+ <td>
+ <input id=transceivers_on type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('transceivers_on')">
+ <td>
+ <span id=transceivers_on_result>...</span>
</table>
</div>
diff --git a/craftui/www/index.thtml b/craftui/www/index.thtml
index e53ddd7..20bba69 100644
--- a/craftui/www/index.thtml
+++ b/craftui/www/index.thtml
@@ -61,10 +61,10 @@
<td align=right><span id="platform/active_bridge_inet6">...</span></td></tr>
<tr>
<td><b>Out-of-Band (PoE)</b></td>
- <td align=right><span id="platform/outofband_mac">...</span></td>
- <td align=right><span id="platform/active_outofband_vlan">...</span></td>
- <td align=right><span id="platform/active_outofband_inet">...</span></td>
- <td align=right><span id="platform/active_outofband_inet6">...</span></td></tr>
+ <td align=right><span id="platform/ooband_mac">...</span></td>
+ <td align=right><span id="platform/active_ooband_vlan">...</span></td>
+ <td align=right><span id="platform/active_ooband_inet">...</span></td>
+ <td align=right><span id="platform/active_ooband_inet6">...</span></td></tr>
<tr>
<td><b>Link (to peer)</b></td>
<td align=right><span id="platform/link_mac">...</span></td>
@@ -177,27 +177,27 @@
<tr>
<td><b>Out-of-Band (PoE)<b></td>
- <td align=right><span id="platform/outofband_rx_bytes">...</span></td>
- <td align=right><span id="platform/outofband_rx_packets">...</span></td>
- <td align=right><span id="platform/outofband_multicast">...</span></td>
- <td align=right><span id="platform/outofband_broadcast">...</span></td>
- <td align=right><span id="platform/outofband_unicast">...</span></td>
+ <td align=right><span id="platform/ooband_rx_bytes">...</span></td>
+ <td align=right><span id="platform/ooband_rx_packets">...</span></td>
+ <td align=right><span id="platform/ooband_multicast">...</span></td>
+ <td align=right><span id="platform/ooband_broadcast">...</span></td>
+ <td align=right><span id="platform/ooband_unicast">...</span></td>
- <td align=right><span id="platform/outofband_tx_bytes">...</span></td>
- <td align=right><span id="platform/outofband_tx_packets">...</span></td>
+ <td align=right><span id="platform/ooband_tx_bytes">...</span></td>
+ <td align=right><span id="platform/ooband_tx_packets">...</span></td>
<td align=right>-</td>
<td align=right>-</td>
<td align=right>-</td>
- <td align=right><span id="platform/outofband_rx_errors">...</span></td>
- <td align=right><span id="platform/outofband_rx_dropped">...</span></td>
+ <td align=right><span id="platform/ooband_rx_errors">...</span></td>
+ <td align=right><span id="platform/ooband_rx_dropped">...</span></td>
<td align=right>-</td>
<td align=right>-</td>
- <td align=right><span id="platform/outofband_tx_errors">...</span></td>
- <td align=right><span id="platform/outofband_tx_dropped">...</span></td>
+ <td align=right><span id="platform/ooband_tx_errors">...</span></td>
+ <td align=right><span id="platform/ooband_tx_dropped">...</span></td>
<td align=right>-</td>
<td align=right>-</td>
- <td align=right><span id="platform/outofband_collisions">...</span></td>
+ <td align=right><span id="platform/ooband_collisions">...</span></td>
<tr>
<td><b>Link (to peer)<b></td>
diff --git a/craftui/www/static/craft.js b/craftui/www/static/craft.js
index 04e0f2c..81501aa 100644
--- a/craftui/www/static/craft.js
+++ b/craftui/www/static/craft.js
@@ -81,11 +81,13 @@
var el = document.getElementById(key);
var value = el.value;
var xhr = new XMLHttpRequest();
+ var action = "Configured";
xhr.open('post', 'content.json');
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
var data;
if (activate) {
data = { config: [ { [key + "_activate"]: "true" } ] };
+ action = "Applied";
} else {
data = { config: [ { [key]: value } ] };
}
@@ -95,7 +97,7 @@
xhr.onload = function(e) {
var json = JSON.parse(xhr.responseText);
if (json.error == 0) {
- el.innerHTML = "Success!";
+ el.innerHTML = action + " successfully.";
} else {
el.innerHTML = "Error: " + json.errorstring;
}
@@ -104,6 +106,7 @@
xhr.onerror = function(e) {
el.innerHTML = xhr.statusText + xhr.responseText;
}
+ el.innerHTML = "sending...";
xhr.send(txt);
};
diff --git a/presterastats/Makefile b/presterastats/Makefile
index 3a5f19b..af578cd 100644
--- a/presterastats/Makefile
+++ b/presterastats/Makefile
@@ -9,6 +9,7 @@
install:
mkdir -p $(BINDIR)
cp presterastats.py $(BINDIR)/presterastats
+ cp prestera_periodic.py $(BINDIR)/prestera_periodic
install-libs:
@echo "No libs to install."
diff --git a/presterastats/prestera_periodic.py b/presterastats/prestera_periodic.py
new file mode 100755
index 0000000..91f146e
--- /dev/null
+++ b/presterastats/prestera_periodic.py
@@ -0,0 +1,121 @@
+#!/usr/bin/python
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Periodically call presterastats and save results to filesystem."""
+
+__author__ = 'poist@google.com (Gregory Poist)'
+
+import errno
+import os
+import subprocess
+import sys
+import tempfile
+import time
+
+import options
+
+
+optspec = """
+presterastats [options]
+--
+startup_delay= wait this many seconds before first query [60]
+interval= interval to read statistics [15]
+"""
+
+
+class PresteraPeriodic(object):
+ """Class wrapping a cpss command to request stats."""
+
+ OUTPUT_DIR = '/tmp/prestera'
+
+ def __init__(self, interval):
+ self.interval = interval
+ self.ports_output_file = os.path.join(self.OUTPUT_DIR, 'ports.json')
+
+ def WriteToStderr(self, msg):
+ """Write a message to stderr."""
+
+ sys.stderr.write(msg)
+ sys.stderr.flush()
+
+ def RunPresteraStats(self):
+ """Run presterastats, return command output."""
+ return subprocess.check_output(['presterastats'])
+
+ def AcquireStats(self):
+ """Call the child process and get stats."""
+
+ # Output goes to a temporary file, which is renamed to the destination
+ tmpfile = ''
+ ports_stats = ''
+ try:
+ ports_stats = self.RunPresteraStats()
+ except OSError as ex:
+ self.WriteToStderr('Failed to run presterastats: %s\n' % ex)
+ except subprocess.CalledProcessError as ex:
+ self.WriteToStderr('presterastats exited non-zero: %s\n' % ex)
+
+ if not ports_stats:
+ self.WriteToStderr('Failed to get data from presterastats\n')
+ return
+
+ try:
+ with tempfile.NamedTemporaryFile(delete=False) as fd:
+ if not self.CreateDirs(os.path.dirname(self.ports_output_file)):
+ self.WriteToStderr('Failed to create output directory: %s\n' %
+ os.path.dirname(self.ports_output_file))
+ return
+ tmpfile = fd.name
+ fd.write(ports_stats)
+ fd.flush()
+ os.fsync(fd.fileno())
+ try:
+ os.rename(tmpfile, self.ports_output_file)
+ except OSError as ex:
+ self.WriteToStderr('Failed to move %s to %s: %s\n' % (
+ tmpfile, self.ports_output_file, ex))
+ return
+ finally:
+ if tmpfile and os.path.exists(tmpfile):
+ os.unlink(tmpfile)
+
+ def CreateDirs(self, dir_to_create):
+ """Recursively creates directories."""
+ try:
+ os.makedirs(dir_to_create)
+ except os.error as ex:
+ if ex.errno == errno.EEXIST:
+ return True
+ self.WriteToStderr('Failed to create directory: %s' % ex)
+ return False
+ return True
+
+ def RunForever(self):
+ while True:
+ self.AcquireStats()
+ time.sleep(self.interval)
+
+
+def main():
+ o = options.Options(optspec)
+ (opt, unused_flags, unused_extra) = o.parse(sys.argv[1:])
+ if opt.startup_delay:
+ time.sleep(opt.startup_delay)
+ prestera = PresteraPeriodic(opt.interval)
+ prestera.RunForever()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/presterastats/prestera_periodic_test.py b/presterastats/prestera_periodic_test.py
new file mode 100644
index 0000000..10be8e4
--- /dev/null
+++ b/presterastats/prestera_periodic_test.py
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for prestera_periodic."""
+
+__author__ = 'poist@google.com (Gregory Poist)'
+
+import errno
+import os
+import subprocess
+import tempfile
+import unittest
+import prestera_periodic
+
+STATS_JSON = """
+{
+ "port-interface-statistics": {
+ "0/0": {
+ "broadcast_packets_received": 8739,
+ "broadcast_packets_sent": 3,
+ "bytes_received": 32061162,
+ "bytes_sent": 10145704,
+ "multicast_packets_received": 35484,
+ "multicast_packets_sent": 20471,
+ "unicast_packets_received": 22875,
+ "unicast_packets_sent": 20737
+ }
+ }
+}
+"""
+
+
+class FakePresteraPeriodic(prestera_periodic.PresteraPeriodic):
+ """Mock PresteraPeriodic."""
+
+ def WriteToStderr(self, msg):
+ self.error_count += 1
+
+ def RunPresteraStats(self):
+ self.get_stats_called = True
+ if self.raise_os_error:
+ raise OSError(errno.ENOENT, 'raise an exception')
+ if self.raise_subprocess:
+ raise subprocess.CalledProcessError(cmd='durp', returncode=1)
+ return self.stats_response
+
+
+class PresteraPeriodicTest(unittest.TestCase):
+
+ def CreateTempFile(self):
+ # Create a temp file and have that be the target output file.
+ fd, self.output_file = tempfile.mkstemp()
+ os.close(fd)
+
+ def DeleteTempFile(self):
+ if os.path.exists(self.output_file):
+ os.unlink(self.output_file)
+
+ def setUp(self):
+ self.CreateTempFile()
+ self.periodic = FakePresteraPeriodic(1000)
+ self.periodic.raise_os_error = False
+ self.periodic.raise_subprocess = False
+ self.periodic.stats_response = STATS_JSON
+ self.periodic.ports_output_file = self.output_file
+ self.periodic.error_count = 0
+
+ def tearDown(self):
+ self.DeleteTempFile()
+
+ def testAcquireStats(self):
+ self.periodic.AcquireStats()
+
+ self.assertEquals(True, self.periodic.get_stats_called)
+ with open(self.output_file, 'r') as f:
+ output = ''.join(line for line in f)
+ self.assertEqual(self.periodic.stats_response, output)
+
+ def testAcquireStatsFailureToCreateOutputDir(self):
+ self.periodic.ports_output_file = '/root/nope/cant/write/this'
+
+ self.periodic.AcquireStats()
+ self.assertTrue(self.periodic.error_count > 0)
+
+ def testSubsequentEmptyDataNoOverwrite(self):
+ self.periodic.AcquireStats()
+
+ self.periodic.stats_response = ''
+ self.periodic.AcquireStats()
+
+ with open(self.output_file, 'r') as f:
+ output = ''.join(line for line in f)
+ self.assertEqual(STATS_JSON, output)
+
+ def testSubsequentExecError(self):
+ self.periodic.AcquireStats()
+
+ self.periodic.raise_os_error = True
+ self.periodic.AcquireStats()
+
+ self.assertTrue(self.periodic.error_count > 0)
+ with open(self.output_file, 'r') as f:
+ output = ''.join(line for line in f)
+ self.assertEqual(STATS_JSON, output)
+
+ def testExecError(self):
+ self.periodic.raise_subprocess = True
+ self.periodic.AcquireStats()
+
+ self.assertTrue(self.periodic.error_count > 0)
+ with open(self.output_file, 'r') as f:
+ output = ''.join(line for line in f)
+ self.assertEqual('', output)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/speedtest/.gitignore b/speedtest/.gitignore
new file mode 100644
index 0000000..17febb8
--- /dev/null
+++ b/speedtest/.gitignore
@@ -0,0 +1 @@
+speedtest
diff --git a/speedtest/Makefile b/speedtest/Makefile
index af5ea27..f926c9a 100644
--- a/speedtest/Makefile
+++ b/speedtest/Makefile
@@ -13,53 +13,116 @@
TFLAGS=$(DEBUG) -isystem ${GTEST_DIR}/include -isystem $(GMOCK_DIR)/include -pthread -std=c++11
LIBS=-lcurl -lpthread -ljsoncpp
-TOBJS=curl_env.o url.o errors.o request.o utils.o
+TOBJS=curl_env.o url.o errors.o request.o status.o utils.o
OBJS=config.o \
curl_env.o \
- download_task.o \
+ download.o \
errors.o \
- http_task.o \
+ find_nearest.o \
+ init.o \
options.o \
- ping_task.o \
+ ping.o \
+ region.o \
request.o \
+ result.o \
speedtest.o \
- task.o \
- timed_runner.o \
+ status.o \
transfer_runner.o \
- transfer_task.o \
- upload_task.o \
+ upload.o \
url.o \
utils.o
all: speedtest
-config.o: config.cc config.h url.h
+config.o: config.cc \
+ config.h \
+ errors.h \
+ region.h \
+ request.h \
+ status.h \
+ url.h \
+ utils.h
+curl_env.o: curl_env.cc curl_env.h errors.h request.h utils.h
+download.o: download.cc \
+ download.h \
+ request.h \
+ status.h \
+ utils.h
errors.o: errors.cc errors.h
-curl_env.o: curl_env.cc curl_env.h errors.h request.h
-download_task.o: download_task.cc download_task.h transfer_task.h utils.h
-http_task.o: http_task.cc http_task.h
-options.o: options.cc options.h url.h
-ping_task.o: ping_task.cc ping_task.h http_task.h request.h url.h utils.h
-request.o: request.cc request.h url.h
+find_nearest.o: find_nearest.cc \
+ find_nearest.h \
+ ping.h \
+ region.h \
+ request.h \
+ status.h \
+ utils.h
+init.o: init.cc \
+ init.h \
+ config.h \
+ find_nearest.h \
+ region.h \
+ request.h \
+ status.h \
+ timed_runner.h \
+ url.h \
+ utils.h
+options.o: options.cc options.h request.h url.h
+ping.o: ping.cc \
+ ping.h \
+ errors.h \
+ region.h \
+ request.h \
+ status.h \
+ url.h \
+ utils.h
+region.o: region.cc \
+ region.h \
+ errors.h \
+ request.h \
+ status.h \
+ region.h \
+ utils.h
+request.o: request.cc request.h url.h utils.h
+result.o: result.cc \
+ result.h \
+ config.h \
+ find_nearest.h \
+ init.h \
+ ping.h \
+ speedtest.h \
+ transfer_runner.h \
+ url.h
speedtest.o: speedtest.cc \
speedtest.h \
config.h \
- curl_env.h \
- download_task.h \
+ download.h \
+ errors.h \
+ init.h \
options.h \
- ping_task.h \
+ region.h \
request.h \
- task.h \
+ result.h \
+ status.h \
timed_runner.h \
transfer_runner.h \
- upload_task.h \
- url.h
-speedtest_main.o: speedtest_main.cc options.h speedtest.h
-task.o: task.cc task.h utils.h
-timed_runner.o: timed_runner.cc timed_runner.h task.h
-transfer_runner.o: transfer_runner.cc transfer_runner.h transfer_task.h utils.h
-transfer_task.o: transfer_task.cc transfer_task.h http_task.h
-upload_task.o: upload_task.cc upload_task.h transfer_task.h utils.h
+ upload.h \
+ url.h \
+ utils.h
+speedtest_main.o: speedtest_main.cc \
+ curl_env.h \
+ options.h \
+ request.h \
+ speedtest.h
+status.o: status.cc status.h utils.h
+transfer_runner.o: transfer_runner.cc \
+ transfer_runner.h \
+ status.h \
+ utils.h
+upload.o: upload.cc \
+ upload.h \
+ request.h \
+ status.h \
+ utils.h
utils.o: utils.cc options.h
url.o: url.cc url.h utils.h
@@ -87,10 +150,10 @@
$(CXX) -c $< $(TFLAGS) $(CXXFLAGS)
%_test: %_test.o %.o libgmock.a libspeedtesttest.a
- $(CXX) -o $@ $(TFLAGS) googlemock/src/gmock_main.cc $< $*.o $(LDFLAGS) $(LIBS) libgmock.a libspeedtesttest.a
+ $(CXX) -o $@ $(TFLAGS) googlemock/src/gmock_main.cc $< $*.o $(LDFLAGS) libgmock.a libspeedtesttest.a $(LIBS)
./$@
-test: config_test options_test request_test url_test
+test: config_test options_test region_test request_test url_test
install: speedtest
$(INSTALL) -m 0755 speedtest $(BINDIR)/
diff --git a/speedtest/config.cc b/speedtest/config.cc
index aede959..41803eb 100644
--- a/speedtest/config.cc
+++ b/speedtest/config.cc
@@ -16,26 +16,63 @@
#include "config.h"
+#include <curl/curl.h>
+#include "errors.h"
+#include "request.h"
+#include "utils.h"
+
// For some reason, the libjsoncpp package installs to /usr/include/jsoncpp/json
// instead of /usr{,/local}/include/json
#include <jsoncpp/json/json.h>
namespace speedtest {
-bool ParseConfig(const std::string &json, Config *config) {
+ConfigResult LoadConfig(ConfigOptions options) {
+ ConfigResult result;
+ result.start_time = SystemTimeMicros();
+ if (!options.request_factory) {
+ result.status = Status(StatusCode::INVALID_ARGUMENT,
+ "request factory not set");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ http::Url config_url(options.region_url);
+ config_url.set_path("/config");
+ if (options.verbose) {
+ std::cout << "Loading config from " << config_url.url() << "\n";
+ }
+ http::Request::Ptr request = options.request_factory(config_url);
+ request->set_url(config_url);
+ request->set_timeout_millis(500);
+ std::string json;
+ CURLcode code = request->Get([&](void *data, size_t size){
+ json.assign(static_cast<const char *>(data), size);
+ });
+ if (code != CURLE_OK) {
+ result.status = Status(StatusCode::INTERNAL, http::ErrorString(code));
+ } else {
+ result.status = ParseConfig(json, &result.config);
+ }
+ result.end_time = SystemTimeMicros();
+ return result;
+}
+
+Status ParseConfig(const std::string &json, Config *config) {
if (!config) {
- return false;
+ return Status(StatusCode::FAILED_PRECONDITION, "Config is null");
}
Json::Reader reader;
Json::Value root;
if (!reader.parse(json, root, false)) {
- return false;
+ return Status(StatusCode::INVALID_ARGUMENT, "Failed to parse config JSON");
}
- config->download_size = root["downloadSize"].asInt();
- config->upload_size = root["uploadSize"].asInt();
+ config->download_bytes = root["downloadSize"].asInt();
+ config->upload_bytes = root["uploadSize"].asInt();
config->interval_millis = root["intervalSize"].asInt();
+ config->location_id = root["locationId"].asString();
config->location_name = root["locationName"].asString();
config->min_transfer_intervals = root["minTransferIntervals"].asInt();
config->max_transfer_intervals = root["maxTransferIntervals"].asInt();
@@ -44,32 +81,12 @@
config->max_transfer_variance = root["maxTransferVariance"].asDouble();
config->num_uploads = root["numConcurrentUploads"].asInt();
config->num_downloads = root["numConcurrentDownloads"].asInt();
- config->ping_runtime = root["pingRunTime"].asInt();
- config->ping_timeout = root["pingTimeout"].asInt();
+ config->ping_runtime_millis = root["pingRunTime"].asInt();
+ config->ping_timeout_millis = root["pingTimeout"].asInt();
config->transfer_port_start = root["transferPortStart"].asInt();
config->transfer_port_end = root["transferPortEnd"].asInt();
- return true;
-}
-
-bool ParseServers(const std::string &json, std::vector<http::Url> *servers) {
- if (!servers) {
- return false;
- }
-
- Json::Reader reader;
- Json::Value root;
- if (!reader.parse(json, root, false)) {
- return false;
- }
-
- for (const auto &it : root["regionalServers"]) {
- http::Url url(it.asString());
- if (!url.ok()) {
- return false;
- }
- servers->emplace_back(url);
- }
- return true;
+ config->average_type = root["averageType"].asString();
+ return Status::OK;
}
void PrintConfig(const Config &config) {
@@ -77,9 +94,10 @@
}
void PrintConfig(std::ostream &out, const Config &config) {
- out << "Download size: " << config.download_size << " bytes\n"
- << "Upload size: " << config.upload_size << " bytes\n"
+ out << "Download size: " << config.download_bytes << " bytes\n"
+ << "Upload size: " << config.upload_bytes << " bytes\n"
<< "Interval size: " << config.interval_millis << " ms\n"
+ << "Location ID: " << config.location_id << "\n"
<< "Location name: " << config.location_name << "\n"
<< "Min transfer intervals: " << config.min_transfer_intervals << "\n"
<< "Max transfer intervals: " << config.max_transfer_intervals << "\n"
@@ -88,10 +106,11 @@
<< "Max transfer variance: " << config.max_transfer_variance << "\n"
<< "Number of downloads: " << config.num_downloads << "\n"
<< "Number of uploads: " << config.num_uploads << "\n"
- << "Ping runtime: " << config.ping_runtime << " ms\n"
- << "Ping timeout: " << config.ping_timeout << " ms\n"
+ << "Ping runtime: " << config.ping_runtime_millis << " ms\n"
+ << "Ping timeout: " << config.ping_timeout_millis << " ms\n"
<< "Transfer port start: " << config.transfer_port_start << "\n"
- << "Transfer port end: " << config.transfer_port_end << "\n";
+ << "Transfer port end: " << config.transfer_port_end << "\n"
+ << "Average type: " << config.average_type << "\n";
}
} // namespace
diff --git a/speedtest/config.h b/speedtest/config.h
index 2988484..f03f6cf 100644
--- a/speedtest/config.h
+++ b/speedtest/config.h
@@ -20,41 +20,56 @@
#include <iostream>
#include <string>
#include <vector>
+#include "region.h"
+#include "request.h"
+#include "status.h"
#include "url.h"
namespace speedtest {
struct Config {
- int download_size = 0;
- int upload_size = 0;
- int interval_millis = 0;
+ long download_bytes = 0;
+ long upload_bytes = 0;
+ long interval_millis = 0;
+ std::string location_id;
std::string location_name;
int min_transfer_intervals = 0;
int max_transfer_intervals = 0;
- int min_transfer_runtime = 0;
- int max_transfer_runtime = 0;
+ long min_transfer_runtime = 0;
+ long max_transfer_runtime = 0;
double max_transfer_variance = 0;
int num_downloads = 0;
int num_uploads = 0;
- int ping_runtime = 0;
- int ping_timeout = 0;
+ long ping_runtime_millis = 0;
+ long ping_timeout_millis = 0;
int transfer_port_start = 0;
int transfer_port_end = 0;
+ std::string average_type;
};
+struct ConfigOptions {
+ bool verbose;
+ http::Request::Factory request_factory;
+ http::Url region_url;
+};
+
+struct ConfigResult {
+ long start_time;
+ long end_time;
+ Status status;
+ Config config;
+};
+
+ConfigResult LoadConfig(ConfigOptions options);
+
// Parses a JSON document into a config struct.
// Returns true with the config struct populated on success.
// Returns false if the JSON is invalid or config is null.
-bool ParseConfig(const std::string &json, Config *config);
-
-// Parses a JSON document into a list of server URLs
-// Returns true with the servers populated in the vector on success.
-// Returns false if the JSON is invalid or servers is null.
-bool ParseServers(const std::string &json, std::vector<http::Url> *servers);
+Status ParseConfig(const std::string &json, Config *config);
void PrintConfig(const Config &config);
void PrintConfig(std::ostream &out, const Config &config);
} // namespace speedtest
-#endif //SPEEDTEST_CONFIG_H
+#endif // SPEEDTEST_CONFIG_H
diff --git a/speedtest/config_test.cc b/speedtest/config_test.cc
index 5924921..7b04d38 100644
--- a/speedtest/config_test.cc
+++ b/speedtest/config_test.cc
@@ -18,6 +18,12 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
+#include <string>
+#include <vector>
+#include "region.h"
+
+#define EXPECT_OK(statement) EXPECT_EQ(::speedtest::Status::OK, (statement))
+#define EXPECT_ERROR(statement) EXPECT_NE(::speedtest::Status::OK, (statement))
namespace speedtest {
namespace {
@@ -26,6 +32,7 @@
{
"downloadSize": 10000000,
"intervalSize": 200,
+ "locationId": "mci",
"locationName": "Kansas City",
"maxTransferIntervals": 25,
"maxTransferRunTime": 20000,
@@ -42,91 +49,42 @@
}
)CONFIG";
-const char *kValidServers = R"SERVERS(
-{
- "locationName": "Kansas City",
- "regionalServers": [
- "http://austin.speed.googlefiber.net/",
- "http://kansas.speed.googlefiber.net/",
- "http://provo.speed.googlefiber.net/",
- "http://stanford.speed.googlefiber.net/"
- ]
-}
-)SERVERS";
-
-const char *kInvalidServers = R"SERVERS(
-{
- "locationName": "Kansas City",
- "regionalServers": [
- "example.com..",
- ]
-}
-)SERVERS";
-
const char *kInvalidJson = "{{}{";
TEST(ParseConfigTest, NullConfig_Invalid) {
- EXPECT_FALSE(ParseConfig(kValidConfig, nullptr));
+ EXPECT_ERROR(ParseConfig(kValidConfig, nullptr));
}
TEST(ParseConfigTest, EmptyJson_Invalid) {
Config config;
- EXPECT_FALSE(ParseConfig("", &config));
+ EXPECT_ERROR(ParseConfig("", &config));
}
TEST(ParseConfigTest, InvalidJson_Invalid) {
Config config;
- EXPECT_FALSE(ParseConfig(kInvalidJson, &config));
+ EXPECT_ERROR(ParseConfig(kInvalidJson, &config));
}
TEST(ParseConfigTest, FullConfig_Valid) {
Config config;
- EXPECT_TRUE(ParseConfig(kValidConfig, &config));
- EXPECT_EQ(10000000, config.download_size);
- EXPECT_EQ(20000000, config.upload_size);
+ EXPECT_OK(ParseConfig(kValidConfig, &config));
+ EXPECT_EQ(10000000, config.download_bytes);
+ EXPECT_EQ(20000000, config.upload_bytes);
EXPECT_EQ(20, config.num_downloads);
EXPECT_EQ(15, config.num_uploads);
EXPECT_EQ(200, config.interval_millis);
+ EXPECT_EQ("mci", config.location_id);
EXPECT_EQ("Kansas City", config.location_name);
EXPECT_EQ(10, config.min_transfer_intervals);
EXPECT_EQ(25, config.max_transfer_intervals);
EXPECT_EQ(5000, config.min_transfer_runtime);
EXPECT_EQ(20000, config.max_transfer_runtime);
EXPECT_EQ(0.08, config.max_transfer_variance);
- EXPECT_EQ(3000, config.ping_runtime);
- EXPECT_EQ(300, config.ping_timeout);
+ EXPECT_EQ(3000, config.ping_runtime_millis);
+ EXPECT_EQ(300, config.ping_timeout_millis);
EXPECT_EQ(3004, config.transfer_port_start);
EXPECT_EQ(3023, config.transfer_port_end);
}
-TEST(ParseServersTest, NullServers_Invalid) {
- EXPECT_FALSE(ParseServers(kValidServers, nullptr));
-}
-
-TEST(ParseServersTest, EmptyServers_Invalid) {
- std::vector<http::Url> servers;
- EXPECT_FALSE(ParseServers("", &servers));
-}
-
-TEST(ParseServersTest, InvalidJson_Invalid) {
- std::vector<http::Url> servers;
- EXPECT_FALSE(ParseServers(kInvalidJson, &servers));
-}
-
-TEST(ParseServersTest, FullServers_Valid) {
- std::vector<http::Url> servers;
- EXPECT_TRUE(ParseServers(kValidServers, &servers));
- EXPECT_THAT(servers, testing::UnorderedElementsAre(
- http::Url("http://austin.speed.googlefiber.net/"),
- http::Url("http://kansas.speed.googlefiber.net/"),
- http::Url("http://provo.speed.googlefiber.net/"),
- http::Url("http://stanford.speed.googlefiber.net/")));
-}
-
-TEST(ParseServersTest, InvalidServers_Invalid) {
- std::vector<http::Url> servers;
- EXPECT_FALSE(ParseServers(kInvalidServers, &servers));
-}
-
} // namespace
} // namespace speedtest
diff --git a/speedtest/curl_env.cc b/speedtest/curl_env.cc
index eed370f..ee959ee 100644
--- a/speedtest/curl_env.cc
+++ b/speedtest/curl_env.cc
@@ -19,7 +19,6 @@
#include <cstdlib>
#include <iostream>
#include "errors.h"
-#include "request.h"
namespace http {
namespace {
@@ -71,7 +70,7 @@
curl_global_cleanup();
}
-std::unique_ptr<Request> CurlEnv::NewRequest(const Url &url) {
+Request::Ptr CurlEnv::NewRequest(const Url &url) {
// curl_global_init is not threadsafe and calling curl_easy_init may
// implicitly call it so we need to mutex lock on creating all requests
// to ensure the global initialization is done in a threadsafe manner.
@@ -101,6 +100,7 @@
}
curl_easy_setopt(handle.get(), CURLOPT_SHARE, share_);
+ curl_easy_setopt(handle.get(), CURLOPT_NOSIGNAL, 1);
return std::unique_ptr<Request>(new Request(handle, url));
}
diff --git a/speedtest/curl_env.h b/speedtest/curl_env.h
index 6a70f28..7bf0b60 100644
--- a/speedtest/curl_env.h
+++ b/speedtest/curl_env.h
@@ -20,7 +20,9 @@
#include <curl/curl.h>
#include <memory>
#include <mutex>
+#include "request.h"
#include "url.h"
+#include "utils.h"
namespace http {
@@ -37,7 +39,7 @@
static std::shared_ptr<CurlEnv> NewCurlEnv(const Options &options);
virtual ~CurlEnv();
- std::unique_ptr<Request> NewRequest(const Url &url);
+ Request::Ptr NewRequest(const Url &url);
void Lock(curl_lock_data lock_type);
void Unlock(curl_lock_data lock_type);
@@ -54,9 +56,7 @@
std::mutex dns_mutex_;
CURLSH *share_; // owned
- // disable
- CurlEnv(const CurlEnv &other) = delete;
- void operator=(const CurlEnv &other) = delete;
+ DISALLOW_COPY_AND_ASSIGN(CurlEnv);
};
} // namespace http
diff --git a/speedtest/download.cc b/speedtest/download.cc
new file mode 100644
index 0000000..f1dcc20
--- /dev/null
+++ b/speedtest/download.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "download.h"
+
+#include <string>
+#include <vector>
+#include <thread>
+
+namespace speedtest {
+
+Download::Download(const Options &options)
+ : options_(options),
+ start_time_(0),
+ end_time_(0),
+ bytes_transferred_(0) {
+}
+
+Download::Result Download::operator()(std::atomic_bool *cancel) {
+ start_time_ = SystemTimeMicros();
+ bytes_transferred_ = 0;
+
+ if (!cancel) {
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status(StatusCode::FAILED_PRECONDITION, "cancel is null"));
+ }
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < options_.num_transfers; ++i) {
+ threads.emplace_back([=]{
+ http::Request::Ptr download = options_.request_factory(i);
+ while (!*cancel) {
+ long downloaded = 0;
+ download->set_param("i", to_string(i));
+ download->set_param("size", to_string(options_.download_bytes));
+ download->set_param("time", to_string(SystemTimeMicros()));
+ download->set_progress_fn([&](curl_off_t,
+ curl_off_t dlnow,
+ curl_off_t,
+ curl_off_t) -> bool {
+ if (dlnow > downloaded) {
+ bytes_transferred_ += dlnow - downloaded;
+ downloaded = dlnow;
+ }
+ return *cancel;
+ });
+ download->Get();
+ download->Reset();
+ }
+ });
+ }
+
+ for (std::thread &thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status::OK);
+}
+
+Download::Result Download::GetResult(Status status) const {
+ Download::Result result;
+ result.start_time = start_time_;
+ result.end_time = end_time_;
+ result.status = status;
+ result.bytes_transferred = bytes_transferred_;
+ return result;
+}
+
+} // namespace
diff --git a/speedtest/download.h b/speedtest/download.h
new file mode 100644
index 0000000..65c202e
--- /dev/null
+++ b/speedtest/download.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_DOWNLOAD_H
+#define SPEEDTEST_DOWNLOAD_H
+
+#include <atomic>
+#include <functional>
+#include "request.h"
+#include "status.h"
+#include "utils.h"
+
+namespace speedtest {
+
+class Download {
+ public:
+ struct Options {
+ bool verbose;
+ std::function<http::Request::Ptr(int)> request_factory;
+ int num_transfers;
+ long download_bytes;
+ };
+
+ struct Result {
+ long start_time;
+ long end_time;
+ Status status;
+ long bytes_transferred;
+ };
+
+ explicit Download(const Options &options);
+
+ Result operator()(std::atomic_bool *cancel);
+
+ long start_time() const { return start_time_; }
+ long end_time() const { return end_time_; }
+ long bytes_transferred() const { return bytes_transferred_; }
+
+ private:
+ Result GetResult(Status status) const;
+
+ Options options_;
+ std::atomic_long start_time_;
+ std::atomic_long end_time_;
+ std::atomic_long bytes_transferred_;
+
+ DISALLOW_COPY_AND_ASSIGN(Download);
+};
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_DOWNLOAD_H
diff --git a/speedtest/download_task.cc b/speedtest/download_task.cc
deleted file mode 100644
index a643725..0000000
--- a/speedtest/download_task.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "download_task.h"
-
-#include <algorithm>
-#include <cassert>
-#include <iostream>
-#include <thread>
-#include "utils.h"
-
-namespace speedtest {
-
-DownloadTask::DownloadTask(const Options &options)
- : TransferTask(options_),
- options_(options) {
- assert(options_.num_transfers > 0);
- assert(options_.download_size > 0);
-}
-
-void DownloadTask::RunInternal() {
- ResetCounters();
- threads_.clear();
- if (options_.verbose) {
- std::cout << "Downloading " << options_.num_transfers
- << " threads with " << options_.download_size << " bytes\n";
- }
- for (int i = 0; i < options_.num_transfers; ++i) {
- threads_.emplace_back([=]{
- RunDownload(i);
- });
- }
-}
-
-void DownloadTask::StopInternal() {
- std::for_each(threads_.begin(), threads_.end(), [](std::thread &t) {
- t.join();
- });
-}
-
-void DownloadTask::RunDownload(int id) {
- http::Request::Ptr download = options_.request_factory(id);
- while (GetStatus() == TaskStatus::RUNNING) {
- long downloaded = 0;
- download->set_param("i", to_string(id));
- download->set_param("size", to_string(options_.download_size));
- download->set_param("time", to_string(SystemTimeMicros()));
- download->set_progress_fn([&](curl_off_t,
- curl_off_t dlnow,
- curl_off_t,
- curl_off_t) -> bool {
- if (dlnow > downloaded) {
- TransferBytes(dlnow - downloaded);
- downloaded = dlnow;
- }
- return GetStatus() != TaskStatus::RUNNING;
- });
- StartRequest();
- download->Get();
- EndRequest();
- download->Reset();
- }
-}
-
-} // namespace speedtest
diff --git a/speedtest/download_task.h b/speedtest/download_task.h
deleted file mode 100644
index 2b65478..0000000
--- a/speedtest/download_task.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SPEEDTEST_DOWNLOAD_TASK_H
-#define SPEEDTEST_DOWNLOAD_TASK_H
-
-#include <thread>
-#include <vector>
-#include "transfer_task.h"
-
-namespace speedtest {
-
-class DownloadTask : public TransferTask {
- public:
- struct Options : TransferTask::Options {
- int download_size = 0;
- };
-
- explicit DownloadTask(const Options &options);
-
- protected:
- void RunInternal() override;
- void StopInternal() override;
-
- private:
- void RunDownload(int id);
-
- Options options_;
- std::vector<std::thread> threads_;
-
- // disallowed
- DownloadTask(const DownloadTask &) = delete;
- void operator=(const DownloadTask &) = delete;
-};
-
-} // namespace speedtest
-
-#endif // SPEEDTEST_DOWNLOAD_TASK_H
diff --git a/speedtest/find_nearest.cc b/speedtest/find_nearest.cc
new file mode 100644
index 0000000..f1547d4
--- /dev/null
+++ b/speedtest/find_nearest.cc
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "find_nearest.h"
+
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <thread>
+
+namespace speedtest {
+namespace {
+
+const long kDefaultPingTimeoutMillis = 500;
+
+}
+
+FindNearest::FindNearest(const Options &options)
+ : options_(options),
+ start_time_(0),
+ end_time_(0) {
+}
+
+FindNearest::Result FindNearest::operator()(std::atomic_bool *cancel) {
+ FindNearest::Result result;
+ result.start_time = SystemTimeMicros();
+
+ if (!cancel) {
+ result.status = Status(StatusCode::FAILED_PRECONDITION, "cancel is null");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ if (options_.regions.size() == 1) {
+ result.selected_region = options_.regions.front();
+ result.status = Status::OK;
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ std::vector<std::thread> threads;
+ std::mutex mutex;
+ for (const Region ®ion : options_.regions) {
+ threads.emplace_back([&]{
+ Ping::Options ping_options;
+ ping_options.verbose = options_.verbose;
+ ping_options.request_factory = options_.request_factory;
+ ping_options.timeout_millis = options_.ping_timeout_millis > 0
+ ? options_.ping_timeout_millis
+ : kDefaultPingTimeoutMillis;
+ ping_options.num_concurrent_pings = 0;
+ ping_options.region = region;
+ Ping ping(ping_options);
+ Ping::Result ping_result = ping(cancel);
+ std::lock_guard<std::mutex> lock(mutex);
+ result.ping_results.push_back(ping_result);
+ });
+ }
+
+ for (std::thread &thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ const Ping::Result *fastest = nullptr;
+ for (const Ping::Result &ping_result : result.ping_results) {
+ if (ping_result.received > 0) {
+ if (!fastest) {
+ fastest = &ping_result;
+ } else if (ping_result.min_ping_micros < fastest->min_ping_micros) {
+ fastest = &ping_result;
+ }
+ }
+ }
+
+ if (!fastest) {
+ result.status = Status(StatusCode::UNAVAILABLE,
+ "All pings failed for find nearest");
+ } else {
+ result.selected_region = fastest->region;
+ result.min_ping_micros = fastest->min_ping_micros;
+ result.status = Status::OK;
+ }
+ result.end_time = SystemTimeMicros();
+ return result;
+}
+
+} // namespace speedtest
diff --git a/speedtest/find_nearest.h b/speedtest/find_nearest.h
new file mode 100644
index 0000000..65ae3bf
--- /dev/null
+++ b/speedtest/find_nearest.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_FIND_NEAREST_H
+#define SPEEDTEST_FIND_NEAREST_H
+
+#include <vector>
+#include "ping.h"
+#include "region.h"
+#include "request.h"
+#include "status.h"
+#include "utils.h"
+
+namespace speedtest {
+
+class FindNearest {
+ public:
+ struct Options {
+ bool verbose;
+ http::Request::Factory request_factory;
+ std::vector<Region> regions;
+ long ping_timeout_millis;
+ };
+
+ struct Result {
+ long start_time;
+ long end_time;
+ std::vector<Ping::Result> ping_results;
+ Status status;
+ Region selected_region;
+ long min_ping_micros;
+ };
+
+ explicit FindNearest(const Options &options);
+
+ Result operator()(std::atomic_bool *cancel);
+
+ long start_time() const { return start_time_; }
+ long end_time() const { return end_time_; }
+
+ private:
+ Options options_;
+ std::atomic_long start_time_;
+ std::atomic_long end_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(FindNearest);
+};
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_FIND_NEAREST_H
diff --git a/speedtest/http_task.cc b/speedtest/http_task.cc
deleted file mode 100644
index 1275aa4..0000000
--- a/speedtest/http_task.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "http_task.h"
-
-namespace speedtest {
-
-HttpTask::HttpTask(const Options &options): Task(options) {
-}
-
-} // namespace speedtest
diff --git a/speedtest/http_task.h b/speedtest/http_task.h
deleted file mode 100644
index a54e4ba..0000000
--- a/speedtest/http_task.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SPEEDTEST_HTTP_TASK_H
-#define SPEEDTEST_HTTP_TASK_H
-
-#include "task.h"
-
-#include "request.h"
-
-namespace speedtest {
-
-class HttpTask : public Task {
- public:
- struct Options : Task::Options {
- bool verbose = false;
- std::function<http::Request::Ptr(int)> request_factory;
- };
-
- explicit HttpTask(const Options &options);
-
- private:
- // disallowed
- HttpTask(const Task &) = delete;
- void operator=(const HttpTask &) = delete;
-};
-
-} // namespace speedtest
-
-#endif // SPEEDTEST_HTTP_TASK_H
diff --git a/speedtest/init.cc b/speedtest/init.cc
new file mode 100644
index 0000000..39b646b
--- /dev/null
+++ b/speedtest/init.cc
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "init.h"
+
+#include "timed_runner.h"
+
+namespace speedtest {
+
+Init::Init(const Options &options)
+ : options_(options) {
+}
+
+Init::Result Init::operator()(std::atomic_bool *cancel) {
+ Init::Result result;
+ result.start_time = SystemTimeMicros();
+
+ if (!cancel) {
+ result.status = Status(StatusCode::FAILED_PRECONDITION, "cancel is null");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "init aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ RegionOptions region_options;
+ region_options.verbose = options_.verbose;
+ region_options.request_factory = options_.request_factory;
+ region_options.global = options_.global;
+ region_options.global_url = options_.global_url;
+ region_options.regional_urls = options_.regional_urls;
+ result.region_result = LoadRegions(region_options);
+ if (!result.region_result.status.ok()) {
+ result.status = result.region_result.status;
+ result.end_time = SystemTimeMicros();
+ if (options_.verbose) {
+ std::cout << "Load regions failed: " << result.status.ToString() << "\n";
+ }
+ return result;
+ }
+ if (options_.verbose) {
+ std::cout << "Load regions succeeded:\n";
+ for (const Region ®ion : result.region_result.regions) {
+ std::cout << " " << DescribeRegion(region) << "\n";
+ }
+ }
+
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "init aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ FindNearest::Options find_options;
+ find_options.verbose = options_.verbose;
+ find_options.request_factory = options_.request_factory;
+ find_options.ping_timeout_millis = options_.ping_timeout_millis;
+ find_options.regions = result.region_result.regions;
+ FindNearest find_nearest(find_options);
+ result.find_nearest_result = RunTimed(std::ref(find_nearest), cancel, 2000);
+ if (!result.find_nearest_result.status.ok()) {
+ result.status = result.find_nearest_result.status;
+ result.end_time = SystemTimeMicros();
+ if (options_.verbose) {
+ std::cout << "Find nearest failed: " << result.status.ToString() << "\n";
+ }
+ return result;
+ }
+ result.selected_region = result.find_nearest_result.selected_region;
+
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "init aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ ConfigOptions config_options;
+ config_options.verbose = options_.verbose;
+ config_options.request_factory = options_.request_factory;
+ config_options.region_url = result.selected_region.urls.front();
+ result.config_result = LoadConfig(config_options);
+ if (!result.config_result.status.ok()) {
+ result.status = result.config_result.status;
+ if (options_.verbose) {
+ std::cout << "Load config failed: " << result.status.ToString() << "\n";
+ }
+ } else {
+ result.status = Status::OK;
+ if (result.selected_region.id.empty()) {
+ result.selected_region.id = result.config_result.config.location_id;
+ }
+ if (result.selected_region.name.empty()) {
+ result.selected_region.name = result.config_result.config.location_name;
+ }
+ }
+
+ result.end_time = SystemTimeMicros();
+ return result;
+}
+
+} // namespace speedtest
diff --git a/speedtest/init.h b/speedtest/init.h
new file mode 100644
index 0000000..5c69120
--- /dev/null
+++ b/speedtest/init.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_INIT_H
+#define SPEEDTEST_INIT_H
+
+#include <atomic>
+#include <vector>
+#include "config.h"
+#include "find_nearest.h"
+#include "region.h"
+#include "request.h"
+#include "status.h"
+#include "url.h"
+#include "utils.h"
+
+namespace speedtest {
+
+class Init {
+ public:
+ struct Options {
+ bool verbose;
+ http::Request::Factory request_factory;
+ bool global;
+ http::Url global_url;
+ std::vector<http::Url> regional_urls;
+ long ping_timeout_millis;
+ };
+
+ struct Result {
+ long start_time;
+ long end_time;
+ Status status;
+ RegionResult region_result;
+ FindNearest::Result find_nearest_result;
+ Region selected_region;
+ ConfigResult config_result;
+ };
+
+ explicit Init(const Options &options);
+
+ Result operator()(std::atomic_bool *cancel);
+
+ private:
+ Options options_;
+
+ DISALLOW_COPY_AND_ASSIGN(Init);
+};
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_INIT_H
diff --git a/speedtest/options.cc b/speedtest/options.cc
index 133b857..f657b0c 100644
--- a/speedtest/options.cc
+++ b/speedtest/options.cc
@@ -24,9 +24,12 @@
#include "url.h"
namespace speedtest {
+namespace {
const char* kDefaultHost = "any.speed.gfsvc.com";
+} // namespace
+
namespace {
bool ParseLong(const char *s, char **endptr, long *number) {
@@ -69,7 +72,11 @@
const int kOptDisableDnsCache = 1000;
const int kOptMaxConnections = 1001;
-const int kOptExponentialMovingAverage = 1002;
+const int kOptReportResults = 1002;
+const int kOptSkipDownload = 1003;
+const int kOptSkipUpload = 1004;
+const int kOptSkipPing = 1005;
+const int kOptNoReportResults = 1006;
const int kOptMinTransferTime = 1100;
const int kOptMaxTransferTime = 1101;
@@ -79,19 +86,23 @@
const int kOptIntervalMillis = 1105;
const int kOptPingRuntime = 1106;
const int kOptPingTimeout = 1107;
+const int kOptExponentialMovingAverage = 1108;
const char *kShortOpts = "hvg:a:d:s:t:u:p:";
struct option kLongOpts[] = {
{"help", no_argument, nullptr, 'h'},
{"verbose", no_argument, nullptr, 'v'},
- {"global_host", required_argument, nullptr, 'g'},
+ {"global_url", required_argument, nullptr, 'g'},
{"user_agent", required_argument, nullptr, 'a'},
{"disable_dns_cache", no_argument, nullptr, kOptDisableDnsCache},
{"max_connections", required_argument, nullptr, kOptMaxConnections},
{"progress_millis", required_argument, nullptr, 'p'},
- {"exponential_moving_average", no_argument, nullptr,
- kOptExponentialMovingAverage},
+ {"skip_download", no_argument, nullptr, kOptSkipDownload},
+ {"skip_upload", no_argument, nullptr, kOptSkipUpload},
+ {"skip_ping", no_argument, nullptr, kOptSkipPing},
+ {"report_results", no_argument, nullptr, kOptReportResults},
+ {"noreport_results", no_argument, nullptr, kOptNoReportResults},
{"num_downloads", required_argument, nullptr, 'd'},
{"download_size", required_argument, nullptr, 's'},
@@ -108,6 +119,8 @@
{"interval_millis", required_argument, nullptr, kOptIntervalMillis},
{"ping_runtime", required_argument, nullptr, kOptPingRuntime},
{"ping_timeout", required_argument, nullptr, kOptPingTimeout},
+ {"exponential_moving_average", no_argument, nullptr,
+ kOptExponentialMovingAverage},
{nullptr, 0, nullptr, 0},
};
const int kMaxNumber = 1000;
@@ -124,12 +137,15 @@
Usage: speedtest [options] [host ...]
-h, --help This help text
-v, --verbose Verbose output
- -g, --global_host URL Global host URL
+ -g, --global_url URL Global host URL
-a, --user_agent AGENT User agent string for HTTP requests
-p, --progress_millis NUM Delay in milliseconds between updates
--disable_dns_cache Disable global DNS cache
--max_connections NUM Maximum number of parallel connections
- --exponential_moving_average Use exponential instead of simple moving average
+ --skip_download Skip the download test
+ --skip_upload Skip the upload test
+ --skip_ping Skip the ping test
+ --[no]report_results Whether to report Speedtest results to server
These options override the speedtest config parameters:
-d, --num_downloads NUM Number of simultaneous downloads
@@ -144,6 +160,7 @@
--interval_millis TIME Interval size in milliseconds
--ping_runtime TIME Ping runtime in milliseconds
--ping_timeout TIME Ping timeout in milliseconds
+ --exponential_moving_average Use exponential instead of simple moving average
)USAGE";
} // namespace
@@ -152,30 +169,34 @@
assert(options != nullptr);
options->usage = false;
options->verbose = false;
- options->global_host = http::Url(kDefaultHost);
+ options->global_url = http::Url(kDefaultHost);
options->global = false;
options->user_agent = "";
options->progress_millis = 0;
options->disable_dns_cache = false;
options->max_connections = 0;
options->exponential_moving_average = false;
+ options->skip_download = false;
+ options->skip_upload = false;
+ options->skip_ping = false;
+ options->report_results = true;
options->num_downloads = 0;
- options->download_size = 0;
+ options->download_bytes = 0;
options->num_uploads = 0;
- options->upload_size = 0;
+ options->upload_bytes = 0;
options->min_transfer_runtime = 0;
options->max_transfer_runtime = 0;
options->min_transfer_intervals = 0;
options->max_transfer_intervals = 0;
options->max_transfer_variance = 0.0;
options->interval_millis = 0;
- options->ping_runtime = 0;
- options->ping_timeout = 0;
+ options->ping_runtime_millis = 0;
+ options->ping_timeout_millis = 0;
- options->hosts.clear();
+ options->regional_urls.clear();
- if (!options->global_host.ok()) {
+ if (!options->global_url.ok()) {
std::cerr << "Invalid global host " << kDefaultHost << "\n";
return false;
}
@@ -210,7 +231,7 @@
std::cerr << "Invalid global host " << optarg << "\n";
return false;
}
- options->global_host = url;
+ options->global_url = url;
break;
}
case 'h':
@@ -228,17 +249,17 @@
<< ", got '" << optarg << "'\n";
return false;
}
- options->progress_millis = static_cast<int>(progress);
+ options->progress_millis = progress;
break;
}
case 's':
- if (!ParseSize(optarg, &options->download_size)) {
+ if (!ParseSize(optarg, &options->download_bytes)) {
std::cerr << "Invalid download size '" << optarg << "'\n";
return false;
}
break;
case 't':
- if (!ParseSize(optarg, &options->upload_size)) {
+ if (!ParseSize(optarg, &options->upload_bytes)) {
std::cerr << "Invalid upload size '" << optarg << "'\n";
return false;
}
@@ -279,8 +300,20 @@
options->max_connections = static_cast<int>(max_connections);
break;
}
- case kOptExponentialMovingAverage:
- options->exponential_moving_average = true;
+ case kOptReportResults:
+ options->report_results = true;
+ break;
+ case kOptSkipDownload:
+ options->skip_download = true;
+ break;
+ case kOptSkipUpload:
+ options->skip_upload = true;
+ break;
+ case kOptSkipPing:
+ options->skip_ping = true;
+ break;
+ case kOptNoReportResults:
+ options->report_results = false;
break;
case kOptMinTransferTime: {
long transfer_time;
@@ -295,7 +328,7 @@
<< optarg << "'\n";
return false;
}
- options->min_transfer_runtime = static_cast<int>(transfer_time);
+ options->min_transfer_runtime = transfer_time;
break;
}
case kOptMaxTransferTime: {
@@ -311,7 +344,7 @@
<< optarg << "'\n";
return false;
}
- options->max_transfer_runtime = static_cast<int>(transfer_time);
+ options->max_transfer_runtime = transfer_time;
break;
}
case kOptMinTransferIntervals: {
@@ -372,7 +405,7 @@
<< optarg << "'\n";
return false;
}
- options->interval_millis = static_cast<int>(interval_millis);
+ options->interval_millis = interval_millis;
break;
}
case kOptPingRuntime: {
@@ -387,7 +420,7 @@
<< optarg << "'\n";
return false;
}
- options->ping_runtime = static_cast<int>(ping_runtime);
+ options->ping_runtime_millis = ping_runtime;
break;
}
case kOptPingTimeout: {
@@ -402,9 +435,12 @@
<< optarg << "'\n";
return false;
}
- options->ping_timeout = static_cast<int>(ping_timeout);
+ options->ping_timeout_millis = ping_timeout;
break;
}
+ case kOptExponentialMovingAverage:
+ options->exponential_moving_average = true;
+ break;
default:
return false;
}
@@ -423,10 +459,10 @@
url.clear_path();
url.clear_query_string();
url.clear_fragment();
- options->hosts.emplace_back(url);
+ options->regional_urls.emplace_back(url);
}
}
- if (options->hosts.empty()) {
+ if (options->regional_urls.empty()) {
options->global = true;
}
return true;
@@ -439,29 +475,37 @@
void PrintOptions(std::ostream &out, const Options &options) {
out << "Usage: " << (options.usage ? "true" : "false") << "\n"
<< "Verbose: " << (options.verbose ? "true" : "false") << "\n"
- << "Global host: " << options.global_host.url() << "\n"
+ << "Global host: " << options.global_url.url() << "\n"
<< "Global: " << (options.global ? "true" : "false") << "\n"
<< "User agent: " << options.user_agent << "\n"
<< "Progress interval: " << options.progress_millis << " ms\n"
<< "Disable DNS cache: "
<< (options.disable_dns_cache ? "true" : "false") << "\n"
<< "Max connections: " << options.max_connections << "\n"
- << "Exponential moving average: "
- << (options.exponential_moving_average ? "true" : "false") << "\n"
+ << "Skip download: "
+ << (options.skip_download ? "true" : "false") << "\n"
+ << "Skip upload: "
+ << (options.skip_upload ? "true" : "false") << "\n"
+ << "Skip ping: "
+ << (options.skip_ping ? "true" : "false") << "\n"
+ << "Report results: "
+ << (options.report_results ? "true" : "false") << "\n"
<< "Number of downloads: " << options.num_downloads << "\n"
- << "Download size: " << options.download_size << " bytes\n"
+ << "Download size: " << options.download_bytes << " bytes\n"
<< "Number of uploads: " << options.num_uploads << "\n"
- << "Upload size: " << options.upload_size << " bytes\n"
+ << "Upload size: " << options.upload_bytes << " bytes\n"
<< "Min transfer runtime: " << options.min_transfer_runtime << " ms\n"
<< "Max transfer runtime: " << options.max_transfer_runtime << " ms\n"
<< "Min transfer intervals: " << options.min_transfer_intervals << "\n"
<< "Max transfer intervals: " << options.max_transfer_intervals << "\n"
<< "Max transfer variance: " << options.max_transfer_variance << "\n"
<< "Interval size: " << options.interval_millis << " ms\n"
- << "Ping runtime: " << options.ping_runtime << " ms\n"
- << "Ping timeout: " << options.ping_timeout << " ms\n"
+ << "Ping runtime: " << options.ping_runtime_millis << " ms\n"
+ << "Ping timeout: " << options.ping_timeout_millis << " ms\n"
+ << "Exponential moving average: "
+ << (options.exponential_moving_average ? "true" : "false") << "\n"
<< "Hosts:\n";
- for (const http::Url &host : options.hosts) {
+ for (const http::Url &host : options.regional_urls) {
out << " " << host.url() << "\n";
}
}
diff --git a/speedtest/options.h b/speedtest/options.h
index 9028f70..090c236 100644
--- a/speedtest/options.h
+++ b/speedtest/options.h
@@ -20,38 +20,43 @@
#include <iostream>
#include <string>
#include <vector>
+#include "request.h"
#include "url.h"
namespace speedtest {
-extern const char* kDefaultHost;
-
struct Options {
+ bool verbose;
+ http::Request::Factory request_factory;
+
bool usage = false;
- bool verbose = false;
- http::Url global_host;
+ http::Url global_url;
bool global = false;
std::string user_agent;
bool disable_dns_cache = false;
int max_connections = 0;
int progress_millis = 0;
- bool exponential_moving_average = false;
+ bool skip_download = false;
+ bool skip_upload = false;
+ bool skip_ping = false;
+ bool report_results = true;
// A value of 0 means use the speedtest config parameters
int num_downloads = 0;
- long download_size = 0;
+ long download_bytes = 0;
int num_uploads = 0;
- long upload_size = 0;
- int min_transfer_runtime = 0;
- int max_transfer_runtime = 0;
+ long upload_bytes = 0;
+ long min_transfer_runtime = 0;
+ long max_transfer_runtime = 0;
int min_transfer_intervals = 0;
int max_transfer_intervals = 0;
double max_transfer_variance = 0.0;
- int interval_millis = 0;
- int ping_runtime = 0;
- int ping_timeout = 0;
+ long interval_millis = 0;
+ long ping_runtime_millis = 0;
+ long ping_timeout_millis = 0;
+ bool exponential_moving_average = false;
- std::vector<http::Url> hosts;
+ std::vector<http::Url> regional_urls;
};
// Parse command line options putting results into 'options'
diff --git a/speedtest/options_test.cc b/speedtest/options_test.cc
index 601984a..67ef986 100644
--- a/speedtest/options_test.cc
+++ b/speedtest/options_test.cc
@@ -86,25 +86,29 @@
EXPECT_FALSE(options.usage);
EXPECT_FALSE(options.verbose);
EXPECT_TRUE(options.global);
- EXPECT_EQ(http::Url("any.speed.gfsvc.com"), options.global_host);
+ EXPECT_EQ(http::Url("any.speed.gfsvc.com"), options.global_url);
EXPECT_FALSE(options.disable_dns_cache);
EXPECT_EQ(0, options.max_connections);
EXPECT_EQ(0, options.progress_millis);
- EXPECT_FALSE(options.exponential_moving_average);
+ EXPECT_FALSE(options.skip_download);
+ EXPECT_FALSE(options.skip_upload);
+ EXPECT_FALSE(options.skip_ping);
+ EXPECT_TRUE(options.report_results);
EXPECT_EQ(0, options.num_downloads);
- EXPECT_EQ(0, options.download_size);
+ EXPECT_EQ(0, options.download_bytes);
EXPECT_EQ(0, options.num_uploads);
- EXPECT_EQ(0, options.upload_size);
+ EXPECT_EQ(0, options.upload_bytes);
EXPECT_EQ(0, options.min_transfer_runtime);
EXPECT_EQ(0, options.max_transfer_runtime);
EXPECT_EQ(0, options.min_transfer_intervals);
EXPECT_EQ(0, options.max_transfer_intervals);
EXPECT_EQ(0, options.max_transfer_variance);
EXPECT_EQ(0, options.interval_millis);
- EXPECT_EQ(0, options.ping_runtime);
- EXPECT_EQ(0, options.ping_timeout);
- EXPECT_THAT(options.hosts, testing::IsEmpty());
+ EXPECT_EQ(0, options.ping_runtime_millis);
+ EXPECT_EQ(0, options.ping_timeout_millis);
+ EXPECT_THAT(options.regional_urls, testing::IsEmpty());
+ EXPECT_FALSE(options.exponential_moving_average);
}
TEST(OptionsTest, Usage_Valid) {
@@ -123,7 +127,7 @@
TEST(OptionsTest, OneHost_Valid) {
Options options;
TestValidOptions({"efgh"}, &options);
- EXPECT_THAT(options.hosts, testing::ElementsAre(http::Url("efgh")));
+ EXPECT_THAT(options.regional_urls, testing::ElementsAre(http::Url("efgh")));
}
TEST(OptionsTest, ShortOptions_Valid) {
@@ -141,12 +145,12 @@
&options);
EXPECT_TRUE(options.verbose);
EXPECT_EQ(20, options.num_downloads);
- EXPECT_EQ(5122, options.download_size);
+ EXPECT_EQ(5122, options.download_bytes);
EXPECT_EQ(15, options.num_uploads);
- EXPECT_EQ(7653, options.upload_size);
+ EXPECT_EQ(7653, options.upload_bytes);
EXPECT_EQ(500, options.progress_millis);
EXPECT_FALSE(options.global);
- EXPECT_EQ(http::Url("speed.gfsvc.com"), options.global_host);
+ EXPECT_EQ(http::Url("speed.gfsvc.com"), options.global_url);
EXPECT_EQ("CrOS", options.user_agent);
EXPECT_EQ(0, options.max_connections);
@@ -158,10 +162,10 @@
EXPECT_EQ(0, options.max_transfer_intervals);
EXPECT_EQ(0, options.max_transfer_variance);
EXPECT_EQ(0, options.interval_millis);
- EXPECT_EQ(0, options.ping_runtime);
- EXPECT_EQ(0, options.ping_timeout);
+ EXPECT_EQ(0, options.ping_runtime_millis);
+ EXPECT_EQ(0, options.ping_timeout_millis);
- EXPECT_THAT(options.hosts, testing::UnorderedElementsAre(
+ EXPECT_THAT(options.regional_urls, testing::UnorderedElementsAre(
http::Url("foo.speed.googlefiber.net"),
http::Url("bar.speed.googlefiber.net")));
}
@@ -169,12 +173,15 @@
TEST(OptionsTest, LongOptions_Valid) {
Options options;
TestValidOptions({"--verbose",
- "--global_host", "speed.gfsvc.com",
+ "--global_url", "speed.gfsvc.com",
"--user_agent", "CrOS",
"--progress_millis", "1000",
"--disable_dns_cache",
"--max_connections", "23",
- "--exponential_moving_average",
+ "--noreport_results",
+ "--skip_download",
+ "--skip_upload",
+ "--skip_ping",
"--num_downloads", "16",
"--download_size", "5122",
"--num_uploads", "12",
@@ -187,21 +194,25 @@
"--interval_millis", "250",
"--ping_runtime", "2500",
"--ping_timeout", "300",
+ "--exponential_moving_average",
"foo.speed.googlefiber.net",
"bar.speed.googlefiber.net"},
&options);
EXPECT_TRUE(options.verbose);
EXPECT_FALSE(options.global);
- EXPECT_EQ(http::Url("speed.gfsvc.com"), options.global_host);
+ EXPECT_EQ(http::Url("speed.gfsvc.com"), options.global_url);
EXPECT_EQ("CrOS", options.user_agent);
EXPECT_EQ(1000, options.progress_millis);
EXPECT_TRUE(options.disable_dns_cache);
EXPECT_EQ(23, options.max_connections);
- EXPECT_TRUE(options.exponential_moving_average);
+ EXPECT_TRUE(options.skip_download);
+ EXPECT_TRUE(options.skip_upload);
+ EXPECT_TRUE(options.skip_ping);
+ EXPECT_FALSE(options.report_results);
EXPECT_EQ(16, options.num_downloads);
- EXPECT_EQ(5122, options.download_size);
+ EXPECT_EQ(5122, options.download_bytes);
EXPECT_EQ(12, options.num_uploads);
- EXPECT_EQ(7653, options.upload_size);
+ EXPECT_EQ(7653, options.upload_bytes);
EXPECT_EQ("CrOS", options.user_agent);
EXPECT_EQ(7500, options.min_transfer_runtime);
EXPECT_EQ(13500, options.max_transfer_runtime);
@@ -209,9 +220,10 @@
EXPECT_EQ(22, options.max_transfer_intervals);
EXPECT_EQ(0.12, options.max_transfer_variance);
EXPECT_EQ(250, options.interval_millis);
- EXPECT_EQ(2500, options.ping_runtime);
- EXPECT_EQ(300, options.ping_timeout);
- EXPECT_THAT(options.hosts, testing::UnorderedElementsAre(
+ EXPECT_EQ(2500, options.ping_runtime_millis);
+ EXPECT_EQ(300, options.ping_timeout_millis);
+ EXPECT_TRUE(options.exponential_moving_average);
+ EXPECT_THAT(options.regional_urls, testing::UnorderedElementsAre(
http::Url("foo.speed.googlefiber.net"),
http::Url("bar.speed.googlefiber.net")));
}
diff --git a/speedtest/ping.cc b/speedtest/ping.cc
new file mode 100644
index 0000000..0a61661
--- /dev/null
+++ b/speedtest/ping.cc
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ping.h"
+
+#include <curl/curl.h>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <thread>
+#include <vector>
+#include "errors.h"
+#include "url.h"
+
+namespace speedtest {
+
+Ping::Ping(const Options &options)
+ : options_(options),
+ start_time_(0),
+ end_time_(0),
+ pings_received_(0),
+ min_ping_micros_(std::numeric_limits<long>::max()) {
+}
+
+Ping::Result Ping::operator()(std::atomic_bool *cancel) {
+ start_time_ = SystemTimeMicros();
+
+ if (!cancel) {
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status(StatusCode::FAILED_PRECONDITION, "cancel is null"));
+ }
+
+ if (!options_.request_factory) {
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status(StatusCode::INVALID_ARGUMENT,
+ "request factory not set"));
+ }
+
+ if (options_.region.urls.empty()) {
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status(StatusCode::INVALID_ARGUMENT, "region URLs empty"));
+ }
+
+ std::vector<std::thread> threads;
+ min_ping_micros_ = std::numeric_limits<long>::max();
+ pings_received_ = 0;
+ int num_pings = options_.num_concurrent_pings > 0
+ ? options_.num_concurrent_pings
+ : options_.region.urls.size();
+ for (int index = 0; index < num_pings; ++index) {
+ threads.emplace_back([&]{
+ size_t url_index = index % options_.region.urls.size();
+ http::Url url(options_.region.urls[url_index]);
+ url.set_path("/ping");
+ http::Request::Ptr ping = options_.request_factory(url);
+ while (!*cancel) {
+ ping->add_param("i", to_string(index + 1));
+ ping->add_param("time", to_string(SystemTimeMicros()));
+ ping->UpdateUrl();
+ if (options_.timeout_millis > 0) {
+ ping->set_timeout_millis(options_.timeout_millis);
+ }
+ long req_start = SystemTimeMicros();
+ CURLcode curl_code = ping->Get();
+ if (curl_code == CURLE_OK) {
+ long req_end = SystemTimeMicros();
+ long ping_time = req_end - req_start;
+ pings_received_++;
+ std::lock_guard<std::mutex> lock(mutex_);
+ min_ping_micros_ = std::min(min_ping_micros_, ping_time);
+ } else if (options_.verbose) {
+ std::cout << "Ping " << ping->url().url() << " failed: "
+ << http::ErrorString(curl_code) << "\n";
+ }
+ ping->Reset();
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ });
+ }
+
+ for (std::thread &thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status::OK);
+}
+
+long Ping::min_ping_micros() const {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return min_ping_micros_;
+}
+
+Ping::Result Ping::GetResult(Status status) const {
+ Ping::Result result;
+ result.start_time = start_time_;
+ result.end_time = end_time_;
+ result.status = status;
+ result.region = options_.region;
+ result.min_ping_micros = min_ping_micros();
+ result.received = pings_received_;
+ return result;
+}
+
+} // namespace speedtest
diff --git a/speedtest/ping.h b/speedtest/ping.h
new file mode 100644
index 0000000..f97e184
--- /dev/null
+++ b/speedtest/ping.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_PING_H
+#define SPEEDTEST_PING_H
+
+#include <atomic>
+#include <mutex>
+#include <string>
+#include "region.h"
+#include "request.h"
+#include "status.h"
+#include "utils.h"
+
+namespace speedtest {
+
+class Ping {
+ public:
+ struct Options {
+ bool verbose;
+ http::Request::Factory request_factory;
+ long timeout_millis;
+ long num_concurrent_pings;
+ Region region;
+ };
+
+ struct Result {
+ long start_time;
+ long end_time;
+ Status status;
+ Region region;
+ long min_ping_micros;
+ int received;
+ };
+
+ explicit Ping(const Options &options);
+
+ Result operator()(std::atomic_bool *cancel);
+
+ long start_time() const { return start_time_; }
+ long end_time() const { return end_time_; }
+ long min_ping_micros() const;
+
+ private:
+ Result GetResult(Status status) const;
+
+ Options options_;
+ std::atomic_long start_time_;
+ std::atomic_long end_time_;
+ std::atomic_int pings_received_;
+
+ mutable std::mutex mutex_;
+ long min_ping_micros_;
+
+ DISALLOW_COPY_AND_ASSIGN(Ping);
+};
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_PING_H
diff --git a/speedtest/ping_task.cc b/speedtest/ping_task.cc
deleted file mode 100644
index 7a1c7be..0000000
--- a/speedtest/ping_task.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ping_task.h"
-
-#include <algorithm>
-#include <cassert>
-#include <iomanip>
-#include <iostream>
-#include "utils.h"
-
-namespace speedtest {
-
-PingTask::PingTask(const Options &options)
- : HttpTask(options),
- options_(options) {
- assert(options_.num_pings > 0);
-}
-
-void PingTask::RunInternal() {
- ResetCounters();
- success_ = false;
- threads_.clear();
- for (int i = 0; i < options_.num_pings; ++i) {
- threads_.emplace_back([=]() {
- RunPing(i);
- });
- }
-}
-
-void PingTask::StopInternal() {
- std::for_each(threads_.begin(), threads_.end(), [](std::thread &t) {
- t.join();
- });
- threads_.clear();
- if (options_.verbose) {
- std::cout << "Pinged " << options_.num_pings << " "
- << (options_.num_pings == 1 ? "host" : "hosts") << ":\n";
- }
- const PingStats *min_stats = nullptr;
- for (const auto &stat : stats_) {
- if (options_.verbose) {
- std::cout << " " << stat.url.url() << ": ";
- if (stat.pings_received == 0) {
- std::cout << "no packets received";
- } else {
- double mean_micros = ((double) stat.total_micros) / stat.pings_received;
- std::cout << "min " << round(stat.min_micros / 1000.0d, 2) << " ms"
- << " from " << stat.pings_received << " pings"
- << " (mean " << round(mean_micros / 1000.0d, 2) << " ms)";
- }
- std::cout << "\n";
- }
- if (stat.pings_received > 0) {
- if (!min_stats || stat.min_micros < min_stats->min_micros) {
- min_stats = &stat;
- }
- }
- }
-
- std::lock_guard<std::mutex> lock(mutex_);
- if (!min_stats) {
- // no servers respondeded
- success_ = false;
- } else {
- fastest_ = *min_stats;
- success_ = true;
- }
-}
-
-void PingTask::RunPing(size_t index) {
- http::Request::Ptr ping = options_.request_factory(index);
- stats_[index].url = ping->url();
- while (GetStatus() == TaskStatus::RUNNING) {
- long req_start = SystemTimeMicros();
- if (ping->Get() == CURLE_OK) {
- long req_end = SystemTimeMicros();
- long ping_time = req_end - req_start;
- stats_[index].total_micros += ping_time;
- stats_[index].pings_received++;
- stats_[index].min_micros = std::min(stats_[index].min_micros, ping_time);
- }
- ping->Reset();
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
-}
-
-bool PingTask::IsSucceeded() const {
- return success_;
-}
-
-PingStats PingTask::GetFastest() const {
- std::lock_guard<std::mutex> lock(mutex_);
- return fastest_;
-}
-
-void PingTask::ResetCounters() {
- stats_.clear();
- stats_.resize(options_.num_pings);
-}
-
-} // namespace speedtest
diff --git a/speedtest/ping_task.h b/speedtest/ping_task.h
deleted file mode 100644
index b2923a8..0000000
--- a/speedtest/ping_task.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SPEEDTEST_PING_TASK_H
-#define SPEEDTEST_PING_TASK_H
-
-#include <atomic>
-#include <functional>
-#include <limits>
-#include <memory>
-#include <mutex>
-#include <thread>
-#include <vector>
-#include "http_task.h"
-#include "request.h"
-#include "url.h"
-
-namespace speedtest {
-
-struct PingStats {
- long total_micros = 0;
- int pings_received = 0;
- long min_micros = std::numeric_limits<long>::max();
- http::Url url;
-};
-
-class PingTask : public HttpTask {
- public:
- struct Options : HttpTask::Options {
- int timeout = 0;
- int num_pings = 0;
- };
-
- explicit PingTask(const Options &options);
-
- bool IsSucceeded() const;
-
- PingStats GetFastest() const;
-
- protected:
- void RunInternal() override;
- void StopInternal() override;
-
- private:
- void RunPing(size_t index);
-
- void ResetCounters();
-
- Options options_;
- std::vector<PingStats> stats_;
- std::vector<std::thread> threads_;
- std::atomic_bool success_;
-
- mutable std::mutex mutex_;
- PingStats fastest_;
-
- // disallowed
- PingTask(const PingTask &) = delete;
- void operator=(const PingTask &) = delete;
-};
-
-} // namespace speedtest
-
-#endif // SPEEDTEST_PING_TASK_H
diff --git a/speedtest/region.cc b/speedtest/region.cc
new file mode 100644
index 0000000..7e9e05a
--- /dev/null
+++ b/speedtest/region.cc
@@ -0,0 +1,154 @@
+#include "region.h"
+
+#include <curl/curl.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+// For some reason, the libjsoncpp package installs to /usr/include/jsoncpp/json
+// instead of /usr{,/local}/include/json
+#include <jsoncpp/json/json.h>
+
+#include "errors.h"
+
+namespace speedtest {
+namespace {
+
+bool AddUrl(const Json::Value &url_json, std::vector<http::Url> *urls) {
+ if (!url_json.isString()) {
+ return false;
+ }
+ http::Url url = http::Url(url_json.asString());
+ if (!url.ok()) {
+ return false;
+ }
+ urls->push_back(url);
+ return true;
+}
+
+} // namesapce
+
+std::string DescribeRegion(const Region ®ion) {
+ if (region.id.empty() && region.name.empty()) {
+ return region.urls.front().url();
+ }
+ if (region.id.empty()) {
+ return region.name;
+ }
+ if (region.name.empty()) {
+ return region.id;
+ }
+ std::stringstream ss;
+ ss << region.name << " (" << region.id << ")";
+ return ss.str();
+}
+
+RegionResult LoadRegions(RegionOptions options) {
+ RegionResult result;
+ result.start_time = SystemTimeMicros();
+ if (!options.request_factory) {
+ result.status = Status(StatusCode::INVALID_ARGUMENT,
+ "request factory not set");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ if (!options.global) {
+ if (options.verbose) {
+ std::cout << "Explicit server list:\n";
+ for (const auto &url : options.regional_urls) {
+ std::cout << " " << url.url() << "\n";
+ }
+ }
+ for (const http::Url &url : options.regional_urls) {
+ Region region;
+ region.urls.emplace_back(url.url());
+ result.regions.emplace_back(region);
+ }
+ result.status = Status::OK;
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ http::Url config_url(options.global_url);
+ config_url.set_path("/config");
+ if (options.verbose) {
+ std::cout << "Loading regions from " << config_url.url() << "\n";
+ }
+ http::Request::Ptr request = options.request_factory(config_url);
+ request->set_url(config_url);
+ request->set_timeout_millis(500);
+ std::string json;
+ CURLcode code = request->Get([&](void *data, size_t size){
+ json.assign(static_cast<const char *>(data), size);
+ });
+ if (code != CURLE_OK) {
+ result.status = Status(StatusCode::INTERNAL, http::ErrorString(code));
+ } else {
+ result.status = ParseRegions(json, &result.regions);
+ }
+ result.end_time = SystemTimeMicros();
+ return result;
+}
+
+Status ParseRegions(const std::string &json, std::vector<Region> *regions) {
+ if (!regions) {
+ return Status(StatusCode::FAILED_PRECONDITION, "Regions is null");
+ }
+
+ Json::Reader reader;
+ Json::Value root;
+ if (!reader.parse(json, root, false)) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Failed to parse regions JSON");
+ }
+
+ if (!root.isMember("regions") || !root["regions"].isArray()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "no regions element found");
+ }
+ for (const auto &it : root["regions"]) {
+ Region region;
+
+ if (!it.isMember("id")) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Region missing id");
+ }
+ if (!it["id"].isString()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Region id not a string");
+ }
+ region.id = it["id"].asString();
+
+ if (it.isMember("name")) {
+ if (!it["name"].isString()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Region name not a string");
+ }
+ region.name = it["name"].asString();
+ }
+
+ if (!it.isMember("url")) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Region URL missing");
+ }
+ if (it["url"].isString()) {
+ if (!AddUrl(it["url"], ®ion.urls)) {
+ return Status(StatusCode::INVALID_ARGUMENT,
+ "Failed to parse region URL");
+ }
+ } else if (it["url"].isArray()) {
+ for (const auto &url_it : it["url"]) {
+ if (!AddUrl(url_it, ®ion.urls)) {
+ return Status(StatusCode::INVALID_ARGUMENT,
+ "Failed to parse region URL");
+ }
+ }
+ if (region.urls.empty()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Region missing URLs");
+ }
+ } else {
+ return Status(StatusCode::INVALID_ARGUMENT,
+ "Region URL not string or array");
+ }
+
+ regions->emplace_back(region);
+ }
+ return Status::OK;
+}
+
+} // namespace
diff --git a/speedtest/region.h b/speedtest/region.h
new file mode 100644
index 0000000..6b94ab1
--- /dev/null
+++ b/speedtest/region.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_REGION_H
+#define SPEEDTEST_REGION_H
+
+#include <string>
+#include <vector>
+#include "request.h"
+#include "status.h"
+#include "url.h"
+
+namespace speedtest {
+
+struct Region {
+ std::string id;
+ std::string name;
+ std::vector<http::Url> urls;
+};
+
+struct RegionOptions {
+ bool verbose;
+ http::Request::Factory request_factory;
+ bool global;
+ http::Url global_url;
+ std::vector<http::Url> regional_urls;
+};
+
+struct RegionResult {
+ long start_time;
+ long end_time;
+ Status status;
+ std::vector<Region> regions;
+};
+
+RegionResult LoadRegions(RegionOptions options);
+
+std::string DescribeRegion(const Region ®ion);
+
+// Parses a JSON document into a list of regions
+// Returns true with the regions populated in the vector on success.
+// Returns false if the JSON is invalid or regions is null.
+Status ParseRegions(const std::string &json, std::vector<Region> *regions);
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_REGION_H
diff --git a/speedtest/region_test.cc b/speedtest/region_test.cc
new file mode 100644
index 0000000..7ef5f7f
--- /dev/null
+++ b/speedtest/region_test.cc
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <string>
+#include <vector>
+#include "region.h"
+
+#define EXPECT_OK(statement) EXPECT_EQ(::speedtest::Status::OK, (statement))
+#define EXPECT_ERROR(statement) EXPECT_NE(::speedtest::Status::OK, (statement))
+
+namespace speedtest {
+namespace {
+
+const char *kValidRegions = R"REGIONS(
+{
+ "regions": [
+ {
+ "id": "aus",
+ "name": "Austin",
+ "url": "http://austin.speed.googlefiber.net/"
+ },
+ {
+ "id": "mci",
+ "name": "Kansas City",
+ "url": [
+ "http://kansas.speed.googlefiber.net/"
+ ]
+ },
+ {
+ "id": "slc",
+ "name": "Provo",
+ "url": [
+ "http://provo.speed.googlefiber.net/"
+ ]
+ },
+ {
+ "id": "sfo",
+ "name": "Stanford",
+ "url": [
+ "http://stanford.speed.googlefiber.net/"
+ ]
+ }
+ ]
+}
+)REGIONS";
+
+const char *kRegionMissingId = R"REGIONS(
+{
+ "regions": [
+ {
+ "name": "Kansas City",
+ "url": [
+ "http://kansas.speed.googlefiber.net/"
+ ]
+ },
+ ]
+}
+)REGIONS";
+
+const char *kRegionMissingName = R"REGIONS(
+{
+ "regions": [
+ {
+ "id": "mci",
+ "url": [
+ "http://kansas.speed.googlefiber.net/"
+ ]
+ },
+ ]
+}
+)REGIONS";
+
+const char *kRegionMissingUrl = R"REGIONS(
+{
+ "regions": [
+ {
+ "id": "mci",
+ "name": "Kansas City",
+ },
+ ]
+}
+)REGIONS";
+
+const char *kRegionEmptyUrl = R"REGIONS(
+{
+ "regions": [
+ {
+ "id": "mci",
+ "name": "Kansas City",
+ "url": [
+ ]
+ },
+ ]
+}
+)REGIONS";
+
+const char *kRegionMultipleUrls = R"REGIONS(
+{
+ "regions": [
+ {
+ "id": "mci",
+ "name": "Kansas City",
+ "url": [
+ "http://kansas1.speed.googlefiber.net/",
+ "http://kansas2.speed.googlefiber.net/"
+ ]
+ }
+ ]
+}
+)REGIONS";
+
+const char *kRegionInvalidUrl = R"REGIONS(
+{
+ "regions": [
+ {
+ "id": "mci",
+ "name": "Kansas City",
+ "url": [
+ "example.com..",
+ ]
+ },
+ ]
+}
+)REGIONS";
+
+const char *kInvalidJson = "{{}{";
+
+std::vector<std::string> RegionList(const std::vector<Region> ®ions) {
+ std::vector<std::string> region_list;
+ for (const Region ®ion : regions) {
+ std::stringstream ss;
+ ss << region.id << ", " << region.name;
+ for (const http::Url &url : region.urls) {
+ ss << ", " << url.url();
+ }
+ region_list.emplace_back(ss.str());
+ }
+ return region_list;
+}
+
+TEST(ParseRegionsTest, NullRegions_Invalid) {
+ EXPECT_ERROR(ParseRegions(kValidRegions, nullptr));
+}
+
+TEST(ParseRegionsTest, EmptyRegions_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions("", ®ions));
+}
+
+TEST(ParseRegionsTest, InvalidJson_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions(kInvalidJson, ®ions));
+}
+
+TEST(ParseRegionsTest, FullRegions_Valid) {
+ std::vector<Region> regions;
+ EXPECT_OK(ParseRegions(kValidRegions, ®ions));
+ EXPECT_THAT(RegionList(regions), testing::UnorderedElementsAre(
+ "aus, Austin, http://austin.speed.googlefiber.net/",
+ "mci, Kansas City, http://kansas.speed.googlefiber.net/",
+ "slc, Provo, http://provo.speed.googlefiber.net/",
+ "sfo, Stanford, http://stanford.speed.googlefiber.net/"));
+}
+
+TEST(ParseRegionsTest, MissingId_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions(kRegionMissingId, ®ions));
+}
+
+TEST(ParseRegionsTest, MissingName_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions(kRegionMissingName, ®ions));
+}
+
+TEST(ParseRegionsTest, MissingUrl_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions(kRegionMissingUrl, ®ions));
+}
+
+TEST(ParseRegionsTest, EmptyUrl_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions(kRegionEmptyUrl, ®ions));
+}
+
+TEST(ParseRegionsTest, InvalidRegionUrl_Invalid) {
+ std::vector<Region> regions;
+ EXPECT_ERROR(ParseRegions(kRegionInvalidUrl, ®ions));
+}
+
+TEST(ParseRegionsTest, MultipleUrls_Valid) {
+ std::vector<Region> regions;
+ EXPECT_OK(ParseRegions(kRegionMultipleUrls, ®ions));
+ EXPECT_THAT(RegionList(regions), testing::UnorderedElementsAre(
+ "mci, Kansas City, http://kansas1.speed.googlefiber.net/, "
+ "http://kansas2.speed.googlefiber.net/"));
+}
+
+} // namespace
+} // namespace speedtest
diff --git a/speedtest/request.h b/speedtest/request.h
index 8588e29..6cc830a 100644
--- a/speedtest/request.h
+++ b/speedtest/request.h
@@ -23,6 +23,7 @@
#include <memory>
#include <string>
#include "url.h"
+#include "utils.h"
namespace http {
@@ -43,6 +44,7 @@
curl_off_t,
curl_off_t)>;
using Ptr = std::unique_ptr<Request>;
+ using Factory = std::function<Ptr(const Url &)>;
Request(std::shared_ptr<CURL> handle, const Url &url);
virtual ~Request();
@@ -100,9 +102,7 @@
QueryStringParams params_;
ProgressFn progress_fn_;
- // disable
- Request(const Request &) = delete;
- void operator=(const Request &) = delete;
+ DISALLOW_COPY_AND_ASSIGN(Request);
};
} // namespace http
diff --git a/speedtest/result.cc b/speedtest/result.cc
new file mode 100644
index 0000000..e27e15a
--- /dev/null
+++ b/speedtest/result.cc
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "result.h"
+
+#include "url.h"
+
+namespace speedtest {
+namespace {
+
+template <typename T>
+void PopulateDuration(Json::Value &json, const T &t) {
+ json["startMillis"] = static_cast<Json::Value::Int64>(t.start_time);
+ json["endMillis"] = static_cast<Json::Value::Int64>(t.end_time);
+}
+
+} // namespace
+
+void PopulateParameters(Json::Value &json, const Config &config) {
+ json["downloadSize"] =
+ static_cast<Json::Value::Int64>(config.download_bytes);
+ json["uploadSize"] =
+ static_cast<Json::Value::Int64>(config.upload_bytes);
+ json["intervalSize"] =
+ static_cast<Json::Value::Int64>(config.interval_millis);
+ json["locationId"] = config.location_id;
+ json["locationName"] = config.location_name;
+ json["minTransferIntervals"] = config.min_transfer_intervals;
+ json["maxTransferIntervals"] = config.max_transfer_intervals;
+ json["minTransferRunTime"] =
+ static_cast<Json::Value::Int64>(config.min_transfer_runtime);
+ json["maxTransferRunTime"] =
+ static_cast<Json::Value::Int64>(config.max_transfer_runtime);
+ json["maxTransferVariance"] = config.max_transfer_variance;
+ json["numConcurrentDownloads"] = config.num_downloads;
+ json["numConcurrentUploads"] = config.num_uploads;
+ json["pingRunTime"] =
+ static_cast<Json::Value::Int64>(config.ping_runtime_millis);
+ json["pingTimeout"] =
+ static_cast<Json::Value::Int64>(config.ping_timeout_millis);
+ json["transferPortStart"] = config.transfer_port_start;
+ json["transferPortEnd"] = config.transfer_port_end;
+ json["averageType"] = config.average_type;
+}
+
+void PopulateConfigResult(Json::Value &json,
+ const ConfigResult &config_result) {
+ PopulateDuration(json, config_result);
+ PopulateParameters(json["parameters"], config_result.config);
+}
+
+void PopulateFindNearest(Json::Value &json,
+ const FindNearest::Result &find_nearest) {
+ PopulateDuration(json, find_nearest);
+ json["pingResults"] = Json::Value(Json::arrayValue);
+ for (const Ping::Result &ping_result : find_nearest.ping_results) {
+ Json::Value ping;
+ ping["id"] = ping_result.region.id;
+ ping["url"] = ping_result.region.urls.front().url();
+ if (ping_result.received > 0) {
+ ping["minPingMillis"] =
+ static_cast<Json::Value::Int64>(ping_result.min_ping_micros);
+ }
+ json["pingResults"].append(ping);
+ }
+}
+
+void PopulateInitResult(Json::Value &json,
+ const Init::Result &init_result) {
+ PopulateDuration(json, init_result);
+ PopulateConfigResult(json["configResult"], init_result.config_result);
+ if (!init_result.find_nearest_result.ping_results.empty()) {
+ PopulateFindNearest(json["findNearest"], init_result.find_nearest_result);
+ }
+ json["selectedRegion"] = init_result.selected_region.id;
+}
+
+void PopulateTransfer(Json::Value &json,
+ const TransferResult &transfer_result) {
+ PopulateDuration(json, transfer_result);
+ json["speedMbps"] = transfer_result.speed_mbps;
+ json["totalBytes"] =
+ static_cast<Json::Value::Int64>(transfer_result.total_bytes);
+ json["buckets"] = Json::Value(Json::arrayValue);
+ for (const Bucket &bucket : transfer_result.buckets) {
+ Json::Value bucket_json;
+ bucket_json["totalBytes"] =
+ static_cast<Json::Value::Int64>(bucket.total_bytes);
+ bucket_json["longSpeedMbps"] = bucket.long_megabits;
+ bucket_json["shortSpeedMbps"] = bucket.short_megabits;
+ bucket_json["offsetMillis"] = bucket.start_time / 1000.0d;
+ json["buckets"].append(bucket_json);
+ }
+}
+
+void PopulatePingResult(Json::Value &json, const Ping::Result &ping_result) {
+ PopulateDuration(json, ping_result);
+ json["id"] = ping_result.region.id;
+ json["url"] = ping_result.region.urls.front().url();
+ if (ping_result.received > 0) {
+ json["minPingMillis"] =
+ static_cast<Json::Value::Int64>(ping_result.min_ping_micros);
+ }
+}
+
+void PopulateSpeedtest(Json::Value &json,
+ const Speedtest::Result &speedtest_result) {
+ PopulateDuration(json, speedtest_result);
+ PopulateInitResult(json["initResult"], speedtest_result.init_result);
+ if (speedtest_result.download_run) {
+ PopulateTransfer(json["downloadResult"], speedtest_result.download_result);
+ }
+ if (speedtest_result.upload_run) {
+ PopulateTransfer(json["uploadResult"], speedtest_result.upload_result);
+ }
+ if (speedtest_result.ping_run) {
+ PopulatePingResult(json["pingResult"], speedtest_result.ping_result);
+ }
+ json["endState"] = "COMPLETE";
+}
+
+} // namespace speedtest
diff --git a/speedtest/result.h b/speedtest/result.h
new file mode 100644
index 0000000..4ffa07c
--- /dev/null
+++ b/speedtest/result.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_RESULT_H
+#define SPEEDTEST_RESULT_H
+
+#include <jsoncpp/json/json.h>
+#include "config.h"
+#include "find_nearest.h"
+#include "init.h"
+#include "ping.h"
+#include "speedtest.h"
+#include "transfer_runner.h"
+
+namespace speedtest {
+
+void PopulateParameters(Json::Value &json, const Config &config);
+void PopulateConfigResult(Json::Value &json,
+ const ConfigResult &config_result);
+void PopulateFindNearest(Json::Value &json,
+ const FindNearest::Result &find_nearest);
+void PopulateInitResult(Json::Value &json,
+ const Init::Result &init_result);
+void PopulateTransfer(Json::Value &json,
+ const TransferResult &transfer_result);
+void PopulatePingResult(Json::Value &json, const Ping::Result &ping_result);
+void PopulateSpeedtest(Json::Value &json,
+ const Speedtest::Result &speedtest_result);
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_RESULT_H
diff --git a/speedtest/speedtest.cc b/speedtest/speedtest.cc
index ed1263d..93a0248 100644
--- a/speedtest/speedtest.cc
+++ b/speedtest/speedtest.cc
@@ -16,408 +16,318 @@
#include "speedtest.h"
-#include <chrono>
-#include <cstring>
-#include <limits>
-#include <random>
-#include <thread>
-#include <iomanip>
-#include <fstream>
-#include <streambuf>
-
+#include <curl/curl.h>
+#include <jsoncpp/json/json.h>
+#include <jsoncpp/json/writer.h>
+#include "download.h"
#include "errors.h"
+#include "result.h"
#include "timed_runner.h"
-#include "transfer_runner.h"
-#include "utils.h"
+#include "upload.h"
namespace speedtest {
-namespace {
-std::shared_ptr<std::string> MakeRandomData(size_t size) {
- std::random_device rd;
- std::default_random_engine random_engine(rd());
- std::uniform_int_distribution<char> uniform_dist(1, 255);
- auto random_data = std::make_shared<std::string>();
- random_data->resize(size);
- for (size_t i = 0; i < size; ++i) {
- (*random_data)[i] = uniform_dist(random_engine);
- }
- return std::move(random_data);
+Speedtest::Speedtest(const Options &options): options_(options) {
}
-const char *kFileSerial = "/etc/serial";
-const char *kFileVersion = "/etc/version";
+Speedtest::Result Speedtest::operator()(std::atomic_bool *cancel) {
+ Speedtest::Result result;
+ result.start_time = SystemTimeMicros();
+ result.download_run = false;
+ result.upload_run = false;
+ result.ping_run = false;
-std::string LoadFile(const std::string &file_name) {
- std::ifstream in(file_name);
- return std::string(std::istreambuf_iterator<char>(in),
- std::istreambuf_iterator<char>());
-}
-
-} // namespace
-
-Speedtest::Speedtest(const Options &options)
- : options_(options) {
- http::CurlEnv::Options curl_options;
- curl_options.disable_dns_cache = options_.disable_dns_cache;
- curl_options.max_connections = options_.max_connections;
- env_ = http::CurlEnv::NewCurlEnv(curl_options);
-}
-
-Speedtest::~Speedtest() {
-}
-
-void Speedtest::Run() {
- InitUserAgent();
- LoadServerList();
- if (servers_.empty()) {
- std::cerr << "No servers found in global server list\n";
- std::exit(1);
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
}
- FindNearestServer();
- if (!server_url_) {
- std::cout << "No servers responded. Exiting\n";
- return;
+
+ Init::Options init_options;
+ init_options.verbose = options_.verbose;
+ init_options.request_factory = options_.request_factory;
+ init_options.global = options_.global;
+ init_options.global_url = options_.global_url;
+ init_options.ping_timeout_millis = options_.ping_timeout_millis;
+ init_options.regional_urls = options_.regional_urls;
+ Init init(init_options);
+ result.init_result = init(cancel);
+ if (!result.init_result.status.ok()) {
+ result.status = result.init_result.status;
+ result.end_time = SystemTimeMicros();
+ return result;
}
- std::string json = LoadConfig(*server_url_);
- if (!ParseConfig(json, &config_)) {
- std::cout << "Could not parse config\n";
- return;
- }
+
+ selected_region_ = result.init_result.selected_region;
if (options_.verbose) {
- std::cout << "Server config:\n";
+ std::cout << "Setting selected region to "
+ << DescribeRegion(selected_region_) << "\n";
+ }
+
+ if (result.init_result.config_result.config.location_id.empty()) {
+ result.init_result.config_result.config.location_id = selected_region_.id;
+ }
+ if (result.init_result.config_result.config.location_name.empty()) {
+ result.init_result.config_result.config.location_name = selected_region_.name;
+ }
+
+ OverrideConfigWithOptions(&result.init_result.config_result.config, options_);
+ config_ = result.init_result.config_result.config;
+ if (options_.verbose) {
PrintConfig(config_);
}
- std::cout << "Location: " << config_.location_name << "\n";
- std::cout << "URL: " << server_url_->url() << "\n";
- RunDownloadTest();
- RunUploadTest();
- RunPingTest();
-}
-void Speedtest::InitUserAgent() {
- if (options_.user_agent.empty()) {
- std::string serial = LoadFile(kFileSerial);
- std::string version = LoadFile(kFileVersion);
- Trim(&serial);
- Trim(&version);
- user_agent_ = "CPE";
- if (!version.empty()) {
- user_agent_ += "/" + version;
- if (!serial.empty()) {
- user_agent_ += "/" + serial;
- }
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ std::cout << "ID: " << result.init_result.selected_region.id << "\n";
+ std::cout << "Location: " << result.init_result.selected_region.name << "\n";
+
+ if (options_.skip_download) {
+ std::cout << "Skipping download test\n";
+ } else {
+ result.download_result = RunDownloadTest(cancel);
+ if (!result.download_result.status.ok()) {
+ result.status = result.download_result.status;
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+ result.download_run = true;
+ std::cout << "Download speed: "
+ << round(result.download_result.speed_mbps, 2)
+ << " Mbps\n";
+ }
+
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ if (options_.skip_upload) {
+ std::cout << "Skipping upload test\n";
+ } else {
+ result.upload_result = RunUploadTest(cancel);
+ if (!result.upload_result.status.ok()) {
+ result.status = result.upload_result.status;
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+ result.upload_run = true;
+ std::cout << "Upload speed: "
+ << round(result.upload_result.speed_mbps, 2)
+ << " Mbps\n";
+ }
+
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "Speedtest aborted");
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+
+ if (options_.skip_ping) {
+ std::cout << "Skipping ping test\n";
+ } else {
+ result.ping_result = RunPingTest(cancel);
+ if (!result.ping_result.status.ok()) {
+ result.status = result.ping_result.status;
+ result.end_time = SystemTimeMicros();
+ return result;
+ }
+ result.ping_run = true;
+ std::cout << "Ping time: "
+ << ToMillis(result.ping_result.min_ping_micros)
+ << " ms\n";
+ }
+
+ result.status = Status::OK;
+ result.end_time = SystemTimeMicros();
+
+ if (!options_.report_results) {
+ if (options_.verbose) {
+ std::cout << "Not reporting results to server\n";
}
} else {
- user_agent_ = options_.user_agent;
- return;
- }
- if (options_.verbose) {
- std::cout << "Setting user agent to " << user_agent_ << "\n";
- }
-}
+ Json::Value root;
+ PopulateSpeedtest(root, result);
+ Json::FastWriter writer;
+ std::string out = writer.write(root);
-void Speedtest::LoadServerList() {
- servers_.clear();
- if (!options_.global) {
+ http::Url result_url(selected_region_.urls.front());
+ result_url.set_path("/result");
if (options_.verbose) {
- std::cout << "Explicit server list:\n";
- for (const auto &url : options_.hosts) {
- std::cout << " " << url.url() << "\n";
+ std::cout << "Posting results to " << result_url.url() << "\n";
+ }
+ http::Request::Ptr request = options_.request_factory(result_url);
+ request->set_header("Content-Type", "application/json");
+ CURLcode curl_code = request->Post(out.c_str(), out.size());
+ if (curl_code == CURLE_OK) {
+ if (options_.verbose) {
+ std::cout << "Result posted successfully\n";
}
- }
- servers_ = options_.hosts;
- return;
- }
-
- std::string json = LoadConfig(options_.global_host);
- if (json.empty()) {
- std::cerr << "Failed to load config JSON\n";
- std::exit(1);
- }
- if (options_.verbose) {
- std::cout << "Loaded config JSON: " << json << "\n";
- }
- if (!ParseServers(json, &servers_)) {
- std::cerr << "Failed to parse server list: " << json << "\n";
- std::exit(1);
- }
- if (options_.verbose) {
- std::cout << "Loaded servers:\n";
- for (const auto &url : servers_) {
- std::cout << " " << url.url() << "\n";
+ } else {
+ std::cout << "Failed to report results: "
+ << http::ErrorString(curl_code) << "\n";
}
}
+ return result;
}
-void Speedtest::FindNearestServer() {
- server_url_.reset();
- if (servers_.size() == 1) {
- server_url_.reset(new http::Url(servers_[0]));
- if (options_.verbose) {
- std::cout << "Only 1 server so using " << server_url_->url() << "\n";
- }
- return;
- }
-
- PingTask::Options options;
- options.verbose = options_.verbose;
- options.timeout = PingTimeout();
- std::vector<http::Url> hosts;
- for (const auto &server : servers_) {
- http::Url url(server);
- url.set_path("/ping");
- hosts.emplace_back(url);
- }
- options.num_pings = hosts.size();
+TransferResult Speedtest::RunDownloadTest(std::atomic_bool *cancel) {
if (options_.verbose) {
- std::cout << "There are " << hosts.size() << " ping URLs:\n";
- for (const auto &host : hosts) {
- std::cout << " " << host.url() << "\n";
- }
+ std::cout << "Starting download test to "
+ << DescribeRegion(selected_region_) << ")\n";
}
- options.request_factory = [&](int id) -> http::Request::Ptr{
- return MakeRequest(hosts[id]);
- };
- PingTask find_nearest(options);
- if (options_.verbose) {
- std::cout << "Starting to find nearest server\n";
- }
- RunTimed(&find_nearest, 1500);
- find_nearest.WaitForEnd();
- if (find_nearest.IsSucceeded()) {
- PingStats fastest = find_nearest.GetFastest();
- server_url_.reset(new http::Url(fastest.url));
- server_url_->clear_path();
- if (options_.verbose) {
- double ping_millis = fastest.min_micros / 1000.0d;
- std::cout << "Found nearest server: " << fastest.url.url()
- << " (" << round(ping_millis, 2) << " ms)\n";
- }
- }
-}
-
-std::string Speedtest::LoadConfig(const http::Url &url) {
- http::Url config_url(url);
- config_url.set_path("/config");
- if (options_.verbose) {
- std::cout << "Loading config from " << config_url.url() << "\n";
- }
- http::Request::Ptr request = MakeRequest(config_url);
- request->set_url(config_url);
- std::string json;
- request->Get([&](void *data, size_t size){
- json.assign(static_cast<const char *>(data), size);
- });
- return json;
-}
-
-void Speedtest::RunPingTest() {
- PingTask::Options options;
- options.verbose = options_.verbose;
- options.timeout = PingTimeout();
- options.num_pings = 1;
- http::Url ping_url(*server_url_);
- ping_url.set_path("/ping");
- options.request_factory = [&](int id) -> http::Request::Ptr{
- return MakeRequest(ping_url);
- };
- std::unique_ptr<PingTask> ping(new PingTask(options));
- RunTimed(ping.get(), PingRunTime());
- ping->WaitForEnd();
- PingStats fastest = ping->GetFastest();
- if (ping->IsSucceeded()) {
- long micros = fastest.min_micros;
- std::cout << "Ping time: " << round(micros / 1000.0d, 3) << " ms\n";
- } else {
- std::cout << "Failed to get ping response from "
- << config_.location_name << " (" << fastest.url << ")\n";
- }
-}
-
-void Speedtest::RunDownloadTest() {
- if (options_.verbose) {
- std::cout << "Starting download test to " << config_.location_name
- << " (" << server_url_->url() << ")\n";
- }
- DownloadTask::Options download_options;
+ Download::Options download_options;
download_options.verbose = options_.verbose;
- download_options.num_transfers = NumDownloads();
- download_options.download_size = DownloadSize();
+ download_options.num_transfers = config_.num_downloads;
+ download_options.download_bytes = config_.download_bytes;
download_options.request_factory = [this](int id) -> http::Request::Ptr{
return MakeTransferRequest(id, "/download");
};
- std::unique_ptr<DownloadTask> download(new DownloadTask(download_options));
- TransferRunner::Options runner_options;
- runner_options.verbose = options_.verbose;
- runner_options.task = download.get();
- runner_options.min_runtime = MinTransferRuntime();
- runner_options.max_runtime = MaxTransferRuntime();
- runner_options.min_intervals = MinTransferIntervals();
- runner_options.max_intervals = MaxTransferIntervals();
- runner_options.max_variance = MaxTransferVariance();
- runner_options.interval_millis = IntervalMillis();
+ Download download(download_options);
+
+ TransferOptions transfer_options;
+ transfer_options.verbose = options_.verbose;
+ transfer_options.min_runtime_millis = config_.min_transfer_runtime;
+ transfer_options.max_runtime_millis = config_.max_transfer_runtime;
+ transfer_options.min_intervals = config_.min_transfer_intervals;
+ transfer_options.max_intervals = config_.max_transfer_intervals;
+ transfer_options.max_variance = config_.max_transfer_variance;
+ transfer_options.interval_millis = config_.interval_millis;
+ transfer_options.exponential_moving_average =
+ config_.average_type == "EXPONENTIAL";
if (options_.progress_millis > 0) {
- runner_options.progress_millis = options_.progress_millis;
- runner_options.progress_fn = [](Interval interval) {
- double speed_variance = variance(interval.short_megabits,
- interval.long_megabits);
- std::cout << "[+" << round(interval.running_time / 1000.0, 0) << " ms] "
- << "Download speed: " << round(interval.short_megabits, 2)
- << " - " << round(interval.long_megabits, 2)
- << " Mbps (" << interval.bytes << " bytes, variance "
+ transfer_options.progress_millis = options_.progress_millis;
+ transfer_options.progress_fn = [](Bucket bucket) {
+ double speed_variance = variance(bucket.short_megabits,
+ bucket.long_megabits);
+ std::cout << "[+" << round(bucket.start_time / 1000.0, 0) << " ms] "
+ << "Download speed: " << round(bucket.short_megabits, 2)
+ << " - " << round(bucket.long_megabits, 2)
+ << " Mbps (" << bucket.total_bytes << " bytes, variance "
<< round(speed_variance, 4) << ")\n";
};
}
- TransferRunner runner(runner_options);
- runner.Run();
- runner.WaitForEnd();
- if (options_.verbose) {
- long running_time = download->GetRunningTimeMicros();
- std::cout << "Downloaded " << download->bytes_transferred()
- << " bytes in " << round(running_time / 1000.0, 0) << " ms\n";
- }
- std::cout << "Download speed: "
- << round(runner.GetSpeedInMegabits(), 2) << " Mbps\n";
+ return RunTransfer(std::ref(download), cancel, transfer_options);
}
-void Speedtest::RunUploadTest() {
+TransferResult Speedtest::RunUploadTest(std::atomic_bool *cancel) {
if (options_.verbose) {
- std::cout << "Starting upload test to " << config_.location_name
- << " (" << server_url_->url() << ")\n";
+ std::cout << "Starting upload test to "
+ << DescribeRegion(selected_region_) << ")\n";
}
- UploadTask::Options upload_options;
+ Upload::Options upload_options;
upload_options.verbose = options_.verbose;
- upload_options.num_transfers = NumUploads();
- upload_options.payload = MakeRandomData(UploadSize());
+ upload_options.num_transfers = config_.num_uploads;
+ upload_options.payload = MakeRandomData(config_.upload_bytes);
upload_options.request_factory = [this](int id) -> http::Request::Ptr{
return MakeTransferRequest(id, "/upload");
};
+ Upload upload(upload_options);
- std::unique_ptr<UploadTask> upload(new UploadTask(upload_options));
- TransferRunner::Options runner_options;
- runner_options.verbose = options_.verbose;
- runner_options.task = upload.get();
- runner_options.min_runtime = MinTransferRuntime();
- runner_options.max_runtime = MaxTransferRuntime();
- runner_options.min_intervals = MinTransferIntervals();
- runner_options.max_intervals = MaxTransferIntervals();
- runner_options.max_variance = MaxTransferVariance();
- runner_options.interval_millis = IntervalMillis();
+ TransferOptions transfer_options;
+ transfer_options.verbose = options_.verbose;
+ transfer_options.min_runtime_millis = config_.min_transfer_runtime;
+ transfer_options.max_runtime_millis = config_.max_transfer_runtime;
+ transfer_options.min_intervals = config_.min_transfer_intervals;
+ transfer_options.max_intervals = config_.max_transfer_intervals;
+ transfer_options.max_variance = config_.max_transfer_variance;
+ transfer_options.interval_millis = config_.interval_millis;
+ transfer_options.exponential_moving_average =
+ config_.average_type == "EXPONENTIAL";
if (options_.progress_millis > 0) {
- runner_options.progress_millis = options_.progress_millis;
- runner_options.progress_fn = [](Interval interval) {
- double speed_variance = variance(interval.short_megabits,
- interval.long_megabits);
- std::cout << "[+" << round(interval.running_time / 1000.0, 0) << " ms] "
- << "Upload speed: " << round(interval.short_megabits, 2)
- << " - " << round(interval.long_megabits, 2)
- << " Mbps (" << interval.bytes << " bytes, variance "
+ transfer_options.progress_millis = options_.progress_millis;
+ transfer_options.progress_fn = [](Bucket bucket) {
+ double speed_variance = variance(bucket.short_megabits,
+ bucket.long_megabits);
+ std::cout << "[+" << round(bucket.start_time / 1000.0, 0) << " ms] "
+ << "Upload speed: " << round(bucket.short_megabits, 2)
+ << " - " << round(bucket.long_megabits, 2)
+ << " Mbps (" << bucket.total_bytes << " bytes, variance "
<< round(speed_variance, 4) << ")\n";
};
}
- TransferRunner runner(runner_options);
- runner.Run();
- runner.WaitForEnd();
- if (options_.verbose) {
- long running_time = upload->GetRunningTimeMicros();
- std::cout << "Uploaded " << upload->bytes_transferred()
- << " bytes in " << round(running_time / 1000.0, 0) << " ms\n";
+ return RunTransfer(std::ref(upload), cancel, transfer_options);
+}
+
+Ping::Result Speedtest::RunPingTest(std::atomic_bool *cancel) {
+ Ping::Options ping_options;
+ ping_options.verbose = options_.verbose;
+ ping_options.timeout_millis = config_.ping_timeout_millis;
+ ping_options.region = selected_region_;
+ ping_options.num_concurrent_pings = 0;
+ ping_options.request_factory = [&](const http::Url &url){
+ return MakeRequest(url);
+ };
+ Ping ping(ping_options);
+ return RunTimed(std::ref(ping), cancel, config_.ping_runtime_millis);
+}
+
+void Speedtest::OverrideConfigWithOptions(Config *config,
+ const Options &options) {
+ if (options_.num_downloads > 0) {
+ config->num_downloads = options_.num_downloads;
}
- std::cout << "Upload speed: "
- << round(runner.GetSpeedInMegabits(), 2) << " Mbps\n";
+ if (options_.download_bytes > 0) {
+ config->download_bytes = options_.download_bytes;
+ }
+ if (options_.num_uploads > 0) {
+ config->num_uploads = options_.num_uploads;
+ }
+ if (options_.upload_bytes > 0) {
+ config->upload_bytes = options_.upload_bytes;
+ }
+ if (options_.ping_runtime_millis > 0) {
+ config->ping_runtime_millis = options_.ping_runtime_millis;
+ }
+ if (options_.ping_timeout_millis > 0) {
+ config->ping_timeout_millis = options_.ping_timeout_millis;
+ }
+ if (options_.min_transfer_runtime > 0) {
+ config->min_transfer_runtime = options_.min_transfer_runtime;
+ }
+ if (options_.max_transfer_runtime > 0) {
+ config->max_transfer_runtime = options_.max_transfer_runtime;
+ }
+ if (options_.min_transfer_intervals > 0) {
+ config->min_transfer_intervals = options_.min_transfer_intervals;
+ }
+ if (options_.max_transfer_intervals > 0) {
+ config->max_transfer_intervals = options_.max_transfer_intervals;
+ }
+ if (options_.max_transfer_variance > 0) {
+ config->max_transfer_variance = options_.max_transfer_variance;
+ }
+ if (options_.interval_millis > 0) {
+ config->interval_millis = options_.interval_millis;
+ }
+ if (options_.exponential_moving_average){
+ config->average_type = "EXPONENTIAL";
+ }
}
-int Speedtest::NumDownloads() const {
- return options_.num_downloads
- ? options_.num_downloads
- : config_.num_downloads;
-}
-
-int Speedtest::DownloadSize() const {
- return options_.download_size
- ? options_.download_size
- : config_.download_size;
-}
-
-int Speedtest::NumUploads() const {
- return options_.num_uploads
- ? options_.num_uploads
- : config_.num_uploads;
-}
-
-int Speedtest::UploadSize() const {
- return options_.upload_size
- ? options_.upload_size
- : config_.upload_size;
-}
-
-int Speedtest::PingRunTime() const {
- return options_.ping_runtime
- ? options_.ping_runtime
- : config_.ping_runtime;
-}
-
-int Speedtest::PingTimeout() const {
- return options_.ping_timeout
- ? options_.ping_timeout
- : config_.ping_timeout;
-}
-
-int Speedtest::MinTransferRuntime() const {
- return options_.min_transfer_runtime
- ? options_.min_transfer_runtime
- : config_.min_transfer_runtime;
-}
-
-int Speedtest::MaxTransferRuntime() const {
- return options_.max_transfer_runtime
- ? options_.max_transfer_runtime
- : config_.max_transfer_runtime;
-}
-
-int Speedtest::MinTransferIntervals() const {
- return options_.min_transfer_intervals
- ? options_.min_transfer_intervals
- : config_.min_transfer_intervals;
-}
-
-int Speedtest::MaxTransferIntervals() const {
- return options_.max_transfer_intervals
- ? options_.max_transfer_intervals
- : config_.max_transfer_intervals;
-}
-
-double Speedtest::MaxTransferVariance() const {
- return options_.max_transfer_variance
- ? options_.max_transfer_variance
- : config_.max_transfer_variance;
-}
-
-int Speedtest::IntervalMillis() const {
- return options_.interval_millis
- ? options_.interval_millis
- : config_.interval_millis;
-}
-
-http::Request::Ptr Speedtest::MakeRequest(const http::Url &url) {
- http::Request::Ptr request = env_->NewRequest(url);
- if (!user_agent_.empty()) {
- request->set_user_agent(user_agent_);
+http::Request::Ptr Speedtest::MakeRequest(const http::Url &url) const {
+ http::Request::Ptr request = options_.request_factory(url);
+ if (!options_.user_agent.empty()) {
+ request->set_user_agent(options_.user_agent);
}
return std::move(request);
}
http::Request::Ptr Speedtest::MakeBaseRequest(
- int id, const std::string &path) {
- http::Url url(*server_url_);
+ int id, const std::string &path) const {
+ http::Url url(selected_region_.urls.front());
url.set_path(path);
return MakeRequest(url);
}
http::Request::Ptr Speedtest::MakeTransferRequest(
- int id, const std::string &path) {
- http::Url url(*server_url_);
+ int id, const std::string &path) const {
+ http::Url url(selected_region_.urls.front().url());
int port_start = config_.transfer_port_start;
int port_end = config_.transfer_port_end;
int num_ports = port_end - port_start + 1;
diff --git a/speedtest/speedtest.h b/speedtest/speedtest.h
index fb32355..7b58122 100644
--- a/speedtest/speedtest.h
+++ b/speedtest/speedtest.h
@@ -18,64 +18,58 @@
#define SPEEDTEST_SPEEDTEST_H
#include <atomic>
-#include <memory>
#include <string>
-
#include "config.h"
-#include "curl_env.h"
-#include "download_task.h"
+#include "init.h"
#include "options.h"
-#include "ping_task.h"
-#include "upload_task.h"
-#include "url.h"
+#include "ping.h"
+#include "region.h"
#include "request.h"
+#include "status.h"
+#include "transfer_runner.h"
+#include "url.h"
+#include "utils.h"
namespace speedtest {
class Speedtest {
public:
- explicit Speedtest(const Options &options);
- virtual ~Speedtest();
+ struct Result {
+ long start_time;
+ long end_time;
+ Status status;
+ Init::Result init_result;
- void Run();
+ bool download_run;
+ TransferResult download_result;
+
+ bool upload_run;
+ TransferResult upload_result;
+
+ bool ping_run;
+ Ping::Result ping_result;
+ };
+
+ explicit Speedtest(const Options &options);
+
+ Result operator()(std::atomic_bool *cancel);
private:
- void InitUserAgent();
- void LoadServerList();
- void FindNearestServer();
- std::string LoadConfig(const http::Url &url);
- void RunPingTest();
- void RunDownloadTest();
- void RunUploadTest();
+ TransferResult RunDownloadTest(std::atomic_bool *cancel);
+ TransferResult RunUploadTest(std::atomic_bool *cancel);
+ Ping::Result RunPingTest(std::atomic_bool *cancel);
- int NumDownloads() const;
- int DownloadSize() const;
- int NumUploads() const;
- int UploadSize() const;
- int PingTimeout() const;
- int PingRunTime() const;
- int MinTransferRuntime() const;
- int MaxTransferRuntime() const;
- int MinTransferIntervals() const;
- int MaxTransferIntervals() const;
- double MaxTransferVariance() const;
- int IntervalMillis() const;
+ void OverrideConfigWithOptions(Config *config, const Options &options);
- http::Request::Ptr MakeRequest(const http::Url &url);
- http::Request::Ptr MakeBaseRequest(int id, const std::string &path);
- http::Request::Ptr MakeTransferRequest(int id, const std::string &path);
+ http::Request::Ptr MakeRequest(const http::Url &url) const;
+ http::Request::Ptr MakeBaseRequest(int id, const std::string &path) const;
+ http::Request::Ptr MakeTransferRequest(int id, const std::string &path) const;
- std::shared_ptr <http::CurlEnv> env_;
Options options_;
Config config_;
- std::string user_agent_;
- std::vector<http::Url> servers_;
- std::unique_ptr<http::Url> server_url_;
- std::unique_ptr<std::string> send_data_;
+ Region selected_region_;
- // disable
- Speedtest(const Speedtest &) = delete;
- void operator=(const Speedtest &) = delete;
+ DISALLOW_COPY_AND_ASSIGN(Speedtest);
};
} // namespace speedtest
diff --git a/speedtest/speedtest_main.cc b/speedtest/speedtest_main.cc
index d756c4b..efe6c68 100644
--- a/speedtest/speedtest_main.cc
+++ b/speedtest/speedtest_main.cc
@@ -14,11 +14,45 @@
* limitations under the License.
*/
+#include <atomic>
#include <cstdlib>
-#include <iostream>
+#include <fstream>
+#include <iterator>
#include <memory>
+#include <string>
+#include "curl_env.h"
#include "options.h"
+#include "request.h"
#include "speedtest.h"
+#include "utils.h"
+
+namespace {
+
+const char *kFileSerial = "/etc/serial";
+const char *kFileVersion = "/etc/version";
+
+std::string LoadFile(const std::string &file_name) {
+ std::ifstream in(file_name);
+ return std::string(std::istreambuf_iterator<char>(in),
+ std::istreambuf_iterator<char>());
+}
+
+std::string GetDefaultUserAgent() {
+ std::string serial = LoadFile(kFileSerial);
+ std::string version = LoadFile(kFileVersion);
+ speedtest::Trim(&serial);
+ speedtest::Trim(&version);
+ std::string user_agent_ = "CPE";
+ if (!version.empty()) {
+ user_agent_ += "/" + version;
+ if (!serial.empty()) {
+ user_agent_ += "/" + serial;
+ }
+ }
+ return user_agent_;
+}
+
+}
int main(int argc, char *argv[]) {
speedtest::Options options;
@@ -26,10 +60,22 @@
speedtest::PrintUsage(argv[0]);
std::exit(1);
}
+ if (options.user_agent.empty()) {
+ options.user_agent = GetDefaultUserAgent();
+ }
if (options.verbose) {
speedtest::PrintOptions(options);
}
+ http::CurlEnv::Options curl_options;
+ curl_options.disable_dns_cache = options.disable_dns_cache;
+ curl_options.max_connections = options.max_connections;
+ std::shared_ptr<http::CurlEnv> curl_env =
+ http::CurlEnv::NewCurlEnv(curl_options);
+ options.request_factory = [&](const http::Url &url) -> http::Request::Ptr {
+ return curl_env->NewRequest(url);
+ };
speedtest::Speedtest speed(options);
- speed.Run();
+ std::atomic_bool cancel(false);
+ speed(&cancel);
return 0;
}
diff --git a/speedtest/status.cc b/speedtest/status.cc
new file mode 100644
index 0000000..2b541bb
--- /dev/null
+++ b/speedtest/status.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "status.h"
+
+#include <sstream>
+#include <type_traits>
+#include "utils.h"
+
+namespace speedtest {
+
+std::string ErrorString(StatusCode status_code) {
+ switch (status_code) {
+ case StatusCode::OK: return "OK";
+ case StatusCode::INVALID_ARGUMENT: return "INVALID_ARGUMENT";
+ case StatusCode::ABORTED: return "ABORTED";
+ case StatusCode::INTERNAL: return "INTERNAL";
+ case StatusCode::FAILED_PRECONDITION: return "FAILED_PRECONDITION";
+ case StatusCode::UNAVAILABLE: return "UNAVAILABLE";
+ case StatusCode::UNKNOWN: return "UNKNOWN";
+ }
+ return std::string("Unknown status code ") + to_string(
+ static_cast<std::underlying_type<StatusCode>::type>(status_code));
+}
+
+const Status Status::OK;
+
+Status::Status(): code_(StatusCode::OK) {
+}
+
+Status::Status(StatusCode code): code_(code) {
+}
+
+Status::Status(StatusCode code, std::string message)
+ : code_(code), message_(message) {
+}
+
+bool Status::ok() const {
+ return code_ == StatusCode::OK;
+}
+
+StatusCode Status::code() const {
+ return code_;
+}
+
+const std::string & Status::message() const {
+ return message_;
+}
+
+std::string Status::ToString() const {
+ std::stringstream ss;
+ ss << ErrorString(code_) << ": " << message_;
+ return ss.str();
+}
+
+bool Status::operator==(const Status &status) const {
+ return code_ == status.code_ && message_ == status.message_;
+}
+
+bool Status::operator!=(const Status &status) const {
+ return code_ != status.code_ || message_ != status.message_;
+}
+
+} // namespace speedtest
diff --git a/speedtest/status.h b/speedtest/status.h
new file mode 100644
index 0000000..4ee8011
--- /dev/null
+++ b/speedtest/status.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_STATUS_H
+#define SPEEDTEST_STATUS_H
+
+#include <string>
+
+namespace speedtest {
+
+enum class StatusCode {
+ OK = 0,
+ INVALID_ARGUMENT = 1,
+ ABORTED = 2,
+ INTERNAL = 3,
+ FAILED_PRECONDITION = 4,
+ UNAVAILABLE = 5,
+ UNKNOWN = 6
+};
+
+std::string ErrorString(StatusCode status_code);
+
+class Status {
+ public:
+ Status();
+ explicit Status(StatusCode code);
+ Status(StatusCode code, std::string message);
+
+ bool ok() const;
+ StatusCode code() const;
+ const std::string &message() const;
+ std::string ToString() const;
+
+ bool operator==(const Status &status) const;
+ bool operator!=(const Status &status) const;
+
+ static const Status OK;
+
+ private:
+ StatusCode code_;
+ std::string message_;
+};
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_STATUS_H
diff --git a/speedtest/task.cc b/speedtest/task.cc
deleted file mode 100644
index 84d12c9..0000000
--- a/speedtest/task.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "task.h"
-
-#include <algorithm>
-#include <cassert>
-#include <iostream>
-#include <thread>
-#include "utils.h"
-
-namespace speedtest {
-
-const char *AsString(TaskStatus status) {
- switch (status) {
- case TaskStatus::NOT_STARTED: return "NOT_STARTED";
- case TaskStatus::RUNNING: return "RUNNING";
- case TaskStatus::STOPPING: return "STOPPING";
- case TaskStatus::STOPPED: return "STOPPED";
- }
- std::exit(1);
-}
-
-Task::Task(const Options &options)
- : status_(TaskStatus::NOT_STARTED) {
- assert(options.request_factory);
-}
-
-Task::~Task() {
- Stop();
- if (runner_.joinable()) {
- runner_.join();
- }
- if (stopper_.joinable()) {
- stopper_.join();
- }
-}
-
-void Task::Run() {
- runner_ = std::thread([=]{
- {
- std::lock_guard <std::mutex> lock(mutex_);
- if (status_ != TaskStatus::NOT_STARTED &&
- status_ != TaskStatus::STOPPED) {
- return;
- }
- UpdateStatusLocked(TaskStatus::RUNNING);
- start_time_ = SystemTimeMicros();
- }
- RunInternal();
- });
- stopper_ = std::thread([=]{
- WaitFor(TaskStatus::STOPPING);
- StopInternal();
- std::lock_guard <std::mutex> lock(mutex_);
- UpdateStatusLocked(TaskStatus::STOPPED);
- end_time_ = SystemTimeMicros();
- });
-}
-
-void Task::Stop() {
- std::lock_guard <std::mutex> lock(mutex_);
- if (status_ != TaskStatus::RUNNING) {
- return;
- }
- UpdateStatusLocked(TaskStatus::STOPPING);
-}
-
-TaskStatus Task::GetStatus() const {
- std::lock_guard <std::mutex> lock(mutex_);
- return status_;
-}
-
-long Task::GetStartTime() const {
- std::lock_guard <std::mutex> lock(mutex_);
- return start_time_;
-}
-
-long Task::GetEndTime() const {
- std::lock_guard <std::mutex> lock(mutex_);
- return end_time_;
-}
-
-long Task::GetRunningTimeMicros() const {
- std::lock_guard <std::mutex> lock(mutex_);
- switch (status_) {
- case TaskStatus::NOT_STARTED:
- break;
- case TaskStatus::RUNNING:
- case TaskStatus::STOPPING:
- return SystemTimeMicros() - start_time_;
- case TaskStatus::STOPPED:
- return end_time_ - start_time_;
- }
- return 0;
-}
-
-void Task::WaitForEnd() {
- WaitFor(TaskStatus::STOPPED);
-}
-
-void Task::UpdateStatusLocked(TaskStatus status) {
- status_ = status;
- status_cond_.notify_all();
-}
-
-void Task::WaitFor(TaskStatus status) {
- std::unique_lock<std::mutex> lock(mutex_);
- status_cond_.wait(lock, [=]{
- return status_ == status;
- });
-}
-
-} // namespace speedtest
diff --git a/speedtest/task.h b/speedtest/task.h
deleted file mode 100644
index 429b078..0000000
--- a/speedtest/task.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SPEEDTEST_TASK_H
-#define SPEEDTEST_TASK_H
-
-#include <condition_variable>
-#include <functional>
-#include <memory>
-#include <mutex>
-#include <thread>
-
-namespace speedtest {
-
-enum class TaskStatus {
- NOT_STARTED,
- RUNNING,
- STOPPING,
- STOPPED
-};
-
-const char *AsString(TaskStatus status);
-
-class Task {
- public:
- struct Options {
- bool verbose = false;
- };
-
- explicit Task(const Options &options);
- virtual ~Task();
-
- void Run();
- void Stop();
-
- TaskStatus GetStatus() const;
- long GetStartTime() const;
- long GetEndTime() const;
- long GetRunningTimeMicros() const;
- void WaitForEnd();
-
- protected:
- virtual void RunInternal() = 0;
- virtual void StopInternal() {}
-
- private:
- // Only call with mutex_
- void UpdateStatusLocked(TaskStatus status);
-
- void WaitFor(TaskStatus status);
-
- mutable std::mutex mutex_;
- std::thread runner_;
- std::thread stopper_;
- std::condition_variable status_cond_;
- TaskStatus status_;
- long start_time_;
- long end_time_;
-
- // disallowed
- Task(const Task &) = delete;
- void operator=(const Task &) = delete;
-};
-
-} // namespace speedtest
-
-#endif //SPEEDTEST_TASK_H
diff --git a/speedtest/timed_runner.cc b/speedtest/timed_runner.cc
deleted file mode 100644
index bf7c4cc..0000000
--- a/speedtest/timed_runner.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "timed_runner.h"
-
-#include <cassert>
-#include <thread>
-
-namespace speedtest {
-
-void RunTimed(Task *task, long millis) {
- assert(task);
- task->Run();
- std::thread timer([=] {
- std::this_thread::sleep_for(
- std::chrono::milliseconds(millis));
- task->Stop();
- });
- timer.join();
-}
-
-} // namespace speedtest
diff --git a/speedtest/timed_runner.h b/speedtest/timed_runner.h
index 02e673f..1a474c1 100644
--- a/speedtest/timed_runner.h
+++ b/speedtest/timed_runner.h
@@ -14,16 +14,34 @@
* limitations under the License.
*/
-#ifndef SPEEDTEST_RUNNER_H
-#define SPEEDTEST_RUNNER_H
+#ifndef SPEEDTEST_TIMED_RUNNER_H
+#define SPEEDTEST_TIMED_RUNNER_H
-#include "task.h"
+#include <future>
+#include <thread>
+#include <type_traits>
+#include "utils.h"
namespace speedtest {
-// Run a task for a set duration
-void RunTimed(Task *task, long millis);
+template <typename F>
+typename std::result_of<F(std::atomic_bool *)>::type
+RunTimed(F &&fn, std::atomic_bool *cancel, long timeout_millis) {
+ std::atomic_bool local_cancel(false);
+ long start_time = SystemTimeMicros();
+ long end_time = start_time + timeout_millis * 1000;
+ auto fut = ReallyAsync(std::forward<F>(fn), &local_cancel);
+ std::thread timer([&]{
+ while (!*cancel && SystemTimeMicros() <= end_time) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ local_cancel = true;
+ });
+ timer.join();
+ fut.wait();
+ return fut.get();
+}
-} // namespace speedtest
+} // namespace
-#endif // SPEEDTEST_RUNNER_H
+#endif // SPEEDTEST_TIMED_RUNNER_H
diff --git a/speedtest/transfer_runner.cc b/speedtest/transfer_runner.cc
index d37f087..5d1c241 100644
--- a/speedtest/transfer_runner.cc
+++ b/speedtest/transfer_runner.cc
@@ -17,12 +17,8 @@
#include "transfer_runner.h"
#include <algorithm>
-#include <cassert>
#include <chrono>
#include <iostream>
-#include <thread>
-#include "transfer_task.h"
-#include "utils.h"
namespace speedtest {
namespace {
@@ -31,141 +27,36 @@
} // namespace
-TransferRunner::TransferRunner(const Options &options)
- : Task(options),
- options_(options) {
- if (options_.interval_millis <= 0) {
- options_.interval_millis = kDefaultIntervalMillis;
- }
-}
-
-void TransferRunner::RunInternal() {
- threads_.clear();
- intervals_.clear();
-
- // sentinel value of all zeroes
- intervals_.emplace_back();
-
- // If progress updates are created add a thread to send updates
- if (options_.progress_fn && options_.progress_millis > 0) {
- if (options_.verbose) {
- std::cout << "Progress updates every "
- << options_.progress_millis << " ms\n";
- }
- threads_.emplace_back([&] {
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.progress_millis));
- while (GetStatus() == TaskStatus::RUNNING) {
- Interval progress = GetLastInterval();
- options_.progress_fn(progress);
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.progress_millis));
- }
- Interval progress = GetLastInterval();
- options_.progress_fn(progress);
- });
- } else if (options_.verbose) {
- std::cout << "No progress updates\n";
- }
-
- // Updating thread
- if (options_.verbose) {
- std::cout << "Transfer runner updates every "
- << options_.interval_millis << " ms\n";
- }
- threads_.emplace_back([&] {
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.interval_millis));
- while (GetStatus() == TaskStatus::RUNNING) {
- const Interval &interval = AddInterval();
- if (interval.running_time > options_.max_runtime * 1000) {
- Stop();
- return;
- }
- if (interval.running_time >= options_.min_runtime * 1000 &&
- interval.long_megabits > 0 &&
- interval.short_megabits > 0) {
- double speed_variance = variance(interval.short_megabits,
- interval.long_megabits);
- if (speed_variance <= options_.max_variance) {
- Stop();
- return;
- }
- }
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.interval_millis));
- }
- });
-
- options_.task->Run();
-}
-
-void TransferRunner::StopInternal() {
- options_.task->Stop();
- options_.task->WaitForEnd();
- std::for_each(threads_.begin(), threads_.end(), [](std::thread &t) {
- t.join();
- });
- threads_.clear();
-}
-
-const Interval &TransferRunner::AddInterval() {
- std::lock_guard <std::mutex> lock(mutex_);
- intervals_.emplace_back();
- Interval &interval = intervals_[intervals_.size() - 1];
- interval.running_time = options_.task->GetRunningTimeMicros();
- interval.bytes = options_.task->bytes_transferred();
- if (options_.exponential_moving_average) {
- interval.short_megabits = GetShortEma(options_.min_intervals);
- interval.long_megabits = GetLongEma(options_.max_intervals);
- } else {
- interval.short_megabits = GetSimpleAverage(options_.min_intervals);
- interval.long_megabits = GetSimpleAverage(options_.max_intervals);
- }
- speed_ = interval.long_megabits;
- return intervals_.back();
-}
-
-Interval TransferRunner::GetLastInterval() const {
- std::lock_guard <std::mutex> lock(mutex_);
- return intervals_.back();
-}
-
-double TransferRunner::GetSpeedInMegabits() const {
- std::lock_guard <std::mutex> lock(mutex_);
- return speed_;
-}
-
-double TransferRunner::GetShortEma(int num_intervals) {
- if (intervals_.empty() || num_intervals <= 0) {
+double GetShortEma(std::vector<Bucket> *buckets, int num_buckets) {
+ if (buckets->empty() || num_buckets <= 0) {
return 0.0;
}
- Interval last_interval = GetLastInterval();
- double percent = 2.0d / (num_intervals + 1);
- return GetSimpleAverage(1) * percent +
- last_interval.short_megabits * (1 - percent);
+ const Bucket &last_bucket = buckets->back();
+ double percent = 2.0d / (num_buckets + 1);
+ return GetSimpleAverage(buckets, 1) * percent +
+ last_bucket.short_megabits * (1 - percent);
}
-double TransferRunner::GetLongEma(int num_intervals) {
- if (intervals_.empty() || num_intervals <= 0) {
+double GetLongEma(std::vector<Bucket> *buckets, int num_buckets) {
+ if (buckets->empty() || num_buckets <= 0) {
return 0.0;
}
- Interval last_interval = GetLastInterval();
- double percent = 2.0d / (num_intervals + 1);
- return GetSimpleAverage(1) * percent +
- last_interval.long_megabits * (1 - percent);
+ const Bucket &last_bucket = buckets->back();
+ double percent = 2.0d / (num_buckets + 1);
+ return GetSimpleAverage(buckets, 1) * percent +
+ last_bucket.long_megabits * (1 - percent);
}
-double TransferRunner::GetSimpleAverage(int num_intervals) {
- if (intervals_.empty() || num_intervals <= 0) {
+double GetSimpleAverage(std::vector<Bucket> *buckets, int num_buckets) {
+ if (buckets->empty() || num_buckets <= 0) {
return 0.0;
}
- int end_index = intervals_.size() - 1;
- int start_index = std::max(0, end_index - num_intervals);
- const Interval &end = intervals_[end_index];
- const Interval &start = intervals_[start_index];
- return ToMegabits(end.bytes - start.bytes,
- end.running_time - start.running_time);
+ int end_index = buckets->size() - 1;
+ int start_index = std::max(0, end_index - num_buckets);
+ const Bucket &end = (*buckets)[end_index];
+ const Bucket &start = (*buckets)[start_index];
+ return ToMegabits(end.total_bytes - start.total_bytes,
+ end.start_time - start.start_time);
}
} // namespace
diff --git a/speedtest/transfer_runner.h b/speedtest/transfer_runner.h
index 793c8ec..4fb0620 100644
--- a/speedtest/transfer_runner.h
+++ b/speedtest/transfer_runner.h
@@ -18,67 +18,164 @@
#define SPEEDTEST_TRANSFER_RUNNER_H
#include <functional>
+#include <iostream>
+#include <memory>
#include <mutex>
#include <thread>
#include <vector>
-#include "task.h"
-#include "transfer_task.h"
+#include "status.h"
+#include "utils.h"
namespace speedtest {
-struct Interval {
- long bytes = 0;
- long running_time = 0;
+struct Bucket {
+ long total_bytes = 0;
+ long start_time = 0;
double short_megabits = 0.0;
double long_megabits = 0.0;
};
+struct TransferOptions {
+ bool verbose = false;
+ long min_runtime_millis = 0;
+ long max_runtime_millis = 0;
+ long interval_millis = 0;
+ long progress_millis = 0;
+ int min_intervals = 0;
+ int max_intervals = 0;
+ double max_variance = 0.0;
+ bool exponential_moving_average = false;
+ std::function<void(const Bucket)> progress_fn;
+};
+
+struct TransferResult {
+ long start_time;
+ long end_time;
+ Status status;
+ std::vector<Bucket> buckets;
+ double speed_mbps;
+ long total_bytes;
+};
+
+double GetShortEma(std::vector<Bucket> *buckets, int num_buckets);
+double GetLongEma(std::vector<Bucket> *buckets, int num_intervals);
+double GetSimpleAverage(std::vector<Bucket> *buckets, int num_intervals);
+
// Run a variable length transfer test using two moving averages.
// The test runs between min_runtime and max_runtime and otherwise
// ends when the speed is "stable" meaning the two moving averages
// are relatively close to one another.
-class TransferRunner : public Task {
- public:
- struct Options : public Task::Options {
- TransferTask *task = nullptr;
- int min_runtime = 0;
- int max_runtime = 0;
- int interval_millis = 0;
- int progress_millis = 0;
- int min_intervals = 0;
- int max_intervals = 0;
- double max_variance = 0.0;
- bool exponential_moving_average = false;
- std::function<void(Interval)> progress_fn;
- };
+template <typename F>
+TransferResult
+RunTransfer(F &&fn, std::atomic_bool *cancel, TransferOptions options) {
+ TransferResult result;
+ result.start_time = SystemTimeMicros();
- explicit TransferRunner(const Options &options);
+ // sentinel value of all zeroes
+ result.buckets.emplace_back();
- double GetSpeedInMegabits() const;
- Interval GetLastInterval() const;
+ // If progress updates are created add a thread to send updates
+ std::thread progress;
+ std::atomic_bool local_cancel(false);
+ if (options.progress_fn && options.progress_millis > 0) {
+ if (options.verbose) {
+ std::cout << "Progress updates every "
+ << options.progress_millis << " ms\n";
+ }
+ progress = std::thread([&] {
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(options.progress_millis));
+ while (!local_cancel) {
+ options.progress_fn(result.buckets.back());
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(options.progress_millis));
+ }
+ options.progress_fn(result.buckets.back());
+ });
+ } else if (options.verbose) {
+ std::cout << "No progress updates\n";
+ }
- protected:
- void RunInternal() override;
- void StopInternal() override;
+ // Updating thread
+ if (options.verbose) {
+ std::cout << "Transfer runner updates every "
+ << options.interval_millis << " ms\n";
+ }
+ long min_runtime_micros = options.min_runtime_millis * 1000;
+ long max_runtime_micros = options.max_runtime_millis * 1000;
+ std::mutex mutex;
+ std::thread updater([&] {
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(options.interval_millis));
+ while (!local_cancel) {
+ if (*cancel) {
+ local_cancel = true;
+ break;
+ }
- private:
- const Interval &AddInterval();
- double GetSimpleAverage(int num_intervals);
- double GetShortEma(int num_intervals);
- double GetLongEma(int num_intervals);
+ Bucket last_bucket;
+ long running_time = SystemTimeMicros() - result.start_time;
+ {
+ std::lock_guard <std::mutex> lock(mutex);
+ result.buckets.emplace_back();
+ Bucket &bucket = result.buckets.back();
+ bucket.start_time = running_time;
+ bucket.total_bytes = fn.get().bytes_transferred();
+ result.total_bytes = bucket.total_bytes;
+ if (options.exponential_moving_average) {
+ bucket.short_megabits = GetShortEma(&result.buckets,
+ options.min_intervals);
+ bucket.long_megabits = GetLongEma(&result.buckets,
+ options.max_intervals);
+ } else {
+ bucket.short_megabits = GetSimpleAverage(&result.buckets,
+ options.min_intervals);
+ bucket.long_megabits = GetSimpleAverage(&result.buckets,
+ options.max_intervals);
+ }
+ result.speed_mbps = bucket.long_megabits;
+ last_bucket = result.buckets.back();
+ }
- Options options_;
+ if (running_time > max_runtime_micros) {
+ local_cancel = true;
+ break;
+ }
+ if (running_time > min_runtime_micros &&
+ last_bucket.short_megabits > 0 &&
+ last_bucket.long_megabits > 0) {
+ double speed_variance = variance(last_bucket.short_megabits,
+ last_bucket.long_megabits);
+ if (speed_variance <= options.max_variance) {
+ local_cancel = true;
+ break;
+ }
+ }
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(options.interval_millis));
+ }
+ });
- mutable std::mutex mutex_;
- std::vector<Interval> intervals_;
- std::vector<std::thread> threads_;
- double speed_;
+ // transfer task
+ std::thread task([&]{
+ fn(&local_cancel);
+ });
- // disallowed
- TransferRunner(const TransferRunner &) = delete;
- void operator=(const TransferRunner &) = delete;
-};
+ task.join();
+ updater.join();
+ if (progress.joinable()) {
+ progress.join();
+ }
+
+ if (*cancel) {
+ result.status = Status(StatusCode::ABORTED, "transfer runner aborted");
+ } else {
+ result.status = Status::OK;
+ }
+ result.end_time = SystemTimeMicros();
+ return result;
+}
} // namespace
-#endif //SPEEDTEST_TRANSFER_RUNNER_H
+#endif // SPEEDTEST_TRANSFER_RUNNER_H
diff --git a/speedtest/transfer_task.cc b/speedtest/transfer_task.cc
deleted file mode 100644
index d742d87..0000000
--- a/speedtest/transfer_task.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "transfer_task.h"
-
-#include <cassert>
-#include <thread>
-#include <vector>
-
-namespace speedtest {
-
-TransferTask::TransferTask(const Options &options)
- : HttpTask(options),
- bytes_transferred_(0),
- requests_started_(0),
- requests_ended_(0) {
- assert(options.num_transfers > 0);
-}
-
-void TransferTask::ResetCounters() {
- bytes_transferred_ = 0;
- requests_started_ = 0;
- requests_ended_ = 0;
-}
-
-void TransferTask::StartRequest() {
- requests_started_++;
-}
-
-void TransferTask::EndRequest() {
- requests_ended_++;
-}
-
-void TransferTask::TransferBytes(long bytes) {
- bytes_transferred_ += bytes;
-}
-
-} // namespace speedtest
diff --git a/speedtest/transfer_task.h b/speedtest/transfer_task.h
deleted file mode 100644
index 83cff9e..0000000
--- a/speedtest/transfer_task.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SPEEDTEST_TRANSFER_TEST_H
-#define SPEEDTEST_TRANSFER_TEST_H
-
-#include <atomic>
-#include "http_task.h"
-
-namespace speedtest {
-
-class TransferTask : public HttpTask {
- public:
- struct Options : HttpTask::Options {
- int num_transfers = 0;
- };
-
- explicit TransferTask(const Options &options);
-
- long bytes_transferred() const { return bytes_transferred_; }
- long requests_started() const { return requests_started_; }
- long requests_ended() const { return requests_ended_; }
-
- protected:
- void ResetCounters();
- void StartRequest();
- void EndRequest();
- void TransferBytes(long bytes);
-
- private:
- std::atomic_long bytes_transferred_;
- std::atomic_int requests_started_;
- std::atomic_int requests_ended_;
-
- // disallowed
- TransferTask(const TransferTask &) = delete;
- void operator=(const TransferTask &) = delete;
-};
-
-} // namespace speedtest
-
-#endif // SPEEDTEST_TRANSFER_TEST_H
diff --git a/speedtest/upload.cc b/speedtest/upload.cc
new file mode 100644
index 0000000..9760078
--- /dev/null
+++ b/speedtest/upload.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "upload.h"
+
+#include <string>
+#include <thread>
+#include <vector>
+
+namespace speedtest {
+
+Upload::Upload(const Options &options)
+ : options_(options),
+ start_time_(0),
+ end_time_(0),
+ bytes_transferred_(0) {
+}
+
+Upload::Result Upload::operator()(std::atomic_bool *cancel) {
+ start_time_ = SystemTimeMicros();
+ bytes_transferred_ = 0;
+
+ if (!cancel) {
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status(StatusCode::FAILED_PRECONDITION, "cancel is null"));
+ }
+
+ std::vector<std::thread> threads;
+ for (int i = 0; i < options_.num_transfers; ++i) {
+ threads.emplace_back([=]{
+ http::Request::Ptr upload = options_.request_factory(i);
+ while (!*cancel) {
+ long uploaded = 0;
+ upload->set_param("i", to_string(i));
+ upload->set_param("time", to_string(SystemTimeMicros()));
+ upload->set_progress_fn([&](curl_off_t,
+ curl_off_t,
+ curl_off_t,
+ curl_off_t ulnow) -> bool {
+ if (ulnow > uploaded) {
+ bytes_transferred_ += ulnow - uploaded;
+ uploaded = ulnow;
+ }
+ return *cancel;
+ });
+
+ // disable the Expect header as the server isn't expecting it (perhaps
+ // it should?). If the server isn't then libcurl waits for 1 second
+ // before sending the data anyway. So sending this header eliminated
+ // the 1 second delay.
+ upload->set_header("Expect", "");
+
+ upload->Post(options_.payload->c_str(), options_.payload->size());
+ upload->Reset();
+ }
+ });
+ }
+
+ for (std::thread &thread : threads) {
+ if (thread.joinable()) {
+ thread.join();
+ }
+ }
+
+ end_time_ = SystemTimeMicros();
+ return GetResult(Status::OK);
+}
+
+Upload::Result Upload::GetResult(Status status) const {
+ Upload::Result result;
+ result.start_time = start_time_;
+ result.end_time = end_time_;
+ result.status = status;
+ result.bytes_transferred = bytes_transferred_;
+ return result;
+}
+
+} // namespace
diff --git a/speedtest/upload.h b/speedtest/upload.h
new file mode 100644
index 0000000..4a5dd8a
--- /dev/null
+++ b/speedtest/upload.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SPEEDTEST_UPLOAD_H
+#define SPEEDTEST_UPLOAD_H
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include "request.h"
+#include "status.h"
+#include "utils.h"
+
+namespace speedtest {
+
+class Upload {
+ public:
+ struct Options {
+ bool verbose;
+ std::function<http::Request::Ptr(int)> request_factory;
+ int num_transfers;
+ std::shared_ptr<std::string> payload;
+ };
+
+ struct Result {
+ long start_time;
+ long end_time;
+ Status status;
+ long bytes_transferred;
+ };
+
+ explicit Upload(const Options &options);
+
+ Result operator()(std::atomic_bool *cancel);
+
+ long start_time() const { return start_time_; }
+ long end_time() const { return end_time_; }
+ long bytes_transferred() const { return bytes_transferred_; }
+
+ private:
+ Result GetResult(Status status) const;
+
+ Options options_;
+ std::atomic_long start_time_;
+ std::atomic_long end_time_;
+ std::atomic_long bytes_transferred_;
+
+ DISALLOW_COPY_AND_ASSIGN(Upload);
+};
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_UPLOAD_H
diff --git a/speedtest/upload_task.cc b/speedtest/upload_task.cc
deleted file mode 100644
index 251fc41..0000000
--- a/speedtest/upload_task.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "upload_task.h"
-
-#include <algorithm>
-#include <cassert>
-#include <iostream>
-#include "utils.h"
-
-namespace speedtest {
-
-UploadTask::UploadTask(const Options &options)
- : TransferTask(options),
- options_(options) {
- assert(options_.payload);
- assert(options_.payload->size() > 0);
-}
-
-void UploadTask::RunInternal() {
- ResetCounters();
- threads_.clear();
- if (options_.verbose) {
- std::cout << "Uploading " << options_.num_transfers
- << " threads with " << options_.payload->size() << " bytes\n";
- }
- for (int i = 0; i < options_.num_transfers; ++i) {
- threads_.emplace_back([=]{
- RunUpload(i);
- });
- }
-}
-
-void UploadTask::StopInternal() {
- std::for_each(threads_.begin(), threads_.end(), [](std::thread &t) {
- t.join();
- });
-}
-
-void UploadTask::RunUpload(int id) {
- http::Request::Ptr upload = options_.request_factory(id);
- while (GetStatus() == TaskStatus::RUNNING) {
- long uploaded = 0;
- upload->set_param("i", to_string(id));
- upload->set_param("time", to_string(SystemTimeMicros()));
- upload->set_progress_fn([&](curl_off_t,
- curl_off_t,
- curl_off_t,
- curl_off_t ulnow) -> bool {
- if (ulnow > uploaded) {
- TransferBytes(ulnow - uploaded);
- uploaded = ulnow;
- }
- return GetStatus() != TaskStatus::RUNNING;
- });
-
- // disable the Expect header as the server isn't expecting it (perhaps
- // it should?). If the server isn't then libcurl waits for 1 second
- // before sending the data anyway. So sending this header eliminated
- // the 1 second delay.
- upload->set_header("Expect", "");
-
- StartRequest();
- upload->Post(options_.payload->c_str(), options_.payload->size());
- EndRequest();
- upload->Reset();
- }
-}
-
-} // namespace speedtest
diff --git a/speedtest/upload_task.h b/speedtest/upload_task.h
deleted file mode 100644
index 323f904..0000000
--- a/speedtest/upload_task.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SPEEDTEST_UPLOAD_TASK_H
-#define SPEEDTEST_UPLOAD_TASK_H
-
-#include <memory>
-#include <string>
-#include <thread>
-#include <vector>
-#include "transfer_task.h"
-
-namespace speedtest {
-
-class UploadTask : public TransferTask {
- public:
- struct Options : TransferTask::Options {
- std::shared_ptr<std::string> payload;
- };
-
- explicit UploadTask(const Options &options);
-
- protected:
- void RunInternal() override;
- void StopInternal() override;
-
- private:
- void RunUpload(int id);
-
- Options options_;
- std::vector<std::thread> threads_;
-
- // disallowed
- UploadTask(const UploadTask &) = delete;
- void operator=(const UploadTask &) = delete;
-};
-
-} // namespace speedtest
-
-#endif // SPEEDTEST_UPLOAD_TASK_H
diff --git a/speedtest/utils.cc b/speedtest/utils.cc
index 8144174..d3c00d0 100644
--- a/speedtest/utils.cc
+++ b/speedtest/utils.cc
@@ -62,6 +62,18 @@
return (8.0d * bytes) / micros;
}
+std::string ToMillis(long micros) {
+ double millis = micros / 1000.0d;
+ if (millis < 1) {
+ return round(millis, 3);
+ } else if (millis < 10) {
+ return round(millis, 2);
+ } else if (millis < 1000) {
+ return round(millis, 1);
+ }
+ return round(millis, 0);
+}
+
bool ParseInt(const std::string &str, int *result) {
if (!result) {
return false;
@@ -95,4 +107,16 @@
RightTrim(s);
}
+std::shared_ptr<std::string> MakeRandomData(size_t size) {
+ std::random_device rd;
+ std::default_random_engine random_engine(rd());
+ std::uniform_int_distribution<char> uniform_dist(1, 255);
+ auto random_data = std::make_shared<std::string>();
+ random_data->resize(size);
+ for (size_t i = 0; i < size; ++i) {
+ (*random_data)[i] = uniform_dist(random_engine);
+ }
+ return std::move(random_data);
+}
+
} // namespace speedtest
diff --git a/speedtest/utils.h b/speedtest/utils.h
index 7e8d251..f7471b7 100644
--- a/speedtest/utils.h
+++ b/speedtest/utils.h
@@ -17,10 +17,23 @@
#ifndef SPEEDTEST_UTILS_H
#define SPEEDTEST_UTILS_H
+#include <future>
+#include <memory>
#include <string>
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&) = delete; \
+ TypeName& operator=(const TypeName&) = delete
+
namespace speedtest {
+template <typename F, typename... Ts>
+inline std::future<typename std::result_of<F(Ts...)>::type>
+ReallyAsync(F&& f, Ts&&... params) {
+ return std::async(std::launch::async, std::forward<F>(f),
+ std::forward<Ts>(params)...);
+}
+
// Return relative time in microseconds
// This isn't convertible to an absolute date and time
long SystemTimeMicros();
@@ -37,6 +50,9 @@
// Convert bytes and time in micros to speed in megabits
double ToMegabits(long bytes, long micros);
+// Convert to milliseconds, round to at least 3 significant figures.
+std::string ToMillis(long micros);
+
// Parse an int.
// If successful, write result to result and return true.
// If result is null or the int can't be parsed, return false.
@@ -54,6 +70,8 @@
// Caller retains ownership
void Trim(std::string *s);
+std::shared_ptr<std::string> MakeRandomData(size_t size);
+
} // namespace speedtst
#endif // SPEEDTEST_UTILS_H
diff --git a/taxonomy/.gitignore b/taxonomy/.gitignore
index 796b96d..b1c9bfa 100644
--- a/taxonomy/.gitignore
+++ b/taxonomy/.gitignore
@@ -1 +1,2 @@
/build
+tax_signature
diff --git a/taxonomy/dhcp.py b/taxonomy/dhcp.py
index 7a503bc..faa9a39 100644
--- a/taxonomy/dhcp.py
+++ b/taxonomy/dhcp.py
@@ -41,6 +41,8 @@
'1,3,6': ['dashbutton'],
+ '1,3,6,28': ['ecobee'],
+
'1,3,6,12,15,17,28,40,41,42': ['epsonprinter'],
'6,3,1,15,66,67,13,44,12': ['hpprinter'],
@@ -52,22 +54,28 @@
'1,3,6,15,119,95,252,44,46,47': ['ipodtouch1'],
+ '252,3,42,15,6,1,12': ['lgtv'],
+
'1,3,6,15,119,95,252,44,46,101': ['macos'],
'1,3,6,15,119,95,252,44,46': ['macos'],
'1,121,3,6,15,119,252,95,44,46': ['macos'],
+ '58,59,6,15,51,54,1,3': ['panasonictv'],
+
'1,3,15,6': ['playstation'],
'1,3,6,15,12': ['roku'],
'1,3,6,12,15,28,42,125': ['samsungtv'],
+ '1,3,6,12,15,28,42': ['visiotv'],
'1,3,6,12,15,28,40,41,42': ['visiotv', 'kindle'],
'1,3,6,15,28,33': ['wii'],
'1,3,6,15': ['wii', 'xbox'],
- '1,15,3,6,44,46,47,31,33,121,249,252,43': ['windows-phone'],
+ '1,15,3,6,44,46,47,31,33,121,249,252,43': ['windows-phone', 'windows'],
+ '1,3,6,15,31,33,43,44,46,47,121,249,252': ['windows'],
}
diff --git a/taxonomy/ethernet.py b/taxonomy/ethernet.py
index 0635073..ca10bee 100644
--- a/taxonomy/ethernet.py
+++ b/taxonomy/ethernet.py
@@ -28,12 +28,17 @@
'10:ae:60': ['amazon'],
'28:ef:01': ['amazon'],
'74:75:48': ['amazon'],
+ '74:c2:46': ['amazon'],
'84:d6:d0': ['amazon'],
'a0:02:dc': ['amazon'],
'f0:27:2d': ['amazon'],
'f0:4f:7c': ['amazon'],
'f0:a2:25': ['amazon'],
+ '08:60:6e': ['asus'],
+ '08:62:66': ['asus'],
+ '1c:87:2c': ['asus'],
+ '2c:56:dc': ['asus'],
'30:85:a9': ['asus'],
'5c:ff:35': ['asus'],
'60:a4:4c': ['asus'],
@@ -41,6 +46,7 @@
'ac:22:0b': ['asus'],
'bc:ee:7b': ['asus'],
'd8:50:e6': ['asus'],
+ 'f8:32:e4': ['asus'],
'30:8c:fb': ['dropcam'],
@@ -53,17 +59,22 @@
# These are registered to AzureWave, but used for Chromecast v1.
'6c:ad:f8': ['azurewave', 'google'],
+ '80:d2:1d': ['azurewave', 'google'],
'b0:ee:45': ['azurewave', 'google'],
'd0:e7:82': ['azurewave', 'google'],
+ '44:61:32': ['ecobee'],
+
'00:23:76': ['htc'],
'00:ee:bd': ['htc'],
'18:87:96': ['htc'],
'1c:b0:94': ['htc'],
+ '2c:8a:72': ['htc'],
'38:e7:d8': ['htc'],
'50:2e:5c': ['htc'],
'64:a7:69': ['htc'],
'7c:61:93': ['htc'],
+ '80:01:84': ['htc'],
'84:7a:88': ['htc'],
'90:e7:c4': ['htc'],
'a0:f4:50': ['htc'],
@@ -75,11 +86,13 @@
'10:68:3f': ['lg'],
'2c:54:cf': ['lg'],
'34:fc:ef': ['lg'],
+ '3c:bd:d8': ['lg'],
'40:b0:fa': ['lg'],
'58:3f:54': ['lg'],
'64:89:9a': ['lg'],
'64:bc:0c': ['lg'],
'78:f8:82': ['lg'],
+ '88:07:4b': ['lg'],
'88:c9:d0': ['lg'],
'8c:3a:e3': ['lg'],
'a0:39:f7': ['lg'],
@@ -87,14 +100,21 @@
'bc:f5:ac': ['lg'],
'c4:43:8f': ['lg'],
'c4:9a:02': ['lg'],
+ 'cc:fa:00': ['lg'],
+ 'e8:5b:5b': ['lg'],
'f8:95:c7': ['lg'],
'f8:a9:d0': ['lg'],
+ '00:1d:d8': ['microsoft'],
'28:18:78': ['microsoft'],
'50:1a:c5': ['microsoft'],
+ '58:82:a8': ['microsoft'],
'60:45:bd': ['microsoft'],
+ '7c:1e:52': ['microsoft'],
'7c:ed:8d': ['microsoft'],
+ 'b4:ae:2b': ['microsoft'],
+ '14:1a:a3': ['motorola'],
'14:30:c6': ['motorola'],
'1c:56:fe': ['motorola'],
'24:da:9b': ['motorola'],
@@ -139,45 +159,68 @@
'00:27:09': ['nintendo'],
'34:af:2c': ['nintendo'],
+ 'c0:ee:fb': ['oneplus'],
+
+ '00:15:99': ['samsung'],
'00:26:37': ['samsung'],
'08:d4:2b': ['samsung'],
'08:ec:a9': ['samsung'],
'14:32:d1': ['samsung'],
'24:4b:81': ['samsung'],
+ '28:ba:b5': ['samsung'],
+ '2c:ae:2b': ['samsung'],
'30:19:66': ['samsung'],
'34:23:ba': ['samsung'],
+ '38:2d:e8': ['samsung'],
'38:aa:3c': ['samsung'],
+ '38:d4:0b': ['samsung'],
'3c:8b:fe': ['samsung'],
'40:0e:85': ['samsung'],
'48:5a:3f': ['samsung', 'wisol'],
+ '50:cc:f8': ['samsung'],
'54:88:0e': ['samsung'],
'5c:0a:5b': ['samsung'],
'5c:f6:dc': ['samsung'],
'6c:2f:2c': ['samsung'],
'6c:83:36': ['samsung'],
+ '78:40:e4': ['samsung'],
'78:d6:f0': ['samsung'],
+ '78:bd:bc': ['samsung'],
'80:65:6d': ['samsung'],
'84:11:9e': ['samsung'],
'84:25:db': ['samsung'],
+ '84:2e:27': ['samsung'],
'84:38:38': ['samsung'],
+ '84:55:a5': ['samsung'],
'88:32:9b': ['samsung'],
'8c:77:12': ['samsung'],
'90:18:7c': ['samsung'],
+ '90:f1:aa': ['samsung'],
'94:35:0a': ['samsung'],
+ '94:b1:0a': ['samsung'],
'a0:0b:ba': ['samsung'],
'a8:06:00': ['samsung'],
'ac:36:13': ['samsung'],
+ 'ac:5f:3e': ['samsung'],
'b0:df:3a': ['samsung'],
'b0:ec:71': ['samsung'],
'b4:07:f9': ['samsung'],
+ 'b4:79:a7': ['samsung'],
+ 'b8:5a:73': ['samsung'],
'bc:20:a4': ['samsung'],
+ 'bc:72:b1': ['samsung'],
+ 'bc:8c:cd': ['samsung'],
+ 'bc:e6:3f': ['samsung'],
'c0:bd:d1': ['samsung'],
'c4:42:02': ['samsung'],
+ 'c4:73:1e': ['samsung'],
'cc:07:ab': ['samsung'],
'cc:3a:61': ['samsung'],
'd0:22:be': ['samsung'],
'e0:99:71': ['samsung'],
+ 'e0:db:10': ['samsung'],
'e4:12:1d': ['samsung'],
+ 'e4:92:fb': ['samsung'],
'e8:3a:12': ['samsung'],
'e8:50:8b': ['samsung'],
'ec:1f:72': ['samsung'],
@@ -189,9 +232,12 @@
'28:0d:fc': ['sony'],
'30:17:c8': ['sony'],
'40:b8:37': ['sony'],
+ '58:48:22': ['sony'],
'b4:52:7e': ['sony'],
'00:24:e4': ['withings'],
+
+ '64:cc:2e': ['xiaomi'],
}
diff --git a/taxonomy/pcaptest.py b/taxonomy/pcaptest.py
index bb76e15..fd620d8 100644
--- a/taxonomy/pcaptest.py
+++ b/taxonomy/pcaptest.py
@@ -9,25 +9,13 @@
regression = [
# devices for which we have a pcap but have decided not to add
- # to the database, generally because the device is not common
- # enough.
- ('Unknown', './testdata/pcaps/Amazon Fire Phone 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/Amazon Fire Phone 5GHz Broadcast.pcap'),
- ('Unknown', './testdata/pcaps/Amazon Fire Phone 5GHz Specific.pcap'),
- ('Unknown', './testdata/pcaps/Amazon Fire Phone 5GHz.pcap'),
+ # to the database
('Unknown', './testdata/pcaps/ASUS Transformer TF300 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Blackberry Bold 9930 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Blackberry Bold 9930 5GHz.pcap'),
- ('Unknown', './testdata/pcaps/iPhone 2 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/iPhone 3 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/iPhone 3GS 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/iPhone 3GS 2.4GHz M137LL.pcap'),
('Unknown', './testdata/pcaps/HTC Evo 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/HTC Incredible 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/HTC Inspire 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/HTC One V 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/HTC One X 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/HTC One X 5GHz.pcap'),
('Unknown', './testdata/pcaps/HTC Sensation 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/HTC Thunderbolt 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/HTC Titan 2.4GHz.pcap'),
@@ -44,8 +32,6 @@
('Unknown', './testdata/pcaps/Motorola Droid Razr 5GHz XT910.pcap'),
('Unknown', './testdata/pcaps/Motorola Droid Razr Maxx 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Nexus One 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/Nokia Lumia 920 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/Nokia Lumia 920 5GHz.pcap'),
('Unknown', './testdata/pcaps/Samsung Charge 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Samsung Captivate 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Samsung Continuum 2.4GHz.pcap'),
@@ -55,12 +41,11 @@
('Unknown', './testdata/pcaps/Samsung Galaxy Tab 2 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Samsung Infuse 5GHz.pcap'),
('Unknown', './testdata/pcaps/Samsung Vibrant 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/Sony Xperia Z5 2.4GHz.pcap'),
- ('Unknown', './testdata/pcaps/Sony Xperia Z5 5GHz.pcap'),
- # Names which contain a slash ('/'), which Linux filenames do not
- # tolerate. Inferring the expected result from the filename doesn't
- # work for these, instead we add them explicitly.
+ # Names where the identified species doesn't exactly match the filename,
+ # usually because multiple devices are too similar to distinguish. We name
+ # the file for the specific device which was captured, and add an entry
+ # here for the best identification which we can manage.
('iPad (1st/2nd gen)', './testdata/pcaps/iPad 1st gen 5GHz.pcap'),
('iPad (1st/2nd gen)', './testdata/pcaps/iPad 2nd gen 5GHz.pcap'),
('iPad (4th gen or Air)', './testdata/pcaps/iPad (4th gen) 5GHz.pcap'),
@@ -81,6 +66,10 @@
('Samsung Galaxy Note or S2+', './testdata/pcaps/Samsung Galaxy Note 5GHz.pcap'),
('Samsung Galaxy S2 or Infuse', './testdata/pcaps/Samsung Galaxy S2 2.4GHz.pcap'),
('Samsung Galaxy S2 or Infuse', './testdata/pcaps/Samsung Infuse 2.4GHz.pcap'),
+ ('Sony Xperia Z4/Z5', './testdata/pcaps/Sony Xperia Z5 5GHz.pcap'),
+ ('Sony Xperia Z4/Z5', './testdata/pcaps/Sony Xperia Z5 2.4GHz.pcap'),
+ ('Sony Xperia Z4/Z5', './testdata/pcaps/Sony Xperia Z4 Tablet 5GHz.pcap'),
+ ('Sony Xperia Z4/Z5', './testdata/pcaps/Sony Xperia Z4 Tablet 2.4GHz.pcap'),
]
diff --git a/taxonomy/testdata/dhcp.leases b/taxonomy/testdata/dhcp.leases
index 64aa1ca..6f13edf 100644
--- a/taxonomy/testdata/dhcp.leases
+++ b/taxonomy/testdata/dhcp.leases
@@ -46,3 +46,6 @@
1432237016 8c:2d:aa:9c:ce:0f 192.168.42.36 iPood-5
1432237016 dc:86:d8:a0:c8:de 192.168.42.37 iPhoone-5c
1432237016 54:ae:27:32:ef:7f 192.168.42.38 iPaad-Air-1
+1432237016 00:1e:c2:24:7f:10 192.168.42.39 iPhoone-2
+1432237016 00:23:12:99:30:93 192.168.42.39 iPhoone-3
+1432237016 34:c8:03:89:d3:e8 192.168.42.40 Nokia-Lumia-920
diff --git a/taxonomy/testdata/dhcp.signatures b/taxonomy/testdata/dhcp.signatures
index 4831315..06b641b 100644
--- a/taxonomy/testdata/dhcp.signatures
+++ b/taxonomy/testdata/dhcp.signatures
@@ -38,3 +38,6 @@
8c:2d:aa:9c:ce:0f 1,3,6,15,119,252
dc:86:d8:a0:c8:de 1,3,6,15,119,252
54:ae:27:32:ef:7f 1,3,6,15,119,252
+00:1e:c2:24:7f:10 1,3,6,15,119,252
+00:23:12:99:30:93 1,3,6,15,119,252
+34:c8:03:89:d3:e8 1,15,3,6,44,46,47,31,33,121,249,252,43
diff --git a/taxonomy/testdata/pcaps/Oneplus X 2.4GHz.pcap b/taxonomy/testdata/pcaps/Oneplus X 2.4GHz.pcap
new file mode 100644
index 0000000..54f6f8f
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Oneplus X 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Samsung Galaxy S7 2.4GHz.pcap b/taxonomy/testdata/pcaps/Samsung Galaxy S7 2.4GHz.pcap
new file mode 100644
index 0000000..8bafff8
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Samsung Galaxy S7 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Samsung Galaxy S7 5GHz.pcap b/taxonomy/testdata/pcaps/Samsung Galaxy S7 5GHz.pcap
new file mode 100644
index 0000000..647546b
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Samsung Galaxy S7 5GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Samsung Galaxy S7 Edge 2.4GHz.pcap b/taxonomy/testdata/pcaps/Samsung Galaxy S7 Edge 2.4GHz.pcap
new file mode 100644
index 0000000..908793a
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Samsung Galaxy S7 Edge 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Samsung Galaxy S7 Edge 5GHz.pcap b/taxonomy/testdata/pcaps/Samsung Galaxy S7 Edge 5GHz.pcap
new file mode 100644
index 0000000..03d468b
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Samsung Galaxy S7 Edge 5GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Xiaomi Redmi 3 2.4GHz.pcap b/taxonomy/testdata/pcaps/Xiaomi Redmi 3 2.4GHz.pcap
new file mode 100644
index 0000000..105d560
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Xiaomi Redmi 3 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index 3a12713..109095c 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -26,16 +26,25 @@
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|os:dashbutton':
('BCM43362', 'Amazon Dash Button', '2.4GHz'),
- 'wifi4|probe:0,1,50|assoc:0,1,50,48,221(0050f2,2)|os:kindle':
+ 'wifi4|probe:0,1,3,45,221(0050f2,8),191,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31805120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe|assoc:0,1,48,45,221(0050f2,2),191,127,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31805120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:00000a0200000040|oui:amazon':
+ ('', 'Amazon Fire Phone', '5GHz'),
+ 'wifi4|probe:0,1,45,221(0050f2,8),191,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe|assoc:0,1,48,45,221(0050f2,2),191,127,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31805120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:00000a0200000040|oui:amazon':
+ ('', 'Amazon Fire Phone', '5GHz'),
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a0200000000|oui:amazon':
+ ('', 'Amazon Fire Phone', '2.4GHz'),
+
+ 'wifi4|probe:0,1,45,htcap:11ee,htagg:02,htmcs:0000ffff|assoc:0,1,33,36,48,221(0050f2,2),45,127,htcap:11ee,htagg:02,htmcs:0000ffff,txpow:0e00,extcap:01|oui:amazon':
+ ('', 'Amazon Kindle', '5GHz'),
+ 'wifi4|probe:0,1,50|assoc:0,1,50,48,221(0050f2,2)|oui:amazon':
('', 'Amazon Kindle', '2.4GHz'),
- 'wifi|probe:0,1,50,45,htcap:01ac,htagg:02,htmcs:0000ffff|assoc:0,1,50,48,221(0050f2,2),45,127,htcap:01ac,htagg:02,htmcs:0000ffff|os:kindle':
+ 'wifi4|probe:0,1,50,45,htcap:01ac,htagg:02,htmcs:0000ffff|assoc:0,1,50,48,221(0050f2,2),45,127,htcap:01ac,htagg:02,htmcs:0000ffff,extcap:01|oui:amazon':
('', 'Amazon Kindle', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:1130,htagg:18,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),htcap:1130,htagg:18,htmcs:000000ff|oui:amazon':
('TI_WL1271', 'Amazon Kindle Fire 7" (2011 edition)', '2.4GHz'),
'wifi|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:KFASWI|assoc:0,1,50,45,127,221(0050f2,2),48,htcap:1172,htagg:03,htmcs:000000ff':
('', 'Amazon Kindle Fire 7" (2014 edition)', '2.4GHz'),
- 'wifi|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:KFFOWI|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff':
+ 'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:KFFOWI|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
('', 'Amazon Kindle Fire 7" (2015 edition)', '2.4GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:007e,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(001018,2),221(0050f2,2),htcap:007e,htagg:1b,htmcs:0000ffff,txpow:e50d|oui:amazon':
@@ -78,6 +87,12 @@
'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1502,extcap:0000000000000040|name:appletv':
('', 'Apple TV (4th gen)', '2.4GHz'),
+ 'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:BLU_DASH_M|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+ ('', 'BLU Dash M', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,127,107,221(0050f2,4),221(506f9a,9),221(506f9a,16),extcap:00000080,wps:BLU_STUDIO_5_0_C_HD|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:0100008000c6':
+ ('', 'BLU Studio 5.0.C HD', '2.4GHz'),
+
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:112c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:112c,htagg:19,htmcs:000000ff|os:brotherprinter':
('', 'Brother Printer', '2.4GHz'),
@@ -116,17 +131,30 @@
'wifi4|probe:0,1,3,45,50,127,191,htcap:0062,htagg:03,htmcs:00000000,vhtcap:33c07030,vhtrxmcs:0124fffc,vhttxmcs:0124fffc,extcap:0000000000000040|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:002c,htagg:03,htmcs:000000ff,extcap:0000000000000140|oui:google':
('Marvell_88W8887', 'Chromecast v2', '2.4GHz'),
- 'wifi|probe:0,1,50,45,htcap:002c,htagg:01,htmcs:000000ff|assoc:0,1,50,45,48,221(0050f2,2),htcap:002c,htagg:01,htmcs:000000ff|oui:dropcam':
+ 'wifi4|probe:0,1,45,htcap:106e,htagg:01,htmcs:000000ff|assoc:0,1,45,33,36,48,221(0050f2,2),htcap:106e,htagg:01,htmcs:000000ff,txpow:0e00|oui:dropcam':
+ ('', 'Dropcam', '5GHz'),
+ 'wifi4|probe:0,1,50,45,htcap:002c,htagg:01,htmcs:000000ff|assoc:0,1,50,45,48,221(0050f2,2),htcap:002c,htagg:01,htmcs:000000ff|oui:dropcam':
('', 'Dropcam', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,htcap:002c,htagg:01,htmcs:000000ff|assoc:0,1,50,45,48,221(0050f2,2),htcap:002c,htagg:01,htmcs:000000ff|oui:ecobee':
+ ('', 'ecobee thermostat', '2.4GHz'),
+
'wifi|probe:0,1,3,45,50,htcap:0162,htagg:00,htmcs:000000ff|assoc:0,1,45,48,127,50,221(0050f2,2),htcap:016e,htagg:1b,htmcs:000000ff|os:epsonprinter':
('', 'Epson Printer', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:182c,htagg:1b,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:182c,htagg:1b,htmcs:000000ff|os:epsonprinter':
+ ('', 'Epson Printer', '2.4GHz'),
+ 'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:epsonprinter':
+ ('', 'Epson Printer', '2.4GHz'),
'wifi|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|os:hpprinter':
('', 'HP Printer', '2.4GHz'),
- 'wifi|probe:0,1,3,45,50,htcap:0160,htagg:03,htmcs:000000ff|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:016c,htagg:03,htmcs:000000ff|os:hpprinter':
+ 'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:102c,htagg:1b,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:102c,htagg:1b,htmcs:000000ff|os:hpprinter':
('', 'HP Printer', '2.4GHz'),
- 'wifi|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:0020,htagg:1a,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(0050f2,2),221(506f9a,9),htcap:0020,htagg:1a,htmcs:000000ff|os:hpprinter':
+ 'wifi4|probe:0,1,3,45,50,htcap:0160,htagg:03,htmcs:000000ff|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:016c,htagg:03,htmcs:000000ff,extcap:00|os:hpprinter':
+ ('', 'HP Printer', '2.4GHz'),
+ 'wifi4|probe:0,1,3,45,50,htcap:0160,htagg:03,htmcs:000000ff|assoc:0,1,45,48,127,50,221(0050f2,2),htcap:016c,htagg:03,htmcs:000000ff,extcap:00000000|os:hpprinter':
+ ('', 'HP Printer', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:0020,htagg:1a,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(0050f2,2),221(506f9a,9),htcap:0020,htagg:1a,htmcs:000000ff|os:hpprinter':
('', 'HP Printer', '2.4GHz'),
'wifi|probe:0,1,3,45,50,htcap:0060,htagg:03,htmcs:000000ff|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:006c,htagg:03,htmcs:000000ff|os:hpprinter':
('', 'HP Printer', '2.4GHz'),
@@ -163,6 +191,14 @@
'wifi4|probe:0,1,50,3,45,127,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:0000088001400040|assoc:0,1,50,33,36,45,127,107,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140|oui:htc':
('BCM4356', 'HTC One M9', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,3,221(0050f2,4),221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff,wps:_|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|oui:htc':
+ ('', 'HTC One V', '2.4GHz'),
+
+ 'wifi4|probe:0,1,45,htcap:0130,htagg:18,htmcs:000000ff|assoc:0,1,48,45,221(0050f2,2),htcap:013c,htagg:18,htmcs:000000ff|oui:htc':
+ ('', 'HTC One X', '5GHz'),
+ 'wifi4|probe:0,1,50,45,htcap:0130,htagg:18,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),htcap:013c,htagg:18,htmcs:000000ff|oui:htc':
+ ('', 'HTC One X', '2.4GHz'),
+
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:080c,htagg:1b,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:080c,htagg:1b,htmcs:000000ff,txpow:1008|os:ios':
('BCM4329', 'iPad (1st/2nd gen)', '5GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0800,htagg:1b,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0800,htagg:1b,htmcs:000000ff,txpow:1008|os:ios':
@@ -180,7 +216,7 @@
('BCM4330', 'iPad (3rd gen)', '5GHz'),
'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0100,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100,htagg:19,htmcs:000000ff,txpow:180f|os:ios':
('BCM4330', 'iPad (3rd gen)', '5GHz'),
- 'wifi|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:0100|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100|os:ios':
+ 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:0100,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100,htagg:19,htmcs:000000ff,txpow:150c|os:ios':
('BCM4330', 'iPad (3rd gen)', '2.4GHz'),
'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:01fe,htagg:1b,htmcs:0000ffff,extcap:00000804|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01fe,htagg:1b,htmcs:0000ffff,txpow:e708|os:ios':
@@ -229,6 +265,15 @@
'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:01bc,htagg:1b,htmcs:0000ffff,extcap:00000804|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01bc,htagg:1b,htmcs:0000ffff,txpow:1603|os:ios':
('BCM4324', 'iPad Mini (2nd gen)', '2.4GHz'),
+ 'wifi4|probe:0,1,3,50|assoc:0,1,48,50|os:ios':
+ ('', 'iPhone 2', '2.4GHz'),
+
+ 'wifi4|probe:0,1,3,50|assoc:0,1,48,50,221(0050f2,2)|os:ios':
+ ('', 'iPhone 3', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,221(001018,2)|assoc:0,1,48,50,221(001018,2),221(0050f2,2)|os:ios':
+ ('', 'iPhone 3GS', '2.4GHz'),
+
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:1800,htagg:1b,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1800,htagg:1b,htmcs:000000ff|os:ios':
('BCM4329', 'iPhone 4', '2.4GHz'),
@@ -248,10 +293,14 @@
'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1805|os:ios':
('BCM4334', 'iPhone 5c', '5GHz'),
+ 'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,33,36,48,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1805|os:ios':
+ ('BCM4334', 'iPhone 5c', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1704|os:ios':
('BCM4334', 'iPhone 5c', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1704|os:ios':
('BCM4334', 'iPhone 5c', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1704|os:ios':
+ ('BCM4334', 'iPhone 5c', '2.4GHz'),
'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000804|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1603|os:ios':
('BCM4334', 'iPhone 5s', '5GHz'),
@@ -285,6 +334,8 @@
('BCM4350', 'iPhone 6s/6s+', '5GHz'),
'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f815832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0400088400000040|assoc:0,1,33,36,48,70,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|os:ios':
('BCM4350', 'iPhone 6s/6s+', '5GHz'),
+ 'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f815832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0400088400000040|assoc:0,1,33,36,48,70,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f815832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e002,extcap:0400000000000040|os:ios':
+ ('BCM4350', 'iPhone 6s/6s+', '5GHz'),
'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0400088400000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|os:ios':
('BCM4350', 'iPhone 6s/6s+', '5GHz'),
'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffe,extcap:0400088400000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|os:ios':
@@ -299,6 +350,8 @@
('BCM4350', 'iPhone 6s/6s+', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1202,extcap:0000000000000040|os:ios':
('BCM4350', 'iPhone 6s/6s+', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:000000ff,txpow:1202,extcap:0000000000000040|os:ios':
+ ('BCM4350', 'iPhone 6s/6s+', '2.4GHz'),
'wifi4|probe:0,1,3,50|assoc:0,1,48,50|os:ipodtouch1':
('Marvell_W8686B22', 'iPod Touch 1st/2nd gen', '2.4GHz'),
@@ -311,6 +364,8 @@
'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1504|os:ios':
('BCM4334', 'iPod Touch 5th gen', '5GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0020,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff,txpow:1504|os:ios':
+ ('BCM4334', 'iPod Touch 5th gen', '5GHz'),
'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1706|os:ios':
('BCM4334', 'iPod Touch 5th gen', '2.4GHz'),
'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:0062,htagg:1a,htmcs:000000ff,extcap:00000004|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1706|os:ios':
@@ -327,16 +382,26 @@
('BCM4339', 'LG G3', '5GHz'),
'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:000000800040|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a8201400000|oui:lg':
('BCM4339', 'LG G3', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:000000800040|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a8201400000|oui:lg':
+ ('BCM4339', 'LG G3', '2.4GHz'),
'wifi4|probe:0,1,3,45,127,107,191,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088001400040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:1d01,extcap:0000008001400040|oui:lg':
('BCM4339', 'LG G4', '5GHz'),
+ 'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088000400040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e008,extcap:0000008000400040|oui:lg':
+ ('BCM4339', 'LG G4', '5GHz'),
'wifi4|probe:0,1,50,45,127,107,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000088001400040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1001,extcap:000000800140|oui:lg':
('BCM4339', 'LG G4', '2.4GHz'),
- 'wifi|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,wps:LGMS323|assoc:0,1,50,48,45,221(0050f2,2),221(004096,3),htcap:012c':
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LGL16C|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff':
+ ('', 'LG Lucky', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LGMS323|assoc:0,1,50,48,45,221(0050f2,2),221(004096,3),htcap:012c,htagg:03,htmcs:000000ff':
('QCA_WCN3360', 'LG Optimus L70', '2.4GHz'),
- 'wifi|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LGLS660|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff':
+ 'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:11ac,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:11ac,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:lgtv':
+ ('', 'LG Smart TV', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:LGLS660|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff':
('', 'LG Tribute', '2.4GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:087e,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:087e,htagg:1b,htmcs:0000ffff,txpow:0f07|os:macos':
@@ -439,6 +504,8 @@
('BCM4339', 'Nexus 5', '5GHz'),
'wifi4|probe:0,1,3,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
('BCM4339', 'Nexus 5', '5GHz'),
+ 'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
+ ('BCM4339', 'Nexus 5', '5GHz'),
'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088001400040|assoc:0,1,33,36,48,45,127,70,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000008001400040|oui:lg':
('BCM4339', 'Nexus 5', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1303|oui:lg':
@@ -545,6 +612,8 @@
('BCM4354', 'Nexus 9', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140,wps:Nexus_9|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:150b,extcap:000008800140':
('BCM4354', 'Nexus 9', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,221(00904c,4),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1309,extcap:000008800140|oui:samsung':
+ ('BCM4354', 'Nexus 9', '2.4GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:01fe,htagg:1b,htmcs:0000ffff|assoc:0,1,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01fe,htagg:1b,htmcs:0000ffff|oui:samsung':
('', 'Nexus 10', '5GHz'),
@@ -553,6 +622,8 @@
'wifi4|probe:0,1,45,127,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000088001400040,wps:Nexus_Player|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e009,extcap:0000088001400040':
('BCM4356', 'Nexus Player', '5GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140,wps:Nexus_Player|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1209,extcap:000008800140':
+ ('BCM4356', 'Nexus Player', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000088001400040,wps:Nexus_Player|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1209,extcap:000008800140':
('BCM4356', 'Nexus Player', '2.4GHz'),
@@ -561,29 +632,73 @@
'wifi4|probe:0,1,50,45,htcap:012c,htagg:1b,htmcs:000000ff|assoc:0,1,48,50,221(0050f2,2),45,51,127,htcap:012c,htagg:1b,htmcs:000000ff,extcap:0100000000000040|os:windows-phone':
('', 'Nokia Lumia 635', '2.4GHz'),
+ 'wifi4|probe:0,1,45,htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,48,45,221(0050f2,2),htcap:016e,htagg:03,htmcs:000000ff|os:windows-phone':
+ ('', 'Nokia Lumia 920', '5GHz'),
+ 'wifi4|probe:0,1,50,45,htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff|os:windows-phone':
+ ('', 'Nokia Lumia 920', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a0200000000|oui:oneplus':
+ ('', 'Oneplus X', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,48|assoc:0,1,33,36,50,221(0050f2,2),45,221(00037f,1),221(00037f,4),48,htcap:1004,htagg:1b,htmcs:0000ffff,txpow:0f0f|os:panasonictv':
+ ('', 'Panasonic TV', '2.4GHz'),
+
'wifi4|probe:0,1,50|assoc:0,1,50,48,221(005043,1)|os:playstation':
('', 'Playstation 3 or 4', '2.4GHz'),
- 'wifi|probe:0,1,3,50|assoc:0,1,48,50,221(0050f2,2),45,htcap:112c,htagg:03,htmcs:0000ffff|os:playstation':
+ 'wifi4|probe:0,1,3,50|assoc:0,1,33,48,50,221(0050f2,2),45,htcap:010c,htagg:03,htmcs:0000ffff,txpow:1209|os:playstation':
+ ('Marvell_88W8797', 'Playstation 4', '2.4GHz'),
+ 'wifi4|probe:0,1,3,50|assoc:0,1,48,50,221(0050f2,2),45,htcap:112c,htagg:03,htmcs:0000ffff,txpow:0f06|os:playstation':
('Marvell_88W8797', 'Playstation 4', '2.4GHz'),
'wifi4|probe:0,1,3,50|assoc:0,1,33,48,50,221(0050f2,2),45,htcap:112c,htagg:03,htmcs:0000ffff,txpow:0f06|os:playstation':
('Marvell_88W8797', 'Playstation 4', '2.4GHz'),
- 'wifi|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|os:roku':
- ('BCM43362', 'Roku HD', '2.4GHz'),
+ 'wifi4|probe:0,1,50,221(0050f2,4),221(506f9a,9),wps:RCT6303W87DK|assoc:0,1,50,45,48,127,221(0050f2,2),htcap:1172,htagg:03,htmcs:000000ff,extcap:01':
+ ('', 'RCA 10 Viking Pro', '2.4GHz'),
- 'wifi|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|os:roku':
- ('BCM4336', 'Roku 2 XD', '2.4GHz'),
+ # Roku model 1100, 2500 and LT model 2450
+ 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|os:roku':
+ ('BCM43362', 'Roku HD/LT', '2.4GHz'),
+ # Roku model 1101
+ 'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:186e,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:186e,htagg:1a,htmcs:0000ffff,txpow:1208|os:roku':
+ ('', 'Roku HD-XR', '2.4GHz'),
+
+ # Roku Streaming Stick model 3400X
+ 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:187c,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:187c,htagg:1a,htmcs:0000ffff,txpow:1208|os:roku':
+ ('', 'Roku Streaming Stick', '2.4GHz'),
+
+ # Roku 1 models 2000, 2050, 2100, and "XD" (not sure of model number)
+ 'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:186e,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:186e,htagg:1a,htmcs:0000ffff,txpow:1308|os:roku':
+ ('', 'Roku 1', '2.4GHz'),
+
+ # Roku 1 model 2710 and Roku LT model 2700
+ 'wifi4|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:0020,htagg:1a,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0020,htagg:1a,htmcs:000000ff|os:roku':
+ ('', 'Roku 1/LT', '2.4GHz'),
+
+ # Roku 2 models 3000, 3050, 3100, and Roku LT model 2400
+ 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|os:roku':
+ ('BCM4336', 'Roku 2/LT', '2.4GHz'),
+
+ # Roku 2 model 2720
+ 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:187c,htagg:1a,htmcs:0000ffff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:187c,htagg:1a,htmcs:0000ffff|os:roku':
+ ('BCM4336', 'Roku 2', '2.4GHz'),
+
+ # Roku 3 model 4230, 4200, 4200X and Roku 2 model 4210 and Roku Streaming Stick model 3500
'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:19bc,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
('BCM43236', 'Roku 3', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:193c,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:193c,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
('BCM43236', 'Roku 3', '2.4GHz'),
+ # Roku 4 model 4400
+ 'wifi4|probe:0,1,45,127,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109,extcap:0000000000000040|os:roku':
+ ('', 'Roku 4', '5GHz'),
'wifi4|probe:0,1,45,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa|assoc:0,1,33,36,48,45,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109|os:roku':
('', 'Roku 4', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1209|os:roku':
('', 'Roku 4', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1209|os:roku':
+ ('', 'Roku 4', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,htcap:0020,htagg:01,htmcs:000000ff|assoc:0,1,50,45,61,48,221(0050f2,2),htcap:0020,htagg:01,htmcs:000000ff|oui:samsung':
('', 'Samsung Galaxy Mini', '2.4GHz'),
@@ -640,6 +755,8 @@
'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9118b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:00080f8401400040|assoc:0,1,33,36,48,45,127,191,199,221(00904c,4),221(001018,2),221(0050f2,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9118b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1102,extcap:0000000000000040|oui:samsung':
('BCM4359', 'Samsung Galaxy Note 5', '5GHz'),
+ 'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9118b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:00080f8401400040|assoc:0,1,33,36,48,70,45,127,191,199,221(00904c,4),221(001018,2),221(0050f2,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9118b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1202,extcap:0000000000000040|oui:samsung':
+ ('BCM4359', 'Samsung Galaxy Note 5', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:00080f8401400040|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1202|oui:samsung':
('BCM4359', 'Samsung Galaxy Note 5', '2.4GHz'),
@@ -681,8 +798,14 @@
'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000088001400040|assoc:0,1,33,36,48,45,127,107,191,221(00904c,4),221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e20b,extcap:0000088001400040|oui:samsung':
('BCM4354', 'Samsung Galaxy S5', '5GHz'),
+ 'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:000008800140|assoc:0,1,33,36,48,45,127,107,191,221(00904c,4),221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e20b,extcap:000008800140|oui:samsung':
+ ('BCM4354', 'Samsung Galaxy S5', '5GHz'),
'wifi4|probe:0,1,45,191,221(00904c,4),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa|assoc:0,1,33,36,48,45,191,221(00904c,4),221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e20b|oui:samsung':
('BCM4354', 'Samsung Galaxy S5', '5GHz'),
+ 'wifi4|probe:0,1,45,191,221(00904c,4),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:9b40fffa,vhttxmcs:18dafffa|assoc:0,1,33,36,48,45,191,221(00904c,4),221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:9b40fffa,vhttxmcs:18dafffa,txpow:e20b|oui:samsung':
+ ('BCM4354', 'Samsung Galaxy S5', '5GHz'),
+ 'wifi4|probe:0,1,45,127,191,221(00904c,4),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:000008800140|assoc:0,1,33,36,48,45,127,191,221(00904c,4),221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e20b,extcap:000008800140|oui:samsung':
+ ('BCM4354', 'Samsung Galaxy S5', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140|assoc:0,1,50,33,36,48,45,127,107,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1209,extcap:000008800140|oui:samsung':
('BCM4354', 'Samsung Galaxy S5', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000088001400040|assoc:0,1,50,33,36,48,45,127,107,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1209,extcap:000008800140|oui:samsung':
@@ -699,6 +822,18 @@
'wifi4|probe:0,1,50,3,45,127,221(00904c,4),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000088001400040|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1402,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy S6', '2.4GHz'),
+ 'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:0163,htagg:17,htmcs:0000ffff,vhtcap:0f907032,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:00080f8401400040|assoc:0,1,33,36,48,70,45,127,191,199,221(00904c,4),221(001018,2),221(0050f2,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9118b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1102,extcap:0000000000000040|oui:samsung':
+ ('', 'Samsung Galaxy S7', '5GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:00080f8401400040|assoc:0,1,50,33,36,48,70,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1402|oui:samsung':
+ ('', 'Samsung Galaxy S7', '2.4GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:00080f840140|assoc:0,1,50,33,36,48,70,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1402|oui:samsung':
+ ('', 'Samsung Galaxy S7', '2.4GHz'),
+
+ 'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9178b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:00080f840140|assoc:0,1,33,36,48,70,45,191,199,221(00904c,4),221(001018,2),221(0050f2,2),htcap:01ef,htagg:17,htmcs:0000ffff,vhtcap:0f9118b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1102|oui:samsung':
+ ('', 'Samsung Galaxy S7 Edge', '5GHz'),
+ 'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(00904c,4),221(0050f2,8),221(001018,2),htcap:1163,htagg:17,htmcs:0000ffff,extcap:00080f8401400040|assoc:0,1,50,33,36,48,70,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1302|oui:samsung':
+ ('', 'Samsung Galaxy S7 Edge', '2.4GHz'),
+
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:082c,htagg:1b,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:082c,htagg:1b,htmcs:000000ff,txpow:0f08|oui:samsung':
('BCM4329', 'Samsung Galaxy Tab', '5GHz'),
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:182c,htagg:1b,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:182c,htagg:1b,htmcs:000000ff,txpow:1208|oui:samsung':
@@ -711,9 +846,9 @@
'wifi4|probe:0,1,3,45,50,htcap:0162,htagg:03,htmcs:00000000|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:012c,htagg:03,htmcs:000000ff,extcap:0000000000000140|oui:samsung':
('Marvell_88W8787', 'Samsung Galaxy Tab 3', '2.4GHz'),
- 'wifi|probe:0,1,45,221(0050f2,8),htcap:016e|assoc:0,1,33,36,48,45,221(0050f2,2),221(004096,3),htcap:016e|oui:samsung':
+ 'wifi4|probe:0,1,45,221(0050f2,8),htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,33,36,48,45,221(0050f2,2),221(004096,3),htcap:016e,htagg:03,htmcs:000000ff,txpow:170d|oui:samsung':
('APQ8026', 'Samsung Galaxy Tab 4', '5GHz'),
- 'wifi|probe:0,1,50,3,45,221(0050f2,8),htcap:012c|assoc:0,1,50,48,45,221(0050f2,2),221(004096,3),htcap:012c|oui:samsung':
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),221(004096,3),htcap:012c,htagg:03,htmcs:000000ff|oui:samsung':
('APQ8026', 'Samsung Galaxy Tab 4', '2.4GHz'),
'wifi4|probe:0,1,45,221(0050f2,4),221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff,wps:_|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0c0a|oui:samsung':
@@ -727,6 +862,15 @@
('', 'Samsung Smart TV', '5GHz'),
'wifi4|probe:0,1,50,45,htcap:01ac,htagg:02,htmcs:0000ffff|assoc:0,1,50,45,127,48,221(0050f2,2),htcap:01ac,htagg:02,htmcs:0000ffff,extcap:01|os:samsungtv':
('', 'Samsung Smart TV', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,221(002d25,32),htcap:01ac,htagg:02,htmcs:0000ffff|assoc:0,1,50,48,221(0050f2,2),45,127,htcap:01ac,htagg:02,htmcs:0000ffff,extcap:01|os:samsungtv':
+ ('', 'Samsung Smart TV', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,htcap:0120,htagg:02,htmcs:000000ff|assoc:0,1,50,48,221(0050f2,2),45,127,htcap:0120,htagg:02,htmcs:000000ff,extcap:01|os:samsungtv':
+ ('', 'Samsung Smart TV', '2.4GHz'),
+
+ 'wifi4|probe:0,1,45,221(0050f2,4),htcap:11ee,htagg:02,htmcs:0000ffff,wps:Sony_BRAVIA|assoc:0,1,33,36,48,221(0050f2,2),45,127,htcap:11ee,htagg:02,htmcs:0000ffff,txpow:0500,extcap:01':
+ ('', 'Sony Bravia TV', '5GHz'),
+ 'wifi4|probe:0,1,50,45,127,221(0050f2,4),221(506f9a,10),221(506f9a,9),htcap:01ed,htagg:13,htmcs:0000ffff,extcap:00,wps:BRAVIA_2015|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,127,htcap:008c,htagg:13,htmcs:0000ffff,extcap:00000a02':
+ ('', 'Sony Bravia TV', '2.4GHz'),
'wifi4|probe:0,1,3,45,221(0050f2,8),191,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffc,vhttxmcs:0000fffc|assoc:0,1,33,36,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff|oui:sony':
('WCN3680', 'Sony Xperia Z Ultra', '5GHz'),
@@ -737,36 +881,64 @@
'wifi4|probe:0,1,50,45,htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a0200000000|oui:sony':
('WCN3680', 'Sony Xperia Z Ultra', '2.4GHz'),
+ 'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815032,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000088001400040|assoc:0,1,33,36,48,70,45,127,107,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815032,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e007,extcap:0000088001400040|oui:sony':
+ ('', 'Sony Xperia Z4/Z5', '5GHz'),
'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000088001400040|assoc:0,1,33,36,48,70,45,127,107,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e007,extcap:0000088001400040|oui:sony':
- ('', 'Sony Xperia Z4 Tablet', '5GHz'),
+ ('', 'Sony Xperia Z4/Z5', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0000088001400040|assoc:0,1,50,33,36,48,70,45,127,107,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1307,extcap:0000088001400040|oui:sony':
- ('', 'Sony Xperia Z4 Tablet', '2.4GHz'),
+ ('', 'Sony Xperia Z4/Z5', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(0050f2,4),221(506f9a,10),221(506f9a,9),221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a820040,wps:831C|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a8201400000':
+ ('', 'Sprint One M8', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(506f9a,16),221(0050f2,8),221(001018,2),htcap:1063,htagg:17,htmcs:000000ff,extcap:000008800140,wps:0PJA2|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:1063,htagg:17,htmcs:000000ff,txpow:1309,extcap:000008800140':
+ ('', 'Sprint One M9', '2.4GHz'),
'wifi4|probe:0,1,50,221(0050f2,4),wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:12,htmcs:000000ff,extcap:01000000|os:visiotv':
('', 'Vizio Smart TV', '2.4GHz'),
- 'wifi|probe:0,1,50,45,127,221(0050f2,4),htcap:106e,htagg:12,htmcs:000000ff,wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:12,htmcs:000000ff,extcap:00000001|os:visiotv':
+ 'wifi4|probe:0,1,50,221(0050f2,4),wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:13,htmcs:000000ff,extcap:01|os:visiotv':
+ ('', 'Vizio Smart TV', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,127,221(0050f2,4),htcap:106e,htagg:12,htmcs:000000ff,extcap:00,wps:Ralink_Wireless_Linux_Client|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,htcap:000c,htagg:12,htmcs:000000ff,extcap:01000000|os:visiotv':
('', 'Vizio Smart TV', '2.4GHz'),
'wifi|probe:0,1,50,48|assoc:0,1,50,221(0050f2,2),45,51,127,48,htcap:012c,htagg:1b,htmcs:000000ff|os:visiotv':
('', 'Vizio Smart TV', '2.4GHz'),
- 'wifi|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:wii':
+ 'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:wii':
('BCM4318', 'Wii', '2.4GHz'),
'wifi4|probe:0,1,50,45,3,221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff|os:wii':
('BCM43362', 'Wii-U', '2.4GHz'),
- 'wifi|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|oui:withings':
+ 'wifi4|probe:0,1,50,45,htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,48,50,221(0050f2,2),45,221(00904c,51),127,htcap:016e,htagg:03,htmcs:000000ff,extcap:0100008000000000|os:windows':
+ ('', 'Windows 802.11n PC', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff|oui:withings':
('', 'Withings Scale', '2.4GHz'),
- 'wifi|probe:0,1,3,45,50,127,htcap:010c,htagg:1b,htmcs:0000ffff|assoc:0,1,45,48,50,221(0050f2,2),htcap:010c,htagg:1b,htmcs:000000ff|oui:microsoft':
+ 'wifi4|probe:0,1,3,45,50,127,htcap:010c,htagg:1b,htmcs:0000ffff,extcap:00|assoc:0,1,45,48,50,221(0050f2,2),htcap:010c,htagg:1b,htmcs:000000ff|oui:microsoft':
+ ('', 'Xbox', '5GHz'),
+ 'wifi4|probe:0,1,3|assoc:0,1,48,33,36,221(0050f2,2),txpow:1405|oui:microsoft':
('', 'Xbox', '5GHz'),
'wifi|probe:0,1,3,45,50,htcap:016e,htagg:03,htmcs:0000ffff|assoc:0,1,33,48,50,127,221(0050f2,2),45,htcap:012c,htagg:03,htmcs:0000ffff,extcap:00000000|oui:microsoft':
('', 'Xbox', '5GHz'),
+ 'wifi4|probe:0,1,50|assoc:0,1,3,33,36,50,221(0050f2,2),45,221(00037f,1),221(00037f,4),48,htcap:104c,htagg:00,htmcs:000000ff,txpow:0f0f|oui:microsoft':
+ ('', 'Xbox', '2.4GHz'),
+ 'wifi4|probe:0,1,50,48|assoc:0,1,3,33,36,50,221(0050f2,2),45,221(00037f,1),221(00037f,4),48,htcap:104c,htagg:00,htmcs:0000ffff,txpow:0f0f|oui:microsoft':
+ ('', 'Xbox', '2.4GHz'),
+ 'wifi4|probe:0,1,45,50,htcap:058f,htagg:03,htmcs:0000ffff|assoc:0,1,33,36,48,221(0050f2,2),45,htcap:058f,htagg:03,htmcs:0000ffff,txpow:1208|oui:microsoft':
+ ('', 'Xbox', '2.4GHz'),
'wifi4|probe:0,1,3,45,50,htcap:058f,htagg:03,htmcs:0000ffff|assoc:0,1,48,50,221(0050f2,2),45,htcap:058d,htagg:03,htmcs:0000ffff|oui:microsoft':
('Marvell_88W8897', 'Xbox One', '2.4GHz'),
'wifi4|probe:0,1,45,50,htcap:058f,htagg:03,htmcs:0000ffff|assoc:0,1,48,50,221(0050f2,2),45,htcap:058d,htagg:03,htmcs:0000ffff|oui:microsoft':
('Marvell_88W8897', 'Xbox One', '2.4GHz'),
+ 'wifi4|probe:0,1|assoc:0,1,50,45,127,221(000c43,0),221(0050f2,2),33,48,htcap:008d,htagg:02,htmcs:0000ffff,txpow:0805,extcap:0100000000000000|oui:microsoft':
+ ('', 'Xbox One', '2.4GHz'),
+ 'wifi4|probe:0,1,50|assoc:0,1,50,45,127,221(000c43,0),221(0050f2,2),33,48,htcap:008d,htagg:02,htmcs:0000ffff,txpow:0805,extcap:0100000000000000|oui:microsoft':
+ ('', 'Xbox One', '2.4GHz'),
+
+ 'wifi4|probe:0,1,50,3,45,221(0050f2,8),htcap:012c,htagg:03,htmcs:000000ff|assoc:0,1,50,33,48,70,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff,txpow:170d|oui:xiaomi':
+ ('', 'Xiaomi Redmi 3', '2.4GHz'),
}
diff --git a/wifi/quantenna.py b/wifi/quantenna.py
index 10af967..b88ccac 100755
--- a/wifi/quantenna.py
+++ b/wifi/quantenna.py
@@ -99,6 +99,13 @@
# 'apply_security_config' must be called instead.
_qcsapi('apply_security_config', 'wifi0')
+ for _ in xrange(10):
+ if _qcsapi('get_status', 'wifi0') == 'Up':
+ break
+ time.sleep(1)
+ else:
+ raise utils.BinWifiException('wpa_supplicant failed to connect')
+
return True
diff --git a/wifi/quantenna_test.py b/wifi/quantenna_test.py
index 72f0333..b68b239 100755
--- a/wifi/quantenna_test.py
+++ b/wifi/quantenna_test.py
@@ -16,7 +16,9 @@
def fake_qcsapi(*args):
calls.append(list(args))
if args[0] == 'is_startprod_done':
- return '1\n' if ['startprod', 'wifi0'] in calls else '0\n'
+ return '1' if ['startprod', 'wifi0'] in calls else '0'
+ if args[0] == 'get_status':
+ return 'Up' if ['get_status', 'wifi0'] in calls else 'Down'
bridge_interfaces = set()