Skip to content

Commit

Permalink
Remote connection (#722)
Browse files Browse the repository at this point in the history
* Transmit the server uuid and websocket port through the flatbufffer protocol

* Add a class to connect to a server using its address

* Dialog to connect to a remote server

* Pass the url to connect to to studio/vpl through the command line

* [TDM] Use a fixed port

* Protect connection to non-localhost TDM with a password.

The TDM generates a random password (6 alpha numeric characters)
that is displayed in the interface of the localhost Launcher,
and broadcasted on the local network via zeroconf.

In effect this password is only yused by clients outside
of the local network.

The UI of the launcher is modified to add a password field.
Studio and VPL classic are modified to read the password
from the command line.

The JS api is modified to take a password a parameter,
in addition of the endpoint url.

createClient(url) becomes createClient(urlm, password)

Scratch, VPL3 and Blockly are NOT modified and need to be adapted.

* move password display in remote connection view

* add checked box for automatic local network connection, status is store in settings

* Support remote connection on mac.

Because launching a bundle has no support for arguments,
we used an url instead.
This alternative merchanism to launch an application did not have
support for the new parameters (endpoint & password)

We modify the mac-specific code to support any argument,
then wire the new parameters in the Qt applications.

* Always connect to localhost, even if zero conf does not work.

The TDM always tries to initiate a connection to the local server,
so that if zero conf is not working ( server disabled, port blocked,
etc), everything can still works on the local machine.

* Use IP filtering to detect remote clients

Clients from the same subnet can connect to the TDM
without password.

The password is no longer broadcasted on zeroconf

* Let the TDM run even when the avahi deamom is missing

* Fix Windows build

* Extract password from handshake message

* Fix ip v4 mask computation on windows

* Hide the avahi error message when a local client is connected

* Wire the checkbox to let the user disable remote connections

* Disables both remote and network-local connections via a
TDM command line switch. Changing the option restarts the TDM

* Add a delay between the tdm process start and the
local connection attempt

* Minor typos fixes

* Add backup mechanism to display the IP

If the IP cannot be displayed, display
a link to an external website that shows the ip.

Also do not try to show a password before the local endpoint
has started.

* Fix detecting endpoint on same network

* The code was always checking for localhost
* IPV6 ips that could be mapped to IPv4 were not tested
against IPv4 interfaces

* Fix checkbox being inverted

* Handling TDM restart while it has not fully started yet.

* Fix empty password.

* Sometimes the TDM would generate an empty password because of incorrect
generation code.
* Sometimes the client would not receive the password due to the UI
not refreshing

* Cleanup logs

* Update JS API version

* Update blockly

* runtim 5.15 for flatpak

* Fix displaying password.

There were a few issues:

* The launcher was still trying to read the password out of the
zeroconf record. Because this is no longer broadcasted by the TDM,
the password was always set to empty once a discovery record was found.

* The signal that a local connection was established before the
endpoint was fully registered.

* upgrade scratch

* Revert "runtim 5.15 for flatpak"

This reverts commit 58716bc.

* fix scratch and blockly

Co-authored-by: Michael Bonani <[email protected]>
  • Loading branch information
cor3ntin and Michael Bonani authored Sep 16, 2021
1 parent 7efdc0a commit de2d752
Show file tree
Hide file tree
Showing 44 changed files with 997 additions and 133 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ include(CPack)
add_subdirectory(aseba)

if(NOT ANDROID AND NOT IOS)
add_subdirectory(js)
add_subdirectory(js)
endif()

if(NOT ANDROID AND NOT IOS)
Expand Down
6 changes: 6 additions & 0 deletions aseba/clients/qtcommon/MobsyaApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ bool mobsya::MobsyaApplication::event(QEvent* event) {
if(openEvent->url().scheme() == "mobsya" && openEvent->url().path() == "connect-to-device") {

QUrlQuery q(openEvent->url());

if(q.hasQueryItem("endpoint")) {
QByteArray password = q.queryItemValue("password", QUrl::FullyDecoded).toUtf8();
QString endpoint = q.queryItemValue("endpoint", QUrl::FullyDecoded);
Q_EMIT endpointConnectionRequest(endpoint, password);
}
if(q.hasQueryItem("uuid")) {
QUuid id = q.queryItemValue("uuid", QUrl::FullyDecoded);
Q_EMIT deviceConnectionRequest(id);
Expand Down
1 change: 1 addition & 0 deletions aseba/clients/qtcommon/MobsyaApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ class MobsyaApplication : public QApplication {
bool event(QEvent* event) override;
Q_SIGNALS:
void deviceConnectionRequest(QUuid);
void endpointConnectionRequest(QUrl, QByteArray);
};
} // namespace mobsya
16 changes: 16 additions & 0 deletions aseba/clients/studio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <QLibraryInfo>
#include <QDebug>
#include <QCommandLineParser>
#include <QTimer>
#include "MobsyaApplication.h"
#include "MainWindow.h"
#include <aseba/qt-thymio-dm-client-lib/thymiodevicemanagerclient.h>
Expand Down Expand Up @@ -63,7 +64,14 @@ int main(int argc, char* argv[]) {
QCommandLineOption uuid(QStringLiteral("uuid"),
QStringLiteral("Uuid of the target to connect to - can be specified multiple times"),
QStringLiteral("uuid"));
QCommandLineOption ep(QStringLiteral("endpoint"), QStringLiteral("Endpoint to connect to"),
QStringLiteral("endpoint"));

QCommandLineOption password(QStringLiteral("password"), QStringLiteral("Password associated with the endpoint"),
QStringLiteral("password"));
parser.addOption(uuid);
parser.addOption(ep);
parser.addOption(password);
parser.addHelpOption();
parser.process(qApp->arguments());

Expand All @@ -75,9 +83,17 @@ int main(int argc, char* argv[]) {
}

mobsya::ThymioDeviceManagerClient thymioClient;

QString s = parser.value(ep);
if(!s.isEmpty()) {
thymioClient.connectToRemoteUrlEndpoint(s, parser.value("password").toUtf8());
}

Aseba::MainWindow window(thymioClient, targetUuids);
QObject::connect(&app, &mobsya::MobsyaApplication::deviceConnectionRequest, &window,
&Aseba::MainWindow::connectToDevice);
QObject::connect(&app, &mobsya::MobsyaApplication::endpointConnectionRequest, &thymioClient,
&mobsya::ThymioDeviceManagerClient::connectToRemoteUrlEndpoint);
window.show();
return app.exec();
}
10 changes: 5 additions & 5 deletions aseba/clients/vpl/ThymioVPLApplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@

namespace Aseba {

ThymioVPLApplication::ThymioVPLApplication(const QUuid& thymioId)
: m_thymioId(thymioId), vpl(nullptr), allocatedVariablesCount(0) {
ThymioVPLApplication::ThymioVPLApplication(mobsya::ThymioDeviceManagerClient* client, const QUuid& thymioId)
: m_client(client), m_thymioId(thymioId), vpl(nullptr), allocatedVariablesCount(0) {

m_client = new mobsya::ThymioDeviceManagerClient(this);
connect(m_client, &mobsya::ThymioDeviceManagerClient::nodeAdded, this, &ThymioVPLApplication::onNodeChanged);
connect(m_client, &mobsya::ThymioDeviceManagerClient::nodeRemoved, this, &ThymioVPLApplication::onNodeChanged);
connect(m_client, &mobsya::ThymioDeviceManagerClient::nodeModified, this, &ThymioVPLApplication::onNodeChanged);
Expand Down Expand Up @@ -69,8 +68,9 @@ void ThymioVPLApplication::loadAndRun() {
return;
auto code = editor->toPlainText();
m_compilation_watcher.setRequest(m_thymio->load_aseba_code(code.toUtf8()));
connect(&m_compilation_watcher, &mobsya::CompilationRequestWatcher::finished, this, [this]() { m_thymio->run(); },
Qt::UniqueConnection);
connect(
&m_compilation_watcher, &mobsya::CompilationRequestWatcher::finished, this, [this]() { m_thymio->run(); },
Qt::UniqueConnection);
}

void ThymioVPLApplication::stop() {
Expand Down
4 changes: 2 additions & 2 deletions aseba/clients/vpl/ThymioVPLApplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class ThymioVPLApplication : public QSplitter {
Q_OBJECT

public:
ThymioVPLApplication(const QUuid& node);
ThymioVPLApplication(mobsya::ThymioDeviceManagerClient* client, const QUuid& node);
~ThymioVPLApplication() override;
void displayCode(const QList<QString>& code, int line);
void loadAndRun();
Expand Down Expand Up @@ -74,9 +74,9 @@ protected Q_SLOTS:
void toggleFullScreen();

protected:
mobsya::ThymioDeviceManagerClient* m_client;
QUuid m_thymioId;
std::shared_ptr<mobsya::ThymioNode> m_thymio;
mobsya::ThymioDeviceManagerClient* m_client;
mobsya::CompilationRequestWatcher m_compilation_watcher;

bool useAnyTarget; //!< if true, allow to connect to non-Thymoi II targets
Expand Down
27 changes: 24 additions & 3 deletions aseba/clients/vpl/thymiovpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <QLocale>
#include <QLibraryInfo>
#include <QDebug>
#include <QTimer>
#include <iostream>
#include <QCommandLineParser>
#include <QUuid>
Expand All @@ -44,13 +45,18 @@ int main(int argc, char* argv[]) {
QCoreApplication::setOrganizationDomain(ASEBA_ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationName("Thymio VPL");

const QString language(QLocale::system().name());

QCommandLineParser parser;
QCommandLineOption uuid(QStringLiteral("uuid"),
QStringLiteral("Uuid of the target to connect to - can be specified multiple times"),
QStringLiteral("uuid"));
QCommandLineOption ep(QStringLiteral("endpoint"), QStringLiteral("Endpoint to connect to"),
QStringLiteral("endpoint"));
QCommandLineOption password(QStringLiteral("password"), QStringLiteral("Password associated with the endpoint"),
QStringLiteral("password"));

parser.addOption(uuid);
parser.addOption(ep);
parser.addOption(password);
parser.addHelpOption();
parser.process(qApp->arguments());

Expand All @@ -71,9 +77,24 @@ int main(int argc, char* argv[]) {
load_trads("qtabout_", ":/");
load_trads("qtbase_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));

Aseba::ThymioVPLApplication vpl(id);
QVector<QUuid> targetUuids;
for(auto&& id : parser.values(uuid)) {
auto uuid = QUuid::fromString(id);
if(!uuid.isNull())
targetUuids.append(uuid);
}

mobsya::ThymioDeviceManagerClient thymioClient;
QString s = parser.value(ep);
if(!s.isEmpty()) {
thymioClient.connectToRemoteUrlEndpoint(s, parser.value("password").toUtf8());
}

Aseba::ThymioVPLApplication vpl(&thymioClient, id);
QObject::connect(&app, &mobsya::MobsyaApplication::deviceConnectionRequest, &vpl,
&Aseba::ThymioVPLApplication::connectToDevice);
QObject::connect(&app, &mobsya::MobsyaApplication::endpointConnectionRequest, &thymioClient,
&mobsya::ThymioDeviceManagerClient::connectToRemoteUrlEndpoint);
vpl.show();
app.setOverrideCursor(Qt::ArrowCursor);
return app.exec();
Expand Down
23 changes: 18 additions & 5 deletions aseba/flatbuffers/thymio.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace mobsya.fb;

///A node id
table NodeId {
id:[ubyte];
}

//Handshake messages
//The client needs to initiate the session by sending a ConnectionHandshake.
//If the client sends a message before a ConnectionHandshake, the server will
Expand All @@ -27,6 +32,19 @@ table ConnectionHandshake {

//In the server -> client direction, this is set to true if the client is on the same machine as the server
localhostPeer:bool = false;

//In the server -> client direction, associated websocket port
ws_port: uint16 = 0;

//In the server -> client direction, server guid
uuid: NodeId;

// In the server -> client, the password that can be used to connect to the TDM
// This is only sent to local client.

// In the client->server direction, a password to identify the server
// This password is broadcasted over mDNS for lan-local clients
password: string;
}

// The server sends ping at short intervals
Expand All @@ -38,11 +56,6 @@ table DeviceManagerShutdownRequest {
request_id:uint;
}

///A node id
table NodeId {
id:[ubyte];
}

/// The Type of a node
/// A node can designate either a robot, a simulator, each with various capabilities.
enum NodeType : int {
Expand Down
32 changes: 29 additions & 3 deletions aseba/launcher/src/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Launcher::Launcher(ThymioDeviceManagerClient* client, QObject* parent) : QObject
#ifdef Q_OS_ANDROID
setUseLocalBrowser(true);
#endif

setAllowRemoteConnections(true);

readSettings();
}

Expand Down Expand Up @@ -243,7 +246,7 @@ void Launcher::writeSettings(){
QSettings settings("ThymioSuite", "Mobsya");
// the use of local browser available
settings.setValue("mainwindowuseLocalBrowser2",QVariant(getUseLocalBrowser()) );

settings.setValue("allowRemoteConnections",QVariant(getAllowRemoteConnections()) );
}

/* **************
Expand All @@ -253,17 +256,29 @@ void Launcher::writeSettings(){
void Launcher::readSettings(){
QSettings settings("ThymioSuite", "Mobsya");
setUseLocalBrowser(settings.value("mainwindowuseLocalBrowser2",QVariant(useLocalBrowser)).toBool());
setAllowRemoteConnections(settings.value("allowRemoteConnections",QVariant(allowRemoteConnections)).toBool());
}


void Launcher::setUseLocalBrowser(bool checked){
useLocalBrowser = checked;
}

bool Launcher::getUseLocalBrowser(){
bool Launcher::getUseLocalBrowser() const{
return useLocalBrowser;
}

void Launcher::setAllowRemoteConnections(bool allow){
if(allow == allowRemoteConnections)
return;
allowRemoteConnections = allow;
Q_EMIT remoteConnectionsAllowedChanged();
}

bool Launcher::getAllowRemoteConnections() const{
return allowRemoteConnections;
}

QString Launcher::getDownloadPath(const QUrl& url) {
QFileDialog d;
const auto name = url.fileName();
Expand All @@ -289,7 +304,7 @@ QUrl Launcher::webapp_base_url(const QString& name) const {
auto it = default_folder_name.find(name);
if(it == default_folder_name.end())
return {};
for(auto dirpath : webappsFolderSearchPaths()) {
for(const auto & dirpath : webappsFolderSearchPaths()) {
QFileInfo d(dirpath + "/" + it->second);
if(d.exists()) {
auto q = QUrl::fromLocalFile(d.absoluteFilePath());
Expand Down Expand Up @@ -348,6 +363,17 @@ Q_INVOKABLE QString Launcher::filenameForLocale(QString pattern) {
bool Launcher::isZeroconfRunning() const {
return m_client->isZeroconfBrowserConnected();
}

ThymioDeviceManagerClient* Launcher::client() const {
return m_client;
}

RemoteConnectionRequest* Launcher::connectToServer(const QString& host, quint16 port, QByteArray password) const {
auto c = new RemoteConnectionRequest(m_client, host, port, password);
QQmlEngine::setObjectOwnership(c, QQmlEngine::JavaScriptOwnership);
return c;
}

#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
Q_INVOKABLE void Launcher::applicationStateChanged(Qt::ApplicationState state) {
static Qt::ApplicationState lastState = Qt::ApplicationActive;
Expand Down
23 changes: 17 additions & 6 deletions aseba/launcher/src/launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <QObject>
#include <QUrl>
#include <qt-thymio-dm-client-lib/thymiodevicemanagerclient.h>
#include <qt-thymio-dm-client-lib/remoteconnectionrequest.h>
#include <QDir>
#include <QCoreApplication>
#include <QSettings>
Expand All @@ -24,8 +25,10 @@ class Launcher : public QObject {
Q_INVOKABLE bool platformIsAndroid() const;
Q_INVOKABLE bool platformIsLinux() const;
Q_INVOKABLE bool platformHasSerialPorts() const;
Q_INVOKABLE void setUseLocalBrowser(bool checked);
Q_INVOKABLE bool getUseLocalBrowser();
Q_INVOKABLE void setUseLocalBrowser(bool allow);
Q_INVOKABLE bool getUseLocalBrowser() const;
Q_INVOKABLE void setAllowRemoteConnections(bool allow);
Q_INVOKABLE bool getAllowRemoteConnections() const;


#ifdef Q_OS_OSX
Expand All @@ -41,13 +44,17 @@ class Launcher : public QObject {

Q_INVOKABLE QString search_program(const QString& name) const;
Q_INVOKABLE QUrl webapp_base_url(const QString& name) const;
bool openUrlWithExternal(const QUrl& url) const;
bool openUrlWithExternal(const QUrl& url) const;
Q_INVOKABLE bool openUrl(const QUrl& url);
Q_INVOKABLE bool launch_process(const QString& program, const QStringList& args = {}) const;
Q_INVOKABLE QByteArray readFileContent(QString path);
Q_INVOKABLE QString filenameForLocale(QString pattern);
Q_INVOKABLE QString getDownloadPath(const QUrl& url);
Q_INVOKABLE bool launchPlayground() const;

Q_INVOKABLE RemoteConnectionRequest* connectToServer(const QString& host, quint16 port, QByteArray password) const;


#ifdef Q_OS_OSX
bool doLaunchPlaygroundBundle() const;
#endif
Expand All @@ -57,20 +64,24 @@ class Launcher : public QObject {

bool isZeroconfRunning() const;

/* *************
* Launcher Application Settings -
************** */
ThymioDeviceManagerClient* client() const;

/* *************
* Launcher Application Settings -
************** */
void writeSettings();
void readSettings();

Q_SIGNALS:
void zeroconfStatusChanged() const;
void remoteConnectionsAllowedChanged() const;

private:
mobsya::ThymioDeviceManagerClient* m_client;
QStringList applicationsSearchPaths() const;
QStringList webappsFolderSearchPaths() const;
bool useLocalBrowser;
bool allowRemoteConnections;
};


Expand Down
10 changes: 8 additions & 2 deletions aseba/launcher/src/launcher.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <QDir>
#include <QCoreApplication>
#include <QDebug>
#include <QUrl>
#include <QUrlQuery>

#ifdef Q_OS_IOS
#include <WebKit/WebKit.h>
Expand Down Expand Up @@ -300,8 +302,12 @@ auto QStringListToNSArray(const QStringList &list)

NSLog(@"Bundle url %@", url);

QUrl appUrl(QStringLiteral("mobsya:connect-to-device?uuid=%1")
.arg(args.value("uuid").toString()));
QUrl appUrl(QStringLiteral("mobsya:connect-to-device"));
QUrlQuery query;
for(auto it = args.begin(); it != args.end(); ++it) {
query.addQueryItem(it.key(), it->toString());
}
appUrl.setQuery(query);


auto urls = @[appUrl.toNSURL()];
Expand Down
9 changes: 9 additions & 0 deletions aseba/launcher/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ int main(int argc, char** argv) {
mobsya::Launcher launcher(&client);
mobsya::TDMSupervisor supervisor(launcher);
supervisor.startLocalTDM();

// Try to connect to the local server directly in case ZeroConf does
// not work or is disabled
// This forces us to run the TDM on a fixed port
// We wait for the TDM to be started befor attempting a connection.
QObject::connect(&supervisor, &mobsya::TDMSupervisor::started, [&client] {
client.connectToRemoteEndpoint("localhost", 8596);
});

mobsya::ThymioDevicesModel model(client);

QApplication::setWindowIcon(QIcon(":/assets/thymio-launcher.ico"));
Expand Down
Loading

0 comments on commit de2d752

Please sign in to comment.