(**************************************************************************)
(*                                                                        *)
     Copyright 2016 - 2018 OCamlPro                                        
(*                                                                        *)
(*  All rights reserved. This file is distributed under the terms of the  *)
   GNU Lesser General Public License version 2.1 , with the special       
(*  exception on linking described in the file LICENSE.                   *)
(*                                                                        *)
(**************************************************************************)

module type ConvSig = sig
  type t

  val bytes : int

  val toggle_big_endian : t -> t
end

module type BufSig = sig
  module Conv : ConvSig

  type t

  val unsafe_get : t -> int -> Conv.t

  val unsafe_set : t -> int -> Conv.t -> unit
end

module type InputSig = sig
  type src

  type t

  type chunk

  type elt

  val init : blocksize:int -> src -> t

  val close : t -> unit

  val byte_size : t -> int

  (** padded with 0, getting chunks after the end of input is allowed *)
  val get_chunk : t -> int -> chunk

  val get : chunk -> int -> elt

  (** only allowed after the end of input *)
  val set : chunk -> int -> elt -> unit

  (** only allowed after the end of input*)
  val set_byte : chunk -> int -> char -> unit
end

module Conv32 = struct
  type t = int32

  let bytes = 4

  external swap : t -> t = "%bswap_int32"

  let toggle_big_endian =
    if Sys.big_endian then
      fun x ->
    x
    else
      swap
end

module Conv64 = struct
  type t = int64

  let bytes = 8

  external swap : t -> t = "%bswap_int64"

  let toggle_big_endian =
    if Sys.big_endian then
      fun x ->
    x
    else
      swap
end

module B = Bigarray
module A = B.Array1

type bigstring = (char, B.int8_unsigned_elt, B.c_layout) A.t

module Buf_Bigstring32 = struct
  module Conv = Conv32

  type t = bigstring

  external unsafe_get : t -> int -> Conv.t = "%caml_bigstring_get32u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_bigstring_set32u"
end

module Buf_Bigstring64 = struct
  module Conv = Conv64

  type t = bigstring

  external unsafe_get : t -> int -> Conv.t = "%caml_bigstring_get64u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_bigstring_set64u"
end

module Buf_String32 = struct
  module Conv = Conv32

  type t = Bytes.t

  external unsafe_get : t -> int -> Conv.t = "%caml_string_get32u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_string_set32u"
end

module Buf_String64 = struct
  module Conv = Conv64

  type t = Bytes.t

  external unsafe_get : t -> int -> Conv.t = "%caml_string_get64u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_string_set64u"
end

module Input_file (Buf : BufSig with type t = bigstring) = struct
  type src = string (* filename *)

  type t =
    { fd : Unix.file_descr;
      blocksize : int;
      buf : Buf.t
    }

  type chunk = Buf.t

  type elt = Buf.Conv.t

  let init ~blocksize src =
    let fd = Unix.openfile src [ Unix.O_RDONLY ] 0 in
    let buf =
      B.(array1_of_genarray (Unix.map_file fd B.char c_layout false [| -1 |]))
    in
    { fd; blocksize; buf }

  let close { fd; _ } = Unix.close fd

  let byte_size { buf; _ } = A.dim buf

  let get_chunk { blocksize; buf; _ } i =
    let len = A.dim buf in
    let block_bytes = blocksize * Buf.Conv.bytes in
    if (i + 1) * block_bytes <= len then
      A.sub buf (i * block_bytes) block_bytes
    else
      let ba = A.create B.char B.c_layout block_bytes in
      A.fill ba '\x00';
      if i * block_bytes < len then
        A.blit
          (A.sub buf (i * block_bytes) (len mod block_bytes))
          (A.sub ba 0 (len mod block_bytes));
      ba

  let get chunk i =
    Buf.Conv.toggle_big_endian (Buf.unsafe_get chunk (i * Buf.Conv.bytes))

  let set chunk i x =
    Buf.unsafe_set chunk (i * Buf.Conv.bytes) (Buf.Conv.toggle_big_endian x)

  let set_byte chunk i c = A.unsafe_set chunk i c
end

