servers=new ArrayList<>();
+ private TestPoint selectedServer=null;
+ private SpeedtestConfig config=new SpeedtestConfig();
+ private TelemetryConfig telemetryConfig=new TelemetryConfig();
+ private int state=0; //0=configs, 1=test points, 2=server selection, 3=ready, 4=testing, 5=finished
+
+ private final Object mutex=new Object();
+
+ private String originalExtra="";
+
+ /**
+ * Initialize an instance of a {@link Speedtest} with the default configuration and no servers.
+ *
+ * Configuration can be added to this object by using {@link #setSpeedtestConfig(SpeedtestConfig)} and {@link #setTelemetryConfig(TelemetryConfig)}
+ *
+ * Servers can be added with {@link #addTestPoint(TestPoint)}, {@link #addTestPoints(TestPoint[])}, {@link #addTestPoint(JSONObject)}
+ * or {@link #addTestPoints(JSONArray)} and a server can be selected for the test with {@link #selectServer(ServerSelectedHandler)} or
+ * {@link #setSelectedServer(TestPoint)}
+ */
+ public Speedtest(){
+
+ }
+
+ /**
+ * Set the configuration for this speed test
+ * @param speedtestConfig the configuration to set
+ */
+ public void setSpeedtestConfig(SpeedtestConfig speedtestConfig){
+ synchronized (mutex){
+ if(state!=0) throw new IllegalStateException("Cannot change config at this moment");
+ config=speedtestConfig.clone();
+ String extra=config.getTelemetry_extra();
+ if(extra!=null&&!extra.isEmpty()) originalExtra=extra;
+ }
+ }
+
+ /**
+ * Set a telemetry configuration for this speed test
+ * @param telemetryConfig the configuration to set
+ */
+ public void setTelemetryConfig(TelemetryConfig telemetryConfig){
+ synchronized (mutex) {
+ if (state != 0) throw new IllegalStateException("Cannot change config at this moment");
+ this.telemetryConfig = telemetryConfig.clone();
+ }
+ }
+
+ /**
+ * Add one server to this speed test.
+ *
+ * Note that the server is not automatically selected for the speed test and that either {@link Speedtest#selectServer(ServerSelectedHandler)}
+ * or {@link Speedtest#setSelectedServer(TestPoint)} still needs to be called
+ *
+ * @param testPoint The server to add
+ */
+ public void addTestPoint(TestPoint testPoint){
+ synchronized (mutex) {
+ if (state == 0) state = 1;
+ if (state > 1) throw new IllegalStateException("Cannot add test points at this moment");
+ servers.add(testPoint);
+ }
+ }
+
+ /**
+ * Add servers to this speed test
+ *
+ * Note that the server is not automatically selected for the speed test and that either {@link Speedtest#selectServer(ServerSelectedHandler)}
+ * or {@link Speedtest#setSelectedServer(TestPoint)} still needs to be called
+ *
+ * @param testPoints The servers to add
+ */
+ public void addTestPoints(TestPoint[] testPoints){
+ synchronized (mutex) {
+ for (TestPoint t : testPoints) addTestPoint(t);
+ }
+ }
+
+ /**
+ * Add one server to this speed test.
+ *
+ * The server is given as a JSON object. This methods parses the JSON object and adds the corresponding server to the list of available
+ * servers
+ *
+ * Note that the server is not automatically selected for the speed test and that either {@link Speedtest#selectServer(ServerSelectedHandler)}
+ * or {@link Speedtest#setSelectedServer(TestPoint)} still needs to be called
+ *
+ * @param jsonTestPoint The server to add
+ */
+ public void addTestPoint(JSONObject jsonTestPoint){
+ synchronized (mutex) {
+ addTestPoint(new TestPoint(jsonTestPoint));
+ }
+ }
+
+ /**
+ * Add servers to this speed test.
+ *
+ * The servers are given as a JSON array. This methods parses the JSON array and adds the corresponding servers to the list of available
+ * servers
+ *
+ * Note that the server is not automatically selected for the speed test and that either {@link Speedtest#selectServer(ServerSelectedHandler)}
+ * or {@link Speedtest#setSelectedServer(TestPoint)} still needs to be called
+ *
+ * @param jsonTestPoints The servers to add
+ */
+ public void addTestPoints(JSONArray jsonTestPoints){
+ synchronized (mutex) {
+ for (int i = 0; i < jsonTestPoints.length(); i++)
+ try {
+ addTestPoint(jsonTestPoints.getJSONObject(i));
+ } catch (JSONException t) {
+ }
+ }
+ }
+
+ /**
+ * Get the servers available for this speed test
+ *
+ * @return The available servers
+ */
+ public TestPoint[] getTestPoints(){
+ synchronized (mutex) {
+ return servers.toArray(new TestPoint[0]);
+ }
+ }
+
+ private ServerSelector ss=null;
+
+ /**
+ * Automatically search for the closest server (by ping) and select it for use in the speed test.
+ *
+ * @param callback A handler defining the action to perform when a server has been selected
+ */
+ public void selectServer(final ServerSelectedHandler callback){
+ synchronized (mutex) {
+ if (state == 0) throw new IllegalStateException("No test points added");
+ if (state == 2) throw new IllegalStateException("Server selection is in progress");
+ if (state > 2) throw new IllegalStateException("Server already selected");
+ state = 2;
+ ss = new ServerSelector(getTestPoints(), config.getPing_connectTimeout()) {
+ @Override
+ void onServerSelected(TestPoint server) {
+ selectedServer = server;
+ synchronized (mutex) {
+ if (server != null) state = 3; else state = 1;
+ }
+ callback.onServerSelected(server);
+ }
+ };
+ ss.start();
+ }
+ }
+
+ /**
+ * Manually set the server to user for the speed test
+ *
+ * @param testPoint The server to use
+ */
+ public void setSelectedServer(TestPoint testPoint){
+ synchronized (mutex) {
+ if (state == 2) throw new IllegalStateException("Server selection is in progress");
+ if (testPoint == null) throw new IllegalArgumentException("t is null");
+ selectedServer = testPoint;
+ state = 3;
+ }
+ }
+
+ private SpeedtestWorker st=null;
+
+ /**
+ * Start the speed test.
+ *
+ * @param callback The handler defining the actions to perform when the speed test makes progress
+ */
+ public void start(final SpeedtestHandler callback){
+ synchronized (mutex) {
+ if (state < 3) throw new IllegalStateException("Server hasn't been selected yet");
+ if (state == 4) throw new IllegalStateException("Test already running");
+ state = 4;
+ try {
+ JSONObject extra = new JSONObject();
+ if (originalExtra != null && !originalExtra.isEmpty())
+ extra.put("extra", originalExtra);
+ extra.put("server", selectedServer.getName());
+ config.setTelemetry_extra(extra.toString());
+ } catch (Throwable t) {
+ }
+ st = new SpeedtestWorker(selectedServer, config, telemetryConfig) {
+ @Override
+ void onDownloadUpdate(double dl, double progress) {
+ callback.onDownloadUpdate(dl, progress);
+ }
+
+ @Override
+ void onUploadUpdate(double ul, double progress) {
+ callback.onUploadUpdate(ul, progress);
+ }
+
+ @Override
+ void onPingJitterUpdate(double ping, double jitter, double progress) {
+ callback.onPingJitterUpdate(ping, jitter, progress);
+ }
+
+ @Override
+ void onIPInfoUpdate(String ipInfo) {
+ callback.onIPInfoUpdate(ipInfo);
+ }
+
+ @Override
+ void onTestIDReceived(String id) {
+ String shareURL=prepareShareURL(telemetryConfig);
+ if(shareURL!=null) shareURL=String.format(shareURL,id);
+ callback.onTestIDReceived(id,shareURL);
+ }
+
+ @Override
+ void onEnd() {
+ synchronized (mutex) {
+ state = 5;
+ }
+ callback.onEnd();
+ }
+
+ @Override
+ void onCriticalFailure(String err) {
+ synchronized (mutex) {
+ state = 5;
+ }
+ callback.onCriticalFailure(err);
+ }
+ };
+ }
+ }
+
+ private String prepareShareURL(TelemetryConfig c){
+ if(c==null) return null;
+ String server=c.getServer(), shareURL=c.getShareURL();
+ if(server==null||server.isEmpty()||shareURL==null||shareURL.isEmpty()) return null;
+ if(!server.endsWith("/")) server=server+"/";
+ while(shareURL.startsWith("/")) shareURL=shareURL.substring(1);
+ if(server.startsWith("//")) server="https:"+server;
+ return server+shareURL;
+ }
+
+ /**
+ * Stop the test whenever possible.
+ *
+ * Note that some tasks might take some time to finish after calling this method.
+ */
+ public void abort(){
+ synchronized (mutex) {
+ if (state == 2) ss.stopASAP();
+ if (state == 4) st.abort();
+ state = 5;
+ }
+ }
+
+ /**
+ * A handler to define action to perform when a server has been selected
+ */
+ public static abstract class ServerSelectedHandler{
+
+ /**
+ * Called when a server has been selected
+ *
+ * @param server The selected server
+ */
+ public abstract void onServerSelected(TestPoint server);
+ }
+
+ /**
+ * A handler to define actions to perform when the speed test makes progress.
+ */
+ public static abstract class SpeedtestHandler{
+ /**
+ * Called when download measurement is actualized.
+ *
+ * @param speed The download speed in Mbps
+ * @param progress The current progress of the download test between 0 and 1 where 0 means that the test hasn't started yet and 1 means that the test is finished
+ */
+ public abstract void onDownloadUpdate(double speed, double progress);
+
+ /**
+ * Called when upload measurement is actualized.
+ *
+ * @param speed The upload speed in Mbps
+ * @param progress The current progress of the upload test between 0 and 1 where 0 means that the test hasn't started yet and 1 means that the test is finished
+ */
+ public abstract void onUploadUpdate(double speed, double progress);
+
+ /**
+ * Called when ping measurement is actualized.
+ *
+ * @param ping The lowest ping measured in milliseconds
+ * @param jitter The ping measurements' jitter in milliseconds
+ * @param progress The current progress of the ping test between 0 and 1 where 0 means that the test hasn't started yet and 1 means that the test is finished
+ */
+ public abstract void onPingJitterUpdate(double ping, double jitter, double progress);
+
+ /**
+ * Called when the client's IP and information about this IP is updated.
+ *
+ * @param ipInfo the IP address and its info
+ */
+ public abstract void onIPInfoUpdate(String ipInfo);
+
+ /**
+ * Called when the test result id has been received from the server.
+ *
+ * @param id The id of the result
+ * @param shareURL The url to the image representing the result
+ */
+ public abstract void onTestIDReceived(String id, String shareURL);
+
+
+ /**
+ * Called when the test finishes.
+ */
+ public abstract void onEnd();
+
+ /**
+ * Called if an error occurs during the test
+ *
+ * @param err Information about the error
+ */
+ public abstract void onCriticalFailure(String err);
+ }
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/SpeedtestWorker.java b/src/main/java/com/fdossena/speedtest/core/SpeedtestWorker.java
new file mode 100644
index 0000000..83c7028
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/SpeedtestWorker.java
@@ -0,0 +1,284 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core;
+
+import com.fdossena.speedtest.core.config.SpeedtestConfig;
+import com.fdossena.speedtest.core.config.TelemetryConfig;
+import org.json.JSONObject;
+
+import java.util.Locale;
+
+abstract class SpeedtestWorker extends Thread{
+ private TestPoint backend;
+ private SpeedtestConfig config;
+ private TelemetryConfig telemetryConfig;
+ private boolean stopASAP=false;
+ private double dl=-1, ul=-1, ping=-1, jitter=-1;
+ private String ipIsp="";
+ private Logger log=new Logger();
+
+ SpeedtestWorker(TestPoint backend, SpeedtestConfig config, TelemetryConfig telemetryConfig){
+ this.backend=backend;
+ this.config=config==null?new SpeedtestConfig():config;
+ this.telemetryConfig=telemetryConfig==null?new TelemetryConfig():telemetryConfig;
+ start();
+ }
+
+ public void run(){
+ log.l("Test started");
+ try {
+ for (char t : config.getTest_order().toCharArray()) {
+ if(stopASAP) break;
+ if (t == '_') Utils.sleep(1000);
+ if (t == 'I') getIP();
+ if (t == 'D') dlTest();
+ if (t == 'U') ulTest();
+ if (t == 'P') pingTest();
+ }
+ }catch (Throwable t){
+ onCriticalFailure(t.toString());
+ }
+ try{
+ sendTelemetry();
+ }catch (Throwable t){}
+ onEnd();
+ }
+
+ private boolean getIPCalled=false;
+ private void getIP(){
+ if(getIPCalled) return; else getIPCalled=true;
+ final long start=System.currentTimeMillis();
+ Connection c = null;
+ try {
+ c = new Connection(backend.getServer(), config.getPing_connectTimeout(), config.getPing_soTimeout(), -1, -1);
+ } catch (Throwable t) {
+ if (config.getErrorHandlingMode().equals(SpeedtestConfig.ONERROR_FAIL)){
+ abort();
+ onCriticalFailure(t.toString());
+ }
+ return;
+ }
+ GetIP g = new GetIP(c, backend.getGetIpURL(), config.getGetIP_isp(), config.getGetIP_distance()) {
+ @Override
+ void onDataReceived(String data) {
+ ipIsp=data;
+ try{
+ data=new JSONObject(data).getString("processedString");
+ }catch (Throwable t){}
+ log.l("GetIP: "+ data+ " (took "+(System.currentTimeMillis()-start)+"ms)");
+ onIPInfoUpdate(data);
+ }
+
+ @Override
+ void onError(String err) {
+ log.l("GetIP: FAILED (took "+(System.currentTimeMillis()-start)+"ms)");
+ abort();
+ onCriticalFailure(err);
+ }
+ };
+ while (g.isAlive()) Utils.sleep(0, 100);
+ }
+
+ private boolean dlCalled=false;
+ private void dlTest(){
+ if(dlCalled) return; else dlCalled=true;
+ final long start=System.currentTimeMillis();
+ onDownloadUpdate(0,0);
+ DownloadStream[] streams=new DownloadStream[config.getDl_parallelStreams()];
+ for(int i=0;i=config.getDl_graceTime()*1000){
+ graceTimeDone=true;
+ for(DownloadStream d:streams) d.resetDownloadCounter();
+ startT=System.currentTimeMillis();
+ continue;
+ }
+ if(stopASAP||t+bonusT>=config.getTime_dl_max()*1000){
+ for(DownloadStream d:streams) d.stopASAP();
+ for(DownloadStream d:streams) d.join();
+ break;
+ }
+ if(graceTimeDone) {
+ long totDownloaded = 0;
+ for (DownloadStream d : streams) totDownloaded += d.getTotalDownloaded();
+ double speed = totDownloaded / ((t<100?100:t) / 1000.0);
+ if (config.getTime_auto()) {
+ double b = (2.5 * speed) / 100000.0;
+ bonusT += b > 200 ? 200 : b;
+ }
+ double progress = (t + bonusT) / (double) (config.getTime_dl_max() * 1000);
+ speed = (speed * 8 * config.getOverheadCompensationFactor()) / (config.getUseMebibits() ? 1048576.0 : 1000000.0);
+ dl = speed;
+ onDownloadUpdate(dl, progress>1?1:progress);
+ }
+ Utils.sleep(100);
+ }
+ if(stopASAP) return;
+ log.l("Download: "+ dl+ " (took "+(System.currentTimeMillis()-start)+"ms)");
+ onDownloadUpdate(dl,1);
+ }
+
+ private boolean ulCalled=false;
+ private void ulTest(){
+ if(ulCalled) return; else ulCalled=true;
+ final long start=System.currentTimeMillis();
+ onUploadUpdate(0,0);
+ UploadStream[] streams=new UploadStream[config.getUl_parallelStreams()];
+ for(int i=0;i=config.getUl_graceTime()*1000){
+ graceTimeDone=true;
+ for(UploadStream u:streams) u.resetUploadCounter();
+ startT=System.currentTimeMillis();
+ continue;
+ }
+ if(stopASAP||t+bonusT>=config.getTime_ul_max()*1000){
+ for(UploadStream u:streams) u.stopASAP();
+ for(UploadStream u:streams) u.join();
+ break;
+ }
+ if(graceTimeDone) {
+ long totUploaded = 0;
+ for (UploadStream u : streams) totUploaded += u.getTotalUploaded();
+ double speed = totUploaded / ((t<100?100:t) / 1000.0);
+ if (config.getTime_auto()) {
+ double b = (2.5 * speed) / 100000.0;
+ bonusT += b > 200 ? 200 : b;
+ }
+ double progress = (t + bonusT) / (double) (config.getTime_ul_max() * 1000);
+ speed = (speed * 8 * config.getOverheadCompensationFactor()) / (config.getUseMebibits() ? 1048576.0 : 1000000.0);
+ ul = speed;
+ onUploadUpdate(ul, progress>1?1:progress);
+ }
+ Utils.sleep(100);
+ }
+ if(stopASAP) return;
+ log.l("Upload: "+ ul+ " (took "+(System.currentTimeMillis()-start)+"ms)");
+ onUploadUpdate(ul,1);
+ }
+
+ private boolean pingCalled=false;
+ private void pingTest(){
+ if(pingCalled) return; else pingCalled=true;
+ final long start=System.currentTimeMillis();
+ onPingJitterUpdate(0,0,0);
+ PingStream ps=new PingStream(backend.getServer(),backend.getPingURL(),config.getCount_ping(),config.getErrorHandlingMode(),config.getPing_connectTimeout(),config.getPing_soTimeout(),config.getPing_recvBuffer(),config.getPing_sendBuffer(),log) {
+ private double minPing=Double.MAX_VALUE, prevPing=-1;
+ private int counter=0;
+ @Override
+ void onError(String err) {
+ log.l("Ping: FAILED (took "+(System.currentTimeMillis()-start)+"ms)");
+ abort();
+ onCriticalFailure(err);
+ }
+
+ @Override
+ boolean onPong(long ns) {
+ counter++;
+ double ms = ns / 1000000.0;
+ if (ms < minPing) minPing = ms;
+ ping = minPing;
+ if (prevPing == -1) {
+ jitter=0;
+ }else {
+ double j = Math.abs(ms - prevPing);
+ jitter=j>jitter?(jitter*0.3+j*0.7):(jitter*0.8+j*0.2);
+ }
+ prevPing = ms;
+ double progress = counter / (double) config.getCount_ping();
+ onPingJitterUpdate(ping, jitter, progress>1?1:progress);
+ return !stopASAP;
+ }
+
+ @Override
+ void onDone() {
+ }
+ };
+ ps.join();
+ if(stopASAP) return;
+ log.l("Ping: "+ ping+" "+jitter+ " (took "+(System.currentTimeMillis()-start)+"ms)");
+ onPingJitterUpdate(ping,jitter,1);
+ }
+
+ private void sendTelemetry(){
+ if(telemetryConfig.getTelemetryLevel().equals(TelemetryConfig.LEVEL_DISABLED)) return;
+ if(stopASAP&&telemetryConfig.getTelemetryLevel().equals(TelemetryConfig.LEVEL_BASIC)) return;
+ try{
+ Connection c=new Connection(telemetryConfig.getServer(),-1,-1,-1,-1);
+ Telemetry t=new Telemetry(c,telemetryConfig.getPath(),telemetryConfig.getTelemetryLevel(),ipIsp,config.getTelemetry_extra(),dl==-1?"":String.format(Locale.ENGLISH,"%.2f",dl),ul==-1?"":String.format(Locale.ENGLISH,"%.2f",ul),ping==-1?"":String.format(Locale.ENGLISH,"%.2f",ping),jitter==-1?"":String.format(Locale.ENGLISH,"%.2f",jitter),log.getLog()) {
+ @Override
+ void onDataReceived(String data) {
+ if(data.startsWith("id")){
+ onTestIDReceived(data.split(" ")[1]);
+ }
+ }
+
+ @Override
+ void onError(String err) {
+ System.err.println("Telemetry error: "+err);
+ }
+ };
+ t.join();
+ }catch (Throwable t){
+ System.err.println("Failed to send telemetry: "+t.toString());
+ t.printStackTrace(System.err);
+ }
+ }
+
+ void abort(){
+ if(stopASAP) return;
+ log.l("Manually aborted");
+ stopASAP=true;
+ }
+
+ abstract void onDownloadUpdate(double dl, double progress);
+ abstract void onUploadUpdate(double ul, double progress);
+ abstract void onPingJitterUpdate(double ping, double jitter, double progress);
+ abstract void onIPInfoUpdate(String ipInfo);
+ abstract void onTestIDReceived(String id);
+ abstract void onEnd();
+
+ abstract void onCriticalFailure(String err);
+
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/Telemetry.java b/src/main/java/com/fdossena/speedtest/core/Telemetry.java
new file mode 100644
index 0000000..1ea72e3
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/Telemetry.java
@@ -0,0 +1,89 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core;
+
+import com.fdossena.speedtest.core.config.TelemetryConfig;
+
+import java.io.PrintStream;
+import java.util.HashMap;
+
+abstract class Telemetry extends Thread{
+ private Connection c;
+ private String path;
+ private String level, ispinfo, extra, dl, ul, ping, jitter, log;
+
+ Telemetry(Connection c, String path, String level, String ispinfo, String extra, String dl, String ul, String ping, String jitter, String log){
+ if(level.equals(TelemetryConfig.LEVEL_DISABLED)){
+ onDataReceived(null);
+ return;
+ }
+ this.c=c;
+ this.path=path;
+ this.level=level;
+ this.ispinfo=ispinfo;
+ this.extra=extra;
+ this.dl=dl;
+ this.ul=ul;
+ this.ping=ping;
+ this.jitter=jitter;
+ this.log=log;
+ start();
+ }
+
+ public void run(){
+ try{
+ String s=path;
+ StringBuilder sb=new StringBuilder();
+ sb.append("ispinfo=");
+ sb.append(Utils.urlEncode(ispinfo));
+ sb.append("&dl=");
+ sb.append(Utils.urlEncode(dl));
+ sb.append("&ul=");
+ sb.append(Utils.urlEncode(ul));
+ sb.append("&ping=");
+ sb.append(Utils.urlEncode(ping));
+ sb.append("&jitter=");
+ sb.append(Utils.urlEncode(jitter));
+ if(level.equals(TelemetryConfig.LEVEL_FULL)) {
+ sb.append("&log=");
+ sb.append(Utils.urlEncode(log));
+ }
+ sb.append("&extra=");
+ sb.append(Utils.urlEncode(extra));
+ c.POST(s,false, "application/x-www-form-urlencoded",sb.length());
+ PrintStream ps=c.getPrintStream();
+ ps.print(sb.toString());
+ ps.flush();
+ HashMap h=c.parseResponseHeaders();
+ String data="";
+ String transferEncoding=h.get("transfer-encoding");
+ if(transferEncoding!=null&&transferEncoding.equalsIgnoreCase("chunked")){
+ c.readLineUnbuffered();
+ }
+ data=c.readLineUnbuffered();
+ onDataReceived(data);
+ c.close();
+ }catch(Throwable t){
+ try{c.close();}catch(Throwable t1){}
+ onError(t.toString());
+ }
+ }
+
+ abstract void onDataReceived(String data);
+ abstract void onError(String err);
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/TestPoint.java b/src/main/java/com/fdossena/speedtest/core/TestPoint.java
new file mode 100644
index 0000000..da6f968
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/TestPoint.java
@@ -0,0 +1,101 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * The representation of a LibreSpeed server
+ *
+ * @see LibreSpeed server
+ */
+public class TestPoint {
+ private final String name, server, dlURL, ulURL, pingURL, getIpURL;
+ protected float ping=-1;
+
+ /**
+ * Create a new instance of {@link TestPoint}.
+ *
+ * @param name A user friendly name for the server
+ * @param server The server's root URL (e.g. https://speedtest.example.com)
+ * @param dlURL The path to the download address (e.g. backend/garbage.php)
+ * @param ulURL The path to the upload address (e.g. backend/empty.php)
+ * @param pingURL The path to the ping address (e.g. backend/empty.php)
+ * @param getIpURL The path to the address to get the IP info (e.g. backend/getIP.php)
+ */
+ public TestPoint(String name, String server, String dlURL, String ulURL, String pingURL, String getIpURL){
+ this.name=name;
+ this.server=server;
+ this.dlURL=dlURL;
+ this.ulURL=ulURL;
+ this.pingURL=pingURL;
+ this.getIpURL=getIpURL;
+ }
+
+ /**
+ * Create a new instance of {@link TestPoint} from a JSON object.
+ * @param json the json object to use for this object's creation
+ */
+ public TestPoint(JSONObject json){
+ try {
+ name = json.getString("name");
+ if (name == null) throw new IllegalArgumentException("Missing name field");
+ server = json.getString("server");
+ if (server == null) throw new IllegalArgumentException("Missing server field");
+ dlURL = json.getString("dlURL");
+ if (dlURL == null) throw new IllegalArgumentException("Missing dlURL field");
+ ulURL = json.getString("ulURL");
+ if (ulURL == null) throw new IllegalArgumentException("Missing ulURL field");
+ pingURL = json.getString("pingURL");
+ if (pingURL == null) throw new IllegalArgumentException("Missing pingURL field");
+ getIpURL = json.getString("getIpURL");
+ if (getIpURL == null) throw new IllegalArgumentException("Missing getIpURL field");
+ }catch (JSONException t){
+ throw new IllegalArgumentException("Invalid JSON");
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public String getDlURL() {
+ return dlURL;
+ }
+
+ public String getUlURL() {
+ return ulURL;
+ }
+
+ public String getPingURL() {
+ return pingURL;
+ }
+
+ public String getGetIpURL() {
+ return getIpURL;
+ }
+
+ public float getPing() {
+ return ping;
+ }
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/UploadStream.java b/src/main/java/com/fdossena/speedtest/core/UploadStream.java
new file mode 100644
index 0000000..b978176
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/UploadStream.java
@@ -0,0 +1,119 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core;
+
+import com.fdossena.speedtest.core.config.SpeedtestConfig;
+
+abstract class UploadStream {
+ private String server, path;
+ private int ckSize;
+ private int connectTimeout, soTimeout, recvBuffer, sendBuffer;
+ private Connection c=null;
+ private Uploader uploader;
+ private String errorHandlingMode;
+ private long currentUploaded=0, previouslyUploaded=0;
+ private boolean stopASAP=false;
+ private Logger log;
+
+ UploadStream(String server, String path, int ckSize, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log){
+ this.server=server;
+ this.path=path;
+ this.ckSize=ckSize;
+ this.errorHandlingMode=errorHandlingMode;
+ this.connectTimeout=connectTimeout;
+ this.soTimeout=soTimeout;
+ this.recvBuffer=recvBuffer;
+ this.sendBuffer=sendBuffer;
+ this.log=log;
+ init();
+ }
+
+ private void init(){
+ if(stopASAP) return;
+ new Thread(){
+ public void run(){
+ if(c!=null){
+ try{c.close();}catch (Throwable t){}
+ }
+ if(uploader !=null) uploader.stopASAP();
+ currentUploaded=0;
+ try {
+ c = new Connection(server, connectTimeout, soTimeout, recvBuffer, sendBuffer);
+ if(stopASAP){
+ try{c.close();}catch (Throwable t){}
+ return;
+ }
+ uploader =new Uploader(c,path,ckSize) {
+ @Override
+ void onProgress(long uploaded) {
+ currentUploaded=uploaded;
+ }
+
+ @Override
+ void onError(String err) {
+ log("An uploader died");
+ if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_FAIL)){
+ UploadStream.this.onError(err);
+ return;
+ }
+ if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
+ previouslyUploaded+=currentUploaded;
+ Utils.sleep(100);
+ init();
+ }
+ }
+ };
+ }catch (Throwable t){
+ log("An uploader failed hard");
+ try{c.close();}catch (Throwable t1){}
+ if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
+ Utils.sleep(100);
+ init();
+ }else onError(t.toString());
+ }
+ }
+ }.start();
+ }
+
+ abstract void onError(String err);
+
+ void stopASAP(){
+ stopASAP=true;
+ if(uploader !=null) uploader.stopASAP();
+ }
+
+ long getTotalUploaded(){
+ return previouslyUploaded+currentUploaded;
+ }
+
+ void resetUploadCounter(){
+ previouslyUploaded=0;
+ currentUploaded=0;
+ if(uploader !=null) uploader.resetUploadCounter();
+ }
+
+ void join(){
+ while(uploader==null) Utils.sleep(0,100);
+ try{uploader.join();}catch (Throwable t){}
+ }
+
+ private void log(String s){
+ if(log!=null) log.l(s);
+ }
+
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/Uploader.java b/src/main/java/com/fdossena/speedtest/core/Uploader.java
new file mode 100644
index 0000000..d61776e
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/Uploader.java
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core;
+
+import java.io.OutputStream;
+import java.util.Random;
+
+abstract class Uploader extends Thread{
+ private Connection c;
+ private String path;
+ private boolean stopASAP=false, resetASAP=false;
+ private long totUploaded=0;
+ private byte[] garbage;
+
+ Uploader(Connection c, String path, int ckSize){
+ this.c=c;
+ this.path=path;
+ garbage=new byte[ckSize*1048576];
+ Random r=new Random(System.nanoTime());
+ r.nextBytes(garbage);
+ start();
+ }
+
+ private static final int BUFFER_SIZE=16384;
+ public void run(){
+ try{
+ String s=path;
+ long lastProgressEvent=System.currentTimeMillis();
+ OutputStream out=c.getOutputStream();
+ byte[] buf=new byte[BUFFER_SIZE];
+ for(;;){
+ if(stopASAP) break;
+ c.POST(s,true,"application/octet-stream",garbage.length);
+ for(int offset=0;offset=garbage.length)?(garbage.length-offset):BUFFER_SIZE;
+ out.write(garbage,offset,l);
+ if(stopASAP) break;
+ if(resetASAP){
+ totUploaded=0;
+ resetASAP=false;
+ }
+ totUploaded+=l;
+ if(System.currentTimeMillis()-lastProgressEvent>200){
+ lastProgressEvent=System.currentTimeMillis();
+ onProgress(totUploaded);
+ }
+ }
+ if(stopASAP) break;
+ while(!c.readLineUnbuffered().trim().isEmpty());
+ }
+ c.close();
+ }catch(Throwable t){
+ try{c.close();}catch(Throwable t1){}
+ onError(t.toString());
+ }
+ }
+
+ void stopASAP(){
+ this.stopASAP=true;
+ }
+
+ abstract void onProgress(long uploaded);
+ abstract void onError(String err);
+
+ void resetUploadCounter(){
+ resetASAP=true;
+ }
+
+ long getUploaded() {
+ return resetASAP?0:totUploaded;
+ }
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/Utils.java b/src/main/java/com/fdossena/speedtest/core/Utils.java
new file mode 100644
index 0000000..68b791d
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/Utils.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core;
+
+import java.net.URLEncoder;
+
+class Utils {
+ static String urlEncode(String s){
+ try{return URLEncoder.encode(s, "utf-8");}catch(Throwable t){return null;}
+ }
+ static void sleep(long ms){
+ try{Thread.sleep(ms);}catch (Throwable t){}
+ }
+ static void sleep(long ms, int ns){
+ try{Thread.sleep(ms,ns);}catch (Throwable t){}
+ }
+ static String url_sep(String url){
+ if(url.contains("?")) return "&"; else return "?";
+ }
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java b/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java
new file mode 100644
index 0000000..3183ba7
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java
@@ -0,0 +1,464 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core.config;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class SpeedtestConfig {
+ private int dl_ckSize=100, ul_ckSize=20;
+ private int dl_parallelStreams=3, ul_parallelStreams=3;
+ private int dl_streamDelay=300, ul_streamDelay=300;
+ private double dl_graceTime=1.5, ul_graceTime=1.5;
+ private int dl_connectTimeout=5000, dl_soTimeout=10000, ul_connectTimeout=5000, ul_soTimeout=10000, ping_connectTimeout=2000, ping_soTimeout=5000;
+ private int dl_recvBuffer=-1, dl_sendBuffer=-1, ul_recvBuffer=-1, ul_sendBuffer=16384, ping_recvBuffer=-1, ping_sendBuffer=-1;
+ private String errorHandlingMode=ONERROR_ATTEMPT_RESTART;
+ public static final String ONERROR_FAIL="fail", ONERROR_ATTEMPT_RESTART="attempt-restart", ONERROR_MUST_RESTART="must-restart";
+ private int time_dl_max=15, time_ul_max=15;
+ private boolean time_auto=true;
+ private int count_ping=10;
+ private String telemetry_extra="";
+ private double overheadCompensationFactor=1.06;
+ private boolean getIP_isp=true;
+ private String getIP_distance=DISTANCE_KM;
+ public static final String DISTANCE_NO="no", DISTANCE_MILES="mi", DISTANCE_KM="km";
+ private boolean useMebibits=false;
+ private String test_order="IP_D_U";
+
+ private void check(){
+ if(dl_ckSize<1) throw new IllegalArgumentException("dl_ckSize must be at least 1");
+ if(ul_ckSize<1) throw new IllegalArgumentException("ul_ckSize must be at least 1");
+ if(dl_parallelStreams<1) throw new IllegalArgumentException("dl_parallelStreams must be at least 1");
+ if(ul_parallelStreams<1) throw new IllegalArgumentException("ul_parallelStreams must be at least 1");
+ if(dl_streamDelay<0) throw new IllegalArgumentException("dl_streamDelay must be at least 0");
+ if(ul_streamDelay<0) throw new IllegalArgumentException("ul_streamDelay must be at least 0");
+ if(dl_graceTime<0) throw new IllegalArgumentException("dl_graceTime must be at least 0");
+ if(ul_graceTime<0) throw new IllegalArgumentException("ul_graceTime must be at least 0");
+ if(!(errorHandlingMode.equals(ONERROR_FAIL)||errorHandlingMode.equals(ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(ONERROR_MUST_RESTART))) throw new IllegalArgumentException("errorHandlingMode must be fail, attempt-restart, or must-restart");
+ if(time_dl_max<1) throw new IllegalArgumentException("time_dl_max must be at least 1");
+ if(time_ul_max<1) throw new IllegalArgumentException("time_ul_max must be at least 1");
+ if(count_ping<1) throw new IllegalArgumentException("count_ping must be at least 1");
+ if(overheadCompensationFactor<1) throw new IllegalArgumentException("overheadCompensationFactor must be at least 1");
+ if(!(getIP_distance.equals(DISTANCE_NO)||getIP_distance.equals(DISTANCE_KM)||getIP_distance.equals(DISTANCE_MILES))) throw new IllegalArgumentException("getIP_distance must be no, km or miles");
+ for(char c:test_order.toCharArray()){
+ if(!(c=='I'||c=='P'||c=='D'||c=='U'||c=='_')) throw new IllegalArgumentException("test_order can only contain characters I, P, D, U, _");
+ }
+ }
+
+ public SpeedtestConfig(){
+ check();
+ }
+
+ public SpeedtestConfig(
+ int dl_ckSize,
+ int ul_ckSize,
+ int dl_parallelStreams,
+ int ul_parallelStreams,
+ int dl_streamDelay,
+ int ul_streamDelay,
+ double dl_graceTime,
+ double ul_graceTime,
+ int dl_connectTimeout,
+ int dl_soTimeout,
+ int ul_connectTimeout,
+ int ul_soTimeout,
+ int ping_connectTimeout,
+ int ping_soTimeout,
+ int dl_recvBuffer,
+ int dl_sendBuffer,
+ int ul_recvBuffer,
+ int ul_sendBuffer,
+ int ping_recvBuffer,
+ int ping_sendBuffer,
+ String errorHandlingMode,
+ int time_dl_max,
+ int time_ul_max,
+ boolean time_auto,
+ int count_ping,
+ String telemetry_extra,
+ double overheadCompensationFactor,
+ boolean getIP_isp,
+ String getIP_distance,
+ boolean useMebibits,
+ String test_order
+ ) {
+ this.dl_ckSize = dl_ckSize;
+ this.ul_ckSize = ul_ckSize;
+ this.dl_parallelStreams = dl_parallelStreams;
+ this.ul_parallelStreams = ul_parallelStreams;
+ this.dl_streamDelay = dl_streamDelay;
+ this.ul_streamDelay = ul_streamDelay;
+ this.dl_graceTime = dl_graceTime;
+ this.ul_graceTime = ul_graceTime;
+ this.dl_connectTimeout = dl_connectTimeout;
+ this.dl_soTimeout = dl_soTimeout;
+ this.ul_connectTimeout = ul_connectTimeout;
+ this.ul_soTimeout = ul_soTimeout;
+ this.ping_connectTimeout = ping_connectTimeout;
+ this.ping_soTimeout = ping_soTimeout;
+ this.dl_recvBuffer = dl_recvBuffer;
+ this.dl_sendBuffer = dl_sendBuffer;
+ this.ul_recvBuffer = ul_recvBuffer;
+ this.ul_sendBuffer = ul_sendBuffer;
+ this.ping_recvBuffer = ping_recvBuffer;
+ this.ping_sendBuffer = ping_sendBuffer;
+ this.errorHandlingMode = errorHandlingMode;
+ this.time_dl_max = time_dl_max;
+ this.time_ul_max = time_ul_max;
+ this.time_auto = time_auto;
+ this.count_ping = count_ping;
+ this.telemetry_extra = telemetry_extra;
+ this.overheadCompensationFactor = overheadCompensationFactor;
+ this.getIP_isp = getIP_isp;
+ this.getIP_distance = getIP_distance;
+ this.useMebibits = useMebibits;
+ this.test_order = test_order;
+ check();
+ }
+
+ public SpeedtestConfig(JSONObject json){
+ try {
+ if (json.has("dl_ckSize")) this.dl_ckSize = json.getInt("dl_ckSize");
+ if (json.has("ul_ckSize")) this.ul_ckSize = json.getInt("ul_ckSize");
+ if (json.has("dl_parallelStreams"))
+ this.dl_parallelStreams = json.getInt("dl_parallelStreams");
+ if (json.has("ul_parallelStreams"))
+ this.ul_parallelStreams = json.getInt("ul_parallelStreams");
+ if (json.has("dl_streamDelay")) this.dl_streamDelay = json.getInt("dl_streamDelay");
+ if (json.has("ul_streamDelay")) this.ul_streamDelay = json.getInt("ul_streamDelay");
+ if (json.has("dl_graceTime")) this.dl_graceTime = json.getDouble("dl_graceTime");
+ if (json.has("ul_graceTime")) this.ul_graceTime = json.getDouble("ul_graceTime");
+ if (json.has("dl_connectTimeout"))
+ this.dl_connectTimeout = json.getInt("dl_connectTimeout");
+ if (json.has("ul_connectTimeout"))
+ this.ul_connectTimeout = json.getInt("ul_connectTimeout");
+ if (json.has("ping_connectTimeout"))
+ this.ping_connectTimeout = json.getInt("ping_connectTimeout");
+ if (json.has("dl_soTimeout")) this.dl_soTimeout = json.getInt("dl_soTimeout");
+ if (json.has("ul_soTimeout")) this.ul_soTimeout = json.getInt("ul_soTimeout");
+ if (json.has("ping_soTimeout")) this.ping_soTimeout = json.getInt("ping_soTimeout");
+ if (json.has("dl_recvBuffer")) this.dl_recvBuffer = json.getInt("dl_recvBuffer");
+ if (json.has("ul_recvBuffer")) this.ul_recvBuffer = json.getInt("ul_recvBuffer");
+ if (json.has("ping_recvBuffer")) this.ping_recvBuffer = json.getInt("ping_recvBuffer");
+ if (json.has("dl_sendBuffer")) this.dl_sendBuffer = json.getInt("dl_sendBuffer");
+ if (json.has("ul_sendBuffer")) this.ul_sendBuffer = json.getInt("ul_sendBuffer");
+ if (json.has("ping_sendBuffer")) this.ping_sendBuffer = json.getInt("ping_sendBuffer");
+ if (json.has("errorHandlingMode"))
+ this.errorHandlingMode = json.getString("errorHandlingMode");
+ if (json.has("time_dl_max")) this.time_dl_max = json.getInt("time_dl_max");
+ if (json.has("time_ul_max")) this.time_ul_max = json.getInt("time_ul_max");
+ if (json.has("count_ping")) this.count_ping = json.getInt("count_ping");
+ if (json.has("telemetry_extra"))
+ this.telemetry_extra = json.getString("telemetry_extra");
+ if (json.has("overheadCompensationFactor"))
+ this.overheadCompensationFactor = json.getDouble("overheadCompensationFactor");
+ if (json.has("getIP_isp")) this.getIP_isp = json.getBoolean("getIP_isp");
+ if (json.has("getIP_distance")) this.getIP_distance = json.getString("getIP_distance");
+ if (json.has("test_order")) this.test_order = json.getString("test_order");
+ if (json.has("useMebibits")) this.useMebibits = json.getBoolean("useMebibits");
+ check();
+ }catch(JSONException t){
+ throw new IllegalArgumentException("Invalid JSON ("+t.toString()+")");
+ }
+ }
+
+ public int getDl_ckSize() {
+ return dl_ckSize;
+ }
+
+ public int getUl_ckSize() {
+ return ul_ckSize;
+ }
+
+ public int getDl_parallelStreams() {
+ return dl_parallelStreams;
+ }
+
+ public int getUl_parallelStreams() {
+ return ul_parallelStreams;
+ }
+
+ public int getDl_streamDelay() {
+ return dl_streamDelay;
+ }
+
+ public int getUl_streamDelay() {
+ return ul_streamDelay;
+ }
+
+ public double getDl_graceTime() {
+ return dl_graceTime;
+ }
+
+ public double getUl_graceTime() {
+ return ul_graceTime;
+ }
+
+ public int getDl_connectTimeout() {
+ return dl_connectTimeout;
+ }
+
+ public int getDl_soTimeout() {
+ return dl_soTimeout;
+ }
+
+ public int getUl_connectTimeout() {
+ return ul_connectTimeout;
+ }
+
+ public int getUl_soTimeout() {
+ return ul_soTimeout;
+ }
+
+ public int getPing_connectTimeout() {
+ return ping_connectTimeout;
+ }
+
+ public int getPing_soTimeout() {
+ return ping_soTimeout;
+ }
+
+ public int getDl_recvBuffer() {
+ return dl_recvBuffer;
+ }
+
+ public int getDl_sendBuffer() {
+ return dl_sendBuffer;
+ }
+
+ public int getUl_recvBuffer() {
+ return ul_recvBuffer;
+ }
+
+ public int getUl_sendBuffer() {
+ return ul_sendBuffer;
+ }
+
+ public int getPing_recvBuffer() {
+ return ping_recvBuffer;
+ }
+
+ public int getPing_sendBuffer() {
+ return ping_sendBuffer;
+ }
+
+ public String getErrorHandlingMode() {
+ return errorHandlingMode;
+ }
+
+ public int getTime_dl_max() {
+ return time_dl_max;
+ }
+
+ public int getTime_ul_max() {
+ return time_ul_max;
+ }
+
+ public boolean getTime_auto() {
+ return time_auto;
+ }
+
+ public int getCount_ping() {
+ return count_ping;
+ }
+
+ public String getTelemetry_extra() {
+ return telemetry_extra;
+ }
+
+ public double getOverheadCompensationFactor() {
+ return overheadCompensationFactor;
+ }
+
+ public boolean getGetIP_isp() {
+ return getIP_isp;
+ }
+
+ public String getGetIP_distance() {
+ return getIP_distance;
+ }
+
+ public boolean getUseMebibits() {
+ return useMebibits;
+ }
+
+ public String getTest_order() {
+ return test_order;
+ }
+
+ public void setDl_ckSize(int dl_ckSize) {
+ if(dl_ckSize<1) throw new IllegalArgumentException("dl_ckSize must be at least 1");
+ this.dl_ckSize = dl_ckSize;
+ }
+
+ public void setUl_ckSize(int ul_ckSize) {
+ if(ul_ckSize<1) throw new IllegalArgumentException("ul_ckSize must be at least 1");
+ this.ul_ckSize = ul_ckSize;
+ }
+
+ public void setDl_parallelStreams(int dl_parallelStreams) {
+ if(dl_parallelStreams<1) throw new IllegalArgumentException("dl_parallelStreams must be at least 1");
+ this.dl_parallelStreams = dl_parallelStreams;
+ }
+
+ public void setUl_parallelStreams(int ul_parallelStreams) {
+ if(ul_parallelStreams<1) throw new IllegalArgumentException("ul_parallelStreams must be at least 1");
+ this.ul_parallelStreams = ul_parallelStreams;
+ }
+
+ public void setDl_streamDelay(int dl_streamDelay) {
+ if(dl_streamDelay<0) throw new IllegalArgumentException("dl_streamDelay must be at least 0");
+ this.dl_streamDelay = dl_streamDelay;
+ }
+
+ public void setUl_streamDelay(int ul_streamDelay) {
+ if(ul_streamDelay<0) throw new IllegalArgumentException("ul_streamDelay must be at least 0");
+ this.ul_streamDelay = ul_streamDelay;
+ }
+
+ public void setDl_graceTime(double dl_graceTime) {
+ if(dl_graceTime<0) throw new IllegalArgumentException("dl_graceTime must be at least 0");
+ this.dl_graceTime = dl_graceTime;
+ }
+
+ public void setUl_graceTime(double ul_graceTime) {
+ if(ul_graceTime<0) throw new IllegalArgumentException("ul_graceTime must be at least 0");
+ this.ul_graceTime = ul_graceTime;
+ }
+
+ public void setDl_connectTimeout(int dl_connectTimeout) {
+
+ this.dl_connectTimeout = dl_connectTimeout;
+ }
+
+ public void setDl_soTimeout(int dl_soTimeout) {
+
+ this.dl_soTimeout = dl_soTimeout;
+ }
+
+ public void setUl_connectTimeout(int ul_connectTimeout) {
+
+ this.ul_connectTimeout = ul_connectTimeout;
+ }
+
+ public void setUl_soTimeout(int ul_soTimeout) {
+
+ this.ul_soTimeout = ul_soTimeout;
+ }
+
+ public void setPing_connectTimeout(int ping_connectTimeout) {
+
+ this.ping_connectTimeout = ping_connectTimeout;
+ }
+
+ public void setPing_soTimeout(int ping_soTimeout) {
+
+ this.ping_soTimeout = ping_soTimeout;
+ }
+
+ public void setDl_recvBuffer(int dl_recvBuffer) {
+
+ this.dl_recvBuffer = dl_recvBuffer;
+ }
+
+ public void setDl_sendBuffer(int dl_sendBuffer) {
+
+ this.dl_sendBuffer = dl_sendBuffer;
+ }
+
+ public void setUl_recvBuffer(int ul_recvBuffer) {
+
+ this.ul_recvBuffer = ul_recvBuffer;
+ }
+
+ public void setUl_sendBuffer(int ul_sendBuffer) {
+
+ this.ul_sendBuffer = ul_sendBuffer;
+ }
+
+ public void setPing_recvBuffer(int ping_recvBuffer) {
+
+ this.ping_recvBuffer = ping_recvBuffer;
+ }
+
+ public void setPing_sendBuffer(int ping_sendBuffer) {
+
+ this.ping_sendBuffer = ping_sendBuffer;
+ }
+
+ public void setErrorHandlingMode(String errorHandlingMode) {
+ if(!(errorHandlingMode.equals(ONERROR_FAIL)||errorHandlingMode.equals(ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(ONERROR_MUST_RESTART))) throw new IllegalArgumentException("errorHandlingMode must be fail, attempt-restart, or must-restart");
+ this.errorHandlingMode = errorHandlingMode;
+ }
+
+ public void setTime_dl_max(int time_dl_max) {
+ if(time_dl_max<1) throw new IllegalArgumentException("time_dl_max must be at least 1");
+ this.time_dl_max = time_dl_max;
+ }
+
+ public void setTime_ul_max(int time_ul_max) {
+ if(time_ul_max<1) throw new IllegalArgumentException("time_ul_max must be at least 1");
+ this.time_ul_max = time_ul_max;
+ }
+
+ public void setTime_auto(boolean time_auto) {
+
+ this.time_auto = time_auto;
+ }
+
+ public void setCount_ping(int count_ping) {
+ if(count_ping<1) throw new IllegalArgumentException("count_ping must be at least 1");
+ this.count_ping = count_ping;
+ }
+
+ public void setTelemetry_extra(String telemetry_extra) {
+
+ this.telemetry_extra = telemetry_extra;
+ }
+
+ public void setOverheadCompensationFactor(double overheadCompensationFactor) {
+ if(overheadCompensationFactor<1) throw new IllegalArgumentException("overheadCompensationFactor must be at least 1");
+ this.overheadCompensationFactor = overheadCompensationFactor;
+ }
+
+ public void setGetIP_isp(boolean getIP_isp) {
+
+ this.getIP_isp = getIP_isp;
+ }
+
+ public void setGetIP_distance(String getIP_distance) {
+ if(!(getIP_distance.equals(DISTANCE_NO)||getIP_distance.equals(DISTANCE_KM)||getIP_distance.equals(DISTANCE_MILES))) throw new IllegalArgumentException("getIP_distance must be no, km or miles");
+ this.getIP_distance = getIP_distance;
+ }
+
+ public void setUseMebibits(boolean useMebibits) {
+
+ this.useMebibits = useMebibits;
+ }
+
+ public void setTest_order(String test_order) {
+ for(char c:test_order.toCharArray()){
+ if(!(c=='I'||c=='P'||c=='D'||c=='U'||c=='_')) throw new IllegalArgumentException("test_order can only contain characters I, P, D, U, _");
+ }
+ this.test_order = test_order;
+ }
+
+ public SpeedtestConfig clone(){
+ return new SpeedtestConfig(dl_ckSize, ul_ckSize, dl_parallelStreams, ul_parallelStreams, dl_streamDelay, ul_streamDelay, dl_graceTime, ul_graceTime, dl_connectTimeout, dl_soTimeout, ul_connectTimeout, ul_soTimeout, ping_connectTimeout, ping_soTimeout, dl_recvBuffer, dl_sendBuffer, ul_recvBuffer, ul_sendBuffer, ping_recvBuffer, ping_sendBuffer, errorHandlingMode, time_dl_max, time_ul_max, time_auto, count_ping, telemetry_extra, overheadCompensationFactor, getIP_isp, getIP_distance, useMebibits, test_order);
+ }
+}
diff --git a/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java b/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java
new file mode 100644
index 0000000..2f0c176
--- /dev/null
+++ b/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of the LibreSpeed speedtest library.
+ *
+ * The LibreSpeed speedtest library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The LibreSpeed speedtest library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with The LibreSpeed speedtest library. If not, see .
+ */
+
+package com.fdossena.speedtest.core.config;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class TelemetryConfig {
+ private String telemetryLevel=LEVEL_DISABLED, server=null, path=null, shareURL=null;
+ public static final String LEVEL_DISABLED="disabled", LEVEL_BASIC="basic", LEVEL_FULL="full";
+
+ private void check(){
+ if(!(telemetryLevel.equals(LEVEL_DISABLED)||telemetryLevel.equals(LEVEL_BASIC)||telemetryLevel.equals(LEVEL_FULL))) throw new IllegalArgumentException("Telemetry level must be disabled, basic or full");
+ }
+
+ public TelemetryConfig(){}
+
+ public TelemetryConfig(String telemetryLevel, String server, String path, String shareURL){
+ this.telemetryLevel=telemetryLevel;
+ this.server=server;
+ this.path=path;
+ this.shareURL=shareURL;
+ check();
+ }
+
+ public TelemetryConfig(JSONObject json){
+ try{
+ if(json.has("telemetryLevel")) telemetryLevel=json.getString("telemetryLevel");
+ if(json.has("server")) server=json.getString("server");
+ if(json.has("path")) path=json.getString("path");
+ if(json.has("shareURL")) shareURL=json.getString("shareURL");
+ check();
+ }catch(JSONException t){
+ throw new IllegalArgumentException("Invalid JSON ("+t.toString()+")");
+ }
+ }
+
+ public String getTelemetryLevel() {
+ return telemetryLevel;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getShareURL() {
+ return shareURL;
+ }
+
+ public TelemetryConfig clone(){
+ return new TelemetryConfig(telemetryLevel,server,path,shareURL);
+ }
+}