<commit_before>package org.owntracks.android.services;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;

import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistable;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttPingSender;
import org.eclipse.paho.client.mqttv3.internal.ClientComms;
import org.owntracks.android.R;
import org.owntracks.android.messages.ConfigurationMessage;
import org.owntracks.android.messages.LocationMessage;
import org.owntracks.android.messages.Message;
import org.owntracks.android.support.Events;
import org.owntracks.android.support.MessageCallbacks;
import org.owntracks.android.support.Preferences;
import org.owntracks.android.support.StringifiedJSONObject;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;

import de.greenrobot.event.EventBus;

public class ServiceBroker implements MqttCallback, ProxyableService {


    public enum State {
        INITIAL, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED, DISCONNECTED_USERDISCONNECT, DISCONNECTED_DATADISABLED, DISCONNECTED_ERROR
    }


	private static State state = State.INITIAL;

	private CustomMqttClient mqttClient;
	private Thread workerThread;
	private LinkedList<Message> deferredPublishables;
	private static Exception error;
	private HandlerThread pubThread;
	private Handler pubHandler;
    private MqttClientPersistence persistenceStore;
	private BroadcastReceiver netConnReceiver;
	private BroadcastReceiver pingSender;
	private ServiceProxy context;
    private LinkedList<String> subscribtions;
    private WakeLock networkWakelock;
    private WakeLock connectionWakelock;
    private String TAG_WAKELOG = "org.owntracks.android.wakelog.broker";
    private ReconnectTimer reconnectTimer;
    private Map<IMqttDeliveryToken, Message> sendMessages = new HashMap<>();
    private List<Message> backlog = new LinkedList<>();

    @Override
	public void onCreate(ServiceProxy p) {
		this.context = p;
		this.workerThread = null;
		this.error = null;
        this.subscribtions = new LinkedList<>();
		this.deferredPublishables = new LinkedList<Message>();
		this.pubThread = new HandlerThread("MQTTPUBTHREAD");
		this.pubThread.start();
		this.pubHandler = new Handler(this.pubThread.getLooper());
        this.persistenceStore = new CustomMemoryPersistence();
        this.reconnectTimer = new ReconnectTimer(context);
        changeState(State.INITIAL);
        doStart();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// doStart();
		return 0;
	}

	private void doStart() {
		doStart(false);
	}

	private void doStart(final boolean force) {


		Thread thread1 = new Thread() {
			@Override
			public void run() {
				handleStart(force);
				if (this == ServiceBroker.this.workerThread) // Clean up worker
																// thread
					ServiceBroker.this.workerThread = null;
			}

			@Override
			public void interrupt() {
				if (this == ServiceBroker.this.workerThread) // Clean up worker
																// thread
					ServiceBroker.this.workerThread = null;
				super.interrupt();
			}
		};
		thread1.start();
	}

	void handleStart(boolean force) {
		//Log.v(this.toString(), "handleStart: force == " + force);
        if(!Preferences.canConnect()) {
            //Log.v(this.toString(), "handleStart: canConnect() == false");
            return;
        }
		// Respect user's wish to stay disconnected. Overwrite with force = true
		// to reconnect manually afterwards
		if ((state == State.DISCONNECTED_USERDISCONNECT)
				&& !force) {
			//Log.d(this.toString(), "handleStart: userdisconnect==true");
			return;
		}

		if (isConnecting()) {
			//Log.d(this.toString(), "handleStart: isConnecting == true");
			return;
		}

		// Respect user's wish to not use data
		if (!isBackgroundDataEnabled()) {
			//Log.e(this.toString(), "handleStart: isBackgroundDataEnabled == false");
			changeState(State.DISCONNECTED_DATADISABLED);
			return;
		}

		// Don't do anything unless we're disconnected

		if (isDisconnected()) {
			//Log.v(this.toString(), "handleStart: isDisconnected() == true");
			// Check if there is a data connection
			if (isOnline()) {
				//Log.v(this.toString(), "handleStart: isOnline() == true");

				if (connect())
					onConnect();

			} else {
				//Log.e(this.toString(), "handleStart: isDisconnected() == false");
				changeState(State.DISCONNECTED_DATADISABLED);
			}
		} else {
			//Log.d(this.toString(), "handleStart: isDisconnected() == false");

		}
	}

