summaryrefslogtreecommitdiff
path: root/src/ByteUtils.scala
blob: 723164dd4ac259e7b3f833ff0af779500e558900 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package ixee.cryptopals.utils

import FunctionUtils.tup
import ConversionUtils._

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 : BitOps](from: U): T = fromLong(from.toLong)
    def truncate[U : SizedNumeric : BitOps](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
    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
    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
    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)
    def @+(b: T): T = implicitly[BitOps[T]].@+(x, b)
    def @-(b: T): T = implicitly[BitOps[T]].@-(x, b)
  }

  implicit class SizedWithByteInfo[T : SizedNumeric : BitOps](x: T) {
    def byteSize = implicitly[SizedNumeric[T]].byteSize
    def octetSize = byteSize * 2
    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 hex: String = s"%0${octetSize}x" format x
    def bitsSet: Int = {
      var bits = 0
      var num = x

      while (num.toLong > 0) {
        bits = bits + 1
        num = num @& (num @- 1.truncatedTo[T])
      }

      bits
    }
    def truncatedTo[U : SizedNumeric]: U = {
      implicitly[SizedNumeric[U]].truncate(x)
    }
  }

  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 }

  implicit class SeqByteOps[T : SizedNumeric : BitOps](seq: Seq[T]) {
    def xor(other: Seq[T]): Seq[T] =
      seq.zip(other).map(tup(_ @^ _))
  }

  def hammingDistance(a: String, b: String): Int =
    hammingDistance(a.asBytes, b.asBytes)

  def hammingDistance[T : BitOps : SizedNumeric](a: Seq[T], b: Seq[T]): Int =
    (a xor b).map(_.bitsSet).reduce(_ @+ _)

  def avgHammingDistance[T : BitOps : SizedNumeric](xs: Seq[Seq[T]]): Double =
    xs.sliding(2).map({ case Stream(a: Seq[T], b: Seq[T]) => hammingDistance(a, b) }).reduce(_ @+ _) / xs.length.toDouble
}