Merge wifitv into master
Change-Id: Ia4edb3026ce2bb2a5beca30781a9d69786920113
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/Makefile b/Makefile
index 2f3aa95..f433d89 100644
--- a/Makefile
+++ b/Makefile
@@ -14,9 +14,11 @@
BUILD_CRYPTDEV?= # default off: needs libdevmapper
BUILD_SIGNING?= # default off: needs libgtest
BUILD_JSONPOLL?=n
+BUILD_PRESTERASTATS?=n
export BUILD_HNVRAM BUILD_SSDP BUILD_DNSSD BUILD_LOGUPLOAD \
BUILD_IBEACON BUILD_WAVEGUIDE BUILD_DVBUTILS BUILD_SYSMGR \
- BUILD_STATUTILS BUILD_CRYPTDEV BUILD_SIGNING BUILD_JSONPOLL
+ BUILD_STATUTILS BUILD_CRYPTDEV BUILD_SIGNING BUILD_JSONPOLL \
+ BUILD_PRESTERASTATS
# note: libgpio is not built here. It's conditionally built
# via buildroot/packages/google/google_platform/google_platform.mk
@@ -78,6 +80,10 @@
DIRS+=conman
endif
+ifeq ($(BUILD_PRESTERASTATS),y)
+DIRS+=presterastats
+endif
+
PREFIX=/usr
BINDIR=$(DESTDIR)$(PREFIX)/bin
LIBDIR=$(DESTDIR)$(PREFIX)/lib
@@ -97,6 +103,15 @@
install:
set -e; for d in $(DIRS); do $(MAKE) -C $$d install; done
$(MAKE) install-optionspy
+ mkdir -p $(BINDIR)
+ rm -fv $(BINDIR)/hnvram
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME), gfmn110)
+ ln -s /usr/bin/hnvram_wrapper $(BINDIR)/hnvram
+else ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME), gflt110)
+ ln -s /usr/bin/hnvram_wrapper $(BINDIR)/hnvram
+else
+ ln -s /usr/bin/hnvram_binary $(BINDIR)/hnvram
+endif
sysmgr/all: base/all libstacktrace/all libexperiments/all
cmds/all: libstacktrace/all libexperiments/all
diff --git a/base/time.cc b/base/time.cc
index d1427c3..2672023 100644
--- a/base/time.cc
+++ b/base/time.cc
@@ -65,7 +65,7 @@
}
// Make sure someone calls it so that it gets initialized
-static uint32 ignore = StartTime();
+static uint32 __attribute__((used)) ignore = StartTime();
uint32 TimeAfter(int32 elapsed) {
ASSERT(elapsed >= 0);
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 87%
rename from cmds/asus_hosts.c
rename to cmds/asustax.cc
index 96ba81f..d4c665f 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,10 @@
int main(int argc, char **argv)
{
int s, opt, i;
- char *ifname = "br0";
+ const char *ifname = "br0";
+
+ setlinebuf(stdout);
+ alarm(30);
while ((opt = getopt(argc, argv, "i:")) != -1) {
switch (opt) {
@@ -213,7 +231,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..ee0ca24
--- /dev/null
+++ b/cmds/dhcpvendortax.c
@@ -0,0 +1,418 @@
+/*
+ * 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>
+#include <unistd.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];
+
+ setlinebuf(stdout);
+ alarm(30);
+ 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/isoping.c b/cmds/isoping.c
index d54a6b8..a669883 100644
--- a/cmds/isoping.c
+++ b/cmds/isoping.c
@@ -210,6 +210,8 @@
int sock = -1, want_timestamps = 0, quiet = 0, ttl = 2;
double packets_per_sec = DEFAULT_PACKETS_PER_SEC, prints_per_sec = -1;
+ setvbuf(stdout, NULL, _IOLBF, 0);
+
int c;
while ((c = getopt(argc, argv, "f:r:t:qTh?")) >= 0) {
switch (c) {
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..1c99553
--- /dev/null
+++ b/cmds/ssdptax.cc
@@ -0,0 +1,595 @@
+/*
+ * 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);
+ alarm(30);
+
+ 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/cmds/test-mmap.sh b/cmds/test-mmap.sh
index 85b3004..32710d9 100755
--- a/cmds/test-mmap.sh
+++ b/cmds/test-mmap.sh
@@ -18,7 +18,14 @@
$PREFIX ../host-mmap $ARGS < INPUT > GOT 2>&1
status=$?
if [ -n "$PREFIX" ]; then
- sleep .5 # script mysteriously delays output
+ # /usr/bin/script mysteriously delays output (child writes cached?)
+ # sleep up to 4 seconds waiting for the output
+ for n in $(seq 1 40); do
+ if [ -s GOT ]; then
+ break
+ fi
+ sleep .1
+ done
fi
if [ "$status" != "$EXIT" ]; then
echo "exit code: expected '$EXIT', got '$status'"
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index e9a2796..aa1b320 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -430,6 +430,7 @@
# This interface is not connected to the WLAN, so scan for potential
# routes to the ACS for provisioning.
if (not self.acs() and
+ not getattr(wifi, 'last_successful_bss_info', None) and
time.time() > wifi.last_wifi_scan_time + self._wifi_scan_period_s):
logging.debug('Performing scan on %s.', wifi.name)
self._wifi_scan(wifi)
@@ -463,7 +464,10 @@
logging.debug('Unable to join WLAN on %s', wifi.name)
self._status.connected_to_wlan = False
if self.acs():
- logging.debug('Connected to ACS')
+ logging.debug('Connected to ACS on %s', wifi.name)
+ wifi.last_successful_bss_info = getattr(wifi,
+ 'last_attempted_bss_info',
+ None)
now = time.time()
if (self._wlan_configuration and
hasattr(wifi, 'waiting_for_acs_since')):
@@ -502,9 +506,23 @@
return result
def _update_interfaces_and_routes(self):
+ """Touch each interface via update_routes."""
+
self.bridge.update_routes()
for wifi in self.wifi:
wifi.update_routes()
+ # If wifi is connected to something that's not the WLAN, it must be a
+ # provisioning attempt, and in particular that attempt must be via
+ # last_attempted_bss_info. If that is the same as the
+ # last_successful_bss_info (i.e. the last attempt was successful) and we
+ # aren't connected to the ACS after calling update_routes (which expires
+ # the connection status cache), then this BSS is no longer successful.
+ if (wifi.wpa_supplicant and
+ not self._connected_to_wlan(wifi) and
+ (getattr(wifi, 'last_successful_bss_info', None) ==
+ getattr(wifi, 'last_attempted_bss_info', None)) and
+ not wifi.acs()):
+ wifi.last_successful_bss_info = None
# Make sure these get called semi-regularly so that exported status is up-
# to-date.
@@ -663,19 +681,22 @@
if not hasattr(wifi, 'cycler'):
return False
- bss_info = wifi.cycler.next()
+ last_successful_bss_info = getattr(wifi, 'last_successful_bss_info', None)
+ bss_info = last_successful_bss_info or wifi.cycler.next()
if bss_info is not None:
+ logging.debug('Attempting to connect to SSID %s for provisioning',
+ bss_info.ssid)
self._status.trying_open = True
- connected = subprocess.call(self.WIFI_SETCLIENT +
- ['--ssid', bss_info.ssid,
- '--band', wifi.bands[0],
- '--bssid', bss_info.bssid]) == 0
+ connected = self._try_bssid(wifi, bss_info)
if connected:
self._status.connected_to_open = True
now = time.time()
wifi.waiting_for_acs_since = now
wifi.complain_about_acs_at = now + 5
logging.info('Attempting to provision via SSID %s', bss_info.ssid)
+ # If we can no longer connect to this, it's no longer successful.
+ elif bss_info == last_successful_bss_info:
+ wifi.last_successful_bss_info = None
return connected
else:
# TODO(rofrankel): There are probably more cases in which this should be
@@ -687,6 +708,13 @@
return False
+ def _try_bssid(self, wifi, bss_info):
+ wifi.last_attempted_bss_info = bss_info
+ return subprocess.call(self.WIFI_SETCLIENT +
+ ['--ssid', bss_info.ssid,
+ '--band', wifi.bands[0],
+ '--bssid', bss_info.bssid]) == 0
+
def _connected_to_wlan(self, wifi):
return (wifi.wpa_supplicant and
any(config.client_up for band, config
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 7b202b7..d96022e 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -291,7 +291,11 @@
self.interface_with_scan_results = None
self.scan_results_include_hidden = False
+ # Should we be able to connect to open network s2?
self.can_connect_to_s2 = True
+ self.can_connect_to_s3 = True
+ # Will s2 fail rather than providing ACS access?
+ self.s2_fail = False
def create_wifi_interfaces(self):
super(ConnectionManager, self).create_wifi_interfaces()
@@ -304,16 +308,18 @@
return ['echo'] + ['%s LOWER_UP' % ifc
for ifc in self.interfaces_already_up]
- def _try_next_bssid(self, wifi):
- if hasattr(wifi, 'cycler'):
- bss_info = wifi.cycler.peek()
- if bss_info:
- self.last_provisioning_attempt = bss_info
- # pylint: disable=protected-access
- if wifi._wpa_control:
- wifi._wpa_control.ssid_testonly = bss_info.ssid
+ def _update_access_point(self, wlan_configuration):
+ client_was_up = wlan_configuration.client_up
+ super(ConnectionManager, self)._update_access_point(wlan_configuration)
+ if wlan_configuration.access_point_up:
+ if client_was_up:
+ wifi = self.wifi_for_band(wlan_configuration.band)
+ wifi.add_terminating_event()
- super(ConnectionManager, self)._try_next_bssid(wifi)
+ def _try_bssid(self, wifi, bss_info):
+ self.last_provisioning_attempt = bss_info
+
+ super(ConnectionManager, self)._try_bssid(wifi, bss_info)
def connect(connection_check_result):
# pylint: disable=protected-access
@@ -331,10 +337,10 @@
return True
if bss_info and bss_info.ssid == 's2' and self.can_connect_to_s2:
- connect('succeed')
+ connect('fail' if self.s2_fail else 'succeed')
return True
- if bss_info and bss_info.ssid == 's3':
+ if bss_info and bss_info.ssid == 's3' and self.can_connect_to_s3:
connect('restricted')
return True
@@ -649,8 +655,11 @@
for _ in range(3):
c.run_once()
wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_OPEN]))
- wvtest.WVPASSEQ(c.last_provisioning_attempt.ssid, 's2')
- wvtest.WVPASSEQ(c.last_provisioning_attempt.bssid, '01:23:45:67:89:ab')
+
+ last_bss_info = c.wifi_for_band(band).last_attempted_bss_info
+ wvtest.WVPASSEQ(last_bss_info.ssid, 's2')
+ wvtest.WVPASSEQ(last_bss_info.bssid, '01:23:45:67:89:ab')
+
# Wait for the connection to be processed.
c.run_once()
wvtest.WVPASS(c.acs())
@@ -747,6 +756,76 @@
wvtest.WVFAIL(c.wifi_for_band(band).current_route())
wvtest.WVPASS(c.bridge.current_route())
+ # Now delete the config and bring down the bridge and make sure we reprovision
+ # via the last working BSS.
+ c.delete_wlan_config(band)
+ c.bridge.set_connection_check_result('fail')
+ scan_count_for_band = c.wifi_for_band(band).wifi_scan_counter
+ c.run_until_interface_update()
+ wvtest.WVFAIL(c.acs())
+ wvtest.WVFAIL(c.internet())
+ # s3 is not what the cycler would suggest trying next.
+ wvtest.WVPASSNE('s3', c.wifi_for_band(band).cycler.peek())
+ # Run only once, so that only one BSS can be tried. It should be the s3 one,
+ # since that worked previously.
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ # Make sure we didn't scan on `band`.
+ wvtest.WVPASSEQ(scan_count_for_band, c.wifi_for_band(band).wifi_scan_counter)
+
+ # Now re-create the WLAN config, connect to the WLAN, and make sure that s3 is
+ # unset as last_successful_bss_info, since it is no longer available.
+ c.write_wlan_config(band, ssid, psk)
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ wvtest.WVPASS(c.internet())
+
+ c.can_connect_to_s3 = False
+ c.scan_results_include_hidden = False
+ c.delete_wlan_config(band)
+ c.run_once()
+ wvtest.WVPASSEQ(c.wifi_for_band(band).last_successful_bss_info, None)
+
+ # Now do the same, except this time s2 is connected to but doesn't provide ACS
+ # access. This requires first re-establishing s2 as successful, so there are
+ # four steps:
+ #
+ # 1) Connect to WLAN.
+ # 2) Disconnect, reprovision via s2 (establishing it as successful).
+ # 3) Reconnect to WLAN so that we can trigger re-provisioning by
+ # disconnecting.
+ # 4) Connect to s2 but get no ACS access; see that last_successful_bss_info is
+ # unset.
+ c.write_wlan_config(band, ssid, psk)
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ wvtest.WVPASS(c.internet())
+
+ c.delete_wlan_config(band)
+ c.run_once()
+ wvtest.WVFAIL(c.wifi_for_band(band).acs())
+
+ c.can_connect_to_s2 = True
+ # Give it time to try all BSSIDs.
+ for _ in range(3):
+ c.run_once()
+ s2_bss = iw.BssInfo('01:23:45:67:89:ab', 's2')
+ wvtest.WVPASSEQ(c.wifi_for_band(band).last_successful_bss_info, s2_bss)
+
+ c.s2_fail = True
+ c.write_wlan_config(band, ssid, psk)
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ wvtest.WVPASS(c.internet())
+
+ wvtest.WVPASSEQ(c.wifi_for_band(band).last_successful_bss_info, s2_bss)
+ c.delete_wlan_config(band)
+ # Run once so that c will reconnect to s2.
+ c.run_once()
+ # Now run until it sees the lack of ACS access.
+ c.run_until_interface_update()
+ wvtest.WVPASSEQ(c.wifi_for_band(band).last_successful_bss_info, None)
+
@wvtest.wvtest
@connection_manager_test(WIFI_SHOW_OUTPUT_MARVELL8897)
diff --git a/conman/iw.py b/conman/iw.py
index b014220..f4932f1 100755
--- a/conman/iw.py
+++ b/conman/iw.py
@@ -37,7 +37,7 @@
def __eq__(self, other):
# pylint: disable=protected-access
- return self.__attrs() == other.__attrs()
+ return isinstance(other, BssInfo) and self.__attrs() == other.__attrs()
def __ne__(self, other):
return not self.__eq__(other)
diff --git a/craftui/.gitignore b/craftui/.gitignore
new file mode 100644
index 0000000..1a5f7ef
--- /dev/null
+++ b/craftui/.gitignore
@@ -0,0 +1,4 @@
+*.swp
+.started
+.sim.extracted
+sim
diff --git a/craftui/HOW.restart_if_changed b/craftui/HOW.restart_if_changed
index 1f3343e..8c17154 100644
--- a/craftui/HOW.restart_if_changed
+++ b/craftui/HOW.restart_if_changed
@@ -6,10 +6,13 @@
restart() {
[ -n "$pid" ] && kill $pid
+ echo "######################################################################"
echo "# starting craftui"
+ gpylint *.py
+ make test
./craftui &
pid=$!
- touch started
+ touch .started
}
onExit() {
@@ -25,7 +28,7 @@
restart
continue
fi
- f=$(find . -type f -newer started)
+ f=$(find . -name '*.swp' -prune -o -type f -newer .started -print)
if [ -n "$f" ]; then
restart
continue
diff --git a/craftui/HOW.updatesim b/craftui/HOW.updatesim
new file mode 100644
index 0000000..8ae3f04
--- /dev/null
+++ b/craftui/HOW.updatesim
@@ -0,0 +1,26 @@
+#! /bin/sh
+
+ssh chimera '
+ rm -rf /tmp/sim;
+ mkdir -p /tmp/sim/proc && cat /proc/uptime > /tmp/sim/proc/uptime;
+ for n in /sys/class/net/*/statistics/*; do
+ mkdir -p /tmp/sim/$(dirname $n);
+ test ! -d $n && cat $n > /tmp/sim/$n;
+ done;
+ ip -o -d link > /tmp/sim/ip.link.txt;
+ ip -o addr > /tmp/sim/ip.addr.txt;
+ presterastats > /tmp/sim/presterastats.json;
+ '
+
+ssh chimera cd / "&&" tar czf - -C / \
+ config/settings \
+ etc/platform \
+ etc/serial \
+ etc/softwaredate \
+ etc/version \
+ tmp/glaukus \
+ tmp/serial \
+ tmp/platform \
+ tmp/gpio/ledstate \
+ tmp/sim \
+ > sim.tgz
diff --git a/craftui/Makefile b/craftui/Makefile
index 6b6757b..b946953 100644
--- a/craftui/Makefile
+++ b/craftui/Makefile
@@ -15,12 +15,18 @@
install-libs:
@echo "No libs to install."
-test: lint
+.sim.extracted: sim.tgz
+ rm -rf sim
+ rsync -av sim-tools/ sim
+ tar xf sim.tgz -C sim
+ touch $@
+
+test: .sim.extracted lint
set -e; \
- for pytest in $(wildcard *_test.py); do \
+ for n in $(wildcard ./*_test.*); do \
echo; \
- echo "Testing $$pytest"; \
- $(PYTHON) $$pytest; \
+ echo "Testing $$n"; \
+ $$n; \
done
clean:
diff --git a/craftui/README b/craftui/README
new file mode 100644
index 0000000..a5a8cef
--- /dev/null
+++ b/craftui/README
@@ -0,0 +1,34 @@
+HOW.restart_if_changed
+ Use this script during development. It runs the craftui in the
+ desktop, restarting on writes to the files, for quick development.
+ Use a browser pointed to localhost:8888 to see UI and reload when
+ it restarts.
+
+HOW.updatesim
+ This script creates the sim.tgz from a real device. Use it to
+ snapshot a new sim when tools like presterastats or glaukusd change
+ files formats.
+
+sim.tgz
+ This is a tar file screated by HOW.updatesim containing snapshots
+ of various files used in the craftui simulation for unit tests
+
+craftui_test.sh
+ starts the craftui and does simple sanity tests
+
+craftui
+ This is the wrapper that launches the craftui, used in both
+ testing and device runtime. If tun on the desktop, it starts
+ craftui_fortesting.py instead of craftui.py, to catch exceptions.
+
+craftui_fortesting.py
+ wrapped to start the craftui with all exceptions caught.
+
+craftui.py
+ The craftui python script.
+
+sim-tools
+ tools used to simulate a real device
+
+www
+ the www tree with html and javascript for the UI
diff --git a/craftui/craftui b/craftui/craftui
index c78bc35..2f5e143 100755
--- a/craftui/craftui
+++ b/craftui/craftui
@@ -1,6 +1,6 @@
#! /bin/sh
-bin=/bin
+pycode=/bin/craftui.py
cw=/usr/catawampus
devcw=../../../../vendor/google/catawampus
localwww=./www
@@ -14,7 +14,8 @@
if [ "$isdev" = 1 ]; then
cw="$devcw"
args="$args --port=8888 --sim=./sim"
- bin=.
+ pycode=./craftui_fortesting.py
+ export PATH="$PWD/sim/bin:$PATH"
fi
# for debugging on the device, use the local (/tmp/www?) web tree
@@ -22,5 +23,10 @@
args="$args --www=$localwww"
fi
+# enable debugger
+if [ "$1" = -d ]; then
+ debug="-m pdb"
+fi
+
export PYTHONPATH="$cw/tr/vendor/tornado:$PYTHONPATH"
-exec python $bin/craftui.py $args
+exec python -u $debug $pycode $args
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 8e06e65..d25b40b 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -20,15 +20,287 @@
import getopt
import json
import os
+import re
+import subprocess
import sys
import urllib2
import tornado.ioloop
import tornado.web
+class ConfigError(Exception):
+ """Configuration errors to pass to browser."""
+
+ def __init__(self, message):
+ super(ConfigError, self).__init__(message)
+
+
+class Validator(object):
+ """Validate the user value and convert to safe config value."""
+ pattern = r'^(.*)$'
+ example = 'any string'
+
+ def __init__(self):
+ self.Reset()
+
+ def Reset(self):
+ self.fields = ()
+ self.config = ''
+
+ def Validate(self, value):
+ self.Reset()
+ self.value = value
+ m = re.search(self.pattern, value)
+ if not m:
+ raise ConfigError('value "%s" does not match pattern "%s", eg: "%s"' %
+ (value, self.pattern, self.example))
+ self.fields = m.groups()
+ self.config = self.fields[0]
+
+
+class VInt(Validator):
+ """Validate as integer."""
+ pattern = r'^(\d+)$'
+ example = '123'
+
+
+class VRange(VInt):
+ """Validate as integer in a range."""
+
+ def __init__(self, low, high):
+ super(VRange, self).__init__()
+ self.low = low
+ self.high = high
+
+ def Validate(self, value):
+ super(VRange, self).Validate(value)
+ self.CheckInRange(int(self.config), self.low, self.high)
+
+ @staticmethod
+ def CheckInRange(num, low, high):
+ if num < low or num > high:
+ raise ConfigError('number %d is out of range %d-%d' % (num, low, high))
+
+
+class VSlash(Validator):
+ """Validate as slash notation (eg 192.168.1.1/24)."""
+ pattern = r'^((\d+).(\d+).(\d+).(\d+)/(\d+))$'
+ example = '192.168.1.1/24'
+
+ def __init__(self):
+ super(VSlash, self).__init__()
+
+ def Validate(self, value):
+ super(VSlash, self).Validate(value)
+ mask = int(self.fields[5])
+ VRange.CheckInRange(mask, 0, 32)
+ for dotted_quad_part in self.fields[1:4]:
+ num = int(dotted_quad_part)
+ VRange.CheckInRange(num, 0, 255)
+
+
+class VVlan(VRange):
+ """Validate as vlan."""
+
+ def __init__(self):
+ super(VVlan, self).__init__(0, 4095)
+
+
+class VFreqHi(VRange):
+ """Validate as Hi E-Band frequency."""
+
+ def __init__(self):
+ super(VFreqHi, self).__init__(82000000, 85000000)
+
+
+class VFreqLo(VRange):
+ """Validate as Low E-Band frequency."""
+
+ def __init__(self):
+ super(VFreqLo, self).__init__(72000000, 75000000)
+
+
+class VPower(VRange):
+ """Validate as PA power level."""
+
+ def __init__(self):
+ 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):
+ """Validate as member of dict."""
+ dict = {}
+
+ def Validate(self, value):
+ super(VDict, self).Validate(value)
+ if value not in self.dict:
+ keys = self.dict.keys()
+ raise ConfigError('value "%s" must be one of "%s"' % (value, keys))
+ self.config = self.dict[value]
+
+
+class VTx(VDict):
+ """Validate: tx/rx."""
+ dict = {'tx': 'tx', 'rx': 'rx'}
+
+
+class VTrueFalse(VDict):
+ """Validate as true or false."""
+ dict = {'true': 'true', 'false': 'false'}
+
+
+class Config(object):
+ """Configure the device after validation."""
+
+ def __init__(self, validator):
+ self.validator = validator()
+
+ def Validate(self, value):
+ self.validator.Validate(value)
+
+ def Configure(self):
+ raise Exception('override Config.Configure')
+
+ @staticmethod
+ def Run(command):
+ """Run a command."""
+ print 'running: %s' % command
+ try:
+ subprocess.check_output(command)
+ except subprocess.CalledProcessError as e:
+ print 'Run: ', str(e)
+ raise ConfigError('command failed with %d' % e.returncode)
+
+
+class PtpConfig(Config):
+ """Configure using ptp-config."""
+
+ def __init__(self, validator, key):
+ super(PtpConfig, self).__init__(validator)
+ self.key = key
+
+ def Configure(self):
+ Config.Run(['ptp-config', '-s', self.key, self.validator.config])
+
+
+class PtpActivate(Config):
+ """Configure using ptp-config."""
+
+ def __init__(self, validator, key):
+ super(PtpActivate, self).__init__(validator)
+ self.key = key
+
+ def Configure(self):
+ Config.Run(['ptp-config', '-i', self.key])
+
+
+class Glaukus(Config):
+ """Configure using glaukus json api."""
+
+ def __init__(self, validator, api, fmt):
+ super(Glaukus, self).__init__(validator)
+ self.api = api
+ self.fmt = fmt
+
+ def Configure(self):
+ """Handle a JSON request to glaukusd."""
+ url = 'http://localhost:8080' + self.api
+ payload = self.fmt % self.validator.config
+ print 'Glaukus: ', url, payload
+ try:
+ fd = urllib2.urlopen(url, payload)
+ except urllib2.URLError as ex:
+ print 'Connection to %s failed: %s' % (url, ex.reason)
+ raise ConfigError('failed to contact glaukus')
+ response = fd.read()
+ j = json.loads(response)
+ print j
+ if j['code'] != 'SUCCESS':
+ if j['message']:
+ raise ConfigError(j.message)
+ raise ConfigError('failed to configure glaukus')
+
+
+class Reboot(Config):
+ """Reboot."""
+
+ def Configure(self):
+ if self.validator.config == 'true':
+ Config.Run(['reboot'])
+
+
class CraftUI(object):
"""A web server that configures and displays Chimera data."""
+ handlers = {
+ 'craft_ipaddr': PtpConfig(VSlash, 'craft_ipaddr'),
+ 'link_ipaddr': PtpConfig(VSlash, 'local_ipaddr'),
+ '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_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',
+ 'sw0.ooband': 'ooband',
+ 'sw0.inband': 'inband',
+ 'sw0.peer': 'link',
+ }
+ ifvlan = [
+ 'sw0.ooband',
+ 'sw0.inband',
+ 'sw0.peer'
+ ]
+ stats = [
+ 'multicast',
+ 'collisions',
+ 'rx_bytes',
+ 'rx_packets',
+ 'rx_errors',
+ 'rx_dropped',
+ 'tx_bytes',
+ 'tx_packets',
+ 'tx_errors',
+ 'tx_dropped'
+ ]
+
def __init__(self, wwwroot, port, sim):
"""initialize."""
self.wwwroot = wwwroot
@@ -37,7 +309,30 @@
self.data = {}
self.data['refreshCount'] = 0
+ def ApplyChanges(self, changes):
+ """Apply changes to system."""
+ if 'config' not in changes:
+ raise ConfigError('missing required config array')
+ conf = changes['config']
+ try:
+ # dry run to validate all
+ for c in conf:
+ for k, v in c.items():
+ if k not in self.handlers:
+ raise ConfigError('unknown key "%s"' % k)
+ h = self.handlers[k]
+ h.Validate(v)
+ # do it again for real
+ for c in conf:
+ for k, v in c.items():
+ h = self.handlers[k]
+ h.Validate(v)
+ h.Configure()
+ except ConfigError as e:
+ raise ConfigError('key "%s": %s' % (k, e))
+
def ReadFile(self, filepath):
+ """cat file."""
text = ''
try:
with open(filepath) as fd:
@@ -55,12 +350,75 @@
js = '{"platform":' + pj + ',"modem":' + mj + ',"radio":' + rj + '}'
return js
+ def AddIpAddr(self, data):
+ """Run ip addr and parse results."""
+ ipaddr = ''
+ try:
+ ipaddr = subprocess.check_output(['ip', '-o', 'addr'])
+ except subprocess.CalledProcessError as e:
+ print 'warning: "ip -o addr" failed: ', e
+ v = {}
+ for line in ipaddr.splitlines():
+ f = line.split()
+ ifname = re.sub(r'[@:].*', '', f[1])
+ m = re.search(r'scope (global|link)', line)
+ scope = m.group(1) if m else 'noscope'
+ v[ifname + ':' + f[2] + ':' + scope] = f[3]
+ m = re.search(r'link/ether (\S+)', line)
+ if m:
+ mac = m.group(1)
+ v[ifname + ':' + 'mac'] = mac
+ for ifname, uiname in self.ifmap.items():
+ mac = v.get(ifname + ':mac')
+ data[uiname + '_mac'] = mac if mac else 'unknown'
+ for inet in ('inet', 'inet6'):
+ kglobal = ifname + ':' + inet + ':' + 'global'
+ vdata = v.get(kglobal, 'unknown')
+ kdata = 'active_' + uiname + '_' + inet
+ data[kdata] = vdata
+
+ def AddInterfaceStats(self, data):
+ """Get if stats."""
+ for ifname, uiname in self.ifmap.items():
+ d = self.sim + '/sys/class/net/' + ifname + '/statistics/'
+ for stat in self.stats:
+ k = uiname + '_' + stat
+ data[k] = self.ReadFile(d + stat)
+
+ def AddSwitchStats(self, data):
+ """Run presterastats and send json."""
+ stats = ''
+ try:
+ stats = subprocess.check_output(['presterastats'])
+ except subprocess.CalledProcessError as e:
+ print 'warning: "presterastats" failed: ', e
+ data['switch'] = json.loads(stats)['port-interface-statistics']
+
+ def AddVlans(self, data):
+ """Run ip -d link and parse results for vlans."""
+ iplink = ''
+ try:
+ iplink = subprocess.check_output(['ip', '-o', '-d', 'link'])
+ except subprocess.CalledProcessError as e:
+ print 'warning: "ip -o -d link" failed: ', e
+ v = {}
+ for line in iplink.splitlines():
+ m = re.search(r'^\d+: ([\w\.]+)@\w+: .* vlan id (\w+)', line)
+ if m:
+ v[m.group(1)] = m.group(2)
+ for ifname in self.ifvlan:
+ uiname = self.ifmap[ifname]
+ vdata = v.get(ifname, 'unknown')
+ kdata = 'active_' + uiname + '_vlan'
+ data[kdata] = vdata
+
def GetPlatformData(self):
"""Get platform data, return a json string."""
data = self.data
sim = self.sim
if data['refreshCount'] == 0:
+ data['serialno'] = self.ReadFile(sim + '/etc/serial')
data['version'] = self.ReadFile(sim + '/etc/version')
data['platform'] = self.ReadFile(sim + '/etc/platform')
data['softwaredate'] = self.ReadFile(sim + '/etc/softwaredate')
@@ -69,10 +427,15 @@
data['ledstate'] = self.ReadFile(sim + '/tmp/gpio/ledstate')
cs = '/config/settings/'
data['craft_ipaddr'] = self.ReadFile(sim + cs + 'craft_ipaddr')
- data['local_ipaddr'] = self.ReadFile(sim + cs + 'local_ipaddr')
+ 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_peer'] = self.ReadFile(sim + cs + 'vlan_peer')
+ data['vlan_ooband'] = self.ReadFile(sim + cs + 'vlan_ooband')
+ data['vlan_link'] = self.ReadFile(sim + cs + 'vlan_peer')
+ self.AddIpAddr(data)
+ self.AddInterfaceStats(data)
+ self.AddSwitchStats(data)
+ self.AddVlans(data)
return json.dumps(data)
def GetModemData(self):
@@ -104,12 +467,20 @@
return response
class MainHandler(tornado.web.RequestHandler):
- """Displays the UI."""
+ """Displays the Craft UI."""
def get(self):
ui = self.settings['ui']
print 'GET craft HTML page'
- self.render(ui.wwwroot + '/index.thtml', peerurl='http://TODO')
+ self.render(ui.wwwroot + '/index.thtml', peerurl='/?peer=1')
+
+ class ConfigHandler(tornado.web.RequestHandler):
+ """Displays the Config page."""
+
+ def get(self):
+ ui = self.settings['ui']
+ print 'GET config HTML page'
+ self.render(ui.wwwroot + '/config.thtml', peerurl='/config/?peer=1')
class RestartHandler(tornado.web.RequestHandler):
"""Restart the box."""
@@ -135,12 +506,40 @@
self.write(jsonstring)
self.finish()
+ def post(self):
+ print 'POST JSON data for craft page'
+ request = self.request.body
+ result = {}
+ result['error'] = 0
+ result['errorstring'] = ''
+ try:
+ try:
+ json_args = json.loads(request)
+ request = json.dumps(json_args)
+ except ValueError as e:
+ print e
+ raise ConfigError('json format error')
+ ui = self.settings['ui']
+ ui.ApplyChanges(json_args)
+ except ConfigError as e:
+ print e
+ result['error'] += 1
+ result['errorstring'] += str(e)
+
+ response = json.dumps(result)
+ print 'request: ', request
+ print 'response: ', response
+ self.set_header('Content-Type', 'application/json')
+ self.write(response)
+ self.finish()
+
def RunUI(self):
"""Create the web server and run forever."""
handlers = [
- (r'/', CraftUI.MainHandler),
- (r'/content.json', CraftUI.JsonHandler),
- (r'/restart', CraftUI.RestartHandler),
+ (r'/', self.MainHandler),
+ (r'/config', self.ConfigHandler),
+ (r'/content.json', self.JsonHandler),
+ (r'/restart', self.RestartHandler),
(r'/static/([^/]*)$', tornado.web.StaticFileHandler,
{'path': self.wwwroot + '/static'}),
]
diff --git a/craftui/craftui_test.py b/craftui/craftui_fortesting.py
similarity index 75%
rename from craftui/craftui_test.py
rename to craftui/craftui_fortesting.py
index ae20df7..82bf147 100644
--- a/craftui/craftui_test.py
+++ b/craftui/craftui_fortesting.py
@@ -17,6 +17,14 @@
__author__ = 'edjames@google.com (Ed James)'
+import traceback
+import craftui
if __name__ == '__main__':
- print 'TODO(edjames)'
+ try:
+ craftui.main()
+ # pylint: disable=broad-except
+ except Exception as e:
+ traceback.print_exc()
+ # exit cleanly to close the socket so next listen doesn't fail with in-use
+ exit(1)
diff --git a/craftui/craftui_test.sh b/craftui/craftui_test.sh
new file mode 100755
index 0000000..9147945
--- /dev/null
+++ b/craftui/craftui_test.sh
@@ -0,0 +1,107 @@
+#! /bin/sh
+
+# some unit tests for the craft UI
+
+# save stdout to 3, dup stdout to a file
+log=.testlog.$$
+exec 3>&1
+exec >$log 2>&1
+
+failcount=0
+passcount=0
+
+fail() {
+ echo "FAIL: $*" >&3
+ echo "FAIL: $*"
+ ((failcount++))
+}
+
+pass() {
+ echo "PASS: $*" >&3
+ echo "PASS: $*"
+ ((passcount++))
+}
+
+testname() {
+ test="$*"
+ echo "---------------------------------------------------------"
+ echo "starting test $test"
+}
+
+check_success() {
+ status=$?
+ echo "check_success: last return code was $status, wanted 0"
+ if [ $status = 0 ]; then
+ pass $test
+ else
+ fail $test
+ fi
+}
+
+check_failure() {
+ status=$?
+ echo "check_failure: last return code was $status, wanted not-0"
+ if [ $status != 0 ]; then
+ pass $test
+ else
+ fail $test
+ fi
+}
+
+onexit() {
+ testname "process running at exit"
+ kill -0 $pid
+ check_success
+
+ # cleanup
+ kill -9 $pid
+
+ exec 1>&3
+ echo "SUMMARY: pass=$passcount fail=$failcount"
+ if [ $failcount -eq 0 ]; then
+ echo "SUCCESS: $passcount tests passed."
+ else
+ echo "FAILURE: $failcount tests failed."
+ echo "details follow:"
+ cat $log
+ fi
+ rm -f $log
+
+ exit $failcount
+}
+
+trap onexit 0 1 2 3
+
+testname "server not running"
+curl -s http://localhost:8888/
+check_failure
+
+./craftui > /tmp/LOG 2>&1 &
+pid=$!
+
+testname "process running"
+kill -0 $pid
+check_success
+
+sleep 1
+
+testname true
+true
+check_success
+
+testname false
+false
+check_failure
+
+testname "main web page"
+curl -s http://localhost:8888/ > /dev/null
+check_success
+
+testname "404 not found"
+curl -s http://localhost:8888/notexist | grep '404: Not Found'
+check_success
+
+testname "json"
+curl -s http://localhost:8888/content.json | grep '"platform": "GFCH100"'
+check_success
+
diff --git a/craftui/sim-tools/bin/ip b/craftui/sim-tools/bin/ip
new file mode 100755
index 0000000..82bcaae
--- /dev/null
+++ b/craftui/sim-tools/bin/ip
@@ -0,0 +1,9 @@
+#! /bin/sh
+
+dir=$(dirname $0)
+
+if [ "$3" = link ]; then
+ cat $dir/../tmp/sim/ip.link.txt
+else
+ cat $dir/../tmp/sim/ip.addr.txt
+fi
diff --git a/craftui/sim-tools/bin/presterastats b/craftui/sim-tools/bin/presterastats
new file mode 100755
index 0000000..ab71721
--- /dev/null
+++ b/craftui/sim-tools/bin/presterastats
@@ -0,0 +1,4 @@
+#! /bin/sh
+
+dir=$(dirname $0)
+cat $dir/../tmp/sim/presterastats.json
diff --git a/craftui/sim-tools/bin/ptp-config b/craftui/sim-tools/bin/ptp-config
new file mode 100755
index 0000000..4031019
--- /dev/null
+++ b/craftui/sim-tools/bin/ptp-config
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+echo "TODO: $0 $*"
diff --git a/craftui/sim-tools/bin/reboot b/craftui/sim-tools/bin/reboot
new file mode 100755
index 0000000..60ee746
--- /dev/null
+++ b/craftui/sim-tools/bin/reboot
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+echo running: $0 $*
diff --git a/craftui/sim-tools/proc b/craftui/sim-tools/proc
new file mode 120000
index 0000000..2b2cb87
--- /dev/null
+++ b/craftui/sim-tools/proc
@@ -0,0 +1 @@
+tmp/sim/proc
\ No newline at end of file
diff --git a/craftui/sim-tools/sys b/craftui/sim-tools/sys
new file mode 120000
index 0000000..cdcfee0
--- /dev/null
+++ b/craftui/sim-tools/sys
@@ -0,0 +1 @@
+tmp/sim/sys
\ No newline at end of file
diff --git a/craftui/sim.tgz b/craftui/sim.tgz
new file mode 100644
index 0000000..136253f
--- /dev/null
+++ b/craftui/sim.tgz
Binary files differ
diff --git a/craftui/sim/config/settings/craft_ipaddr b/craftui/sim/config/settings/craft_ipaddr
deleted file mode 100644
index a1f2875..0000000
--- a/craftui/sim/config/settings/craft_ipaddr
+++ /dev/null
@@ -1 +0,0 @@
-192.168.5.99/24
diff --git a/craftui/sim/config/settings/local_ipaddr b/craftui/sim/config/settings/local_ipaddr
deleted file mode 100644
index 6f5d511..0000000
--- a/craftui/sim/config/settings/local_ipaddr
+++ /dev/null
@@ -1 +0,0 @@
-192.168.2.1/24
diff --git a/craftui/sim/config/settings/peer_ipaddr b/craftui/sim/config/settings/peer_ipaddr
deleted file mode 100644
index 85be23f..0000000
--- a/craftui/sim/config/settings/peer_ipaddr
+++ /dev/null
@@ -1 +0,0 @@
-192.168.2.2/24
diff --git a/craftui/sim/config/settings/vlan_inband b/craftui/sim/config/settings/vlan_inband
deleted file mode 100644
index 6b61c08..0000000
--- a/craftui/sim/config/settings/vlan_inband
+++ /dev/null
@@ -1 +0,0 @@
-4090
diff --git a/craftui/sim/config/settings/vlan_peer b/craftui/sim/config/settings/vlan_peer
deleted file mode 100644
index 8bd1af1..0000000
--- a/craftui/sim/config/settings/vlan_peer
+++ /dev/null
@@ -1 +0,0 @@
-2000
diff --git a/craftui/sim/etc/platform b/craftui/sim/etc/platform
deleted file mode 100644
index 91bb929..0000000
--- a/craftui/sim/etc/platform
+++ /dev/null
@@ -1 +0,0 @@
-GFCH100
diff --git a/craftui/sim/etc/softwaredate b/craftui/sim/etc/softwaredate
deleted file mode 100644
index 8bdcf18..0000000
--- a/craftui/sim/etc/softwaredate
+++ /dev/null
@@ -1,2 +0,0 @@
-1457565485
-2016-03-09 15:18:05 -0800
diff --git a/craftui/sim/etc/version b/craftui/sim/etc/version
deleted file mode 100644
index 67debcc..0000000
--- a/craftui/sim/etc/version
+++ /dev/null
@@ -1 +0,0 @@
-gfch100-47-pre5-44951-g1e8917f-ed
diff --git a/craftui/sim/proc/uptime b/craftui/sim/proc/uptime
deleted file mode 100644
index 47bea91..0000000
--- a/craftui/sim/proc/uptime
+++ /dev/null
@@ -1 +0,0 @@
-86648.37 85251.17
diff --git a/craftui/sim/tmp/glaukus/modem.json b/craftui/sim/tmp/glaukus/modem.json
deleted file mode 100644
index ba7f021..0000000
--- a/craftui/sim/tmp/glaukus/modem.json
+++ /dev/null
@@ -1 +0,0 @@
-{"firmware":"\/etc\/glaukus\/firmware\/bcm85100mc_1007128.fw","network":{"rxCounters":{"broadcast":15449,"bytes":5662990,"crcErrors":0,"frames":17417,"frames1024_1518":0,"frames128_255":1968,"frames256_511":15449,"frames512_1023":0,"frames64":0,"frames65_127":0,"framesJumbo":0,"framesUndersized":0,"multicast":1968,"unicast":0},"status":0,"statusStr":"UP","txCounters":{"broadcast":0,"bytes":0,"crcErrors":0,"frames":0,"frames1024_1518":0,"frames128_255":0,"frames256_511":0,"frames512_1023":0,"frames64":0,"frames65_127":0,"framesJumbo":0,"framesUndersized":0,"multicast":0,"unicast":0}},"profile":"\/etc\/glaukus\/profiles\/C01_1007128_1500_48_C1_260X300_3c3f2.bin","status":{"absoluteMse":-32768,"acmEngineRxSensorsEnabled":true,"acmEngineTxSwitchEnabled":true,"acquireStatus":0,"acquireStatusStr":"Acquire in progress","carrierOffset":-3749990,"debugIndications":2,"externalAgc":122,"internalAgc":560,"lastAcquireError":3,"lastAcquireErrorStr":"Acquisition failed at frequency sweep.","normalizedMse":-32693,"radialMse":-32663,"resPhNoiseVal":0,"rxAcmProfile":0,"rxSymbolRate":374830203,"txAcmProfile":6,"txSymbolRate":375000000},"temperature":50.19281005859375,"transmitter":{"dcLeakageI":0,"dcLeakageQ":0,"mode":0,"modeStr":"NORMAL","sweepTime":0,"toneFreq":0,"toneSecFreq":0},"version":{"build":128,"chipType":"BCM85100IFSBG","major":100,"minor":7}}
\ No newline at end of file
diff --git a/craftui/sim/tmp/glaukus/radio.json b/craftui/sim/tmp/glaukus/radio.json
deleted file mode 100644
index 4946225..0000000
--- a/craftui/sim/tmp/glaukus/radio.json
+++ /dev/null
@@ -1 +0,0 @@
-{"heaterEnabled":false,"hiTransceiver":{"epot":{"control":"auto","driver":0,"lna":0,"pa":0},"icModel":"BGT80","mode":"tx","pll":{"frequency":85500000,"lockCounts":0,"locked":true},"temp":1481},"loTransceiver":{"epot":{"control":"auto","driver":0,"lna":0,"pa":0},"icModel":"BGT70","mode":"rx","pll":{"frequency":75500000,"lockCounts":0,"locked":true},"temp":1671},"paLnaPowerEnabled":true,"paLnaPowerStatus":"normal","rx":{"agcDigitalGain":42,"agcDigitalGainIndex":3,"lnaCurrentMeas":0,"lnaCurrentSet":0,"rssi":1342},"transceiversPowerEnabled":true,"tx":{"dcI":63,"dcQ":63,"driverCurrentMeas":0,"driverCurrentSet":0,"paCurrentMeas":1428,"paCurrentSet":0,"paTemp":4084,"txPowerControl":"auto","txPowerMeas":2895,"txPowerSet":0,"vgaGain":14},"version":{"hardware":{"major":1,"minor":1,"type":"Chimera V1 000001"},"software":{"build":0,"major":0,"minor":1}}}
\ No newline at end of file
diff --git a/craftui/sim/tmp/gpio/ledstate b/craftui/sim/tmp/gpio/ledstate
deleted file mode 100644
index 80e3de0..0000000
--- a/craftui/sim/tmp/gpio/ledstate
+++ /dev/null
@@ -1 +0,0 @@
-IPV6ACQUIRED
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
new file mode 100644
index 0000000..ddca423
--- /dev/null
+++ b/craftui/www/config.thtml
@@ -0,0 +1,206 @@
+<html>
+<head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <script src="static/jquery-2.1.4.min.js"></script>
+ <link rel="stylesheet" type="text/css" href="static/craft.css">
+ <link rel=icon href=static/favicon.ico>
+ <link rel=stylesheet href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&lang=en">
+ <link rel=stylesheet href=static/default.css>
+</head>
+<body>
+ <header>
+ <section>
+ <h1><img src=static/logo.png alt="Google Fiber"></h1>
+ <nav>
+ <ul>
+ <li ><a href=/>GFCH100</a></li>
+ <li class=active><a href=/config>Configuration</a></li>
+ <li ><a href={{ peerurl }} target=_blank>Peer</a></li>
+ </ul>
+ </nav>
+ </section>
+ </header>
+ <br>
+ <div class="tabs">
+ <div class="tab">
+ <input type="radio" id="tab-1" name="tab-group-1" checked>
+ <label for="tab-1">Site Configuration</label>
+ <div class="content">
+ <b>Platform Parameters:</b>
+ <table>
+ <tr>
+ <td align=center><b>Parameter
+ <td align=center><b>Active Value
+ <td align=center><b>Last Configured
+ <td align=center><b>Configure
+ <td align=center><b>Status
+
+ <tr>
+ <td><b>Craft IP Address
+ <td align=right><span id="platform/active_craft_inet">...</span>
+ <td align=right>
+ <span id="platform/craft_ipaddr">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('craft_ipaddr', 1)">
+ <td>
+ <input id=craft_ipaddr type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('craft_ipaddr')">
+ <td>
+ <span id=craft_ipaddr_result>...</span>
+
+ <tr>
+ <td><b>Link IP Address
+ <td align=right><span id="platform/active_link_inet">...</span>
+ <td align=right>
+ <span id="platform/link_ipaddr">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('link_ipaddr', 1)">
+ <td>
+ <input id=link_ipaddr type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('link_ipaddr')">
+ <td>
+ <span id=link_ipaddr_result>...</span>
+
+ <tr>
+ <td><b>Peer IP Address
+ <td align=right>See Peer
+ <td align=right>
+ <span id="platform/peer_ipaddr">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('peer_ipaddr', 1)">
+ <td>
+ <input id=peer_ipaddr type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('peer_ipaddr')">
+ <td>
+ <span id=peer_ipaddr_result>...</span>
+
+ <tr>
+ <td><b>In-band Management VLAN
+ <td align=right><span id="platform/active_inband_vlan">...</span>
+ <td align=right>
+ <span id="platform/vlan_inband">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_inband', 1)">
+ <td>
+ <input id=vlan_inband type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('vlan_inband')">
+ <td>
+ <span id=vlan_inband_result>...</span>
+
+ <tr>
+ <td><b>Out-of-band Management VLAN
+ <td align=right><span id="platform/active_ooband_vlan">...</span>
+ <td align=right>
+ <span id="platform/vlan_ooband">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_ooband', 1)">
+ <td>
+ <input id=vlan_ooband type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('vlan_ooband')">
+ <td>
+ <span id=vlan_ooband_result>...</span>
+
+ <tr>
+ <td><b>Link VLAN (to peer)
+ <td align=right><span id="platform/active_link_vlan">...</span>
+ <td align=right>
+ <span id="platform/vlan_link">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_peer', 1)">
+ <td>
+ <input id=vlan_peer type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('vlan_peer')">
+ <td>
+ <span id=vlan_peer_result>...</span>
+
+ </table>
+ <b>Radio Parameters:</b>
+ <table>
+ <tr>
+ <td align=center><b>Parameter
+ <td align=center><b>Active Value
+ <td align=center><b>Configure and Apply
+ <td align=center><b>Status
+
+ <tr>
+ <td><b>High Frequency
+ <td align=right><span id="radio/hiTransceiver/pll/frequency">...</span>
+ <td>
+ <input id=freq_hi type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('freq_hi')">
+ <td>
+ <span id=freq_hi_result>...</span>
+
+ <tr>
+ <td><b>Low Frequency
+ <td align=right><span id="radio/loTransceiver/pll/frequency">...</span>
+ <td>
+ <input id=freq_lo type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('freq_lo')">
+ <td>
+ <span id=freq_lo_result>...</span>
+
+ <tr>
+ <td><b>High Frequency Mode
+ <td align=right><span id="radio/hiTransceiver/mode">...</span>
+ <td>
+ <input id=mode_hi type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('mode_hi')">
+ <td>
+ <span id=mode_hi_result>...</span>
+
+ <tr>
+ <td><b>Transmit Power (dB x 100)
+ <td align=right><span id="radio/tx/paPowerSet">...</span>
+ <td>
+ <input id=tx_powerlevel type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('tx_powerlevel')">
+ <td>
+ <span id=tx_powerlevel_result>...</span>
+
+ <tr>
+ <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=palna_on type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('palna_on')">
+ <td>
+ <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>
+ </div>
+ <div class="tab">
+ <input type="radio" id="tab-2" name="tab-group-1">
+ <label for="tab-2">Debug</label>
+ <div class="content">
+ <b>refreshCount:</b><span class="values" id="platform/refreshCount">...</span><br>
+ <b>unhandled xml:</b><span class="values" id="unhandled"></span>
+ </div>
+ </div>
+ </div>
+ <script src="static/craft.js"></script>
+</body>
+</html>
diff --git a/craftui/www/index.thtml b/craftui/www/index.thtml
index 4388f77..20bba69 100644
--- a/craftui/www/index.thtml
+++ b/craftui/www/index.thtml
@@ -11,11 +11,11 @@
<body>
<header>
<section>
- <h1><a href=https://fiber.google.com/myfiber/><img src=static/logo.png alt="Google Fiber"></a></h1>
+ <h1><img src=static/logo.png alt="Google Fiber"></h1>
<nav>
<ul>
<li class=active><a href=/>GFCH100</a></li>
- <li ><a href=craft/config/>Configuration</a></li>
+ <li ><a href=/config>Configuration</a></li>
<li ><a href={{ peerurl }} target=_blank>Peer</a></li>
</ul>
</nav>
@@ -27,6 +27,7 @@
<input type="radio" id="tab-1" name="tab-group-1" checked>
<label for="tab-1">Platform</label>
<div class="content">
+ <b>Serial Number:</b><span class="values" id="platform/serialno">...</span><br>
<b>Platform:</b><span class="values" id="platform/platform">...</span><br>
<b>Software Version:</b><span class="values" id="platform/version">...</span><br>
<b>Software Date:</b><span class="values" id="platform/softwaredate">...</span><br>
@@ -38,68 +39,286 @@
<input type="radio" id="tab-2" name="tab-group-1">
<label for="tab-2">Network</label>
<div class="content">
- <b>Craft Port IP Address:</b><span class="values" id="platform/craft_ipaddr">...</span><br>
- <b>Local IP Address:</b><span class="values" id="platform/local_ipaddr">...</span><br>
- <b>Peer IP Address:</b><span class="values" id="platform/peer_ipaddr">...</span><br>
- <b>Inband Vlan:</b><span class="values" id="platform/vlan_inband">...</span><br>
- <b>Peer Vlan:</b><span class="values" id="platform/vlan_peer">...</span><br>
+ <b>IP Addresses:</b>
+ <table>
+ <tr>
+ <td align=center><b>Port</b></td>
+ <td align=center><b>MAC</b></td>
+ <td align=center><b>VLAN</b></td>
+ <td align=center><b>IPv4</b></td>
+ <td align=center><b>IPv6</b></td></tr>
+ <tr>
+ <td><b>Craft</b></td>
+ <td align=right><span id="platform/craft_mac">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/active_craft_inet">...</span></td>
+ <td align=right><span id="platform/active_craft_inet6">...</span></td></tr>
+ <tr>
+ <td><b>In-Band</b></td>
+ <td align=right><span id="platform/bridge_mac">...</span></td>
+ <td align=right><span id="platform/active_inband_vlan">...</span></td>
+ <td align=right><span id="platform/active_bridge_inet">...</span></td>
+ <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/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>
+ <td align=right><span id="platform/active_link_vlan">...</span></td>
+ <td align=right><span id="platform/active_link_inet">...</span></td>
+ <td align=right><span id="platform/active_link_inet6">...</span></td></tr>
+ </table>
+ <b>Packet Counters:</b>
<table>
<tr>
<td><b></b></td>
- <td colspan=3><b>received</b></td>
- <td colspan=3><b>transmitted</b></td></tr>
+ <td colspan=5 align=center><b>received</b></td>
+ <td colspan=5 align=center><b>transmitted</b></td>
+ <td colspan=9 align=center><b>errors</b></td></tr>
<tr>
- <td><b>interface</b></td>
- <td><b>bytes (UC BC MC)</b></td>
- <td><b>frames (<64 <128 <256 <512 <1024 jumbo)</b></td>
- <td><b>errors</b></td>
- <td><b>bytes (UC BC MC)</b></td>
- <td><b>frames</b></td>
- <td><b>errors</b></td></tr>
+ <td align=center><b>interface</b></td>
+
+ <td align=center><b>bytes</b></td>
+ <td align=center><b>frames</b></td>
+ <td align=center><b>multicast</b></td>
+ <td align=center><b>broadcast</b></td>
+ <td align=center><b>unicast</b></td>
+
+ <td align=center><b>bytes</b></td>
+ <td align=center><b>frames</b></td>
+ <td align=center><b>multicast</b></td>
+ <td align=center><b>broadcast</b></td>
+ <td align=center><b>unicast</b></td>
+
+ <td align=center><b>rx errors</b></td>
+ <td align=center><b>rx dropped</b></td>
+ <td align=center><b>rx CRC</b></td>
+ <td align=center><b>rx Undersize</b></td>
+ <td align=center><b>tx errors</b></td>
+ <td align=center><b>tx dropped</b></td>
+ <td align=center><b>tx CRC</b></td>
+ <td align=center><b>tx Undersize</b></td>
+ <td align=center><b>collisions</b></td>
<tr>
- <td>modem (from/to switch)</td>
- <td>
- <span id="modem/network/rxCounters/bytes">...</span>
- (<span id="modem/network/rxCounters/unicast">...</span>
- <span id="modem/network/rxCounters/broadcast">...</span>
- <span id="modem/network/rxCounters/multicast">...</span>)</td>
- <td>
- <span id="modem/network/rxCounters/frames">...</span>
- (<span id="modem/network/rxCounters/frames64">...</span>
- <span id="modem/network/rxCounters/frames65_127">...</span>
- <span id="modem/network/rxCounters/frames128_255">...</span>
- <span id="modem/network/rxCounters/frames256_511">...</span>
- <span id="modem/network/rxCounters/frames512_1023">...</span>
- <span id="modem/network/rxCounters/frames1024_1518">...</span>
- <span id="modem/network/rxCounters/framesJumbo">...</span>)</td>
- <td>
- CRC: <span id="modem/network/rxCounters/crcErrors">...</span>
- Undersize: <span id="modem/network/rxCounters/framesUndersized">...</span></td>
- <td>
- <span id="modem/network/txCounters/bytes">...</span>
- (<span id="modem/network/txCounters/unicast">...</span>
- <span id="modem/network/txCounters/broadcast">...</span>
- <span id="modem/network/txCounters/multicast">...</span>)</td>
- <td>
- <span id="modem/network/txCounters/frames">...</span>
- (<span id="modem/network/txCounters/frames64">...</span>
- <span id="modem/network/txCounters/frames65_127">...</span>
- <span id="modem/network/txCounters/frames128_255">...</span>
- <span id="modem/network/txCounters/frames256_511">...</span>
- <span id="modem/network/txCounters/frames512_1023">...</span>
- <span id="modem/network/txCounters/frames1024_1518">...</span>
- <span id="modem/network/txCounters/framesJumbo">...</span>)</td>
- <td>
- CRC: <span id="modem/network/txCounters/crcErrors">...</span>
- Undersize: <span id="modem/network/txCounters/framesUndersized">...</span></td></tr>
+ <td><b>Modem (from/to switch)<b></td>
+ <td align=right><span id="modem/network/rxCounters/bytes">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/frames">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/multicast">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/broadcast">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/unicast">...</span></td>
+
+ <td align=right><span id="modem/network/txCounters/bytes">...</span></td>
+ <td align=right><span id="modem/network/txCounters/frames">...</span></td>
+ <td align=right><span id="modem/network/txCounters/multicast">...</span></td>
+ <td align=right><span id="modem/network/txCounters/broadcast">...</span></td>
+ <td align=right><span id="modem/network/txCounters/unicast">...</span></td>
+
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="modem/network/rxCounters/crcErrors">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/framesUndersized">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="modem/network/txCounters/crcErrors">...</span></td>
+ <td align=right><span id="modem/network/txCounters/framesUndersized">...</span></td>
+ <td align=right>-</td></tr>
+
+ <tr>
+ <td><b>Craft<b></td>
+ <td align=right><span id="platform/craft_rx_bytes">...</span></td>
+ <td align=right><span id="platform/craft_rx_packets">...</span></td>
+ <td align=right><span id="platform/craft_multicast">...</span></td>
+ <td align=right><span id="platform/craft_broadcast">...</span></td>
+ <td align=right><span id="platform/craft_unicast">...</span></td>
+
+ <td align=right><span id="platform/craft_tx_bytes">...</span></td>
+ <td align=right><span id="platform/craft_tx_packets">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
+ <td align=right><span id="platform/craft_rx_errors">...</span></td>
+ <td align=right><span id="platform/craft_rx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/craft_tx_errors">...</span></td>
+ <td align=right><span id="platform/craft_tx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/craft_collisions">...</span></td>
+
+ <tr>
+ <td><b>In-Band<b></td>
+ <td align=right><span id="platform/bridge_rx_bytes">...</span></td>
+ <td align=right><span id="platform/bridge_rx_packets">...</span></td>
+ <td align=right><span id="platform/bridge_multicast">...</span></td>
+ <td align=right><span id="platform/bridge_broadcast">...</span></td>
+ <td align=right><span id="platform/bridge_unicast">...</span></td>
+
+ <td align=right><span id="platform/bridge_tx_bytes">...</span></td>
+ <td align=right><span id="platform/bridge_tx_packets">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
+ <td align=right><span id="platform/bridge_rx_errors">...</span></td>
+ <td align=right><span id="platform/bridge_rx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/bridge_tx_errors">...</span></td>
+ <td align=right><span id="platform/bridge_tx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/bridge_collisions">...</span></td>
+
+ <tr>
+ <td><b>Out-of-Band (PoE)<b></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/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/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/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/ooband_collisions">...</span></td>
+
+ <tr>
+ <td><b>Link (to peer)<b></td>
+ <td align=right><span id="platform/link_rx_bytes">...</span></td>
+ <td align=right><span id="platform/link_rx_packets">...</span></td>
+ <td align=right><span id="platform/link_multicast">...</span></td>
+ <td align=right><span id="platform/link_broadcast">...</span></td>
+ <td align=right><span id="platform/link_unicast">...</span></td>
+
+ <td align=right><span id="platform/link_tx_bytes">...</span></td>
+ <td align=right><span id="platform/link_tx_packets">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
+ <td align=right><span id="platform/link_rx_errors">...</span></td>
+ <td align=right><span id="platform/link_rx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/link_tx_errors">...</span></td>
+ <td align=right><span id="platform/link_tx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/link_collisions">...</span></td>
+
<tr>
- <td>SOC (from/to switch)</td>
+ <td><b>Switch Port 0/0 (PoE)</b></td>
+ <td align=right><span id="platform/switch/0/0/bytes_received">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/0/multicast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/0/broadcast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/0/unicast_packets_received">...</span></td>
+
+ <td align=right><span id="platform/switch/0/0/bytes_sent">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/0/multicast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/0/broadcast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/0/unicast_packets_sent">...</span></td>
+
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
<tr>
- <td>SOC (port 0)</td>
+ <td><b>Switch Port 0/4 (SOC)</b></td>
+ <td align=right><span id="platform/switch/0/4/bytes_received">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/4/multicast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/4/broadcast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/4/unicast_packets_received">...</span></td>
+
+ <td align=right><span id="platform/switch/0/4/bytes_sent">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/4/multicast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/4/broadcast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/4/unicast_packets_sent">...</span></td>
+
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
<tr>
- <td>POE (port 0)</td>
+ <td><b>Switch Port 0/24 (modem)</b></td>
+ <td align=right><span id="platform/switch/0/24/bytes_received">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/24/multicast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/24/broadcast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/24/unicast_packets_received">...</span></td>
+
+ <td align=right><span id="platform/switch/0/24/bytes_sent">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/24/multicast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/24/broadcast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/24/unicast_packets_sent">...</span></td>
+
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
<tr>
- <td>POE (port 0)</td>
+ <td><b>Switch Port 0/25 (SFP+)</b></td>
+ <td align=right><span id="platform/switch/0/25/bytes_received">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/25/multicast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/25/broadcast_packets_received">...</span></td>
+ <td align=right><span id="platform/switch/0/25/unicast_packets_received">...</span></td>
+
+ <td align=right><span id="platform/switch/0/25/bytes_sent">...</span></td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/switch/0/25/multicast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/25/broadcast_packets_sent">...</span></td>
+ <td align=right><span id="platform/switch/0/25/unicast_packets_sent">...</span></td>
+
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+
</table>
</div>
</div>
@@ -107,10 +326,13 @@
<input type="radio" id="tab-3" name="tab-group-1">
<label for="tab-3">Modem</label>
<div class="content">
- <b>Chip:</b><span class="values" id="modem/version/chipType">...</span><br>
+ <b>Chip:</b><span class="values" id="modem/version/api/chipType">...</span><br>
<b>Firmware:</b><span class="values" id="modem/firmware">...</span><br>
- <b>Version:</b><span class="values">
- <span id="modem/version/major">...</span>.<span id="modem/version/minor">?</span>.<span id="modem/version/build">?</span>
+ <b>Bootloader Version:</b><span class="values">
+ <span id="modem/version/bootloader/major">...</span>.<span id="modem/version/bootloader/minor">?</span>.<span id="modem/version/bootloader/build">?</span>
+ </span><br>
+ <b>API Version:</b><span class="values">
+ <span id="modem/version/api/major">...</span>.<span id="modem/version/api/minor">?</span>.<span id="modem/version/api/build">?</span>
</span><br>
<b>Profile:</b><span class="values" id="modem/profile">...</span><br>
<b>Temperature:</b><span class="values" id="modem/temperature">...</span><br>
@@ -159,6 +381,7 @@
Q: <span id="modem/transmitter/dcLeakageQ">...</span>
</span><br>
<b>Transmitter:</b><span class="values">
+ signal gain: <span id="modem/transmitter/signalGain">...</span>
sweep time: <span id="modem/transmitter/sweepTime">...</span>
tone freq: <span id="modem/transmitter/toneFreq">...</span>
tone sec freq: <span id="modem/transmitter/toneSecFreq">...</span>
diff --git a/craftui/www/static/craft.js b/craftui/www/static/craft.js
index 620ff71..81501aa 100644
--- a/craftui/www/static/craft.js
+++ b/craftui/www/static/craft.js
@@ -68,9 +68,6 @@
CraftUI.flattenAndUpdateFields(list, '');
}
CraftUI.updateField('unhandled', self.unhandled);
- if (self.unhandled.length > 0) {
- console.log(self.unhandled);
- }
};
var payload = [];
payload.push('checksum=' + encodeURIComponent(CraftUI.info.checksum));
@@ -79,4 +76,38 @@
xhr.send();
};
+CraftUI.config = function(key, activate) {
+ // POST as json
+ 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 } ] };
+ }
+ var txt = JSON.stringify(data);
+ var resultid = key + "_result"
+ var el = document.getElementById(resultid);
+ xhr.onload = function(e) {
+ var json = JSON.parse(xhr.responseText);
+ if (json.error == 0) {
+ el.innerHTML = action + " successfully.";
+ } else {
+ el.innerHTML = "Error: " + json.errorstring;
+ }
+ CraftUI.getInfo();
+ }
+ xhr.onerror = function(e) {
+ el.innerHTML = xhr.statusText + xhr.responseText;
+ }
+ el.innerHTML = "sending...";
+ xhr.send(txt);
+};
+
new CraftUI();
diff --git a/ginstall/ginstall.py b/ginstall/ginstall.py
index 1d233c6..983d76c 100755
--- a/ginstall/ginstall.py
+++ b/ginstall/ginstall.py
@@ -771,13 +771,9 @@
Raises:
Fatal: if install fails
"""
- if GetPlatform() == 'GFHD254' and GetMemTotal() < 4*1e9:
- print 'Skipping bootloader on 2GB lockdown.'
- return
-
loader_start = loader.filelike.tell()
installed = False
- for i in ['cfe', 'loader', 'loader0', 'loader1', 'flash0.bolt']:
+ for i in ['cfe', 'loader', 'loader0', 'loader1', 'flash0.bolt', 'uboot']:
mtd = GetMtdDevForNameOrNone(i)
if mtd:
WriteLoaderToMtd(loader, loader_start, mtd, 'loader')
diff --git a/gpio-mailbox/broadcom.c b/gpio-mailbox/broadcom.c
index 315b8f8..575f197 100644
--- a/gpio-mailbox/broadcom.c
+++ b/gpio-mailbox/broadcom.c
@@ -26,6 +26,11 @@
#define DEVMEM "/dev/mem"
+/* This value, from old code, controls the pwm period. The duty cycle
+ is defined as on/(period + 1) and on is defined as (on/Fv). Fv is
+ the frequency of the variable rate PWM.*/
+static const int PWM_CYCLE_PERIOD = 0x63;
+
struct PinHandle_s {
int unused;
};
@@ -61,8 +66,7 @@
int old_val;
};
-
-struct Fan {
+struct PwmControl {
int is_present;
int open_drain;
unsigned int offset_data;
@@ -70,33 +74,35 @@
int old_percent;
};
-
struct Temp {
int is_present;
unsigned int offset_data;
double (*get_temp)(struct Temp* t);
};
-
struct Voltage {
int is_present;
unsigned int offset_data;
double (*get_voltage)(struct Voltage* v);
};
+struct Leds {
+ struct Gpio led_red;
+ struct Gpio led_blue;
+ struct Gpio led_activity;
+ struct Gpio led_standby;
+ struct PwmControl led_brightness;
+};
struct platform_info {
const char *name;
off_t mmap_base;
size_t mmap_size;
void (*init)(struct platform_info* p);
- struct Gpio led_red;
- struct Gpio led_blue;
- struct Gpio led_activity;
- struct Gpio led_standby;
+ struct Leds leds;
struct Gpio reset_button;
struct Gpio fan_tick;
- struct Fan fan_control;
+ struct PwmControl fan_control;
struct Temp temp_monitor;
struct Voltage voltage_monitor;
};
@@ -113,49 +119,51 @@
.name = "GFHD100",
.mmap_base = 0x10400000, // base of many brcm registers
.mmap_size = 0x40000,
- .led_red = {
- .is_present = 1, // GPIO 17
- .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
- .offset_data = 0x94c4, // GIO_AON_DATA_LO
- .mask = 0x00020000, // 1<<17
- .shift = 17,
- .off_value = 0,
- .on_value = 1,
- .direction_value = 0,
- .old_val = -1,
- },
- .led_blue = {
- .is_present = 1, // GPIO 12
- .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
- .offset_data = 0x94c4, // GIO_AON_DATA_LO
- .mask = 0x00001000, // 1<<12
- .shift = 12,
- .off_value = 0,
- .on_value = 1,
- .direction_value = 0,
- .old_val = -1,
- },
- .led_activity = {
- .is_present = 1, // GPIO 13
- .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
- .offset_data = 0x94c4, // GIO_AON_DATA_LO
- .mask = 0x00002000, // 1<<13
- .shift = 13,
- .off_value = 0,
- .on_value = 1,
- .direction_value = 0,
- .old_val = -1,
- },
- .led_standby = {
- .is_present = 1, // GPIO 10
- .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
- .offset_data = 0x94c4, // GIO_AON_DATA_LO
- .mask = 0x00000400, // 1<<10
- .shift = 10,
- .off_value = 0,
- .on_value = 1,
- .direction_value = 0,
- .old_val = -1,
+ .leds = {
+ .led_red = {
+ .is_present = 1, // GPIO 17
+ .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
+ .offset_data = 0x94c4, // GIO_AON_DATA_LO
+ .mask = 0x00020000, // 1<<17
+ .shift = 17,
+ .off_value = 0,
+ .on_value = 1,
+ .direction_value = 0,
+ .old_val = -1,
+ },
+ .led_blue = {
+ .is_present = 1, // GPIO 12
+ .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
+ .offset_data = 0x94c4, // GIO_AON_DATA_LO
+ .mask = 0x00001000, // 1<<12
+ .shift = 12,
+ .off_value = 0,
+ .on_value = 1,
+ .direction_value = 0,
+ .old_val = -1,
+ },
+ .led_activity = {
+ .is_present = 1, // GPIO 13
+ .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
+ .offset_data = 0x94c4, // GIO_AON_DATA_LO
+ .mask = 0x00002000, // 1<<13
+ .shift = 13,
+ .off_value = 0,
+ .on_value = 1,
+ .direction_value = 0,
+ .old_val = -1,
+ },
+ .led_standby = {
+ .is_present = 1, // GPIO 10
+ .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
+ .offset_data = 0x94c4, // GIO_AON_DATA_LO
+ .mask = 0x00000400, // 1<<10
+ .shift = 10,
+ .off_value = 0,
+ .on_value = 1,
+ .direction_value = 0,
+ .old_val = -1,
+ },
},
.reset_button = {
.is_present = 1, // GPIO 4
@@ -200,33 +208,35 @@
.name = "GFMS100",
.mmap_base = 0x10400000, // base of many brcm registers
.mmap_size = 0x40000,
- .led_red = {
- .is_present = 1, // GPIO 17
- .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
- .offset_data = 0x94c4, // GIO_AON_DATA_LO 0..17
- .mask = 0x00020000, // 1<<17
- .shift = 17,
- .off_value = 0,
- .on_value = 1,
- .direction_value = 0,
- .old_val = -1,
- },
- .led_blue = {
- .is_present = 0,
- },
- .led_activity = {
- .is_present = 1, // GPIO 13
- .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
- .offset_data = 0x94c4, // GIO_AON_DATA_LO
- .mask = 0x00002000, // 1<<13
- .shift = 13,
- .off_value = 0,
- .on_value = 1,
- .direction_value = 0,
- .old_val = -1,
- },
- .led_standby = {
- .is_present = 0,
+ .leds = {
+ .led_red = {
+ .is_present = 1, // GPIO 17
+ .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
+ .offset_data = 0x94c4, // GIO_AON_DATA_LO 0..17
+ .mask = 0x00020000, // 1<<17
+ .shift = 17,
+ .off_value = 0,
+ .on_value = 1,
+ .direction_value = 0,
+ .old_val = -1,
+ },
+ .led_blue = {
+ .is_present = 0,
+ },
+ .led_activity = {
+ .is_present = 1, // GPIO 13
+ .offset_direction = 0x94c8, // GIO_AON_IODIR_LO
+ .offset_data = 0x94c4, // GIO_AON_DATA_LO
+ .mask = 0x00002000, // 1<<13
+ .shift = 13,
+ .off_value = 0,
+ .on_value = 1,
+ .direction_value = 0,
+ .old_val = -1,
+ },
+ .led_standby = {
+ .is_present = 0,
+ },
},
.reset_button = {
.is_present = 1, // GPIO 4
@@ -272,35 +282,37 @@
.init = init_gfhd200,
.mmap_base = 0x10400000, // AON_PIN_CTRL ...
.mmap_size = 0x30000,
- .led_red = {
- .is_present = 1, // GPIO 5
- .pinmux_offset = 0x8500, // PIN_MUX_CTRL_0
- .pinmux_mask = 0xf0000000,
- .pinmux_value = 0x10000000, // LED_LD1 (segment 1 on led digit1)
- .offset_data = 0x9018, // GIO_AON_DATA_LO
- .mask = 0x00000002, // 1<<1
- .shift = 1,
- .off_value =1,
- .on_value = 0,
- .old_val = -1,
- },
- .led_blue = {
- .is_present = 0,
- },
- .led_activity = {
- .is_present = 1, // GPIO 4
- .pinmux_offset = 0x8500, // PIN_MUX_CTRL_0
- .pinmux_mask = 0x0f000000,
- .pinmux_value = 0x01000000, // LED_LD0 (segment 0 on led digit1)
- .offset_data = 0x9018, // GIO_AON_DATA_LO
- .mask = 0x00000001, // 1<<0
- .shift = 0,
- .off_value = 1,
- .on_value = 0,
- .old_val = -1,
- },
- .led_standby = {
- .is_present = 0,
+ .leds = {
+ .led_red = {
+ .is_present = 1, // GPIO 5
+ .pinmux_offset = 0x8500, // PIN_MUX_CTRL_0
+ .pinmux_mask = 0xf0000000,
+ .pinmux_value = 0x10000000, // LED_LD1 (segment 1 on led digit1)
+ .offset_data = 0x9018, // GIO_AON_DATA_LO
+ .mask = 0x00000002, // 1<<1
+ .shift = 1,
+ .off_value =1,
+ .on_value = 0,
+ .old_val = -1,
+ },
+ .led_blue = {
+ .is_present = 0,
+ },
+ .led_activity = {
+ .is_present = 1, // GPIO 4
+ .pinmux_offset = 0x8500, // PIN_MUX_CTRL_0
+ .pinmux_mask = 0x0f000000,
+ .pinmux_value = 0x01000000, // LED_LD0 (segment 0 on led digit1)
+ .offset_data = 0x9018, // GIO_AON_DATA_LO
+ .mask = 0x00000001, // 1<<0
+ .shift = 0,
+ .off_value = 1,
+ .on_value = 0,
+ .old_val = -1,
+ },
+ .led_standby = {
+ .is_present = 0,
+ },
},
.reset_button = {
.is_present = 1, // GPIO 3
@@ -332,35 +344,44 @@
.init = init_gfhd254,
.mmap_base = 0xf0400000, // AON_PIN_CTRL ...
.mmap_size = 0xe0000,
- .led_red = {
- .is_present = 1, // AON_GPIO_05
- .pinmux_offset = 0x10700, // PIN_MUX_CTRL_0
- .pinmux_mask = 0x00f00000,
- .pinmux_value = 0x00200000, // LED_LD_13
- .offset_data = 0x1701c, // LDK_DIGIT1
- .mask = 1<<13, // 1<<13
- .shift = 13,
- .off_value =1,
- .on_value = 0,
- .old_val = -1,
- },
- .led_blue = {
- .is_present = 0,
- },
- .led_activity = {
- .is_present = 1, // AON_GPIO_04
- .pinmux_offset = 0x10700, // PIN_MUX_CTRL_0
- .pinmux_mask = 0x000f0000,
- .pinmux_value = 0x00020000, // LED_LD_12
- .offset_data = 0x1701c, // LDK_DIGIT1
- .mask = 1<<12, // 1<<12
- .shift = 12,
- .off_value = 1,
- .on_value = 0,
- .old_val = -1,
- },
- .led_standby = {
- .is_present = 0,
+ .leds = {
+ .led_red = {
+ .is_present = 1, // AON_GPIO_05
+ .pinmux_offset = 0x10700, // PIN_MUX_CTRL_0
+ .pinmux_mask = 0x00f00000,
+ .pinmux_value = 0x00200000, // LED_LD_13
+ .offset_data = 0x1701c, // LDK_DIGIT1
+ .mask = 1<<13, // 1<<13
+ .shift = 13,
+ .off_value =1,
+ .on_value = 0,
+ .old_val = -1,
+ },
+ .led_blue = {
+ .is_present = 0,
+ },
+ .led_activity = {
+ .is_present = 1, // AON_GPIO_04
+ .pinmux_offset = 0x10700, // PIN_MUX_CTRL_0
+ .pinmux_mask = 0x000f0000,
+ .pinmux_value = 0x00020000, // LED_LD_12
+ .offset_data = 0x1701c, // LDK_DIGIT1
+ .mask = 1<<12, // 1<<12
+ .shift = 12,
+ .off_value = 1,
+ .on_value = 0,
+ .old_val = -1,
+ },
+ .led_standby = {
+ .is_present = 0,
+ },
+ .led_brightness = {
+ .is_present = 1, // GPIO_098
+ .open_drain = 0,
+ .offset_data = 0x9000, // PWM_2
+ .channel = 0,
+ .old_percent = -1,
+ },
},
.reset_button = {
.is_present = 1, // GPIO_009
@@ -408,79 +429,21 @@
struct platform_info *platform = NULL;
-/* set LED/Keypad timings to control LED brightness */
-static void init_gfhd200(UNUSED struct platform_info* p) {
- volatile uint32_t* reg;
+/* PWM operates on either channel 0 or 1. We want to get the duty cycle value
+ by calculating it from the "ON" register, located offset 6 for channel 0
+ and 8 for channel 1.
- reg = mmap_addr + 0x9034; // LDK_CONTROL
- *reg = 0x01; // reset
- *reg = 0x18; // ver=1 inv_led=1
-
- reg = mmap_addr + 0x9008; // LDK_PRESCHI, LO (clock divisor)
- reg[0] = 0x00;
- reg[1] = 0x10; // tick = clock / 0x0010, not sure what clock is
-
- reg = mmap_addr + 0x9010; // LDK_DUTYOFF, ON
- reg[0] = 0x40;
- reg[1] = 0xc0; // 0x40 off ticks then 0xc0 on ticks == 75% brightness
-}
-
-/* set LED/Keypad timings to control LED brightness */
-static void init_gfhd254(UNUSED struct platform_info* p) {
- volatile uint32_t* reg;
-
- // The led display controller works like this:
- // - there are 16 gpios (we connect our leds to 2 of these)
- // - the controller steps through digit1-4 and then status
- // - bit0 in a register maps to a particular gpio
- // when digit1 is being displayed the controller uses digit1_bit[15:0] to
- // drive the gpios. When digit 2 is displayed digit2[15:0] and so forth.
- // - duty_on controls how many clocks a digit is displayed
- // - duty_off controls number of clocks of all off time when switching
- // between digits
- //
- // To get 100% brightness you set all of digit1-4 and status to 1 for the led
- // you are drivng, and set duty_off to 0.
- //
- // Here we also invert the values, so a 1 means off, and 0 means on, this is
- // done because for unknown reasons the time between status and digit1 is on,
- // so we can't get the brightness to 0 unless we invert.
- //
- // For simplicity we enable only one of the digits because the leds are
- // already insanely bright, and then to disable an led we simply toggle the
- // bit in that one digit register.
- //
- // The red led is attached to bit 13 and blue led is attached to bit 12.
-
- reg = mmap_addr + 0x17034; // LDK_CONTROL
- *reg = 0x01; // reset
- *reg = 0x18; // ver=1
-
- reg = mmap_addr + 0x17018;
- reg[0] = 0xffff; // LDK_DIGIT2
- reg[1] = 0xcfff; // LDK_DIGIT1
- reg[2] = 0xffff; // LDK_DIGIT4
- reg[3] = 0xffff; // LDK_DIGIT3
- reg[5] = 0xffff; // LDK_STATUS
-
- reg = mmap_addr + 0x17008; // LDK_PRESCHI, LO (clock divisor)
- reg[0] = 0x00;
- reg[1] = 0x10; // tick = clock / 0x0010, not sure what clock is
-
- reg = mmap_addr + 0x17010; // LDK_DUTYOFF, ON
- reg[0] = 0x40;
- reg[1] = 0xc0; // 0x40 off ticks then 0xc0 on ticks to dim a bit more.
-
-
- // The fan is connected to PWM3, the register PWM3_CWORD_LSB is set to 1,
- // this is the frequency of the PWM, the other pwm register control
- // the duty cycle.
- reg = mmap_addr + 0x9014; // PWM3_CWORD_LSB
- reg[0] = 1;
+ Duty cycle is calculated by ON / Period.
+*/
+static UNUSED int get_pwm(struct PwmControl *f) {
+ volatile uint32_t* reg = mmap_addr + f->offset_data;
+ uint8_t offset = f->channel ? 8 : 6;
+ uint32_t val = reg[offset];
+ return ((uint64_t)val * 100) / PWM_CYCLE_PERIOD;
}
// Set the given PWM (pulse width modulator) to the given percent duty cycle.
-static void set_pwm(struct Fan *f, int percent) {
+static void set_pwm(struct PwmControl *f, int percent) {
volatile uint32_t* reg;
uint32_t mask0, val0, mask1, val1, on;
@@ -509,8 +472,8 @@
}
reg[0] = (reg[0] & mask0) | val0;
reg[1] = (reg[1] & mask1) | val1;
- reg[on] = 0x63 * percent/100; // 0x63 is what old code used
- reg[on+1] = 0x63;
+ reg[on] = (PWM_CYCLE_PERIOD * percent)/100; // 0x63 is what old code used
+ reg[on+1] = PWM_CYCLE_PERIOD;
}
// Get the CPU temperature. I think it's in Celsius.
@@ -771,51 +734,138 @@
}
int has_red_led(void) {
- return (platform->led_red.is_present);
+ return (platform->leds.led_red.is_present);
}
int has_blue_led(void) {
- return (platform->led_blue.is_present);
+ return (platform->leds.led_blue.is_present);
}
int has_activity_led(void) {
- return (platform->led_activity.is_present);
+ return (platform->leds.led_activity.is_present);
}
int has_standby_led(void) {
- return (platform->led_standby.is_present);
+ return (platform->leds.led_standby.is_present);
}
int get_red_led(void) {
- return get_gpio(&platform->led_red);
+ return get_gpio(&platform->leds.led_red);
}
int get_blue_led(void) {
- return get_gpio(&platform->led_blue);
+ return get_gpio(&platform->leds.led_blue);
}
int get_activity_led(void) {
- return get_gpio(&platform->led_activity);
+ return get_gpio(&platform->leds.led_activity);
}
int get_standby_led(void) {
- return get_gpio(&platform->led_standby);
+ return get_gpio(&platform->leds.led_standby);
}
+/* TODO(doughorn): The set_*_led functions should apply the brightness
+ as well */
void set_red_led(int level) {
- set_gpio(&platform->led_red, level ? 1 : 0);
+ set_gpio(&platform->leds.led_red, level ? 1 : 0);
}
void set_blue_led(int level) {
- set_gpio(&platform->led_blue, level ? 1 : 0);
+ set_gpio(&platform->leds.led_blue, level ? 1 : 0);
}
void set_activity_led(int level) {
- set_gpio(&platform->led_activity, level ? 1 : 0);
+ set_gpio(&platform->leds.led_activity, level ? 1 : 0);
}
void set_standby_led(int level) {
- set_gpio(&platform->led_standby, level ? 1 : 0);
+ set_gpio(&platform->leds.led_standby, level ? 1 : 0);
+}
+
+void set_led_brightness(int level) {
+ set_pwm(&platform->leds.led_brightness, level);
+}
+
+/* set LED/Keypad timings to control LED brightness */
+static void init_gfhd200(UNUSED struct platform_info* p) {
+ volatile uint32_t* reg;
+
+ reg = mmap_addr + 0x9034; // LDK_CONTROL
+ *reg = 0x01; // reset
+ *reg = 0x18; // ver=1 inv_led=1
+
+ reg = mmap_addr + 0x9008; // LDK_PRESCHI, LO (clock divisor)
+ reg[0] = 0x00;
+ reg[1] = 0x10; // tick = clock / 0x0010, not sure what clock is
+
+ reg = mmap_addr + 0x9010; // LDK_DUTYOFF, ON
+ reg[0] = 0x40;
+ reg[1] = 0xc0; // 0x40 off ticks then 0xc0 on ticks == 75% brightness
+}
+
+/* set LED/Keypad timings to control LED brightness */
+static void init_gfhd254(UNUSED struct platform_info* p) {
+ volatile uint32_t* reg;
+
+ // The following comment explains how the LED controller works on <= EVT3.
+ // For EVT4+, the LED controller was changed to control via PWM. We currently
+ // configure both. The EVT3 specific code can be removed at a later date.
+ //
+ // The led display controller works like this:
+ // - there are 16 gpios (we connect our leds to 2 of these)
+ // - the controller steps through digit1-4 and then status
+ // - bit0 in a register maps to a particular gpio
+ // when digit1 is being displayed the controller uses digit1_bit[15:0] to
+ // drive the gpios. When digit 2 is displayed digit2[15:0] and so forth.
+ // - duty_on controls how many clocks a digit is displayed
+ // - duty_off controls number of clocks of all off time when switching
+ // between digits
+ //
+ // To get 100% brightness you set all of digit1-4 and status to 1 for the led
+ // you are drivng, and set duty_off to 0.
+ //
+ // Here we also invert the values, so a 1 means off, and 0 means on, this is
+ // done because for unknown reasons the time between status and digit1 is on,
+ // so we can't get the brightness to 0 unless we invert.
+ //
+ // For simplicity we enable only one of the digits because the leds are
+ // already insanely bright, and then to disable an led we simply toggle the
+ // bit in that one digit register.
+ //
+ // The red led is attached to bit 13 and blue led is attached to bit 12.
+ reg = mmap_addr + 0x17034; // LDK_CONTROL
+ *reg = 0x01; // reset
+ *reg = 0x18; // ver=1
+
+ reg = mmap_addr + 0x17018;
+ reg[0] = 0xffff; // LDK_DIGIT2
+ reg[1] = 0xcfff; // LDK_DIGIT1
+ reg[2] = 0xffff; // LDK_DIGIT4
+ reg[3] = 0xffff; // LDK_DIGIT3
+ reg[5] = 0xffff; // LDK_STATUS
+
+ reg = mmap_addr + 0x17008; // LDK_PRESCHI, LO (clock divisor)
+ reg[0] = 0x00;
+ reg[1] = 0x10; // tick = clock / 0x0010, not sure what clock is
+
+ reg = mmap_addr + 0x17010; // LDK_DUTYOFF, ON
+ reg[0] = 0x40;
+ reg[1] = 0xc0; // 0x40 off ticks then 0xc0 on ticks to dim a bit more.
+
+ // The fan is connected to PWM3, the register PWM3_CWORD_LSB is set to 1,
+ // this is the frequency of the PWM, the other pwm register control
+ // the duty cycle.
+ reg = mmap_addr + 0x9014; // PWM3_CWORD_LSB
+ reg[0] = 1;
+
+ // LEDs are connected to PWM2. Setting CWORD_LSB to 0xf to control
+ // the output freq of the var rate clock.
+ reg = mmap_addr + 0x900c;
+ reg[0] = 0xf;
+
+ // Default the LED brightness to 50.
+ set_led_brightness(50);
}
static void init_platform(struct platform_info* p) {
@@ -827,15 +877,15 @@
static void initialize_gpios(void) {
init_platform(platform);
- set_pinmux(&platform->led_red);
- set_pinmux(&platform->led_blue);
- set_pinmux(&platform->led_activity);
- set_pinmux(&platform->led_standby);
+ set_pinmux(&platform->leds.led_red);
+ set_pinmux(&platform->leds.led_blue);
+ set_pinmux(&platform->leds.led_activity);
+ set_pinmux(&platform->leds.led_standby);
- set_direction(&platform->led_red);
- set_direction(&platform->led_blue);
- set_direction(&platform->led_activity);
- set_direction(&platform->led_standby);
+ set_direction(&platform->leds.led_red);
+ set_direction(&platform->leds.led_blue);
+ set_direction(&platform->leds.led_activity);
+ set_direction(&platform->leds.led_standby);
set_direction(&platform->reset_button);
set_direction(&platform->fan_tick);
}
diff --git a/hnvram/Makefile b/hnvram/Makefile
index c3368cb..2790f4d 100644
--- a/hnvram/Makefile
+++ b/hnvram/Makefile
@@ -7,7 +7,8 @@
AR:=$(CROSS_COMPILE)ar
RANLIB:=$(CROSS_COMPILE)ranlib
STRIP:=$(CROSS_COMPILE)strip
-BINDIR=$(DESTDIR)/bin
+PREFIX=/usr
+BINDIR=$(DESTDIR)$(PREFIX)/bin
HUMAX_UPGRADE_DIR ?= ../../../humax/misc/libupgrade
CFLAGS += -g -Os -I$(HUMAX_UPGRADE_DIR) $(EXTRACFLAGS)
@@ -29,7 +30,7 @@
install:
mkdir -p $(BINDIR)
- cp hnvram $(BINDIR)
+ cp hnvram $(BINDIR)/hnvram_binary
install-libs:
@echo "No libs to install."
diff --git a/hnvram/hnvram_main.c b/hnvram/hnvram_main.c
index 6e86f26..b850048 100644
--- a/hnvram/hnvram_main.c
+++ b/hnvram/hnvram_main.c
@@ -79,7 +79,10 @@
{"PAIRED_DISK", NVRAM_FIELD_PAIRED_DISK, HNVRAM_STRING},
{"PARTITION_VER", NVRAM_FIELD_PARTITION_VER, HNVRAM_STRING},
{"HW_VER", NVRAM_FIELD_HW_VER, HNVRAM_UINT8},
- {"UITYPE", NVRAM_FIELD_UITYPE, HNVRAM_STRING}
+ {"UITYPE", NVRAM_FIELD_UITYPE, HNVRAM_STRING},
+ {"LASER_CHANNEL", NVRAM_FIELD_LASER_CHANNEL, HNVRAM_STRING},
+ {"MAC_ADDR_PON", NVRAM_FIELD_MAC_ADDR_PON, HNVRAM_MAC},
+ {"PRODUCTION_UNIT", NVRAM_FIELD_PRODUCTION_UNIT, HNVRAM_STRING},
};
const hnvram_field_t* get_nvram_field(const char* name) {
diff --git a/libexperiments/experiments.cc b/libexperiments/experiments.cc
index d3dcb2b..89fec64 100644
--- a/libexperiments/experiments.cc
+++ b/libexperiments/experiments.cc
@@ -64,13 +64,17 @@
register_func_ = register_func;
min_time_between_refresh_usec_ = min_time_between_refresh_usec;
- if (!Register_Locked(names_to_register))
- return false;
+ initialized_ = true; // initialization part succeeded at this point
- // initial read of registered experiments states
- Refresh();
+ // register any provided experiments
+ if (!names_to_register.empty()) {
+ if (!Register_Locked(names_to_register))
+ return false;
- initialized_ = true;
+ // initial read of registered experiments states
+ Refresh();
+ }
+
return true;
}
@@ -156,7 +160,7 @@
experiments = new Experiments();
return experiments->Initialize(config_dir, min_time_between_refresh_usec,
- register_func, {""});
+ register_func, {});
}
int experiments_is_initialized() {
@@ -171,6 +175,10 @@
return experiments ? experiments->IsRegistered(name) : false;
}
+int experiments_get_num_of_registered_experiments() {
+ return experiments ? experiments->GetNumOfRegisteredExperiments() : 0;
+}
+
int experiments_is_enabled(const char *name) {
return experiments ? experiments->IsEnabled(name) : false;
}
diff --git a/libexperiments/experiments.h b/libexperiments/experiments.h
index 54c6389..1f086ba 100644
--- a/libexperiments/experiments.h
+++ b/libexperiments/experiments.h
@@ -84,8 +84,10 @@
last_time_refreshed_usec_(0) {}
virtual ~Experiments() {}
- // Initializes the instance:
- // * Sets the provided experiments config directory and register function.
+ // Initializes the instance and registers any provided experiments. In detail:
+ // * Sets the provided experiments config directory and register function and
+ // makes sure they are valid. If successful the instance is marked as
+ // initialized.
// * Calls the register function for the provided experiment names.
// * Scans the config folder to determine initial state of all registered
// experiments.
@@ -113,6 +115,10 @@
return Register(names);
}
+ int GetNumOfRegisteredExperiments() const {
+ return registered_experiments_.size();
+ }
+
// Returns true if the given experiment is registered.
bool IsRegistered(const std::string &name);
@@ -205,6 +211,9 @@
// else 0 (boolean false).
int experiments_is_registered(const char *name);
+// Returns the number of experiments registered.
+int experiments_get_num_of_registered_experiments();
+
// Returns non-zero (boolean true) if the given experiment is active, else 0
// (boolean false). If the minimum time between refreshes has passed, re-scans
// the config folder for updates first.
diff --git a/libexperiments/experiments_test.cc b/libexperiments/experiments_test.cc
index e0370cc..5f0178d 100644
--- a/libexperiments/experiments_test.cc
+++ b/libexperiments/experiments_test.cc
@@ -131,6 +131,7 @@
ASSERT_TRUE(e.Initialize(test_folder_path_, 0, &DummyExperimentsRegisterFunc,
{"exp1"}));
EXPECT_TRUE(e.IsRegistered("exp1"));
+ EXPECT_EQ(1, e.GetNumOfRegisteredExperiments());
// add one more
EXPECT_FALSE(e.IsRegistered("exp2"));
@@ -160,6 +161,7 @@
ASSERT_TRUE(e.Initialize(test_folder_path_, 0, &DummyExperimentsRegisterFunc,
{"exp1"}));
EXPECT_FALSE(e.IsEnabled("exp1"));
+ EXPECT_EQ(1, e.GetNumOfRegisteredExperiments());
EXPECT_TRUE(SetActive(&e, "exp1"));
EXPECT_TRUE(e.IsEnabled("exp1"));
@@ -178,6 +180,7 @@
Experiments e;
ASSERT_TRUE(e.Initialize(test_folder_path_, 0, &DummyExperimentsRegisterFunc,
{"exp1", "exp2", "exp3"}));
+ EXPECT_EQ(3, e.GetNumOfRegisteredExperiments());
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_FALSE(e.IsEnabled("exp2"));
EXPECT_FALSE(e.IsEnabled("exp3"));
@@ -233,6 +236,7 @@
Experiments e;
ASSERT_TRUE(e.Initialize(test_folder_path_, kMinTimeBetweenRefresh,
&DummyExperimentsRegisterFunc, {"exp1"}));
+ EXPECT_EQ(1, e.GetNumOfRegisteredExperiments());
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_TRUE(SetActive(&e, "exp1"));
@@ -263,9 +267,11 @@
// initialize
EXPECT_TRUE(test_experiments_initialize(test_folder_path_));
EXPECT_TRUE(test_experiments_is_initialized());
+ EXPECT_EQ(0, experiments_get_num_of_registered_experiments());
EXPECT_TRUE(test_experiments_register("exp1"));
EXPECT_TRUE(test_experiments_is_registered("exp1"));
+ EXPECT_EQ(1, experiments_get_num_of_registered_experiments());
EXPECT_FALSE(test_experiments_is_enabled("exp1"));
EXPECT_TRUE(SetActive(experiments, "exp1"));
diff --git a/presterastats/Makefile b/presterastats/Makefile
new file mode 100644
index 0000000..af578cd
--- /dev/null
+++ b/presterastats/Makefile
@@ -0,0 +1,31 @@
+default:
+
+PREFIX=/
+BINDIR=$(DESTDIR)$(PREFIX)/bin
+PYTHON?=python
+
+all:
+
+install:
+ mkdir -p $(BINDIR)
+ cp presterastats.py $(BINDIR)/presterastats
+ cp prestera_periodic.py $(BINDIR)/prestera_periodic
+
+install-libs:
+ @echo "No libs to install."
+
+test: lint
+ set -e; \
+ for pytest in $(wildcard *_test.py); do \
+ echo; \
+ echo "Testing $$pytest"; \
+ $(PYTHON) $$pytest; \
+ done
+
+clean:
+ rm -rf *.pyc
+
+lint:
+ for n in $(filter-out options.py, $(wildcard *.py)); do \
+ gpylint $$n || exit 1; \
+ done
diff --git a/presterastats/options.py b/presterastats/options.py
new file mode 100644
index 0000000..7fb5bcf
--- /dev/null
+++ b/presterastats/options.py
@@ -0,0 +1,274 @@
+# Copyright 2010-2012 Avery Pennarun and options.py contributors.
+# All rights reserved.
+#
+# (This license applies to this file but not necessarily the other files in
+# this package.)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AVERY PENNARUN ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+"""Command-line options parser.
+With the help of an options spec string, easily parse command-line options.
+
+An options spec is made up of two parts, separated by a line with two dashes.
+The first part is the synopsis of the command and the second one specifies
+options, one per line.
+
+Each non-empty line in the synopsis gives a set of options that can be used
+together.
+
+Option flags must be at the begining of the line and multiple flags are
+separated by commas. Usually, options have a short, one character flag, and a
+longer one, but the short one can be omitted.
+
+Long option flags are used as the option's key for the OptDict produced when
+parsing options.
+
+When the flag definition is ended with an equal sign, the option takes one
+string as an argument. Otherwise, the option does not take an argument and
+corresponds to a boolean flag that is true when the option is given on the
+command line.
+
+The option's description is found at the right of its flags definition, after
+one or more spaces. The description ends at the end of the line. If the
+description contains text enclosed in square brackets, the enclosed text will
+be used as the option's default value.
+
+Options can be put in different groups. Options in the same group must be on
+consecutive lines. Groups are formed by inserting a line that begins with a
+space. The text on that line will be output after an empty line.
+"""
+import sys, os, textwrap, getopt, re, struct
+
+
+def _invert(v, invert):
+ if invert:
+ return not v
+ return v
+
+
+def _remove_negative_kv(k, v):
+ if k.startswith('no-') or k.startswith('no_'):
+ return k[3:], not v
+ return k,v
+
+
+class OptDict(object):
+ """Dictionary that exposes keys as attributes.
+
+ Keys can be set or accessed with a "no-" or "no_" prefix to negate the
+ value.
+ """
+ def __init__(self, aliases):
+ self._opts = {}
+ self._aliases = aliases
+
+ def _unalias(self, k):
+ k, reinvert = _remove_negative_kv(k, False)
+ k, invert = self._aliases[k]
+ return k, invert ^ reinvert
+
+ def __setitem__(self, k, v):
+ k, invert = self._unalias(k)
+ self._opts[k] = _invert(v, invert)
+
+ def __getitem__(self, k):
+ k, invert = self._unalias(k)
+ return _invert(self._opts[k], invert)
+
+ def __getattr__(self, k):
+ return self[k]
+
+
+def _default_onabort(msg):
+ sys.exit(97)
+
+
+def _intify(v):
+ try:
+ vv = int(v or '')
+ if str(vv) == v:
+ return vv
+ except ValueError:
+ pass
+ return v
+
+
+def _atoi(v):
+ try:
+ return int(v or 0)
+ except ValueError:
+ return 0
+
+
+def _tty_width():
+ s = struct.pack("HHHH", 0, 0, 0, 0)
+ try:
+ import fcntl, termios
+ s = fcntl.ioctl(sys.stderr.fileno(), termios.TIOCGWINSZ, s)
+ except (IOError, ImportError):
+ return _atoi(os.environ.get('WIDTH')) or 70
+ (ysize,xsize,ypix,xpix) = struct.unpack('HHHH', s)
+ return xsize or 70
+
+
+class Options:
+ """Option parser.
+ When constructed, a string called an option spec must be given. It
+ specifies the synopsis and option flags and their description. For more
+ information about option specs, see the docstring at the top of this file.
+
+ Two optional arguments specify an alternative parsing function and an
+ alternative behaviour on abort (after having output the usage string).
+
+ By default, the parser function is getopt.gnu_getopt, and the abort
+ behaviour is to exit the program.
+ """
+ def __init__(self, optspec, optfunc=getopt.gnu_getopt,
+ onabort=_default_onabort):
+ self.optspec = optspec
+ self._onabort = onabort
+ self.optfunc = optfunc
+ self._aliases = {}
+ self._shortopts = 'h?'
+ self._longopts = ['help', 'usage']
+ self._hasparms = {}
+ self._defaults = {}
+ self._usagestr = self._gen_usage() # this also parses the optspec
+
+ def _gen_usage(self):
+ out = []
+ lines = self.optspec.strip().split('\n')
+ lines.reverse()
+ first_syn = True
+ while lines:
+ l = lines.pop()
+ if l == '--': break
+ out.append('%s: %s\n' % (first_syn and 'usage' or ' or', l))
+ first_syn = False
+ out.append('\n')
+ last_was_option = False
+ while lines:
+ l = lines.pop()
+ if l.startswith(' '):
+ out.append('%s%s\n' % (last_was_option and '\n' or '',
+ l.lstrip()))
+ last_was_option = False
+ elif l:
+ (flags,extra) = (l + ' ').split(' ', 1)
+ extra = extra.strip()
+ if flags.endswith('='):
+ flags = flags[:-1]
+ has_parm = 1
+ else:
+ has_parm = 0
+ g = re.search(r'\[([^\]]*)\]$', extra)
+ if g:
+ defval = _intify(g.group(1))
+ else:
+ defval = None
+ flagl = flags.split(',')
+ flagl_nice = []
+ flag_main, invert_main = _remove_negative_kv(flagl[0], False)
+ self._defaults[flag_main] = _invert(defval, invert_main)
+ for _f in flagl:
+ f,invert = _remove_negative_kv(_f, 0)
+ self._aliases[f] = (flag_main, invert_main ^ invert)
+ self._hasparms[f] = has_parm
+ if f == '#':
+ self._shortopts += '0123456789'
+ flagl_nice.append('-#')
+ elif len(f) == 1:
+ self._shortopts += f + (has_parm and ':' or '')
+ flagl_nice.append('-' + f)
+ else:
+ f_nice = re.sub(r'\W', '_', f)
+ self._aliases[f_nice] = (flag_main,
+ invert_main ^ invert)
+ self._longopts.append(f + (has_parm and '=' or ''))
+ self._longopts.append('no-' + f)
+ flagl_nice.append('--' + _f)
+ flags_nice = ', '.join(flagl_nice)
+ if has_parm:
+ flags_nice += ' ...'
+ prefix = ' %-20s ' % flags_nice
+ argtext = '\n'.join(textwrap.wrap(extra, width=_tty_width(),
+ initial_indent=prefix,
+ subsequent_indent=' '*28))
+ out.append(argtext + '\n')
+ last_was_option = True
+ else:
+ out.append('\n')
+ last_was_option = False
+ return ''.join(out).rstrip() + '\n'
+
+ def usage(self, msg=""):
+ """Print usage string to stderr and abort."""
+ sys.stderr.write(self._usagestr)
+ if msg:
+ sys.stderr.write(msg)
+ e = self._onabort and self._onabort(msg) or None
+ if e:
+ raise e
+
+ def fatal(self, msg):
+ """Print an error message to stderr and abort with usage string."""
+ msg = '\nerror: %s\n' % msg
+ return self.usage(msg)
+
+ def parse(self, args):
+ """Parse a list of arguments and return (options, flags, extra).
+
+ In the returned tuple, "options" is an OptDict with known options,
+ "flags" is a list of option flags that were used on the command-line,
+ and "extra" is a list of positional arguments.
+ """
+ try:
+ (flags,extra) = self.optfunc(args, self._shortopts, self._longopts)
+ except getopt.GetoptError, e:
+ self.fatal(e)
+
+ opt = OptDict(aliases=self._aliases)
+
+ for k,v in self._defaults.iteritems():
+ opt[k] = v
+
+ for (k,v) in flags:
+ k = k.lstrip('-')
+ if k in ('h', '?', 'help', 'usage'):
+ self.usage()
+ if (self._aliases.get('#') and
+ k in ('0','1','2','3','4','5','6','7','8','9')):
+ v = int(k) # guaranteed to be exactly one digit
+ k, invert = self._aliases['#']
+ opt['#'] = v
+ else:
+ k, invert = opt._unalias(k)
+ if not self._hasparms[k]:
+ assert(v == '')
+ v = (opt._opts.get(k) or 0) + 1
+ else:
+ v = _intify(v)
+ opt[k] = _invert(v, invert)
+ return (opt,flags,extra)
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/presterastats/presterastats.py b/presterastats/presterastats.py
new file mode 100755
index 0000000..bb703c5
--- /dev/null
+++ b/presterastats/presterastats.py
@@ -0,0 +1,113 @@
+#!/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.
+
+"""Retrieve packet statistics from cpss, emit in JSON format."""
+
+__author__ = 'poist@google.com (Gregory Poist)'
+
+import itertools
+import json
+import os
+import signal
+import subprocess
+import sys
+import textwrap
+import threading
+
+import options
+
+
+optspec = """
+presterastats [options]
+--
+ports= prestera ports to collect [0/0,0/4,0/24,0/25]
+timeout= seconds to wait for cpss response [5]
+"""
+
+
+class PresteraStats(object):
+ """Class wrapping a cpss command to request stats."""
+
+ def __init__(self, ports, timeout):
+ self.ports = ports
+ self.timeout = timeout
+
+ def WriteToStderr(self, msg, is_json=False):
+ """Write a message to stderr."""
+ if is_json:
+ # Make the json easier to parse from the logs.
+ json_data = json.loads(msg)
+ json_str = json.dumps(json_data, sort_keys=True, indent=2,
+ separators=(',', ': '))
+ # Logging pretty-printed json is like logging one huge line. Logos is
+ # configured to limit lines to 768 characters. Split the logged output at
+ # half of that to make sure logos doesn't clip our output.
+ sys.stderr.write('\n'.join(textwrap.wrap(json_str, width=384)))
+ sys.stderr.flush()
+ else:
+ sys.stderr.write(msg)
+ sys.stderr.flush()
+
+ def StartCpssSubprocess(self):
+ """Start execution of the cpss_cmd sub-process."""
+ return subprocess.Popen(['cpss_cmd'],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ preexec_fn=os.setsid)
+
+ def GetMibStats(self):
+ """Extract statistics from cpss_cmd output."""
+ result = None
+ proc = self.StartCpssSubprocess()
+ if not proc:
+ self.WriteToStderr('Failed to start subprocess.')
+ return
+ kill_proc = lambda p: os.killpg(os.getpgid(p.pid), signal.SIGTERM)
+ timer = threading.Timer(self.timeout, kill_proc, [proc])
+ cpss_cmd_prefix = 'show interfaces mac json-counters ethernet '
+ try:
+ timer.start()
+ cpssout, _ = proc.communicate(input=cpss_cmd_prefix + self.ports + '\n')
+
+ # itertools magic to take only the lines between JSONSTART and JSONEND.
+ it = itertools.dropwhile(lambda line: line.strip() != 'JSONSTART',
+ cpssout.splitlines())
+ it = itertools.islice(it, 1, None)
+ it = itertools.takewhile(lambda line: line.strip() != 'JSONEND', it)
+
+ # smack itertools iterable down to a string
+ result = ''.join(it)
+ finally:
+ timer.cancel()
+
+ if result:
+ return json.loads(result)
+
+
+def main():
+ o = options.Options(optspec)
+ (opt, unused_flags, unused_extra) = o.parse(sys.argv[1:])
+ prestera = PresteraStats(opt.ports, opt.timeout)
+ results = prestera.GetMibStats()
+
+ if results:
+ print json.dumps(results, sort_keys=True,
+ indent=2, separators=(',', ': '))
+ sys.exit(0)
+
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/presterastats/presterastats_test.py b/presterastats/presterastats_test.py
new file mode 100644
index 0000000..b96e019
--- /dev/null
+++ b/presterastats/presterastats_test.py
@@ -0,0 +1,113 @@
+#!/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 presterastats."""
+
+__author__ = 'poist@google.com (Gregory Poist)'
+
+import json
+import os
+import subprocess
+import time
+import unittest
+
+import presterastats
+
+VALID_JSON_RESPONSE = """
+garbage here
+JSONSTART
+{ "valid": {
+ "0/0": {
+ "unicast_packets_sent": 19
+ }
+}}
+JSONEND
+garbage there
+"""
+
+VALID_JSON_CONTENT = """
+{ "valid": {
+ "0/0": {
+ "unicast_packets_sent": 19
+ }
+}}
+"""
+
+NO_START_BLOCK_RESPONSE = """
+blah
+blah
+blah
+no json here
+"""
+
+
+class FakePresteraStats(presterastats.PresteraStats):
+ """Mock PresteraStats."""
+
+ def StartCpssSubprocess(self):
+ return subprocess.Popen(self.command.split(),
+ stdin=self.cpss_in, stdout=self.cpss_out,
+ preexec_fn=os.setsid)
+
+
+class PresteraStatsTest(unittest.TestCase):
+
+ def setUp(self):
+ self.poller = FakePresteraStats('0/0,0/4', 1)
+ self.poller.command = ''
+ self.poller.cpss_in = subprocess.PIPE
+ self.poller.cpss_out = subprocess.PIPE
+
+ def testRequestStatsTimeout(self):
+ start_time = time.time()
+ self.poller.command = '/bin/sleep 30'
+ self.poller.subproc_response_fd = subprocess.PIPE
+ result = self.poller.GetMibStats()
+ end_time = time.time()
+
+ self.assertIsNone(result)
+ self.assertTrue(end_time - start_time < 30)
+
+ def testValidJsonBlock(self):
+ self.poller.command = '/bin/cat'
+ self.poller.cpss_in, out_fd = os.pipe()
+ os.write(out_fd, VALID_JSON_RESPONSE)
+ os.close(out_fd)
+ result = self.poller.GetMibStats()
+ os.close(self.poller.cpss_in)
+
+ self.assertEquals(result, json.loads(VALID_JSON_CONTENT))
+
+ def testNoJsonBlock(self):
+ self.poller.command = '/bin/cat'
+ self.poller.cpss_in, out_fd = os.pipe()
+ os.write(out_fd, NO_START_BLOCK_RESPONSE)
+ os.close(out_fd)
+ result = self.poller.GetMibStats()
+ os.close(self.poller.cpss_in)
+
+ self.assertIsNone(result)
+
+ def testBogusCommand(self):
+ self.poller.command = 'aintgottimeforthat'
+ try:
+ _ = self.poller.GetMibStats()
+ self.fail('Should explode')
+ except OSError:
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/signing/repack.py b/signing/repack.py
index 031b20d..54dce21 100755
--- a/signing/repack.py
+++ b/signing/repack.py
@@ -146,7 +146,7 @@
def RealSignBolt(hostdir, fname):
"""Sign the kernel image with the bolt signing tool.
- This uses the broadcom signing tool to sign a kerel for
+ This uses the broadcom signing tool to sign a kernel for
secure boot. The function expects to be called with the
current directory equal to out/build/images.
@@ -159,12 +159,15 @@
"""
tool_path = os.path.join(hostdir, 'usr/bin/boltsigning/gfiber')
- shutil.copy('signing/gfhd254_private.pem', '/dev/shm/gfhd254_private.pem')
+ # Remove any existing file before trying to copy the key over.
+ key_path = '/dev/shm/gfhd254_private.pem'
+ subprocess.call(['shred', '-fuz', key_path])
+ shutil.copy('signing/gfhd254_private.pem', key_path)
shutil.copy(fname, os.path.join(tool_path, 'kernel.img'))
exit_code = subprocess.call(
['wine', '../imagetool.exe', '-L', 'kernel', '-O', 'kernel.cfg',
'-K', 'signing=true'], cwd=tool_path)
- subprocess.call(['shred', '-fuz', '/dev/shm/gfhd254_private.pem'])
+ subprocess.call(['shred', '-fuz', key_path])
if exit_code:
raise Exception('bolt signing tool returned exit code %d' % (exit_code,))
shutil.copy(os.path.join(tool_path, 'kernel.img.signed'), fname)
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..7267856 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,12 @@
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 kOptServerId = 1007;
const int kOptMinTransferTime = 1100;
const int kOptMaxTransferTime = 1101;
@@ -79,19 +87,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 +120,9 @@
{"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},
+ {"serverid", required_argument, nullptr, kOptServerId}, // ignored
{nullptr, 0, nullptr, 0},
};
const int kMaxNumber = 1000;
@@ -124,12 +139,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 +162,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 +171,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 +233,7 @@
std::cerr << "Invalid global host " << optarg << "\n";
return false;
}
- options->global_host = url;
+ options->global_url = url;
break;
}
case 'h':
@@ -228,17 +251,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 +302,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 +330,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 +346,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 +407,7 @@
<< optarg << "'\n";
return false;
}
- options->interval_millis = static_cast<int>(interval_millis);
+ options->interval_millis = interval_millis;
break;
}
case kOptPingRuntime: {
@@ -387,7 +422,7 @@
<< optarg << "'\n";
return false;
}
- options->ping_runtime = static_cast<int>(ping_runtime);
+ options->ping_runtime_millis = ping_runtime;
break;
}
case kOptPingTimeout: {
@@ -402,9 +437,15 @@
<< 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;
+ case kOptServerId:
+ // --serverid is accepted but ignored, for backwards compatibility.
+ break;
default:
return false;
}
@@ -423,10 +464,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 +480,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 580b54b..d3c00d0 100644
--- a/speedtest/utils.cc
+++ b/speedtest/utils.cc
@@ -62,12 +62,24 @@
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;
}
std::istringstream n(str);
- return n >> *result;
+ return !(n >> *result).fail();
}
// Trim from start in place
@@ -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/sysmgr/peripheral/fancontrol.cc b/sysmgr/peripheral/fancontrol.cc
index a19afa9..8e81fe1 100644
--- a/sysmgr/peripheral/fancontrol.cc
+++ b/sysmgr/peripheral/fancontrol.cc
@@ -226,6 +226,16 @@
temp_overheat : 97,
};
+const FanControlParams FanControl::kGFLT300FanCtrlSocDefaults = {
+ temp_setpt : 0, /* No fan */
+ temp_max : 0,
+ temp_step : 0,
+ duty_cycle_min: 0,
+ duty_cycle_max: 0,
+ pwm_step : 0,
+ temp_overheat : 97,
+ };
+
FanControl::~FanControl() {
Terminate();
}
@@ -310,6 +320,9 @@
case BRUNO_GFLT110:
pfan_ctrl_params_[BRUNO_SOC] = kGFLT110FanCtrlSocDefaults;
break;
+ case BRUNO_GFLT300:
+ pfan_ctrl_params_[BRUNO_SOC] = kGFLT300FanCtrlSocDefaults;
+ break;
case BRUNO_UNKNOWN:
LOG(LS_ERROR) << "Invalid platform type, ignore ... " << platform_;
break;
diff --git a/sysmgr/peripheral/fancontrol.h b/sysmgr/peripheral/fancontrol.h
index 2e552e3..06e8499 100644
--- a/sysmgr/peripheral/fancontrol.h
+++ b/sysmgr/peripheral/fancontrol.h
@@ -81,6 +81,7 @@
static const FanControlParams kGFHD254FanCtrlAux1Defaults;
static const FanControlParams kGFLT110FanCtrlSocDefaults;
+ static const FanControlParams kGFLT300FanCtrlSocDefaults;
explicit FanControl(Platform *platform)
: state_(OFF),
diff --git a/sysmgr/peripheral/platform.cc b/sysmgr/peripheral/platform.cc
index 23572a0..ba239fd 100644
--- a/sysmgr/peripheral/platform.cc
+++ b/sysmgr/peripheral/platform.cc
@@ -20,6 +20,7 @@
Platform("GFLT110", BRUNO_GFLT110, false, false, false),
Platform("GFLT120", BRUNO_GFLT110, false, false, false),
Platform("GFHD254", BRUNO_GFHD254, false, true, true),
+ Platform("GFLT300", BRUNO_GFLT300, false, false, false),
Platform("UNKNOWN PLATFORM", BRUNO_UNKNOWN, false, false, false),
};
diff --git a/sysmgr/peripheral/platform.h b/sysmgr/peripheral/platform.h
index 80e0a74..91a5925 100644
--- a/sysmgr/peripheral/platform.h
+++ b/sysmgr/peripheral/platform.h
@@ -26,6 +26,7 @@
BRUNO_GFHD200, /* Camaro */
BRUNO_GFLT110, /* Fiber Jack */
BRUNO_GFHD254, /* Lockdown */
+ BRUNO_GFLT300, /* Go-Long FiberJack */
BRUNO_UNKNOWN
};
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 cab824c..e75a337 100644
--- a/taxonomy/dhcp.py
+++ b/taxonomy/dhcp.py
@@ -36,13 +36,18 @@
'6,3,1,15,66,67,13,44,2,42,12': ['brotherprinter'],
+ '1,3,6,15,44,47': ['canonprinter'],
+
'1,121,33,3,6,12,15,26,28,51,54,58,59,119,252': ['chromeos'],
'1,121,33,3,6,12,15,26,28,51,54,58,59,119': ['chromeos'],
- '1,3,6': ['dashbutton'],
+ '1,3,6': ['dashbutton', 'canonprinter'],
+
+ '1,3,6,28': ['ecobee'],
'1,3,6,12,15,17,28,40,41,42': ['epsonprinter'],
+ '6,3,1,15,66,67,13,44': ['hpprinter'],
'6,3,1,15,66,67,13,44,12': ['hpprinter'],
'6,3,1,15,66,67,13,44,12,81': ['hpprinter'],
'6,3,1,15,66,67,13,44,119,12,81,252': ['hpprinter'],
@@ -52,22 +57,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'],
+ '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 b62f0a2..c3673bb 100644
--- a/taxonomy/ethernet.py
+++ b/taxonomy/ethernet.py
@@ -24,21 +24,30 @@
# Galaxy S4.
database = {
'00:bb:3a': ['amazon'],
+ '0c:47:c9': ['amazon'],
'10:ae:60': ['amazon'],
'28:ef:01': ['amazon'],
+ '44:65:0d': ['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'],
'74:d0:2b': ['asus'],
'ac:22:0b': ['asus'],
'bc:ee:7b': ['asus'],
'd8:50:e6': ['asus'],
+ 'f8:32:e4': ['asus'],
'30:8c:fb': ['dropcam'],
@@ -51,17 +60,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'],
@@ -69,29 +83,49 @@
'd8:b3:77': ['htc'],
'e8:99:c4': ['htc'],
+ '00:34:da': ['lg'],
'0c:48:85': ['lg'],
'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'],
'a0:91:69': ['lg'],
'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:0d:3a': ['microsoft'],
+ '00:12:5a': ['microsoft'],
+ '00:17:fa': ['microsoft'],
+ '00:1d:d8': ['microsoft'],
+ '00:22:48': ['microsoft'],
+ '00:25:ae': ['microsoft'],
+ '00:50:f2': ['microsoft'],
'28:18:78': ['microsoft'],
+ '30:59:b7': ['microsoft'],
+ '4c:0b:be': ['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'],
+ 'c0:33:5e': ['microsoft'],
+ '14:1a:a3': ['motorola'],
'14:30:c6': ['motorola'],
'1c:56:fe': ['motorola'],
'24:da:9b': ['motorola'],
@@ -133,62 +167,90 @@
'18:b4:30': ['nest'],
- '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'],
+ '18:22:7e': ['samsung'],
+ '20:6e:9c': ['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'],
+ '3c:a1:0d': ['samsung'],
'40:0e:85': ['samsung'],
'48:5a:3f': ['samsung', 'wisol'],
+ '4c:bc:a5': ['samsung'],
+ '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'],
'ec:9b:f3': ['samsung'],
'f0:25:b7': ['samsung'],
'f4:09:d8': ['samsung'],
+ 'fc:f1:36': ['samsung'],
'00:d9:d1': ['sony'],
'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/tests/wifi_test.py b/taxonomy/tests/wifi_test.py
index 5ccb745..13eb234 100755
--- a/taxonomy/tests/wifi_test.py
+++ b/taxonomy/tests/wifi_test.py
@@ -30,7 +30,7 @@
dhcp.DHCP_SIGNATURE_FILE = 'testdata/dhcp.signatures'
def testLookup(self):
- signature = ('wifi|probe:0,1,50,45,htcap:186e|assoc:0,1,50,48,'
+ signature = ('wifi4|probe:0,1,50,45,htcap:186e|assoc:0,1,50,48,'
'221(0050f2,2),45,127,htcap:086c,htmcs:000000ff')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual(3, len(taxonomy))
@@ -114,76 +114,76 @@
self.assertIn('Unknown', taxonomy[1])
def testUnknown(self):
- signature = 'wifi|probe:0,1,2,vhtcap:0033|assoc:3,4,vhtcap:0033'
+ signature = 'wifi4|probe:0,1,2,vhtcap:0033|assoc:3,4,vhtcap:0033'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertIn('802.11ac', taxonomy[2])
self.assertNotIn('802.11n', taxonomy[2])
self.assertNotIn('802.11a/b/g', taxonomy[2])
- signature = 'wifi|probe:0,1,2,htcap:0033|assoc:3,4,htcap:0033'
+ signature = 'wifi4|probe:0,1,2,htcap:0033|assoc:3,4,htcap:0033'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertNotIn('802.11ac', taxonomy[2])
self.assertIn('802.11n', taxonomy[2])
self.assertNotIn('802.11a/b/g', taxonomy[2])
- signature = 'wifi|probe:0,1,2|assoc:3,4'
+ signature = 'wifi4|probe:0,1,2|assoc:3,4'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertNotIn('802.11ac', taxonomy[2])
self.assertNotIn('802.11n', taxonomy[2])
self.assertIn('802.11a/b/g', taxonomy[2])
def test802_11n_NssWidth(self):
- signature = 'wifi|probe:0|assoc:1,htcap:012c,htagg:03,htmcs:000000ff'
+ signature = 'wifi4|probe:0|assoc:1,htcap:012c,htagg:03,htmcs:000000ff'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:1,w:20', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1,htcap:0102,htagg:03,htmcs:0000ffff'
+ signature = 'wifi4|probe:0|assoc:1,htcap:0102,htagg:03,htmcs:0000ffff'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:2,w:40', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1,htcap:0200,htagg:03,htmcs:00ffffff'
+ signature = 'wifi4|probe:0|assoc:1,htcap:0200,htagg:03,htmcs:00ffffff'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:3,w:20', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1,htcap:0302,htagg:03,htmcs:ffffffff'
+ signature = 'wifi4|probe:0|assoc:1,htcap:0302,htagg:03,htmcs:ffffffff'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:4,w:40', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1'
+ signature = 'wifi4|probe:0|assoc:1'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11a/b/g n:1,w:20', taxonomy[2])
def test802_11ac_Width(self):
- signature = ('wifi|probe:0|assoc:1,htcap:0302,htmcs:000000ff,'
+ signature = ('wifi4|probe:0|assoc:1,htcap:0302,htmcs:000000ff,'
'vhtcap:00000000,vhtrxmcs:0000ffaa,vhttxmcs:0000ffaa')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11ac n:4,w:80', taxonomy[2])
- signature = ('wifi|probe:0|assoc:1,htcap:0200,htmcs:000000ff,'
+ signature = ('wifi4|probe:0|assoc:1,htcap:0200,htmcs:000000ff,'
'vhtcap:00000004,vhtrxmcs:0000ffea,vhttxmcs:0000ffea')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11ac n:3,w:160', taxonomy[2])
- signature = ('wifi|probe:0|assoc:1,htcap:0200,htmcs:000000ff,'
+ signature = ('wifi4|probe:0|assoc:1,htcap:0200,htmcs:000000ff,'
'vhtcap:00000004,vhtrxmcs:0000fffa,vhttxmcs:0000fffa')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11ac n:2,w:160', taxonomy[2])
- signature = ('wifi|probe:0|assoc:1,htcap:0200,htmcs:000000ff,'
+ signature = ('wifi4|probe:0|assoc:1,htcap:0200,htmcs:000000ff,'
'vhtcap:00000004,vhtrxmcs:0000fffe,vhttxmcs:0000fffe')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11ac n:1,w:160', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1,vhtcap:00000008'
+ signature = 'wifi4|probe:0|assoc:1,vhtcap:00000008'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11ac n:?,w:80+80', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1,vhtcap:0000000c'
+ signature = 'wifi4|probe:0|assoc:1,vhtcap:0000000c'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11ac n:?,w:??', taxonomy[2])
def testPerformanceInfoBroken(self):
- signature = ('wifi|probe:0,htmcs:000000ff|assoc:0,htmcs:000000ff')
+ signature = ('wifi4|probe:0,htmcs:000000ff|assoc:0,htmcs:000000ff')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11a/b/g n:1,w:20', taxonomy[2])
- signature = ('wifi|probe:0,htcap:wrong,htmcs:ffffffff|'
+ signature = ('wifi4|probe:0,htcap:wrong,htmcs:ffffffff|'
'assoc:0,htcap:wrong,htmcs:ffffffff')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:4,w:??', taxonomy[2])
- signature = ('wifi|probe:0,htcap:012c,htmcs:wrong|'
+ signature = ('wifi4|probe:0,htcap:012c,htmcs:wrong|'
'assoc:0,htcap:012c,htmcs:wrong')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:?,w:20', taxonomy[2])
- signature = ('wifi|probe:0,htcap:wrong,htmcs:wrong|'
+ signature = ('wifi4|probe:0,htcap:wrong,htmcs:wrong|'
'assoc:0,htcap:wrong,htmcs:wrong')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual('802.11n n:?,w:??', taxonomy[2])
@@ -191,13 +191,13 @@
def testRealClientsPerformance(self):
"""Test the performance information for a few real clients."""
# Nest Thermostat
- sig = ('wifi|probe:0,1,50,45,htcap:0130,htagg:18,htmcs:000000ff|assoc:'
+ sig = ('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')
taxonomy = wifi.identify_wifi_device(sig, '18:b4:30:00:00:01')
self.assertEqual('802.11n n:1,w:20', taxonomy[2])
# Samsung Galaxy S4
sig = (
- 'wifi|probe:0,1,45,127,191,221(001018,2),221(00904c,51),221(00904c,'
+ 'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),221(00904c,'
'4),221(0050f2,8),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,'
'vhtrxmcs:0000fffe,vhttxmcs:0000fffe|assoc:0,1,33,36,48,45,127,191,'
'221(001018,2),221(00904c,4),221(0050f2,2),htcap:006f,htagg:17,htmcs:'
@@ -206,7 +206,7 @@
self.assertEqual('802.11ac n:1,w:80', taxonomy[2])
# MacBook Pro 802.11ac
sig = (
- 'wifi|probe:0,1,45,127,191,221(00904c,51),htcap:09ef,htagg:17,'
+ 'wifi4|probe:0,1,45,127,191,221(00904c,51),htcap:09ef,htagg:17,'
'htmcs:0000ffff,vhtcap:0f8259b2,vhtrxmcs:0000ffea,vhttxmcs:0000ffea|'
'assoc:0,1,33,36,48,45,127,191,221(00904c,51),221(0050f2,2),htcap:09ef,'
'htagg:17,htmcs:0000ffff,vhtcap:0f8259b2,vhtrxmcs:0000ffea,'
@@ -225,7 +225,7 @@
look at the Association for determining client
performance characteristics.
"""
- signature = ('wifi|probe:0,1,50,45,221(0050f2,8),191,221(0050f2,4),'
+ signature = ('wifi4|probe:0,1,50,45,221(0050f2,8),191,221(0050f2,4),'
'221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,'
'vhtcap:31811120,vhtrxmcs:01b2fffc,vhttxmcs:01b2fffc,'
'wps:Nexus_4|assoc:0,1,50,48,45,221(0050f2,2),'
@@ -234,76 +234,13 @@
self.assertEqual('802.11n n:1,w:20', taxonomy[2])
def testCorruptFiles(self):
- signature = 'wifi|probe:0|assoc:1,htcap:this_is_not_a_number'
+ signature = 'wifi4|probe:0|assoc:1,htcap:this_is_not_a_number'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertIn('802.11n', taxonomy[2])
- signature = 'wifi|probe:0|assoc:1,vhtcap:this_is_not_a_number'
+ signature = 'wifi4|probe:0|assoc:1,vhtcap:this_is_not_a_number'
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertIn('802.11ac', taxonomy[2])
- def testV1Signature(self):
- sig = ('wifi|probe:0,1,50,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),'
- 'htcap:012c,htagg:03,htmcs:000000ff,wps:Nexus_4|assoc:0,1,50,48,45,'
- '221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff')
- expected = (
- 'wifi|probe:0,1,50,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),'
- 'htcap:012c,wps:Nexus_4|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c')
- v1 = wifi.make_v1_signature(sig)
- self.assertEqual(v1, expected)
- sig = ('wifi|probe:0,1,45,127,191,221(001018,2),221(00904c,51),'
- '221(00904c,4),221(0050f2,8),htcap:006f,htagg:17,htmcs:000000ff,'
- 'vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe|assoc:0,1,33,'
- '36,48,45,127,191,221(001018,2),221(00904c,4),221(0050f2,2),'
- 'htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,'
- 'vhtrxmcs:0000fffe,vhttxmcs:0000fffe')
- expected = ('wifi|probe:0,1,45,127,191,221(001018,2),221(00904c,51),'
- '221(00904c,4),221(0050f2,8),htcap:006f,vhtcap:0f805832|assoc:'
- '0,1,33,36,48,45,127,191,221(001018,2),221(00904c,4),'
- '221(0050f2,2),htcap:006f,vhtcap:0f805832')
- v1 = wifi.make_v1_signature(sig)
- self.assertEqual(v1, expected)
-
- def testV2Signature(self):
- sig = 'wifi4|probe:0,1,50|assoc:0,1,50,htcap:012c,txpow:3210'
- exp = 'wifi|probe:0,1,50|assoc:0,1,50,htcap:012c'
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
- sig = 'wifi4|probe:0,1,50|assoc:0,1,50,extcap:0123456789abcdef'
- exp = 'wifi|probe:0,1,50|assoc:0,1,50,extcap:67452301'
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
- # iPhone 6s signature
- sig = ('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,45,127,221(001018,2),'
- '221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,'
- 'txpow:1202,extcap:0000000000000040|os:ios')
- exp = ('wifi|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),'
- 'htcap:002d,htagg:17,htmcs:0000ffff,'
- 'extcap:84080004|assoc:0,1,50,33,36,48,45,127,221(001018,2),'
- '221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,'
- 'extcap:00000000|os:ios')
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
-
- def testV2SignatureSmallExtcap(self):
- sig = 'wifi4|probe:0,1,50|assoc:0,1,50,extcap:01234567'
- exp = 'wifi|probe:0,1,50|assoc:0,1,50,extcap:67452301'
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
- sig = 'wifi4|probe:0,1,50|assoc:0,1,50,extcap:012345'
- exp = 'wifi|probe:0,1,50|assoc:0,1,50,extcap:452301'
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
- sig = 'wifi4|probe:0,1,50|assoc:0,1,50,extcap:0123'
- exp = 'wifi|probe:0,1,50|assoc:0,1,50,extcap:2301'
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
- sig = 'wifi4|probe:0,1,50|assoc:0,1,50,extcap:01'
- exp = 'wifi|probe:0,1,50|assoc:0,1,50,extcap:01'
- v2 = wifi.make_v2_signature(sig)
- self.assertEqual(v2, exp)
-
if __name__ == '__main__':
unittest.main()
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index afbd709..ee4aee2 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -26,18 +26,28 @@
'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,50,45,127,221(0050f2,4),221(506f9a,9),htcap:01ed,htagg:1f,htmcs:0000ffff,extcap:00,wps:AFTS|assoc:0,1,50,45,127,221(000c43,6),221(0050f2,2),48,127,htcap:008c,htagg:1f,htmcs:0000ffff,extcap:00000a02':
+ ('', 'Amazon Fire TV', '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':
('', 'Amazon Fire TV Stick', '5GHz'),
'wifi4|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:003c,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:003c,htagg:1b,htmcs:0000ffff,txpow:170c|oui:amazon':
@@ -78,9 +88,18 @@
'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'),
+ 'wifi4|probe:0,1,3,45,50,htcap:007e,htagg:00,htmcs:000000ff|assoc:0,1,45,48,50,221(0050f2,2),htcap:000c,htagg:1b,htmcs:000000ff|os:canonprinter':
+ ('', 'Canon Printer', '2.4GHz'),
+
'wifi4|probe:0,1,45,191,htcap:11e2,htagg:17,htmcs:0000ffff,vhtcap:038071a0,vhtrxmcs:0000fffa,vhttxmcs:0000fffa|assoc:0,1,48,45,127,191,221(0050f2,2),htcap:11e6,htagg:17,htmcs:0000ffff,vhtcap:038001a0,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|os:chromeos':
('Intel_7260', 'Chromebook Pixel 2', '5GHz'),
'wifi4|probe:0,1,45,191,htcap:11e2,htagg:17,htmcs:0000ffff,vhtcap:038071a0,vhtrxmcs:0000fffa,vhttxmcs:0000fffa|assoc:0,1,48,45,127,191,221(0050f2,2),htcap:11ee,htagg:17,htmcs:0000ffff,vhtcap:038001a0,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|os:chromeos':
@@ -97,9 +116,6 @@
'wifi4|probe:0,1,50,3,45,htcap:11ef,htagg:1b,htmcs:0000ffff|assoc:0,1,50,48,45,221(0050f2,2),htcap:11ef,htagg:1b,htmcs:0000ffff|os:chromeos':
('AR5822', 'Chromebook 14" HP', '2.4GHz'),
- 'wifi|probe:0,1,3,45,50,htcap:01ff|assoc:0,1,50,127,221(0050f2,1),221(0050f2,2),45,htcap:01ff|os:chromeos':
- ('Marvell_88W8897', 'Chromebook 14" HP (Tegra)', '2.4GHz'),
-
'wifi4|probe:0,1,45,50,htcap:016e,htagg:03,htmcs:0000ffff|assoc:0,1,48,127,221(0050f2,2),45,htcap:016e,htagg:03,htmcs:0000ffff,extcap:00':
('AR9382', 'Chromebook 11" Samsung', '5GHz'),
'wifi4|probe:0,1,3,45,50,htcap:016e,htagg:03,htmcs:0000ffff|assoc:0,1,48,50,127,221(0050f2,2),45,htcap:016e,htagg:03,htmcs:0000ffff,extcap:00':
@@ -116,22 +132,37 @@
'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'),
- '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':
+ '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'),
+
+ 'wifi4|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,extcap:00|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':
+ '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: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'),
- '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':
+ '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'),
+ 'wifi4|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,extcap:00|os:hpprinter':
('', 'HP Printer', '2.4GHz'),
'wifi4|probe:0,1,3,45,50,127,htcap:010c,htagg:1b,htmcs:0000ffff,extcap:00|assoc:0,1,45,48,127,50,221(0050f2,2),htcap:016c,htagg:1b,htmcs:000000ff,extcap:00|os:hpprinter':
('', 'HP Printer', '2.4GHz'),
+ 'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2)|os:hpprinter':
+ ('', 'HP Printer', '2.4GHz'),
'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:03800032,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:03800032,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e008,extcap:0000000000000040|oui:htc':
('BCM4335', 'HTC One', '5GHz'),
@@ -151,6 +182,8 @@
('WCN3680', 'HTC One M8', '5GHz'),
'wifi4|probe:0,1,3,45,221(0050f2,8),191,127,107,221(0050f2,4),221(506f9a,9),221(506f9a,16),htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:00000a820040,wps:HTC_One_M8|assoc:0,1,48,45,221(0050f2,2),191,127,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:00000a8201400040':
('WCN3680', 'HTC One M8', '5GHz'),
+ 'wifi4|probe:0,1,3,45,221(0050f2,8),191,127,107,221(0050f2,4),221(506f9a,10),221(506f9a,9),221(506f9a,16),htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:000000800040,wps:HTC_One_M8|assoc:0,1,33,36,48,70,45,221(0050f2,2),191,127,htcap:016e,htagg:03,htmcs:000000ff,vhtcap:31800120,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:1e0d,extcap:0000008001400040':
+ ('WCN3680', 'HTC One M8', '5GHz'),
'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,107,221(0050f2,4),221(506f9a,9),221(506f9a,16),htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a820040,wps:HTC_One_M8|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a8201400000':
('WCN3680', 'HTC One M8', '2.4GHz'),
'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:00000a8201400000|oui:htc':
@@ -163,6 +196,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,13 +221,15 @@
('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':
('BCM4334', 'iPad (4th gen or Air)', '5GHz'),
'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,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01fe,htagg:1b,htmcs:0000ffff,txpow:e708|os:ios':
('BCM4334', 'iPad (4th gen or Air)', '5GHz'),
+ 'wifi4|probe:0,1,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:01fe,htagg:1b,htmcs:0000ffff,extcap:00000004|assoc:0,1,33,36,48,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01fe,htagg:1b,htmcs:0000ffff,txpow:e708|os:ios':
+ ('BCM4334', 'iPad (4th gen or Air)', '5GHz'),
'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,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01bc,htagg:1b,htmcs:0000ffff,txpow:1805|os:ios':
('BCM4334', 'iPad (4th gen or Air)', '2.4GHz'),
'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:1805|os:ios':
@@ -229,6 +272,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 +300,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 +341,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 +357,10 @@
('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,50,3,45,127,107,221(00904c,51),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: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 +373,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 +391,29 @@
('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,3,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,wps:LG_V400|assoc:0,1,33,36,48,70,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:170d,extcap:00000a0200000000':
+ ('', 'LG Pad v400', '5GHz'),
+
+ '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 +516,10 @@
('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':
('BCM4339', 'Nexus 5', '2.4GHz'),
'wifi4|probe:0,1,50,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':
@@ -466,8 +547,6 @@
('QCA6174', 'Nexus 5X', '2.4GHz'),
'wifi4|probe:0,1,50,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,extcap:000000000000000080|oui:lg':
('QCA6174', 'Nexus 5X', '2.4GHz'),
- 'wifi4|probe:0,1,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,33,36,48,70,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,txpow:1e08,extcap:000000000000004080|oui:lg':
- ('QCA6174', 'Nexus 5X', '2.4GHz'),
'wifi4|probe:0,1,50,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,txpow:1e08,extcap:000000000000000080|oui:lg':
('QCA6174', 'Nexus 5X', '2.4GHz'),
'wifi4|probe:0,1,50,127,extcap:00000a020100004080|assoc:0,1,50,33,48,70,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,txpow:1e08,extcap:000000000000000080|oui:lg':
@@ -481,6 +560,10 @@
('BCM4356', 'Nexus 6', '5GHz'),
'wifi4|probe:0,1,45,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,wps:Nexus_6|assoc:0,1,33,36,48,45,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e009':
('BCM4356', 'Nexus 6', '5GHz'),
+ '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_6|assoc:0,1,33,36,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 6', '5GHz'),
+ '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:000008800140,wps:Nexus_6|assoc:0,1,33,36,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e009,extcap:000008800140':
+ ('BCM4356', 'Nexus 6', '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:0000088001400040,wps:Nexus_6|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 6', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:000008800140,wps:Nexus_6|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':
@@ -524,6 +607,8 @@
('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
'wifi4|probe:0,1,45,221(0050f2,8),htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,33,36,48,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:1e0d,extcap:00000a02|oui:asus':
('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
+ 'wifi4|probe:0,1,45,htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,33,36,48,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:1e0d,extcap:00000a02|oui:asus':
+ ('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
'wifi4|probe:0,1,50,45,221(0050f2,8),127,221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a02,wps:Nexus_7|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a02':
('QCA_WCN3660', 'Nexus 7 (2013)', '2.4GHz'),
'wifi4|probe:0,1,50,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:Nexus_7|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a02':
@@ -532,6 +617,10 @@
('QCA_WCN3660', 'Nexus 7 (2013)', '2.4GHz'),
'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:00000a02|oui:asus':
('QCA_WCN3660', 'Nexus 7 (2013)', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,htcap:016e,htagg:03,htmcs:000000ff|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a02|oui:asus':
+ ('QCA_WCN3660', 'Nexus 7 (2013)', '2.4GHz'),
+ 'wifi4|probe:0,1,50,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:012c,htagg:03,htmcs:000000ff,wps:Nexus_7|assoc:0,1,50,48,45,221(0050f2,2),htcap:012c,htagg:03,htmcs:000000ff':
+ ('QCA_WCN3660', 'Nexus 7 (2013)', '2.4GHz'),
'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:000008800140,wps:Nexus_9|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:000008800140':
('BCM4354', 'Nexus 9', '5GHz'),
@@ -539,6 +628,10 @@
('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(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:1309,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'),
@@ -547,6 +640,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'),
@@ -555,29 +650,77 @@
'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,45,221(0050f2,4),htcap:01ad,htagg:02,htmcs:0000ffff,wps:WPS_SUPPLICANT_STATION|assoc:0,1,50,45,48,221(0050f2,2),htcap:01ad,htagg:02,htmcs:0000ffff|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,45,127,221(001018,2),221(00904c,51),htcap:09bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:09bc,htagg:16,htmcs:0000ffff,txpow:100a,extcap:0000000000000040|os:roku':
+ ('BCM43236', 'Roku 3/SS', '5GHz'),
'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'),
+ ('BCM43236', 'Roku 3/SS', '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'),
+ ('BCM43236', 'Roku 3/SS', '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'),
@@ -634,6 +777,10 @@
'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,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: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:1202|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'),
@@ -675,20 +822,44 @@
'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':
('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:000008800140|assoc:0,1,50,33,36,48,70,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,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:0000088001400040|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:e002,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy S6', '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:0000088001400040|assoc:0,1,33,36,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:e002,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy S6', '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:e002|oui:samsung':
+ ('BCM4358', 'Samsung Galaxy S6', '5GHz'),
'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,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,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':
@@ -701,9 +872,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':
@@ -717,6 +888,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'),
@@ -727,115 +907,69 @@
'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'),
- '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':
+ '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'),
+ 'wifi4|probe:0,1,50,48|assoc:0,1,50,221(0050f2,2),45,51,127,48,htcap:012c,htagg:1b,htmcs:000000ff,extcap:01|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'),
- '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':
+ 'wifi4|probe:0,1,3|assoc:0,1,48,33,36,221(0050f2,2),txpow:1405|oui:microsoft':
('', 'Xbox', '5GHz'),
+ 'wifi4|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,txpow:1208,extcap:0000000000000140|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,50|assoc:0,1,33,48,50,127,127,221(0050f2,2),45,htcap:012c,htagg:03,htmcs:0000ffff,txpow:1208,extcap:0000000000000140|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'),
}
-def make_v2_signature(sig):
- """Degrade a v3 / v4 signature to match the equivalent v2 signature.
-
- v3/v4 signatures include additional information from the Wifi MLME
- frames, based on experience of working with v1 and v2. Return a string
- which matches what the v2 signature would have been, by removing
- the additional information.
-
- This allows us to retain the base of v2 signatures.
-
- Args:
- sig: the text signature.
-
- Returns:
- a v2 signature.
- """
- new_sig = []
- for s in sig.split('|'):
- fields = s.split(',')
- new_fields = []
- for x in fields:
- if x.startswith('extcap:'):
- val = x[7:]
- # v2 sig includes only the first 4 bytes, swapped.
- swapped = ''
- if len(val) >= 8:
- swapped = swapped + val[6:8]
- if len(val) >= 6:
- swapped = swapped + val[4:6]
- if len(val) >= 4:
- swapped = swapped + val[2:4]
- if len(val) >= 2:
- swapped = swapped + val[0:2]
- x = 'extcap:' + swapped
- if x.startswith('cap') or x.startswith('txpow'):
- # new fields in v3, omit from v2 sig
- continue
- new_fields.append(x)
- new_sig.append(','.join(new_fields))
- new_sig[0] = 'wifi'
- return '|'.join(new_sig)
-
-
-def v2only(field):
- """Return true if field only occurs in a v2 signature."""
- labels = set(['htagg', 'htmcs', 'vhtrxmcs', 'vhttxmcs', 'intwrk', 'extcap'])
- for l in labels:
- if l in field:
- return True
- return False
-
-
-def make_v1_signature(sig):
- """Degrade a v2 signature to match the equivalent v1 signature.
-
- v2 signatures include additional information from the Wifi MLME
- frames, based on experience of working with v1. Return a string
- which matches what the v1 signature would have been, by removing
- the additional information.
-
- This allows us to retain the base of v1 signatures.
-
- Args:
- sig: the text signature.
-
- Returns:
- a v1 signature.
- """
- new_sig = []
- for s in sig.split('|'):
- fields = s.split(',')
- new_fields = [x for x in fields if not v2only(x)]
- new_sig.append(','.join(new_fields))
- return '|'.join(new_sig)
-
-
def performance_characteristics(signature):
"""Parse 802.11n/ac capabilities bitmasks from sig.
@@ -951,10 +1085,8 @@
return (SHA256, 'Unknown', PerformanceInfo)
"""
- v4_sig = signature.strip()
- v2_sig = make_v2_signature(v4_sig)
- v1_sig = make_v1_signature(v2_sig)
- perf = performance_info(*performance_characteristics(v4_sig))
+ sig = signature.strip()
+ perf = performance_info(*performance_characteristics(sig))
name = dhcp.LookupHostname(mac)
opersys = dhcp.LookupOperatingSystem(mac)
oui = ethernet.LookupOUI(mac)
@@ -966,11 +1098,10 @@
for o in oui:
suffixes.append('|oui:' + o)
suffixes.append('')
- for sig in [v4_sig, v2_sig, v1_sig]:
- for suffix in suffixes:
- result = database.get(sig + suffix, None)
- if result is not None:
- return (result[0], result[1], perf)
+ for suffix in suffixes:
+ result = database.get(sig + suffix, None)
+ if result is not None:
+ return (result[0], result[1], perf)
# We have no idea what the client is.
slug = 'SHA:' + hashlib.sha256(signature).hexdigest()
@@ -983,13 +1114,5 @@
# Remove os, oui, etc qualifiers if present.
a = k.split('|')
if len(a) > 3:
- v4_sig = '|'.join(a[0:3])
- print 'SHA:' + hashlib.sha256(v4_sig).hexdigest() + ' ' + v[1] + ' (unqualified)'
-
- v2_sig = make_v2_signature(v4_sig)
- if v2_sig != v4_sig:
- print 'SHA:' + hashlib.sha256(v2_sig).hexdigest() + ' ' + v[1] + ' (unqualified, v2)'
-
- v1_sig = make_v1_signature(v2_sig)
- if v1_sig != v2_sig:
- print 'SHA:' + hashlib.sha256(v1_sig).hexdigest() + ' ' + v[1] + ' (unqualified, v1)'
+ sig = '|'.join(a[0:3])
+ print 'SHA:' + hashlib.sha256(sig).hexdigest() + ' ' + v[1] + ' (unqualified)'
diff --git a/waveguide/clientinfo_test.py b/waveguide/clientinfo_test.py
index 282ec62..e7471f3 100755
--- a/waveguide/clientinfo_test.py
+++ b/waveguide/clientinfo_test.py
@@ -13,6 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# One version of gpylint wants to see clientinfo first and taxonomy last.
+# Another wants to see the reverse. Cannot satisfy both, so tell both of them
+# to shove the error so far up their stdin that it should never trouble us
+# again.
+# pylint:disable=g-bad-import-order
import taxonomy
import clientinfo
from wvtest import wvtest
diff --git a/waveguide/waveguide.py b/waveguide/waveguide.py
index db2f9ed..cbc9fe8 100755
--- a/waveguide/waveguide.py
+++ b/waveguide/waveguide.py
@@ -783,7 +783,7 @@
a = wgdata.Assoc(mac=mac, rssi=rssi, last_seen=last_seen, can5G=can5G)
if mac not in self.assoc_list:
self.Debug('Added: %r', a)
- self.wifiblaster_controller.Measure(self.vdevname, mac)
+ self.wifiblaster_controller.MeasureOnAssociation(self.vdevname, mac)
assoc_list[mac] = a
for line in stdout.split('\n'):
@@ -904,16 +904,18 @@
- Scheduling parameters
- wifiblaster.enable Enable WiFi performance measurement.
- wifiblaster.interval Average time between automated measurements in
- seconds, or 0 to disable automated measurements.
- wifiblaster.measureall Unix time at which to measure all clients.
+ wifiblaster.enable Enable WiFi performance measurement.
+ wifiblaster.interval Average time between automated measurements in
+ seconds, or 0 to disable automated measurements.
+ wifiblaster.measureall Unix time at which to measure all clients.
+ wifiblaster.onassociation Enable WiFi performance measurement after clients
+ associate.
- Measurement parameters
- wifiblaster.duration Measurement duration in seconds.
- wifiblaster.fraction Number of samples per measurement.
- wifiblaster.size Packet size in bytes.
+ wifiblaster.duration Measurement duration in seconds.
+ wifiblaster.fraction Number of samples per measurement.
+ wifiblaster.size Packet size in bytes.
"""
def __init__(self, managers, basedir):
@@ -985,6 +987,12 @@
'-f', str(fraction), '-s', str(size),
helpers.DecodeMAC(client)])
+ def MeasureOnAssociation(self, interface, client):
+ """Measures the performance of a client after association."""
+ onassociation = self._ReadParameter('onassociation', self._StrToBool)
+ if onassociation:
+ self.Measure(interface, client)
+
def Poll(self, now):
"""Polls the state machine."""
diff --git a/waveguide/wifiblaster_controller_test.py b/waveguide/wifiblaster_controller_test.py
index 04e71f8..6b562b1 100755
--- a/waveguide/wifiblaster_controller_test.py
+++ b/waveguide/wifiblaster_controller_test.py
@@ -208,6 +208,15 @@
wvtest.WVPASSEQ(CountRuns(), 1)
wc.Poll(501)
wvtest.WVPASSEQ(CountRuns(), 0)
+
+ # Measure on association only if enabled.
+ wc.MeasureOnAssociation(manager.vdevname,
+ manager.GetState().assoc[0].mac)
+ wvtest.WVPASSEQ(CountRuns(), 0)
+ WriteConfig('onassociation', 'True')
+ wc.MeasureOnAssociation(manager.vdevname,
+ manager.GetState().assoc[0].mac)
+ wvtest.WVPASSEQ(CountRuns(), 1)
finally:
time.time = oldtime
shutil.rmtree(d)