Skip to content

Commit

Permalink
Add name based UUIDv5 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
LukaJCB committed Dec 9, 2019
1 parent c8c44fb commit 8c1c4bf
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 0 deletions.
45 changes: 45 additions & 0 deletions modules/core/src/main/scala/io/chrisdavenport/fuuid/FUUID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cats._
import cats.implicits._
import cats.effect.Sync
import java.util.UUID
import java.security.MessageDigest

import scala.reflect.macros.blackbox

Expand Down Expand Up @@ -74,6 +75,50 @@ object FUUID {
}
}

/**
* Creates a new name-based UUIDv5
**/
def nameBased[F[_]](namespace: FUUID, name: String)(implicit AE: ApplicativeError[F, Throwable]): F[FUUID] =
Either
.catchNonFatal(
MessageDigest
.getInstance("SHA-1")
.digest(
uuidBytes(namespace) ++
name.getBytes("UTF-8")
)
)
.map { bs =>
val cs = bs.take(16) // Truncate 160 bits (20 bytes) SHA-1 to fit into our 128 bits of space
cs(6) = (cs(6) & 0x0f).asInstanceOf[Byte] /* clear version */
cs(6) = (cs(6) | 0x50).asInstanceOf[Byte] /* set to version 5, SHA1 UUID */
cs(8) = (cs(8) & 0x3f).asInstanceOf[Byte] /* clear variant */
cs(8) = (cs(8) | 0x80).asInstanceOf[Byte] /* set to IETF variant */
cs
}
.flatMap(
bs =>
Either.catchNonFatal {
val bb = java.nio.ByteBuffer.allocate(java.lang.Long.BYTES)
bb.put(bs, 0, 8)
bb.flip
val most = bb.getLong
bb.flip
bb.put(bs, 8, 8)
bb.flip
val least = bb.getLong
new FUUID(new UUID(most, least))
}
)
.liftTo[F]

private def uuidBytes(fuuid: FUUID): Array[Byte] = {
val bb = java.nio.ByteBuffer.allocate(2 * java.lang.Long.BYTES)
bb.putLong(fuuid.uuid.getMostSignificantBits)
bb.putLong(fuuid.uuid.getLeastSignificantBits)
bb.array
}

/**
* A Home For functions we don't trust
* Hopefully making it very clear that this code needs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ object FUUIDSpec extends mutable.Specification with ScalaCheck {
}
}

"FUUID.nameBased" should {
"produce a valid UUID" in {
(for {
namespace <- FUUID.randomFUUID[IO]
namebased <- FUUID.nameBased[IO](namespace, "What up yo!")
parsed <- FUUID.fromStringF[IO](namebased.toString)
} yield parsed)
.attempt
.unsafeRunSync
.isRight must_=== true
}

"conform to an example" in {
val namespace = FUUID.fromStringF[IO]("dc79a6bc-de3c-5bc7-a877-712bea708d8f").unsafeRunSync()
val name = "What up yo!"

val expected = FUUID.fromStringF[IO]("1cce4593-d217-5b33-be0d-2e81462e79d3").unsafeRunSync()

FUUID.nameBased[IO](namespace, name).unsafeRunSync must_=== expected
}
}

"FUUID.hashCode" should {
"have same hashcode as uuid" in {
val baseString = "00000000-075b-cd15-0000-0000075bcd15"
Expand Down

0 comments on commit 8c1c4bf

Please sign in to comment.