diff --git a/src/main/scala/fluence/crypto/cipher/Crypt.scala b/src/main/scala/fluence/crypto/cipher/Crypt.scala index 8724b61..a8edac5 100644 --- a/src/main/scala/fluence/crypto/cipher/Crypt.scala +++ b/src/main/scala/fluence/crypto/cipher/Crypt.scala @@ -24,10 +24,10 @@ package fluence.crypto.cipher * @tparam P The type of plain text, input * @tparam C The type of cipher text, output */ -trait Crypt[P, C] { +trait Crypt[F[_], P, C] { - def encrypt(plainText: P): C + def encrypt(plainText: P): F[C] - def decrypt(cipherText: C): P + def decrypt(cipherText: C): F[P] } diff --git a/src/main/scala/fluence/crypto/cipher/CryptoSearching.scala b/src/main/scala/fluence/crypto/cipher/CryptoSearching.scala index 30e94d3..ed81dab 100644 --- a/src/main/scala/fluence/crypto/cipher/CryptoSearching.scala +++ b/src/main/scala/fluence/crypto/cipher/CryptoSearching.scala @@ -17,7 +17,8 @@ package fluence.crypto.cipher -import scala.annotation.tailrec +import cats.Monad +import cats.syntax.functor._ import scala.collection.Searching.{ Found, InsertionPoint, SearchResult } import scala.math.Ordering @@ -35,7 +36,7 @@ import scala.math.Ordering */ object CryptoSearching { - class CryptoSearchImpl[A](coll: IndexedSeq[A]) { + class CryptoSearchImpl[F[_], A](coll: IndexedSeq[A])(implicit F: Monad[F]) { /** * Searches the specified indexedSeq for the search element using the binary search algorithm. @@ -49,28 +50,26 @@ object CryptoSearching { * sequence. A `InsertionPoint` value containing the index where the element would be inserted if * the search element is not found in the sequence. */ - final def binarySearch[B](searchElem: B)(implicit ordering: Ordering[B], decrypt: A ⇒ B): SearchResult = { - binarySearchRec(searchElem, 0, coll.length, ordering, decrypt) - } - - @tailrec - private def binarySearchRec[B](elem: B, from: Int, to: Int, ordering: Ordering[B], decrypt: A ⇒ B): SearchResult = { - - if (from == to) return InsertionPoint(from) - - val idx = from + (to - from - 1) / 2 - math.signum(ordering.compare(elem, decrypt(coll(idx)))) match { - case -1 ⇒ binarySearchRec(elem, from, idx, ordering, decrypt) - case 1 ⇒ binarySearchRec(elem, idx + 1, to, ordering, decrypt) - case _ ⇒ Found(idx) + final def binarySearch[B](searchElem: B)(implicit ordering: Ordering[B], decrypt: A ⇒ F[B]): F[SearchResult] = { + F.tailRecM((0, coll.length)) { + case (from, to) if from == to ⇒ F.pure(Right(InsertionPoint(from))) + case (from, to) ⇒ + val idx = from + (to - from - 1) / 2 + decrypt(coll(idx)).map { d ⇒ + math.signum(ordering.compare(searchElem, d)) match { + case -1 ⇒ Left((from, idx)) + case 1 ⇒ Left((idx + 1, to)) + case _ ⇒ Right(Found(idx)) + } + } } } } - implicit def search[A](indexedSeq: IndexedSeq[A]): CryptoSearchImpl[A] = + implicit def search[F[_] : Monad, A](indexedSeq: IndexedSeq[A]): CryptoSearchImpl[F, A] = new CryptoSearchImpl(indexedSeq) - implicit def search[A](array: Array[A]): CryptoSearchImpl[A] = + implicit def search[F[_] : Monad, A](array: Array[A]): CryptoSearchImpl[F, A] = new CryptoSearchImpl(array) } diff --git a/src/main/scala/fluence/crypto/cipher/NoOpCrypt.scala b/src/main/scala/fluence/crypto/cipher/NoOpCrypt.scala index 0a43b2e..58100e8 100644 --- a/src/main/scala/fluence/crypto/cipher/NoOpCrypt.scala +++ b/src/main/scala/fluence/crypto/cipher/NoOpCrypt.scala @@ -19,29 +19,32 @@ package fluence.crypto.cipher import java.nio.ByteBuffer +import cats.Applicative +import cats.syntax.applicative._ + /** * No operation implementation. Just convert the element to bytes back and forth without any cryptography. */ -class NoOpCrypt[T](serializer: T ⇒ Array[Byte], deserializer: Array[Byte] ⇒ T) extends Crypt[T, Array[Byte]] { +class NoOpCrypt[F[_], T](serializer: T ⇒ F[Array[Byte]], deserializer: Array[Byte] ⇒ F[T]) extends Crypt[F, T, Array[Byte]] { - def encrypt(plainText: T): Array[Byte] = serializer(plainText) + def encrypt(plainText: T): F[Array[Byte]] = serializer(plainText) - def decrypt(cipherText: Array[Byte]): T = deserializer(cipherText) + def decrypt(cipherText: Array[Byte]): F[T] = deserializer(cipherText) } object NoOpCrypt { - val forString: NoOpCrypt[String] = apply[String]( - serializer = _.getBytes, - deserializer = bytes ⇒ new String(bytes)) + def forString[F[_] : Applicative]: NoOpCrypt[F, String] = apply[F, String]( + serializer = _.getBytes.pure[F], + deserializer = bytes ⇒ new String(bytes).pure[F]) - val forLong: NoOpCrypt[Long] = apply[Long]( - serializer = ByteBuffer.allocate(java.lang.Long.BYTES).putLong(_).array(), - deserializer = bytes ⇒ ByteBuffer.wrap(bytes).getLong() + def forLong[F[_] : Applicative]: NoOpCrypt[F, Long] = apply[F, Long]( + serializer = ByteBuffer.allocate(java.lang.Long.BYTES).putLong(_).array().pure[F], + deserializer = bytes ⇒ ByteBuffer.wrap(bytes).getLong().pure[F] ) - def apply[T](serializer: T ⇒ Array[Byte], deserializer: Array[Byte] ⇒ T): NoOpCrypt[T] = + def apply[F[_], T](serializer: T ⇒ F[Array[Byte]], deserializer: Array[Byte] ⇒ F[T]): NoOpCrypt[F, T] = new NoOpCrypt(serializer, deserializer) } diff --git a/src/test/scala/fluence/crypto/CryptoSearchingSpec.scala b/src/test/scala/fluence/crypto/CryptoSearchingSpec.scala index cce312b..14769af 100644 --- a/src/test/scala/fluence/crypto/CryptoSearchingSpec.scala +++ b/src/test/scala/fluence/crypto/CryptoSearchingSpec.scala @@ -1,30 +1,32 @@ package fluence.crypto import fluence.crypto.cipher.NoOpCrypt +import cats.instances.try_._ import org.scalatest.{ Matchers, WordSpec } import scala.collection.Searching.{ Found, InsertionPoint } +import scala.util.Try class CryptoSearchingSpec extends WordSpec with Matchers { "search" should { "correct search plainText key in encrypted data" in { - val crypt: NoOpCrypt[String] = NoOpCrypt.forString + val crypt: NoOpCrypt[Try, String] = NoOpCrypt.forString val plainTextElements = Array("A", "B", "C", "D", "E") - val encryptedElements = plainTextElements.map(crypt.encrypt) + val encryptedElements = plainTextElements.map(t ⇒ crypt.encrypt(t).get) import fluence.crypto.cipher.CryptoSearching._ - implicit val decryptFn: (Array[Byte]) ⇒ String = crypt.decrypt + implicit val decryptFn: Array[Byte] ⇒ Try[String] = crypt.decrypt - encryptedElements.binarySearch("B") shouldBe Found(1) - encryptedElements.binarySearch("D") shouldBe Found(3) - encryptedElements.binarySearch("E") shouldBe Found(4) + encryptedElements.binarySearch("B").get shouldBe Found(1) + encryptedElements.binarySearch("D").get shouldBe Found(3) + encryptedElements.binarySearch("E").get shouldBe Found(4) - encryptedElements.binarySearch("0") shouldBe InsertionPoint(0) - encryptedElements.binarySearch("BB") shouldBe InsertionPoint(2) - encryptedElements.binarySearch("ZZ") shouldBe InsertionPoint(5) + encryptedElements.binarySearch("0").get shouldBe InsertionPoint(0) + encryptedElements.binarySearch("BB").get shouldBe InsertionPoint(2) + encryptedElements.binarySearch("ZZ").get shouldBe InsertionPoint(5) } }