REO progress

This commit is contained in:
rusefi 2020-08-14 23:34:53 -04:00
parent 4d003ebe78
commit e249d19077
13 changed files with 167 additions and 59 deletions

View File

@ -33,6 +33,7 @@ import static com.rusefi.binaryprotocol.BinaryProtocol.sleep;
* see NetworkConnectorStartup
*/
public class NetworkConnector implements Closeable {
public static final byte REBOOT = 15;
private final static Logging log = Logging.getLogging(NetworkConnector.class);
private boolean isClosed;
@ -120,7 +121,17 @@ public class NetworkConnector implements Closeable {
@Override
protected void handleCommand(BinaryProtocolServer.Packet packet, TcpIoStream stream) throws IOException {
super.handleCommand(packet, stream);
log.info("Relaying request to controller " + BinaryProtocol.findCommand(packet.getPacket()[0]));
byte command = packet.getPacket()[0];
if (command == Fields.TS_ONLINE_PROTOCOL) {
byte connectorCommand = packet.getPacket()[1];
log.info("Got connector command " + packet.getPacket());
if (connectorCommand == NetworkConnector.REBOOT) {
context.onConnectorSoftwareUpdateRequest();
}
return;
}
log.info("Relaying request to controller " + BinaryProtocol.findCommand(command));
targetEcuSocket.sendPacket(packet);
BinaryProtocolServer.Packet response = targetEcuSocket.readPacket();
@ -178,6 +189,7 @@ public class NetworkConnector implements Closeable {
}
};
void onReconnect();
}

View File

@ -1,10 +1,16 @@
package com.rusefi.proxy;
import com.devexperts.logging.Logging;
import com.rusefi.Timeouts;
import com.rusefi.proxy.client.LocalApplicationProxyContext;
import com.rusefi.tools.online.ProxyClient;
import static com.devexperts.logging.Logging.getLogging;
public class NetworkConnectorContext {
private static final Logging log = getLogging(NetworkConnectorContext.class);
private static final int UPDATE_SOFTWARE_EXIT_CODE = 15;
public int reconnectDelay() {
return 15; // this one is seconds
}
@ -25,4 +31,9 @@ public class NetworkConnectorContext {
public int serverPortForControllers() {
return ProxyClient.SERVER_PORT_FOR_CONTROLLERS;
}
public void onConnectorSoftwareUpdateRequest() {
log.info("onConnectorSoftwareUpdateRequest");
System.exit(UPDATE_SOFTWARE_EXIT_CODE);
}
}

View File

@ -9,25 +9,25 @@ public class ApplicationRequest {
private static final String SESSION = "session";
private final SessionDetails sessionDetails;
private final UserDetails targetUser;
private final UserDetails vehicleOwner;
public ApplicationRequest(SessionDetails sessionDetails, UserDetails targetUser) {
public ApplicationRequest(SessionDetails sessionDetails, UserDetails vehicleOwner) {
this.sessionDetails = sessionDetails;
this.targetUser = targetUser;
this.vehicleOwner = vehicleOwner;
}
public SessionDetails getSessionDetails() {
return sessionDetails;
}
public UserDetails getTargetUser() {
return targetUser;
public UserDetails getVehicleOwner() {
return vehicleOwner;
}
public String toJson() {
JSONObject jsonObject = new JSONObject();
jsonObject.put(SESSION, sessionDetails.toJson());
targetUser.put(jsonObject);
vehicleOwner.put(jsonObject);
return jsonObject.toJSONString();
}
@ -45,20 +45,20 @@ public class ApplicationRequest {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApplicationRequest that = (ApplicationRequest) o;
return targetUser == that.targetUser &&
return vehicleOwner == that.vehicleOwner &&
sessionDetails.equals(that.sessionDetails);
}
@Override
public int hashCode() {
return Objects.hash(sessionDetails, targetUser);
return Objects.hash(sessionDetails, vehicleOwner);
}
@Override
public String toString() {
return "ApplicationRequest{" +
"sessionDetails=" + sessionDetails +
", targetUserId=" + targetUser +
", targetUserId=" + vehicleOwner +
'}';
}
}

View File

@ -10,15 +10,15 @@ import java.util.Random;
* A session from Controller, including some sensitive information
*/
public class SessionDetails {
private static final String ONE_TIME_TOKEN = "oneTime";
private static final String AUTH_TOKEN = "authToken";
public static final String VEHICLE_TOKEN = "vehicleToken";
public static final String AUTH_TOKEN = "authToken";
private static final String CONTROLLER = "controller";
private static final String CONNECTOR_VERSION = "connectorVersion";
private static final String HARDCODED_ONE_TIME_CODE = System.getProperty("ONE_TIME_CODE");
private final ControllerInfo controllerInfo;
private final int oneTimeToken;
private final int vehicleToken;
private final String authToken;
private final int consoleVersion;
@ -27,7 +27,7 @@ public class SessionDetails {
Objects.requireNonNull(controllerInfo);
Objects.requireNonNull(authToken);
this.controllerInfo = controllerInfo;
this.oneTimeToken = oneTimeCode;
this.vehicleToken = oneTimeCode;
this.authToken = authToken;
}
@ -36,7 +36,7 @@ public class SessionDetails {
}
public int getOneTimeToken() {
return oneTimeToken;
return vehicleToken;
}
public ControllerInfo getControllerInfo() {
@ -50,7 +50,7 @@ public class SessionDetails {
public String toJson() {
JSONObject jsonObject = new JSONObject();
jsonObject.put(CONTROLLER, controllerInfo.toJson());
jsonObject.put(ONE_TIME_TOKEN, oneTimeToken);
jsonObject.put(VEHICLE_TOKEN, vehicleToken);
jsonObject.put(AUTH_TOKEN, authToken);
jsonObject.put(CONNECTOR_VERSION, consoleVersion);
return jsonObject.toJSONString();
@ -60,7 +60,7 @@ public class SessionDetails {
JSONObject jsonObject = HttpUtil.parse(jsonString);
String authToken = (String) jsonObject.get(AUTH_TOKEN);
long oneTimeCode = (Long)jsonObject.get(ONE_TIME_TOKEN);
long oneTimeCode = (Long)jsonObject.get(VEHICLE_TOKEN);
long connectorVersion = (long) jsonObject.get(CONNECTOR_VERSION);
ControllerInfo controllerInfo = ControllerInfo.valueOf((String) jsonObject.get(CONTROLLER));
@ -73,14 +73,14 @@ public class SessionDetails {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SessionDetails that = (SessionDetails) o;
return oneTimeToken == that.oneTimeToken &&
return vehicleToken == that.vehicleToken &&
controllerInfo.equals(that.controllerInfo) &&
authToken.equals(that.authToken);
}
@Override
public int hashCode() {
return Objects.hash(controllerInfo, oneTimeToken, authToken);
return Objects.hash(controllerInfo, vehicleToken, authToken);
}
@Override

View File

@ -2,6 +2,9 @@ package com.rusefi.server;
import org.json.simple.JSONObject;
/**
* rusEFI Online user - ID and username
*/
public class UserDetails {
public static final String USER_ID = "user_id";
public static final String USERNAME = "username";

View File

@ -19,7 +19,8 @@ public class ProxyClient {
public static final String LIST_CONTROLLERS_PATH = "/list_controllers";
public static final String LIST_APPLICATIONS_PATH = "/list_applications";
public static final String VERSION_PATH = "/version";
public static final String BACKEND_VERSION = "0.0001";
public static final String UPDATE_CONNECTOR_SOFTWARE = "/update_connector_software";
public static final String BACKEND_VERSION = "0.0002";
public static final String IS_USED = "isUsed";
public static final String OWNER = "owner";
/**
@ -49,10 +50,10 @@ public class ProxyClient {
JSONObject element = (JSONObject) array.get(i);
ControllerInfo ci = ControllerInfo.valueOf(element);
UserDetails userDetails = UserDetails.valueOf(element);
UserDetails vehicleOwner = UserDetails.valueOf(element);
boolean isUsed = (Boolean) element.get(IS_USED);
String ownerName = (String) element.get(OWNER);
userLists.add(new PublicSession(userDetails, ci, isUsed, ownerName));
userLists.add(new PublicSession(vehicleOwner, ci, isUsed, ownerName));
}
System.out.println("object=" + array);

View File

@ -4,20 +4,26 @@ import com.rusefi.server.ControllerInfo;
import com.rusefi.server.UserDetails;
public class PublicSession {
private final UserDetails userDetails;
/**
* owner of physical ECU/vehicle
*/
private final UserDetails vehicleOwner;
private final ControllerInfo controllerInfo;
private final boolean isUsed;
private final String ownerName;
/**
* Person currently in control of tuning session
*/
private final String tunerName;
public PublicSession(UserDetails userDetails, ControllerInfo controllerInfo, boolean isUsed, String ownerName) {
this.userDetails = userDetails;
public PublicSession(UserDetails vehicleOwner, ControllerInfo controllerInfo, boolean isUsed, String tunerName) {
this.vehicleOwner = vehicleOwner;
this.controllerInfo = controllerInfo;
this.isUsed = isUsed;
this.ownerName = ownerName;
this.tunerName = tunerName;
}
public UserDetails getUserDetails() {
return userDetails;
public UserDetails getVehicleOwner() {
return vehicleOwner;
}
public ControllerInfo getControllerInfo() {
@ -28,17 +34,17 @@ public class PublicSession {
return isUsed;
}
public String getOwnerName() {
return ownerName;
public String getTunerName() {
return tunerName;
}
@Override
public String toString() {
return "PublicSession{" +
"userDetails=" + userDetails +
"userDetails=" + vehicleOwner +
", controllerInfo=" + controllerInfo +
", isUsed=" + isUsed +
", ownerName='" + ownerName + '\'' +
", ownerName='" + tunerName + '\'' +
'}';
}
}

View File

@ -15,11 +15,24 @@ import com.rusefi.proxy.client.LocalApplicationProxy;
import com.rusefi.proxy.client.LocalApplicationProxyContext;
import com.rusefi.server.*;
import com.rusefi.tools.online.HttpUtil;
import com.rusefi.tools.online.ProxyClient;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -94,11 +107,18 @@ public class FullServerTest {
ConfigurationImage controllerImage = prepareImage(value, createIniField(Fields.CYLINDERSCOUNT));
TestHelper.createVirtualController(controllerPort, controllerImage, new BinaryProtocolServer.Context());
CountDownLatch softwareUpdateRequest = new CountDownLatch(1);
NetworkConnectorContext networkConnectorContext = new NetworkConnectorContext() {
@Override
public int serverPortForControllers() {
return serverPortForControllers;
}
@Override
public void onConnectorSoftwareUpdateRequest() {
softwareUpdateRequest.countDown();
}
};
// start "rusEFI network connector" to connect controller with backend since in real life controller has only local serial port it does not have network
@ -146,6 +166,22 @@ public class FullServerTest {
assertTrue("applicationClosed", applicationClosed.await(3 * applicationTimeout, TimeUnit.MILLISECONDS));
assertEquals("applications size", 0, backend.getApplications().size());
HttpPost httpPost = new HttpPost(ProxyClient.getHttpAddress(httpPort) + ProxyClient.UPDATE_CONNECTOR_SOFTWARE);
List<NameValuePair> form = new ArrayList<>();
form.add(new BasicNameValuePair("json", applicationRequest.toJson()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(form, Consts.UTF_8);
httpPost.setEntity(entity);
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(httpPost);
log.info(response.toString());
assertTrue("update requested", softwareUpdateRequest.await(3 * applicationTimeout, TimeUnit.MILLISECONDS));
}
}
}

View File

@ -16,6 +16,7 @@ import com.rusefi.shared.FileUtil;
import com.rusefi.tools.online.ProxyClient;
import net.jcip.annotations.GuardedBy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.takes.Take;
import org.takes.facets.fork.FkRegex;
import org.takes.facets.fork.TkFork;
@ -107,7 +108,7 @@ public class Backend implements Closeable {
showOnlineApplications,
new Monitoring(this).showStatistics,
new FkRegex(ProxyClient.VERSION_PATH, ProxyClient.BACKEND_VERSION),
new FkRegex("/update", new UpdateRequestHandler()),
new FkRegex(ProxyClient.UPDATE_CONNECTOR_SOFTWARE, new UpdateRequestHandler(this)),
new FkRegex("/", new RsHtml("<html><body>\n" +
"<br/><a href='https://rusefi.com/online/'>rusEFI Online</a>\n" +
"<br/><br/><br/>\n" +
@ -191,7 +192,7 @@ public class Backend implements Closeable {
return;
}
ControllerKey controllerKey = new ControllerKey(applicationRequest.getTargetUser().getUserId(), applicationRequest.getSessionDetails().getControllerInfo());
ControllerKey controllerKey = new ControllerKey(applicationRequest.getVehicleOwner().getUserId(), applicationRequest.getSessionDetails().getControllerInfo());
ControllerConnectionState state;
synchronized (lock) {
state = acquire(controllerKey, userDetails);
@ -217,7 +218,8 @@ public class Backend implements Closeable {
}, serverPortForApplications, "ApplicationServer", serverSocketCreationCallback, BinaryProtocolServer.SECURE_SOCKET_FACTORY);
}
private ControllerConnectionState acquire(ControllerKey controllerKey, UserDetails userDetails) {
@Nullable
public ControllerConnectionState acquire(ControllerKey controllerKey, UserDetails userDetails) {
synchronized (lock) {
ControllerConnectionState state = controllersByKey.get(controllerKey);
if (state == null) {

View File

@ -3,11 +3,13 @@ package com.rusefi.server;
import com.devexperts.logging.Logging;
import com.rusefi.auth.AutoTokenUtil;
import com.rusefi.binaryprotocol.IncomingDataBuffer;
import com.rusefi.config.generated.Fields;
import com.rusefi.core.SensorsHolder;
import com.rusefi.io.IoStream;
import com.rusefi.io.commands.GetOutputsCommand;
import com.rusefi.io.commands.HelloCommand;
import com.rusefi.io.tcp.TcpIoStream;
import com.rusefi.proxy.NetworkConnector;
import com.rusefi.shared.FileUtil;
import org.jetbrains.annotations.NotNull;
@ -142,4 +144,11 @@ public class ControllerConnectionState {
backend.close(this);
}
}
public void requestConnectorSoftwareUpdate() throws IOException {
byte[] packet = new byte[2];
packet[0] = Fields.TS_ONLINE_PROTOCOL;
packet[1] = NetworkConnector.REBOOT;
stream.sendPacket(packet);
}
}

View File

@ -36,17 +36,17 @@ public class TwoKindSemaphore {
/**
* @return true if acquired successfully, false if not
* @param userDetails
* @param tuner
*/
public boolean acquireForLongTermUsage(UserDetails userDetails) {
return acquireForLongTermUsage(userDetails, 10);
public boolean acquireForLongTermUsage(UserDetails tuner) {
return acquireForLongTermUsage(tuner, 10);
}
public boolean acquireForLongTermUsage(UserDetails userDetails, int timeout) {
public boolean acquireForLongTermUsage(UserDetails tuner, int timeout) {
try {
boolean isAcquired = semaphore.tryAcquire(LONG_TERM, timeout, TimeUnit.SECONDS);
if (isAcquired) {
owner = userDetails;
owner = tuner;
}
return isAcquired;
} catch (InterruptedException e) {

View File

@ -16,8 +16,11 @@ import static com.devexperts.logging.Logging.getLogging;
public class UpdateRequestHandler implements Take {
private static final Logging log = getLogging(UpdateRequestHandler.class);
private static final String AUTH_TOKEN = "auth_token";
private static final String VEHICLE_TOKEN = "vehicle_token";
private final Backend backend;
public UpdateRequestHandler(Backend backend) {
this.backend = backend;
}
@Override
public Response act(Request req) throws IOException {
@ -26,11 +29,36 @@ public class UpdateRequestHandler implements Take {
try {
RqForm rqForm = new RqFormBase(req);
String authToken = get(rqForm, AUTH_TOKEN);
String vehicleToken = get(rqForm, VEHICLE_TOKEN);
log.debug("Update request " + authToken + " " + vehicleToken);
String json = rqForm.param("json").iterator().next();
ApplicationRequest applicationRequest = ApplicationRequest.valueOf(json);
UserDetails tuner = backend.getUserDetailsResolver().apply(applicationRequest.getSessionDetails().getAuthToken());
ControllerKey key = new ControllerKey(applicationRequest.getVehicleOwner().getUserId(), applicationRequest.getSessionDetails().getControllerInfo());
ControllerConnectionState state = backend.acquire(key, tuner);
if (state == null)
throw new IOException("Not acquired " + tuner);
// should controller communication happen on http thread or not?
new Thread(new Runnable() {
@Override
public void run() {
try {
state.requestConnectorSoftwareUpdate();
} catch (IOException e) {
objectBuilder.add("result", "error");
throw new IllegalStateException(e);
}
}
}).start();
log.debug("Update request " + tuner);
} catch (IOException e) {
objectBuilder.add("result", "error: " + e);
return new RsJson(objectBuilder.build());
}

View File

@ -117,7 +117,7 @@ public class RemoteTab {
if (currentState == null) {
requestListDownload();
} else {
setConnectedStatus(currentState.getApplicationRequest().getTargetUser(), null);
setConnectedStatus(currentState.getApplicationRequest().getVehicleOwner(), null);
}
}
@ -159,17 +159,17 @@ public class RemoteTab {
private JComponent createSessionControl(PublicSession publicSession) {
JComponent topLine = new JPanel(new FlowLayout());
topLine.add(new JLabel(publicSession.getUserDetails().getUserName()));
topLine.add(new JLabel(publicSession.getVehicleOwner().getUserName()));
ControllerInfo controllerInfo = publicSession.getControllerInfo();
topLine.add(new JLabel(controllerInfo.getVehicleName() + " " + controllerInfo.getEngineMake() + " " + controllerInfo.getEngineCode()));
JPanel bottomPanel = new JPanel(new FlowLayout());
if (publicSession.isUsed()) {
bottomPanel.add(new JLabel(" Used by " + publicSession.getOwnerName()));
bottomPanel.add(new JLabel(" Used by " + publicSession.getTunerName()));
} else {
JButton connect = new JButton("Connect to " + publicSession.getUserDetails().getUserName());
connect.addActionListener(event -> connectToProxy(publicSession, controllerInfo));
JButton connect = new JButton("Connect to " + publicSession.getVehicleOwner().getUserName());
connect.addActionListener(event -> connectToProxy(publicSession));
bottomPanel.add(connect);
}
@ -184,22 +184,22 @@ public class RemoteTab {
return userPanel;
}
private void connectToProxy(PublicSession publicSession, ControllerInfo controllerInfo) {
private void connectToProxy(PublicSession publicSession) {
RemoteTabController.INSTANCE.setState(RemoteTabController.State.CONNECTING);
setStatus("Connecting to " + publicSession.getUserDetails().getUserName());
setStatus("Connecting to " + publicSession.getVehicleOwner().getUserName());
LocalApplicationProxy.ConnectionListener connectionListener = (localApplicationProxy, authenticatorToProxyStream) -> {
RemoteTabController.INSTANCE.setConnected(localApplicationProxy);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setConnectedStatus(publicSession.getUserDetails(), authenticatorToProxyStream);
setConnectedStatus(publicSession.getVehicleOwner(), authenticatorToProxyStream);
}
});
};
new Thread(() -> {
runAuthenticator(publicSession, controllerInfo, connectionListener);
runAuthenticator(publicSession, connectionListener);
}, "Authenticator").start();
}
@ -224,11 +224,11 @@ public class RemoteTab {
AutoupdateUtil.trueLayout(list);
}
private void runAuthenticator(PublicSession publicSession, ControllerInfo controllerInfo, LocalApplicationProxy.ConnectionListener connectionListener) {
SessionDetails sessionDetails = new SessionDetails(controllerInfo, AuthTokenPanel.getAuthToken(),
private void runAuthenticator(PublicSession publicSession, LocalApplicationProxy.ConnectionListener connectionListener) {
SessionDetails sessionDetails = new SessionDetails(publicSession.getControllerInfo(), AuthTokenPanel.getAuthToken(),
Integer.parseInt(oneTimePasswordControl.getText()), rusEFIVersion.CONSOLE_VERSION);
ApplicationRequest applicationRequest = new ApplicationRequest(sessionDetails, publicSession.getUserDetails());
ApplicationRequest applicationRequest = new ApplicationRequest(sessionDetails, publicSession.getVehicleOwner());
try {
AtomicReference<ServerSocketReference> serverHolderAtomicReference = new AtomicReference<>();