	private boolean isDisconnected() {

		return (state == State.INITIAL)
				|| (state == State.DISCONNECTED)
				|| (state == State.DISCONNECTED_USERDISCONNECT)
				|| (state == State.DISCONNECTED_DATADISABLED)
				|| (state == State.DISCONNECTED_ERROR)

				// In some cases the internal state may diverge from the mqtt
				// client state.
				|| !isConnected();
	}

	private boolean init() {
		if (this.mqttClient != null) {
			return true;
		}

		try {
			String prefix = Preferences.getTls() ? "ssl" : "tcp";
			String cid = Preferences.getClientId(true);
            Log.v(this.toString(), "Using client id: " + cid);

            this.mqttClient = new CustomMqttClient(prefix + "://" + Preferences.getHost() + ":" + Preferences.getPort(), cid, persistenceStore, new AlarmPingSender(context));
			this.mqttClient.setCallback(this);

		} catch (Exception e) {
			// something went wrong!
			this.mqttClient = null;
			changeState(e);
            return false;
		}
        return true;
	}

	private static class CustomSocketFactory extends javax.net.ssl.SSLSocketFactory{
        private javax.net.ssl.SSLSocketFactory factory;

        public CustomSocketFactory(boolean sideloadCa) throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException {
            Log.v(this.toString(), "initializing CustomSocketFactory");

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);


            if(sideloadCa) {
                Log.v(this.toString(), "CA sideload: true");

                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                keyStore.load(null, null);

                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                InputStream caInput = new BufferedInputStream(new FileInputStream(Preferences.getTlsCrtPath()));
                Log.v(this.toString(), "Using custom tls cert from : " + Preferences.getTlsCrtPath());
                java.security.cert.Certificate ca;
                try {
                    ca = cf.generateCertificate(caInput);
                    keyStore.setCertificateEntry("owntracks-custom-tls-root", ca);

                } catch (Exception e) {
                    Log.e(this.toString(), e.toString());
                } finally {
                    caInput.close();
                }


                Log.v(this.toString(), "Keystore content: ");
                Enumeration<String> aliases = keyStore.aliases();

                for (; aliases.hasMoreElements();) {
                    String o = aliases.nextElement();
                    Log.v(this.toString(), "Alias: " + o);
                }

                tmf.init(keyStore);

            } else {
                Log.v(this.toString(), "CA sideload: false, using system keystore");
                // Use system KeyStore. This is some kind of magic.
                // On devices with hardware backed keystore, one does not get a an instance of the
                // system keystore when using KeyStore.getInstance("AndroidKeystore"). Instead,
                // an empty keystore is returned. However, when passing null to the tmf.init method
                // the system keystore is used
                tmf.init((KeyStore) null);

            }

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(null, tmf.getTrustManagers(), null);
            this.factory= context.getSocketFactory();

        }

        @Override
        public String[] getDefaultCipherSuites() {
            return this.factory.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return this.factory.getSupportedCipherSuites();
        }

        @Override
        public Socket createSocket() throws IOException{
            SSLSocket r = (SSLSocket)this.factory.createSocket();
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(s, host, port, autoClose);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException, UnknownHostException {

            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port, localHost, localPort);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(address, port, localAddress,localPort);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    }

