diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt index 01bac1f08..f858aeaa6 100644 --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -1,248 +1,244 @@ # For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html project(comm CXX C) set(CMAKE_CXX_STANDARD 17) # C0103 is a naming convention, but the variable names which need to be set # are determined by the upstream project # cmake-lint: disable=C0103 # Disable line length as some paths are hard to reduce without becoming cryptic # cmake-lint: disable=C0301 # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.18) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. set(PACKAGE_NAME "comm_jni_module") find_library(log-lib log) find_package(fbjni REQUIRED CONFIG) set(BUILD_TESTING OFF) set(HAVE_SYMBOLIZE OFF) set(WITH_GTEST OFF CACHE BOOL "Use googletest" FORCE) set(WITH_GFLAGS OFF CACHE BOOL "Use gflags" FORCE) # General set(_third_party_dir ${CMAKE_CURRENT_SOURCE_DIR}/build/third-party-ndk) set(_android_build_dir build/${CMAKE_ANDROID_ARCH_ABI}) include(FetchContent) if(CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a) set(Rust_CARGO_TARGET aarch64-linux-android) elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL x86_64) set(Rust_CARGO_TARGET x86_64-linux-android) elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a) set(Rust_CARGO_TARGET armv7-linux-androideabi) endif() string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} CMAKE_HOST_SYSTEM_NAME_LOWER) set(_toolchain_path "$ENV{ANDROID_HOME}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${CMAKE_HOST_SYSTEM_NAME_LOWER}-x86_64/bin" ) if(EXISTS "${_toolchain_path}/${Rust_CARGO_TARGET}-ar") set(AR "${_toolchain_path}/${Rust_CARGO_TARGET}-ar") else() set(AR "${_toolchain_path}/llvm-ar") endif() FetchContent_Declare( Corrosion GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git GIT_TAG v0.2.1 ) FetchContent_MakeAvailable(Corrosion) include(../../../shared/cmake/corrosion-cxx.cmake) -add_library_rust( - PATH ../../native_rust_library - FEATURES android - NAMESPACE comm -) +add_library_rust(PATH ../../native_rust_library NAMESPACE comm) # We're updating parameters below for Cmake's find_OpenSSL() function set(OPENSSL_ROOT_DIR "${_third_party_dir}/openssl/openssl-${OPENSSL_VERSION}/${_android_build_dir}" ) list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}") # Override HAVE_EXECINFO_H in glog's CMakeLists.txt if( CMAKE_ANDROID_ARCH_ABI STREQUAL arm64-v8a OR CMAKE_ANDROID_ARCH_ABI STREQUAL armeabi-v7a ) set(HAVE_EXECINFO_H OFF CACHE BOOL "Whether platform has execinfo.h") endif() add_subdirectory(${_third_party_dir}/glog/glog-${GLOG_VERSION}/) file(GLOB LIBRN_DIR "${REACT_NATIVE_SO_DIR}/${ANDROID_ABI}") if (NOT LIBRN_DIR) # If /${ANDROID_ABI} dir not found, then ${REACT_NATIVE_SO_DIR} is probably: # ReactAndroid/build/react-ndk/exported file(GLOB LIBRN_DIR "${REACT_NATIVE_SO_DIR}") endif () include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/folly-target.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/openssl-target.cmake) add_subdirectory(../../node_modules/olm ./build) set(_node_modules_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../node_modules) set(_react_native_dir ${_node_modules_dir}/react-native) add_subdirectory(../../cpp/CommonCpp/ ${CMAKE_CURRENT_BINARY_DIR}/build/CommonCpp EXCLUDE_FROM_ALL ) file(GLOB SQLCIPHER "${_node_modules_dir}/@commapp/sqlcipher-amalgamation/src/*.c" ) # Add files which aren't a part of comm-tools list(APPEND ANDROID_NATIVE_CODE "./src/cpp/CommSecureStore.cpp" "./src/cpp/DatabaseInitializerJNIHelper.cpp" "./src/cpp/GlobalDBSingleton.cpp" "./src/cpp/Logger.cpp" "./src/cpp/MessageOperationsUtilitiesJNIHelper.cpp" "./src/cpp/PlatformSpecificTools.cpp" "./src/cpp/TerminateApp.cpp" "./src/cpp/ThreadOperationsJNIHelper.cpp" "./src/cpp/jsiInstaller.cpp" "./src/cpp/NotificationsCryptoModuleJNIHelper.cpp" "./src/cpp/StaffUtilsJNIHelper.cpp" "./src/cpp/AESCrypto.cpp" "./src/cpp/CommServicesAuthMetadataEmitter.cpp" ) list(APPEND GENERATED_NATIVE_CODE "../../cpp/CommonCpp/_generated/commJSI-generated.cpp" "../../cpp/CommonCpp/_generated/utilsJSI-generated.cpp" "../../cpp/CommonCpp/_generated/rustJSI-generated.cpp" ) list(APPEND RUST_NATIVE_CODE "../../native_rust_library/RustCallback.cpp" "../../native_rust_library/RustAESCrypto.cpp" "../../native_rust_library/RustCSAMetadataEmitter.cpp" "../../native_rust_library/RustSecureStore.cpp" ) file(GLOB CRYPTO_NATIVE_CODE "../../cpp/CommonCpp/CryptoTools/*.cpp") file(GLOB DB_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/*.cpp") file(GLOB DB_ENTITIES_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/entities/*.cpp") file(GLOB_RECURSE MODULE_NATIVE_CODE "../../cpp/CommonCpp/NativeModules/**/*.cpp") file(GLOB MODULE_ROOT_NATIVE_CODE "../../cpp/CommonCpp/NativeModules/*.cpp") file(GLOB NOTIFICATIONS_NATIVE_CODE "../../cpp/CommonCpp/Notifications/**/*.cpp") add_library( # Sets the name of the library ${PACKAGE_NAME} # Sets the library as a shared library SHARED # React dependencies ${_react_native_dir}/ReactCommon/jsi/jsi/jsi.cpp ${_react_native_dir}/ReactCommon/jsi/jsi/JSIDynamic.cpp ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp ${_react_native_dir}/ReactCommon/react/bridging/LongLivedObject.cpp ${_react_native_dir}/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.cpp # Third party dependencies ${SQLCIPHER} # comm code ${ANDROID_NATIVE_CODE} ${GENERATED_NATIVE_CODE} ${CRYPTO_NATIVE_CODE} ${DB_NATIVE_CODE} ${DB_ENTITIES_NATIVE_CODE} ${MODULE_NATIVE_CODE} ${MODULE_ROOT_NATIVE_CODE} ${TOOLS_NATIVE_CODE} ${NOTIFICATIONS_NATIVE_CODE} ${RUST_NATIVE_CODE} ) set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build) target_include_directories( ${PACKAGE_NAME} PRIVATE # React Native ${_react_native_dir}/React ${_react_native_dir}/React/Base ${_react_native_dir}/ReactCommon ${_react_native_dir}/ReactCommon/jsi ${_react_native_dir}/ReactCommon/callinvoker ${_react_native_dir}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon # SQLCipher amalgamation ${_node_modules_dir}/@commapp/sqlcipher-amalgamation/src # SQLite ORM ../../cpp/third-party/sqlite_orm # symlinked React Native headers ../headers # comm android specific code ./src/cpp # comm native mutual code ../../cpp/CommonCpp/ ../../cpp/CommonCpp/NativeModules ../../cpp/CommonCpp/NativeModules/InternalModules ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/DataStores ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/ThreadOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/MessageOperationsUtilities ../../cpp/CommonCpp/NativeModules/PersistentStorageUtilities/MessageOperationsUtilities/MessageSpecs ../../cpp/CommonCpp/DatabaseManagers ../../cpp/CommonCpp/Notifications ../../cpp/CommonCpp/Notifications/BackgroundDataStorage # native rust library ${native_rust_library_include_dir} ) add_definitions( # SQLCipher -DSQLITE_THREADSAFE=0 -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLCIPHER_CRYPTO_OPENSSL ) target_link_libraries( ${PACKAGE_NAME} fbjni::fbjni android ${log-lib} Folly::folly glog::glog olm openssl-crypto openssl-ssl comm::native_rust_library comm-tools ) # add a dummy library which is required by CallInvokerHolderImpl.java add_library( turbomodulejsijni # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ./src/cpp/dummy.cpp ) diff --git a/native/android/app/build.gradle b/native/android/app/build.gradle index 24fd1b382..a1786d9db 100644 --- a/native/android/app/build.gradle +++ b/native/android/app/build.gradle @@ -1,747 +1,747 @@ apply plugin: "com.android.application" import com.android.build.OutputFile import de.undercouch.gradle.tasks.download.Download import app.comm.gradle.tasks.GitModules /** * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * and bundleReleaseJsAndAssets). * These basically call `react-native bundle` with the correct arguments during the Android build * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the * bundle directly from the development server. Below you can see all the possible configurations * and their defaults. If you decide to add a configuration block, make sure to add it before the * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation. If none specified and * // "index.android.js" exists, it will be used. Otherwise "index.js" is * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * * // https://reactnative.dev/docs/performance#enable-the-ram-format * bundleCommand: "ram-bundle", * * // whether to bundle JS and assets in debug mode * bundleInDebug: false, * * // whether to bundle JS and assets in release mode * bundleInRelease: true, * * // whether to bundle JS and assets in another build variant (if configured). * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants * // The configuration property can be in the following formats * // 'bundleIn${productFlavor}${buildType}' * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, * * // whether to disable dev mode in custom build variants (by default only disabled in release) * // for example: to disable dev mode in the staging build type (if configured) * devDisabledInStaging: true, * // The configuration property can be in the following formats * // 'devDisabledIn${productFlavor}${buildType}' * // 'devDisabledIn${buildType}' * * // the root of your project, i.e. where "package.json" lives * root: "../../", * * // where to put the JS bundle asset in debug mode * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", * * // where to put the JS bundle asset in release mode * jsBundleDirRelease: "$buildDir/intermediates/assets/release", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in debug mode * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", * * // where to put drawable resources / React Native assets, e.g. the ones you use via * // require('./image.png')), in release mode * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", * * // by default the gradle tasks are skipped if none of the JS files or assets change; this means * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. * inputExcludes: ["android/**", "ios/**"], * * // override which node gets called and with what additional arguments * nodeExecutableAndArgs: ["node"], * * // supply additional arguments to the packager * extraPackagerArgs: [] * ] */ project.ext.react = [ enableHermes: true, // clean and rebuild if changing cliPath: ["node", "-e", "console.log(require('react-native/cli').bin);"].execute([], projectDir).text.trim(), ] apply from: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../react.gradle") /** * Set this to true to create two separate APKs instead of one: * - An APK that only works on ARM devices * - An APK that only works on x86 devices * The advantage is the size of the APK is reduced by about 4MB. * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ def enableSeparateBuildPerCPUArchitecture = false /** * Run Proguard to shrink the Java bytecode in release builds. */ def enableProguardInReleaseBuilds = false /** * The preferred build flavor of JavaScriptCore. * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' /** * Whether to enable the Hermes VM. * * This should be set on project.ext.react and that value will be read here. If it is not set * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode * and the benefits of using Hermes will therefore be sharply reduced. */ def enableHermes = project.ext.react.get("enableHermes", false) /** * Architectures to build native code for. */ def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") return value ? value.split(",") : ["armeabi-v7a", "x86_64", "arm64-v8a"] } def customDownloadsDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR") def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") def downloadsDir = customDownloadsDir ? new File(customDownloadsDir) : new File("$buildDir/downloads") def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") task createNativeDepsDirectories { downloadsDir.mkdirs() thirdPartyNdkDir.mkdirs() } def REACT_NATIVE_DIR = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).parent def reactNativeThirdParty = new File("$REACT_NATIVE_DIR/ReactAndroid/src/main/jni/third-party") def reactProperties = new Properties() file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } def FOLLY_VERSION = reactProperties.getProperty("FOLLY_VERSION") def BOOST_VERSION = reactProperties.getProperty("BOOST_VERSION") def DOUBLE_CONVERSION_VERSION = reactProperties.getProperty("DOUBLE_CONVERSION_VERSION") // FOLLY task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz")) } task prepareFolly(dependsOn: [downloadFolly], type: Copy) { from(tarTree(downloadFolly.dest)) from("$reactNativeThirdParty/folly/Android.mk") include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } includeEmptyDirs = false into("$thirdPartyNdkDir/folly") } // GLOG task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz")) } task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) { from(dependenciesPath ?: tarTree(downloadGlog.dest)) include("glog-${GLOG_VERSION}/**/*") includeEmptyDirs = false into("$thirdPartyNdkDir/glog") } // BOOST // The Boost library is a very large download (>100MB). // If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable // and the build will use that. def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH") task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { src("https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION.replace("_", ".")}/source/boost_${BOOST_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz")) } task prepareBoost(dependsOn: [downloadBoost], type: Copy) { from(tarTree(resources.gzip(downloadBoost.dest))) from("$reactNativeThirdParty/boost/Android.mk") include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") includeEmptyDirs = false into("$thirdPartyNdkDir/boost") doLast { file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_${BOOST_VERSION}") } } // DOUBLE-CONVERSION task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz")) } task prepareDoubleConversion(dependsOn: [downloadDoubleConversion], type: Copy) { from(tarTree(downloadDoubleConversion.dest)) from("$reactNativeThirdParty/double-conversion/Android.mk") include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) includeEmptyDirs = false into("$thirdPartyNdkDir/double-conversion") } // OPENSSL def hostSystem = System.getProperty('os.name').toLowerCase(Locale.ROOT) def hostTag = hostSystem.contains('mac') ? 'darwin-x86_64' : 'linux-x86_64' task downloadOpenSSL(dependsOn: createNativeDepsDirectories, type: Download) { src("https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz") onlyIfNewer(true) overwrite(false) dest(new File(downloadsDir, "openssl-${OPENSSL_VERSION}.tar.gz")) } task prepareOpenSSL( dependsOn: dependenciesPath ? [] : [downloadOpenSSL], type: Copy ) { from(dependenciesPath ?: tarTree(downloadOpenSSL.dest)) include("openssl-${OPENSSL_VERSION}/**/*") includeEmptyDirs = false into("${thirdPartyNdkDir}/openssl") } task prebuildOpenSSL(dependsOn: dependenciesPath ? [] : [prepareOpenSSL]) { inputs.properties([ 'openssl.version': OPENSSL_VERSION, 'openssl.abis': getBuildTypeABIs(reactNativeArchitectures()) ]) outputs.dir("${thirdPartyNdkDir}/openssl/openssl-${OPENSSL_VERSION}/build/") .withPropertyName('openssl.output') doFirst { getBuildTypeABIs(reactNativeArchitectures()).each { buildABI -> logger.info("Building OpenSSL library for the ${buildABI}") exec { commandLine './bash/build_openssl.sh', // OPENSSL_SUBMODULE_PATH "${thirdPartyNdkDir}/openssl/openssl-${OPENSSL_VERSION}/", // HOST_TAG hostTag, // ANDROID_ARCH_ABI "${buildABI}", // MIN_SDK_VERSION rootProject.ext.minSdkVersion, // ANDROID_NDK_HOME android.ndkDirectory.absolutePath, // PARALLEL_THREADS Runtime.getRuntime().availableProcessors() } } } } // JNI def REACT_NATIVE_AAR = "${buildDir}/react-native.aar" def extractReactNativeAAR = { buildType -> def suffix = buildType == 'Debug' ? '-debug' : '-release' def rnAARs = fileTree(REACT_NATIVE_DIR).matching { include "**/react-native/**/*${suffix}.aar" } if (rnAARs.isEmpty()) { rnAARs = fileTree(REACT_NATIVE_DIR).matching { include "**/react-native/**/*.aar" } } if (rnAARs.any() && rnAARs.size() > 1) { logger.error("More than one React Native AAR file has been found:") rnAARs.each { println(it) } throw new GradleException( "Multiple React Native AARs found:\n${rnAARs.join("\n")}" + "\nRemove the old ones and try again" ) } def rnAAR = rnAARs.singleFile def file = rnAAR.absoluteFile def packageName = file.name.tokenize('-')[0] copy { from zipTree(file) into REACT_NATIVE_AAR include "jni/**/*" } } task extractReactNativeAARRelease { doLast { extractReactNativeAAR('Release') } } task extractReactNativeAARDebug { doLast { extractReactNativeAAR('Debug') } } task extractAARHeaders { doLast { configurations.extractHeaders.files.each { def file = it.absoluteFile copy { from zipTree(file) into "$buildDir/$file.name" include "**/*.h" } } } } task extractJNIFiles { doLast { configurations.extractJNI.files.each { def file = it.absoluteFile copy { from zipTree(file) into "$buildDir/$file.name" include "jni/**/*" } } } } tasks.whenTaskAdded { task -> if ( !task.name.contains("Clean") && (task.name.contains('externalNativeBuild') || task.name.startsWith('configureCMake') || task.name.startsWith('buildCMake')) ) { def buildType = task.name.endsWith('Debug') ? 'Debug' : 'Release' task.dependsOn(extractAARHeaders) task.dependsOn(extractJNIFiles) task.dependsOn("extractReactNativeAAR${buildType}") } } // EXTERNAL LIBS task prepareExternalLibs { dependsOn prepareFolly dependsOn prepareGlog dependsOn prepareBoost dependsOn prepareDoubleConversion dependsOn prebuildOpenSSL } // Removes the '.cxx' directory to prevent running // ninja clean when the 'clean' command is executed task removeCXX(type: Exec) { commandLine 'rm', '-rf', '.cxx' } // Add cross-compilation targets to Rust toolchain task updateRustToolchain(type: Exec) { commandLine "rustup", "target", "add", "aarch64-linux-android", "armv7-linux-androideabi", "x86_64-linux-android" } def nativeRustLibraryDir = "../../native_rust_library" def nativeRustLibraryManifestPath = "${nativeRustLibraryDir}/Cargo.toml" def cxxBridgeBindingDir = "${nativeRustLibraryDir}/target/cxxbridge/native_rust_library/src" def cxxBridgeCommonDir = "${nativeRustLibraryDir}/target/cxxbridge/rust" task buildNativeRustLibrary(type: Exec) { - commandLine "cargo", "build", "--features", "android", "--manifest-path", nativeRustLibraryManifestPath + commandLine "cargo", "build", "--manifest-path", nativeRustLibraryManifestPath } task copyNativeRustLibraryFiles(dependsOn: buildNativeRustLibrary, type: Copy) { from(cxxBridgeBindingDir) { include 'lib.rs.h' include 'lib.rs.cc' } from(cxxBridgeCommonDir) { include 'cxx.h' } into nativeRustLibraryDir } // Bind preBuild dependencies only if not 'clean' running if (!isCleanRunning()) { afterEvaluate { preBuild.dependsOn(prepareExternalLibs, updateRustToolchain, copyNativeRustLibraryFiles) } } // Run removing CXX task before the clean execution beforeEvaluate { clean.dependsOn(removeCXX) } // Detects are we running the 'clean' commands def isCleanRunning() { gradle.startParameter.taskRequests.any { !it.args.isEmpty() && it.args.first().startsWith('clean') } } // Release keystore via macOS Keychain Access def getPassword(String keyLabel) { if (System.getenv('ANDROID_SIGNING_PASSWORD')) { return System.getenv('ANDROID_SIGNING_PASSWORD') } def stdout = new ByteArrayOutputStream() exec { commandLine 'security', 'find-generic-password', '-wl', keyLabel, '-a', System.properties['user.name'] standardOutput = stdout ignoreExitValue true } return stdout.toString().strip() } // Returns all ABIs architectures for the 'bundleRelease' // or only from 'adb devices' if running debug release. def getBuildTypeABIs(nativeArchitectures) { if (System.getenv("BUILDKITE") == "true") { return ["arm64-v8a"] } def isBundleRelease = gradle.startParameter.taskRequests.any { !it.args.isEmpty() && it.args.first().contains("bundleRelease") } if (isBundleRelease) { // All of the supported ABIs // https://developer.android.com/ndk/guides/abis.html#sa final allAbis = ["armeabi-v7a", "arm64-v8a", "x86_64"] logger.info("Using all architectures to build: ${allAbis}") return allAbis } if (nativeArchitectures) { return nativeArchitectures } // Get current 'adb devices' architectures def commandOutput = new ByteArrayOutputStream() exec { commandLine "./bash/detect_abis.sh" standardOutput = commandOutput } final detectedAbis = commandOutput.toString("UTF-8").trim().tokenize() logger.info("Detected architectures to build: ${detectedAbis}") return detectedAbis } def REACT_NATIVE_SO_DIR = "${REACT_NATIVE_AAR}/jni" android { buildFeatures { prefab true } configurations { all*.exclude module: 'fbjni-java-only' extractHeaders extractJNI } dependencies { implementation 'com.facebook.fbjni:fbjni:0.2.2' compileOnly 'com.facebook.fbjni:fbjni:0.2.2' extractHeaders 'com.facebook.fbjni:fbjni:0.2.2:headers' extractJNI 'com.facebook.fbjni:fbjni:0.2.2' } ndkVersion rootProject.ext.ndkVersion compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { applicationId 'app.comm.android' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 308 versionName '1.0.308' buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { // We configure the CMake build only if you decide to opt-in for the New Architecture. externalNativeBuild { cmake { arguments "-DPROJECT_BUILD_DIR=$buildDir", "-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", "-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build", "-DNODE_MODULES_DIR=$rootDir/../node_modules", "-DANDROID_STL=c++_shared" } } if (!enableSeparateBuildPerCPUArchitecture) { ndk { abiFilters (*reactNativeArchitectures()) } } } missingDimensionStrategy 'react-native-camera', 'general' multiDexEnabled true } if (isNewArchitectureEnabled()) { // We configure the NDK build only if you decide to opt-in for the New Architecture. externalNativeBuild { cmake { path "$projectDir/src/main/jni/CMakeLists.txt" } } def reactAndroidProjectDir = project(':ReactAndroid').projectDir def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") into("$buildDir/react-ndk/exported") } def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") into("$buildDir/react-ndk/exported") } afterEvaluate { // If you wish to add a custom TurboModule or component locally, // you should uncomment this line. // preBuild.dependsOn("generateCodegenArtifactsFromSchema") preDebugBuild.dependsOn(packageReactNdkDebugLibs) preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) // Due to a bug inside AGP, we have to explicitly set a dependency // between configureCMakeDebug* tasks and the preBuild tasks. // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild) configureCMakeDebug.dependsOn(preDebugBuild) reactNativeArchitectures().each { architecture -> tasks.findByName("configureCMakeDebug[${architecture}]")?.configure { dependsOn("preDebugBuild") } tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure { dependsOn("preReleaseBuild") } } } } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include (*reactNativeArchitectures()) } } signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } release { if (project.hasProperty('COMM_UPLOAD_STORE_FILE')) { def password = getPassword('CommAndroidKeyPassword') storeFile file(COMM_UPLOAD_STORE_FILE) storePassword password keyAlias COMM_UPLOAD_KEY_ALIAS keyPassword password } } } buildTypes { final buildABIs = getBuildTypeABIs(reactNativeArchitectures()) release { if (project.hasProperty('COMM_UPLOAD_STORE_FILE')) { signingConfig signingConfigs.release } else { signingConfig signingConfigs.debug } minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ndk { abiFilters = [] abiFilters.addAll(buildABIs) } } debug { signingConfig signingConfigs.debug ndk { abiFilters = [] abiFilters.addAll(buildABIs) } } } packagingOptions { pickFirst "**/libc++_shared.so" pickFirst "**/libfbjni.so" } defaultConfig { externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared", "-DGLOG_VERSION=" + GLOG_VERSION, "-DOPENSSL_VERSION=" + OPENSSL_VERSION, "-DNDK_VERSION=" + rootProject.ext.ndkVersion, "-DREACT_NATIVE_SO_DIR=${REACT_NATIVE_SO_DIR}", "-DBOOST_VERSION=${BOOST_VERSION}", "-DCI=${System.getenv('CI')}" targets "comm_jni_module", "turbomodulejsijni" } } } externalNativeBuild { cmake { path "CMakeLists.txt" version "3.18.1" } } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. def versionCodes = ["armeabi-v7a": 1, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = defaultConfig.versionCode * 1000 + versionCodes.get(abi) } } } afterEvaluate { extractAARHeaders.dependsOn(prepareExternalLibs) extractJNIFiles.dependsOn(prepareExternalLibs) } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" //noinspection GradleDynamicVersion implementation("com.facebook.react:react-native:+") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; // If your app supports Android versions before Ice Cream Sandwich (API level 14) // All fresco packages should use the same version if (isGifEnabled || isWebpEnabled) { implementation 'com.facebook.fresco:fresco:2.5.0' implementation 'com.facebook.fresco:imagepipeline-okhttp3:2.5.0' } if (isGifEnabled) { // For animated gif support implementation 'com.facebook.fresco:animated-gif:2.5.0' } if (isWebpEnabled) { // For webp support implementation 'com.facebook.fresco:webpsupport:2.5.0' if (isWebpAnimatedEnabled) { // Animated webp support implementation 'com.facebook.fresco:animated-webp:2.5.0' } } implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "com.google.android.gms:play-services-base:16.1.0" implementation "com.google.firebase:firebase-core:21.1.0" implementation "com.google.firebase:firebase-messaging:21.1.0" implementation "me.leolin:ShortcutBadger:1.1.21@aar" implementation project(':reactnativekeyboardinput') implementation "androidx.multidex:multidex:2.0.1" implementation "androidx.lifecycle:lifecycle-process:2.5.1" implementation 'com.facebook.fresco:fresco:2.5.0' implementation 'com.facebook.fresco:animated-gif:2.5.0' implementation 'com.facebook.fresco:animated-webp:2.5.0' implementation 'com.facebook.fresco:webpsupport:2.5.0' implementation 'org.conscrypt:conscrypt-android:2.0.0' if (enableHermes) { //noinspection GradleDynamicVersion implementation("com.facebook.react:hermes-engine:+") { // From node_modules exclude group:'com.facebook.fbjni' } } else { implementation jscFlavor } } if (isNewArchitectureEnabled()) { // If new architecture is enabled, we let you build RN from source // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. // This will be applied to all the imported transtitive dependency. configurations.all { resolutionStrategy.dependencySubstitution { substitute(module("com.facebook.react:react-native")) .using(project(":ReactAndroid")) .because("On New Architecture we're building React Native from source") substitute(module("com.facebook.react:hermes-engine")) .using(project(":ReactAndroid:hermes-engine")) .because("On New Architecture we're building Hermes from source") } } } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.implementation into 'libs' } apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); applyNativeModulesAppBuildGradle(project) def isNewArchitectureEnabled() { // To opt-in for the New Architecture, you can either: // - Set `newArchEnabled` to true inside the `gradle.properties` file // - Invoke gradle with `-newArchEnabled=true` // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" } apply plugin: 'com.google.gms.google-services' diff --git a/native/native_rust_library/Cargo.toml b/native/native_rust_library/Cargo.toml index 489cea0d7..e67440594 100644 --- a/native/native_rust_library/Cargo.toml +++ b/native/native_rust_library/Cargo.toml @@ -1,41 +1,38 @@ [package] name = "native_rust_library" version = "0.1.0" edition = "2021" license = "BSD-3-Clause" [dependencies] cxx = "1.0" tokio = { version = "1.24", features = ["macros", "rt-multi-thread"] } tonic = "0.9.1" lazy_static = "1.4" tracing = "0.1" comm-opaque2 = { path = "../../shared/comm-opaque2" } derive_more = "0.99" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" argon2 = { version = "0.5.1", features = ["std"] } grpc_clients = { path = "../../shared/grpc_clients" } base64 = "0.21" [target.'cfg(target_os = "android")'.dependencies] backup_client = { path = "../../shared/backup_client", default-features = false, features = [ "rustls-tls", ] } [target.'cfg(not(target_os = "android"))'.dependencies] backup_client = { path = "../../shared/backup_client", default-features = false, features = [ "native-tls", ] } [build-dependencies] cxx-build = "1.0" regex = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" [lib] crate-type = ["staticlib"] - -[features] -android = [] diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs index 1ea3b1094..53a0e7b40 100644 --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -1,929 +1,926 @@ use backup::ffi::*; use comm_opaque2::client::{Login, Registration}; use comm_opaque2::grpc::opaque_error_to_grpc_status as handle_error; -use ffi::{ - bool_callback, send_auth_metadata_to_js, string_callback, void_callback, -}; +use ffi::{bool_callback, string_callback, void_callback}; use grpc_clients::identity::protos::authenticated::{ OutboundKeyInfo, OutboundKeysForUserRequest, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, }; use grpc_clients::identity::protos::client::{ DeviceKeyUpload, DeviceType, Empty, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest, PreKey, RegistrationFinishRequest, RegistrationStartRequest, WalletLoginRequest, }; use grpc_clients::identity::{ get_auth_client, get_unauthenticated_client, REQUEST_METADATA_COOKIE_KEY, RESPONSE_METADATA_COOKIE_KEY, }; use lazy_static::lazy_static; use serde::Serialize; use std::sync::Arc; use tokio::runtime::{Builder, Runtime}; use tonic::{Request, Status}; use tracing::instrument; mod argon2_tools; mod backup; mod constants; use argon2_tools::compute_backup_key_str; mod generated { // We get the CODE_VERSION from this generated file include!(concat!(env!("OUT_DIR"), "/version.rs")); // We get the IDENTITY_SOCKET_ADDR from this generated file include!(concat!(env!("OUT_DIR"), "/socket_config.rs")); } pub use generated::CODE_VERSION; pub use generated::{BACKUP_SOCKET_ADDR, IDENTITY_SOCKET_ADDR}; -#[cfg(not(feature = "android"))] +#[cfg(not(target_os = "android"))] pub const DEVICE_TYPE: DeviceType = DeviceType::Ios; -#[cfg(feature = "android")] +#[cfg(target_os = "android")] pub const DEVICE_TYPE: DeviceType = DeviceType::Android; lazy_static! { static ref RUNTIME: Arc = Arc::new(Builder::new_multi_thread().enable_all().build().unwrap()); } #[cxx::bridge] mod ffi { extern "Rust" { #[cxx_name = "identityRegisterUser"] fn register_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); #[cxx_name = "identityLoginPasswordUser"] fn login_password_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); #[cxx_name = "identityLoginWalletUser"] fn login_wallet_user( siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, social_proof: String, promise_id: u32, ); #[cxx_name = "identityUpdateUserPassword"] fn update_user_password( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ); #[cxx_name = "identityDeleteUser"] fn delete_user( user_id: String, device_id: String, access_token: String, promise_id: u32, ); #[cxx_name = "identityGetOutboundKeysForUserDevice"] fn get_outbound_keys_for_user_device( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, device_id: String, promise_id: u32, ); #[cxx_name = "identityGenerateNonce"] fn generate_nonce(promise_id: u32); #[cxx_name = "identityVersionSupported"] fn version_supported(promise_id: u32); // Argon2 #[cxx_name = "compute_backup_key"] fn compute_backup_key_str( password: &str, backup_id: &str, ) -> Result<[u8; 32]>; } unsafe extern "C++" { include!("RustCallback.h"); #[namespace = "comm"] #[cxx_name = "stringCallback"] fn string_callback(error: String, promise_id: u32, ret: String); #[namespace = "comm"] #[cxx_name = "voidCallback"] fn void_callback(error: String, promise_id: u32); #[namespace = "comm"] #[cxx_name = "boolCallback"] fn bool_callback(error: String, promise_id: u32, ret: bool); } // AES cryptography #[namespace = "comm"] unsafe extern "C++" { include!("RustAESCrypto.h"); #[allow(unused)] #[cxx_name = "aesGenerateKey"] fn generate_key(buffer: &mut [u8]) -> Result<()>; /// The first two argument aren't mutated but creation of Java ByteBuffer /// requires the underlying bytes to be mutable. #[allow(unused)] #[cxx_name = "aesEncrypt"] fn encrypt( key: &mut [u8], plaintext: &mut [u8], sealed_data: &mut [u8], ) -> Result<()>; /// The first two argument aren't mutated but creation of Java ByteBuffer /// requires the underlying bytes to be mutable. #[allow(unused)] #[cxx_name = "aesDecrypt"] fn decrypt( key: &mut [u8], sealed_data: &mut [u8], plaintext: &mut [u8], ) -> Result<()>; } // Comm Services Auth Metadata Emission #[namespace = "comm"] unsafe extern "C++" { include!("RustCSAMetadataEmitter.h"); #[allow(unused)] #[cxx_name = "sendAuthMetadataToJS"] fn send_auth_metadata_to_js( access_token: String, user_id: String, ) -> Result<()>; } // Backup extern "Rust" { #[cxx_name = "createBackup"] fn create_backup_sync( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, user_data: String, promise_id: u32, ); #[cxx_name = "restoreBackup"] fn restore_backup_sync(backup_secret: String, promise_id: u32); } // Secure store #[namespace = "comm"] unsafe extern "C++" { include!("RustSecureStore.h"); #[allow(unused)] #[cxx_name = "secureStoreSet"] fn secure_store_set(key: &str, value: String) -> Result<()>; #[cxx_name = "secureStoreGet"] fn secure_store_get(key: &str) -> Result; } } fn handle_string_result_as_callback( result: Result, promise_id: u32, ) where E: std::fmt::Display, { match result { Err(e) => string_callback(e.to_string(), promise_id, "".to_string()), Ok(r) => string_callback("".to_string(), promise_id, r), } } fn handle_void_result_as_callback(result: Result<(), E>, promise_id: u32) where E: std::fmt::Display, { match result { Err(e) => void_callback(e.to_string(), promise_id), Ok(_) => void_callback("".to_string(), promise_id), } } fn handle_bool_result_as_callback(result: Result, promise_id: u32) where E: std::fmt::Display, { match result { Err(e) => bool_callback(e.to_string(), promise_id, false), Ok(r) => bool_callback("".to_string(), promise_id, r), } } fn generate_nonce(promise_id: u32) { RUNTIME.spawn(async move { let result = fetch_nonce().await; handle_string_result_as_callback(result, promise_id); }); } async fn fetch_nonce() -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let nonce = identity_client .generate_nonce(Empty {}) .await? .into_inner() .nonce; Ok(nonce) } fn version_supported(promise_id: u32) { RUNTIME.spawn(async move { let result = version_supported_helper().await; handle_bool_result_as_callback(result, promise_id); }); } async fn version_supported_helper() -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client.ping(Empty {}).await; match response { Ok(_) => Ok(true), Err(e) => { if grpc_clients::error::is_version_unsupported(&e) { Ok(false) } else { Err(e.into()) } } } } struct AuthInfo { user_id: String, device_id: String, access_token: String, } #[instrument] fn register_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ) { RUNTIME.spawn(async move { let password_user_info = PasswordUserInfo { username, password, key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }; let result = register_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } struct PasswordUserInfo { username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct UserIDAndDeviceAccessToken { #[serde(rename = "userID")] user_id: String, access_token: String, } async fn register_user_helper( password_user_info: PasswordUserInfo, ) -> Result { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&password_user_info.password) .map_err(handle_error)?; let registration_start_request = RegistrationStartRequest { opaque_registration_request, username: password_user_info.username, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: password_user_info.key_payload, payload_signature: password_user_info.key_payload_signature, social_proof: None, }), content_upload: Some(PreKey { pre_key: password_user_info.content_prekey, pre_key_signature: password_user_info.content_prekey_signature, }), notif_upload: Some(PreKey { pre_key: password_user_info.notif_prekey, pre_key_signature: password_user_info.notif_prekey_signature, }), one_time_content_prekeys: password_user_info.content_one_time_keys, one_time_notif_prekeys: password_user_info.notif_one_time_keys, device_type: DEVICE_TYPE.into(), }), }; let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .register_password_user_start(registration_start_request) .await?; // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let registration_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &password_user_info.password, ®istration_start_response.opaque_registration_response, ) .map_err(handle_error)?; let registration_finish_request = RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }; let mut finish_request = Request::new(registration_finish_request); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } let registration_finish_response = identity_client .register_password_user_finish(finish_request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken { user_id: registration_finish_response.user_id, access_token: registration_finish_response.access_token, }; Ok(serde_json::to_string(&user_id_and_access_token)?) } #[instrument] fn login_password_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ) { RUNTIME.spawn(async move { let password_user_info = PasswordUserInfo { username, password, key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }; let result = login_password_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } async fn login_password_user_helper( password_user_info: PasswordUserInfo, ) -> Result { let mut client_login = Login::new(); let opaque_login_request = client_login .start(&password_user_info.password) .map_err(handle_error)?; let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username: password_user_info.username, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: password_user_info.key_payload, payload_signature: password_user_info.key_payload_signature, social_proof: None, }), content_upload: Some(PreKey { pre_key: password_user_info.content_prekey, pre_key_signature: password_user_info.content_prekey_signature, }), notif_upload: Some(PreKey { pre_key: password_user_info.notif_prekey, pre_key_signature: password_user_info.notif_prekey_signature, }), one_time_content_prekeys: password_user_info.content_one_time_keys, one_time_notif_prekeys: password_user_info.notif_one_time_keys, device_type: DEVICE_TYPE.into(), }), }; let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .login_password_user_start(login_start_request) .await?; // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let login_start_response = response.into_inner(); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) .map_err(handle_error)?; let login_finish_request = OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }; let mut finish_request = Request::new(login_finish_request); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } let login_finish_response = identity_client .login_password_user_finish(finish_request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken { user_id: login_finish_response.user_id, access_token: login_finish_response.access_token, }; Ok(serde_json::to_string(&user_id_and_access_token)?) } struct WalletUserInfo { siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, social_proof: String, } #[instrument] fn login_wallet_user( siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, social_proof: String, promise_id: u32, ) { RUNTIME.spawn(async move { let wallet_user_info = WalletUserInfo { siwe_message, siwe_signature, key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, social_proof, }; let result = login_wallet_user_helper(wallet_user_info).await; handle_string_result_as_callback(result, promise_id); }); } async fn login_wallet_user_helper( wallet_user_info: WalletUserInfo, ) -> Result { let login_request = WalletLoginRequest { siwe_message: wallet_user_info.siwe_message, siwe_signature: wallet_user_info.siwe_signature, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: wallet_user_info.key_payload, payload_signature: wallet_user_info.key_payload_signature, social_proof: Some(wallet_user_info.social_proof), }), content_upload: Some(PreKey { pre_key: wallet_user_info.content_prekey, pre_key_signature: wallet_user_info.content_prekey_signature, }), notif_upload: Some(PreKey { pre_key: wallet_user_info.notif_prekey, pre_key_signature: wallet_user_info.notif_prekey_signature, }), one_time_content_prekeys: wallet_user_info.content_one_time_keys, one_time_notif_prekeys: wallet_user_info.notif_one_time_keys, device_type: DEVICE_TYPE.into(), }), }; let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let login_response = identity_client .login_wallet_user(login_request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken { user_id: login_response.user_id, access_token: login_response.access_token, }; Ok(serde_json::to_string(&user_id_and_access_token)?) } struct UpdatePasswordInfo { user_id: String, device_id: String, access_token: String, password: String, } fn update_user_password( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ) { RUNTIME.spawn(async move { let update_password_info = UpdatePasswordInfo { access_token, user_id, device_id, password, }; let result = update_user_password_helper(update_password_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn update_user_password_helper( update_password_info: UpdatePasswordInfo, ) -> Result<(), Error> { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&update_password_info.password) .map_err(handle_error)?; let update_password_start_request = UpdateUserPasswordStartRequest { opaque_registration_request, }; let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, update_password_info.user_id, update_password_info.device_id, update_password_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .update_user_password_start(update_password_start_request) .await?; // We need to get the load balancer cookie from from the response and send it // in the subsequent request to ensure it is routed to the same identity // service instance as the first request let cookie = response .metadata() .get(RESPONSE_METADATA_COOKIE_KEY) .cloned(); let update_password_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &update_password_info.password, &update_password_start_response.opaque_registration_response, ) .map_err(handle_error)?; let update_password_finish_request = UpdateUserPasswordFinishRequest { session_id: update_password_start_response.session_id, opaque_registration_upload, }; let mut finish_request = Request::new(update_password_finish_request); // Cookie won't be available in local dev environments if let Some(cookie_metadata) = cookie { finish_request .metadata_mut() .insert(REQUEST_METADATA_COOKIE_KEY, cookie_metadata); } identity_client .update_user_password_finish(finish_request) .await?; Ok(()) } fn delete_user( user_id: String, device_id: String, access_token: String, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token, user_id, device_id, }; let result = delete_user_helper(auth_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn delete_user_helper(auth_info: AuthInfo) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; identity_client.delete_user(Empty {}).await?; Ok(()) } struct GetOutboundKeysRequestInfo { user_id: String, device_id: String, } // This struct should not be altered without also updating // OutboundKeyInfoResponse in lib/types/identity-service-types.js #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct OutboundKeyInfoResponse { pub payload: String, pub payload_signature: String, pub social_proof: Option, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, pub one_time_content_prekey: Option, pub one_time_notif_prekey: Option, } impl TryFrom for OutboundKeyInfoResponse { type Error = Error; fn try_from(key_info: OutboundKeyInfo) -> Result { let identity_info = key_info.identity_info.ok_or(Error::MissingResponseData)?; let IdentityKeyInfo { payload, payload_signature, social_proof, } = identity_info; let content_prekey = key_info.content_prekey.ok_or(Error::MissingResponseData)?; let PreKey { pre_key: content_prekey_value, pre_key_signature: content_prekey_signature, } = content_prekey; let notif_prekey = key_info.notif_prekey.ok_or(Error::MissingResponseData)?; let PreKey { pre_key: notif_prekey_value, pre_key_signature: notif_prekey_signature, } = notif_prekey; let one_time_content_prekey = key_info.one_time_content_prekey; let one_time_notif_prekey = key_info.one_time_notif_prekey; Ok(Self { payload, payload_signature, social_proof, content_prekey: content_prekey_value, content_prekey_signature, notif_prekey: notif_prekey_value, notif_prekey_signature, one_time_content_prekey, one_time_notif_prekey, }) } } fn get_outbound_keys_for_user_device( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, device_id: String, promise_id: u32, ) { RUNTIME.spawn(async move { let get_outbound_keys_request_info = GetOutboundKeysRequestInfo { user_id, device_id }; let auth_info = AuthInfo { access_token: auth_access_token, user_id: auth_user_id, device_id: auth_device_id, }; let result = get_outbound_keys_for_user_device_helper( get_outbound_keys_request_info, auth_info, ) .await; handle_string_result_as_callback(result, promise_id); }); } async fn get_outbound_keys_for_user_device_helper( get_outbound_keys_request_info: GetOutboundKeysRequestInfo, auth_info: AuthInfo, ) -> Result { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let mut response = identity_client .get_outbound_keys_for_user(OutboundKeysForUserRequest { user_id: get_outbound_keys_request_info.user_id, }) .await? .into_inner(); let outbound_key_info = OutboundKeyInfoResponse::try_from( response .devices .remove(&get_outbound_keys_request_info.device_id) .ok_or(Error::MissingResponseData)?, )?; Ok(serde_json::to_string(&outbound_key_info)?) } #[derive( Debug, derive_more::Display, derive_more::From, derive_more::Error, )] pub enum Error { #[display(...)] TonicGRPC(Status), #[display(...)] SerdeJson(serde_json::Error), #[display(...)] MissingResponseData, GRPClient(grpc_clients::error::Error), } #[cfg(test)] mod tests { - use super::CODE_VERSION; - use super::{BACKUP_SOCKET_ADDR, IDENTITY_SOCKET_ADDR}; + use super::{BACKUP_SOCKET_ADDR, CODE_VERSION, IDENTITY_SOCKET_ADDR}; #[test] fn test_code_version_exists() { assert!(CODE_VERSION > 0); } #[test] fn test_identity_socket_addr_exists() { assert!(IDENTITY_SOCKET_ADDR.len() > 0); assert!(BACKUP_SOCKET_ADDR.len() > 0); } } diff --git a/shared/cmake/corrosion-cxx.cmake b/shared/cmake/corrosion-cxx.cmake index a271dba86..a129eba10 100644 --- a/shared/cmake/corrosion-cxx.cmake +++ b/shared/cmake/corrosion-cxx.cmake @@ -1,133 +1,130 @@ # Creates a target including rust lib and cxxbridge which is # named as ${NAMESPACE}::${_LIB_PATH_STEM} # <_LIB_PATH_STEM> must match the crate name: # "path/to/myrustcrate" -> "libmyrustcrate.a" function(add_library_rust) - set(value_keywords PATH NAMESPACE FEATURES CXX_BRIDGE_SOURCE_FILE) + set(value_keywords PATH NAMESPACE CXX_BRIDGE_SOURCE_FILE) cmake_parse_arguments( rust_lib "${OPTIONS}" "${value_keywords}" "${MULTI_value_KEYWORDS}" ${ARGN} ) if("${Rust_CARGO_TARGET}" STREQUAL "") message( FATAL_ERROR "Rust_CARGO_TARGET is not detected and empty") endif() if("${rust_lib_PATH}" STREQUAL "") message( FATAL_ERROR "add_library_rust called without a given path to root of a rust crate") endif() if("${rust_lib_NAMESPACE}" STREQUAL "") message( FATAL_ERROR "Must supply a namespace given by keyvalue NAMESPACE ") endif() set(rust_lib_SOURCE_FOLDER "src") if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${rust_lib_PATH}/Cargo.toml") message( FATAL_ERROR "${CMAKE_CURRENT_LIST_DIR}/${rust_lib_PATH} doesn't contain a Cargo.toml") endif() set(lib_path ${rust_lib_PATH}) set(namespace ${rust_lib_NAMESPACE}) set(cxx_bridge_source_file "${rust_lib_SOURCE_FOLDER}/lib.rs") - corrosion_import_crate( - MANIFEST_PATH "${lib_path}/Cargo.toml" - FEATURES "${rust_lib_FEATURES}" - ) + corrosion_import_crate(MANIFEST_PATH "${lib_path}/Cargo.toml") get_filename_component(_LIB_PATH_STEM ${lib_path} NAME) message(STATUS "Library stem path: ${_LIB_PATH_STEM}") # Set AR env var if it's present (Android-specific) if(AR) corrosion_set_env_vars(${_LIB_PATH_STEM} "AR=${AR}") endif() # Resolve directory path which can be consumed globally get_filename_component(REALPATH_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH ) # Set cxxbridge values set( cxx_bridge_binary_folder ${REALPATH_BINARY_DIR}/cargo/build/${Rust_CARGO_TARGET}/cxxbridge) set( common_header ${cxx_bridge_binary_folder}/rust/cxx.h) # We name the variable dynamically in case a single # build has multiple entrypoints into this script # cmake-lint: disable=C0103 set( "${_LIB_PATH_STEM}_include_dir" ${cxx_bridge_binary_folder}/${_LIB_PATH_STEM}/${rust_lib_SOURCE_FOLDER} PARENT_SCOPE) set( binding_header ${${_LIB_PATH_STEM}_include_dir}/${cxx_bridge_source_file}.h) set( binding_source ${cxx_bridge_binary_folder}/${_LIB_PATH_STEM}/${cxx_bridge_source_file}.cc) set( cxx_binding_include_dir ${cxx_bridge_binary_folder}) # Create cxxbridge target add_custom_command( OUTPUT ${common_header} ${binding_header} ${binding_source} COMMAND DEPENDS ${_LIB_PATH_STEM}-static COMMENT "Fixing cmake to find source files" ) add_library(${_LIB_PATH_STEM}_cxxbridge ${common_header} ${binding_header} ${binding_source} ) target_include_directories(${_LIB_PATH_STEM}_cxxbridge PUBLIC ${cxx_binding_include_dir} # Try to pick up any other headers exposed by target ${CMAKE_CURRENT_LIST_DIR}/${rust_lib_PATH} ) # Create total target with alias with given namespace add_library(${_LIB_PATH_STEM}-total INTERFACE) target_link_libraries(${_LIB_PATH_STEM}-total INTERFACE ${_LIB_PATH_STEM}_cxxbridge ${_LIB_PATH_STEM} ) # For end-user to link into project add_library(${namespace}::${_LIB_PATH_STEM} ALIAS ${_LIB_PATH_STEM}-total) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(CARGO_BUILD_TYPE "debug") elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") set(CARGO_BUILD_TYPE "release") endif() if(CI STREQUAL "true" AND (NOT "${CARGO_BUILD_TYPE}" STREQUAL "")) add_custom_command(TARGET ${_LIB_PATH_STEM}_cxxbridge POST_BUILD COMMAND rm -rf cargo/build/${CARGO_BUILD_TYPE} cargo/build/${Rust_CARGO_TARGET}/${CARGO_BUILD_TYPE} COMMENT "Deleting Cargo artifacts for CI build" ) endif() endfunction(add_library_rust)