All Downloads are FREE. Search and download functionalities are using the official Maven repository.

replication.dtn.dtn.scala Maven / Gradle / Ivy

The newest version!
package replication.dtn

import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker
import de.rmgk.delay.*
import rdts.base.{Bottom, Lattice, LocalUid, Uid}
import rdts.datatypes.PosNegCounter
import rdts.syntax.*

import java.net.URI
import java.net.http.HttpResponse.BodyHandlers
import java.net.http.WebSocket.Listener
import java.net.http.{HttpClient, HttpRequest, WebSocket}
import java.nio.ByteBuffer
import java.time.Duration
import java.util.concurrent.CompletionStage
import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.{Await, Future}

/** API base path used for http request */
def api(using scheme: String = "http"): String =
  val ip   = "127.0.0.1"
  val port = 3000
  s"$scheme://$ip:$port"

val client: HttpClient = HttpClient.newBuilder()
  .connectTimeout(Duration.ofSeconds(20))
  .build()

/** get uri body as string, throwing on any errors */
def sget(uri: java.net.URI): Async[Any, String] =
  client.sendAsync(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofString).toAsync.map(_.body())
def bget(uri: java.net.URI): Async[Any, Array[Byte]] =
  client.sendAsync(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofByteArray()).toAsync.map(_.body())

class BinaryAsBase64(val payload: Array[Byte])

given JsonValueCodec[BinaryAsBase64] = new JsonValueCodec[BinaryAsBase64] {
  override def decodeValue(in: JsonReader, default: BinaryAsBase64): BinaryAsBase64 =
    BinaryAsBase64(in.readBase64AsBytes(Array.empty))
  override def encodeValue(x: BinaryAsBase64, out: JsonWriter): Unit =
    out.writeBase64Val(x.payload, true)
  override def nullValue: BinaryAsBase64 = BinaryAsBase64(Array.empty)
}

// what follows are the data type and codec definitions for receiving and sending bundles
case class WsRecvData(bid: String, src: String, dst: String, data: BinaryAsBase64)
case class WsSendData(src: String, dst: String, data: BinaryAsBase64, delivery_notification: Boolean, lifetime: Long)
given JsonValueCodec[WsRecvData] = JsonCodecMaker.make
given JsonValueCodec[WsSendData] = JsonCodecMaker.make

import replication.JsoniterCodecs.given
given JsonValueCodec[PosNegCounter] = JsonCodecMaker.make

class Replica[S: Lattice: JsonValueCodec](val id: Uid, dtnNodeId: String, val service: String, @volatile var data: S) {

  val connections: AtomicReference[List[WebSocket]] = {
    val ar = new AtomicReference[List[WebSocket]]
    ar.set(Nil)
    ar
  }

  val mut: ReplicaMutator[S] = new ReplicaMutator[S](this)

  def applyRemoteDelta(delta: S): Unit = synchronized {
    data = data `merge` delta
  }

  def applyLocalDelta(delta: S): Unit = synchronized {
    data = data `merge` delta
    val msg = message(delta)
    connections.get().foreach { ws =>
      ws.sendBinary(msg, true)
    }
  }

  def message(data: S): ByteBuffer = {
    val sendData = WsSendData(dtnNodeId, service, new BinaryAsBase64(writeToArray(data)), false, 60 * 1000)
    ByteBuffer.wrap(writeToArray(sendData))
  }

  def receive(data: ByteBuffer): Unit = {
    val receieved = readFromByteBuffer[WsRecvData](data)
    println(s"received: $receieved")
    val delta = readFromArray[S](receieved.data.payload)
    println(s"applying $delta")
    applyRemoteDelta(delta)
    println(s"value is now ${this.data}")
  }

  def connectOn(uri: URI): Async[Any, Unit] =
    Async {
      val listener      = ReplicaListener(this)
      val ws: WebSocket = client.newWebSocketBuilder().buildAsync(uri, listener).toAsync.bind
      println(s"starting ws handler")
      // select json communication
      ws.sendText("/json", true).toAsync.bind
      // ask to receive messages on the the given path
      ws.sendText(s"/subscribe ${service}", true).toAsync.bind
      ws.request(1) // start receiving

      listener.modeSwitched.async.bind

      // enable sending
      connections.updateAndGet(ws :: _)

      ws.sendBinary(message(data), true)
      ()
    }
}

class ReplicaListener[S: Lattice: JsonValueCodec](replica: Replica[S]) extends Listener {

  val modeSwitched: Promise[true] = Promise[true]

  override def onOpen(webSocket: WebSocket): Unit = ()
  override def onText(webSocket: WebSocket, data: CharSequence, last: Boolean): CompletionStage[?] =
    if CharSequence.compare(data, "200 tx mode: JSON") == 0 then modeSwitched.succeed(true)
    println(data)
    super.onText(webSocket, data, last)
  override def onBinary(webSocket: WebSocket, data: ByteBuffer, last: Boolean): CompletionStage[?] = {
    replica.receive(data)

    super.onBinary(webSocket, data, last)
  }
}

class ReplicaMutator[S](val replica: Replica[S]) {
  inline def apply(f: S => S)(using Lattice[S]): Unit =
    replica.applyLocalDelta(f(replica.data))
}

def traverse[T](list: List[Async[Any, T]]): Async[Any, List[T]] = list match
  case Nil => Async { Nil }
  case h :: t => Async {
      val hr   = h.bind
      val rest = traverse(t).bind
      hr :: rest
    }

def run(): Unit =
  val service = "dtn://rdt/~test"

  val res = Async[Unit] {
    val nodeId = sget(URI.create(s"$api/status/nodeid")).bind
    sget(URI.create(s"$api/register?$service")).bind

    val replica    = Replica(Uid.gen(), nodeId, service, PosNegCounter.zero)
    given LocalUid = replica.id.convert

    val bundleString = sget(URI.create(s"$api/status/bundles")).bind
    val bundles = traverse(readFromString[List[String]](bundleString)(JsonCodecMaker.make).map { id =>
      bget(URI.create(s"$api/download?$id"))
    }).bind

    bundles.foreach(b => println(new String(b)))

    replica.connectOn(URI.create(s"${api(using "ws")}/ws")).bind

    Thread.sleep(1000)

    replica.mut(_.add(10))

  }.runToFuture(using ())

  Await.result(res, scala.concurrent.duration.Duration.Inf)
  Thread.sleep(1000)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy