Skip to content

Commit

Permalink
Merge pull request #1943 from dedis/work-fe2-johan-federation
Browse files Browse the repository at this point in the history
Federation Data Exchange for FE2
  • Loading branch information
quadcopterman authored Jun 27, 2024
2 parents e68e9ee + 5008da9 commit 81e32e9
Show file tree
Hide file tree
Showing 31 changed files with 915 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.github.dedis.popstellar.model.network.method.message.data.election.El
import com.github.dedis.popstellar.model.network.method.message.data.election.ElectionResult
import com.github.dedis.popstellar.model.network.method.message.data.election.ElectionSetup
import com.github.dedis.popstellar.model.network.method.message.data.federation.Challenge
import com.github.dedis.popstellar.model.network.method.message.data.federation.FederationResult
import com.github.dedis.popstellar.model.network.method.message.data.federation.TokensExchange
import com.github.dedis.popstellar.model.network.method.message.data.lao.CreateLao
import com.github.dedis.popstellar.model.network.method.message.data.lao.GreetLao
import com.github.dedis.popstellar.model.network.method.message.data.lao.StateLao
Expand Down Expand Up @@ -259,6 +261,18 @@ object DataRegistryModule {
linkedOrganizationsHandler.handleChallenge(context, challenge)
}

builder.add(Objects.FEDERATION, Action.RESULT, FederationResult::class.java) {
context: HandlerContext,
result: FederationResult ->
linkedOrganizationsHandler.handleResult(context, result)
}

builder.add(Objects.FEDERATION, Action.TOKENS_EXCHANGE, TokensExchange::class.java) {
context: HandlerContext,
tokenExchange: TokensExchange ->
linkedOrganizationsHandler.handleTokensExchange(context, tokenExchange)
}

return builder.build()
}

