#lang racket
(require web-server/stuffers/stuffer
         racket/runtime-path
         (rename-in ffi/unsafe
                    [-> f->]))

(define-runtime-path libcrypto-so
  (case (system-type)
    [(windows) '(so "libeay32")]
    [else '(so "libcrypto")]))

(define libcrypto
  (with-handlers ([exn:fail? (lambda (x) #f)])
    (ffi-lib libcrypto-so '("" "0.9.8b" "0.9.8" "0.9.7"))))

(define EVP_SHA1
  (and libcrypto
       (get-ffi-obj 'EVP_sha1 libcrypto
                    (_fun f-> _fpointer))))

(define HMAC-SHA1/raw
  (if libcrypto
      (get-ffi-obj 'HMAC libcrypto
                   (_fun [EVP_MD : _fpointer = (EVP_SHA1)]
                         [key : _bytes]
                         [key_len : _int = (bytes-length key)]
                         [data : _bytes]
                         [data_len : _int = (bytes-length data)]
                         [md : (_bytes o 20)]
                         [md_len : (_ptr o _uint)]
                         f-> _bytes
                         f-> md))
      (lambda (key data) (error 'HMAC-SHA1/raw "libcrypto could not load"))))

(define (HMAC-SHA1 key data)
  (HMAC-SHA1/raw key data)
  ; It returns the same pointer always
  #;(bytes-copy
   ; A SHA1 is 20 bytes, including 0s
   (make-sized-byte-string (HMAC-SHA1/raw key data) 20)))

(define (HMAC-SHA1-stuffer key)
  (make-stuffer
   (lambda (ib)
     (bytes-append (HMAC-SHA1 key ib) ib))
   (lambda (ob)
     (define hib (subbytes ob 0 20))
     (define ib (subbytes ob 20))
     (define true-hib (HMAC-SHA1 key ib))
     (if (bytes=? hib true-hib)
         ib
         (error 'HMAC-SHA1-stuffer "Signature does not match!")))))

(provide/contract
 [HMAC-SHA1 (bytes? bytes? . -> . bytes?)]
 [HMAC-SHA1-stuffer (bytes? . -> . (stuffer/c bytes? bytes?))])
