Skip to content

Commit

Permalink
Initial draft of new hashing package
Browse files Browse the repository at this point in the history
  • Loading branch information
mpilquist committed Jul 4, 2024
1 parent 2ebcb12 commit b71ce8f
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 0 deletions.
31 changes: 31 additions & 0 deletions core/js/src/main/scala/fs2/hashing/HashCompanionPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2013 Functional Streams for Scala
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package fs2
package hashing

import cats.effect.Sync

trait HashCompanionPlatform {

def unsafe[F[_]: Sync](algorithm: String): Hash[F] =
???
}
43 changes: 43 additions & 0 deletions core/jvm/src/main/scala/fs2/hashing/HashCompanionPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2013 Functional Streams for Scala
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package fs2
package hashing

import cats.effect.Sync

import java.security.MessageDigest

private[hashing] trait HashCompanionPlatform {
def unsafe[F[_]: Sync](algorithm: String): Hash[F] =
unsafeFromMessageDigest(MessageDigest.getInstance(algorithm))

def unsafeFromMessageDigest[F[_]: Sync](d: MessageDigest): Hash[F] =
new Hash[F] {
def addChunk(bytes: Chunk[Byte]): F[Unit] = Sync[F].delay(unsafeAddChunk(bytes.toArraySlice))
def computeAndReset: F[Chunk[Byte]] = Sync[F].delay(unsafeComputeAndReset())

def unsafeAddChunk(slice: Chunk.ArraySlice[Byte]): Unit =
d.update(slice.values, slice.offset, slice.size)

def unsafeComputeAndReset(): Chunk[Byte] = Chunk.array(d.digest())
}
}
31 changes: 31 additions & 0 deletions core/native/src/main/scala/fs2/hashing/HashCompanionPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2013 Functional Streams for Scala
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package fs2
package hashing

import cats.effect.Sync

trait HashCompanionPlatform {

def unsafe[F[_]: Sync](algorithm: String): Hash[F] =
???
}
62 changes: 62 additions & 0 deletions core/shared/src/main/scala/fs2/hashing/Hash.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2013 Functional Streams for Scala
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package fs2
package hashing

import cats.effect.Sync

trait Hash[F[_]] {
def addChunk(bytes: Chunk[Byte]): F[Unit]
def computeAndReset: F[Chunk[Byte]]

protected def unsafeAddChunk(slice: Chunk.ArraySlice[Byte]): Unit
protected def unsafeComputeAndReset(): Chunk[Byte]

def update: Pipe[F, Byte, Byte] =
_.mapChunks { c =>
unsafeAddChunk(c.toArraySlice)
c
}

def observe(source: Stream[F, Byte], sink: Pipe[F, Byte, Nothing]): Stream[F, Byte] =
update(source).through(sink) ++ Stream.evalUnChunk(computeAndReset)

def hash: Pipe[F, Byte, Byte] =
source => observe(source, _.drain)

def verify(expected: Chunk[Byte])(implicit F: RaiseThrowable[F]): Pipe[F, Byte, Byte] =
source =>
update(source)
.onComplete(
Stream
.eval(computeAndReset)
.flatMap(actual =>
if (actual == expected) Stream.empty
else Stream.raiseError(HashVerificationException(expected, actual))
)
)
}

object Hash extends HashCompanionPlatform {
def apply[F[_]: Sync](algorithm: String): F[Hash[F]] =
Sync[F].delay(unsafe(algorithm))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2013 Functional Streams for Scala
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package fs2
package hashing

import java.io.IOException

case class HashVerificationException(
expected: Chunk[Byte],
actual: Chunk[Byte]
) extends IOException(
s"Digest did not match, expected: ${expected.toByteVector.toHex}, actual: ${actual.toByteVector.toHex}"
)
52 changes: 52 additions & 0 deletions core/shared/src/main/scala/fs2/hashing/Hashing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2013 Functional Streams for Scala
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package fs2
package hashing

import cats.effect.Sync

/** Capability trait that provides hashing.
*
* The [[create]] method returns an action that instantiates a fresh `Hash` object.
* `Hash` is a mutable object that supports incremental computation of hashes. A `Hash`
* instance should be created for each hash you want to compute.
*/
trait Hashing[F[_]] {
def create(algorithm: String): F[Hash[F]]
def md5: F[Hash[F]] = create("MD-5")
def sha1: F[Hash[F]] = create("SHA-1")
def sha256: F[Hash[F]] = create("SHA-256")
def sha384: F[Hash[F]] = create("SHA-384")
def sha512: F[Hash[F]] = create("SHA-512")

def hashWith(hash: F[Hash[F]]): Pipe[F, Byte, Byte] =
source => Stream.eval(hash).flatMap(h => h.hash(source))
}

object Hashing {
implicit def apply[F[_]](implicit F: Hashing[F]): F.type = F

implicit def forSync[F[_]: Sync]: Hashing[F] = new Hashing[F] {
def create(algorithm: String): F[Hash[F]] =
Hash[F](algorithm)
}
}

0 comments on commit b71ce8f

Please sign in to comment.