	private boolean connect() {
		this.workerThread = Thread.currentThread(); // We connect, so we're the worker thread
		error = null; // clear previous error on connect
		if(!init()) {
            return false;
        }

		try {
			changeState(State.CONNECTING);
			MqttConnectOptions options = new MqttConnectOptions();
			setWill(options);

			if (Preferences.getAuth()) {
				options.setPassword(Preferences.getPassword().toCharArray());
				options.setUserName(Preferences.getUsername());
			}

			if (Preferences.getTls()) {
                options.setSocketFactory(new CustomSocketFactory(Preferences.getTlsCrtPath().length() > 0));
            }

			// setWill(options);
			options.setKeepAliveInterval(Preferences.getKeepalive());
			options.setConnectionTimeout(30);
			options.setCleanSession(Preferences.getCleanSession());

			this.mqttClient.connect(options);
			changeState(State.CONNECTED);

			return true;

		} catch (Exception e) { // Catch paho and socket factory exceptions
			Log.e(this.toString(), e.toString());
            e.printStackTrace();
			changeState(e);
			return false;
		}
	}

	private void setWill(MqttConnectOptions m) {
		StringBuilder payload = new StringBuilder();
		payload.append("{");
		payload.append("\"_type\": ").append("\"").append("lwt").append("\"");
		payload.append(", \"tst\": ")
				.append("\"")
				.append((int) (TimeUnit.MILLISECONDS.toSeconds(System
						.currentTimeMillis()))).append("\"");
		payload.append("}");


		m.setWill(Preferences.getBaseTopic(), payload.toString().getBytes(), 0, false);

	}

