Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Contact pairing #42

Merged
merged 37 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e6b0651
initial commit with a lot of functionality done
GabeGiro Jul 19, 2023
250f0d6
functionality done without running the app
GabeGiro Jul 20, 2023
eab5bb8
updated a lot. next is migrating database
GabeGiro Jul 20, 2023
43e69d5
updated minor typos and commented some unused code as potential idea …
GabeGiro Jul 20, 2023
320054f
added fallbackToDestructiveMigrationOnDowngrade
GabeGiro Jul 20, 2023
f23c5e2
updated the flow based on few conditions
GabeGiro Jul 20, 2023
913bbbd
updated PairingMatchDataModel and removed switched values in 2 places
GabeGiro Jul 21, 2023
39f267b
updated 1 comment
GabeGiro Jul 21, 2023
b838831
updated one comment
GabeGiro Jul 21, 2023
e8e356a
removed PublicKey from contact entity
GabeGiro Jul 21, 2023
5da4ef0
updated the authorization received
GabeGiro Jul 21, 2023
52851d6
fixed few bugs
GabeGiro Jul 21, 2023
84507fa
optimized sending the pairing request
GabeGiro Jul 21, 2023
bcdef51
removed some comments and added TODOs
GabeGiro Jul 21, 2023
3814924
replaced all address instances with veraId
GabeGiro Jul 21, 2023
1fbb9d9
updated so that receiving messages is only started when the gateway i…
GabeGiro Jul 24, 2023
09a0baf
made sure the messages are received only once and made one part of th…
GabeGiro Jul 24, 2023
bb3f344
updated so that we dont save multiple same contact veraIds
GabeGiro Jul 24, 2023
97b72fd
added few check just to make sure that once the contact is authorized…
GabeGiro Jul 24, 2023
af717bf
setup project and theme properties + splash theme
migulyaev Sep 5, 2023
4c98ce5
registration + waiting for registration + pair with others screens
migulyaev Sep 8, 2023
67e6f39
registration screen ui fixes
migulyaev Sep 8, 2023
12fb7fa
code refactoring
migulyaev Sep 8, 2023
e27ec91
move deprecated code to app-old module
migulyaev Sep 8, 2023
bd5d46a
design fixes
migulyaev Sep 8, 2023
8091693
share id feature
migulyaev Sep 8, 2023
4ebf076
design fixes
migulyaev Sep 8, 2023
63b1a8c
fixes after refactoring
migulyaev Sep 8, 2023
3709c6f
design fix
migulyaev Sep 8, 2023
5aaa659
AwalaManager flow -> channel fix + extract parser of messages to a se…
migulyaev Sep 10, 2023
dcda647
fix linter
migulyaev Sep 10, 2023
eeb5a45
contact pairing
migulyaev Sep 11, 2023
a52afbe
process incoming messages in-band
migulyaev Sep 11, 2023
17938fc
Merge branch 'in-band-message-processing' into contact_pairing
migulyaev Sep 11, 2023
241125c
allow sent multiple contact request / hints on PairWithOthersScreen /…
migulyaev Sep 12, 2023
46da2ad
merge main -> contact_pairing
migulyaev Sep 12, 2023
0838359
fix inserting the same contact multiple times
migulyaev Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/schemas/tech.relaycorp.letro.storage.LetroDatabase/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "f727ada9d0ee39552687eb25fef6f248",
"identityHash": "d5edc6419007841d2365ac43dd8af292",
"entities": [
{
"tableName": "account",
Expand Down Expand Up @@ -38,7 +38,7 @@
},
{
"tableName": "contacts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ownerVeraId` TEXT NOT NULL, `contactVeraId` TEXT NOT NULL, `alias` TEXT, `contactEndpointId` TEXT, `status` TEXT NOT NULL, FOREIGN KEY(`ownerVeraId`) REFERENCES `account`(`veraId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ownerVeraId` TEXT NOT NULL, `contactVeraId` TEXT NOT NULL, `alias` TEXT, `contactEndpointId` TEXT, `status` INTEGER NOT NULL, FOREIGN KEY(`ownerVeraId`) REFERENCES `account`(`veraId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
Expand Down Expand Up @@ -73,7 +73,7 @@
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"affinity": "INTEGER",
"notNull": true
}
],
Expand Down Expand Up @@ -112,7 +112,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f727ada9d0ee39552687eb25fef6f248')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd5edc6419007841d2365ac43dd8af292')"
]
}
}
30 changes: 28 additions & 2 deletions app/src/main/java/tech/relaycorp/letro/awala/AwalaManager.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tech.relaycorp.letro.awala

import android.content.Context
import android.util.Base64
import android.util.Log
import androidx.annotation.RawRes
import dagger.hilt.android.qualifiers.ApplicationContext
Expand Down Expand Up @@ -37,6 +38,11 @@ interface AwalaManager {
recipient: MessageRecipient,
)
suspend fun isAwalaInstalled(currentScreen: Route): Boolean
suspend fun authorizeUsers(
// TODO: after MVP handle several first party endpoints
thirdPartyPublicKey: ByteArray,
)
suspend fun getFirstPartyPublicKey(): String
}

