siphash

class ref SipHash24Streaming
  """
  Provides SipHash24 for non-array data
  given as separate `U64`s.

  ### Usage:

  ```pony
  use "collections"

  actor Main
    new create(env: Env) =>
      try
        let sip = SipHash24Streaming.create()
        for x in Range[U64](1, 100).values() do
          // feed consecutive U64s to be hashed
          sip.update(x)
        end
        // execute finishing steps, reset internal state so this instance can be
        // reused, and output the computed hash
        let hash = sip.finish()
        env.out.print("HASHED: " + hash.string())
      end
  ```

  """
  var _v0: U64 = 0
  var _v1: U64 = 0
  var _v2: U64 = 0
  var _v3: U64 = 0

  var _size: USize = 0

  new ref create() =>
    reset()

  fun ref reset() =>
    """
    Reset the internal state.
    """
    _v0 = SipHash24._k0() xor 0x736f6d6570736575
    _v1 = SipHash24._k1() xor 0x646f72616e646f6d
    _v2 = SipHash24._k0() xor 0x6c7967656e657261
    _v3 = SipHash24._k1() xor 0x7465646279746573
    _size = 0

  fun ref update(m: U64) =>
    """
    Hash the given `m` and update the internal state accordingly.
    """
    _v3 = _v3 xor m
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    _v0 = _v0 xor m
    _size = _size + 8

  fun ref finish(): U64 =>
    """
    This method finally computes the hash from all the data added with `update`,
    and resets the internal state,
    so this instance can be conveniently reused for another hash calculation.
    """
    let b = (_size << USize(56)).u64()
    _v3 = _v3 xor b
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    _v0 = _v0 xor b
    _v2 = _v2 xor 0xFF
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = SipHash24._sipround64(_v0, _v1, _v2, _v3)

    let result = _v0 xor _v1 xor _v2 xor _v3
    reset() // BEWARE
    result

