diff --git a/src/Client.js b/src/Client.js index 722e3bdc7..cb8672af2 100644 --- a/src/Client.js +++ b/src/Client.js @@ -21,7 +21,7 @@ export type ClientQuery = {| app: string, os: string, device: string, - device_id: ?string, + device_id: string, |}; type RequestMetadata = {method: string, id: number, params: ?Object}; diff --git a/src/server.js b/src/server.js index 4fb96ad89..25d668908 100644 --- a/src/server.js +++ b/src/server.js @@ -181,9 +181,11 @@ export default class Server extends EventEmitter { subscriber.onSubscribe(); this.certificateProvider .processCertificateSigningRequest(csr, clientData.os, destination) - .then(_ => { + .then(result => { subscriber.onComplete({ - data: JSON.stringify({}), + data: JSON.stringify({ + deviceId: result.deviceId, + }), metadata: '', }); }) @@ -241,7 +243,7 @@ export default class Server extends EventEmitter { addConnection(conn: ReactiveSocket, query: ClientQuery): Client { invariant(query, 'expected query'); - const id = `${query.app}-${query.os}-${query.device}`; + const id = `${query.app}-${query.os}-${query.device}-${query.device_id}`; console.debug(`Device connected: ${id}`, 'connection'); const client = new Client(id, query, conn, this.logger); diff --git a/src/utils/CertificateProvider.js b/src/utils/CertificateProvider.js index e9d0f65e6..060cd0514 100644 --- a/src/utils/CertificateProvider.js +++ b/src/utils/CertificateProvider.js @@ -80,7 +80,7 @@ export default class CertificateProvider { csr: string, os: string, appDirectory: string, - ): Promise { + ): Promise<{|deviceId: string|}> { this.ensureOpenSSLIsAvailable(); return this.certificateSetup .then(_ => this.getCACertificate()) @@ -102,7 +102,36 @@ export default class CertificateProvider { csr, os, ), - ); + ) + .then(_ => this.extractAppNameFromCSR(csr)) + .then(appName => this.getTargetDeviceId(os, appName, appDirectory, csr)) + .then(deviceId => { + return { + deviceId, + }; + }); + } + + getTargetDeviceId( + os: string, + appName: string, + appDirectory: string, + csr: string, + ): Promise { + if (os === 'Android') { + return this.getTargetAndroidDeviceId(appName, appDirectory, csr); + } else if (os === 'iOS') { + const matches = /\/Devices\/([^/]+)\//.exec(appDirectory); + if (matches === null || matches.length < 2) { + return Promise.reject( + new Error( + `iOS simulator directory doesn't match expected format: ${appDirectory}`, + ), + ); + } + return Promise.resolve(matches[1]); + } + return Promise.resolve('unknown'); } ensureOpenSSLIsAvailable(): void { @@ -150,7 +179,7 @@ export default class CertificateProvider { if (os === 'Android') { const appNamePromise = this.extractAppNameFromCSR(csr); const deviceIdPromise = appNamePromise.then(app => - this.getTargetDeviceId(app, destination, csr), + this.getTargetAndroidDeviceId(app, destination, csr), ); return Promise.all([deviceIdPromise, appNamePromise]).then( ([deviceId, appName]) => @@ -179,7 +208,7 @@ export default class CertificateProvider { return Promise.reject(new RecurringError(`Unsupported device os: ${os}`)); } - getTargetDeviceId( + getTargetAndroidDeviceId( appName: string, deviceCsrFilePath: string, csr: string, diff --git a/xplat/Sonar/SonarWebSocketImpl.cpp b/xplat/Sonar/SonarWebSocketImpl.cpp index 46727ada0..75468c801 100644 --- a/xplat/Sonar/SonarWebSocketImpl.cpp +++ b/xplat/Sonar/SonarWebSocketImpl.cpp @@ -35,6 +35,7 @@ #define SONAR_CA_FILE_NAME "sonarCA.crt" #define CLIENT_CERT_FILE_NAME "device.crt" #define PRIVATE_KEY_FILE "privateKey.pem" +#define CONNECTION_CONFIG_FILE "connection_config.json" #define WRONG_THREAD_EXIT_MSG \ "ERROR: Aborting sonar initialization because it's not running in the sonar thread." @@ -47,6 +48,7 @@ namespace facebook { namespace sonar { bool fileExists(std::string fileName); +void writeStringToFile(std::string content, std::string fileName); class ConnectionEvents : public rsocket::RSocketConnectionEvents { private: @@ -176,9 +178,12 @@ void SonarWebSocketImpl::doCertificateExchange() { void SonarWebSocketImpl::connectSecurely() { rsocket::SetupParameters parameters; folly::SocketAddress address; + + auto deviceId = getDeviceId(); + parameters.payload = rsocket::Payload(folly::toJson(folly::dynamic::object( "os", deviceData_.os)("device", deviceData_.device)( - "device_id", deviceData_.deviceId)("app", deviceData_.app))); + "device_id", deviceId)("app", deviceData_.app))); address.setFromHostPort(deviceData_.host, securePort); std::shared_ptr sslContext = @@ -242,6 +247,27 @@ void SonarWebSocketImpl::sendMessage(const folly::dynamic& message) { }); } +std::string SonarWebSocketImpl::getDeviceId() { + /* On android we can't reliably get the serial of the current device + So rely on our locally written config, which is provided by the + desktop app. + For backwards compatibility, when this isn't present, fall back to the + unreliable source. */ + auto gettingDeviceId = sonarState_->start("Get deviceId"); + std::string config = loadStringFromFile(absoluteFilePath(CONNECTION_CONFIG_FILE)); + auto maybeDeviceId = folly::parseJson(config)["deviceId"]; + std::string deviceId; + if (maybeDeviceId.isString()) { + deviceId = maybeDeviceId.getString(); + } else { + deviceId = deviceData_.deviceId; + } + if (deviceId.compare("unknown")) { + gettingDeviceId->complete(); + } + return deviceId; +} + bool SonarWebSocketImpl::isCertificateExchangeNeeded() { if (failedConnectionAttempts_ >= 2) { @@ -281,6 +307,8 @@ void SonarWebSocketImpl::requestSignedCertFromSonar() { client_->getRequester() ->requestResponse(rsocket::Payload(folly::toJson(message))) ->subscribe([this, gettingCert](rsocket::Payload p) { + auto response = p.moveDataToString(); + writeStringToFile(response, absoluteFilePath(CONNECTION_CONFIG_FILE)); gettingCert->complete(); SONAR_LOG("Certificate exchange complete."); // Disconnect after message sending is complete. @@ -338,6 +366,11 @@ std::string SonarWebSocketImpl::loadStringFromFile(std::string fileName) { return s; } +void writeStringToFile(std::string content, std::string fileName) { + std::ofstream out(fileName); + out << content; +} + std::string SonarWebSocketImpl::absoluteFilePath(const char* filename) { return std::string(deviceData_.privateAppDirectory + "/sonar/" + filename); } diff --git a/xplat/Sonar/SonarWebSocketImpl.h b/xplat/Sonar/SonarWebSocketImpl.h index 6a76eaddb..5c6030c15 100644 --- a/xplat/Sonar/SonarWebSocketImpl.h +++ b/xplat/Sonar/SonarWebSocketImpl.h @@ -66,6 +66,7 @@ class SonarWebSocketImpl : public SonarWebSocket { bool ensureSonarDirExists(); bool isRunningInOwnThread(); void sendLegacyCertificateRequest(folly::dynamic message); + std::string getDeviceId(); }; } // namespace sonar