Skip to content

Commit

Permalink
Feat/multiple nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
jsamol authored and godenzim committed Sep 9, 2021
1 parent 64c0d21 commit d1d9f1d
Show file tree
Hide file tree
Showing 148 changed files with 5,054 additions and 2,594 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM androidsdk/android-29:latest
FROM androidsdk/android-30:latest

RUN mkdir /build
WORKDIR /build
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath 'com.android.tools.build:gradle:7.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Version.kotlin}"
classpath "org.jetbrains.kotlin:kotlin-serialization:${Version.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.32"
Expand Down
25 changes: 12 additions & 13 deletions buildSrc/src/main/java/GradleConfig.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
object Version {
// TODO: Update to Kotlin 1.5 once KT-46477 (https://youtrack.jetbrains.com/issue/KT-46477) is fixed
const val kotlin = "1.4.32"
const val kotlin = "1.5.30"

const val kotlinSerialization = "1.2.1"
const val kotlinSerialization = "1.2.2"

const val androidxCore = "1.3.2"
const val androidxAppCompat = "1.2.0"
const val androidxConstraintLayout = "2.0.4"
const val androidxCore = "1.6.0"
const val androidxAppCompat = "1.3.1"
const val androidxConstraintLayout = "2.1.0"

const val androidxActivity = "1.2.3"
const val androidxActivity = "1.3.1"
const val androidxLifecycle = "2.3.1"

const val androidxSecurity = "1.0.0"

const val coroutines = "1.5.0"
const val coroutines = "1.5.1"

const val ktor = "1.5.4"
const val ktor = "1.6.2"

const val lazySodium = "5.0.2"
const val jna = "5.8.0"

const val materialComponents = "1.3.0"
const val materialComponents = "1.4.0"

const val junit = "4.13.2"

const val androidxJunit = "1.1.2"
const val androidxEspresso = "3.3.0"
const val androidxJunit = "1.1.3"
const val androidxEspresso = "3.4.0"

const val mockk = "1.11.0"
const val mockk = "1.12.0"
}