module Input_string (Buf : BufSig with type t = Bytes.t) = struct
  type src = Bytes.t

  type t =
    { blocksize : int;
      buf : Bytes.t
    }

  type chunk =
    { offset : int;
      b : Bytes.t
    }

  type elt = Buf.Conv.t

  let init ~blocksize buf = { blocksize; buf }

  let close _ = ()

  let byte_size { buf; _ } = Bytes.length buf

  let get_chunk { blocksize; buf } i =
    let len = Bytes.length buf in
    let block_bytes = blocksize * Buf.Conv.bytes in
    if (i + 1) * block_bytes <= len then
      { offset = i * block_bytes; b = buf }
    else
      let b = Bytes.make block_bytes '\x00' in
      if i * block_bytes < len then
        Bytes.blit buf (i * block_bytes) b 0 (len mod block_bytes);
      { offset = 0; b }

  let get { offset; b } i =
    Buf.Conv.toggle_big_endian
      (Buf.unsafe_get b (offset + (i * Buf.Conv.bytes)))

  let set { offset; b } i x =
    Buf.unsafe_set b
      (offset + (i * Buf.Conv.bytes))
      (Buf.Conv.toggle_big_endian x)

  let set_byte { offset; b } i c = Bytes.unsafe_set b (offset + i) c
end

