Merge "gfch100: add craft ui"
diff --git a/cmds/logos.c b/cmds/logos.c
index 2f453d0..6c186a5 100644
--- a/cmds/logos.c
+++ b/cmds/logos.c
@@ -25,7 +25,6 @@
* - cleans up control characters (ie. chars < 32).
* - makes sure output lines are in "facility: message" format.
* - doesn't rely on syslogd.
- * - suppresses logging of MAC addresses.
* - suppresses logging of filenames of personal media.
*/
#include <assert.h>
@@ -461,52 +460,6 @@
}
-static int is_mac_address(const uint8_t *s, char sep) {
- if ((s[2] == sep) && (s[5] == sep) && (s[8] == sep) &&
- (s[11] == sep) && (s[14] == sep) &&
- isxdigit(s[0]) && isxdigit(s[1]) &&
- isxdigit(s[3]) && isxdigit(s[4]) &&
- isxdigit(s[6]) && isxdigit(s[7]) &&
- isxdigit(s[9]) && isxdigit(s[10]) &&
- isxdigit(s[12]) && isxdigit(s[13]) &&
- isxdigit(s[15]) && isxdigit(s[16])) {
- return 1;
- }
-
- return 0;
-}
-
-
-static void blot_out_mac_address(uint8_t *s) {
- s[12] = 'X';
- s[13] = 'X';
- s[15] = 'X';
- s[16] = 'X';
-}
-
-
-/*
- * search for text patterns which look like MAC addresses,
- * and cross out the last two bytes with 'X' characters.
- * Ex: f8:8f:ca:00:00:01 and f8-8f-ca-00-00-01
- */
-#define MAC_ADDR_LEN 17
-static void suppress_mac_addresses(uint8_t *line, ssize_t len, char sep) {
- uint8_t *s = line;
-
- while (len >= MAC_ADDR_LEN) {
- if (is_mac_address(s, sep)) {
- blot_out_mac_address(s);
- s += MAC_ADDR_LEN;
- len -= MAC_ADDR_LEN;
- } else {
- s += 1;
- len -= 1;
- }
- }
-}
-
-
/*
* Return true for a character which we expect to terminate a
* media filename.
@@ -700,9 +653,6 @@
uint8_t *start = buf, *next = buf + used, *end = buf + used + got, *p;
while ((p = memchr(next, '\n', end - next)) != NULL) {
ssize_t linelen = p - start;
- suppress_mac_addresses(start, linelen, ':');
- suppress_mac_addresses(start, linelen, '-');
- suppress_mac_addresses(start, linelen, '_');
suppress_media_filenames(start, linelen, "/var/media/pictures/");
suppress_media_filenames(start, linelen, "/var/media/videos/");
flush(header, headerlen, start, linelen);
diff --git a/cmds/test-logos.py b/cmds/test-logos.py
index c934c58..d930ccb 100755
--- a/cmds/test-logos.py
+++ b/cmds/test-logos.py
@@ -11,10 +11,6 @@
from wvtest.wvtest import *
-def macAddressShapedString():
- chars = '0123456789abcdef::::::'
- return ''.join(random.choice(chars) for x in range(17))
-
@wvtest
def testLogos():
# We use a SOCK_DGRAM here rather than a normal pipe, because datagram
@@ -90,25 +86,6 @@
os.write(fd1, '\n')
WVPASSEQ('<7>fac: booga!\n', _Read())
- # MAC addresses
- os.write(fd1, 'f8:8f:ca:00:00:01\n')
- WVPASSEQ('<7>fac: f8:8f:ca:00:XX:XX\n', _Read())
- os.write(fd1, '8:8f:ca:00:00:01\n')
- WVPASSEQ('<7>fac: 8:8f:ca:00:00:01\n', _Read())
- os.write(fd1, '8:8f:ca:00:00:01:\n')
- WVPASSEQ('<7>fac: 8:8f:ca:00:00:01:\n', _Read())
- os.write(fd1, ':::semicolons:f8:8f:ca:00:00:01:and:after\n')
- WVPASSEQ('<7>fac: :::semicolons:f8:8f:ca:00:XX:XX:and:after\n', _Read())
- os.write(fd1, 'f8-8f-ca-00-00-01\n')
- WVPASSEQ('<7>fac: f8-8f-ca-00-XX-XX\n', _Read())
-
- # Send in random strings to look for crashes.
- for x in range(10):
- mac = macAddressShapedString()
- print 'Trying %s to check for crashes' % mac
- os.write(fd1, mac + '\n')
- print _Read()
-
# Filenames
os.write(fd1, 'Accessing /var/media/pictures/MyPicture.jpg for decode\n')
WVPASSEQ('<7>fac: Accessing /var/media/pictures/XXXXXXXXXXXXX for decode\n',
diff --git a/cmds/wifi_files.c b/cmds/wifi_files.c
index 31f9d4a..49e77c6 100644
--- a/cmds/wifi_files.c
+++ b/cmds/wifi_files.c
@@ -777,26 +777,6 @@
}
-static void TouchUpdateFile()
-{
- char filename[PATH_MAX];
- int fd;
-
- snprintf(filename, sizeof(filename), "%s/updated.new", STATIONS_DIR);
- if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) < 0) {
- perror("TouchUpdatedFile open");
- exit(1);
- }
-
- if (write(fd, "updated", 7) < 7) {
- perror("TouchUpdatedFile write");
- exit(1);
- }
-
- close(fd);
-} /* TouchUpdateFile */
-
-
static void ClientStateToLog(gpointer key, gpointer value, gpointer user_data)
{
const client_state_t *state = (const client_state_t *)value;
@@ -850,7 +830,6 @@
void UpdateAssociatedDevices()
{
g_hash_table_foreach(clients, ClientStateToJson, NULL);
- TouchUpdateFile();
}
@@ -887,12 +866,21 @@
int i;
for (i = 0; i < len; i++) {
- if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\')
- fprintf(f, "%c", data[i]);
- else if (data[i] == ' ' && (i != 0 && i != len -1))
- fprintf(f, " ");
- else
- fprintf(f, "\\x%.2x", data[i]);
+ switch(data[i]) {
+ case '\\': fprintf(f, "\\\\"); break;
+ case '"': fprintf(f, "\\\""); break;
+ case '\b': fprintf(f, "\\b"); break;
+ case '\f': fprintf(f, "\\f"); break;
+ case '\n': fprintf(f, "\\n"); break;
+ case '\r': fprintf(f, "\\r"); break;
+ case '\t': fprintf(f, "\\t"); break;
+ default:
+ if ((data[i] <= 0x1f) || !isprint(data[i])) {
+ fprintf(f, "\\u00%02x", data[i]);
+ } else {
+ fprintf(f, "%c", data[i]); break;
+ }
+ }
}
}
@@ -1027,6 +1015,26 @@
}
#ifndef UNIT_TESTS
+static void TouchUpdateFile()
+{
+ char filename[PATH_MAX];
+ int fd;
+
+ snprintf(filename, sizeof(filename), "%s/updated.new", STATIONS_DIR);
+ if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) < 0) {
+ perror("TouchUpdatedFile open");
+ exit(1);
+ }
+
+ if (write(fd, "updated", 7) < 7) {
+ perror("TouchUpdatedFile write");
+ exit(1);
+ }
+
+ close(fd);
+} /* TouchUpdateFile */
+
+
int main(int argc, char **argv)
{
int done = 0;
diff --git a/cmds/wifi_files_test.c b/cmds/wifi_files_test.c
index bb0043c..9d48dd1 100644
--- a/cmds/wifi_files_test.c
+++ b/cmds/wifi_files_test.c
@@ -34,8 +34,8 @@
{
FILE *f = tmpfile();
char buf[32];
- const uint8_t ssid[] = {'a', 'b', 0x86, ' ', 'c'}; /* not NUL terminated. */
- const uint8_t expected[] = {'a', 'b', '\\', 'x', '8', '6', ' ', 'c'};
+ const uint8_t ssid[] = {'b', 0x86, ' ', 'c'}; /* not NUL terminated. */
+ const uint8_t expected[] = {'b', '\\', 'u', '0', '0', '8', '6', ' ', 'c'};
printf("Testing \"%s\" in %s:\n", __FUNCTION__, __FILE__);
memset(buf, 0, sizeof(buf));
diff --git a/logupload/client/Makefile b/logupload/client/Makefile
index f7a56d3..5e72977 100644
--- a/logupload/client/Makefile
+++ b/logupload/client/Makefile
@@ -10,7 +10,7 @@
CFLAGS+=-Wall -Werror $(EXTRACFLAGS)
LDFLAGS+=$(EXTRALDFLAGS)
-LIBS=-lrt -lcurl -lz -lm
+LIBS=-lrt -lcurl -lz -lm -lcrypto
# Test Flags
TEST_LDFLAGS=$(LDFLAGS)
diff --git a/logupload/client/log_uploader.c b/logupload/client/log_uploader.c
index 5b63174..8aa0990 100644
--- a/logupload/client/log_uploader.c
+++ b/logupload/client/log_uploader.c
@@ -1,3 +1,4 @@
+#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
@@ -6,6 +7,10 @@
#include <time.h>
#include <unistd.h>
#include <inttypes.h>
+#include <openssl/md5.h>
+#include <openssl/hmac.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include "log_uploader.h"
#include "utils.h"
@@ -108,6 +113,8 @@
}
}
+ num_read = suppress_mac_addresses(params->line_buffer, num_read);
+
// Parse the data on the line to get the extra information
if (parse_line_data(params->line_buffer, &parsed_line)) {
// We don't want to be fatal if we fail to parse a line for some
@@ -177,3 +184,158 @@
}
return params->log_buffer;
}
+
+const char SOFT[] = "AEIOUY" "V";
+const char HARD[] = "BCDFGHJKLMNPQRSTVWXYZ" "AEIOU";
+const char *consensus_key_file = "/tmp/waveguide/consensus_key";
+#define CONSENSUS_KEY_LEN 16
+uint8_t consensus_key[CONSENSUS_KEY_LEN];
+#define MAC_ADDR_LEN 17
+
+void default_consensus_key()
+{
+ int fd;
+
+ if ((fd = open("/dev/urandom", O_RDONLY)) >= 0) {
+ ssize_t siz = sizeof(consensus_key);
+ if (read(fd, consensus_key, siz) != siz) {
+ /* https://xkcd.com/221/ */
+ memset(consensus_key, time(NULL), siz);
+ }
+ close(fd);
+ }
+}
+
+/* Read the waveguide consensus_key, if any */
+static void get_consensus_key()
+{
+ static ino_t ino = 0;
+ static time_t mtime = 0;
+ struct stat statbuf;
+ int fd;
+
+ if (stat(consensus_key_file, &statbuf)) {
+ if ((statbuf.st_ino == ino) && (statbuf.st_mtime == mtime)) {
+ return;
+ }
+ }
+
+ fd = open(consensus_key_file, O_RDONLY);
+ if (fd >= 0) {
+ uint8_t new_key[sizeof(consensus_key)];
+ if (read(fd, new_key, sizeof(new_key)) == sizeof(new_key)) {
+ memcpy(consensus_key, new_key, sizeof(consensus_key));
+ ino = statbuf.st_ino;
+ mtime = statbuf.st_mtime;
+ }
+ close(fd);
+ }
+}
+
+/* Given a value from 0..4095, encode it as a cons+vowel+cons sequence. */
+static void trigraph(int num, char *out)
+{
+ int ns = sizeof(SOFT) - 1;
+ int nh = sizeof(HARD) - 1;
+ int c1, c2, c3;
+
+ c3 = num % nh;
+ c2 = (num / nh) % ns;
+ c1 = num / nh / ns;
+ out[0] = HARD[c1];
+ out[1] = SOFT[c2];
+ out[2] = HARD[c3];
+}
+
+static int hex_chr_to_int(char hex) {
+ switch(hex) {
+ case '0' ... '9':
+ return hex - '0';
+ case 'a' ... 'f':
+ return hex - 'a' + 10;
+ case 'A' ... 'F':
+ return hex - 'A' + 10;
+ }
+
+ return 0;
+}
+
+/*
+ * Convert a string of the form "00:11:22:33:44:55" to
+ * a binary array 001122334455.
+ */
+static void get_binary_mac(const char *mac, uint8_t *out) {
+ int i;
+ for (i = 0; i < MAC_ADDR_LEN; i += 3) {
+ *out = (hex_chr_to_int(mac[i]) << 4) | hex_chr_to_int(mac[i+1]);
+ out++;
+ }
+}
+
+static const char *get_anonid_for_mac(const char *mac, char *out) {
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ unsigned int digest_len = sizeof(digest);
+ uint8_t macbin[6];
+ uint32_t num;
+
+ get_consensus_key();
+ get_binary_mac(mac, macbin);
+ HMAC(EVP_md5(), consensus_key, sizeof(consensus_key), macbin, sizeof(macbin),
+ digest, &digest_len);
+ num = (digest[0] << 16) | (digest[1] << 8) | digest[2];
+ trigraph((num >> 12) & 0x0fff, out);
+ trigraph((num ) & 0x0fff, out + 3);
+
+ return out;
+}
+
+static ssize_t anonymize_mac_address(char *s, ssize_t len) {
+ char anonid[6];
+ ssize_t offset = MAC_ADDR_LEN - sizeof(anonid);
+
+ get_anonid_for_mac(s, anonid);
+ memcpy(s, anonid, sizeof(anonid));
+ s += sizeof(anonid);
+ len -= offset;
+ memmove(s, s + offset, len);
+ return offset;
+}
+
+static int is_mac_addr(const char *s, char sep) {
+ if ((s[2] == sep) && (s[5] == sep) && (s[8] == sep) &&
+ (s[11] == sep) && (s[14] == sep) &&
+ isxdigit(s[0]) && isxdigit(s[1]) &&
+ isxdigit(s[3]) && isxdigit(s[4]) &&
+ isxdigit(s[6]) && isxdigit(s[7]) &&
+ isxdigit(s[9]) && isxdigit(s[10]) &&
+ isxdigit(s[12]) && isxdigit(s[13]) &&
+ isxdigit(s[15]) && isxdigit(s[16])) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * search for text patterns which look like MAC addresses,
+ * and anonymize them.
+ * Ex: f8:8f:ca:00:00:01 to PEEVJB
+ */
+unsigned long suppress_mac_addresses(char *line, ssize_t len) {
+ char *s = line;
+ unsigned long new_len = len;
+ ssize_t reduce;
+
+ while (len >= MAC_ADDR_LEN) {
+ if (is_mac_addr(s, ':') || is_mac_addr(s, '-') || is_mac_addr(s, '_')) {
+ reduce = anonymize_mac_address(s, len);
+ len -= reduce;
+ new_len -= reduce;
+ } else {
+ s += 1;
+ len -= 1;
+ }
+ }
+
+ return new_len;
+}
diff --git a/logupload/client/log_uploader.h b/logupload/client/log_uploader.h
index bb6011c..4a3672f 100644
--- a/logupload/client/log_uploader.h
+++ b/logupload/client/log_uploader.h
@@ -49,6 +49,13 @@
int logmark_once(const char* output_path, const char* version_path,
const char* ntp_sync_path);
+// Rewrite any MAC addresses of the form 00:11:22:33:44:55 (or similar)
+// as anonids like ABCDEF.
+unsigned long suppress_mac_addresses(char *line, ssize_t len);
+
+// initialize a random key for anonymization.
+void default_consensus_key();
+
#ifdef __cplusplus
}
#endif
diff --git a/logupload/client/log_uploader_main.c b/logupload/client/log_uploader_main.c
index 8d3afc4..7a4e0fb 100644
--- a/logupload/client/log_uploader_main.c
+++ b/logupload/client/log_uploader_main.c
@@ -170,6 +170,8 @@
snprintf(config.upload_target, sizeof(config.upload_target), "%s",
DEFAULT_UPLOAD_TARGET);
+ default_consensus_key();
+
if (argc > 1) {
if (parse_args(&config, argc, argv) < 0) {
usage(argv[0]);
diff --git a/logupload/client/log_uploader_test.cc b/logupload/client/log_uploader_test.cc
index af10f41..ad3d61f 100644
--- a/logupload/client/log_uploader_test.cc
+++ b/logupload/client/log_uploader_test.cc
@@ -315,3 +315,30 @@
remove(test_dev_kmsg_path);
rmdir(tdir);
}
+
+
+struct log_data test_MAC_data[] = {
+ { 1, 1000LL, 100LL, "-", "f8:8f:ca:00:00:01\n", NULL },
+ { 4, 1001LL, 101LL, "-", "8:8f:ca:00:00:01\n", NULL },
+ { 2, 1010LL, 102LL, "-", "8:8f:ca:00:00:01:\n", NULL },
+ { 5, 2030000LL, 104LL, "-", ":::semicolons:f8:8f:ca:00:00:01:and:after\n",
+ NULL },
+ { 3, 3030000LL, 105LL, "-", "f8-8f-ca-00-00-01\n", NULL },
+ { 3, 3030000LL, 105LL, "-", "f8_8f_ca_00_00_01\n", NULL },
+};
+int test_MAC_data_size = sizeof(test_MAC_data) / sizeof(struct log_data);
+
+
+TEST(LogUploader, anonymize_mac_addresses) {
+ struct log_parse_params* params = create_log_parse_params(test_MAC_data,
+ test_MAC_data_size);
+ char* res_buffer = parse_and_consume_log_data(params);
+
+ /* Verify that the MAC address has been anonymized. */
+ printf("%s\n", res_buffer);
+ EXPECT_TRUE(strstr(res_buffer, "f8:8f:ca:00:00:01") == NULL);
+ EXPECT_TRUE(strstr(res_buffer, "f8-8f-ca-00-00-01") == NULL);
+ EXPECT_TRUE(strstr(res_buffer, "f8_8f_ca_00_00_01") == NULL);
+
+ free_log_parse_params(params);
+}
diff --git a/speedtest/Makefile b/speedtest/Makefile
index 56dc3e1..af5ea27 100644
--- a/speedtest/Makefile
+++ b/speedtest/Makefile
@@ -4,27 +4,65 @@
BINDIR=$(PREFIX)/bin
DEBUG?=-g
WARNINGS=-Wall -Werror -Wno-unused-result -Wno-unused-but-set-variable
-CXXFLAGS=$(DEBUG) $(WARNINGS) -O3 -DNDEBUG -std=c++11 $(EXTRACFLAGS)
+CXXFLAGS=$(DEBUG) $(WARNINGS) -DNDEBUG -std=c++11 $(EXTRACFLAGS)
+#CXXFLAGS=$(DEBUG) $(WARNINGS) -O3 -DNDEBUG -std=c++11 $(EXTRACFLAGS)
LDFLAGS=$(DEBUG) $(EXTRALDFLAGS)
GTEST_DIR=googletest
GMOCK_DIR=googlemock
TFLAGS=$(DEBUG) -isystem ${GTEST_DIR}/include -isystem $(GMOCK_DIR)/include -pthread -std=c++11
-LIBS=-lcurl -lpthread
+LIBS=-lcurl -lpthread -ljsoncpp
TOBJS=curl_env.o url.o errors.o request.o utils.o
-OBJS=errors.o curl_env.o options.o request.o utils.o speedtest.o url.o
+OBJS=config.o \
+ curl_env.o \
+ download_task.o \
+ errors.o \
+ http_task.o \
+ options.o \
+ ping_task.o \
+ request.o \
+ speedtest.o \
+ task.o \
+ timed_runner.o \
+ transfer_runner.o \
+ transfer_task.o \
+ upload_task.o \
+ url.o \
+ utils.o
all: speedtest
+config.o: config.cc config.h url.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
-utils.o: utils.cc options.h
-request.o: request.cc request.h curl_env.h url.h
-url.o: url.cc url.h
-speedtest.o: speedtest.cc speedtest.h curl_env.h options.h request.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
+speedtest.o: speedtest.cc \
+ speedtest.h \
+ config.h \
+ curl_env.h \
+ download_task.h \
+ options.h \
+ ping_task.h \
+ request.h \
+ task.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
+utils.o: utils.cc options.h
+url.o: url.cc url.h utils.h
+
speedtest: speedtest_main.o $(OBJS)
$(CXX) -o $@ $< $(OBJS) $(LDFLAGS) $(LIBS)
@@ -45,14 +83,14 @@
libspeedtesttest.a: $(TOBJS)
ar -rv libspeedtesttest.a $(TOBJS)
-%_test.o: %_test.cc %.h
+%_test.o: %_test.cc %.h %.cc
$(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
./$@
-test: options_test request_test url_test
+test: config_test options_test request_test url_test
install: speedtest
$(INSTALL) -m 0755 speedtest $(BINDIR)/
diff --git a/speedtest/config.cc b/speedtest/config.cc
new file mode 100644
index 0000000..aede959
--- /dev/null
+++ b/speedtest/config.cc
@@ -0,0 +1,97 @@
+/*
+ * 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"
+
+// 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) {
+ if (!config) {
+ return false;
+ }
+
+ Json::Reader reader;
+ Json::Value root;
+ if (!reader.parse(json, root, false)) {
+ return false;
+ }
+
+ config->download_size = root["downloadSize"].asInt();
+ config->upload_size = root["uploadSize"].asInt();
+ config->interval_millis = root["intervalSize"].asInt();
+ config->location_name = root["locationName"].asString();
+ config->min_transfer_intervals = root["minTransferIntervals"].asInt();
+ config->max_transfer_intervals = root["maxTransferIntervals"].asInt();
+ config->min_transfer_runtime = root["minTransferRunTime"].asInt();
+ config->max_transfer_runtime = root["maxTransferRunTime"].asInt();
+ 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->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;
+}
+
+void PrintConfig(const Config &config) {
+ PrintConfig(std::cout, config);
+}
+
+void PrintConfig(std::ostream &out, const Config &config) {
+ out << "Download size: " << config.download_size << " bytes\n"
+ << "Upload size: " << config.upload_size << " bytes\n"
+ << "Interval size: " << config.interval_millis << " ms\n"
+ << "Location name: " << config.location_name << "\n"
+ << "Min transfer intervals: " << config.min_transfer_intervals << "\n"
+ << "Max transfer intervals: " << config.max_transfer_intervals << "\n"
+ << "Min transfer runtime: " << config.min_transfer_runtime << " ms\n"
+ << "Max transfer runtime: " << config.max_transfer_runtime << " ms\n"
+ << "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"
+ << "Transfer port start: " << config.transfer_port_start << "\n"
+ << "Transfer port end: " << config.transfer_port_end << "\n";
+}
+
+} // namespace
diff --git a/speedtest/config.h b/speedtest/config.h
new file mode 100644
index 0000000..2988484
--- /dev/null
+++ b/speedtest/config.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_CONFIG_H
+#define SPEEDTEST_CONFIG_H
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include "url.h"
+
+namespace speedtest {
+
+struct Config {
+ int download_size = 0;
+ int upload_size = 0;
+ int interval_millis = 0;
+ std::string location_name;
+ int min_transfer_intervals = 0;
+ int max_transfer_intervals = 0;
+ int min_transfer_runtime = 0;
+ int 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;
+ int transfer_port_start = 0;
+ int transfer_port_end = 0;
+};
+
+// 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);
+
+void PrintConfig(const Config &config);
+void PrintConfig(std::ostream &out, const Config &config);
+
+} // namespace speedtest
+
+#endif //SPEEDTEST_CONFIG_H
diff --git a/speedtest/config_test.cc b/speedtest/config_test.cc
new file mode 100644
index 0000000..5924921
--- /dev/null
+++ b/speedtest/config_test.cc
@@ -0,0 +1,132 @@
+/*
+ * 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>
+
+namespace speedtest {
+namespace {
+
+const char *kValidConfig = R"CONFIG(
+{
+ "downloadSize": 10000000,
+ "intervalSize": 200,
+ "locationName": "Kansas City",
+ "maxTransferIntervals": 25,
+ "maxTransferRunTime": 20000,
+ "maxTransferVariance": 0.08,
+ "minTransferIntervals": 10,
+ "minTransferRunTime": 5000,
+ "numConcurrentDownloads": 20,
+ "numConcurrentUploads": 15,
+ "pingRunTime": 3000,
+ "pingTimeout": 300,
+ "transferPortEnd": 3023,
+ "transferPortStart": 3004,
+ "uploadSize": 20000000
+}
+)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));
+}
+
+TEST(ParseConfigTest, EmptyJson_Invalid) {
+ Config config;
+ EXPECT_FALSE(ParseConfig("", &config));
+}
+
+TEST(ParseConfigTest, InvalidJson_Invalid) {
+ Config config;
+ EXPECT_FALSE(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_EQ(20, config.num_downloads);
+ EXPECT_EQ(15, config.num_uploads);
+ EXPECT_EQ(200, config.interval_millis);
+ 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(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 c02835c..eed370f 100644
--- a/speedtest/curl_env.cc
+++ b/speedtest/curl_env.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -18,34 +18,106 @@
#include <cstdlib>
#include <iostream>
-#include <curl/curl.h>
#include "errors.h"
#include "request.h"
namespace http {
+namespace {
-CurlEnv::CurlEnv() {
- init(CURL_GLOBAL_NOTHING);
+void LockFn(CURL *handle,
+ curl_lock_data data,
+ curl_lock_access access,
+ void *userp) {
+ CurlEnv *env = static_cast<CurlEnv *>(userp);
+ env->Lock(data);
}
-CurlEnv::CurlEnv(int init_options) {
- init(init_options);
+void UnlockFn(CURL *handle, curl_lock_data data, void *userp) {
+ CurlEnv *env = static_cast<CurlEnv *>(userp);
+ env->Unlock(data);
}
-CurlEnv::~CurlEnv() {
- curl_global_cleanup();
+} // namespace
+
+std::shared_ptr<CurlEnv> CurlEnv::NewCurlEnv(const Options &options) {
+ return std::shared_ptr<CurlEnv>(new CurlEnv(options));
}
-std::unique_ptr<Request> CurlEnv::NewRequest() {
- return std::unique_ptr<Request>(new Request(shared_from_this()));
-}
-
-void CurlEnv::init(int init_flags) {
- CURLcode status = curl_global_init(init_flags);
+CurlEnv::CurlEnv(const Options &options)
+ : options_(options),
+ set_max_connections_(false),
+ share_(nullptr) {
+ CURLcode status;
+ {
+ std::lock_guard <std::mutex> lock(curl_mutex_);
+ status = curl_global_init(options_.curl_options);
+ }
if (status != 0) {
std::cerr << "Curl initialization failed: " << ErrorString(status);
std::exit(1);
}
+ if (!options_.disable_dns_cache) {
+ share_ = curl_share_init();
+ curl_share_setopt(share_, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
+ curl_share_setopt(share_, CURLSHOPT_USERDATA, this);
+ curl_share_setopt(share_, CURLSHOPT_LOCKFUNC, &LockFn);
+ curl_share_setopt(share_, CURLSHOPT_UNLOCKFUNC, &UnlockFn);
+ }
+}
+
+CurlEnv::~CurlEnv() {
+ curl_share_cleanup(share_);
+ share_ = nullptr;
+ curl_global_cleanup();
+}
+
+std::unique_ptr<Request> 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.
+ std::lock_guard <std::mutex> lock(curl_mutex_);
+
+ // We use an aliasing constructor on a shared_ptr to keep a reference to
+ // CurlEnv as when the refcount drops to 0 we want to do global cleanup.
+ // So the CURL handle for this shared_ptr is _unmanaged_ and the Request
+ // object is responsible for cleaning it up, which involves calling
+ // curl_easy_cleanup().
+ //
+ // This way Request doesn't need to know about CurlEnv at all, while
+ // all Request instances will still keep an implicit reference to
+ // CurlEnv.
+ std::shared_ptr<CURL> handle(shared_from_this(), curl_easy_init());
+
+ // For some reason libcurl sets the max connections on a handle.
+ // According to the docs, doing so when there are open connections may
+ // close them so we maintain this boolean so as to set the maximum
+ // number of connections on the connection pool associated with this
+ // handle before any connections are opened.
+ if (!set_max_connections_ && options_.max_connections > 0) {
+ curl_easy_setopt(handle.get(),
+ CURLOPT_MAXCONNECTS,
+ options_.max_connections);
+ set_max_connections_ = true;
+ }
+
+ curl_easy_setopt(handle.get(), CURLOPT_SHARE, share_);
+ return std::unique_ptr<Request>(new Request(handle, url));
+}
+
+void CurlEnv::Lock(curl_lock_data lock_type) {
+ if (lock_type == CURL_LOCK_DATA_DNS) {
+ // It is ill-advised to call lock directly but libcurl uses
+ // separate lock/unlock functions.
+ dns_mutex_.lock();
+ }
+}
+
+void CurlEnv::Unlock(curl_lock_data lock_type) {
+ if (lock_type == CURL_LOCK_DATA_DNS) {
+ // It is ill-advised to call lock directly but libcurl uses
+ // separate lock/unlock functiounknns.
+ dns_mutex_.unlock();
+ }
}
} // namespace http
diff --git a/speedtest/curl_env.h b/speedtest/curl_env.h
index 09511fa..6a70f28 100644
--- a/speedtest/curl_env.h
+++ b/speedtest/curl_env.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -17,27 +17,45 @@
#ifndef HTTP_CURL_ENV_H
#define HTTP_CURL_ENV_H
+#include <curl/curl.h>
#include <memory>
+#include <mutex>
+#include "url.h"
namespace http {
class Request;
-// Curl initialization to cleanup automatically
class CurlEnv : public std::enable_shared_from_this<CurlEnv> {
public:
- CurlEnv();
- explicit CurlEnv(int init_options);
+ struct Options {
+ int curl_options = CURL_GLOBAL_NOTHING;
+ bool disable_dns_cache = false;
+ int max_connections = 0;
+ };
+
+ static std::shared_ptr<CurlEnv> NewCurlEnv(const Options &options);
virtual ~CurlEnv();
- std::unique_ptr<Request> NewRequest();
+ std::unique_ptr<Request> NewRequest(const Url &url);
+
+ void Lock(curl_lock_data lock_type);
+ void Unlock(curl_lock_data lock_type);
private:
- void init(int flags);
+ explicit CurlEnv(const Options &options);
+
+ Options options_;
+
+ // used to lock on curl global state
+ std::mutex curl_mutex_;
+ bool set_max_connections_;
+
+ std::mutex dns_mutex_;
+ CURLSH *share_; // owned
// disable
CurlEnv(const CurlEnv &other) = delete;
-
void operator=(const CurlEnv &other) = delete;
};
diff --git a/speedtest/download_task.cc b/speedtest/download_task.cc
new file mode 100644
index 0000000..a643725
--- /dev/null
+++ b/speedtest/download_task.cc
@@ -0,0 +1,78 @@
+/*
+ * 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
new file mode 100644
index 0000000..2b65478
--- /dev/null
+++ b/speedtest/download_task.h
@@ -0,0 +1,51 @@
+/*
+ * 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/errors.cc b/speedtest/errors.cc
index 05f382b..43c94f8 100644
--- a/speedtest/errors.cc
+++ b/speedtest/errors.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
diff --git a/speedtest/errors.h b/speedtest/errors.h
index 334689d..4c70003 100644
--- a/speedtest/errors.h
+++ b/speedtest/errors.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
diff --git a/speedtest/http_task.cc b/speedtest/http_task.cc
new file mode 100644
index 0000000..1275aa4
--- /dev/null
+++ b/speedtest/http_task.cc
@@ -0,0 +1,24 @@
+/*
+ * 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
new file mode 100644
index 0000000..a54e4ba
--- /dev/null
+++ b/speedtest/http_task.h
@@ -0,0 +1,43 @@
+/*
+ * 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/options.cc b/speedtest/options.cc
index 4d07099..133b857 100644
--- a/speedtest/options.cc
+++ b/speedtest/options.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -25,14 +25,23 @@
namespace speedtest {
-const char* kDefaultHost = "speedtest.googlefiber.net";
+const char* kDefaultHost = "any.speed.gfsvc.com";
namespace {
-bool ParseLong(const char *s, char **endptr, long *size) {
- assert(s != nullptr);
- assert(size != nullptr);
- *size = strtol(s, endptr, 10);
+bool ParseLong(const char *s, char **endptr, long *number) {
+ assert(s);
+ assert(endptr);
+ assert(number != nullptr);
+ *number = strtol(s, endptr, 10);
+ return !**endptr;
+}
+
+bool ParseDouble(const char *s, char **endptr, double *number) {
+ assert(s);
+ assert(endptr);
+ assert(number != nullptr);
+ *number = strtod(s, endptr);
return !**endptr;
}
@@ -58,67 +67,129 @@
return true;
}
-const char *kShortOpts = "d:u:t:n:p:s:vh";
+const int kOptDisableDnsCache = 1000;
+const int kOptMaxConnections = 1001;
+const int kOptExponentialMovingAverage = 1002;
+
+const int kOptMinTransferTime = 1100;
+const int kOptMaxTransferTime = 1101;
+const int kOptMinTransferIntervals = 1102;
+const int kOptMaxTransferIntervals = 1103;
+const int kOptMaxTransferVariance = 1104;
+const int kOptIntervalMillis = 1105;
+const int kOptPingRuntime = 1106;
+const int kOptPingTimeout = 1107;
+
+const char *kShortOpts = "hvg:a:d:s:t:u:p:";
+
struct option kLongOpts[] = {
{"help", no_argument, nullptr, 'h'},
- {"number", required_argument, nullptr, 'n'},
- {"download_size", required_argument, nullptr, 'd'},
- {"upload_size", required_argument, nullptr, 'u'},
- {"progress", required_argument, nullptr, 'p'},
- {"serverid", required_argument, nullptr, 's'}, // ignored
- {"time", required_argument, nullptr, 't'},
{"verbose", no_argument, nullptr, 'v'},
+ {"global_host", 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},
+
+ {"num_downloads", required_argument, nullptr, 'd'},
+ {"download_size", required_argument, nullptr, 's'},
+ {"num_uploads", required_argument, nullptr, 'u'},
+ {"upload_size", required_argument, nullptr, 't'},
+ {"min_transfer_runtime", required_argument, nullptr, kOptMinTransferTime},
+ {"max_transfer_runtime", required_argument, nullptr, kOptMaxTransferTime},
+ {"min_transfer_intervals", required_argument, nullptr,
+ kOptMinTransferIntervals},
+ {"max_transfer_intervals", required_argument, nullptr,
+ kOptMaxTransferIntervals},
+ {"max_transfer_variance", required_argument, nullptr,
+ kOptMaxTransferVariance},
+ {"interval_millis", required_argument, nullptr, kOptIntervalMillis},
+ {"ping_runtime", required_argument, nullptr, kOptPingRuntime},
+ {"ping_timeout", required_argument, nullptr, kOptPingTimeout},
{nullptr, 0, nullptr, 0},
};
-const int kDefaultNumber = 10;
-const int kDefaultDownloadSize = 10000000;
-const int kDefaultUploadSize = 10000000;
-const int kDefaultTimeMillis = 5000;
const int kMaxNumber = 1000;
const int kMaxProgress = 1000000;
+const char *kSpeedtestHelp = R"USAGE(: run an HTTP speedtest.
+
+If no hosts are specified, the global host is queried for a list
+of servers to use, otherwise the list of supplied hosts will be
+used. Each will be pinged several times and the one with the
+lowest ping time will be used. If only one host is supplied, it
+will be used without pinging.
+
+Usage: speedtest [options] [host ...]
+ -h, --help This help text
+ -v, --verbose Verbose output
+ -g, --global_host 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
+
+These options override the speedtest config parameters:
+ -d, --num_downloads NUM Number of simultaneous downloads
+ -s, --download_size SIZE Download size in bytes
+ -t, --upload_size SIZE Upload size in bytes
+ -u, --num_uploads NUM Number of simultaneous uploads
+ --min_transfer_runtime TIME Minimum transfer time in milliseconds
+ --max_transfer_runtime TIME Maximum transfer time in milliseconds
+ --min_transfer_intervals NUM Short moving average intervals
+ --max_transfer_intervals NUM Long moving average intervals
+ --max_transfer_variance NUM Max difference between moving averages
+ --interval_millis TIME Interval size in milliseconds
+ --ping_runtime TIME Ping runtime in milliseconds
+ --ping_timeout TIME Ping timeout in milliseconds
+)USAGE";
+
} // namespace
bool ParseOptions(int argc, char *argv[], Options *options) {
assert(options != nullptr);
- options->number = kDefaultNumber;
- options->download_size = kDefaultDownloadSize;
- options->upload_size = kDefaultUploadSize;
- options->time_millis = kDefaultTimeMillis;
- options->progress_millis = 0;
- options->verbose = false;
options->usage = false;
+ options->verbose = false;
+ options->global_host = 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->num_downloads = 0;
+ options->download_size = 0;
+ options->num_uploads = 0;
+ options->upload_size = 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->hosts.clear();
+ if (!options->global_host.ok()) {
+ std::cerr << "Invalid global host " << kDefaultHost << "\n";
+ return false;
+ }
+
// Manually set this to 0 to allow repeated calls
optind = 0;
-
int opt = 0, long_index = 0;
while ((opt = getopt_long(argc, argv,
kShortOpts, kLongOpts, &long_index)) != -1) {
switch (opt) {
- case 'd':
- if (!ParseSize(optarg, &options->download_size)) {
- std::cerr << "Invalid download size '" << optarg << "'\n";
- return false;
- }
+ case 'a':
+ options->user_agent = optarg;
break;
- case 'u':
- if (!ParseSize(optarg, &options->upload_size)) {
- std::cerr << "Invalid upload size '" << optarg << "'\n";
- return false;
- }
- break;
- case 't':
- options->time_millis = atoi(optarg);
- break;
- case 'v':
- options->verbose = true;
- break;
- case 'h':
- options->usage = true;
- return true;
- case 'n': {
+ case 'd': {
long number;
char *endptr;
if (!ParseLong(optarg, &endptr, &number)) {
@@ -127,12 +198,24 @@
}
if (number < 1 || number > kMaxNumber) {
std::cerr << "Number must be between 1 and " << kMaxNumber
- << ", got '" << optarg << "'\n";
+ << ", got '" << optarg << "'\n";
return false;
}
- options->number = static_cast<int>(number);
+ options->num_downloads = static_cast<int>(number);
break;
}
+ case 'g': {
+ http::Url url(optarg);
+ if (!url.ok()) {
+ std::cerr << "Invalid global host " << optarg << "\n";
+ return false;
+ }
+ options->global_host = url;
+ break;
+ }
+ case 'h':
+ options->usage = true;
+ return true;
case 'p': {
long progress;
char *endptr;
@@ -142,16 +225,186 @@
}
if (progress < 0 || progress > kMaxProgress) {
std::cerr << "Number must be between 0 and " << kMaxProgress
- << ", got '" << optarg << "'\n";
+ << ", got '" << optarg << "'\n";
return false;
}
options->progress_millis = static_cast<int>(progress);
break;
}
case 's':
- // serverid is an argument supported by the older speedtest
- // implementation. It is ignored here to ease the transition.
+ if (!ParseSize(optarg, &options->download_size)) {
+ std::cerr << "Invalid download size '" << optarg << "'\n";
+ return false;
+ }
break;
+ case 't':
+ if (!ParseSize(optarg, &options->upload_size)) {
+ std::cerr << "Invalid upload size '" << optarg << "'\n";
+ return false;
+ }
+ break;
+ case 'u': {
+ long number;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &number)) {
+ std::cerr << "Could not parse number '" << optarg << "'\n";
+ return false;
+ }
+ if (number < 1 || number > kMaxNumber) {
+ std::cerr << "Number must be between 1 and " << kMaxNumber
+ << ", got '" << optarg << "'\n";
+ return false;
+ }
+ options->num_uploads = static_cast<int>(number);
+ break;
+ }
+ case 'v':
+ options->verbose = true;
+ break;
+ case kOptDisableDnsCache:
+ options->disable_dns_cache = true;
+ break;
+ case kOptMaxConnections: {
+ long max_connections;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &max_connections)) {
+ std::cerr << "Could not parse max connections '" << optarg << "'\n";
+ return false;
+ }
+ if (max_connections < 0) {
+ std::cerr << "Max connections must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->max_connections = static_cast<int>(max_connections);
+ break;
+ }
+ case kOptExponentialMovingAverage:
+ options->exponential_moving_average = true;
+ break;
+ case kOptMinTransferTime: {
+ long transfer_time;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &transfer_time)) {
+ std::cerr << "Could not parse minimum transfer time '"
+ << optarg << "'\n";
+ return false;
+ }
+ if (transfer_time < 0) {
+ std::cerr << "Minimum transfer runtime must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->min_transfer_runtime = static_cast<int>(transfer_time);
+ break;
+ }
+ case kOptMaxTransferTime: {
+ long transfer_time;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &transfer_time)) {
+ std::cerr << "Could not parse maximum transfer time '"
+ << optarg << "'\n";
+ return false;
+ }
+ if (transfer_time < 0) {
+ std::cerr << "Maximum transfer runtime must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->max_transfer_runtime = static_cast<int>(transfer_time);
+ break;
+ }
+ case kOptMinTransferIntervals: {
+ long intervals;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &intervals)) {
+ std::cerr << "Could not parse minimum transfer intervals '"
+ << optarg << "'\n";
+ return false;
+ }
+ if (intervals < 0) {
+ std::cerr << "Minimum transfer intervals must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->min_transfer_intervals = static_cast<int>(intervals);
+ break;
+ }
+ case kOptMaxTransferIntervals: {
+ long intervals;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &intervals)) {
+ std::cerr << "Could not parse maximum transfer intervals '"
+ << optarg << "'\n";
+ return false;
+ }
+ if (intervals < 0) {
+ std::cerr << "Maximum transfer intervals must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->max_transfer_intervals = static_cast<int>(intervals);
+ break;
+ }
+ case kOptMaxTransferVariance: {
+ double variance;
+ char *endptr;
+ if (!ParseDouble(optarg, &endptr, &variance)) {
+ std::cerr << "Could not parse variance '" << optarg << "'\n";
+ return false;
+ }
+ if (variance < 0) {
+ std::cerr << "Variances must be nonnegative, got " << optarg << "'\n";
+ return false;
+ }
+ options->max_transfer_variance = variance;
+ break;
+ }
+ case kOptIntervalMillis: {
+ long interval_millis;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &interval_millis)) {
+ std::cerr << "Could not parse interval time '" << optarg << "'\n";
+ return false;
+ }
+ if (interval_millis < 0) {
+ std::cerr << "Interval time must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->interval_millis = static_cast<int>(interval_millis);
+ break;
+ }
+ case kOptPingRuntime: {
+ long ping_runtime;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &ping_runtime)) {
+ std::cerr << "Could not parse ping time '" << optarg << "'\n";
+ return false;
+ }
+ if (ping_runtime < 0) {
+ std::cerr << "Ping time must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->ping_runtime = static_cast<int>(ping_runtime);
+ break;
+ }
+ case kOptPingTimeout: {
+ long ping_timeout;
+ char *endptr;
+ if (!ParseLong(optarg, &endptr, &ping_timeout)) {
+ std::cerr << "Could not parse ping timeout '" << optarg << "'\n";
+ return false;
+ }
+ if (ping_timeout < 0) {
+ std::cerr << "Ping timeout must be nonnegative, got "
+ << optarg << "'\n";
+ return false;
+ }
+ options->ping_timeout = static_cast<int>(ping_timeout);
+ break;
+ }
default:
return false;
}
@@ -174,7 +427,7 @@
}
}
if (options->hosts.empty()) {
- options->hosts.emplace_back(http::Url(kDefaultHost));
+ options->global = true;
}
return true;
}
@@ -184,13 +437,29 @@
}
void PrintOptions(std::ostream &out, const Options &options) {
- out << "Number: " << options.number << "\n"
- << "Upload size: " << options.upload_size << "\n"
- << "Download size: " << options.download_size << "\n"
- << "Time: " << options.time_millis << " ms\n"
- << "Progress interval: " << options.progress_millis << " ms\n"
+ out << "Usage: " << (options.usage ? "true" : "false") << "\n"
<< "Verbose: " << (options.verbose ? "true" : "false") << "\n"
- << "Usage: " << (options.usage ? "true" : "false") << "\n"
+ << "Global host: " << options.global_host.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"
+ << "Number of downloads: " << options.num_downloads << "\n"
+ << "Download size: " << options.download_size << " bytes\n"
+ << "Number of uploads: " << options.num_uploads << "\n"
+ << "Upload size: " << options.upload_size << " 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"
<< "Hosts:\n";
for (const http::Url &host : options.hosts) {
out << " " << host.url() << "\n";
@@ -205,15 +474,7 @@
assert(app_path != nullptr);
const char *last_slash = strrchr(app_path, '/');
const char *app_name = last_slash == nullptr ? app_path : last_slash + 1;
- out << basename(app_name) << ": run an HTTP speedtest\n\n"
- << "Usage: speedtest [options] <host> ...\n"
- << " -h, --help This help text\n"
- << " -n, --number NUM Number of simultaneous transfers\n"
- << " -d, --download_size SIZE Download size in bytes\n"
- << " -u, --upload_size SIZE Upload size in bytes\n"
- << " -t, --time TIME Time per test in milliseconds\n"
- << " -p, --progress TIME Progress intervals in milliseconds\n"
- << " -v, --verbose Verbose output\n";
+ out << basename(app_name) << kSpeedtestHelp;
}
} // namespace speedtest
diff --git a/speedtest/options.h b/speedtest/options.h
index 0996798..9028f70 100644
--- a/speedtest/options.h
+++ b/speedtest/options.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -27,14 +27,31 @@
extern const char* kDefaultHost;
struct Options {
+ bool usage = false;
+ bool verbose = false;
+ http::Url global_host;
+ 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;
+
+ // A value of 0 means use the speedtest config parameters
+ int num_downloads = 0;
+ long download_size = 0;
+ int num_uploads = 0;
+ long upload_size = 0;
+ int min_transfer_runtime = 0;
+ int 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;
+
std::vector<http::Url> hosts;
- int number;
- long download_size;
- long upload_size;
- int time_millis;
- int progress_millis;
- bool verbose;
- bool usage;
};
// Parse command line options putting results into 'options'
diff --git a/speedtest/options_test.cc b/speedtest/options_test.cc
index 2754552..601984a 100644
--- a/speedtest/options_test.cc
+++ b/speedtest/options_test.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -14,12 +14,14 @@
* limitations under the License.
*/
+#include "options.h"
+
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <mutex>
#include <string.h>
#include <vector>
-#include "options.h"
+#include "url.h"
namespace speedtest {
namespace {
@@ -81,8 +83,28 @@
TEST(OptionsTest, Empty_ValidDefault) {
Options options;
TestValidOptions({}, &options);
- EXPECT_FALSE(options.verbose);
EXPECT_FALSE(options.usage);
+ EXPECT_FALSE(options.verbose);
+ EXPECT_TRUE(options.global);
+ EXPECT_EQ(http::Url("any.speed.gfsvc.com"), options.global_host);
+ 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_EQ(0, options.num_downloads);
+ EXPECT_EQ(0, options.download_size);
+ EXPECT_EQ(0, options.num_uploads);
+ EXPECT_EQ(0, options.upload_size);
+ 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());
}
TEST(OptionsTest, Usage_Valid) {
@@ -104,35 +126,94 @@
EXPECT_THAT(options.hosts, testing::ElementsAre(http::Url("efgh")));
}
-TEST(OptionsTest, FullShort_Valid) {
+TEST(OptionsTest, ShortOptions_Valid) {
Options options;
- TestValidOptions({"-d", "5122",
- "-u", "7653",
- "-t", "123",
- "-n", "15",
- "-p", "500"},
+ TestValidOptions({"-v",
+ "-s", "5122",
+ "-t", "7653",
+ "-d", "20",
+ "-u", "15",
+ "-p", "500",
+ "-g", "speed.gfsvc.com",
+ "-a", "CrOS",
+ "foo.speed.googlefiber.net",
+ "bar.speed.googlefiber.net"},
&options);
+ EXPECT_TRUE(options.verbose);
+ EXPECT_EQ(20, options.num_downloads);
EXPECT_EQ(5122, options.download_size);
+ EXPECT_EQ(15, options.num_uploads);
EXPECT_EQ(7653, options.upload_size);
- EXPECT_EQ(123, options.time_millis);
- EXPECT_EQ(15, options.number);
EXPECT_EQ(500, options.progress_millis);
+ EXPECT_FALSE(options.global);
+ EXPECT_EQ(http::Url("speed.gfsvc.com"), options.global_host);
+ EXPECT_EQ("CrOS", options.user_agent);
+
+ EXPECT_EQ(0, options.max_connections);
+ EXPECT_FALSE(options.disable_dns_cache);
+ EXPECT_FALSE(options.exponential_moving_average);
+ 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::UnorderedElementsAre(
+ http::Url("foo.speed.googlefiber.net"),
+ http::Url("bar.speed.googlefiber.net")));
}
-TEST(OptionsTest, FullLong_Valid) {
+TEST(OptionsTest, LongOptions_Valid) {
Options options;
- TestValidOptions({"--download_size", "5122",
+ TestValidOptions({"--verbose",
+ "--global_host", "speed.gfsvc.com",
+ "--user_agent", "CrOS",
+ "--progress_millis", "1000",
+ "--disable_dns_cache",
+ "--max_connections", "23",
+ "--exponential_moving_average",
+ "--num_downloads", "16",
+ "--download_size", "5122",
+ "--num_uploads", "12",
"--upload_size", "7653",
- "--time", "123",
- "--progress", "1000",
- "--number", "12"},
+ "--min_transfer_runtime", "7500",
+ "--max_transfer_runtime", "13500",
+ "--min_transfer_intervals", "13",
+ "--max_transfer_intervals", "22",
+ "--max_transfer_variance", "0.12",
+ "--interval_millis", "250",
+ "--ping_runtime", "2500",
+ "--ping_timeout", "300",
+ "foo.speed.googlefiber.net",
+ "bar.speed.googlefiber.net"},
&options);
- EXPECT_EQ(5122, options.download_size);
- EXPECT_EQ(7653, options.upload_size);
- EXPECT_EQ(123, options.time_millis);
- EXPECT_EQ(12, options.number);
+ EXPECT_TRUE(options.verbose);
+ EXPECT_FALSE(options.global);
+ EXPECT_EQ(http::Url("speed.gfsvc.com"), options.global_host);
+ EXPECT_EQ("CrOS", options.user_agent);
EXPECT_EQ(1000, options.progress_millis);
- EXPECT_THAT(options.hosts, testing::ElementsAre(http::Url(kDefaultHost)));
+ EXPECT_TRUE(options.disable_dns_cache);
+ EXPECT_EQ(23, options.max_connections);
+ EXPECT_TRUE(options.exponential_moving_average);
+ EXPECT_EQ(16, options.num_downloads);
+ EXPECT_EQ(5122, options.download_size);
+ EXPECT_EQ(12, options.num_uploads);
+ EXPECT_EQ(7653, options.upload_size);
+ EXPECT_EQ("CrOS", options.user_agent);
+ EXPECT_EQ(7500, options.min_transfer_runtime);
+ EXPECT_EQ(13500, options.max_transfer_runtime);
+ EXPECT_EQ(13, options.min_transfer_intervals);
+ 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(
+ http::Url("foo.speed.googlefiber.net"),
+ http::Url("bar.speed.googlefiber.net")));
}
} // namespace
diff --git a/speedtest/ping_task.cc b/speedtest/ping_task.cc
new file mode 100644
index 0000000..7a1c7be
--- /dev/null
+++ b/speedtest/ping_task.cc
@@ -0,0 +1,115 @@
+/*
+ * 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
new file mode 100644
index 0000000..b2923a8
--- /dev/null
+++ b/speedtest/ping_task.h
@@ -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.
+ */
+
+#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/request.cc b/speedtest/request.cc
index b0a2b89..ef46d2d 100644
--- a/speedtest/request.cc
+++ b/speedtest/request.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -64,17 +64,14 @@
const int kDefaultQueryStringSize = 200;
+const Request::DownloadFn noop = [](void *, size_t) { };
+
} // namespace
-Request::Request(std::shared_ptr<CurlEnv> env)
- : curl_headers_(nullptr),
- env_(env) {
- assert(env_);
- handle_ = curl_easy_init();
- if (!handle_) {
- std::cerr << "Failed to create handle\n";
- std::exit(1);
- }
+Request::Request(std::shared_ptr<CURL> handle, const Url &url)
+ : handle_(handle),
+ curl_headers_(nullptr),
+ url_(url) {
}
Request::~Request() {
@@ -82,35 +79,40 @@
curl_slist_free_all(curl_headers_);
curl_headers_ = nullptr;
}
- curl_easy_cleanup(handle_);
+
+ curl_easy_cleanup(handle_.get());
+}
+
+CURLcode Request::Get() {
+ return Get(noop);
}
CURLcode Request::Get(DownloadFn download_fn) {
CommonSetup();
if (download_fn) {
- curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, &WriteCallback);
- curl_easy_setopt(handle_, CURLOPT_WRITEDATA, &download_fn);
+ curl_easy_setopt(handle_.get(), CURLOPT_WRITEFUNCTION, &WriteCallback);
+ curl_easy_setopt(handle_.get(), CURLOPT_WRITEDATA, &download_fn);
}
return Execute();
}
CURLcode Request::Post(UploadFn upload_fn) {
CommonSetup();
- curl_easy_setopt(handle_, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(handle_, CURLOPT_READFUNCTION, &ReadCallback);
- curl_easy_setopt(handle_, CURLOPT_READDATA, &upload_fn);
+ curl_easy_setopt(handle_.get(), CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(handle_.get(), CURLOPT_READFUNCTION, &ReadCallback);
+ curl_easy_setopt(handle_.get(), CURLOPT_READDATA, &upload_fn);
return Execute();
}
CURLcode Request::Post(const char *data, curl_off_t data_len) {
CommonSetup();
- curl_easy_setopt(handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_len);
- curl_easy_setopt(handle_, CURLOPT_POSTFIELDS, data);
+ curl_easy_setopt(handle_.get(), CURLOPT_POSTFIELDSIZE_LARGE, data_len);
+ curl_easy_setopt(handle_.get(), CURLOPT_POSTFIELDS, data);
return Execute();
}
void Request::Reset() {
- curl_easy_reset(handle_);
+ curl_easy_reset(handle_.get());
clear_progress_fn();
clear_headers();
clear_params();
@@ -156,6 +158,10 @@
params_.clear();
}
+void Request::set_timeout_millis(long millis) {
+ curl_easy_setopt(handle_.get(), CURLOPT_TIMEOUT_MS, millis);
+}
+
void Request::UpdateUrl() {
std::string query_string;
query_string.reserve(kDefaultQueryStringSize);
@@ -165,10 +171,10 @@
if (!query_string.empty()) {
query_string.append("&");
}
- char *name = curl_easy_escape(handle_,
+ char *name = curl_easy_escape(handle_.get(),
iter->first.data(),
iter->first.length());
- char *value = curl_easy_escape(handle_,
+ char *value = curl_easy_escape(handle_.get(),
iter->second.data(),
iter->second.length());
query_string.append(name);
@@ -183,12 +189,14 @@
void Request::CommonSetup() {
UpdateUrl();
std::string request_url = url_.url();
- curl_easy_setopt(handle_, CURLOPT_URL, request_url.c_str());
- curl_easy_setopt(handle_, CURLOPT_USERAGENT, user_agent_.c_str());
+ curl_easy_setopt(handle_.get(), CURLOPT_URL, request_url.c_str());
+ curl_easy_setopt(handle_.get(), CURLOPT_USERAGENT, user_agent_.c_str());
if (progress_fn_) {
- curl_easy_setopt(handle_, CURLOPT_NOPROGRESS, 0);
- curl_easy_setopt(handle_, CURLOPT_XFERINFOFUNCTION, &ProgressCallback);
- curl_easy_setopt(handle_, CURLOPT_XFERINFODATA, &progress_fn_);
+ curl_easy_setopt(handle_.get(), CURLOPT_NOPROGRESS, 0);
+ curl_easy_setopt(handle_.get(),
+ CURLOPT_XFERINFOFUNCTION,
+ &ProgressCallback);
+ curl_easy_setopt(handle_.get(), CURLOPT_XFERINFODATA, &progress_fn_);
}
if (!headers_.empty()) {
struct curl_slist *headers = nullptr;
@@ -200,12 +208,12 @@
header.append(iter->second);
headers = curl_slist_append(headers, header.c_str());
}
- curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(handle_.get(), CURLOPT_HTTPHEADER, headers);
}
}
CURLcode Request::Execute() {
- return curl_easy_perform(handle_);
+ return curl_easy_perform(handle_.get());
}
} // namespace http
diff --git a/speedtest/request.h b/speedtest/request.h
index 837eecc..8588e29 100644
--- a/speedtest/request.h
+++ b/speedtest/request.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -22,8 +22,6 @@
#include <map>
#include <memory>
#include <string>
-
-#include "curl_env.h"
#include "url.h"
namespace http {
@@ -44,10 +42,12 @@
curl_off_t,
curl_off_t,
curl_off_t)>;
+ using Ptr = std::unique_ptr<Request>;
- explicit Request(std::shared_ptr<CurlEnv> env);
+ Request(std::shared_ptr<CURL> handle, const Url &url);
virtual ~Request();
+ CURLcode Get();
CURLcode Get(DownloadFn download_fn);
CURLcode Post(UploadFn upload_fn);
CURLcode Post(const char *data, curl_off_t data_len);
@@ -80,24 +80,25 @@
void set_progress_fn(ProgressFn progress_fn) { progress_fn_ = progress_fn; }
void clear_progress_fn() { progress_fn_ = nullptr; }
+ // Request timeout
+ void set_timeout_millis(long millis);
+
void UpdateUrl();
private:
void CommonSetup();
+
CURLcode Execute();
// owned
- CURL *handle_;
+ std::shared_ptr<CURL> handle_;
struct curl_slist *curl_headers_;
-
- // ref-count CURL global config
- std::shared_ptr<CurlEnv> env_;
Url url_;
std::string user_agent_;
Headers headers_;
QueryStringParams params_;
- ProgressFn progress_fn_; // unowned
+ ProgressFn progress_fn_;
// disable
Request(const Request &) = delete;
diff --git a/speedtest/request_test.cc b/speedtest/request_test.cc
index d8b11e9..ae7b74f 100644
--- a/speedtest/request_test.cc
+++ b/speedtest/request_test.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include "request.h"
+#include <gtest/gtest.h>
#include <memory>
#include "curl_env.h"
@@ -30,8 +29,8 @@
std::unique_ptr<Request> request;
void SetUp() override {
- env = std::make_shared<CurlEnv>();
- request = env->NewRequest();
+ env = CurlEnv::NewCurlEnv({});
+ request = env->NewRequest(http::Url("http://example.com/foo"));
}
void VerifyQueryString(const char *expected,
@@ -92,9 +91,9 @@
}
TEST_F(RequestTest, Url_OneParamTwoValues_Ok) {
- VerifyUrl("http://example.com/?abc=def&abc=ghi",
+ VerifyUrl("http://example.com/?abc=def&abc=def",
"http://example.com",
- {{"abc", "def"}, {"abc", "ghi"}});
+ {{"abc", "def"}, {"abc", "def"}});
}
TEST_F(RequestTest, Url_EscapeParam_Ok) {
diff --git a/speedtest/speedtest.cc b/speedtest/speedtest.cc
index bf8fe3c..ed1263d 100644
--- a/speedtest/speedtest.cc
+++ b/speedtest/speedtest.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -16,241 +16,416 @@
#include "speedtest.h"
-#include <assert.h>
#include <chrono>
#include <cstring>
#include <limits>
#include <random>
#include <thread>
#include <iomanip>
+#include <fstream>
+#include <streambuf>
#include "errors.h"
+#include "timed_runner.h"
+#include "transfer_runner.h"
#include "utils.h"
namespace speedtest {
+namespace {
-Speedtest::Speedtest(const Options &options)
- : options_(options),
- bytes_downloaded_(0),
- bytes_uploaded_(0) {
- assert(!options_.hosts.empty());
- env_ = std::make_shared<http::CurlEnv>();
+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);
- char *data = new char[options_.upload_size];
- for (int i = 0; i < options_.upload_size; ++i) {
- data[i] = uniform_dist(random_engine);
+ 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);
}
- send_data_ = data;
+ return std::move(random_data);
+}
+
+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>());
+}
+
+} // 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() {
- delete[] send_data_;
}
void Speedtest::Run() {
- if (!RunPingTest()) {
+ InitUserAgent();
+ LoadServerList();
+ if (servers_.empty()) {
+ std::cerr << "No servers found in global server list\n";
+ std::exit(1);
+ }
+ FindNearestServer();
+ if (!server_url_) {
std::cout << "No servers responded. Exiting\n";
return;
}
+ std::string json = LoadConfig(*server_url_);
+ if (!ParseConfig(json, &config_)) {
+ std::cout << "Could not parse config\n";
+ return;
+ }
+ if (options_.verbose) {
+ std::cout << "Server config:\n";
+ 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;
+ }
+ }
+ } else {
+ user_agent_ = options_.user_agent;
+ return;
+ }
+ if (options_.verbose) {
+ std::cout << "Setting user agent to " << user_agent_ << "\n";
+ }
+}
+
+void Speedtest::LoadServerList() {
+ servers_.clear();
+ if (!options_.global) {
+ if (options_.verbose) {
+ std::cout << "Explicit server list:\n";
+ for (const auto &url : options_.hosts) {
+ std::cout << " " << url.url() << "\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";
+ }
+ }
+}
+
+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();
+ if (options_.verbose) {
+ std::cout << "There are " << hosts.size() << " ping URLs:\n";
+ for (const auto &host : hosts) {
+ std::cout << " " << host.url() << "\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() {
- end_download_ = false;
- long start_time = SystemTimeMicros();
- bytes_downloaded_ = 0;
- std::thread threads[options_.number];
- for (int i = 0; i < options_.number; ++i) {
- threads[i] = std::thread([=]() {
- RunDownload(i);
- });
+ if (options_.verbose) {
+ std::cout << "Starting download test to " << config_.location_name
+ << " (" << server_url_->url() << ")\n";
}
- std::thread timer([&]{
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.time_millis));
- end_download_ = true;
- });
- timer.join();
- for (auto &thread : threads) {
- thread.join();
+ DownloadTask::Options download_options;
+ download_options.verbose = options_.verbose;
+ download_options.num_transfers = NumDownloads();
+ download_options.download_size = DownloadSize();
+ 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();
+ 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 "
+ << round(speed_variance, 4) << ")\n";
+ };
}
- long end_time = speedtest::SystemTimeMicros();
-
- double running_time = (end_time - start_time) / 1000000.0;
- double megabits = bytes_downloaded_ * 8 / 1000000.0 / running_time;
- std::cout << "Downloaded " << bytes_downloaded_
- << " bytes in " << running_time * 1000 << " ms ("
- << megabits << " Mbps)\n";
-}
-
-void Speedtest::RunDownload(int id) {
- auto download = MakeRequest(id, "/download");
- http::Request::DownloadFn noop = [](void *, size_t) {};
- while (!end_download_) {
- long downloaded = 0;
- download->set_param("i", speedtest::to_string(id));
- download->set_param("size", speedtest::to_string(options_.download_size));
- download->set_param("time", speedtest::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_downloaded_ += dlnow - downloaded;
- downloaded = dlnow;
- }
- return end_download_;
- });
- download->Get(noop);
- download->Reset();
+ 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";
}
void Speedtest::RunUploadTest() {
- end_upload_ = false;
- long start_time = SystemTimeMicros();
- bytes_uploaded_ = 0;
- std::thread threads[options_.number];
- for (int i = 0; i < options_.number; ++i) {
- threads[i] = std::thread([=]() {
- RunUpload(i);
- });
- }
- std::thread timer([&]{
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.time_millis));
- end_upload_ = true;
- });
- timer.join();
- for (auto &thread : threads) {
- thread.join();
- }
- long end_time = speedtest::SystemTimeMicros();
-
- double running_time = (end_time - start_time) / 1000000.0;
- double megabits = bytes_uploaded_ * 8 / 1000000.0 / running_time;
- std::cout << "Uploaded " << bytes_uploaded_
- << " bytes in " << running_time * 1000 << " ms ("
- << megabits << " Mbps)\n";
-}
-
-void Speedtest::RunUpload(int id) {
- auto upload = MakeRequest(id, "/upload");
- while (!end_upload_) {
- long uploaded = 0;
- upload->set_progress_fn([&](curl_off_t,
- curl_off_t,
- curl_off_t,
- curl_off_t ulnow) -> bool {
- if (ulnow > uploaded) {
- bytes_uploaded_ += ulnow - uploaded;
- uploaded = ulnow;
- }
- return end_upload_;
- });
-
- // 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(send_data_, options_.upload_size);
- upload->Reset();
- }
-}
-
-bool Speedtest::RunPingTest() {
- end_ping_ = false;
- size_t num_hosts = options_.hosts.size();
- std::thread threads[num_hosts];
- min_ping_micros_.clear();
- min_ping_micros_.resize(num_hosts);
- for (size_t i = 0; i < num_hosts; ++i) {
- threads[i] = std::thread([=]() {
- RunPing(i);
- });
- }
- std::thread timer([&]{
- std::this_thread::sleep_for(
- std::chrono::milliseconds(options_.time_millis));
- end_ping_ = true;
- });
- timer.join();
- for (auto &thread : threads) {
- thread.join();
- }
if (options_.verbose) {
- std::cout << "Pinged " << num_hosts << " "
- << (num_hosts == 1 ? "host" : "hosts:") << "\n";
+ std::cout << "Starting upload test to " << config_.location_name
+ << " (" << server_url_->url() << ")\n";
}
- size_t min_index = 0;
- for (size_t i = 0; i < num_hosts; ++i) {
- if (options_.verbose) {
- std::cout << " " << options_.hosts[i].url() << ": ";
- if (min_ping_micros_[i] == std::numeric_limits<long>::max()) {
- std::cout << "no packets received";
- } else {
- double ping_ms = min_ping_micros_[i] / 1000.0;
- if (ping_ms < 10) {
- std::cout << std::fixed << std::setprecision(1);
- } else {
- std::cout << std::fixed << std::setprecision(0);
- }
- std::cout << ping_ms << " ms";
- }
- std::cout << "\n";
- }
- if (min_ping_micros_[i] < min_ping_micros_[min_index]) {
- min_index = i;
- }
+ UploadTask::Options upload_options;
+ upload_options.verbose = options_.verbose;
+ upload_options.num_transfers = NumUploads();
+ upload_options.payload = MakeRandomData(UploadSize());
+ upload_options.request_factory = [this](int id) -> http::Request::Ptr{
+ return MakeTransferRequest(id, "/upload");
+ };
+
+ 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();
+ 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 "
+ << round(speed_variance, 4) << ")\n";
+ };
}
- if (min_ping_micros_[min_index] == std::numeric_limits<long>::max()) {
- // no servers respondeded
- return false;
+ 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";
}
- url_ = options_.hosts[min_index];
- std::cout << "Host for Speedtest: " << url_.url() << " (";
- double ping_ms = min_ping_micros_[min_index] / 1000.0;
- if (ping_ms < 10) {
- std::cout << std::fixed << std::setprecision(1);
- } else {
- std::cout << std::fixed << std::setprecision(0);
- }
- std::cout << ping_ms << " ms)\n";
- return true;
+ std::cout << "Upload speed: "
+ << round(runner.GetSpeedInMegabits(), 2) << " Mbps\n";
}
-void Speedtest::RunPing(size_t index) {
- http::Request::DownloadFn noop = [](void *, size_t) {};
- min_ping_micros_[index] = std::numeric_limits<long>::max();
- http::Url url(options_.hosts[index]);
- url.set_path("/ping");
- auto ping = env_->NewRequest();
- ping->set_url(url);
- while (!end_ping_) {
- long req_start = SystemTimeMicros();
- if (ping->Get(noop) == CURLE_OK) {
- long req_end = SystemTimeMicros();
- long ping_time = req_end - req_start;
- min_ping_micros_[index] = std::min(min_ping_micros_[index], ping_time);
- }
- ping->Reset();
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
+int Speedtest::NumDownloads() const {
+ return options_.num_downloads
+ ? options_.num_downloads
+ : config_.num_downloads;
}
-std::unique_ptr<http::Request> Speedtest::MakeRequest(int id,
- const std::string &path) {
- auto request = env_->NewRequest();
- http::Url url(url_);
- int port = (id % 20) + url.port() + 1;
- url.set_port(port);
- url.set_path(path);
- request->set_url(url);
+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_);
+ }
return std::move(request);
}
+http::Request::Ptr Speedtest::MakeBaseRequest(
+ int id, const std::string &path) {
+ http::Url url(*server_url_);
+ url.set_path(path);
+ return MakeRequest(url);
+}
+
+http::Request::Ptr Speedtest::MakeTransferRequest(
+ int id, const std::string &path) {
+ http::Url url(*server_url_);
+ int port_start = config_.transfer_port_start;
+ int port_end = config_.transfer_port_end;
+ int num_ports = port_end - port_start + 1;
+ if (num_ports > 0) {
+ url.set_port(port_start + (id % num_ports));
+ }
+ url.set_path(path);
+ return MakeRequest(url);
+}
+
} // namespace speedtest
diff --git a/speedtest/speedtest.h b/speedtest/speedtest.h
index 01e6f75..fb32355 100644
--- a/speedtest/speedtest.h
+++ b/speedtest/speedtest.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -21,8 +21,12 @@
#include <memory>
#include <string>
+#include "config.h"
#include "curl_env.h"
+#include "download_task.h"
#include "options.h"
+#include "ping_task.h"
+#include "upload_task.h"
#include "url.h"
#include "request.h"
@@ -34,27 +38,40 @@
virtual ~Speedtest();
void Run();
- void RunDownloadTest();
- void RunUploadTest();
- bool RunPingTest();
private:
- void RunDownload(int id);
- void RunUpload(int id);
- void RunPing(size_t host_index);
+ void InitUserAgent();
+ void LoadServerList();
+ void FindNearestServer();
+ std::string LoadConfig(const http::Url &url);
+ void RunPingTest();
+ void RunDownloadTest();
+ void RunUploadTest();
- std::unique_ptr<http::Request> MakeRequest(int id, const std::string &path);
+ 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;
- std::shared_ptr<http::CurlEnv> env_;
+ 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);
+
+ std::shared_ptr <http::CurlEnv> env_;
Options options_;
- http::Url url_;
- std::atomic_bool end_ping_;
- std::atomic_bool end_download_;
- std::atomic_bool end_upload_;
- std::atomic_long bytes_downloaded_;
- std::atomic_long bytes_uploaded_;
- std::vector<long> min_ping_micros_;
- const char *send_data_;
+ 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_;
// disable
Speedtest(const Speedtest &) = delete;
diff --git a/speedtest/speedtest_main.cc b/speedtest/speedtest_main.cc
index 8a9c2c9..d756c4b 100644
--- a/speedtest/speedtest_main.cc
+++ b/speedtest/speedtest_main.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
diff --git a/speedtest/task.cc b/speedtest/task.cc
new file mode 100644
index 0000000..84d12c9
--- /dev/null
+++ b/speedtest/task.cc
@@ -0,0 +1,127 @@
+/*
+ * 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
new file mode 100644
index 0000000..429b078
--- /dev/null
+++ b/speedtest/task.h
@@ -0,0 +1,80 @@
+/*
+ * 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
new file mode 100644
index 0000000..bf7c4cc
--- /dev/null
+++ b/speedtest/timed_runner.cc
@@ -0,0 +1,35 @@
+/*
+ * 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
new file mode 100644
index 0000000..02e673f
--- /dev/null
+++ b/speedtest/timed_runner.h
@@ -0,0 +1,29 @@
+/*
+ * 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_RUNNER_H
+#define SPEEDTEST_RUNNER_H
+
+#include "task.h"
+
+namespace speedtest {
+
+// Run a task for a set duration
+void RunTimed(Task *task, long millis);
+
+} // namespace speedtest
+
+#endif // SPEEDTEST_RUNNER_H
diff --git a/speedtest/transfer_runner.cc b/speedtest/transfer_runner.cc
new file mode 100644
index 0000000..d37f087
--- /dev/null
+++ b/speedtest/transfer_runner.cc
@@ -0,0 +1,171 @@
+/*
+ * 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_runner.h"
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <iostream>
+#include <thread>
+#include "transfer_task.h"
+#include "utils.h"
+
+namespace speedtest {
+namespace {
+
+const int kDefaultIntervalMillis = 200;
+
+} // 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) {
+ return 0.0;
+ }
+ Interval last_interval = GetLastInterval();
+ double percent = 2.0d / (num_intervals + 1);
+ return GetSimpleAverage(1) * percent +
+ last_interval.short_megabits * (1 - percent);
+}
+
+double TransferRunner::GetLongEma(int num_intervals) {
+ if (intervals_.empty() || num_intervals <= 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);
+}
+
+double TransferRunner::GetSimpleAverage(int num_intervals) {
+ if (intervals_.empty() || num_intervals <= 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);
+}
+
+} // namespace
diff --git a/speedtest/transfer_runner.h b/speedtest/transfer_runner.h
new file mode 100644
index 0000000..793c8ec
--- /dev/null
+++ b/speedtest/transfer_runner.h
@@ -0,0 +1,84 @@
+/*
+ * 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_RUNNER_H
+#define SPEEDTEST_TRANSFER_RUNNER_H
+
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <vector>
+#include "task.h"
+#include "transfer_task.h"
+
+namespace speedtest {
+
+struct Interval {
+ long bytes = 0;
+ long running_time = 0;
+ double short_megabits = 0.0;
+ double long_megabits = 0.0;
+};
+
+// 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;
+ };
+
+ explicit TransferRunner(const Options &options);
+
+ double GetSpeedInMegabits() const;
+ Interval GetLastInterval() const;
+
+ protected:
+ void RunInternal() override;
+ void StopInternal() override;
+
+ private:
+ const Interval &AddInterval();
+ double GetSimpleAverage(int num_intervals);
+ double GetShortEma(int num_intervals);
+ double GetLongEma(int num_intervals);
+
+ Options options_;
+
+ mutable std::mutex mutex_;
+ std::vector<Interval> intervals_;
+ std::vector<std::thread> threads_;
+ double speed_;
+
+ // disallowed
+ TransferRunner(const TransferRunner &) = delete;
+ void operator=(const TransferRunner &) = delete;
+};
+
+} // namespace
+
+#endif //SPEEDTEST_TRANSFER_RUNNER_H
diff --git a/speedtest/transfer_task.cc b/speedtest/transfer_task.cc
new file mode 100644
index 0000000..d742d87
--- /dev/null
+++ b/speedtest/transfer_task.cc
@@ -0,0 +1,51 @@
+/*
+ * 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
new file mode 100644
index 0000000..83cff9e
--- /dev/null
+++ b/speedtest/transfer_task.h
@@ -0,0 +1,55 @@
+/*
+ * 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_task.cc b/speedtest/upload_task.cc
new file mode 100644
index 0000000..251fc41
--- /dev/null
+++ b/speedtest/upload_task.cc
@@ -0,0 +1,83 @@
+/*
+ * 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
new file mode 100644
index 0000000..323f904
--- /dev/null
+++ b/speedtest/upload_task.h
@@ -0,0 +1,53 @@
+/*
+ * 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/url.cc b/speedtest/url.cc
index c0934a0..61588a0 100644
--- a/speedtest/url.cc
+++ b/speedtest/url.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -132,6 +132,10 @@
return url1.url() == url2.url();
}
+std::ostream &operator<<(std::ostream &os, const Url &url) {
+ return os << (url.ok() ? url.url() : "{invalid URL}");
+}
+
Url::Url(): parsed_(false), absolute_(false), port_(0) {
}
@@ -150,7 +154,8 @@
fragment_ = other.fragment_;
}
-Url::Url(const char *url): parsed_(false), absolute_(false), port_(0) {
+Url::Url(const std::string &url)
+ : parsed_(false), absolute_(false), port_(0) {
Parse(url);
}
@@ -411,8 +416,9 @@
return false;
}
std::string port(start, iter);
- int portnum = speedtest::stoi(port);
- if (portnum < 1 || portnum > 65535) {
+ int portnum;
+ if (!speedtest::ParseInt(port, &portnum) ||
+ portnum < 1 || portnum > 65535) {
return false;
}
current_ = iter;
diff --git a/speedtest/url.h b/speedtest/url.h
index 6aeb68a..4844916 100644
--- a/speedtest/url.h
+++ b/speedtest/url.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -24,6 +24,7 @@
class Url;
bool operator==(const Url &url1, const Url &url2);
+std::ostream &operator<<(std::ostream &os, const Url &url);
// Partial implementation of a URL parser. This is needed because URLs need
// to be manipulated for creating URLs for Speedtest, which is otherwise
@@ -43,7 +44,7 @@
public:
Url();
Url(const Url &other);
- explicit Url(const char *url);
+ explicit Url(const std::string &url);
Url &operator=(const Url &other);
bool Parse(const std::string &url);
@@ -75,6 +76,7 @@
std::string url() const;
friend bool operator==(const Url &url1, const Url &url2);
+ friend std::ostream &operator<<(std::ostream &os, const Url &url);
private:
using Iter = std::string::const_iterator;
diff --git a/speedtest/url_test.cc b/speedtest/url_test.cc
index 06c2d72..f2945a5 100644
--- a/speedtest/url_test.cc
+++ b/speedtest/url_test.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include "url.h"
+#include <gtest/gtest.h>
+
namespace http {
namespace {
diff --git a/speedtest/utils.cc b/speedtest/utils.cc
index 012e1eb..580b54b 100644
--- a/speedtest/utils.cc
+++ b/speedtest/utils.cc
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -16,9 +16,13 @@
#include "utils.h"
+#include <algorithm>
+#include <cctype>
#include <cstdlib>
+#include <functional>
#include <iostream>
#include <stdexcept>
+#include <stdio.h>
#include <string>
#include <sstream>
@@ -33,24 +37,62 @@
return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}
-std::string to_string(long n)
-{
+std::string to_string(long n) {
std::ostringstream s;
s << n;
return s.str();
}
-int stoi(const std::string& str)
-{
- int rc;
- std::istringstream n(str);
+std::string round(double d, int digits) {
+ char buf[20];
+ sprintf(buf, "%.*f", digits, d);
+ return buf;
+}
- if (!(n >> rc)) {
- std::cerr << "Not a number: " << str;
- std::exit(1);
+double variance(double d1, double d2) {
+ if (d2 == 0) {
+ return 0.0;
}
+ double smaller = std::min(d1, d2);
+ double larger = std::max(d1, d2);
+ return 1.0 - smaller / larger;
+}
- return rc;
+double ToMegabits(long bytes, long micros) {
+ return (8.0d * bytes) / micros;
+}
+
+bool ParseInt(const std::string &str, int *result) {
+ if (!result) {
+ return false;
+ }
+ std::istringstream n(str);
+ return n >> *result;
+}
+
+// Trim from start in place
+// Caller retains ownership
+void LeftTrim(std::string *s) {
+ s->erase(s->begin(),
+ std::find_if(s->begin(),
+ s->end(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))));
+}
+
+// Trim from end in place
+// Caller retains ownership
+void RightTrim(std::string *s) {
+ s->erase(std::find_if(s->rbegin(),
+ s->rend(),
+ std::not1(std::ptr_fun<int, int>(std::isspace))).base(),
+ s->end());
+}
+
+// Trim from both ends in place
+// Caller retains ownership
+void Trim(std::string *s) {
+ LeftTrim(s);
+ RightTrim(s);
}
} // namespace speedtest
diff --git a/speedtest/utils.h b/speedtest/utils.h
index 7ad4289..7e8d251 100644
--- a/speedtest/utils.h
+++ b/speedtest/utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Google Inc. All rights reserved.
+ * 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.
@@ -28,8 +28,31 @@
// Return a string representation of n
std::string to_string(long n);
-// return an integer value from the string str.
-int stoi(const std::string& str);
+// Round a double to a minimum number of significant digits
+std::string round(double d, int digits);
+
+// Return 1 - (shorter / larger)
+double variance(double d1, double d2);
+
+// Convert bytes and time in micros to speed in megabits
+double ToMegabits(long bytes, 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.
+bool ParseInt(const std::string &str, int *result);
+
+// Trim from start in place
+// Caller retains ownership
+void LeftTrim(std::string *s);
+
+// Trim from end in place
+// Caller retains ownership
+void RightTrim(std::string *s);
+
+// Trim from both ends in place
+// Caller retains ownership
+void Trim(std::string *s);
} // namespace speedtst
diff --git a/taxonomy/ethernet.py b/taxonomy/ethernet.py
index d0aaf45..e746f13 100644
--- a/taxonomy/ethernet.py
+++ b/taxonomy/ethernet.py
@@ -37,6 +37,8 @@
'5c:ff:35': ['asus'],
'74:d0:2b': ['asus'],
'ac:22:0b': ['asus'],
+ 'bc:ee:7b': ['asus'],
+ 'd8:50:e6': ['asus'],
'30:8c:fb': ['dropcam'],
@@ -49,6 +51,7 @@
# These are registered to AzureWave, but used for Chromecast v1.
'6c:ad:f8': ['azurewave', 'google'],
+ 'b0:ee:45': ['azurewave', 'google'],
'd0:e7:82': ['azurewave', 'google'],
'00:23:76': ['htc'],
@@ -69,6 +72,7 @@
'0c:48:85': ['lg'],
'10:68:3f': ['lg'],
'2c:54:cf': ['lg'],
+ '34:fc:ef': ['lg'],
'40:b0:fa': ['lg'],
'58:3f:54': ['lg'],
'64:89:9a': ['lg'],
@@ -91,6 +95,7 @@
'1c:56:fe': ['motorola'],
'24:da:9b': ['motorola'],
'3c:43:8e': ['motorola'],
+ '40:78:6a': ['motorola'],
'44:80:eb': ['motorola'],
'5c:51:88': ['motorola'],
'60:be:b5': ['motorola'],
@@ -100,26 +105,29 @@
'98:4b:4a': ['motorola'],
'9c:d9:17': ['motorola'],
'cc:c3:ea': ['motorola'],
+ 'ec:88:92': ['motorola'],
'e8:91:20': ['motorola'],
'f8:7b:7a': ['motorola'],
'f8:cf:c5': ['motorola'],
'f8:e0:79': ['motorola'],
'f8:f1:b6': ['motorola'],
- '00:26:e8': ['murata'],
- '10:a5:d0': ['murata'],
- '14:7d:c5': ['murata'],
- '1c:99:4c': ['murata'],
- '20:02:af': ['murata'],
- '40:f3:08': ['murata'],
- '44:a7:cf': ['murata'],
- '5c:da:d4': ['murata'],
- '78:4b:87': ['murata'],
- '90:b6:86': ['murata'],
- '98:f1:70': ['murata'],
- 'f0:27:65': ['murata'],
- 'fc:c2:de': ['murata'],
- 'fc:db:b3': ['murata'],
+ '00:26:e8': ['murata', 'samsung'],
+ '00:ae:fa': ['murata', 'samsung'],
+ '10:a5:d0': ['murata', 'samsung'],
+ '14:7d:c5': ['murata', 'samsung'],
+ '1c:99:4c': ['murata', 'samsung'],
+ '20:02:af': ['murata', 'samsung'],
+ '40:f3:08': ['murata', 'samsung'],
+ '44:a7:cf': ['murata', 'samsung'],
+ '5c:da:d4': ['murata', 'samsung'],
+ '5c:f8:a1': ['murata', 'samsung'],
+ '78:4b:87': ['murata', 'samsung'],
+ '90:b6:86': ['murata', 'samsung'],
+ '98:f1:70': ['murata', 'samsung'],
+ 'f0:27:65': ['murata', 'samsung'],
+ 'fc:c2:de': ['murata', 'samsung'],
+ 'fc:db:b3': ['murata', 'samsung'],
'18:b4:30': ['nest'],
@@ -137,12 +145,14 @@
'3c:8b:fe': ['samsung'],
'40:0e:85': ['samsung'],
'48:5a:3f': ['samsung', 'wisol'],
+ '54:88:0e': ['samsung'],
'5c:0a:5b': ['samsung'],
'5c:f6:dc': ['samsung'],
'6c:2f:2c': ['samsung'],
'6c:83:36': ['samsung'],
'78:d6:f0': ['samsung'],
'80:65:6d': ['samsung'],
+ '84:11:9e': ['samsung'],
'84:25:db': ['samsung'],
'84:38:38': ['samsung'],
'88:32:9b': ['samsung'],
@@ -155,6 +165,7 @@
'b0:df:3a': ['samsung'],
'b0:ec:71': ['samsung'],
'b4:07:f9': ['samsung'],
+ 'bc:20:a4': ['samsung'],
'c0:bd:d1': ['samsung'],
'c4:42:02': ['samsung'],
'cc:07:ab': ['samsung'],
diff --git a/taxonomy/pcaptest.py b/taxonomy/pcaptest.py
index ffc7161..bb76e15 100644
--- a/taxonomy/pcaptest.py
+++ b/taxonomy/pcaptest.py
@@ -38,6 +38,10 @@
('Unknown', './testdata/pcaps/MediaTek MT7610U 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Motorola Droid 2 2.4GHz.pcap'),
('Unknown', './testdata/pcaps/Motorola Droid 3 2.4GHz.pcap'),
+ ('Unknown', './testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Broadcast Probe.pcap'),
+ ('Unknown', './testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Specific Probe.pcap'),
+ ('Unknown', './testdata/pcaps/Motorola Droid Razr 2.4GHz XT910.pcap'),
+ ('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'),
diff --git a/taxonomy/ssdp.py b/taxonomy/ssdp.py
index f16b3ce..f359b1b 100644
--- a/taxonomy/ssdp.py
+++ b/taxonomy/ssdp.py
@@ -21,8 +21,11 @@
database = {
+ 'Canon IJ-UPnP/1.0 UPnP/1.0 UPnP-Device-Host/1.0': 'Canon Printer',
'OpenRG/6.0.7.1.4 UPnP/1.0': 'Google Fiber GFRG1x0',
'HDHomeRun/1.0 UPnP/1.0': 'HDHomeRun',
+ 'Linux UPnP/1.0 Sonos/31.9-26010 (ZPS1)': 'Sonos ZPS1',
+ 'Linux UPnP/1.0 Sonos/31.9-26010 (ZPS5)': 'Somos ZPS5',
'Linux UPnP/1.0 Sonos/28.1-83040 (ZP90)': 'Sonos ZP90',
'Linux UPnP/1.0 Sonos/28.1-83040 (ZP120)': 'Sonos ZP120',
'WNDR3700v2 UPnP/1.0 miniupnpd/1.0': 'Netgear WNDR3700',
diff --git a/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Broadcast Probe.pcap b/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Broadcast Probe.pcap
new file mode 100644
index 0000000..07b4d99
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Broadcast Probe.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Specific Probe.pcap b/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Specific Probe.pcap
new file mode 100644
index 0000000..7cd4d3b
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910 Specific Probe.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910.pcap b/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910.pcap
new file mode 100644
index 0000000..77cb52a
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Motorola Droid Razr 2.4GHz XT910.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Motorola Droid Razr 5GHz XT910.pcap b/taxonomy/testdata/pcaps/Motorola Droid Razr 5GHz XT910.pcap
new file mode 100644
index 0000000..d52dcda
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Motorola Droid Razr 5GHz XT910.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Nexus Player 2.4GHz.pcap b/taxonomy/testdata/pcaps/Nexus Player 2.4GHz.pcap
new file mode 100644
index 0000000..429dc07
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus Player 2.4GHz.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Nexus Player 5GHz.pcap b/taxonomy/testdata/pcaps/Nexus Player 5GHz.pcap
new file mode 100644
index 0000000..aaff1e4
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Nexus Player 5GHz.pcap
Binary files differ
diff --git a/taxonomy/tests/wifi_test.py b/taxonomy/tests/wifi_test.py
index 8fae201..c16824d 100755
--- a/taxonomy/tests/wifi_test.py
+++ b/taxonomy/tests/wifi_test.py
@@ -48,16 +48,18 @@
self.assertEqual('802.11n n:2,w:40', taxonomy[2])
def testNameLookup(self):
- signature = ('wifi|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,'
- '221(001018,2),221(0050f2,2)')
+ signature = ('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')
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual(3, len(taxonomy))
self.assertEqual('Unknown', taxonomy[1])
taxonomy = wifi.identify_wifi_device(signature, '00:00:01:00:00:01')
self.assertEqual(3, len(taxonomy))
self.assertEqual('Unknown', taxonomy[1])
- taxonomy = wifi.identify_wifi_device(signature, '2c:1f:23:ff:ff:01')
- self.assertEqual('iPod Touch 3rd gen', taxonomy[1])
+ taxonomy = wifi.identify_wifi_device(signature, 'c8:69:cd:5e:b5:43')
+ self.assertEqual('Apple TV (3rd gen)', taxonomy[1])
def testChecksumWhenNoIdentification(self):
taxonomy = wifi.identify_wifi_device('wifi|probe:1,2,3,4,htcap:0|assoc:1',
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index c8e224f..c46191a 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -59,7 +59,9 @@
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:180c,htagg:1b,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:180c,htagg:1b,htmcs:000000ff,txpow:1308|os:ios':
('BCM4329', 'Apple TV (2nd gen)', '2.4GHz'),
- 'wifi|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0100|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100|name:Apple-TV':
+ '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|name:appletv':
+ ('BCM4330', 'Apple TV (3rd gen)', '2.4GHz'),
+ '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,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100,htagg:19,htmcs:000000ff,txpow:180f|name:appletv':
('BCM4330', 'Apple TV (3rd 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:1907|os:ios':
@@ -76,7 +78,7 @@
'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'),
- 'wifi|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':
+ '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,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':
@@ -109,12 +111,11 @@
('Marvell_88W8887', 'Chromecast v2', '5GHz'),
'wifi4|probe:0,1,45,50,127,191,htcap:0062,htagg:03,htmcs:00000000,vhtcap:33c07030,vhtrxmcs:0124fffc,vhttxmcs:0124fffc,extcap:0000000000000040|assoc:0,1,33,36,48,127,221(0050f2,2),45,191,htcap:006e,htagg:03,htmcs:000000ff,vhtcap:33c07030,vhtrxmcs:0186fffe,vhttxmcs:0186fffe,extcap:0400000000000140|oui:google':
('Marvell_88W8887', 'Chromecast v2', '5GHz'),
+ 'wifi4|probe:0,1,45,50,127,191,htcap:0062,htagg:03,htmcs:00000000,vhtcap:33c07030,vhtrxmcs:0124fffc,vhttxmcs:0124fffc,extcap:0000000000000040|assoc:0,1,33,36,48,127,221(0050f2,2),45,191,htcap:006e,htagg:03,htmcs:000000ff,vhtcap:33c07030,vhtrxmcs:0186fffe,vhttxmcs:0186fffe,txpow:1308,extcap:0400000000000140|oui:google':
+ ('Marvell_88W8887', 'Chromecast v2', '5GHz'),
'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,45,221(001018,2),221(00904c,51),htcap:007c|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:007c':
- ('', 'DirecTV HR-44', ''),
-
'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':
('', 'Dropcam', '2.4GHz'),
@@ -173,7 +174,9 @@
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:1800,htagg:1b,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1800,htagg:1b,htmcs:000000ff,txpow:1108|os:ios':
('BCM4329', 'iPad (2nd gen)', '2.4GHz'),
- 'wifi|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0100|assoc:0,1,33,36,48,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100|os:ios':
+ '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,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0100,htagg:19,htmcs:000000ff,txpow:180f|os:ios':
+ ('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':
('BCM4330', 'iPad (3rd gen)', '2.4GHz'),
@@ -278,6 +281,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:0000ffff,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: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: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':
@@ -296,7 +301,7 @@
'wifi4|probe:0,1,3,50|assoc:0,1,48,50|os:ipodtouch1':
('Marvell_W8686B22', 'iPod Touch 1st/2nd gen', '2.4GHz'),
- 'wifi|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2),221(0050f2,2)|name:ipod':
+ 'wifi4|probe:0,1,50,221(001018,2)|assoc:0,1,48,50,221(001018,2),221(0050f2,2)|name:ipod':
('BCM4329', 'iPod Touch 3rd gen', '2.4GHz'),
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:180c,htagg:1b,htmcs:000000ff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:180c,htagg:1b,htmcs:000000ff|os:ios':
@@ -304,6 +309,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,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':
('BCM4334', 'iPod Touch 5th gen', '2.4GHz'),
@@ -330,17 +337,12 @@
'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':
('', 'LG Tribute', '2.4GHz'),
- 'wifi|probe:0,1,50,45,221(00904c,51),htcap:182c|assoc:0,1,33,36,48,50,45,221(00904c,51),221(0050f2,2),htcap:182c|os:macos':
- ('BCM4322', 'MacBook late 2008 (A1278)', '5GHz'),
- 'wifi|probe:0,1,50,3,45,221(00904c,51),htcap:182c|assoc:0,1,33,36,48,50,45,221(00904c,51),221(0050f2,2),htcap:182c|os:macos':
- ('BCM4322', 'MacBook late 2008 (A1278)', '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':
('BCM43224', 'MacBook Air late 2010 (A1369)', '5GHz'),
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:187c,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:187c,htagg:1b,htmcs:0000ffff,txpow:1207|os:macos':
('BCM43224', 'MacBook Air late 2010 (A1369)', '2.4GHz'),
- 'wifi|probe:0,1,45,221(00904c,51),htcap:086e,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(00904c,51),221(0050f2,2),htcap:086e,htagg:1b,htmcs:0000ffff|os:macos':
+ 'wifi4|probe:0,1,45,221(00904c,51),htcap:086e,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(00904c,51),221(0050f2,2),htcap:086e,htagg:1b,htmcs:0000ffff,txpow:0f07|os:macos':
('BCM4322', 'MacBook Air late 2011', '5GHz'),
'wifi4|probe:0,1,45,221(00904c,51),htcap:09ef,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,48,45,221(00904c,51),221(0050f2,2),htcap:09ef,htagg:1b,htmcs:0000ffff,txpow:0005|os:macos':
@@ -432,6 +434,8 @@
('QCA6174', 'Nexus 5X', '5GHz'),
'wifi4|probe:0,1,45,221(0050f2,8),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:000000000000004080|assoc:0,1,48,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:000000000000004080|oui:lg':
('QCA6174', 'Nexus 5X', '5GHz'),
+ 'wifi4|probe:0,1,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a020100004080|assoc:0,1,48,45,221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:000000000000004080|oui:lg':
+ ('QCA6174', 'Nexus 5X', '5GHz'),
'wifi4|probe:0,1,50,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2,vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a0201000040|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,extcap:0000000000000000|oui:lg':
('QCA6174', 'Nexus 5X', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,221(0050f2,8),127,htcap:01ad,htagg:03,htmcs:0000ffff,extcap:000000000000000080|assoc:0,1,50,48,45,221(0050f2,2),127,htcap:01ad,htagg:03,htmcs:0000ffff,extcap:000000000000000080|oui:lg':
@@ -443,11 +447,17 @@
('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,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':
('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,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':
('BCM4356', 'Nexus 6', '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_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,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_6P|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:e002':
+ ('BCM4358', 'Nexus 6P', '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_6P|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:0000088001400040':
('BCM4358', 'Nexus 6P', '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_6P|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':
@@ -468,6 +478,14 @@
('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
'wifi4|probe:0,1,45,221(0050f2,8),127,221(0050f2,4),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,extcap:00000a02,wps:Nexus_7|assoc:0,1,48,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,extcap:00000a02':
('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
+ 'wifi4|probe:0,1,45,221(0050f2,8),127,221(0050f2,4),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,extcap:00000a02,wps:Nexus_7|assoc:0,1,33,36,48,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:1e0d,extcap:00000a02':
+ ('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
+ 'wifi4|probe:0,1,45,221(0050f2,8),127,221(0050f2,4),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,extcap:00000a02,wps:Nexus_7|assoc:0,1,48,45,221(0050f2,2),127,htcap:012c,htagg:03,htmcs:000000ff,extcap:00000a02':
+ ('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
+ 'wifi4|probe:0,1,45,221(0050f2,8),127,221(0050f2,4),221(506f9a,10),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,extcap:00000a02,wps:Nexus_7|assoc:0,1,33,36,48,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:1e0d,extcap:00000a02':
+ ('QCA_WCN3660', 'Nexus 7 (2013)', '5GHz'),
+ 'wifi4|probe:0,1,45,221(0050f2,8),221(0050f2,4),221(506f9a,9),htcap:016e,htagg:03,htmcs:000000ff,wps:Nexus_7|assoc:0,1,33,36,48,45,221(0050f2,2),127,htcap:016e,htagg:03,htmcs:000000ff,txpow:1e0d,extcap:00000a02':
+ ('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':
@@ -479,19 +497,17 @@
('BCM4354', 'Nexus 9', '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_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(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,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'),
- '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:murata':
- ('', 'Nexus 10', '5GHz'),
'wifi4|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:01bc,htagg:1b,htmcs:0000ffff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01bc,htagg:1b,htmcs:0000ffff|oui:samsung':
('', 'Nexus 10', '2.4GHz'),
- 'wifi4|probe:0,1,50,3,45,221(001018,2),221(00904c,51),htcap:01bc,htagg:1b,htmcs:0000ffff|assoc:0,1,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01bc,htagg:1b,htmcs:0000ffff|oui:murata':
- ('', 'Nexus 10', '2.4GHz'),
- 'wifi|probe:0,1,45,127,191,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:006f,vhtcap:0f815832,wps:Nexus_Player|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,vhtcap:0f815832':
+ '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'),
- 'wifi|probe:0,1,50,3,45,127,221(0050f2,4),221(506f9a,9),221(001018,2),htcap:002d,wps:Nexus_Player|assoc:0,1,50,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:002d':
+ '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'),
'wifi4|probe:0,1,50,45,51,127,htcap:012c,htagg:1b,htmcs:000000ff,extcap:0100000000000040|assoc:0,1,48,50,221(0050f2,2),45,51,127,htcap:012c,htagg:1b,htmcs:000000ff,extcap:0100000000000040|os:windows-phone':
@@ -513,7 +529,7 @@
'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'),
- 'wifi|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:00000000|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:00000000|os:roku':
+ 'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:19bc,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
('BCM43236', 'Roku 3', '2.4GHz'),
'wifi4|probe:0,1,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':
@@ -523,8 +539,6 @@
'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'),
- '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:murata':
- ('', 'Samsung Galaxy Mini', '2.4GHz'),
'wifi4|probe:0,1,45,3,221(0050f2,4),221(001018,2),221(00904c,51),htcap:010c,htagg:19,htmcs:000000ff,wps:Galaxy_Nexus|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:010c,htagg:19,htmcs:000000ff,txpow:0f09':
('BCM4330', 'Samsung Galaxy Nexus', '5GHz'),
@@ -532,236 +546,130 @@
('BCM4330', 'Samsung Galaxy Nexus', '5GHz'),
'wifi4|probe:0,1,45,3,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:0f0a|oui:samsung':
('BCM4330', 'Samsung Galaxy Nexus', '5GHz'),
- 'wifi4|probe:0,1,45,3,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:0f0a|oui:murata':
- ('BCM4330', 'Samsung Galaxy Nexus', '5GHz'),
'wifi4|probe:0,1,50,45,221(0050f2,4),221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff,wps:Galaxy_Nexus|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff,txpow:1209':
('BCM4330', 'Samsung Galaxy Nexus', '2.4GHz'),
'wifi4|probe:0,1,50,45,3,221(0050f2,4),221(001018,2),221(00904c,51),htcap:110c,htagg:19,htmcs:000000ff,wps:Galaxy_Nexus|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:110c,htagg:19,htmcs:000000ff,txpow:1209':
('BCM4330', 'Samsung Galaxy Nexus', '2.4GHz'),
'wifi4|probe:0,1,50,45,221(0050f2,4),221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff,wps:_|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:120a|oui:samsung':
('BCM4330', 'Samsung Galaxy Nexus', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,221(0050f2,4),221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff,wps:_|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:120a|oui:murata':
- ('BCM4330', 'Samsung Galaxy Nexus', '2.4GHz'),
'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0f09|oui:samsung':
('', 'Samsung Galaxy Note or S2+', '5GHz'),
- 'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0f09|oui:murata':
- ('', 'Samsung Galaxy Note or S2+', '5GHz'),
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:1409|oui:samsung':
('', 'Samsung Galaxy Note', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:1409|oui:murata':
- ('', 'Samsung Galaxy Note', '2.4GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:samsung':
('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
- 'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:murata':
- ('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:samsung':
('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
- 'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:murata':
- ('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:samsung':
('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
- 'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:murata':
- ('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:samsung':
('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
- 'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:0e09|oui:murata':
- ('BCM4330', 'Samsung Galaxy Note 2', '5GHz'),
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:1020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1020,htagg:1a,htmcs:000000ff,txpow:1209|oui:samsung':
('BCM4330', 'Samsung Galaxy Note 2', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:1020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1020,htagg:1a,htmcs:000000ff,txpow:1209|oui:murata':
- ('BCM4330', 'Samsung Galaxy Note 2', '2.4GHz'),
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:1020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1020,htagg:1a,htmcs:000000ff,txpow:1209|oui:samsung':
('BCM4330', 'Samsung Galaxy Note 2', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:1020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1020,htagg:1a,htmcs:000000ff,txpow:1209|oui:murata':
- ('BCM4330', 'Samsung Galaxy Note 2', '2.4GHz'),
'wifi4|probe:0,1,3,45,127,191,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:0000080000000040|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:0000000000000040|oui:samsung':
('BCM4335', 'Samsung Galaxy Note 3', '5GHz'),
- 'wifi4|probe:0,1,3,45,127,191,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:0000080000000040|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:0000000000000040|oui:murata':
- ('BCM4335', 'Samsung Galaxy Note 3', '5GHz'),
'wifi4|probe:0,1,45,127,191,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:0000080000000040|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:0000000000000040|oui:samsung':
('BCM4335', 'Samsung Galaxy Note 3', '5GHz'),
- 'wifi4|probe:0,1,45,127,191,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:0000080000000040|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:0000000000000040|oui:murata':
- ('BCM4335', 'Samsung Galaxy Note 3', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000080000000040|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1208|oui:samsung':
('BCM4335', 'Samsung Galaxy Note 3', '2.4GHz'),
- 'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000080000000040|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1208|oui:murata':
- ('BCM4335', 'Samsung Galaxy Note 3', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000080000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1208|oui:samsung':
('BCM4335', 'Samsung Galaxy Note 3', '2.4GHz'),
- 'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:112d,htagg:17,htmcs:000000ff,extcap:0000080000000040|assoc:0,1,33,36,48,50,45,127,221(001018,2),221(0050f2,2),htcap:112d,htagg:17,htmcs:000000ff,txpow:1208|oui:murata':
- ('BCM4335', 'Samsung Galaxy Note 3', '2.4GHz'),
'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:e009,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy Note 4', '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: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:e009,extcap:0000088001400040|oui:murata':
- ('BCM4358', 'Samsung Galaxy Note 4', '5GHz'),
'wifi4|probe:0,1,45,127,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,70,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:e009,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy Note 4', '5GHz'),
- 'wifi4|probe:0,1,45,127,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,70,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:e009,extcap:0000088001400040|oui:murata':
- ('BCM4358', 'Samsung Galaxy Note 4', '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:0000088001400040|assoc:0,1,50,33,36,48,45,127,107,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1509,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy Note 4', '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:1509,extcap:0000088001400040|oui:murata':
- ('BCM4358', 'Samsung Galaxy Note 4', '2.4GHz'),
'wifi4|probe:0,1,50,3,45,127,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,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1509,extcap:0000088001400040|oui:samsung':
('BCM4358', 'Samsung Galaxy Note 4', '2.4GHz'),
- 'wifi4|probe:0,1,50,3,45,127,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,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1509,extcap:0000088001400040|oui:murata':
- ('BCM4358', 'Samsung Galaxy Note 4', '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: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,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:murata':
- ('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'),
- '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:murata':
- ('BCM4359', 'Samsung Galaxy Note 5', '2.4GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0f0a|oui:samsung':
('', 'Samsung Galaxy S2', '5GHz'),
- 'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0f0a|oui:murata':
- ('', 'Samsung Galaxy S2', '5GHz'),
'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0f0a|oui:samsung':
('', 'Samsung Galaxy S2', '5GHz'),
- 'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:000c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:000c,htagg:19,htmcs:000000ff,txpow:0f0a|oui:murata':
- ('', 'Samsung Galaxy S2', '5GHz'),
'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:120a|oui:samsung':
('', 'Samsung Galaxy S2 or Infuse','2.4GHz'),
- 'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:120a|oui:murata':
- ('', 'Samsung Galaxy S2 or Infuse','2.4GHz'),
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:120a|oui:samsung':
('', 'Samsung Galaxy S2 or Infuse','2.4GHz'),
- 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:120a|oui:murata':
- ('', 'Samsung Galaxy S2 or Infuse','2.4GHz'),
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:1209|oui:samsung':
('', 'Samsung Galaxy S2+', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:100c,htagg:19,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:1209|oui:murata':
- ('', 'Samsung Galaxy S2+', '2.4GHz'),
'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1409|oui:samsung':
('BCM4334', 'Samsung Galaxy S3', '5GHz'),
- 'wifi4|probe:0,1,45,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1409|oui:murata':
- ('BCM4334', 'Samsung Galaxy S3', '5GHz'),
'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1409|oui:samsung':
('BCM4334', 'Samsung Galaxy S3', '5GHz'),
- 'wifi4|probe:0,1,45,3,221(001018,2),221(00904c,51),htcap:0062,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:0062,htagg:1a,htmcs:000000ff,txpow:1409|oui:murata':
- ('BCM4334', 'Samsung Galaxy S3', '5GHz'),
'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:1020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1020,htagg:1a,htmcs:000000ff,txpow:1409|oui:samsung':
('BCM4334', 'Samsung Galaxy S3', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,3,221(001018,2),221(00904c,51),htcap:1020,htagg:1a,htmcs:000000ff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:1020,htagg:1a,htmcs:000000ff,txpow:1409|oui:murata':
- ('BCM4334', 'Samsung Galaxy S3', '2.4GHz'),
'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,extcap:0000080000000040|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,txpow:e001,extcap:0000000000000040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '5GHz'),
- '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,extcap:0000080000000040|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,txpow:e001,extcap:0000000000000040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '5GHz'),
'wifi4|probe:0,1,3,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,extcap:0000080000000040|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,txpow:e001,extcap:0000000000000040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '5GHz'),
- 'wifi4|probe:0,1,3,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,extcap:0000080000000040|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,txpow:e001,extcap:0000000000000040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '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:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088000400040|assoc:0,1,33,36,48,45,127,107,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e001,extcap:0000008000400040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '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:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088000400040|assoc:0,1,33,36,48,45,127,107,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e001,extcap:0000008000400040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '5GHz'),
'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:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088000400040|assoc:0,1,33,36,48,45,127,107,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e001,extcap:0000008000400040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '5GHz'),
- '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:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088000400040|assoc:0,1,33,36,48,45,127,107,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f805832,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e001,extcap:0000008000400040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '5GHz'),
'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,extcap:0000080000400040|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,txpow:e001,extcap:0000000000400040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '5GHz'),
- '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,extcap:0000080000400040|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,txpow:e001,extcap:0000000000400040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '5GHz'),
'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:102d,htagg:17,htmcs:000000ff,extcap:0000088000400040|assoc:0,1,33,36,48,50,45,127,107,221(001018,2),221(0050f2,2),htcap:102d,htagg:17,htmcs:000000ff,txpow:1201,extcap:000000800040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '2.4GHz'),
- 'wifi4|probe:0,1,50,3,45,127,107,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:102d,htagg:17,htmcs:000000ff,extcap:0000088000400040|assoc:0,1,33,36,48,50,45,127,107,221(001018,2),221(0050f2,2),htcap:102d,htagg:17,htmcs:000000ff,txpow:1201,extcap:000000800040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '2.4GHz'),
'wifi4|probe:0,1,50,45,127,221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:102d,htagg:17,htmcs:000000ff,extcap:0000080000000040|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:102d,htagg:17,htmcs:000000ff,txpow:1201|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,127,221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:102d,htagg:17,htmcs:000000ff,extcap:0000080000000040|assoc:0,1,33,36,48,50,45,221(001018,2),221(0050f2,2),htcap:102d,htagg:17,htmcs:000000ff,txpow:1201|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '2.4GHz'),
'wifi4|probe:0,1,50,45,127,107,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:102d,htagg:17,htmcs:000000ff,extcap:0000088000400040|assoc:0,1,33,36,48,50,45,127,107,221(001018,2),221(0050f2,2),htcap:102d,htagg:17,htmcs:000000ff,txpow:1201,extcap:000000800040|oui:samsung':
('BCM4335', 'Samsung Galaxy S4', '2.4GHz'),
- 'wifi4|probe:0,1,50,45,127,107,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:102d,htagg:17,htmcs:000000ff,extcap:0000088000400040|assoc:0,1,33,36,48,50,45,127,107,221(001018,2),221(0050f2,2),htcap:102d,htagg:17,htmcs:000000ff,txpow:1201,extcap:000000800040|oui:murata':
- ('BCM4335', 'Samsung Galaxy S4', '2.4GHz'),
'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: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:murata':
- ('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: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:murata':
- ('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: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:murata':
- ('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,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:murata':
- ('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,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:murata':
- ('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,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:0000ffff,txpow:1402,extcap:0000088001400040|oui:murata':
- ('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,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:murata':
- ('BCM4358', 'Samsung Galaxy S6', '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,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:murata':
- ('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':
('BCM4329', 'Samsung Galaxy Tab', '2.4GHz'),
- '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:murata':
- ('BCM4329', 'Samsung Galaxy Tab', '2.4GHz'),
'wifi4|probe:0,1,45,50,htcap:0162,htagg:03,htmcs:00000000|assoc:0,1,48,127,221(0050f2,2),45,htcap:016e,htagg:03,htmcs:000000ff,extcap:0400000000000140|oui:samsung':
('Marvell_88W8787', 'Samsung Galaxy Tab 3', '5GHz'),
- 'wifi4|probe:0,1,45,50,htcap:0162,htagg:03,htmcs:00000000|assoc:0,1,48,127,221(0050f2,2),45,htcap:016e,htagg:03,htmcs:000000ff,extcap:0400000000000140|oui:murata':
- ('Marvell_88W8787', 'Samsung Galaxy Tab 3', '5GHz'),
'wifi4|probe:0,1,45,50,htcap:0162,htagg:03,htmcs:00000000|assoc:0,1,33,36,48,127,221(0050f2,2),45,htcap:016e,htagg:03,htmcs:000000ff,txpow:1208,extcap:0400000000000140|oui:samsung':
('Marvell_88W8787', 'Samsung Galaxy Tab 3', '5GHz'),
- 'wifi4|probe:0,1,45,50,htcap:0162,htagg:03,htmcs:00000000|assoc:0,1,33,36,48,127,221(0050f2,2),45,htcap:016e,htagg:03,htmcs:000000ff,txpow:1208,extcap:0400000000000140|oui:murata':
- ('Marvell_88W8787', 'Samsung Galaxy Tab 3', '5GHz'),
'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'),
- '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:murata':
- ('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':
('APQ8026', 'Samsung Galaxy Tab 4', '5GHz'),
- '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:murata':
- ('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':
('APQ8026', 'Samsung Galaxy Tab 4', '2.4GHz'),
- '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:murata':
- ('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':
('BCM4330', 'Samsung Galaxy Tab 10.1', '5GHz'),
- '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:murata':
- ('BCM4330', 'Samsung Galaxy Tab 10.1', '5GHz'),
'wifi4|probe:0,1,45,3,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':
('BCM4330', 'Samsung Galaxy Tab 10.1', '5GHz'),
- 'wifi4|probe:0,1,45,3,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:murata':
- ('BCM4330', 'Samsung Galaxy Tab 10.1', '5GHz'),
'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,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:0f0a|oui:samsung':
('BCM4330', 'Samsung Galaxy Tab 10.1', '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,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:100c,htagg:19,htmcs:000000ff,txpow:0f0a|oui:murata':
- ('BCM4330', 'Samsung Galaxy Tab 10.1', '2.4GHz'),
'wifi4|probe:0,1,45,htcap:11ee,htagg:02,htmcs:0000ffff|assoc:0,1,45,127,33,36,48,221(0050f2,2),htcap:11ee,htagg:02,htmcs:0000ffff,txpow:1100,extcap:01|os:samsungtv':
('', 'Samsung Smart TV', '5GHz'),
diff --git a/wifi/iw.py b/wifi/iw.py
index b1d49b5..db6590f 100644
--- a/wifi/iw.py
+++ b/wifi/iw.py
@@ -46,11 +46,15 @@
def _info(interface, **kwargs):
- return subprocess.check_output(('iw', interface, 'info'), **kwargs)
+ return subprocess.check_output(('iw', 'dev', interface, 'info'), **kwargs)
def _link(interface, **kwargs):
- return subprocess.check_output(('iw', interface, 'link'), **kwargs)
+ return subprocess.check_output(('iw', 'dev', interface, 'link'), **kwargs)
+
+
+def _scan(interface, **kwargs):
+ return subprocess.check_output(('iw', 'dev', interface, 'scan'), **kwargs)
_WIPHY_RE = re.compile(r'Wiphy (?P<phy>\S+)')
@@ -356,3 +360,8 @@
result.add(band)
return result
+
+
+def scan(interface):
+ """Return 'iw scan' output for printing."""
+ return _scan(interface)
diff --git a/wifi/wifi.py b/wifi/wifi.py
index f57e09e..c4fde30 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -30,8 +30,9 @@
{bin} stopclient Disable wifi clients. Takes -b, -P, -S.
{bin} restore Restore saved client and access point options. Takes -b, -S.
{bin} show Print all known parameters. Takes -b, -S.
+{bin} scan Print 'iw scan' results for a single band. Takes -b, -S.
--
-b,band= Wifi band(s) to use (5 GHz and/or 2.4 GHz). set commands have a default of 2.4 and cannot take multiple-band values. [2.4 5]
+b,band= Wifi band(s) to use (5 GHz and/or 2.4 GHz). set, setclient, and scan have a default of 2.4 and cannot take multiple-band values. [2.4 5]
c,channel= Channel to use [auto]
a,autotype= Autochannel method to use (LOW, HIGH, DFS, NONDFS, ANY,OVERLAP) [NONDFS]
s,ssid= SSID to use [{ssid}]
@@ -491,6 +492,30 @@
return True
+@iw.requires_iw
+def scan_wifi(opt):
+ """Prints 'iw scan' results.
+
+ Args:
+ opt: The OptDict parsed from command line options.
+
+ Returns:
+ True.
+
+ Raises:
+ BinWifiException: If an expected interface is not found.
+ """
+ band = opt.band.split()[0]
+ interface = iw.find_interface_from_band(
+ band, iw.INTERFACE_TYPE.ap, opt.interface_suffix)
+ if interface is None:
+ raise utils.BinWifiException('No client interface for band %s', band)
+
+ print(iw.scan(interface))
+
+ return True
+
+
def _is_hostapd_running(interface):
return utils.subprocess_quiet(
('hostapd_cli', '-i', interface, 'status'), no_stdout=True) == 0
@@ -974,6 +999,7 @@
'setclient': set_client_wifi,
'stopclient': stop_client_wifi,
'stopap': stop_ap_wifi,
+ 'scan': scan_wifi,
}[extra[0]]
except KeyError:
parser.fatal('Unrecognized command %s' % extra[0])