primitive SipHash24

  fun _k0(): U64 => U64(0x8A109C6B22D309FE)
  fun _k1(): U64 => U64(0x9F923FCCB57235E1)

  fun _sipround64(v0: U64, v1: U64, v2: U64, v3: U64): (U64, U64, U64, U64) =>
    var t0 = v0 + v1
    var t1 = v1.rotl(13)
    t1 = t1 xor t0
    t0 = t0.rotl(32)
    var t2 = v2 + v3
    var t3 = v3.rotl(16)
    t3 = t3 xor t2
    t0 = t0 + t3
    t3 = t3.rotl(21)
    t3 = t3 xor t0
    t2 = t2 + t1
    t1 = t1.rotl(17)
    t1 = t1 xor t2
    t2 = t2.rotl(32)


    (t0, t1, t2, t3)

  fun apply[T: ReadSeq[U8] #read](data: T): U64 =>
    let size = data.size()
    var b: U64  = (size << USize(56)).u64()

    var v0 = _k0() xor 0x736f6d6570736575
    var v1 = _k1() xor 0x646f72616e646f6d
    var v2 = _k0() xor 0x6c7967656e657261
    var v3 = _k1() xor 0x7465646279746573

    let endi: USize = size - (size % 8)

    try
      var i = USize(0)
      while i < endi do
        let m: U64 =
          iftype T <: ReadAsNumerics then
            data.read_u64(i)?
          elseif T <: String val then
            data.array().read_u64(i)?
          else
            (data(i)?.u64()) or
            (data(i + 1)?.u64() << 8) or
            (data(i + 2)?.u64() << 16) or
            (data(i + 3)?.u64() << 24) or
            (data(i + 4)?.u64() << 32) or
            (data(i + 5)?.u64() << 40) or
            (data(i + 6)?.u64() << 48) or
            (data(i + 7)?.u64() << 56)
          end
        v3 = v3 xor m
        (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
        (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
        v0 = v0 xor m

        i = i + 8
      end

      // bad emulation of a C switch statement with  fallthrough
      let rest = size and 7
      if rest >= 1 then
        if rest >= 2 then
          if rest >= 3 then
            if rest >= 4 then
              if rest >= 5 then
                if rest >= 6 then
                  if rest == 7 then
                    b = b or (data(endi + 6)?.u64() << 48)
                  end
                  b = b or (data(endi + 5)?.u64() << 40)
                end
                b = b or (data(endi + 4)?.u64() << 32)
              end
              b = b or (data(endi + 3)?.u64() << 24)
            end
            b = b or (data(endi + 2)?.u64() << 16)
          end
          b = b or (data(endi + 1)?.u64() << 8)
        end
        b = b or data(endi)?.u64()
      end

      v3 = v3 xor b
      (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
      v0 = v0 xor b
      v2 = v2 xor 0xFF
      (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround64(v0, v1, v2, v3)
      v0 xor v1 xor v2 xor v3
    else
      // should never happen, but we can't prove it to the compiler...
      -1
    end


class ref HalfSipHash24Streaming
  var _v0: U32 = 0
  var _v1: U32 = 0
  var _v2: U32 = 0
  var _v3: U32 = 0

  var _size: USize = 0

  new ref create() =>
    reset()

  fun ref reset() =>
    """
    Reset the internal state.
    """
    _v0 = HalfSipHash24._k0() xor 0x736f6d65
    _v1 = HalfSipHash24._k1() xor 0x646f7261
    _v2 = HalfSipHash24._k0() xor 0x6c796765
    _v3 = HalfSipHash24._k1() xor 0x74656462
    _size = 0

  fun ref update(m: U32) =>
    _v3 = _v3 xor m
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    _v0 = _v0 xor m
    _size = _size + 4

  fun ref finish(): U32 =>
    let b  = (_size << USize(24)).u32()
    _v3 = _v3 xor b
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    _v0 = _v0 xor b
    _v2 = _v2 xor 0xFF
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    (_v0, _v1, _v2, _v3) = HalfSipHash24._sipround32(_v0, _v1, _v2, _v3)
    let result = _v0 xor _v1 xor _v2 xor _v3
    reset()
    result

primitive HalfSipHash24

  fun _k0(): U32 => U32(0x22D309FE)
  fun _k1(): U32 => U32(0x8A109C6B)

  fun _sipround32(v0: U32, v1: U32, v2: U32, v3: U32): (U32, U32, U32, U32) =>
    var t0 = v0 + v1
    var t1 = v1.rotl(5)
    t1 = t1 xor t0
    t0 = t0.rotl(16)
    var t2 = v2 + v3
    var t3 = v3.rotl(8)
    t3 = t3 xor t2
    t0 = t0 + t3
    t3 = t3.rotl(7)
    t3 = t3 xor t0
    t2 = t2 + t1
    t1 = t1.rotl(13)
    t1 = t1 xor t2
    t2 = t2.rotl(16)

    (t0, t1, t2, t3)

  fun apply[T: ReadSeq[U8] #read](data: T): U32 =>

    let size = data.size()
    var b: U32  = (size << USize(24)).u32()

    var v0 = _k0() xor 0x736f6d65
    var v1 = _k1() xor 0x646f7261
    var v2 = _k0() xor 0x6c796765
    var v3 = _k1() xor 0x74656462

    let endi: USize = size - (size % 4)

    try
      var i = USize(0)
      while i < endi do
        let m: U32 =
          iftype T <: ReadAsNumerics then
            data.read_u32(i)?
          elseif T <: String val then
            data.array().read_u32(i)?
          else
            (data(i)?.u32()) or
            (data(i + 1)?.u32() << 8) or
            (data(i + 2)?.u32() << 16) or
            (data(i + 3)?.u32() << 24)
          end
        v3 = v3 xor m
        (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
        (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
        v0 = v0 xor m

        i = i + 4
      end

      // bad emulation of a C switch statement with  fallthrough
      let rest = size and 3
      if rest >= 1 then
        if rest >= 2 then
          if rest >= 3 then
            b = b or (data(endi + 2)?.u32() << 16)
          end
          b = b or (data(endi + 1)?.u32() << 8)
        end
        b = b or data(endi)?.u32()
      end

      v3 = v3 xor b
      (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
      v0 = v0 xor b
      v2 = v2 xor 0xFF
      (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
      (v0, v1, v2, v3) = _sipround32(v0, v1, v2, v3)
      v0 xor v1 xor v2 xor v3
    else
      // should never happen, but we can't prove it to the compiler...
      -1
    end