Expand Down Expand Up @@ -327,6 +341,8 @@ object DataRegistryModule {

// Federation
builder.add(Objects.FEDERATION, Action.CHALLENGE, Challenge::class.java, null)
builder.add(Objects.FEDERATION, Action.RESULT, FederationResult::class.java, null)
builder.add(Objects.FEDERATION, Action.TOKENS_EXCHANGE, TokensExchange::class.java, null)

return builder.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum class Action
CHALLENGE("challenge"),
INIT("init"),
EXPECT("expect"),
TOKENS_EXCHANGE("tokens_exchange"),
RUMOR("rumor");

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.github.dedis.popstellar.model.network.method.message.data.federation

import com.github.dedis.popstellar.model.network.method.message.MessageGeneral
import com.github.dedis.popstellar.model.network.method.message.data.Action
import com.github.dedis.popstellar.model.network.method.message.data.Data
import com.github.dedis.popstellar.model.network.method.message.data.Objects
import com.github.dedis.popstellar.utility.MessageValidator
import com.google.gson.annotations.SerializedName

/** Informs about the result of the authentication procedure */
class FederationResult
/**
* Constructor for a data Federation Result
*
* @param status status of the result (either success or failure)
* @param reason reason of the failure
* @param publicKey public key of the other LAO organizer
* @param challenge challenge used to connect the LAOs
*/
(
val status: String,
val reason: String? = null,
@SerializedName("public_key") val publicKey: String? = null,
val challenge: MessageGeneral,
) : Data {

init {
MessageValidator.verify().isValidFederationResult(status, reason, publicKey, challenge)
}

override val `object`: String
get() = Objects.FEDERATION.`object`

override val action: String
get() = Action.RESULT.action

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
val that = other as FederationResult
return status == that.status &&
reason == that.reason &&
publicKey == that.publicKey &&
challenge == that.challenge
}

override fun hashCode(): Int {
return java.util.Objects.hash(status, reason, publicKey, challenge)
}

override fun toString(): String {
if (status == FAILURE) {
return "FederationResult{status='$status', reason='$reason', challenge='$challenge'}"
} else if (status == SUCCESS) {
return "FederationResult{status='$status', public_key='$publicKey', " +
"challenge='$challenge'}"
}
return "FederationResult{ERROR}"
}

fun isSuccess(): Boolean {
return status == SUCCESS
}

companion object {
const val SUCCESS = "success"
const val FAILURE = "failure"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.github.dedis.popstellar.model.network.method.message.data.federation

import com.github.dedis.popstellar.model.network.method.message.data.Action
import com.github.dedis.popstellar.model.network.method.message.data.Data
import com.github.dedis.popstellar.model.network.method.message.data.Objects
import com.github.dedis.popstellar.utility.MessageValidator
import com.google.gson.annotations.SerializedName

/** Token exchange to be broadcast in the LAO */
class TokensExchange
/**
* Constructor for a data TokenExchange
*
* @param laoId ID of the remote LAO
* @param rollCallId ID of the rollCall of the remote LAO
* @param tokens array of tokens contained in the rollCall
* @param timestamp timestamp of the message
*/
(
@SerializedName("lao_id") val laoId: String,
@SerializedName("roll_call_id") val rollCallId: String,
val tokens: Array<String>,
val timestamp: Long
) : Data {

init {
MessageValidator.verify()
.isNotEmptyBase64(laoId, "lao_id")
.isBase64(rollCallId, "roll_call_id")
.arrayElementsNotEmptyBase64(tokens, field = "tokens")
.validPastTimes(timestamp)
}

override val `object`: String
get() = Objects.FEDERATION.`object`

override val action: String
get() = Action.TOKENS_EXCHANGE.action

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
val that = other as TokensExchange
return laoId == that.laoId &&
rollCallId == that.rollCallId &&
tokens.contentEquals(that.tokens) &&
timestamp == that.timestamp
}

override fun hashCode(): Int {
return java.util.Objects.hash(laoId, rollCallId, tokens, timestamp)
}

override fun toString(): String {
return "TokensExchange{lao_id='$laoId', roll_call_id='$rollCallId', " +
"tokens='$tokens', timestamp='$timestamp'}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ class Chirp : Copyable<Chirp> {
val timestamp: Long
val isDeleted: Boolean
val parentId: MessageID
val laoId: String

constructor(
id: MessageID,
sender: PublicKey,
text: String,
timestamp: Long,
isDeleted: Boolean,
parentId: MessageID
parentId: MessageID,
laoId: String
) {
require(id.encoded.isNotEmpty()) { "The id of the Chirp is empty" }
require(timestamp >= 0) { "The timestamp of the Chirp is negative" }
Expand All @@ -32,15 +34,17 @@ class Chirp : Copyable<Chirp> {
this.timestamp = timestamp
this.parentId = parentId
this.isDeleted = isDeleted
this.laoId = laoId
}

constructor(
id: MessageID,
sender: PublicKey,
text: String,
timestamp: Long,
parentId: MessageID
) : this(id, sender, text, timestamp, false, parentId)
parentId: MessageID,
laoId: String
) : this(id, sender, text, timestamp, false, parentId, laoId)

constructor(chirp: Chirp, deleted: Boolean) {
id = chirp.id
Expand All @@ -49,6 +53,7 @@ class Chirp : Copyable<Chirp> {
timestamp = chirp.timestamp
parentId = chirp.parentId
isDeleted = deleted
laoId = chirp.laoId
}

constructor(chirp: Chirp) {
Expand All @@ -58,6 +63,7 @@ class Chirp : Copyable<Chirp> {
timestamp = chirp.timestamp
isDeleted = chirp.isDeleted
parentId = chirp.parentId
laoId = chirp.laoId
}

override fun copy(): Chirp {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package com.github.dedis.popstellar.repository

import android.app.Activity
import android.app.Application
import androidx.lifecycle.Lifecycle
import com.github.dedis.popstellar.model.network.method.message.data.federation.Challenge
import com.github.dedis.popstellar.utility.GeneralUtils
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import java.util.EnumMap
import java.util.function.Consumer
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -10,18 +18,25 @@ import javax.inject.Singleton
* Its main purpose is to store received messages
*/
@Singleton
class LinkedOrganizationsRepository @Inject constructor() {
class LinkedOrganizationsRepository @Inject constructor(application: Application) {
private var challenge: Challenge? = null
private var onChallengeUpdatedCallback: ((Challenge) -> Unit)? = null
private var linkedLaos: MutableMap<String, MutableMap<String, Array<String>>> = mutableMapOf()
private var onLinkedLaosUpdatedCallback: ((String, MutableMap<String, Array<String>>) -> Unit)? =
null
private var newTokensNotifyFunction: ((String, String, String, Array<String>) -> Unit)? = null
private val disposables = CompositeDisposable()
var otherLaoId: String? = null
var otherServerAddr: String? = null
var otherPublicKey: String? = null

/**
* Updates the challenge
*
* @param challenge the new Challenge
*/
init {
val consumerMap: MutableMap<Lifecycle.Event, Consumer<Activity>> =
EnumMap(Lifecycle.Event::class.java)
consumerMap[Lifecycle.Event.ON_STOP] = Consumer { disposables.clear() }
application.registerActivityLifecycleCallbacks(GeneralUtils.buildLifecycleCallback(consumerMap))
}

fun updateChallenge(challenge: Challenge) {
this.challenge = challenge
onChallengeUpdatedCallback?.invoke(challenge)
Expand All @@ -35,6 +50,40 @@ class LinkedOrganizationsRepository @Inject constructor() {
return challenge
}

fun addLinkedLao(laoId: String, otherLaoId: String, tokens: Array<String>) {
val laoMap = linkedLaos.getOrPut(laoId) { mutableMapOf() }
laoMap[otherLaoId] = tokens
onLinkedLaosUpdatedCallback?.invoke(laoId, laoMap)
}

fun updateAndNotifyLinkedLao(
laoId: String,
otherLaoId: String,
tokens: Array<String>,
rollCallId: String
) {
addLinkedLao(laoId, otherLaoId, tokens)
newTokensNotifyFunction?.invoke(laoId, otherLaoId, rollCallId, tokens)
}

fun setOnLinkedLaosUpdatedCallback(
callback: (String, MutableMap<String, Array<String>>) -> Unit
) {
onLinkedLaosUpdatedCallback = callback
}

fun setNewTokensNotifyFunction(function: (String, String, String, Array<String>) -> Unit) {
newTokensNotifyFunction = function
}

fun getLinkedLaos(laoId: String): MutableMap<String, Array<String>> {
return linkedLaos.getOrDefault(laoId, mutableMapOf())
}

fun addDisposable(disposable: Disposable) {
disposables.add(disposable)
}

fun flush() {
otherLaoId = null
otherServerAddr = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import com.github.dedis.popstellar.ui.lao.event.LaoDetailAnimation.showIn
import com.github.dedis.popstellar.ui.lao.event.LaoDetailAnimation.showOut
import com.github.dedis.popstellar.ui.qrcode.QrScannerFragment
import com.github.dedis.popstellar.ui.qrcode.ScanningAction
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

/**
* A simple [Fragment] subclass. Use the [LinkedOrganizationsFragment.newInstance] factory method to
Expand All @@ -41,9 +44,6 @@ class LinkedOrganizationsFragment : Fragment() {
linkedOrganizationsViewModel =
obtainLinkedOrganizationsViewModel(requireActivity(), laoViewModel.laoId)

// Starts from a clean repository
linkedOrganizationsViewModel.flushRepository()

// Sets the text and the button depending on the user's role
laoViewModel.role.observe(viewLifecycleOwner) { role: Role ->
if (role == Role.ORGANIZER) {
Expand All @@ -59,6 +59,19 @@ class LinkedOrganizationsFragment : Fragment() {
binding.inviteOtherOrganization.setOnClickListener(invitationPage)
binding.joinOtherOrganizationInvitation.setOnClickListener(joinButton)

// Displaying the linked organizations
val laos = linkedOrganizationsViewModel.getLinkedLaosMap().keys
displayLinkedOrganizations(laos)
linkedOrganizationsViewModel.doWhenLinkedLaosIsUpdated { laoId, laoMap ->
if (laoId == laoViewModel.laoId) {
CoroutineScope(Dispatchers.Main).launch {
val currentLaos = laoMap.keys
displayLinkedOrganizations(currentLaos)
}
}
}

linkedOrganizationsViewModel.setLinkedLaosNotifyFunction()
handleBackNav()

return binding.root
Expand Down Expand Up @@ -97,13 +110,27 @@ class LinkedOrganizationsFragment : Fragment() {

private var joinButton =
View.OnClickListener {
linkedOrganizationsViewModel.flushRepository()
laoViewModel.setIsTab(false)
linkedOrganizationsViewModel.manager = parentFragmentManager
LaoActivity.setCurrentFragment(parentFragmentManager, R.id.fragment_qr_scanner) {
QrScannerFragment.newInstance(ScanningAction.FEDERATION_JOIN)
}
}

private fun displayLinkedOrganizations(laos: Set<String>) {
if (laos.isNotEmpty()) {
val laosText = laos.joinToString(separator = "\n\n")
val textToDisplay = context?.getString(R.string.list_organizations, laosText)
binding.noOrganizationsText.visibility = View.GONE
binding.listOrganizationsText.visibility = View.VISIBLE
binding.listOrganizationsText.text = textToDisplay
} else {
binding.listOrganizationsText.visibility = View.GONE
binding.noOrganizationsText.visibility = View.VISIBLE
}
}

private fun handleBackNav() {
LaoActivity.addBackNavigationCallbackToEvents(requireActivity(), viewLifecycleOwner, TAG)
}
Expand Down
Loading

0 comments on commit 81e32e9

Please sign in to comment.