@OptIn(ExperimentalCoroutinesApi::class)
Expand All @@ -63,7 +69,6 @@ class AwalaManagerImpl @Inject constructor(
private var isReceivingMessages = false

private var firstPartyEndpoint: FirstPartyEndpoint? = null

private var thirdPartyServerEndpoint: ThirdPartyEndpoint? = null

init {
Expand Down Expand Up @@ -116,6 +121,27 @@ class AwalaManagerImpl @Inject constructor(
return isInstalled
}

override suspend fun authorizeUsers(thirdPartyPublicKey: ByteArray) {
withContext(awalaThreadContext) {
val firstPartyEndpoint = loadFirstPartyEndpoint()
val auth = firstPartyEndpoint.authorizeIndefinitely(thirdPartyPublicKey)
sendMessage(
outgoingMessage = AwalaOutgoingMessage(
type = MessageType.ContactPairingAuthorization,
content = auth,
),
recipient = MessageRecipient.Server(),
)
}
}

override suspend fun getFirstPartyPublicKey(): String {
return withContext(awalaThreadContext) {
val firstPartyEndpoint = loadFirstPartyEndpoint()
Base64.encodeToString(firstPartyEndpoint.publicKey.encoded, Base64.NO_WRAP)
}
}

private suspend fun loadFirstPartyEndpoint(): FirstPartyEndpoint {
return withContext(awalaThreadContext) {
val firstPartyEndpointNodeId = awalaRepository.getServerFirstPartyEndpointNodeId()
Expand Down Expand Up @@ -159,7 +185,7 @@ class AwalaManagerImpl @Inject constructor(
Log.i(TAG, "start receiving messages...")
GatewayClient.receiveMessages().collect { message ->
Log.i(TAG, "Receive message: ${message.type}: ($message)")
processor.process(message)
processor.process(message, this@AwalaManagerImpl)
Log.i(TAG, "Message processed")
message.ack()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package tech.relaycorp.letro.awala.message

import android.util.Log
import tech.relaycorp.letro.awala.AwalaManagerImpl

sealed class MessageType(val value: String) {
object AccountCreationRequest : MessageType("application/vnd.relaycorp.letro.account-creation-request")
object AccountCreationCompleted : MessageType("application/vnd.relaycorp.letro.account-creation-completed-tmp")
Expand All @@ -18,7 +21,10 @@ sealed class MessageType(val value: String) {
ContactPairingRequest.value -> ContactPairingRequest
ContactPairingMatch.value -> ContactPairingMatch
ContactPairingAuthorization.value -> ContactPairingAuthorization
else -> Unknown
else -> {
Log.e(AwalaManagerImpl.TAG, "Unknown message type $type")
Unknown
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +0,0 @@
package tech.relaycorp.letro.awala.parser

import tech.relaycorp.letro.awala.message.AwalaIncomingMessage
import tech.relaycorp.letro.awala.message.MessageType
import javax.inject.Inject

interface UnknownMessageParser : AwalaMessageParser

class UnknownMessageParserImpl @Inject constructor() : UnknownMessageParser {

override fun parse(content: ByteArray): AwalaIncomingMessage<*> {
return UnknownIncomingMessage(
content = content.decodeToString(),
)
}
}

data class UnknownIncomingMessage(
override val content: String,
) : AwalaIncomingMessage<String> {
override val type: MessageType = MessageType.Unknown
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package tech.relaycorp.letro.awala.processor

import tech.relaycorp.awaladroid.messaging.IncomingMessage
import tech.relaycorp.letro.awala.AwalaManager
import tech.relaycorp.letro.awala.message.MessageType

interface AwalaMessageProcessor {
suspend fun process(message: IncomingMessage)
suspend fun process(message: IncomingMessage, awalaManager: AwalaManager)
}

class AwalaMessageProcessorImpl constructor(
private val processors: Map<MessageType, AwalaMessageProcessor>,
) : AwalaMessageProcessor {

override suspend fun process(message: IncomingMessage) {
override suspend fun process(message: IncomingMessage, awalaManager: AwalaManager) {
val type = MessageType.from(message.type)
processors[type]!!.process(message)
processors[type]!!.process(message, awalaManager)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package tech.relaycorp.letro.awala.processor

import android.util.Log
import tech.relaycorp.awaladroid.messaging.IncomingMessage
import tech.relaycorp.letro.awala.AwalaManager
import tech.relaycorp.letro.awala.AwalaManagerImpl
import javax.inject.Inject

class UnknownMessageProcessor @Inject constructor() : AwalaMessageProcessor {

override suspend fun process(message: IncomingMessage) {
override suspend fun process(message: IncomingMessage, awalaManager: AwalaManager) {
Log.w(AwalaManagerImpl.TAG, "Unknown message processor for type: ${message.type}")
}
}
25 changes: 17 additions & 8 deletions app/src/main/java/tech/relaycorp/letro/contacts/model/Contact.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package tech.relaycorp.letro.contacts.model

import androidx.annotation.IntDef
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import tech.relaycorp.letro.account.model.Account
import tech.relaycorp.letro.contacts.model.ContactPairingStatus.Companion.AUTHORIZATION_SENT
import tech.relaycorp.letro.contacts.model.ContactPairingStatus.Companion.COMPLETED
import tech.relaycorp.letro.contacts.model.ContactPairingStatus.Companion.MATCH
import tech.relaycorp.letro.contacts.model.ContactPairingStatus.Companion.REQUEST_SENT
import tech.relaycorp.letro.contacts.model.ContactPairingStatus.Companion.UNPAIRED

const val TABLE_NAME_CONTACTS = "contacts"

Expand All @@ -22,18 +28,21 @@ const val TABLE_NAME_CONTACTS = "contacts"
)
data class Contact(
@PrimaryKey(autoGenerate = true)
val id: Long,
val id: Long = 0L,
val ownerVeraId: String,
val contactVeraId: String,
val alias: String? = null,
val contactEndpointId: String? = null,
val status: ContactPairingStatus = ContactPairingStatus.Unpaired,
@ContactPairingStatus val status: Int = UNPAIRED,
)

sealed interface ContactPairingStatus {
object Unpaired : ContactPairingStatus
object RequestSent : ContactPairingStatus
object Match : ContactPairingStatus
object AuthorizationSent : ContactPairingStatus
object Complete : ContactPairingStatus
@IntDef(UNPAIRED, REQUEST_SENT, MATCH, AUTHORIZATION_SENT, COMPLETED)
annotation class ContactPairingStatus {
companion object {
const val UNPAIRED = 0
const val REQUEST_SENT = 1
const val MATCH = 2
const val AUTHORIZATION_SENT = 3
const val COMPLETED = 4
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ interface ContactsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(contact: Contact): Long

@Query("SELECT * FROM $TABLE_NAME_CONTACTS WHERE ownerVeraId = :ownerVeraId AND contactVeraId = :contactVeraId")
suspend fun getContact(
ownerVeraId: String,
contactVeraId: String,
): Contact?

@Update
suspend fun update(contact: Contact)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,31 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import tech.relaycorp.letro.account.model.Account
import tech.relaycorp.letro.account.storage.AccountRepository
import tech.relaycorp.letro.awala.AwalaManager
import tech.relaycorp.letro.awala.message.AwalaOutgoingMessage
import tech.relaycorp.letro.awala.message.MessageRecipient
import tech.relaycorp.letro.awala.message.MessageType
import tech.relaycorp.letro.contacts.model.Contact
import tech.relaycorp.letro.contacts.model.ContactPairingStatus
import javax.inject.Inject

interface ContactsRepository {
val isPairedContactsExist: Flow<Boolean>
fun getContacts(ownerVeraId: String): Flow<List<Contact>>

fun addNewContact(contact: Contact)
}

class ContactsRepositoryImpl @Inject constructor(
private val contactsDao: ContactsDao,
private val accountRepository: AccountRepository,
private val awalaManager: AwalaManager,
) : ContactsRepository {

private val scope = CoroutineScope(Dispatchers.IO)
private val contacts = MutableStateFlow<List<Contact>>(emptyList())

private var currentAccount: Account? = null
private val _isPairedContactsExist: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isPairedContactsExist: StateFlow<Boolean>
get() = _isPairedContactsExist
Expand All @@ -35,6 +43,7 @@ class ContactsRepositoryImpl @Inject constructor(
contactsDao.getAll().collect {
contacts.emit(it)
startCollectAccountFlow()
updatePairedContactExist(currentAccount)
}
}
}
Expand All @@ -44,9 +53,42 @@ class ContactsRepositoryImpl @Inject constructor(
.map { it.filter { it.ownerVeraId == ownerVeraId } }
}

override fun addNewContact(contact: Contact) {
scope.launch {
val existingContact = contactsDao.getContact(
ownerVeraId = contact.ownerVeraId,
contactVeraId = contact.contactVeraId,
)

if (existingContact == null || existingContact.status <= ContactPairingStatus.REQUEST_SENT) {
if (existingContact == null) {
contactsDao.insert(
contact.copy(
status = ContactPairingStatus.REQUEST_SENT,
),
)
} else {
contactsDao.update(
contact.copy(
status = ContactPairingStatus.REQUEST_SENT,
),
)
}
awalaManager.sendMessage(
outgoingMessage = AwalaOutgoingMessage(
type = MessageType.ContactPairingRequest,
content = "${contact.ownerVeraId},${contact.contactVeraId},${awalaManager.getFirstPartyPublicKey()}".toByteArray(),
),
recipient = MessageRecipient.Server(),
)
}
}
}

private fun startCollectAccountFlow() {
scope.launch {
accountRepository.currentAccount.collect {
currentAccount = it
updatePairedContactExist(it)
}
}
Expand All @@ -61,8 +103,7 @@ class ContactsRepositoryImpl @Inject constructor(
contacts
.value
.any {
it.ownerVeraId == account.veraId &&
it.status == ContactPairingStatus.Complete
it.ownerVeraId == account.veraId && it.status == ContactPairingStatus.COMPLETED
},
)
}
Expand Down

This file was deleted.

Loading
Loading