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

jsonRPC handle of rumor msg in scala backend #1799

Merged
merged 16 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
3 changes: 3 additions & 0 deletions be2-scala/src/main/resources/protocol/query/query.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
},
{
"$ref": "method/greet_server.json"
},
{
"$ref": "method/rumor.json"
}
],

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ object HighLevelProtocol extends DefaultJsonProtocol {
case paramsWithChannel: ParamsWithChannel => paramsWithChannel.toJson
case paramsWithMap: ParamsWithMap => paramsWithMap.toJson
case greetServer: GreetServer => greetServer.toJson(GreetServerFormat)
case rumor: Rumor => rumor.toJson(RumorFormat)
}

}
Expand Down Expand Up @@ -198,6 +199,42 @@ object HighLevelProtocol extends DefaultJsonProtocol {
}
}

implicit object RumorFormat extends RootJsonFormat[Rumor] {
final private val PARAM_SENDER_PK: String = "sender_id"
final private val PARAM_RUMOR_ID: String = "rumor_id"
final private val PARAM_MESSAGES: String = "messages"

override def read(json: JsValue): Rumor = {
val jsonObject: JsObject = json.asJsObject
jsonObject.getFields(PARAM_SENDER_PK, PARAM_RUMOR_ID, PARAM_MESSAGES) match {
DanielTavaresA marked this conversation as resolved.
Show resolved Hide resolved
case Seq(senderPk @ JsString(_), rumorId @ JsNumber(_), JsArray(messages)) =>
val map: Map[Channel, Array[Message]] = messages.map {
case JsObject(obj) =>
val channelKey = obj.keys.headOption.getOrElse {
throw new IllegalArgumentException(s"No key found in JSON object: $obj")
}
val channel = Channel(channelKey)
val messagesJson = obj(channelKey).asInstanceOf[JsArray]
val messages = messagesJson.elements.map(_.convertTo[Message]).toArray
channel -> messages
case _ => throw new IllegalArgumentException(s"Unrecognizable rumor in $json")
}.toMap
new Rumor(senderPk.convertTo[PublicKey], rumorId.convertTo[Int], HashMap.from(map))
case _ => throw new IllegalArgumentException(s"Can't parse json value $json to a Rumor object")
}
}

override def write(obj: Rumor): JsValue = {
val jsObjContent: ListMap[String, JsValue] = ListMap[String, JsValue](
PARAM_SENDER_PK -> obj.senderPk.toJson,
PARAM_RUMOR_ID -> obj.rumorId.toJson,
PARAM_MESSAGES -> obj.messages.map((chan, messages) => Map(chan -> messages)).toJson
)
JsObject(jsObjContent)
}

}

implicit val errorObjectFormat: JsonFormat[ErrorObject] = jsonFormat2(ErrorObject.apply)