module Make_SHA256 (I : InputSig with type elt = int32) = struct
  open Int32

  let k =
    [| 0x428a2f98l;
       0x71374491l;
       0xb5c0fbcfl;
       0xe9b5dba5l;
       0x3956c25bl;
       0x59f111f1l;
       0x923f82a4l;
       0xab1c5ed5l;
       0xd807aa98l;
       0x12835b01l;
       0x243185bel;
       0x550c7dc3l;
       0x72be5d74l;
       0x80deb1fel;
       0x9bdc06a7l;
       0xc19bf174l;
       0xe49b69c1l;
       0xefbe4786l;
       0x0fc19dc6l;
       0x240ca1ccl;
       0x2de92c6fl;
       0x4a7484aal;
       0x5cb0a9dcl;
       0x76f988dal;
       0x983e5152l;
       0xa831c66dl;
       0xb00327c8l;
       0xbf597fc7l;
       0xc6e00bf3l;
       0xd5a79147l;
       0x06ca6351l;
       0x14292967l;
       0x27b70a85l;
       0x2e1b2138l;
       0x4d2c6dfcl;
       0x53380d13l;
       0x650a7354l;
       0x766a0abbl;
       0x81c2c92el;
       0x92722c85l;
       0xa2bfe8a1l;
       0xa81a664bl;
       0xc24b8b70l;
       0xc76c51a3l;
       0xd192e819l;
       0xd6990624l;
       0xf40e3585l;
       0x106aa070l;
       0x19a4c116l;
       0x1e376c08l;
       0x2748774cl;
       0x34b0bcb5l;
       0x391c0cb3l;
       0x4ed8aa4al;
       0x5b9cca4fl;
       0x682e6ff3l;
       0x748f82eel;
       0x78a5636fl;
       0x84c87814l;
       0x8cc70208l;
       0x90befffal;
       0xa4506cebl;
       0xbef9a3f7l;
       0xc67178f2l
    |]

  let ch x y z = logxor (logand x y) (logand (lognot x) z)

  let maj x y z = logxor (logand x y) (logxor (logand x z) (logand y z))

  let sum0 x =
    logxor
      (logor (shift_right_logical x 2) (shift_left x (32 - 2)))
      (logxor
         (logor (shift_right_logical x 13) (shift_left x (32 - 13)))
         (logor (shift_right_logical x 22) (shift_left x (32 - 22))) )

  let sum1 x =
    logxor
      (logor (shift_right_logical x 6) (shift_left x (32 - 6)))
      (logxor
         (logor (shift_right_logical x 11) (shift_left x (32 - 11)))
         (logor (shift_right_logical x 25) (shift_left x (32 - 25))) )

  let lsig0 x =
    logxor
      (logor (shift_right_logical x 7) (shift_left x (32 - 7)))
      (logxor
         (logor (shift_right_logical x 18) (shift_left x (32 - 18)))
         (logor (shift_right_logical x 3) (shift_right_logical x 3)) )

  let lsig1 x =
    logxor
      (logor (shift_right_logical x 17) (shift_left x (32 - 17)))
      (logxor
         (logor (shift_right_logical x 19) (shift_left x (32 - 19)))
         (logor (shift_right_logical x 10) (shift_right_logical x 10)) )

  let sha_init =
    ( 0x6a09e667l,
      0xbb67ae85l,
      0x3c6ef372l,
      0xa54ff53al,
      0x510e527fl,
      0x9b05688cl,
      0x1f83d9abl,
      0x5be0cd19l )

  let hash_block =
    let warr = Array.make 64 0l in
    fun hh block ->
      for t = 0 to 15 do
        warr.(t) <- I.get block t
      done;
      for t = 16 to 63 do
        warr.(t) <-
          add
            (add (lsig1 warr.(t - 2)) warr.(t - 7))
            (add (lsig0 warr.(t - 15)) warr.(t - 16))
      done;

      let rec stir t (a, b, c, d, e, f, g, h) =
        if t >= 64 then
          let a', b', c', d', e', f', g', h' = hh in
          ( add a a',
            add b b',
            add c c',
            add d d',
            add e e',
            add f f',
            add g g',
            add h h' )
        else
          let t1 = add (add h (sum1 e)) (add (add (ch e f g) k.(t)) warr.(t)) in
          let t2 = add (sum0 a) (maj a b c) in
          stir (t + 1) (add t1 t2, a, b, c, add d t1, e, f, g)
      in
      stir 0 hh

  let blocksize = 16

  let hash src =
    let bs = I.init ~blocksize src in
    let nbytes = I.byte_size bs in
    let blocks = nbytes / (blocksize * 4) in
    let rem = nbytes mod (blocksize * 4) in
    let h = ref sha_init in
    for i = 0 to blocks - 1 do
      h := hash_block !h (I.get_chunk bs i)
    done;
    let lastblock = I.get_chunk bs blocks in
    I.set_byte lastblock rem '\x80';
    let lastblock =
      if rem <= 55 then
        lastblock
      else (
        h := hash_block !h lastblock;
        I.get_chunk bs (blocks + 1)
      )
    in
    let bitsz = Int64.mul 8L (Int64.of_int nbytes) in
    I.set lastblock 14 Int64.(to_int32 (shift_right_logical bitsz 32));
    I.set lastblock 15 Int64.(to_int32 (logand 0xffffffffL bitsz));
    let a, b, c, d, e, f, g, h = hash_block !h lastblock in
    I.close bs;
    Printf.sprintf "%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx" a b c d e f g h
end

module Make_SHA512 (I : InputSig with type elt = int64) = struct
  open Int64

  let k =
    [| 0x428a2f98d728ae22L;
       0x7137449123ef65cdL;
       0xb5c0fbcfec4d3b2fL;
       0xe9b5dba58189dbbcL;
       0x3956c25bf348b538L;
       0x59f111f1b605d019L;
       0x923f82a4af194f9bL;
       0xab1c5ed5da6d8118L;
       0xd807aa98a3030242L;
       0x12835b0145706fbeL;
       0x243185be4ee4b28cL;
       0x550c7dc3d5ffb4e2L;
       0x72be5d74f27b896fL;
       0x80deb1fe3b1696b1L;
       0x9bdc06a725c71235L;
       0xc19bf174cf692694L;
       0xe49b69c19ef14ad2L;
       0xefbe4786384f25e3L;
       0x0fc19dc68b8cd5b5L;
       0x240ca1cc77ac9c65L;
       0x2de92c6f592b0275L;
       0x4a7484aa6ea6e483L;
       0x5cb0a9dcbd41fbd4L;
       0x76f988da831153b5L;
       0x983e5152ee66dfabL;
       0xa831c66d2db43210L;
       0xb00327c898fb213fL;
       0xbf597fc7beef0ee4L;
       0xc6e00bf33da88fc2L;
       0xd5a79147930aa725L;
       0x06ca6351e003826fL;
       0x142929670a0e6e70L;
       0x27b70a8546d22ffcL;
       0x2e1b21385c26c926L;
       0x4d2c6dfc5ac42aedL;
       0x53380d139d95b3dfL;
       0x650a73548baf63deL;
       0x766a0abb3c77b2a8L;
       0x81c2c92e47edaee6L;
       0x92722c851482353bL;
       0xa2bfe8a14cf10364L;
       0xa81a664bbc423001L;
       0xc24b8b70d0f89791L;
       0xc76c51a30654be30L;
       0xd192e819d6ef5218L;
       0xd69906245565a910L;
       0xf40e35855771202aL;
       0x106aa07032bbd1b8L;
       0x19a4c116b8d2d0c8L;
       0x1e376c085141ab53L;
       0x2748774cdf8eeb99L;
       0x34b0bcb5e19b48a8L;
       0x391c0cb3c5c95a63L;
       0x4ed8aa4ae3418acbL;
       0x5b9cca4f7763e373L;
       0x682e6ff3d6b2b8a3L;
       0x748f82ee5defb2fcL;
       0x78a5636f43172f60L;
       0x84c87814a1f0ab72L;
       0x8cc702081a6439ecL;
       0x90befffa23631e28L;
       0xa4506cebde82bde9L;
       0xbef9a3f7b2c67915L;
       0xc67178f2e372532bL;
       0xca273eceea26619cL;
       0xd186b8c721c0c207L;
       0xeada7dd6cde0eb1eL;
       0xf57d4f7fee6ed178L;
       0x06f067aa72176fbaL;
       0x0a637dc5a2c898a6L;
       0x113f9804bef90daeL;
       0x1b710b35131c471bL;
       0x28db77f523047d84L;
       0x32caab7b40c72493L;
       0x3c9ebe0a15c9bebcL;
       0x431d67c49c100d4cL;
       0x4cc5d4becb3e42b6L;
       0x597f299cfc657e2aL;
       0x5fcb6fab3ad6faecL;
       0x6c44198c4a475817L
    |]

  let rotate x n = logor (shift_right_logical x n) (shift_left x (64 - n))

  let ch x y z = logxor (logand x y) (logand (lognot x) z)

  let maj x y z = logxor (logand x y) (logxor (logand x z) (logand y z))

  let sum0 x = logxor (rotate x 28) (logxor (rotate x 34) (rotate x 39))

  let sum1 x = logxor (rotate x 14) (logxor (rotate x 18) (rotate x 41))

  let lsig0 x =
    logxor (rotate x 1) (logxor (rotate x 8) (shift_right_logical x 7))

  let lsig1 x =
    logxor (rotate x 19) (logxor (rotate x 61) (shift_right_logical x 6))

  let sha_init =
    ( 0x6a09e667f3bcc908L,
      0xbb67ae8584caa73bL,
      0x3c6ef372fe94f82bL,
      0xa54ff53a5f1d36f1L,
      0x510e527fade682d1L,
      0x9b05688c2b3e6c1fL,
      0x1f83d9abfb41bd6bL,
      0x5be0cd19137e2179L )

  let hash_block =
    let warr = Array.make 80 0L in
    fun hh block ->
      for t = 0 to 15 do
        warr.(t) <- I.get block t
      done;
      for t = 16 to 79 do
        warr.(t) <-
          add
            (add (lsig1 warr.(t - 2)) warr.(t - 7))
            (add (lsig0 warr.(t - 15)) warr.(t - 16))
      done;

      let rec stir t (a, b, c, d, e, f, g, h) =
        if t >= 80 then
          let a', b', c', d', e', f', g', h' = hh in
          ( add a a',
            add b b',
            add c c',
            add d d',
            add e e',
            add f f',
            add g g',
            add h h' )
        else
          let t1 = add (add h (sum1 e)) (add (add (ch e f g) k.(t)) warr.(t)) in
          let t2 = add (sum0 a) (maj a b c) in
          stir (t + 1) (add t1 t2, a, b, c, add d t1, e, f, g)
      in
      stir 0 hh

  let blocksize = 16

  let hash src =
    let bs = I.init ~blocksize src in
    let nbytes = I.byte_size bs in
    let blocks = nbytes / (blocksize * 8) in
    let rem = nbytes mod (blocksize * 8) in
    let h = ref sha_init in
    for i = 0 to blocks - 1 do
      h := hash_block !h (I.get_chunk bs i)
    done;
    let lastblock = I.get_chunk bs blocks in
    I.set_byte lastblock rem '\x80';
    let lastblock =
      if rem <= 111 then
        lastblock
      else (
        h := hash_block !h lastblock;
        I.get_chunk bs (blocks + 1)
      )
    in
  We assume sz fits in 61 bits ...
    let bitsz = Int64.mul 8L (Int64.of_int nbytes) in
    I.set lastblock 15 bitsz;
    let a, b, c, d, e, f, g, h = hash_block !h lastblock in
    I.close bs;
    Printf.sprintf "%016Lx%016Lx%016Lx%016Lx%016Lx%016Lx%016Lx%016Lx" a b c d e
      f g h
end

module SHA256_file = Make_SHA256 (Input_file (Buf_Bigstring32))
module SHA512_file = Make_SHA512 (Input_file (Buf_Bigstring64))
module SHA256_string = Make_SHA256 (Input_string (Buf_String32))
module SHA512_string = Make_SHA512 (Input_string (Buf_String64))

let sha256_file = SHA256_file.hash

let sha512_file = SHA512_file.hash

let hash_file = function
  | `SHA256 -> sha256_file
  | `SHA512 -> sha512_file

let sha256_bytes = SHA256_string.hash

let sha512_bytes = SHA512_string.hash

let hash_bytes = function
  | `SHA256 -> sha256_bytes
  | `SHA512 -> sha512_bytes

let sha256 = sha256_file

let sha512 = sha512_file

let hash = hash_file

************************************************************************
                                                                        
                                                                        
  All rights reserved. This file is distributed under the terms of the  
  exception on linking described in the file LICENSE.                   
                                                                        
************************************************************************
* padded with 0, getting chunks after the end of input is allowed 
* only allowed after the end of input 
* only allowed after the end of input
 filename 
     Copyright 2016 - 2018 OCamlPro                                        
   GNU Lesser General Public License version 2.1 , with the special       

module type ConvSig = sig
  type t

  val bytes : int

  val toggle_big_endian : t -> t
end

module type BufSig = sig
  module Conv : ConvSig

  type t

  val unsafe_get : t -> int -> Conv.t

  val unsafe_set : t -> int -> Conv.t -> unit
end

module type InputSig = sig
  type src

  type t

  type chunk

  type elt

  val init : blocksize:int -> src -> t

  val close : t -> unit

  val byte_size : t -> int

  val get_chunk : t -> int -> chunk

  val get : chunk -> int -> elt

  val set : chunk -> int -> elt -> unit

  val set_byte : chunk -> int -> char -> unit
end

module Conv32 = struct
  type t = int32

  let bytes = 4

  external swap : t -> t = "%bswap_int32"

  let toggle_big_endian =
    if Sys.big_endian then
      fun x ->
    x
    else
      swap
end

module Conv64 = struct
  type t = int64

  let bytes = 8

  external swap : t -> t = "%bswap_int64"

  let toggle_big_endian =
    if Sys.big_endian then
      fun x ->
    x
    else
      swap
end

module B = Bigarray
module A = B.Array1

type bigstring = (char, B.int8_unsigned_elt, B.c_layout) A.t

module Buf_Bigstring32 = struct
  module Conv = Conv32

  type t = bigstring

  external unsafe_get : t -> int -> Conv.t = "%caml_bigstring_get32u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_bigstring_set32u"
end

module Buf_Bigstring64 = struct
  module Conv = Conv64

  type t = bigstring

  external unsafe_get : t -> int -> Conv.t = "%caml_bigstring_get64u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_bigstring_set64u"
end

module Buf_String32 = struct
  module Conv = Conv32

  type t = Bytes.t

  external unsafe_get : t -> int -> Conv.t = "%caml_string_get32u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_string_set32u"
end

module Buf_String64 = struct
  module Conv = Conv64

  type t = Bytes.t

  external unsafe_get : t -> int -> Conv.t = "%caml_string_get64u"

  external unsafe_set : t -> int -> Conv.t -> unit = "%caml_string_set64u"
end

module Input_file (Buf : BufSig with type t = bigstring) = struct

  type t =
    { fd : Unix.file_descr;
      blocksize : int;
      buf : Buf.t
    }

  type chunk = Buf.t

  type elt = Buf.Conv.t

  let init ~blocksize src =
    let fd = Unix.openfile src [ Unix.O_RDONLY ] 0 in
    let buf =
      B.(array1_of_genarray (Unix.map_file fd B.char c_layout false [| -1 |]))
    in
    { fd; blocksize; buf }

  let close { fd; _ } = Unix.close fd

  let byte_size { buf; _ } = A.dim buf

  let get_chunk { blocksize; buf; _ } i =
    let len = A.dim buf in
    let block_bytes = blocksize * Buf.Conv.bytes in
    if (i + 1) * block_bytes <= len then
      A.sub buf (i * block_bytes) block_bytes
    else
      let ba = A.create B.char B.c_layout block_bytes in
      A.fill ba '\x00';
      if i * block_bytes < len then
        A.blit
          (A.sub buf (i * block_bytes) (len mod block_bytes))
          (A.sub ba 0 (len mod block_bytes));
      ba

  let get chunk i =
    Buf.Conv.toggle_big_endian (Buf.unsafe_get chunk (i * Buf.Conv.bytes))

  let set chunk i x =
    Buf.unsafe_set chunk (i * Buf.Conv.bytes) (Buf.Conv.toggle_big_endian x)

  let set_byte chunk i c = A.unsafe_set chunk i c
end

module Input_string (Buf : BufSig with type t = Bytes.t) = struct
  type src = Bytes.t

  type t =
    { blocksize : int;
      buf : Bytes.t
    }

  type chunk =
    { offset : int;
      b : Bytes.t
    }

  type elt = Buf.Conv.t

  let init ~blocksize buf = { blocksize; buf }

  let close _ = ()

  let byte_size { buf; _ } = Bytes.length buf

  let get_chunk { blocksize; buf } i =
    let len = Bytes.length buf in
    let block_bytes = blocksize * Buf.Conv.bytes in
    if (i + 1) * block_bytes <= len then
      { offset = i * block_bytes; b = buf }
    else
      let b = Bytes.make block_bytes '\x00' in
      if i * block_bytes < len then
        Bytes.blit buf (i * block_bytes) b 0 (len mod block_bytes);
      { offset = 0; b }

  let get { offset; b } i =
    Buf.Conv.toggle_big_endian
      (Buf.unsafe_get b (offset + (i * Buf.Conv.bytes)))

  let set { offset; b } i x =
    Buf.unsafe_set b
      (offset + (i * Buf.Conv.bytes))
      (Buf.Conv.toggle_big_endian x)

  let set_byte { offset; b } i c = Bytes.unsafe_set b (offset + i) c
end

module Make_SHA256 (I : InputSig with type elt = int32) = struct
  open Int32

  let k =
    [| 0x428a2f98l;
       0x71374491l;
       0xb5c0fbcfl;
       0xe9b5dba5l;
       0x3956c25bl;
       0x59f111f1l;
       0x923f82a4l;
       0xab1c5ed5l;
       0xd807aa98l;
       0x12835b01l;
       0x243185bel;
       0x550c7dc3l;
       0x72be5d74l;
       0x80deb1fel;
       0x9bdc06a7l;
       0xc19bf174l;
       0xe49b69c1l;
       0xefbe4786l;
       0x0fc19dc6l;
       0x240ca1ccl;
       0x2de92c6fl;
       0x4a7484aal;
       0x5cb0a9dcl;
       0x76f988dal;
       0x983e5152l;
       0xa831c66dl;
       0xb00327c8l;
       0xbf597fc7l;
       0xc6e00bf3l;
       0xd5a79147l;
       0x06ca6351l;
       0x14292967l;
       0x27b70a85l;
       0x2e1b2138l;
       0x4d2c6dfcl;
       0x53380d13l;
       0x650a7354l;
       0x766a0abbl;
       0x81c2c92el;
       0x92722c85l;
       0xa2bfe8a1l;
       0xa81a664bl;
       0xc24b8b70l;
       0xc76c51a3l;
       0xd192e819l;
       0xd6990624l;
       0xf40e3585l;
       0x106aa070l;
       0x19a4c116l;
       0x1e376c08l;
       0x2748774cl;
       0x34b0bcb5l;
       0x391c0cb3l;
       0x4ed8aa4al;
       0x5b9cca4fl;
       0x682e6ff3l;
       0x748f82eel;
       0x78a5636fl;
       0x84c87814l;
       0x8cc70208l;
       0x90befffal;
       0xa4506cebl;
       0xbef9a3f7l;
       0xc67178f2l
    |]

  let ch x y z = logxor (logand x y) (logand (lognot x) z)

  let maj x y z = logxor (logand x y) (logxor (logand x z) (logand y z))

  let sum0 x =
    logxor
      (logor (shift_right_logical x 2) (shift_left x (32 - 2)))
      (logxor
         (logor (shift_right_logical x 13) (shift_left x (32 - 13)))
         (logor (shift_right_logical x 22) (shift_left x (32 - 22))) )

  let sum1 x =
    logxor
      (logor (shift_right_logical x 6) (shift_left x (32 - 6)))
      (logxor
         (logor (shift_right_logical x 11) (shift_left x (32 - 11)))
         (logor (shift_right_logical x 25) (shift_left x (32 - 25))) )

  let lsig0 x =
    logxor
      (logor (shift_right_logical x 7) (shift_left x (32 - 7)))
      (logxor
         (logor (shift_right_logical x 18) (shift_left x (32 - 18)))
         (logor (shift_right_logical x 3) (shift_right_logical x 3)) )

  let lsig1 x =
    logxor
      (logor (shift_right_logical x 17) (shift_left x (32 - 17)))
      (logxor
         (logor (shift_right_logical x 19) (shift_left x (32 - 19)))
         (logor (shift_right_logical x 10) (shift_right_logical x 10)) )

  let sha_init =
    ( 0x6a09e667l,
      0xbb67ae85l,
      0x3c6ef372l,
      0xa54ff53al,
      0x510e527fl,
      0x9b05688cl,
      0x1f83d9abl,
      0x5be0cd19l )

  let hash_block =
    let warr = Array.make 64 0l in
    fun hh block ->
      for t = 0 to 15 do
        warr.(t) <- I.get block t
      done;
      for t = 16 to 63 do
        warr.(t) <-
          add
            (add (lsig1 warr.(t - 2)) warr.(t - 7))
            (add (lsig0 warr.(t - 15)) warr.(t - 16))
      done;

      let rec stir t (a, b, c, d, e, f, g, h) =
        if t >= 64 then
          let a', b', c', d', e', f', g', h' = hh in
          ( add a a',
            add b b',
            add c c',
            add d d',
            add e e',
            add f f',
            add g g',
            add h h' )
        else
          let t1 = add (add h (sum1 e)) (add (add (ch e f g) k.(t)) warr.(t)) in
          let t2 = add (sum0 a) (maj a b c) in
          stir (t + 1) (add t1 t2, a, b, c, add d t1, e, f, g)
      in
      stir 0 hh

  let blocksize = 16

  let hash src =
    let bs = I.init ~blocksize src in
    let nbytes = I.byte_size bs in
    let blocks = nbytes / (blocksize * 4) in
    let rem = nbytes mod (blocksize * 4) in
    let h = ref sha_init in
    for i = 0 to blocks - 1 do
      h := hash_block !h (I.get_chunk bs i)
    done;
    let lastblock = I.get_chunk bs blocks in
    I.set_byte lastblock rem '\x80';
    let lastblock =
      if rem <= 55 then
        lastblock
      else (
        h := hash_block !h lastblock;
        I.get_chunk bs (blocks + 1)
      )
    in
    let bitsz = Int64.mul 8L (Int64.of_int nbytes) in
    I.set lastblock 14 Int64.(to_int32 (shift_right_logical bitsz 32));
    I.set lastblock 15 Int64.(to_int32 (logand 0xffffffffL bitsz));
    let a, b, c, d, e, f, g, h = hash_block !h lastblock in
    I.close bs;
    Printf.sprintf "%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx" a b c d e f g h
end

module Make_SHA512 (I : InputSig with type elt = int64) = struct
  open Int64

  let k =
    [| 0x428a2f98d728ae22L;
       0x7137449123ef65cdL;
       0xb5c0fbcfec4d3b2fL;
       0xe9b5dba58189dbbcL;
       0x3956c25bf348b538L;
       0x59f111f1b605d019L;
       0x923f82a4af194f9bL;
       0xab1c5ed5da6d8118L;
       0xd807aa98a3030242L;
       0x12835b0145706fbeL;
       0x243185be4ee4b28cL;
       0x550c7dc3d5ffb4e2L;
       0x72be5d74f27b896fL;
       0x80deb1fe3b1696b1L;
       0x9bdc06a725c71235L;
       0xc19bf174cf692694L;
       0xe49b69c19ef14ad2L;
       0xefbe4786384f25e3L;
       0x0fc19dc68b8cd5b5L;
       0x240ca1cc77ac9c65L;
       0x2de92c6f592b0275L;
       0x4a7484aa6ea6e483L;
       0x5cb0a9dcbd41fbd4L;
       0x76f988da831153b5L;
       0x983e5152ee66dfabL;
       0xa831c66d2db43210L;
       0xb00327c898fb213fL;
       0xbf597fc7beef0ee4L;
       0xc6e00bf33da88fc2L;
       0xd5a79147930aa725L;
       0x06ca6351e003826fL;
       0x142929670a0e6e70L;
       0x27b70a8546d22ffcL;
       0x2e1b21385c26c926L;
       0x4d2c6dfc5ac42aedL;
       0x53380d139d95b3dfL;
       0x650a73548baf63deL;
       0x766a0abb3c77b2a8L;
       0x81c2c92e47edaee6L;
       0x92722c851482353bL;
       0xa2bfe8a14cf10364L;
       0xa81a664bbc423001L;
       0xc24b8b70d0f89791L;
       0xc76c51a30654be30L;
       0xd192e819d6ef5218L;
       0xd69906245565a910L;
       0xf40e35855771202aL;
       0x106aa07032bbd1b8L;
       0x19a4c116b8d2d0c8L;
       0x1e376c085141ab53L;
       0x2748774cdf8eeb99L;
       0x34b0bcb5e19b48a8L;
       0x391c0cb3c5c95a63L;
       0x4ed8aa4ae3418acbL;
       0x5b9cca4f7763e373L;
       0x682e6ff3d6b2b8a3L;
       0x748f82ee5defb2fcL;
       0x78a5636f43172f60L;
       0x84c87814a1f0ab72L;
       0x8cc702081a6439ecL;
       0x90befffa23631e28L;
       0xa4506cebde82bde9L;
       0xbef9a3f7b2c67915L;
       0xc67178f2e372532bL;
       0xca273eceea26619cL;
       0xd186b8c721c0c207L;
       0xeada7dd6cde0eb1eL;
       0xf57d4f7fee6ed178L;
       0x06f067aa72176fbaL;
       0x0a637dc5a2c898a6L;
       0x113f9804bef90daeL;
       0x1b710b35131c471bL;
       0x28db77f523047d84L;
       0x32caab7b40c72493L;
       0x3c9ebe0a15c9bebcL;
       0x431d67c49c100d4cL;
       0x4cc5d4becb3e42b6L;
       0x597f299cfc657e2aL;
       0x5fcb6fab3ad6faecL;
       0x6c44198c4a475817L
    |]

  let rotate x n = logor (shift_right_logical x n) (shift_left x (64 - n))

  let ch x y z = logxor (logand x y) (logand (lognot x) z)

  let maj x y z = logxor (logand x y) (logxor (logand x z) (logand y z))

  let sum0 x = logxor (rotate x 28) (logxor (rotate x 34) (rotate x 39))

  let sum1 x = logxor (rotate x 14) (logxor (rotate x 18) (rotate x 41))

  let lsig0 x =
    logxor (rotate x 1) (logxor (rotate x 8) (shift_right_logical x 7))

  let lsig1 x =
    logxor (rotate x 19) (logxor (rotate x 61) (shift_right_logical x 6))

  let sha_init =
    ( 0x6a09e667f3bcc908L,
      0xbb67ae8584caa73bL,
      0x3c6ef372fe94f82bL,
      0xa54ff53a5f1d36f1L,
      0x510e527fade682d1L,
      0x9b05688c2b3e6c1fL,
      0x1f83d9abfb41bd6bL,
      0x5be0cd19137e2179L )

  let hash_block =
    let warr = Array.make 80 0L in
    fun hh block ->
      for t = 0 to 15 do
        warr.(t) <- I.get block t
      done;
      for t = 16 to 79 do
        warr.(t) <-
          add
            (add (lsig1 warr.(t - 2)) warr.(t - 7))
            (add (lsig0 warr.(t - 15)) warr.(t - 16))
      done;

      let rec stir t (a, b, c, d, e, f, g, h) =
        if t >= 80 then
          let a', b', c', d', e', f', g', h' = hh in
          ( add a a',
            add b b',
            add c c',
            add d d',
            add e e',
            add f f',
            add g g',
            add h h' )
        else
          let t1 = add (add h (sum1 e)) (add (add (ch e f g) k.(t)) warr.(t)) in
          let t2 = add (sum0 a) (maj a b c) in
          stir (t + 1) (add t1 t2, a, b, c, add d t1, e, f, g)
      in
      stir 0 hh

  let blocksize = 16

  let hash src =
    let bs = I.init ~blocksize src in
    let nbytes = I.byte_size bs in
    let blocks = nbytes / (blocksize * 8) in
    let rem = nbytes mod (blocksize * 8) in
    let h = ref sha_init in
    for i = 0 to blocks - 1 do
      h := hash_block !h (I.get_chunk bs i)
    done;
    let lastblock = I.get_chunk bs blocks in
    I.set_byte lastblock rem '\x80';
    let lastblock =
      if rem <= 111 then
        lastblock
      else (
        h := hash_block !h lastblock;
        I.get_chunk bs (blocks + 1)
      )
    in
  We assume sz fits in 61 bits ...
    let bitsz = Int64.mul 8L (Int64.of_int nbytes) in
    I.set lastblock 15 bitsz;
    let a, b, c, d, e, f, g, h = hash_block !h lastblock in
    I.close bs;
    Printf.sprintf "%016Lx%016Lx%016Lx%016Lx%016Lx%016Lx%016Lx%016Lx" a b c d e
      f g h
end

module SHA256_file = Make_SHA256 (Input_file (Buf_Bigstring32))
module SHA512_file = Make_SHA512 (Input_file (Buf_Bigstring64))
module SHA256_string = Make_SHA256 (Input_string (Buf_String32))
module SHA512_string = Make_SHA512 (Input_string (Buf_String64))

let sha256_file = SHA256_file.hash

let sha512_file = SHA512_file.hash

let hash_file = function
  | `SHA256 -> sha256_file
  | `SHA512 -> sha512_file

let sha256_bytes = SHA256_string.hash

let sha512_bytes = SHA512_string.hash

let hash_bytes = function
  | `SHA256 -> sha256_bytes
  | `SHA512 -> sha512_bytes

let sha256 = sha256_file

let sha512 = sha512_file

let hash = hash_file