	private void onConnect() {

		//if (!isConnected())
		//	Log.e(this.toString(), "onConnect: !isConnected");

        reconnectTimer.stop();

		// Establish observer to monitor wifi and radio connectivity
		if (this.netConnReceiver == null) {
			this.netConnReceiver = new NetworkConnectionIntentReceiver();<commit_msg>earlier state change on connect<commit_after>package org.owntracks.android.services;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;

import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistable;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttPingSender;
import org.eclipse.paho.client.mqttv3.internal.ClientComms;
import org.owntracks.android.R;
import org.owntracks.android.messages.ConfigurationMessage;
import org.owntracks.android.messages.LocationMessage;
import org.owntracks.android.messages.Message;
import org.owntracks.android.support.Events;
import org.owntracks.android.support.MessageCallbacks;
import org.owntracks.android.support.Preferences;
import org.owntracks.android.support.StringifiedJSONObject;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;

import de.greenrobot.event.EventBus;

public class ServiceBroker implements MqttCallback, ProxyableService {


    public enum State {
        INITIAL, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED, DISCONNECTED_USERDISCONNECT, DISCONNECTED_DATADISABLED, DISCONNECTED_ERROR
    }


	private static State state = State.INITIAL;

	private CustomMqttClient mqttClient;
	private Thread workerThread;
	private LinkedList<Message> deferredPublishables;
	private static Exception error;
	private HandlerThread pubThread;
	private Handler pubHandler;
    private MqttClientPersistence persistenceStore;
	private BroadcastReceiver netConnReceiver;
	private BroadcastReceiver pingSender;
	private ServiceProxy context;
    private LinkedList<String> subscribtions;
    private WakeLock networkWakelock;
    private WakeLock connectionWakelock;
    private String TAG_WAKELOG = "org.owntracks.android.wakelog.broker";
    private ReconnectTimer reconnectTimer;
    private Map<IMqttDeliveryToken, Message> sendMessages = new HashMap<>();
    private List<Message> backlog = new LinkedList<>();

    @Override
	public void onCreate(ServiceProxy p) {
		this.context = p;
		this.workerThread = null;
		this.error = null;
        this.subscribtions = new LinkedList<>();
		this.deferredPublishables = new LinkedList<Message>();
		this.pubThread = new HandlerThread("MQTTPUBTHREAD");
		this.pubThread.start();
		this.pubHandler = new Handler(this.pubThread.getLooper());
        this.persistenceStore = new CustomMemoryPersistence();
        this.reconnectTimer = new ReconnectTimer(context);
        changeState(State.INITIAL);
        doStart();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// doStart();
		return 0;
	}

	private void doStart() {
		doStart(false);
	}

	private void doStart(final boolean force) {


		Thread thread1 = new Thread() {
			@Override
			public void run() {
				handleStart(force);
				if (this == ServiceBroker.this.workerThread) // Clean up worker
																// thread
					ServiceBroker.this.workerThread = null;
			}

			@Override
			public void interrupt() {
				if (this == ServiceBroker.this.workerThread) // Clean up worker
																// thread
					ServiceBroker.this.workerThread = null;
				super.interrupt();
			}
		};
		thread1.start();
	}

	void handleStart(boolean force) {
		//Log.v(this.toString(), "handleStart: force == " + force);
        if(!Preferences.canConnect()) {
            //Log.v(this.toString(), "handleStart: canConnect() == false");
            return;
        }
		// Respect user's wish to stay disconnected. Overwrite with force = true
		// to reconnect manually afterwards
		if ((state == State.DISCONNECTED_USERDISCONNECT)
				&& !force) {
			//Log.d(this.toString(), "handleStart: userdisconnect==true");
			return;
		}

		if (isConnecting()) {
			//Log.d(this.toString(), "handleStart: isConnecting == true");
			return;
		}

		// Respect user's wish to not use data
		if (!isBackgroundDataEnabled()) {
			//Log.e(this.toString(), "handleStart: isBackgroundDataEnabled == false");
			changeState(State.DISCONNECTED_DATADISABLED);
			return;
		}

		// Don't do anything unless we're disconnected

		if (isDisconnected()) {
			//Log.v(this.toString(), "handleStart: isDisconnected() == true");
			// Check if there is a data connection
			if (isOnline()) {
				//Log.v(this.toString(), "handleStart: isOnline() == true");

				if (connect())
					onConnect();

			} else {
				//Log.e(this.toString(), "handleStart: isDisconnected() == false");
				changeState(State.DISCONNECTED_DATADISABLED);
			}
		} else {
			//Log.d(this.toString(), "handleStart: isDisconnected() == false");

		}
	}

	private boolean isDisconnected() {

		return (state == State.INITIAL)
				|| (state == State.DISCONNECTED)
				|| (state == State.DISCONNECTED_USERDISCONNECT)
				|| (state == State.DISCONNECTED_DATADISABLED)
				|| (state == State.DISCONNECTED_ERROR)

				// In some cases the internal state may diverge from the mqtt
				// client state.
				|| !isConnected();
	}

	private boolean init() {
		if (this.mqttClient != null) {
			return true;
		}

		try {
			String prefix = Preferences.getTls() ? "ssl" : "tcp";
			String cid = Preferences.getClientId(true);
            Log.v(this.toString(), "Using client id: " + cid);

            this.mqttClient = new CustomMqttClient(prefix + "://" + Preferences.getHost() + ":" + Preferences.getPort(), cid, persistenceStore, new AlarmPingSender(context));
			this.mqttClient.setCallback(this);

		} catch (Exception e) {
			// something went wrong!
			this.mqttClient = null;
			changeState(e);
            return false;
		}
        return true;
	}

	private static class CustomSocketFactory extends javax.net.ssl.SSLSocketFactory{
        private javax.net.ssl.SSLSocketFactory factory;

        public CustomSocketFactory(boolean sideloadCa) throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException {
            Log.v(this.toString(), "initializing CustomSocketFactory");

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);


            if(sideloadCa) {
                Log.v(this.toString(), "CA sideload: true");

                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                keyStore.load(null, null);

                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                InputStream caInput = new BufferedInputStream(new FileInputStream(Preferences.getTlsCrtPath()));
                Log.v(this.toString(), "Using custom tls cert from : " + Preferences.getTlsCrtPath());
                java.security.cert.Certificate ca;
                try {
                    ca = cf.generateCertificate(caInput);
                    keyStore.setCertificateEntry("owntracks-custom-tls-root", ca);

                } catch (Exception e) {
                    Log.e(this.toString(), e.toString());
                } finally {
                    caInput.close();
                }


                Log.v(this.toString(), "Keystore content: ");
                Enumeration<String> aliases = keyStore.aliases();

                for (; aliases.hasMoreElements();) {
                    String o = aliases.nextElement();
                    Log.v(this.toString(), "Alias: " + o);
                }

                tmf.init(keyStore);

            } else {
                Log.v(this.toString(), "CA sideload: false, using system keystore");
                // Use system KeyStore. This is some kind of magic.
                // On devices with hardware backed keystore, one does not get a an instance of the
                // system keystore when using KeyStore.getInstance("AndroidKeystore"). Instead,
                // an empty keystore is returned. However, when passing null to the tmf.init method
                // the system keystore is used
                tmf.init((KeyStore) null);

            }

            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(null, tmf.getTrustManagers(), null);
            this.factory= context.getSocketFactory();

        }

        @Override
        public String[] getDefaultCipherSuites() {
            return this.factory.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return this.factory.getSupportedCipherSuites();
        }

        @Override
        public Socket createSocket() throws IOException{
            SSLSocket r = (SSLSocket)this.factory.createSocket();
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(s, host, port, autoClose);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException, UnknownHostException {

            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port, localHost, localPort);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            SSLSocket r = (SSLSocket)this.factory.createSocket(address, port, localAddress,localPort);
            r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
            return r;
        }
    }

	private boolean connect() {
        this.workerThread = Thread.currentThread(); // We connect, so we're the worker thread
        changeState(State.CONNECTING);

		error = null; // clear previous error on connect
		if(!init()) {
            return false;
        }

		try {
			MqttConnectOptions options = new MqttConnectOptions();
			setWill(options);

			if (Preferences.getAuth()) {
				options.setPassword(<PASSWORD>.<PASSWORD>().<PASSWORD>());
				options.setUserName(Preferences.getUsername());
			}

			if (Preferences.getTls()) {
                options.setSocketFactory(new CustomSocketFactory(Preferences.getTlsCrtPath().length() > 0));
            }

			// setWill(options);
			options.setKeepAliveInterval(Preferences.getKeepalive());
			options.setConnectionTimeout(30);
			options.setCleanSession(Preferences.getCleanSession());

			this.mqttClient.connect(options);
			changeState(State.CONNECTED);

			return true;

		} catch (Exception e) { // Catch paho and socket factory exceptions
			Log.e(this.toString(), e.toString());
            e.printStackTrace();
			changeState(e);
			return false;
		}
	}

	private void setWill(MqttConnectOptions m) {
		StringBuilder payload = new StringBuilder();
		payload.append("{");
		payload.append("\"_type\": ").append("\"").append("lwt").append("\"");
		payload.append(", \"tst\": ")
				.append("\"")
				.append((int) (TimeUnit.MILLISECONDS.toSeconds(System
                        .currentTimeMillis()))).append("\"");
		payload.append("}");


		m.setWill(Preferences.getBaseTopic(), payload.toString().getBytes(), 0, false);

	}

	private void onConnect() {

		//if (!isConnected())
		//	Log.e(this.toString(), "onConnect: !isConnected");

        reconnectTimer.stop();

		// Establish observer to monitor wifi and radio connectivity
		if (this.netConnReceiver == null) {
			this.netConnReceiver = new NetworkConnectionIntentReceiver();
{'fraction_non_alphanumeric': 0.1167857269787113, 'fraction_numerical': 0.004407008565234389, 'mean_word_length': 3.195318324138959, 'pattern_counts': {'":': 8, '<': 24, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6345324', 'n_tokens_mistral': 8810, 'n_tokens_neox': 8075, 'n_words': 1755}