<commit_before>package dt.call.aclient.background;

import android.app.AlarmManager;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;

import com.goterl.lazycode.lazysodium.LazySodium;
import com.goterl.lazycode.lazysodium.LazySodiumAndroid;
import com.goterl.lazycode.lazysodium.SodiumAndroid;
import com.goterl.lazycode.lazysodium.interfaces.Box;
import com.goterl.lazycode.lazysodium.interfaces.SecretBox;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays;

import dt.call.aclient.CallState;
import dt.call.aclient.Const;
import dt.call.aclient.R;
import dt.call.aclient.Utils;
import dt.call.aclient.Vars;
import dt.call.aclient.background.async.CommandEndAsync;
import dt.call.aclient.screens.CallIncoming;
import dt.call.aclient.sodium.SodiumUtils;
import dt.call.aclient.sqlite.SQLiteDb;

/**
 * Created by Daniel on 1/19/16.
 * Mostly copied and pasted from jclient
 */
public class CmdListener extends IntentService
{
	private static final String tag = "CmdListener";

	//copied over from jclient
	private boolean inputValid = true; //causes the thread to stop whether for technical or paranoia

	//for deciding when to send the ready command
	private boolean haveAesKey = false;
	private boolean preparationsComplete = false;
	private boolean isCallInitiator = false;

	public CmdListener()
	{
		super(tag);
	}