object Dependencies {
Expand Down
1 change: 0 additions & 1 deletion core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
apply plugin: 'org.jetbrains.dokka'

Expand Down
9 changes: 1 addition & 8 deletions core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@
<!-- Until `androidx.security:1.1.0` is production ready -->
<uses-sdk tools:overrideLibrary="androidx.security" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<application>
<provider
android:name="it.airgap.beaconsdk.provider.BeaconInitProvider"
android:authorities="${applicationId}.beaconinitprovider"
Expand Down
48 changes: 25 additions & 23 deletions core/src/main/java/it/airgap/beaconsdk/client/BeaconClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package it.airgap.beaconsdk.client

import it.airgap.beaconsdk.data.beacon.*
import it.airgap.beaconsdk.exception.BeaconException
import it.airgap.beaconsdk.internal.BeaconApp
import it.airgap.beaconsdk.internal.BeaconSdk
import it.airgap.beaconsdk.internal.controller.ConnectionController
import it.airgap.beaconsdk.internal.controller.MessageController
import it.airgap.beaconsdk.internal.crypto.Crypto
Expand Down Expand Up @@ -88,16 +88,18 @@ public class BeaconClient internal constructor(
*/
public fun connect(): Flow<Result<BeaconRequest>> =
connectionController.subscribe()
.map { result -> result.flatMapSuspend { messageController.onIncomingMessage(it.origin, it.content) } }
.map { result -> result.flatMap { messageController.onIncomingMessage(it.origin, it.content) } }
.onEach { result -> result.getOrNull()?.let { processMessage(it) } }
.mapNotNull {
when (it) {
is Success -> when (val message = it.value) {
is BeaconRequest -> Result.success(message)
else -> null
}
is Failure -> Result.failure(BeaconException.from(it.error))
}
.mapNotNull { result ->
result.fold(
onSuccess = {
when (it) {
is BeaconRequest -> Result.success(it)
else -> null
}
},
onFailure = { Result.failure(BeaconException.from(it)) }
)
}

/**
Expand All @@ -107,7 +109,7 @@ public class BeaconClient internal constructor(
*/
@Throws(IllegalArgumentException::class, IllegalStateException::class, BeaconException::class)
public suspend fun respond(response: BeaconResponse) {
send(response, isTerminal = true).mapError { BeaconException.from(it) }.get()
send(response, isTerminal = true).mapException { BeaconException.from(it) }.getOrThrow()
}

/**
Expand Down Expand Up @@ -150,7 +152,7 @@ public class BeaconClient internal constructor(
*/
public suspend fun removePeers(peers: List<Peer>) {
storageManager.removePeers(peers)
peers.launch { disconnect(it) }
peers.launchForEach { disconnect(it) }
}

/**
Expand All @@ -161,7 +163,7 @@ public class BeaconClient internal constructor(
public suspend fun removeAllPeers() {
val peers = storageManager.getPeers()
storageManager.removePeers()
peers.launch { disconnect(it) }
peers.launchForEach { disconnect(it) }
}

/**
Expand Down Expand Up @@ -259,37 +261,37 @@ public class BeaconClient internal constructor(
storageManager.removePermissions()
}

private suspend fun processMessage(message: BeaconMessage): InternalResult<Unit> =
private suspend fun processMessage(message: BeaconMessage): Result<Unit> =
when (message) {
is BeaconRequest -> acknowledge(message)
is DisconnectBeaconMessage -> {
removePeer(message.origin.id)
Success()
Result.success()
}
else -> {
/* no action */
Success()
Result.success()
}
}

private suspend fun removePeer(publicKey: String) {
storageManager.removePeers { it.publicKey == publicKey }
}

private suspend fun acknowledge(request: BeaconRequest): InternalResult<Unit> {
private suspend fun acknowledge(request: BeaconRequest): Result<Unit> {
val acknowledgeResponse = AcknowledgeBeaconResponse.from(request, beaconId)
return send(acknowledgeResponse, isTerminal = false)
}

private suspend fun disconnect(peer: Peer): InternalResult<Unit> =
flatTryResult {
val message = DisconnectBeaconMessage(crypto.guid().get(), beaconId, peer.version, Origin.forPeer(peer))
private suspend fun disconnect(peer: Peer): Result<Unit> =
runCatchingFlat {
val message = DisconnectBeaconMessage(crypto.guid().getOrThrow(), beaconId, peer.version, Origin.forPeer(peer))
send(message, isTerminal = true)
}

private suspend fun send(message: BeaconMessage, isTerminal: Boolean): InternalResult<Unit> =
private suspend fun send(message: BeaconMessage, isTerminal: Boolean): Result<Unit> =
messageController.onOutgoingMessage(beaconId, message, isTerminal)
.flatMapSuspend { connectionController.send(BeaconConnectionMessage(it)) }
.flatMap { connectionController.send(BeaconConnectionMessage(it)) }

public companion object {}

Expand Down Expand Up @@ -319,7 +321,7 @@ public class BeaconClient internal constructor(
* Builds a new instance of [BeaconClient].
*/
public suspend fun build(): BeaconClient {
val beaconApp = BeaconApp.instance
val beaconApp = BeaconSdk.instance
val storage = SharedPreferencesStorage.create(beaconApp.applicationContext)
val secureStorage = SharedPreferencesSecureStorage.create(beaconApp.applicationContext)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ import it.airgap.beaconsdk.client.BeaconClient
import it.airgap.beaconsdk.exception.BeaconException
import it.airgap.beaconsdk.message.BeaconMessage
import it.airgap.beaconsdk.message.BeaconResponse
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

/**
* Callback to be invoked when [build] finishes execution.
*/
public interface BuildCallback {
public fun onSuccess(beaconClient: BeaconClient)
public fun onError(error: Throwable)
public fun onCancel() {}
}

/**
Expand All @@ -24,6 +22,7 @@ public interface BuildCallback {
public interface OnNewMessageListener {
public fun onNewMessage(message: BeaconMessage)
public fun onError(error: Throwable)
public fun onCancel() {}
}

/**
Expand All @@ -32,36 +31,56 @@ public interface OnNewMessageListener {
public interface ResponseCallback {
public fun onSuccess()
public fun onError(error: Throwable)
public fun onCancel() {}
}

/**
* Connects with known peers and listens for incoming messages with the given [listener].
*/
public fun BeaconClient.connect(listener: OnNewMessageListener) {
receiveScope {
val listenerId = BeaconCompat.addListener(listener)
BeaconCompat.receiveScope {
try {
connect().collect {
when {
it.isSuccess -> listener.onNewMessage(it.getOrThrow())
it.isFailure -> listener.onError(BeaconException.from(it.exceptionOrNull()))
}
connect().collect { result ->
val listener = BeaconCompat.listeners[listenerId] ?: return@collect
result
.onSuccess { listener.onNewMessage(it) }
.onFailure { listener.onError(BeaconException.from(it)) }
}
} catch (e: CancellationException) {
listener.onCancel()
} catch (e: Exception) {
listener.onError(e)
}
}
}

/**
* Removes the given [listener] from the set of listeners receiving updates on incoming messages.
*/
public fun BeaconClient.disconnect(listener: OnNewMessageListener) {
BeaconCompat.removeListener(listener)
}

/**
* Cancels all listeners and callbacks.
*/
public fun BeaconClient.stop() {
BeaconCompat.cancelScopes()
}

/**
* Sends the [response] in reply to a previously received request and calls the [callback] when finished.
*
* The method will fail if there is no pending request that matches the [response].
*/
public fun BeaconClient.respond(response: BeaconResponse, callback: ResponseCallback) {
sendScope {
BeaconCompat.sendScope {
try {
respond(response)
callback.onSuccess()
} catch (e: CancellationException) {
callback.onCancel()
} catch (e: Exception) {
callback.onError(e)
}
Expand All @@ -72,31 +91,14 @@ public fun BeaconClient.respond(response: BeaconResponse, callback: ResponseCall
* Builds a new instance of [BeaconClient] and calls the [callback] with the result.
*/
public fun BeaconClient.Builder.build(callback: BuildCallback) {
buildScope {
BeaconCompat.buildScope {
try {
val beaconClient = build()
callback.onSuccess(beaconClient)
} catch (e: CancellationException) {
callback.onCancel()
} catch (e: Exception) {
callback.onError(e)
}
}
}


private fun receiveScope(block: suspend () -> Unit) {
jobScope(CoroutineName("BeaconClient#receive"), block)
}

private fun sendScope(block: suspend () -> Unit) {
jobScope(CoroutineName("BeaconClient#send"), block)
}

private fun buildScope(block: suspend () -> Unit) {
jobScope(CoroutineName("BeaconClient.Builder#build"), block)
}

private fun jobScope(coroutineName: CoroutineName, block: suspend () -> Unit) {
CoroutineScope(coroutineName + Dispatchers.Default).launch {
block()
}
}
Loading

0 comments on commit d1d9f1d

Please sign in to comment.