implicit object ResultObjectFormat extends RootJsonFormat[ResultObject] {
Expand Down Expand Up @@ -234,6 +271,7 @@ object HighLevelProtocol extends DefaultJsonProtocol {
case MethodType.unsubscribe => paramsJsObject.convertTo[Unsubscribe]
case MethodType.catchup => paramsJsObject.convertTo[Catchup]
case MethodType.get_messages_by_id => paramsJsObject.convertTo[GetMessagesById]
case MethodType.rumor => paramsJsObject.convertTo[Rumor]
case _ => throw new IllegalArgumentException(s"Can't parse json value $json with unknown method ${method.toString}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ enum MethodType(val method: String):
case heartbeat extends MethodType("heartbeat")
case get_messages_by_id extends MethodType("get_messages_by_id")
case greet_server extends MethodType("greet_server")
case rumor extends MethodType("rumor")
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ch.epfl.pop.model.network.method

import ch.epfl.pop.json.HighLevelProtocol.RumorFormat
import ch.epfl.pop.model.network.Parsable
import ch.epfl.pop.model.network.method.message.Message
import ch.epfl.pop.model.objects.{Channel, PublicKey}
import spray.json._

final case class Rumor(senderPk: PublicKey, rumorId: Int, messages: Map[Channel, Array[Message]]) extends Params {
DanielTavaresA marked this conversation as resolved.
Show resolved Hide resolved

override def hasChannel: Boolean = true

override def hasMessage: Boolean = true

}

object Rumor extends Parsable {

def apply(senderPk: PublicKey, rumorId: Int, messages: Map[Channel, Array[Message]]): Rumor = {
new Rumor(senderPk, rumorId, messages)
}

override def buildFromJson(payload: String): Rumor = payload.parseJson.asJsObject.convertTo[Rumor]

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package ch.epfl.pop.json

import ch.epfl.pop.model.network.method.message.Message
import ch.epfl.pop.model.network.method.{GreetServer, ParamsWithChannel, ParamsWithMap, ParamsWithMessage}
import ch.epfl.pop.model.network.method.{GreetServer, ParamsWithChannel, ParamsWithMap, ParamsWithMessage, Rumor}
import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType, ResultObject}
import ch.epfl.pop.model.objects.*
import ch.epfl.pop.pubsub.graph.validators.RpcValidator
import org.scalatest.Inspectors.forEvery
import org.scalatest.funsuite.AnyFunSuite as FunSuite
import org.scalatest.matchers.should.Matchers
import util.examples.json.rumor.exampleRumor
import spray.json.*

import scala.collection.immutable.{HashMap, Set}
Expand Down Expand Up @@ -237,6 +238,49 @@ class HighLevelProtocolSuite extends FunSuite with Matchers {
broadcastFromJson.id should equal(None)
}

test("parse correctly rumor message") {
val serverPk: PublicKey = PublicKey(Base64Data("J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM="))
val rumorId: Int = 1

val chan: Channel = Channel("/root/nLghr9_P406lfkMjaNWqyohLxOiGlQee8zad4qAfj18=/social/8qlv4aUT5-tBodKp4RszY284CFYVaoDZK6XKiw9isSw=")
val id: String = "f1jTxH8TU2UGUBnikGU3wRTHjhOmIEQVmxZBK55QpsE="
val sender: String = "to_klZLtiHV446Fv98OLNdNmi-EP5OaTtbBkotTYLic="
val signature: String = "2VDJCWg11eNPUvZOnvq5YhqqIKLBcik45n-6o87aUKefmiywagivzD4o_YmjWHzYcb9qg-OgDBZbBNWSUgJICA=="
val data: String = "eyJjcmVhdGlvbiI6MTYzMTg4NzQ5NiwiaWQiOiJ4aWdzV0ZlUG1veGxkd2txMUt1b0wzT1ZhODl4amdYalRPZEJnSldjR1drPSIsIm5hbWUiOiJoZ2dnZ2dnIiwib3JnYW5pemVyIjoidG9fa2xaTHRpSFY0NDZGdjk4T0xOZE5taS1FUDVPYVR0YkJrb3RUWUxpYz0iLCJ3aXRuZXNzZXMiOltdLCJvYmplY3QiOiJsYW8iLCJhY3Rpb24iOiJjcmVhdGUifQ=="
val message: Message = buildExpected(id, sender, signature, data)

val messages: Map[Channel, Array[Message]] = HashMap(
chan -> Array(message)
)

val rpcId: Option[Int] = Some(1)

val rumorJsValue = HighLevelProtocol.jsonRpcRequestFormat.write(JsonRpcRequest(RpcValidator.JSON_RPC_VERSION, MethodType.rumor, new Rumor(serverPk, rumorId, messages), rpcId))
val rumorFromJson = JsonRpcRequest.buildFromJson(rumorJsValue.prettyPrint)

// Test
rumorFromJson.jsonrpc should equal(RpcValidator.JSON_RPC_VERSION)
rumorFromJson.method should equal(MethodType.rumor)
rumorFromJson.getParams.asInstanceOf[Rumor].rumorId should equal(rumorId)
rumorFromJson.getParams.asInstanceOf[Rumor].senderPk should equal(serverPk)
rumorFromJson.getParams.asInstanceOf[Rumor].messages.keys should equal(messages.keys)
rumorFromJson.getParams.asInstanceOf[Rumor].messages.values.zip(messages.values).foreach((arrMsg1, arrMsg2) => arrMsg1 should equal(arrMsg2))
rumorFromJson.id should equal(rpcId)

}

test("parse jsonRPC correctly to rumor") {
val jsonRpcRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(exampleRumor.rumorJson)
val rumor: Rumor = jsonRpcRequest.getParams.asInstanceOf[Rumor]

rumor.rumorId should equal(1)
rumor.senderPk should equal(PublicKey(Base64Data("J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=")))
rumor.messages.keys.size should equal(2)
rumor.messages.values.foreach { x =>
x.length should not be 0
}
}

test("parse correctly get_messages_by_id answers") {

val chan1 = Channel("/root/nLghr9_P406lfkMjaNWqyohLxOiGlQee8zad4qAfj18=/social/8qlv4aUT5-tBodKp4RszY284CFYVaoDZK6XKiw9isSw=")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ch.epfl.pop.model.network.method

import ch.epfl.pop.model.network.method.message.Message
import ch.epfl.pop.model.objects.{Base64Data, Channel, Hash, PublicKey, Signature}
import org.scalatest.matchers.should.Matchers
import org.scalatest.funsuite.AnyFunSuite as FunSuite
import ch.epfl.pop.json.HighLevelProtocol.RumorFormat
import spray.json.enrichAny

import scala.collection.immutable.HashMap
import scala.language.postfixOps

class RumorSuite extends FunSuite with Matchers {
private final val serverPk: PublicKey = new PublicKey(Base64Data.encode("PublickKey"))

def buildExpected(id: String, sender: String, signature: String, data: String): Message = {
Message(
Base64Data(data),
PublicKey(Base64Data(sender)),
Signature(Base64Data(signature)),
Hash(Base64Data(id)),
Nil // Witnesses will be added in each test that needs them
)
}

test("Constructor from Json works for Rumor") {
val chan: Channel = Channel("/root/nLghr9_P406lfkMjaNWqyohLxOiGlQee8zad4qAfj18=/social/8qlv4aUT5-tBodKp4RszY284CFYVaoDZK6XKiw9isSw=")
val id: String = "f1jTxH8TU2UGUBnikGU3wRTHjhOmIEQVmxZBK55QpsE="
val sender: String = "to_klZLtiHV446Fv98OLNdNmi-EP5OaTtbBkotTYLic="
val signature: String = "2VDJCWg11eNPUvZOnvq5YhqqIKLBcik45n-6o87aUKefmiywagivzD4o_YmjWHzYcb9qg-OgDBZbBNWSUgJICA=="
val data: String = "eyJjcmVhdGlvbiI6MTYzMTg4NzQ5NiwiaWQiOiJ4aWdzV0ZlUG1veGxkd2txMUt1b0wzT1ZhODl4amdYalRPZEJnSldjR1drPSIsIm5hbWUiOiJoZ2dnZ2dnIiwib3JnYW5pemVyIjoidG9fa2xaTHRpSFY0NDZGdjk4T0xOZE5taS1FUDVPYVR0YkJrb3RUWUxpYz0iLCJ3aXRuZXNzZXMiOltdLCJvYmplY3QiOiJsYW8iLCJhY3Rpb24iOiJjcmVhdGUifQ=="
val message: Message = buildExpected(id, sender, signature, data)

val messages: Map[Channel, Array[Message]] = HashMap(
chan -> Array(message)
)
val rumor: Rumor = new Rumor(senderPk = serverPk, rumorId = 1, messages = messages)

val encodedDecoded = Rumor.buildFromJson(rumor.toJson.toString)

encodedDecoded.rumorId shouldBe rumor.rumorId
encodedDecoded.senderPk shouldBe rumor.senderPk
encodedDecoded.messages.values.zip(rumor.messages.values).foreach((arrMsg1, arrMsg2) => arrMsg1 shouldBe arrMsg2)
encodedDecoded.messages.keys shouldBe rumor.messages.keys
}

}
DanielTavaresA marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package util.examples.json.rumor

object exampleRumor {
val rumorJson: String =
"{\n \"jsonrpc\": \"2.0\",\n \"id\": 4,\n \"method\": \"rumor\",\n \"params\": {\n \"sender_id\" : \"J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=\",\n \"rumor_id\": 1,\n \"messages\": [\n {\n \"/root/nLghr9_P406lfkMjaNWqyohLxOiGlQee8zad4qAfj18=/social/8qlv4aUT5-tBodKp4RszY284CFYVaoDZK6XKiw9isSw=\": [\n {\n \"data\": \"eyJvYmplY3QiOiJyb2xsX2NhbGwiLCJhY3Rpb24iOiJjcmVhdGUiLCJuYW1lIjoiUm9sbCBDYWxsIiwiY3JlYXRpb24iOjE2MzMwMzYxMjAsInByb3Bvc2VkX3N0YXJ0IjoxNjMzMDM2Mzg4LCJwcm9wb3NlZF9lbmQiOjE2MzMwMzk2ODgsImxvY2F0aW9uIjoiRVBGTCIsImlkIjoial9kSmhZYnpubXZNYnVMc0ZNQ2dzYlB5YjJ6Nm1vZ2VtSmFON1NWaHVVTT0ifQ==\",\n \"sender\": \"J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=\",\n \"signature\": \"FFqBXhZSaKvBnTvrDNIeEYMpFKI5oIa5SAewquxIBHTTEyTIDnUgmvkwgccV9NrujPwDnRt1f4CIEqzXqhbjCw==\",\n \"message_id\": \"DCBX48EuNO6q-Sr42ONqsj7opKiNeXyRzrjqTbZ_aMI=\",\n \"witness_signatures\": []\n }\n ]\n },\n {\n \"/root/nLghr9_P406lfkMjaNWqyohLxOiGlQee8zad4qAfj18=/HnXDyvSSron676Icmvcjk5zXvGLkPJ1fVOaWOxItzBE=\": [\n {\n \"data\": \"eyJvYmplY3QiOiJyb2xsX2NhbGwiLCJhY3Rpb24iOiJjcmVhdGUiLCJuYW1lIjoiUm9sbCBDYWxsIiwiY3JlYXRpb24iOjE2MzMwMzYxMjAsInByb3Bvc2VkX3N0YXJ0IjoxNjMzMDM2Mzg4LCJwcm9wb3NlZF9lbmQiOjE2MzMwMzk2ODgsImxvY2F0aW9uIjoiRVBGTCIsImlkIjoial9kSmhZYnpubXZNYnVMc0ZNQ2dzYlB5YjJ6Nm1vZ2VtSmFON1NWaHVVTT0ifQ==\",\n \"sender\": \"J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=\",\n \"signature\": \"FFqBXhZSaKvBnTvrDNIeEYMpFKI5oIa5SAewquxIBHTTEyTIDnUgmvkwgccV9NrujPwDnRt1f4CIEqzXqhbjCw==\",\n \"message_id\": \"z6SbjJ0Hw36k8L09-GVRq4PNmi06yQX4e8aZRSbUDwc=\",\n \"witness_signatures\": []\n },\n {\n \"data\": \"eyJvYmplY3QiOiJyb2xsX2NhbGwiLCJhY3Rpb24iOiJjcmVhdGUiLCJuYW1lIjoiUm9sbCBDYWxsIiwiY3JlYXRpb24iOjE2MzMwMzYxMjAsInByb3Bvc2VkX3N0YXJ0IjoxNjMzMDM2Mzg4LCJwcm9wb3NlZF9lbmQiOjE2MzMwMzk2ODgsImxvY2F0aW9uIjoiRVBGTCIsImlkIjoial9kSmhZYnpubXZNYnVMc0ZNQ2dzYlB5YjJ6Nm1vZ2VtSmFON1NWaHVVTT0ifQ==\",\n \"sender\": \"J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=\",\n \"signature\": \"FFqBXhZSaKvBnTvrDNIeEYMpFKI5oIa5SAewquxIBHTTEyTIDnUgmvkwgccV9NrujPwDnRt1f4CIEqzXqhbjCw==\",\n \"message_id\": \"txbTmVMwCDkZdoaAiEYfAKozVizZzkeMkeOlzq5qMlg=\",\n \"witness_signatures\": []\n }\n ]\n }\n ]\n }\n}"
}
Loading