Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor parameter interface between RN<>module #50

Merged
merged 9 commits into from
Nov 12, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,14 @@ public String getName() {
}

@ReactMethod
public void toggleInProxy(int maxClients, int limitUpstreamBytesPerSecond, int limitDownstreamBytesPerSecond,
String privateKey, Promise promise) {
public void toggleInProxy(ReadableMap params, Promise promise) {
try {
ConduitServiceInteractor.toggleInProxy(getReactApplicationContext(), maxClients,
limitUpstreamBytesPerSecond, limitDownstreamBytesPerSecond, privateKey);
ConduitServiceParameters conduitServiceParameters = ConduitServiceParameters.parse(params);
if (conduitServiceParameters == null) {
throw new IllegalArgumentException("Invalid parameters");
}
ConduitServiceInteractor.toggleInProxy(getReactApplicationContext(), conduitServiceParameters);
promise.resolve(null);

} catch (Exception e) {
MyLog.e(TAG, "Failed to toggle conduit service: " + e);
promise.reject("TOGGLE_SERVICE_ERROR", "Failed to toggle conduit service", e);
Expand All @@ -148,8 +149,11 @@ public void toggleInProxy(int maxClients, int limitUpstreamBytesPerSecond, int l
@ReactMethod
public void paramsChanged(ReadableMap params, Promise promise) {
try {
Map<String, Object> paramsMap = toMap(params);
ConduitServiceInteractor.paramsChanged(getReactApplicationContext(), paramsMap);
ConduitServiceParameters conduitServiceParameters = ConduitServiceParameters.parse(params);
if (conduitServiceParameters == null) {
throw new IllegalArgumentException("Invalid parameters");
}
ConduitServiceInteractor.paramsChanged(getReactApplicationContext(), conduitServiceParameters);
promise.resolve(null);
} catch (Exception e) {
MyLog.e(TAG, "Failed to change conduit service params: " + e);
Expand Down Expand Up @@ -518,33 +522,4 @@ private WritableMap getProxyEventMap(String action, Bundle bundle) {
}
return eventData;
}

private Map<String, Object> toMap(ReadableMap readableMap) {
Map<String, Object> map = new HashMap<>();
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
switch (readableMap.getType(key)) {
case Boolean:
map.put(key, readableMap.getBoolean(key));
break;
case Number:
// Check if the number is an integer or a double
double value = readableMap.getDouble(key);
if (value == Math.rint(value)) { // Check if the number is an integer
map.put(key, (int) value); // Cast to Integer if it's a whole number
} else {
map.put(key, value); // Keep it as Double otherwise
}
break;
case String:
map.put(key, readableMap.getString(key));
break;
default:
map.put(key, null);
break;
}
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -99,9 +97,6 @@ private enum ForegroundServiceState {
// ExecutorService for running the Psiphon in-proxy task
private final ExecutorService executorService = Executors.newSingleThreadExecutor();

// Service parameters passed to the Psiphon tunnel via the config
private ConduitServiceParameters conduitServiceParameters;

private final Handler handler = new Handler(Looper.getMainLooper());

// AIDL binder implementation
Expand Down Expand Up @@ -152,9 +147,13 @@ public Context getContext() {

@Override
public String getPsiphonConfig() {
// Validate passed Conduit parameters
if (!conduitServiceParameters.validateParameters()) {
throw new IllegalStateException("Service parameters are not initialized.");
// Load conduit parameters from shared preferences
ConduitServiceParameters conduitServiceParameters = ConduitServiceParameters.load(getApplicationContext());

if (conduitServiceParameters == null) {
// Log the error and crash the app
MyLog.e(TAG, "Failed to load conduit parameters from shared preferences");
throw new IllegalStateException("Failed to load conduit parameters from shared preferences");
}

// Read the psiphon config from raw res file named psiphon_config
Expand Down Expand Up @@ -200,20 +199,14 @@ public String getPsiphonConfig() {
.put("RotatingSyncFrequency", 0));

// Set inproxy parameters that we stored in shared preferences earlier
String storedProxyPrivateKey = conduitServiceParameters.getProxyPrivateKey();
// Set only if not null, otherwise an ephemeral key will be generated internally
if (storedProxyPrivateKey != null) {
psiphonConfig.put("InproxyProxySessionPrivateKey", storedProxyPrivateKey);
}
// We trust that the parameters are valid as they were validated when they were loaded in the beginning of this method
psiphonConfig.put("InproxyProxySessionPrivateKey", conduitServiceParameters.privateKey());

int storedMaxClients = conduitServiceParameters.getMaxClients();
psiphonConfig.put("InproxyMaxClients", storedMaxClients);
psiphonConfig.put("InproxyMaxClients", conduitServiceParameters.maxClients());

int storedLimitUpstream = conduitServiceParameters.getLimitUpstreamBytes();
psiphonConfig.put("InproxyLimitUpstreamBytesPerSecond", storedLimitUpstream);
psiphonConfig.put("InproxyLimitUpstreamBytesPerSecond", conduitServiceParameters.limitUpstreamBytes());

int storedLimitDownstream = conduitServiceParameters.getLimitDownstreamBytes();
psiphonConfig.put("InproxyLimitDownstreamBytesPerSecond", storedLimitDownstream);
psiphonConfig.put("InproxyLimitDownstreamBytesPerSecond", conduitServiceParameters.limitDownstreamBytes());

// Convert back to json string
return psiphonConfig.toString();
Expand Down Expand Up @@ -277,7 +270,6 @@ public void onApplicationParameters(@NonNull Object o) {
public void onCreate() {
super.onCreate();
MyLog.init(getApplicationContext());
conduitServiceParameters = new ConduitServiceParameters(getApplicationContext());
}

@Override
Expand All @@ -291,7 +283,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
synchronized (this) {
return switch (action) {
case INTENT_ACTION_STOP_SERVICE -> handleStopAction();
case INTENT_ACTION_TOGGLE_IN_PROXY -> handleToggleAction();
case INTENT_ACTION_TOGGLE_IN_PROXY -> handleToggleAction(intent);
case INTENT_ACTION_PARAMS_CHANGED -> handleParamsChangedAction(intent);
case INTENT_ACTION_START_IN_PROXY_WITH_LAST_PARAMS -> handleStartInProxyWithLastParamsAction();
default -> {
Expand All @@ -317,7 +309,7 @@ private int handleStopAction() {
return START_NOT_STICKY;
}

private int handleToggleAction() {
private int handleToggleAction(Intent intent) {
MyLog.i(TAG, "Received toggle action");
ForegroundServiceState state = foregroundServiceState.get();
switch (state) {
Expand All @@ -329,7 +321,20 @@ private int handleToggleAction() {
}
case STOPPED -> {
MyLog.i(TAG, "Service is not running; starting with new parameters.");
startServiceWithParameters(conduitServiceParameters.loadLastKnownParameters());

// Parse the parameters from the intent
ConduitServiceParameters conduitServiceParameters = ConduitServiceParameters.parse(intent);
if (conduitServiceParameters == null) {
MyLog.e(TAG, "Attempted to start service with invalid parameters, crashing the app.");
throw new IllegalStateException("Invalid parameters received");
}

// Store the parameters
conduitServiceParameters.store(getApplicationContext());

// Start the service
startForegroundService();

return START_REDELIVER_INTENT;
}
case STARTING, STOPPING -> {
Expand All @@ -345,10 +350,18 @@ private int handleToggleAction() {

private int handleParamsChangedAction(Intent intent) {
ForegroundServiceState state = foregroundServiceState.get();
Map<String, Object> params = extractParametersFromIntent(intent);

// Parse the parameters from the intent
ConduitServiceParameters conduitServiceParameters = ConduitServiceParameters.parse(intent);

// If the parameters are invalid, crash the app
if (conduitServiceParameters == null) {
MyLog.e(TAG, "Attempted to update parameters with invalid parameters, crashing the app.");
throw new IllegalStateException("Invalid parameters received");
}

// Update and persist parameters, storing whether changes occurred
boolean paramsUpdated = conduitServiceParameters.updateParametersFromMap(params);
boolean paramsUpdated = conduitServiceParameters.store(getApplicationContext());
MyLog.i(TAG, paramsUpdated ? "Parameters updated; changes persisted." : "Parameters update called, but no changes detected.");

// If the service is in the STOPPED state, stop it to prevent it from running unnecessarily
Expand All @@ -368,6 +381,9 @@ private int handleParamsChangedAction(Intent intent) {
} catch (PsiphonTunnel.Exception e) {
MyLog.e(TAG, "Failed to restart psiphon: " + e);

// Stop foreground service if restart failed
stopForegroundService();

// Prepare and deliver failure notification
Bundle extras = new Bundle();
extras.putString("errorMessage", e.getMessage());
Expand All @@ -385,48 +401,20 @@ private int handleStartInProxyWithLastParamsAction() {
ForegroundServiceState state = foregroundServiceState.get();
if (state == ForegroundServiceState.STOPPED) {
MyLog.i(TAG, "Service is stopped; starting with last known parameters.");
startServiceWithParameters(conduitServiceParameters.loadLastKnownParameters());
// Validate the last known parameters before starting the service
ConduitServiceParameters conduitServiceParameters = ConduitServiceParameters.load(getApplicationContext());
if (conduitServiceParameters == null) {
MyLog.e(TAG, "Failed to load conduit parameters from shared preferences; will not start service.");
return START_NOT_STICKY;
}
startForegroundService();
return START_REDELIVER_INTENT;
} else {
MyLog.i(TAG, "Service is not stopped; ignoring start with last parameters action.");
return START_NOT_STICKY;
}
}

private void startServiceWithParameters(Map<String, Object> params) {
conduitServiceParameters.updateParametersFromMap(params);
Utils.setServiceRunningFlag(this, true);
startForegroundService();
}

private Map<String, Object> extractParametersFromIntent(Intent intent) {
// Create a map to hold the parameters from the intent
Map<String, Object> params = new HashMap<>();

// Extract parameters from the intent and put them in the map
if (intent.hasExtra(ConduitServiceInteractor.MAX_CLIENTS)) {
int maxClients = intent.getIntExtra(ConduitServiceInteractor.MAX_CLIENTS, 0);
params.put(ConduitServiceParameters.MAX_CLIENTS_KEY, maxClients);
}

if (intent.hasExtra(ConduitServiceInteractor.LIMIT_UPSTREAM_BYTES)) {
int limitUpstreamBytesPerSecond = intent.getIntExtra(ConduitServiceInteractor.LIMIT_UPSTREAM_BYTES, 0);
params.put(ConduitServiceParameters.LIMIT_UPSTREAM_BYTES_KEY, limitUpstreamBytesPerSecond);
}

if (intent.hasExtra(ConduitServiceInteractor.LIMIT_DOWNSTREAM_BYTES)) {
int limitDownstreamBytesPerSecond = intent.getIntExtra(ConduitServiceInteractor.LIMIT_DOWNSTREAM_BYTES, 0);
params.put(ConduitServiceParameters.LIMIT_DOWNSTREAM_BYTES_KEY, limitDownstreamBytesPerSecond);
}

if (intent.hasExtra(ConduitServiceInteractor.INPROXY_PRIVATE_KEY)) {
String proxyPrivateKey = intent.getStringExtra(ConduitServiceInteractor.INPROXY_PRIVATE_KEY);
params.put(ConduitServiceParameters.INPROXY_PRIVATE_KEY_KEY, proxyPrivateKey);
}

return params;
}

private synchronized void startForegroundService() {
if (!foregroundServiceState.compareAndSet(ForegroundServiceState.STOPPED, ForegroundServiceState.STARTING)) {
MyLog.i(TAG, "Service is not stopped; cannot start.");
Expand All @@ -435,6 +423,9 @@ private synchronized void startForegroundService() {

MyLog.i(TAG, "Starting in-proxy.");

// Also persist the service running flag
Utils.setServiceRunningFlag(this, true);

// Clear error notifications before starting the service
cancelErrorNotifications();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import com.jakewharton.rxrelay2.Relay;

import java.util.List;
import java.util.Map;

import ca.psiphon.conduit.nativemodule.logging.MyLog;
import ca.psiphon.conduit.nativemodule.stats.ProxyActivityStats;
Expand Down Expand Up @@ -117,16 +116,11 @@ public void onReceive(Context context, Intent intent) {
proxyStateRelay.accept(ProxyState.unknown());
}

public static void toggleInProxy(Context context, int maxClients, int limitUpstreamBytesPerSecond,
int limitDownstreamBytesPerSecond, String privateKey) {
public static void toggleInProxy(Context context, ConduitServiceParameters conduitServiceParameters) {
Intent intent = new Intent(context, ConduitService.class);
intent.setAction(ConduitService.INTENT_ACTION_TOGGLE_IN_PROXY);

// Add parameters to the intent using the keys from ConduitServiceParameters
intent.putExtra(MAX_CLIENTS, maxClients);
intent.putExtra(LIMIT_UPSTREAM_BYTES, limitUpstreamBytesPerSecond);
intent.putExtra(LIMIT_DOWNSTREAM_BYTES, limitDownstreamBytesPerSecond);
intent.putExtra(INPROXY_PRIVATE_KEY, privateKey);
// Add parameters to the intent
conduitServiceParameters.putIntoIntent(intent);

// Send the intent to the service to toggle the proxy
// and let the service handle the logic in onStartCommand
Expand All @@ -142,32 +136,11 @@ public static void startInProxyWithLastKnownParams(Context context) {
sendStartCommandToService(context, intent);
}

public static void paramsChanged(Context context, Map<String, Object> params) {
public static void paramsChanged(Context context, ConduitServiceParameters conduitServiceParameters) {
Intent intent = new Intent(context, ConduitService.class);
intent.setAction(ConduitService.INTENT_ACTION_PARAMS_CHANGED);

// Add known parameters to the intent
if (params.containsKey(MAX_CLIENTS)) {
Integer maxClients = (Integer) params.get(MAX_CLIENTS);
if (maxClients != null) {
intent.putExtra(MAX_CLIENTS, maxClients);
}
}
if (params.containsKey(LIMIT_UPSTREAM_BYTES)) {
Integer limitUpstreamBytes = (Integer) params.get(LIMIT_UPSTREAM_BYTES);
if (limitUpstreamBytes != null) {
intent.putExtra(LIMIT_UPSTREAM_BYTES, limitUpstreamBytes);
}
}
if (params.containsKey(LIMIT_DOWNSTREAM_BYTES)) {
Integer limitDownstreamBytes = (Integer) params.get(LIMIT_DOWNSTREAM_BYTES);
if (limitDownstreamBytes != null) {
intent.putExtra(LIMIT_DOWNSTREAM_BYTES, limitDownstreamBytes);
}
}
if (params.containsKey(INPROXY_PRIVATE_KEY)) {
intent.putExtra(INPROXY_PRIVATE_KEY, (String) params.get(INPROXY_PRIVATE_KEY));
}
// Add parameters to the intent
conduitServiceParameters.putIntoIntent(intent);

// Send the intent to the service to update the parameters
// and let the service handle the logic in onStartCommand
Expand Down
Loading