Coverage report: /home/ellis/comp/ext/ironclad/src/digests/digest.lisp
Kind | Covered | All | % |
expression | 105 | 496 | 21.2 |
branch | 9 | 52 | 17.3 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;;; digest.lisp -- common functions for hashing
4
;;; defining digest (hash) functions
5
(eval-when (:compile-toplevel :load-toplevel)
6
(defconstant +buffer-size+ (* 128 1024))
7
(defconstant +seq-copy-buffer-size+ 512))
9
(deftype buffer-index () `(integer 0 (,+buffer-size+)))
11
(defun update-digest-from-stream (digest stream &key buffer (start 0) end)
13
((subtypep (stream-element-type stream) '(unsigned-byte 8))
14
(flet ((frob (read-buffer start end)
15
(loop for last-updated = (read-sequence read-buffer stream
16
:start start :end end)
17
do (update-digest digest read-buffer
18
:start start :end last-updated)
19
until (< last-updated end)
20
finally (return digest))))
22
(frob buffer start (or end (length buffer)))
23
(let ((buffer (make-array +buffer-size+ :element-type '(unsigned-byte 8))))
24
(declare (dynamic-extent buffer))
25
(frob buffer 0 +buffer-size+)))))
27
(error 'ironclad-error
28
:format-control "Unsupported stream element-type ~S for stream ~S."
29
:format-arguments (list (stream-element-type stream) stream)))))
31
(declaim (inline update-digest-from-vector))
33
(defun update-digest-from-vector (digest vector start end)
34
;; SBCL and CMUCL have with-array-data, so copying can be avoided even
35
;; for non-simple vectors.
36
(declare (type (vector (unsigned-byte 8)) vector)
37
(type index start end))
38
(sb-kernel:with-array-data ((data vector) (real-start start) (real-end end))
39
(declare (ignore real-end))
40
(update-digest digest data :start real-start :end (+ real-start (- end start)))))
42
;;; Storing a length at the end of the hashed data is very common and
43
;;; can be a small bottleneck when generating lots of hashes over small
44
;;; quantities of data. We assume that the appropriate locations have
45
;;; already been zeroed if necessary. LENGTH is also assumed to be an
46
;;; (effectively) 64-bit quantity.
47
(declaim (inline store-data-length))
48
(defun store-data-length (block length offset &optional big-endian-p)
49
(let ((lo (if big-endian-p (1+ offset) offset))
50
(hi (if big-endian-p offset (1+ offset))))
53
((sb-int:fixnump length)
54
(setf (aref block lo) length))
55
;; Otherwise, we have a bignum.
57
(locally (declare (optimize (safety 0))
60
((= (sb-bignum:%bignum-length length) 1)
61
(setf (aref block lo) (sb-bignum:%bignum-ref length 0)))
63
(setf (aref block lo) (sb-bignum:%bignum-ref length 0)
64
(aref block hi) (sb-bignum:%bignum-ref length 1)))))))
66
(setf (aref block lo) (ldb (byte 32 0) length)
67
(aref block hi) (ldb (byte 32 32) length))))
69
;;; macros for "mid-level" functions
70
(defmacro define-digest-registers ((digest-name &key (endian :big) (size 4) (digest-registers nil)) &rest registers)
71
(let* ((struct-name (symbolicate digest-name '#:-regs))
72
(constructor (symbolicate '#:initial- struct-name))
73
(copier (symbolicate '#:copy- struct-name))
74
(digest-fun (symbolicate digest-name '#:-regs-digest))
75
(register-bit-size (* size 8))
76
(digest-size (* size (or digest-registers
78
(ref-fun (ubref-fun-name register-bit-size (eq endian :big))))
80
(eval-when (:compile-toplevel :load-toplevel :execute)
81
(defstruct (,struct-name
82
(:type (vector (unsigned-byte ,register-bit-size)))
83
(:constructor ,constructor ())
86
;; Some versions of LispWorks incorrectly define STRUCT-NAME as
87
;; a type with DEFSTRUCT, so avoid gratuitous warnings.
88
#-(and lispworks lispworks5.0)
89
(deftype ,struct-name ()
90
'(simple-array (unsigned-byte ,register-bit-size) (,(length registers)))))
91
(defun ,digest-fun (regs buffer start)
92
(declare (type ,struct-name regs)
93
(type simple-octet-vector buffer)
94
(type (integer 0 ,(- array-dimension-limit digest-size)) start)
96
,(let ((inlined-unpacking
97
`(setf ,@(loop for (reg value) in registers
98
for index from 0 below digest-size by size
99
nconc `((,ref-fun buffer (+ start ,index))
100
(,(symbolicate digest-name '#:-regs- reg)
105
`(if (and (= start 0) (<= ,register-bit-size sb-vm:n-word-bits))
106
(sb-kernel:ub8-bash-copy regs 0 buffer 0 ,digest-size)
110
`(if (and (= start 0) (<= ,register-bit-size sb-vm:n-word-bits))
111
(sb-kernel:ub8-bash-copy regs 0 buffer 0 ,digest-size)
113
(t inlined-unpacking)))
116
(defmacro define-digest-updater (digest-name &body body)
117
(destructuring-bind (maybe-doc-string &rest rest) body
118
`(defmethod update-digest ((state ,digest-name) (sequence vector) &key (start 0) (end (length sequence)))
119
,@(when (stringp maybe-doc-string)
120
`(,maybe-doc-string))
122
(check-type sequence simple-octet-vector)
123
(check-type start index)
124
(check-type end index)
125
,@(if (stringp maybe-doc-string)
129
;;; SPECS is either (DIGEST-NAME DIGEST-BYTES) or a list of the same.
130
;;; The latter spelling is for digests that are related, but have
131
;;; different output sizes (e.g. SHA2-512 and SHA2-384). In that case,
132
;;; the first list is expected to be for the "major" variant of the
133
;;; pair; its digest type is expected to be the supertype of the
135
(defmacro define-digest-finalizer (specs &body body)
136
(let* ((single-digest-p (not (consp (car specs))))
137
(specs (if single-digest-p (list specs) specs))
138
(inner-fun-name (symbolicate '#:finalize- (caar specs) '#:-state)))
139
(destructuring-bind (maybe-doc-string &rest rest) body
140
(let ((primary-digest (caar specs)))
141
`(defmethod produce-digest ((state ,primary-digest)
142
&key digest (digest-start 0))
143
,@(when (stringp maybe-doc-string)
144
`(,maybe-doc-string))
145
(flet ((,inner-fun-name (state digest digest-start)
146
;; CCL requires special treatment to not introduce
147
;; array indexing errors.
149
((member :ccl *features*)
150
'(declare (optimize (speed 0))))
152
(macrolet ((finalize-registers (state regs)
153
(declare (ignorable state))
155
(loop for (digest-name digest-length) in ',specs
156
collect `(,digest-name
157
(,(symbolicate digest-name '#:-regs-digest)
158
,regs digest digest-start)))))
160
(second (first clauses))
161
(list* 'etypecase state
162
(reverse clauses))))))
163
,@(if (stringp maybe-doc-string)
166
(let ((digest-size ,(if single-digest-p
167
(second (first specs))
170
(state-copy (copy-digest state)))
173
;; verify that the buffer is large enough
174
(if (<= digest-size (- (length digest) digest-start))
175
(,inner-fun-name state-copy digest digest-start)
176
(error 'insufficient-buffer-space
177
:buffer digest :start digest-start
178
:length digest-size)))
180
(,inner-fun-name state-copy
181
(make-array digest-size
182
:element-type '(unsigned-byte 8))
185
;;; common superclass (superstructure?) for MD5-style digest functions
189
;; This is technically an (UNSIGNED-BYTE 61). But the type-checking
190
;; penalties that imposes on a good 32-bit implementation are
191
;; significant. We've opted to omit the type declaration here. If
192
;; you really need to digest exabytes of data, I'm sure we can work
195
;; Most "64-bit" digest functions (e.g. SHA512) will need to override
196
;; this initial value in an &AUX.
197
(buffer (make-array 64 :element-type '(unsigned-byte 8)) :read-only t
198
:type simple-octet-vector)
199
;; This fixed type should be big enough for "64-bit" digest functions.
200
(buffer-index 0 :type (integer 0 128)))
202
(declaim (inline mdx-updater))
203
(defun mdx-updater (state compressor seq start end)
204
(declare (type mdx state))
205
(declare (type function compressor))
206
(declare (type index start end))
207
(let* ((buffer (mdx-buffer state))
208
(buffer-index (mdx-buffer-index state))
209
(buffer-length (length buffer))
210
(length (- end start)))
211
(declare (type fixnum length))
212
(unless (zerop buffer-index)
213
(let ((amount (min (- buffer-length buffer-index)
215
(copy-to-buffer seq start amount buffer buffer-index)
216
(setq start (+ start amount))
217
(let ((new-index (logand (+ buffer-index amount)
218
(1- buffer-length))))
219
(when (zerop new-index)
220
(funcall compressor state buffer 0))
222
(setf (mdx-buffer-index state) new-index)
223
(incf (mdx-amount state) length)
224
(return-from mdx-updater state)))))
225
(loop until (< (- end start) buffer-length)
226
do (funcall compressor state seq start)
227
(setq start (the fixnum (+ start buffer-length)))
229
(let ((amount (- end start)))
230
(unless (zerop amount)
231
(copy-to-buffer seq start amount buffer 0))
232
(setf (mdx-buffer-index state) amount)
233
(incf (mdx-amount state) length)
235
(declaim (notinline mdx-updater))
237
;;; high-level generic function drivers
239
;;; These three functions are intended to be one-shot ways to digest
240
;;; an object of some kind. You could write these in terms of the more
241
;;; familiar digest interface below, but these are likely to be slightly
242
;;; more efficient, as well as more obvious about what you're trying to
244
(declaim (notinline make-digest))
246
(defmethod digest-file ((digest-name cons) pathname &rest kwargs)
247
(apply #'digest-file (apply #'make-digest digest-name) pathname kwargs))
248
(defmethod digest-file ((digest-name symbol) pathname &rest kwargs)
249
(apply #'digest-file (make-digest digest-name) pathname kwargs))
251
(defmethod digest-file (state pathname &key buffer (start 0) end
252
digest (digest-start 0))
253
(with-open-file (stream pathname :element-type '(unsigned-byte 8)
255
:if-does-not-exist :error)
256
(update-digest-from-stream state stream
257
:buffer buffer :start start :end end)
258
(produce-digest state :digest digest :digest-start digest-start)))
260
(defmethod digest-stream ((digest-name cons) stream &rest kwargs)
261
(apply #'digest-stream (apply #'make-digest digest-name) stream kwargs))
262
(defmethod digest-stream ((digest-name symbol) stream &rest kwargs)
263
(apply #'digest-stream (make-digest digest-name) stream kwargs))
265
(defmethod digest-stream (state stream &key buffer (start 0) end
266
digest (digest-start 0))
267
(update-digest-from-stream state stream
268
:buffer buffer :start start :end end)
269
(produce-digest state :digest digest :digest-start digest-start))
271
(defmethod digest-sequence ((digest-name symbol) sequence &rest kwargs)
272
(apply #'digest-sequence (make-digest digest-name) sequence kwargs))
274
(defmethod digest-sequence (state sequence &key (start 0) end
275
digest (digest-start 0))
276
(declare (type index start))
277
(check-type sequence (vector (unsigned-byte 8)))
278
(let ((end (or end (length sequence))))
279
(update-digest-from-vector state sequence start end))
280
(produce-digest state :digest digest :digest-start digest-start))
282
;;; These four functions represent the common interface for digests in
283
;;; other crypto toolkits (OpenSSL, Botan, Python, etc.). You obtain
284
;;; some state object for a particular digest, you update it with some
285
;;; data, and then you get the actual digest. Flexibility is the name
286
;;; of the game with these functions.
287
(defun make-digest (digest-name &rest keys &key &allow-other-keys)
288
"Return a digest object which uses the algorithm DIGEST-NAME."
289
(typecase digest-name
291
(let ((name (massage-symbol digest-name)))
293
(apply (the function (get name '%make-digest)) keys)
294
(error 'unsupported-digest :name digest-name))))
296
(error 'type-error :datum digest-name :expected-type 'symbol))))
298
;;; the digest-defining macro
300
(get sym '%digest-length))
302
(defun list-all-digests ()
303
(loop for symbol being each external-symbol of (find-package :ironclad)
305
collect (intern (symbol-name symbol) :keyword) into digests
306
finally (return (sort digests #'string<))))
308
(defun digest-supported-p (name)
309
"Return T if the digest NAME is a valid digest name."
311
(not (null (digestp (massage-symbol name))))))
313
(defmethod digest-length ((digest-name symbol))
314
(or (digestp (massage-symbol digest-name))
315
(error 'unsupported-digest :name digest-name)))
317
(defmethod digest-length (digest-name)
318
(error 'unsupported-digest :name digest-name))
320
(defmethod update-digest (digester (stream stream) &key buffer (start 0) end
322
(update-digest-from-stream digester stream
323
:buffer buffer :start start :end end))
325
(eval-when (:compile-toplevel :load-toplevel)
326
(defun optimized-maker-name (name)
327
(let ((*package* (find-package :ironclad)))
328
(symbolicate '#:%make- name '#:-digest))))
330
(defmacro defdigest (name &key digest-length block-length)
331
(let ((optimized-maker-name (optimized-maker-name name)))
333
(setf (get ',name '%digest-length) ,digest-length)
334
(setf (get ',name '%make-digest) (symbol-function ',optimized-maker-name))
335
(defmethod digest-length ((digest ,name))
337
(defmethod block-length ((digest ,name))
340
;;; If we pass a constant argument to MAKE-DIGEST, convert the
341
;;; MAKE-DIGEST call to a direct call to the state creation function.
342
(define-compiler-macro make-digest (&whole form &environment env
343
name &rest keys &key &allow-other-keys)
344
(declare (ignore env))
347
(and (quotationp name) (symbolp name)))
348
(let ((name (massage-symbol (unquote name))))
350
`(,(optimized-maker-name name) ,@keys)
354
;;; And do the same for various one-shot digest functions.
355
(defun maybe-expand-one-shot-call (form funname name 2nd-arg keys)
358
(and (quotationp name) (symbolp name)))
359
(let ((name (massage-symbol (unquote name))))
361
`(,funname (,(optimized-maker-name name)) ,2nd-arg ,@keys)
365
(define-compiler-macro digest-sequence (&whole form &environment env
366
name sequence &rest keys)
367
(declare (ignore env))
368
(maybe-expand-one-shot-call form 'digest-sequence name sequence keys))
370
(define-compiler-macro digest-stream (&whole form &environment env
371
name stream &rest keys)
372
(declare (ignore env))
373
(maybe-expand-one-shot-call form 'digest-stream name stream keys))
375
(define-compiler-macro digest-file (&whole form &environment env
376
name file &rest keys)
377
(declare (ignore env))
378
(maybe-expand-one-shot-call form 'digest-file name file keys))