From 2bb99ab3878ea6c6ae058106d84a42539e3b7e5d Mon Sep 17 00:00:00 2001 From: rusefi Date: Sun, 12 Jul 2020 14:33:18 -0400 Subject: [PATCH] proxy progress --- .../java/com/rusefi/MockRusEfiDevice.java | 14 +-- .../src/test/java/com/rusefi/ServerTest.java | 14 ++- java_tools/proxy_server/proxy_server.iml | 1 + .../main/java/com/rusefi/server/Backend.java | 8 +- .../rusefi/server/ClientConnectionState.java | 29 +++--- .../com/rusefi/server/ControllerInfo.java | 97 +++++++++++++++++++ .../com/rusefi/server/SessionDetails.java | 80 +++++++++++++++ .../java/com/rusefi/server/UserDetails.java | 12 +-- .../com/rusefi/server/SessionDetailsTest.java | 21 ++++ 9 files changed, 243 insertions(+), 33 deletions(-) create mode 100644 java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerInfo.java create mode 100644 java_tools/proxy_server/src/main/java/com/rusefi/server/SessionDetails.java create mode 100644 java_tools/proxy_server/src/test/java/com/rusefi/server/SessionDetailsTest.java diff --git a/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java b/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java index aa829dfd6e..615f8f2940 100644 --- a/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java +++ b/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java @@ -6,6 +6,8 @@ import com.rusefi.config.generated.Fields; import com.rusefi.io.commands.HelloCommand; import com.rusefi.io.tcp.BinaryProtocolServer; import com.rusefi.io.tcp.TcpIoStream; +import com.rusefi.server.ControllerInfo; +import com.rusefi.server.SessionDetails; import java.io.IOException; import java.net.Socket; @@ -15,13 +17,14 @@ import static com.rusefi.io.tcp.BinaryProtocolServer.getPacketLength; import static com.rusefi.io.tcp.BinaryProtocolServer.readPromisedBytes; public class MockRusEfiDevice { - private final String authToken; - private final String signature; + private SessionDetails sessionDetails; private final Logger logger; public MockRusEfiDevice(String authToken, String signature, Logger logger) { - this.authToken = authToken; - this.signature = signature; + ControllerInfo ci = new ControllerInfo("vehicle", "make", "code", signature); + + sessionDetails = new SessionDetails(ci, authToken, SessionDetails.createOneTimeCode()); + this.logger = logger; } @@ -40,9 +43,8 @@ public class MockRusEfiDevice { byte command = payload[0]; - if (command == Fields.TS_HELLO_COMMAND) { - new HelloCommand(logger, authToken + signature).handle(packet, stream); + new HelloCommand(logger, sessionDetails.toJson()).handle(packet, stream); } else { handleCommand(); } diff --git a/java_console/ui/src/test/java/com/rusefi/ServerTest.java b/java_console/ui/src/test/java/com/rusefi/ServerTest.java index 5e0d7f5334..3554efc2ce 100644 --- a/java_console/ui/src/test/java/com/rusefi/ServerTest.java +++ b/java_console/ui/src/test/java/com/rusefi/ServerTest.java @@ -27,7 +27,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import static com.rusefi.binaryprotocol.BinaryProtocol.sleep; import static com.rusefi.server.Backend.LIST_PATH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -49,9 +48,16 @@ public class ServerTest { Function userDetailsResolver = authToken -> new UserDetails(authToken.substring(0, 5), authToken.charAt(6)); CountDownLatch allClientsDisconnected = new CountDownLatch(1); + CountDownLatch onConnected = new CountDownLatch(2); int httpPort = 8000; Backend backend = new Backend(userDetailsResolver, httpPort) { + @Override + public void register(ClientConnectionState clientConnectionState) { + super.register(clientConnectionState); + onConnected.countDown(); + } + @Override public void close(ClientConnectionState inactiveClient) { super.close(inactiveClient); @@ -68,7 +74,7 @@ public class ServerTest { public void run() { ClientConnectionState clientConnectionState = new ClientConnectionState(clientSocket, logger, backend.getUserDetailsResolver()); try { - clientConnectionState.sayHello(); + clientConnectionState.requestControllerInfo(); backend.register(clientConnectionState); clientConnectionState.runEndlessLoop(); @@ -87,9 +93,7 @@ public class ServerTest { new MockRusEfiDevice("00000000-1234-1234-1234-123456789012", "rusEFI 2020.07.06.frankenso_na6.2468827536", logger).connect(serverPort); new MockRusEfiDevice("12345678-1234-1234-1234-123456789012", "rusEFI 2020.07.11.proteus_f4.1986715563", logger).connect(serverPort); - - // todo: technically we should have callbacks for 'connect', will make this better if this would be failing - sleep(Timeouts.SECOND); + assertTrue(onConnected.await(30, TimeUnit.SECONDS)); List clients = backend.getClients(); assertEquals(2, clients.size()); diff --git a/java_tools/proxy_server/proxy_server.iml b/java_tools/proxy_server/proxy_server.iml index 55d1282c89..e317acc97b 100644 --- a/java_tools/proxy_server/proxy_server.iml +++ b/java_tools/proxy_server/proxy_server.iml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java index a2216e0351..444725899f 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java @@ -19,8 +19,9 @@ import java.util.Set; import java.util.function.Function; public class Backend { - public static final String LIST_PATH = "/list_online"; + public static final String VERSION_PATH = "/version"; + public static final String BACKEND_VERSION = "0.0001"; private final FkRegex showOnlineUsers = new FkRegex(LIST_PATH, (Take) req -> getUsersOnline() @@ -33,9 +34,9 @@ public class Backend { for (ClientConnectionState client : clients) { JsonObject clientObject = Json.createObjectBuilder() - .add(UserDetails.USER_ID, client.getUserDetails().getId()) + .add(UserDetails.USER_ID, client.getUserDetails().getUserId()) .add(UserDetails.USERNAME, client.getUserDetails().getUserName()) - .add("signature", client.getSignature()) + .add(ControllerInfo.SIGNATURE, client.getSessionDetails().getControllerInfo().getSignature()) .build(); builder.add(clientObject); } @@ -58,6 +59,7 @@ public class Backend { try { new FtBasic( new TkFork(showOnlineUsers, + new FkRegex(VERSION_PATH, BACKEND_VERSION), new FkRegex("/", "rusEFI Online") ), httpPort ).start(Exit.NEVER); diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java index 18f164518f..b018efc1c1 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java @@ -10,7 +10,6 @@ import com.rusefi.io.commands.HelloCommand; import com.rusefi.io.tcp.TcpIoStream; import java.io.Closeable; -import java.io.EOFException; import java.io.IOException; import java.net.Socket; import java.util.function.Function; @@ -22,12 +21,17 @@ public class ClientConnectionState { private final Logger logger; private final Function userDetailsResolver; - private long lastActivityTimestamp; private boolean isClosed; private IoStream stream; private IncomingDataBuffer incomingData; + /** + * Data from controller + */ + private SessionDetails sessionDetails; + /** + * user info from rusEFI database based on auth token + */ private UserDetails userDetails; - private String signature; public ClientConnectionState(Socket clientSocket, Logger logger, Function userDetailsResolver) { this.clientSocket = clientSocket; @@ -55,19 +59,18 @@ public class ClientConnectionState { close(clientSocket); } - public void sayHello() throws IOException { + public void requestControllerInfo() throws IOException { HelloCommand.send(stream, logger); byte[] response = incomingData.getPacket(logger, "", false); if (!checkResponseCode(response, BinaryProtocolCommands.RESPONSE_OK)) return; - String tokenAndSignature = new String(response, 1, response.length - 1); - String authToken = tokenAndSignature.length() > AutoTokenUtil.TOKEN_LENGTH ? tokenAndSignature.substring(0, AutoTokenUtil.TOKEN_LENGTH) : null; - if (!AutoTokenUtil.isToken(authToken)) - throw new IOException("Invalid token"); - signature = tokenAndSignature.substring(AutoTokenUtil.TOKEN_LENGTH); + String jsonString = new String(response, 1, response.length - 1); + sessionDetails = SessionDetails.valueOf(jsonString); + if (!AutoTokenUtil.isToken(sessionDetails.getAuthToken())) + throw new IOException("Invalid token in " + jsonString); - logger.info(authToken + " New client: " + signature); - userDetails = userDetailsResolver.apply(authToken); + logger.info(sessionDetails.getAuthToken() + " New client: " + sessionDetails.getControllerInfo()); + userDetails = userDetailsResolver.apply(sessionDetails.getAuthToken()); logger.info("User " + userDetails); } @@ -75,8 +78,8 @@ public class ClientConnectionState { return userDetails; } - public String getSignature() { - return signature; + public SessionDetails getSessionDetails() { + return sessionDetails; } private static void close(Closeable closeable) { diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerInfo.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerInfo.java new file mode 100644 index 0000000000..cac3469a77 --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerInfo.java @@ -0,0 +1,97 @@ +package com.rusefi.server; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.StringReader; +import java.util.Objects; + +/** + * Controller description without any sensitive information + */ +public class ControllerInfo { + public static final String VEHICLE_NAME = "vehicleName"; + public static final String ENGINE_MAKE = "engineMake"; + public static final String ENGINE_CODE = "engineCode"; + public static final String SIGNATURE = "signature"; + + private final String vehicleName; + private final String engineMake; + private final String engineCode; + private final String signature; + + public ControllerInfo(String vehicleName, String engineCode, String engineMake, String signature) { + Objects.requireNonNull(vehicleName); + Objects.requireNonNull(engineMake); + Objects.requireNonNull(engineCode); + + this.vehicleName = vehicleName; + this.engineCode = engineCode; + this.engineMake = engineMake; + this.signature = signature; + } + + @Override + public String toString() { + return "ControllerInfo{" + + "vehicleName='" + vehicleName + '\'' + + ", engineMake='" + engineMake + '\'' + + ", engineCode='" + engineCode + '\'' + + ", signature='" + signature + '\'' + + '}'; + } + + public String getVehicleName() { + return vehicleName; + } + + public String getEngineMake() { + return engineMake; + } + + public String getEngineCode() { + return engineCode; + } + + public String getSignature() { + return signature; + } + + public static ControllerInfo valueOf(String jsonString) { + JsonReader reader = Json.createReader(new StringReader(jsonString)); + + JsonObject jsonObject = reader.readObject(); + String vehicleName = jsonObject.getString(VEHICLE_NAME); + String engineMake = jsonObject.getString(ENGINE_MAKE); + String engineCode = jsonObject.getString(ENGINE_CODE); + String signature = jsonObject.getString(SIGNATURE); + + return new ControllerInfo(vehicleName, engineCode, engineMake, signature); + } + + public String toJson() { + JsonObject jsonObject = Json.createObjectBuilder() + .add(ENGINE_MAKE, engineMake) + .add(ENGINE_CODE, engineCode) + .add(VEHICLE_NAME, vehicleName) + .add(SIGNATURE, signature) + .build(); + return jsonObject.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ControllerInfo that = (ControllerInfo) o; + return vehicleName.equals(that.vehicleName) && + engineMake.equals(that.engineMake) && + engineCode.equals(that.engineCode) && + signature.equals(that.signature); + } + + @Override + public int hashCode() { + return Objects.hash(vehicleName, engineMake, engineCode, signature); + } +} diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/SessionDetails.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/SessionDetails.java new file mode 100644 index 0000000000..2c2802434a --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/SessionDetails.java @@ -0,0 +1,80 @@ +package com.rusefi.server; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.StringReader; +import java.util.Objects; +import java.util.Random; + +public class SessionDetails { + public static final String ONE_TIME_TOKEN = "oneTime"; + public static final String AUTH_TOKEN = "authToken"; + public static final String CONTROLLER = "controller"; + + private final ControllerInfo controllerInfo; + + private final int oneTimeToken; + private final String authToken; + + public SessionDetails(ControllerInfo controllerInfo, String authToken, int oneTimeCode) { + Objects.requireNonNull(controllerInfo); + Objects.requireNonNull(authToken); + this.controllerInfo = controllerInfo; + this.oneTimeToken = oneTimeCode; + this.authToken = authToken; + } + + public static int createOneTimeCode() { + return new Random().nextInt(60000); + } + + public int getOneTimeToken() { + return oneTimeToken; + } + + public ControllerInfo getControllerInfo() { + return controllerInfo; + } + + public String getAuthToken() { + return authToken; + } + + public String toJson() { + JsonObject jsonObject = Json.createObjectBuilder() + .add(CONTROLLER, controllerInfo.toJson()) + .add(ONE_TIME_TOKEN, oneTimeToken) + .add(AUTH_TOKEN, authToken) + .build(); + return jsonObject.toString(); + } + + public static SessionDetails valueOf(String jsonString) { + JsonReader reader = Json.createReader(new StringReader(jsonString)); + + JsonObject jsonObject = reader.readObject(); + String authToken = jsonObject.getString(AUTH_TOKEN); + int oneTimeCode = jsonObject.getInt(ONE_TIME_TOKEN); + + ControllerInfo controllerInfo = ControllerInfo.valueOf(jsonObject.getString(CONTROLLER)); + + return new SessionDetails(controllerInfo, authToken, oneTimeCode); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SessionDetails that = (SessionDetails) o; + return oneTimeToken == that.oneTimeToken && + controllerInfo.equals(that.controllerInfo) && + authToken.equals(that.authToken); + } + + @Override + public int hashCode() { + return Objects.hash(controllerInfo, oneTimeToken, authToken); + } +} diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java index 2eb08e7186..b1a62d7189 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java @@ -8,11 +8,11 @@ public class UserDetails { public static final String USER_ID = "user_id"; public static final String USERNAME = "username"; private final String userName; - private final int id; + private final int userId; - public UserDetails(String userName, int id) { + public UserDetails(String userName, int userId) { this.userName = userName; - this.id = id; + this.userId = userId; } public static UserDetails valueOf(JSONObject element) { @@ -25,15 +25,15 @@ public class UserDetails { return userName; } - public int getId() { - return id; + public int getUserId() { + return userId; } @Override public String toString() { return "UserDetails{" + "userName='" + userName + '\'' + - ", id=" + id + + ", id=" + userId + '}'; } } diff --git a/java_tools/proxy_server/src/test/java/com/rusefi/server/SessionDetailsTest.java b/java_tools/proxy_server/src/test/java/com/rusefi/server/SessionDetailsTest.java new file mode 100644 index 0000000000..bce93d0b9e --- /dev/null +++ b/java_tools/proxy_server/src/test/java/com/rusefi/server/SessionDetailsTest.java @@ -0,0 +1,21 @@ +package com.rusefi.server; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SessionDetailsTest { + @Test + public void testSerialization() { + ControllerInfo ci = new ControllerInfo("name", "make", "code", "sign"); + + SessionDetails sd = new SessionDetails(ci, "auth", 123); + + String json = sd.toJson(); + + SessionDetails fromJson = SessionDetails.valueOf(json); + + assertEquals(sd, fromJson); + } + +}