	@Override
	protected void onHandleIntent(Intent workIntent)
	{
		Utils.logcat(Const.LOGD, tag, "command listener INTENT SERVICE started");
		final LazySodium lazySodium = new LazySodiumAndroid(new SodiumAndroid());

		while(inputValid)
		{
			//responses from the server command connection will always be in text format
			//timestamp|available|other_person
			//timestamp|incoming|trying_to_call
			//timestamp|start|other_person
			//timestamp|end|other_person
			//timestamp|prepare|public key|other_person
			//timestamp|direct|(encrypted aes key)|other_person
			//timestamp|invalid

			String logd = ""; //accumulate all the diagnostic message together to prevent multiple entries of diagnostics in log ui just for cmd listener
			try
			{//the async magic here... it will patiently wait until something comes in

				final String fromServer = Vars.commandSocket.readString(Const.SIZE_COMMAND);
				final String[] respContents = fromServer.split("\\|");
				logd = "Server response raw: " + fromServer + "\n";

				//check for properly formatted command
				if(respContents.length > Const.COMMAND_MAX_SEGMENTS)
				{
					Utils.logcat(Const.LOGW, tag, logd+"command has too many segments to be valid");
					continue;
				}

				//verify timestamp
				final long ts = Long.valueOf(respContents[0]);
				if(!Utils.validTS(ts))
				{
					Utils.logcat(Const.LOGW, tag, logd+"Rejecting server response for bad timestamp");
					continue;
				}

				//look at what the server is telling the call simulator to do
				final String command = respContents[1];
				final String involved = respContents[respContents.length-1];

				//"incoming" has no "other person you're in a call with" to verify because incoming defines who that other person is
				if (command.equals("incoming"))
				{
					//wake up the cell phone
					final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
					Vars.incomingCallLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, Const.WAKELOCK_INCOMING);
					Vars.incomingCallLock.acquire();

					//build the contacts list if it doesn't already exist
					if(Vars.contactTable == null)
					{
						SQLiteDb.getInstance(getApplication()).populateContacts();
						SQLiteDb.getInstance(getApplication()).populatePublicKeys();
					}

					Vars.state = CallState.INIT;
					isCallInitiator = false;
					haveAesKey = false;
					Vars.callWith = involved;

					//launch the incoming call screen
					Utils.setNotification(R.string.state_popup_incoming, R.color.material_light_blue, Vars.go2CallIncomingPending);
					final Intent showIncoming = new Intent(getApplicationContext(), CallIncoming.class);
					showIncoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //needed to start activity from background
					startActivity(showIncoming);
					continue;
				}

				//for all commands except "incoming", need to verify that the other person the command says you're in a call with
				//	is really the other person you're in a call with
				if (!involved.equals(Vars.callWith))
				{
					Utils.logcat(Const.LOGW, tag, logd+"Erroneous command involving: " + involved + " instead of: " + Vars.callWith);
					continue;
				}

				if(command.equals("available"))
				{
					Vars.state = CallState.INIT;
					isCallInitiator = true;
					haveAesKey = true; //person who makes the call gets to choose the key
					notifyCallStateChange(Const.BROADCAST_CALL_TRY);
					Utils.setNotification(R.string.state_popup_init, R.color.material_light_blue, Vars.go2CallMainPending);
				}
				else if (command.equals("start"))
				{
					Vars.state = CallState.INCALL;
					notifyCallStateChange(Const.BROADCAST_CALL_START);
					Utils.setNotification(R.string.state_popup_incall, R.color.material_light_blue, Vars.go2CallMainPending);
				}
				else if (command.equals("end"))
				{
					Vars.state = CallState.NONE;
					Vars.callWith = Const.nobody;
					notifyCallStateChange(Const.BROADCAST_CALL_END);
					Utils.setNotification(R.string.state_popup_idle, R.color.material_green, Vars.go2HomePending);
				}
				else if(command.equals("prepare"))
				{
					//prepare the server's presentation of the person's public key for use
					final String receivedKeyDump = respContents[2];
					final byte[] receivedKey = SodiumUtils.interpretKey(receivedKeyDump.getBytes(), false);
					byte[] expectedKey = Vars.publicSodiumTable.get(Vars.callWith);

					//if this person's public key is known, sanity check the server to make sure it sent the right one
					if(Vars.publicSodiumTable.containsKey(Vars.callWith))
					{
						if(Arrays.equals(receivedKey, expectedKey))
						{
							//if the server presented a mismatched key stop the process.
							//either you didn't know about the key change or something is very wrong
							Utils.logcat(Const.LOGE, tag, "Server sent a MISMATCHED public key for " + Vars.callWith + " . It was:\n" + receivedKeyDump + "\nBut expected: " + Utils.stringify(expectedKey));
							giveUp();
							continue;
						}
					}
					else
					{
						//with nothing else to go on, assume this really is the person's key
//						byte[] receivedUserKey = Utils.interpretSodiumPublicKey(receivedKeyDump);
						Vars.publicSodiumTable.put(Vars.callWith, receivedKey);
//						Vars.publicSodiumDumps.put(Vars.callWith, receivedKeyDump);
						SQLiteDb.getInstance(getApplication()).insertPublicKey(Vars.callWith, receivedKey);
						expectedKey = receivedKey;
					}

					//first person gets to choose the key for the call
					if(isCallInitiator)
					{
						//choose sodium key
						Vars.voiceSymmetricKey = lazySodium.randomBytesBuf(SecretBox.KEYBYTES);<commit_msg>Actually, I'd rather prefer the keys DO match.<commit_after>package dt.call.aclient.background;

import android.app.AlarmManager;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;

import com.goterl.lazycode.lazysodium.LazySodium;
import com.goterl.lazycode.lazysodium.LazySodiumAndroid;
import com.goterl.lazycode.lazysodium.SodiumAndroid;
import com.goterl.lazycode.lazysodium.interfaces.Box;
import com.goterl.lazycode.lazysodium.interfaces.SecretBox;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays;

import dt.call.aclient.CallState;
import dt.call.aclient.Const;
import dt.call.aclient.R;
import dt.call.aclient.Utils;
import dt.call.aclient.Vars;
import dt.call.aclient.background.async.CommandEndAsync;
import dt.call.aclient.screens.CallIncoming;
import dt.call.aclient.sodium.SodiumUtils;
import dt.call.aclient.sqlite.SQLiteDb;

/**
 * Created by Daniel on 1/19/16.
 * Mostly copied and pasted from jclient
 */
public class CmdListener extends IntentService
{
	private static final String tag = "CmdListener";

	//copied over from jclient
	private boolean inputValid = true; //causes the thread to stop whether for technical or paranoia

	//for deciding when to send the ready command
	private boolean haveAesKey = false;
	private boolean preparationsComplete = false;
	private boolean isCallInitiator = false;

	public CmdListener()
	{
		super(tag);
	}

	@Override
	protected void onHandleIntent(Intent workIntent)
	{
		Utils.logcat(Const.LOGD, tag, "command listener INTENT SERVICE started");
		final LazySodium lazySodium = new LazySodiumAndroid(new SodiumAndroid());

		while(inputValid)
		{
			//responses from the server command connection will always be in text format
			//timestamp|available|other_person
			//timestamp|incoming|trying_to_call
			//timestamp|start|other_person
			//timestamp|end|other_person
			//timestamp|prepare|public key|other_person
			//timestamp|direct|(encrypted aes key)|other_person
			//timestamp|invalid

			String logd = ""; //accumulate all the diagnostic message together to prevent multiple entries of diagnostics in log ui just for cmd listener
			try
			{//the async magic here... it will patiently wait until something comes in

				final String fromServer = Vars.commandSocket.readString(Const.SIZE_COMMAND);
				final String[] respContents = fromServer.split("\\|");
				logd = "Server response raw: " + fromServer + "\n";

				//check for properly formatted command
				if(respContents.length > Const.COMMAND_MAX_SEGMENTS)
				{
					Utils.logcat(Const.LOGW, tag, logd+"command has too many segments to be valid");
					continue;
				}

				//verify timestamp
				final long ts = Long.valueOf(respContents[0]);
				if(!Utils.validTS(ts))
				{
					Utils.logcat(Const.LOGW, tag, logd+"Rejecting server response for bad timestamp");
					continue;
				}

				//look at what the server is telling the call simulator to do
				final String command = respContents[1];
				final String involved = respContents[respContents.length-1];

				//"incoming" has no "other person you're in a call with" to verify because incoming defines who that other person is
				if (command.equals("incoming"))
				{
					//wake up the cell phone
					final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
					Vars.incomingCallLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, Const.WAKELOCK_INCOMING);
					Vars.incomingCallLock.acquire();

					//build the contacts list if it doesn't already exist
					if(Vars.contactTable == null)
					{
						SQLiteDb.getInstance(getApplication()).populateContacts();
						SQLiteDb.getInstance(getApplication()).populatePublicKeys();
					}

					Vars.state = CallState.INIT;
					isCallInitiator = false;
					haveAesKey = false;
					Vars.callWith = involved;

					//launch the incoming call screen
					Utils.setNotification(R.string.state_popup_incoming, R.color.material_light_blue, Vars.go2CallIncomingPending);
					final Intent showIncoming = new Intent(getApplicationContext(), CallIncoming.class);
					showIncoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //needed to start activity from background
					startActivity(showIncoming);
					continue;
				}

				//for all commands except "incoming", need to verify that the other person the command says you're in a call with
				//	is really the other person you're in a call with
				if (!involved.equals(Vars.callWith))
				{
					Utils.logcat(Const.LOGW, tag, logd+"Erroneous command involving: " + involved + " instead of: " + Vars.callWith);
					continue;
				}

				if(command.equals("available"))
				{
					Vars.state = CallState.INIT;
					isCallInitiator = true;
					haveAesKey = true; //person who makes the call gets to choose the key
					notifyCallStateChange(Const.BROADCAST_CALL_TRY);
					Utils.setNotification(R.string.state_popup_init, R.color.material_light_blue, Vars.go2CallMainPending);
				}
				else if (command.equals("start"))
				{
					Vars.state = CallState.INCALL;
					notifyCallStateChange(Const.BROADCAST_CALL_START);
					Utils.setNotification(R.string.state_popup_incall, R.color.material_light_blue, Vars.go2CallMainPending);
				}
				else if (command.equals("end"))
				{
					Vars.state = CallState.NONE;
					Vars.callWith = Const.nobody;
					notifyCallStateChange(Const.BROADCAST_CALL_END);
					Utils.setNotification(R.string.state_popup_idle, R.color.material_green, Vars.go2HomePending);
				}
				else if(command.equals("prepare"))
				{
					//prepare the server's presentation of the person's public key for use
					final String receivedKeyDump = respContents[2];
					final byte[] receivedKey = SodiumUtils.interpretKey(receivedKeyDump.getBytes(), false);
					byte[] expectedKey = Vars.publicSodiumTable.get(Vars.callWith);

					//if this person's public key is known, sanity check the server to make sure it sent the right one
					if(Vars.publicSodiumTable.containsKey(Vars.callWith))
					{
						if(!Arrays.equals(receivedKey, expectedKey))
						{
							//if the server presented a mismatched key stop the process.
							//either you didn't know about the key change or something is very wrong
							Utils.logcat(Const.LOGE, tag,
									"Server sent a MISMATCHED public key for " + Vars.callWith +
											" . It was:\n" + receivedKeyDump +
											"\nBut expected: " + Const.SODIUM_PUBLIC_HEADER+Utils.stringify(expectedKey));
							giveUp();
							continue;
						}
					}
					else
					{
						//with nothing else to go on, assume this really is the person's key
//						byte[] receivedUserKey = Utils.interpretSodiumPublicKey(receivedKeyDump);
						Vars.publicSodiumTable.put(Vars.callWith, receivedKey);
//						Vars.publicSodiumDumps.put(Vars.callWith, receivedKeyDump);
						SQLiteDb.getInstance(getApplication()).insertPublicKey(Vars.callWith, receivedKey);
						expectedKey = receivedKey;
					}

					//first person gets to choose the key for the call
					if(isCallInitiator)
					{
						//choose sodium key
						Vars.voiceSymmetricKey = lazySodium.randomBytesBuf(SecretBox.KEYBYTES);
{'fraction_non_alphanumeric': 0.09615384615384616, 'fraction_numerical': 0.0017857142857142857, 'mean_word_length': 3.8135537190082647, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, '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': '2276097', 'n_tokens_mistral': 5328, 'n_tokens_neox': 4351, 'n_words': 1110}