From 708c0c523df97d1542b7b6d128c5218f6e2cc460 Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 21 Nov 2014 00:48:10 -0800 Subject: Move things around a bit --- build.sbt | 5 ++ src/Base64.scala | 69 ------------------------- src/ByteUtils.scala | 109 ---------------------------------------- src/ConversionUtils.scala | 38 -------------- src/utils/ByteUtils.scala | 109 ++++++++++++++++++++++++++++++++++++++++ src/utils/ConversionUtils.scala | 38 ++++++++++++++ src/utils/external/Base64.scala | 69 +++++++++++++++++++++++++ test/ByteUtilsSpec.scala | 85 +++++++++++++++++++++++++++++++ test/ConversionUtilsSpec.scala | 31 ++++++++++++ 9 files changed, 337 insertions(+), 216 deletions(-) delete mode 100644 src/Base64.scala delete mode 100644 src/ByteUtils.scala delete mode 100644 src/ConversionUtils.scala create mode 100644 src/utils/ByteUtils.scala create mode 100644 src/utils/ConversionUtils.scala create mode 100644 src/utils/external/Base64.scala create mode 100644 test/ByteUtilsSpec.scala create mode 100644 test/ConversionUtilsSpec.scala diff --git a/build.sbt b/build.sbt index bc8c1c9..ac67f2e 100644 --- a/build.sbt +++ b/build.sbt @@ -6,6 +6,11 @@ scalaSource in Compile := baseDirectory.value / "src" scalaSource in Test := baseDirectory.value / "test" +lazy val testUtils = RootProject(file("/toy/scala/test_utils")) + +lazy val root = Project("root", file(".")) + .dependsOn(testUtils) + resolvers ++= Seq( "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases" ) diff --git a/src/Base64.scala b/src/Base64.scala deleted file mode 100644 index 692abb8..0000000 --- a/src/Base64.scala +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.marklister.base64 - -/** - * Base64 encoder - * @author Mark Lister - * (c) Mark Lister 2014 - * - * I, the copyright holder of this work, release this work into the public domain. - * This applies worldwide. In some countries this may not be legally possible; - * if so: I grant anyone the right to use this work for any purpose, without any - * conditions, unless such conditions are required by law. - * - * The repo for this Base64 encoder lives at https://github.com/marklister/base64 - * Please send your issues, suggestions and pull requests there. - * - */ - -object Base64 { - - class B64Scheme(val encodeTable: IndexedSeq[Char]) { - lazy val decodeTable = collection.immutable.TreeMap(encodeTable.zipWithIndex: _*) - } - - lazy val base64 = new B64Scheme(('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ Seq('+', '/')) - lazy val base64Url = new B64Scheme(base64.encodeTable.dropRight(2) ++ Seq('-', '_')) - - implicit class Encoder(b: Array[Byte]) { - private[this] val zero = Array(0, 0).map(_.toByte) - lazy val pad = (3 - b.length % 3) % 3 - - def toBase64(implicit scheme: B64Scheme = base64): String = { - def sixBits(x: Array[Byte]): Seq[Int] = { - val a = (x(0) & 0xfc) >> 2 - val b = ((x(0) & 0x3) << 4) + ((x(1) & 0xf0) >> 4) - val c = ((x(1) & 0xf) << 2) + ((x(2) & 0xc0) >> 6) - val d = (x(2)) & 0x3f - Seq(a, b, c, d) - } - ((b ++ zero.take(pad)).grouped(3) - .flatMap(sixBits(_)) - .map(x => scheme.encodeTable(x)) - .toSeq - .dropRight(pad) :+ "=" * pad) - .mkString - } - } - - implicit class Decoder(s: String) { - lazy val cleanS = s.reverse.dropWhile(_ == '=').reverse - lazy val pad = s.length - cleanS.length - - def toByteArray(implicit scheme: B64Scheme = base64): Array[Byte] = { - def threeBytes(s: Seq[Char]): Array[Byte] = { - val r = s.map(scheme.decodeTable(_)).foldLeft(0)((a,b)=>(a << 6) +b) - java.nio.ByteBuffer.allocate(8).putLong(r).array().takeRight(3) - } - if (pad > 2 || s.length % 4 != 0) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s) - if (!cleanS.forall(scheme.encodeTable.contains(_))) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s) - - (cleanS + "A" * pad) - .grouped(4) - .map(threeBytes(_)) - .flatten - .toArray - .dropRight(pad) - } - } - -} diff --git a/src/ByteUtils.scala b/src/ByteUtils.scala deleted file mode 100644 index 3b6046c..0000000 --- a/src/ByteUtils.scala +++ /dev/null @@ -1,109 +0,0 @@ -package main.utils - -object ByteUtils { - trait SizedNumeric[T] { - def manifest: Manifest[T] - def byteSize: Int - def bitSize: Int - def fromLong(l: Long): T - def toLong(l: T): Long - def buildFrom[U : SizedNumeric](from: U): T = fromLong(from.toLong) - def truncate[U : SizedNumeric](from: U): T = buildFrom(from) - override def toString = s"SizedNumeric[${manifest}]" - } - - trait BitOps[T] { - def @>>>(t: T, x: Int): T - def @<<(t: T, x: Int): T - def @|(a: T, b: T): T - def @&(a: T, b: T): T - def @+(a: T, b: T): T - } - - implicit val byteOps: BitOps[Byte] = new BitOps[Byte] { - def @>>>(t: Byte, x: Int): Byte = (t >>> x).toByte - def @<<(t: Byte, x: Int): Byte = (t << x).toByte - def @|(a: Byte, b: Byte): Byte = (a | b).toByte - def @&(a: Byte, b: Byte): Byte = (a & b).toByte - def @+(a: Byte, b: Byte): Byte = (a + b).toByte - } - - implicit val intOps: BitOps[Int] = new BitOps[Int] { - def @>>>(t: Int, x: Int): Int = t >>> x - def @<<(t: Int, x: Int): Int = (t << x) - def @|(a: Int, b: Int): Int = a | b - def @&(a: Int, b: Int): Int = a & b - def @+(a: Int, b: Int): Int = a + b - } - - implicit class WithBitOpts[T : BitOps](x: T) { - def @>>>(b: Int): T = implicitly[BitOps[T]].@>>>(x, b) - def @<<(b: Int): T = implicitly[BitOps[T]].@<<(x, b) - def @|(b: T): T = implicitly[BitOps[T]].@|(x, b) - def @&(b: T): T = implicitly[BitOps[T]].@&(x, b) - def @+(b: T): T = implicitly[BitOps[T]].@+(x, b) - } - - implicit class SizedWithByteInfo[T : SizedNumeric](x: T) { - def byteSize = implicitly[SizedNumeric[T]].byteSize - def bitSize = implicitly[SizedNumeric[T]].bitSize - def toLong = implicitly[SizedNumeric[T]].toLong(x) - def liftedTo[U : SizedNumeric]: U = { - val uSize = implicitly[SizedNumeric[U]] - val tSize = implicitly[SizedNumeric[T]] - if (uSize.byteSize < tSize.byteSize) { - throw new RuntimeException(s"Target size ($uSize) is smaller than the source size ($tSize)") - } - - uSize.buildFrom(x) - } - def truncatedTo[U : SizedNumeric]: U = { - implicitly[SizedNumeric[U]].truncate(x) - } - } - - implicit class RichArrayOfBytes(b: Array[Byte]) { - def to[T : SizedNumeric : BitOps]: T = { - val numeric = implicitly[SizedNumeric[T]] - if(b.length < numeric.byteSize) { - throw new RuntimeException("Byte input is not long enough") - } - - var out: Long = b(0) - for(i <- 1 until numeric.byteSize) { - out << 8 - out = b(i) | out - } - numeric.fromLong(out) - } - } - def sizedNumeric[T : Manifest](bytes: Int)(from: Long => T)(to: T => Long) = new SizedNumeric[T] { - def manifest = implicitly[Manifest[T]] - def byteSize = bytes - def bitSize = bytes * 8 - def toLong(t: T) = to(t) - def fromLong(l: Long) = from(l) - } - - implicit val intSized = sizedNumeric(4) { x: Long => x.toInt } { _.toLong } - implicit val shortized = sizedNumeric(2) { x: Long => x.toShort } { _.toLong } - implicit val byteSized = sizedNumeric(1) { x: Long => x.toByte } { _.toLong } - - def toArrayBuf[T : SizedNumeric : BitOps](x: T): Array[Byte] = { - val buf = new Array[Byte](x.byteSize) - for(i <- 0 until x.byteSize) { - val shiftAmount: Int = (x.byteSize - i - 1) << 3 - buf(i) = (x @>>> shiftAmount).truncatedTo[Byte] - } - buf - } - - def toBinaryString[T : SizedNumeric](x: T)(implicit a: BitOps[T]): String = { - val buf = new StringBuilder(x.bitSize) - for(i <- 0 until x.bitSize) { - val shiftAmount: Int = x.bitSize - i - 1 - buf.append((x @>>> shiftAmount) @& 0x01.liftedTo[T]) - } - buf.toString() - } -} diff --git a/src/ConversionUtils.scala b/src/ConversionUtils.scala deleted file mode 100644 index 5d01429..0000000 --- a/src/ConversionUtils.scala +++ /dev/null @@ -1,38 +0,0 @@ -package main.utils - -import ByteUtils.WithBitOpts - -object ConversionUtils { - def hexStr2ByteArr(s: String): Iterator[Byte] = - padToByte(s).grouped(2).map(byteStr2Byte) - - def byteStr2Byte(str: String): Byte = - charSeq2Byte(str.toSeq) - - def charSeq2Byte(seq: Seq[Char]): Byte = - seq.map(octet2nibble).reduce(joinNibbles) - - def joinNibbles(a: Byte, b: Byte): Byte = - ((a @<< 4) @+ b) - - def padToByte(s: String): String = - if (s.length % 2 != 0) s + "0" else s - - def octet2nibble(c: Char): Byte = { - (c.toLower match { - case c if c >= 'a' && c <= 'f' => - (c - 'a') + 10.toByte - case c if c >= '0' && c <= '9' => - c - '0' - case _ => - throw new IllegalArgumentException(s"Invalid hexadecimal character: $c") - }).toByte - } - - def hexStr2Base64String(s: String): String = { - import io.github.marklister.base64.Base64._ - - hexStr2ByteArr(s).toArray.toBase64 - } -} - diff --git a/src/utils/ByteUtils.scala b/src/utils/ByteUtils.scala new file mode 100644 index 0000000..344f6f7 --- /dev/null +++ b/src/utils/ByteUtils.scala @@ -0,0 +1,109 @@ +package ixee.cryptopals.utils + +object ByteUtils { + trait SizedNumeric[T] { + def manifest: Manifest[T] + def byteSize: Int + def bitSize: Int + def fromLong(l: Long): T + def toLong(l: T): Long + def buildFrom[U : SizedNumeric](from: U): T = fromLong(from.toLong) + def truncate[U : SizedNumeric](from: U): T = buildFrom(from) + override def toString = s"SizedNumeric[${manifest}]" + } + + trait BitOps[T] { + def @>>>(t: T, x: Int): T + def @<<(t: T, x: Int): T + def @|(a: T, b: T): T + def @&(a: T, b: T): T + def @+(a: T, b: T): T + } + + implicit val byteOps: BitOps[Byte] = new BitOps[Byte] { + def @>>>(t: Byte, x: Int): Byte = (t >>> x).toByte + def @<<(t: Byte, x: Int): Byte = (t << x).toByte + def @|(a: Byte, b: Byte): Byte = (a | b).toByte + def @&(a: Byte, b: Byte): Byte = (a & b).toByte + def @+(a: Byte, b: Byte): Byte = (a + b).toByte + } + + implicit val intOps: BitOps[Int] = new BitOps[Int] { + def @>>>(t: Int, x: Int): Int = t >>> x + def @<<(t: Int, x: Int): Int = (t << x) + def @|(a: Int, b: Int): Int = a | b + def @&(a: Int, b: Int): Int = a & b + def @+(a: Int, b: Int): Int = a + b + } + + implicit class WithBitOpts[T : BitOps](x: T) { + def @>>>(b: Int): T = implicitly[BitOps[T]].@>>>(x, b) + def @<<(b: Int): T = implicitly[BitOps[T]].@<<(x, b) + def @|(b: T): T = implicitly[BitOps[T]].@|(x, b) + def @&(b: T): T = implicitly[BitOps[T]].@&(x, b) + def @+(b: T): T = implicitly[BitOps[T]].@+(x, b) + } + + implicit class SizedWithByteInfo[T : SizedNumeric](x: T) { + def byteSize = implicitly[SizedNumeric[T]].byteSize + def bitSize = implicitly[SizedNumeric[T]].bitSize + def toLong = implicitly[SizedNumeric[T]].toLong(x) + def liftedTo[U : SizedNumeric]: U = { + val uSize = implicitly[SizedNumeric[U]] + val tSize = implicitly[SizedNumeric[T]] + if (uSize.byteSize < tSize.byteSize) { + throw new RuntimeException(s"Target size ($uSize) is smaller than the source size ($tSize)") + } + + uSize.buildFrom(x) + } + def truncatedTo[U : SizedNumeric]: U = { + implicitly[SizedNumeric[U]].truncate(x) + } + } + + implicit class RichArrayOfBytes(b: Array[Byte]) { + def to[T : SizedNumeric : BitOps]: T = { + val numeric = implicitly[SizedNumeric[T]] + if(b.length < numeric.byteSize) { + throw new RuntimeException("Byte input is not long enough") + } + + var out: Long = b(0) + for(i <- 1 until numeric.byteSize) { + out << 8 + out = b(i) | out + } + numeric.fromLong(out) + } + } + def sizedNumeric[T : Manifest](bytes: Int)(from: Long => T)(to: T => Long) = new SizedNumeric[T] { + def manifest = implicitly[Manifest[T]] + def byteSize = bytes + def bitSize = bytes * 8 + def toLong(t: T) = to(t) + def fromLong(l: Long) = from(l) + } + + implicit val intSized = sizedNumeric(4) { x: Long => x.toInt } { _.toLong } + implicit val shortized = sizedNumeric(2) { x: Long => x.toShort } { _.toLong } + implicit val byteSized = sizedNumeric(1) { x: Long => x.toByte } { _.toLong } + + def toArrayBuf[T : SizedNumeric : BitOps](x: T): Array[Byte] = { + val buf = new Array[Byte](x.byteSize) + for(i <- 0 until x.byteSize) { + val shiftAmount: Int = (x.byteSize - i - 1) << 3 + buf(i) = (x @>>> shiftAmount).truncatedTo[Byte] + } + buf + } + + def toBinaryString[T : SizedNumeric](x: T)(implicit a: BitOps[T]): String = { + val buf = new StringBuilder(x.bitSize) + for(i <- 0 until x.bitSize) { + val shiftAmount: Int = x.bitSize - i - 1 + buf.append((x @>>> shiftAmount) @& 0x01.liftedTo[T]) + } + buf.toString() + } +} diff --git a/src/utils/ConversionUtils.scala b/src/utils/ConversionUtils.scala new file mode 100644 index 0000000..26f2d24 --- /dev/null +++ b/src/utils/ConversionUtils.scala @@ -0,0 +1,38 @@ +package ixee.cryptopals.utils + +import ByteUtils.WithBitOpts + +object ConversionUtils { + def hexStr2Bytes(s: String): Iterator[Byte] = + padToByte(s).grouped(2).map(byteStr2Byte) + + def byteStr2Byte(str: String): Byte = + charSeq2Byte(str.toSeq) + + def charSeq2Byte(seq: Seq[Char]): Byte = + seq.map(octet2nibble).reduce(joinNibbles) + + def joinNibbles(a: Byte, b: Byte): Byte = + ((a @<< 4) @+ b) + + def padToByte(s: String): String = + if (s.length % 2 != 0) "0" + s else s + + def octet2nibble(c: Char): Byte = { + (c.toLower match { + case c if c >= 'a' && c <= 'f' => + (c - 'a') + 10.toByte + case c if c >= '0' && c <= '9' => + c - '0' + case _ => + throw new IllegalArgumentException(s"Invalid hexadecimal character: $c") + }).toByte + } + + def hexStr2Base64String(s: String): String = { + import io.github.marklister.base64.Base64._ + + hexStr2Bytes(s).toArray.toBase64 + } +} + diff --git a/src/utils/external/Base64.scala b/src/utils/external/Base64.scala new file mode 100644 index 0000000..692abb8 --- /dev/null +++ b/src/utils/external/Base64.scala @@ -0,0 +1,69 @@ +package io.github.marklister.base64 + +/** + * Base64 encoder + * @author Mark Lister + * (c) Mark Lister 2014 + * + * I, the copyright holder of this work, release this work into the public domain. + * This applies worldwide. In some countries this may not be legally possible; + * if so: I grant anyone the right to use this work for any purpose, without any + * conditions, unless such conditions are required by law. + * + * The repo for this Base64 encoder lives at https://github.com/marklister/base64 + * Please send your issues, suggestions and pull requests there. + * + */ + +object Base64 { + + class B64Scheme(val encodeTable: IndexedSeq[Char]) { + lazy val decodeTable = collection.immutable.TreeMap(encodeTable.zipWithIndex: _*) + } + + lazy val base64 = new B64Scheme(('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ Seq('+', '/')) + lazy val base64Url = new B64Scheme(base64.encodeTable.dropRight(2) ++ Seq('-', '_')) + + implicit class Encoder(b: Array[Byte]) { + private[this] val zero = Array(0, 0).map(_.toByte) + lazy val pad = (3 - b.length % 3) % 3 + + def toBase64(implicit scheme: B64Scheme = base64): String = { + def sixBits(x: Array[Byte]): Seq[Int] = { + val a = (x(0) & 0xfc) >> 2 + val b = ((x(0) & 0x3) << 4) + ((x(1) & 0xf0) >> 4) + val c = ((x(1) & 0xf) << 2) + ((x(2) & 0xc0) >> 6) + val d = (x(2)) & 0x3f + Seq(a, b, c, d) + } + ((b ++ zero.take(pad)).grouped(3) + .flatMap(sixBits(_)) + .map(x => scheme.encodeTable(x)) + .toSeq + .dropRight(pad) :+ "=" * pad) + .mkString + } + } + + implicit class Decoder(s: String) { + lazy val cleanS = s.reverse.dropWhile(_ == '=').reverse + lazy val pad = s.length - cleanS.length + + def toByteArray(implicit scheme: B64Scheme = base64): Array[Byte] = { + def threeBytes(s: Seq[Char]): Array[Byte] = { + val r = s.map(scheme.decodeTable(_)).foldLeft(0)((a,b)=>(a << 6) +b) + java.nio.ByteBuffer.allocate(8).putLong(r).array().takeRight(3) + } + if (pad > 2 || s.length % 4 != 0) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s) + if (!cleanS.forall(scheme.encodeTable.contains(_))) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s) + + (cleanS + "A" * pad) + .grouped(4) + .map(threeBytes(_)) + .flatten + .toArray + .dropRight(pad) + } + } + +} diff --git a/test/ByteUtilsSpec.scala b/test/ByteUtilsSpec.scala new file mode 100644 index 0000000..8c7c7c9 --- /dev/null +++ b/test/ByteUtilsSpec.scala @@ -0,0 +1,85 @@ +package ixee.cryptopals.test + +import ixee.cryptopals.utils.ByteUtils + +import com.ixee.IxeeSpec + +class ByteUtilsSpec extends IxeeSpec { + + "ByteUtils" - { + + "SizedNumeric" - { + + ".liftedTo" - { + + "when the destination is >= the source in range" - { + + "losslessly converts to the destination type" - { + + } + + } + + "when the destination is smaller than the source" - { + + "throws an exception TODO: make it a Try?" - { + + } + + } + + } + + ".truncatedTo" - { + + "converts to the target type, truncating extra bytes" - { + + } + + } + + ".byteSize" - { + + "returns the size of a the type, in bytes" - { + + } + + } + + ".bitSize" - { + + "returns the size of a type, in bits" - { + + } + + } + + } + + "BitOps" - { + + "provides typed operations that don't upcast to Int" - { + + } + + } + + "toByteArray TODO: toByteSeq?" - { + + "returns the value as an array of bytes TODO: endianness?" - { + + } + + } + + "toBinaryString" - { + + "returns the value as a string of 1s and 0s, padded to the full size" - { + + } + + } + + } + +} diff --git a/test/ConversionUtilsSpec.scala b/test/ConversionUtilsSpec.scala new file mode 100644 index 0000000..d6c17b3 --- /dev/null +++ b/test/ConversionUtilsSpec.scala @@ -0,0 +1,31 @@ +package ixee.cryptopals.test + +import com.ixee.IxeeSpec +import org.scalatest._ + +class ConversionUtilsSpec extends FreeSpec with MustMatchers { + + import ixee.cryptopals.utils.ConversionUtils._ + + "ConversionUtils" - { + ".hexStr2Bytes" - { + "converts a hexadecimal string to an equivalent iterator of bytes" - { + hexStr2Bytes("12348765").toArray must equal( + Array[Byte]( + 0x12.toByte, + 0x34.toByte, + 0x87.toByte, + 0x65.toByte + ) + ) + } + + "when the string is not an even length" - { + "prepends a 0" - { + hexStr2Bytes("2123").toArray must equal(hexStr2Bytes("0123").toArray) + } + } + } + } + +} -- cgit v1.1