1/*
2 * checksum.c:   checksum routines
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24#define APR_WANT_BYTEFUNC
25
26#include <ctype.h>
27
28#include <apr_md5.h>
29#include <apr_sha1.h>
30
31#include "svn_checksum.h"
32#include "svn_error.h"
33#include "svn_ctype.h"
34#include "svn_sorts.h"
35
36#include "checksum.h"
37#include "fnv1a.h"
38
39#include "private/svn_subr_private.h"
40
41#include "svn_private_config.h"
42
43
44
45/* The MD5 digest for the empty string. */
46static const unsigned char md5_empty_string_digest_array[] = {
47  0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
48  0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e
49};
50
51/* The SHA1 digest for the empty string. */
52static const unsigned char sha1_empty_string_digest_array[] = {
53  0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55,
54  0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09
55};
56
57/* The FNV-1a digest for the empty string. */
58static const unsigned char fnv1a_32_empty_string_digest_array[] = {
59  0x81, 0x1c, 0x9d, 0xc5
60};
61
62/* The FNV-1a digest for the empty string. */
63static const unsigned char fnv1a_32x4_empty_string_digest_array[] = {
64  0xcd, 0x6d, 0x9a, 0x85
65};
66
67/* Digests for an empty string, indexed by checksum type */
68static const unsigned char * empty_string_digests[] = {
69  md5_empty_string_digest_array,
70  sha1_empty_string_digest_array,
71  fnv1a_32_empty_string_digest_array,
72  fnv1a_32x4_empty_string_digest_array
73};
74
75/* Digest sizes in bytes, indexed by checksum type */
76static const apr_size_t digest_sizes[] = {
77  APR_MD5_DIGESTSIZE,
78  APR_SHA1_DIGESTSIZE,
79  sizeof(apr_uint32_t),
80  sizeof(apr_uint32_t)
81};
82
83/* Checksum type prefixes used in serialized checksums. */
84static const char *ckind_str[] = {
85  "$md5 $",
86  "$sha1$",
87  "$fnv1$",
88  "$fnvm$",
89};
90
91/* Returns the digest size of it's argument. */
92#define DIGESTSIZE(k) \
93  (((k) < svn_checksum_md5 || (k) > svn_checksum_fnv1a_32x4) ? 0 : digest_sizes[k])
94
95/* Largest supported digest size */
96#define MAX_DIGESTSIZE (MAX(APR_MD5_DIGESTSIZE,APR_SHA1_DIGESTSIZE))
97
98const unsigned char *
99svn__empty_string_digest(svn_checksum_kind_t kind)
100{
101  return empty_string_digests[kind];
102}
103
104const char *
105svn__digest_to_cstring_display(const unsigned char digest[],
106                               apr_size_t digest_size,
107                               apr_pool_t *pool)
108{
109  static const char *hex = "0123456789abcdef";
110  char *str = apr_palloc(pool, (digest_size * 2) + 1);
111  apr_size_t i;
112
113  for (i = 0; i < digest_size; i++)
114    {
115      str[i*2]   = hex[digest[i] >> 4];
116      str[i*2+1] = hex[digest[i] & 0x0f];
117    }
118  str[i*2] = '\0';
119
120  return str;
121}
122
123
124const char *
125svn__digest_to_cstring(const unsigned char digest[],
126                       apr_size_t digest_size,
127                       apr_pool_t *pool)
128{
129  static const unsigned char zeros_digest[MAX_DIGESTSIZE] = { 0 };
130
131  if (memcmp(digest, zeros_digest, digest_size) != 0)
132    return svn__digest_to_cstring_display(digest, digest_size, pool);
133  else
134    return NULL;
135}
136
137
138svn_boolean_t
139svn__digests_match(const unsigned char d1[],
140                   const unsigned char d2[],
141                   apr_size_t digest_size)
142{
143  static const unsigned char zeros[MAX_DIGESTSIZE] = { 0 };
144
145  return ((memcmp(d1, d2, digest_size) == 0)
146          || (memcmp(d2, zeros, digest_size) == 0)
147          || (memcmp(d1, zeros, digest_size) == 0));
148}
149
150/* Check to see if KIND is something we recognize.  If not, return
151 * SVN_ERR_BAD_CHECKSUM_KIND */
152static svn_error_t *
153validate_kind(svn_checksum_kind_t kind)
154{
155  if (kind >= svn_checksum_md5 && kind <= svn_checksum_fnv1a_32x4)
156    return SVN_NO_ERROR;
157  else
158    return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
159}
160
161/* Create a svn_checksum_t with everything but the contents of the
162   digest populated. */
163static svn_checksum_t *
164checksum_create_without_digest(svn_checksum_kind_t kind,
165                               apr_size_t digest_size,
166                               apr_pool_t *pool)
167{
168  /* Use apr_palloc() instead of apr_pcalloc() so that the digest
169   * contents are only set once by the caller. */
170  svn_checksum_t *checksum = apr_palloc(pool, sizeof(*checksum) + digest_size);
171  checksum->digest = (unsigned char *)checksum + sizeof(*checksum);
172  checksum->kind = kind;
173  return checksum;
174}
175
176/* Return a checksum object, allocated in POOL.  The checksum will be of
177 * type KIND and contain the given DIGEST.
178 */
179static svn_checksum_t *
180checksum_create(svn_checksum_kind_t kind,
181                const unsigned char *digest,
182                apr_pool_t *pool)
183{
184  apr_size_t digest_size = DIGESTSIZE(kind);
185  svn_checksum_t *checksum = checksum_create_without_digest(kind, digest_size,
186                                                            pool);
187  memcpy((unsigned char *)checksum->digest, digest, digest_size);
188  return checksum;
189}
190
191svn_checksum_t *
192svn_checksum_create(svn_checksum_kind_t kind,
193                    apr_pool_t *pool)
194{
195  svn_checksum_t *checksum;
196  apr_size_t digest_size;
197
198  switch (kind)
199    {
200      case svn_checksum_md5:
201      case svn_checksum_sha1:
202      case svn_checksum_fnv1a_32:
203      case svn_checksum_fnv1a_32x4:
204        digest_size = digest_sizes[kind];
205        break;
206
207      default:
208        return NULL;
209    }
210
211  checksum = checksum_create_without_digest(kind, digest_size, pool);
212  memset((unsigned char *) checksum->digest, 0, digest_size);
213  return checksum;
214}
215
216svn_checksum_t *
217svn_checksum__from_digest_md5(const unsigned char *digest,
218                              apr_pool_t *result_pool)
219{
220  return checksum_create(svn_checksum_md5, digest, result_pool);
221}
222
223svn_checksum_t *
224svn_checksum__from_digest_sha1(const unsigned char *digest,
225                               apr_pool_t *result_pool)
226{
227  return checksum_create(svn_checksum_sha1, digest, result_pool);
228}
229
230svn_checksum_t *
231svn_checksum__from_digest_fnv1a_32(const unsigned char *digest,
232                                   apr_pool_t *result_pool)
233{
234  return checksum_create(svn_checksum_fnv1a_32, digest, result_pool);
235}
236
237svn_checksum_t *
238svn_checksum__from_digest_fnv1a_32x4(const unsigned char *digest,
239                                     apr_pool_t *result_pool)
240{
241  return checksum_create(svn_checksum_fnv1a_32x4, digest, result_pool);
242}
243
244svn_error_t *
245svn_checksum_clear(svn_checksum_t *checksum)
246{
247  SVN_ERR(validate_kind(checksum->kind));
248
249  memset((unsigned char *) checksum->digest, 0, DIGESTSIZE(checksum->kind));
250  return SVN_NO_ERROR;
251}
252
253svn_boolean_t
254svn_checksum_match(const svn_checksum_t *checksum1,
255                   const svn_checksum_t *checksum2)
256{
257  if (checksum1 == NULL || checksum2 == NULL)
258    return TRUE;
259
260  if (checksum1->kind != checksum2->kind)
261    return FALSE;
262
263  switch (checksum1->kind)
264    {
265      case svn_checksum_md5:
266      case svn_checksum_sha1:
267      case svn_checksum_fnv1a_32:
268      case svn_checksum_fnv1a_32x4:
269        return svn__digests_match(checksum1->digest,
270                                  checksum2->digest,
271                                  digest_sizes[checksum1->kind]);
272
273      default:
274        /* We really shouldn't get here, but if we do... */
275        return FALSE;
276    }
277}
278
279const char *
280svn_checksum_to_cstring_display(const svn_checksum_t *checksum,
281                                apr_pool_t *pool)
282{
283  switch (checksum->kind)
284    {
285      case svn_checksum_md5:
286      case svn_checksum_sha1:
287      case svn_checksum_fnv1a_32:
288      case svn_checksum_fnv1a_32x4:
289        return svn__digest_to_cstring_display(checksum->digest,
290                                              digest_sizes[checksum->kind],
291                                              pool);
292
293      default:
294        /* We really shouldn't get here, but if we do... */
295        return NULL;
296    }
297}
298
299const char *
300svn_checksum_to_cstring(const svn_checksum_t *checksum,
301                        apr_pool_t *pool)
302{
303  if (checksum == NULL)
304    return NULL;
305
306  switch (checksum->kind)
307    {
308      case svn_checksum_md5:
309      case svn_checksum_sha1:
310      case svn_checksum_fnv1a_32:
311      case svn_checksum_fnv1a_32x4:
312        return svn__digest_to_cstring(checksum->digest,
313                                      digest_sizes[checksum->kind],
314                                      pool);
315
316      default:
317        /* We really shouldn't get here, but if we do... */
318        return NULL;
319    }
320}
321
322
323const char *
324svn_checksum_serialize(const svn_checksum_t *checksum,
325                       apr_pool_t *result_pool,
326                       apr_pool_t *scratch_pool)
327{
328  SVN_ERR_ASSERT_NO_RETURN(checksum->kind >= svn_checksum_md5
329                           || checksum->kind <= svn_checksum_fnv1a_32x4);
330  return apr_pstrcat(result_pool,
331                     ckind_str[checksum->kind],
332                     svn_checksum_to_cstring(checksum, scratch_pool),
333                     SVN_VA_NULL);
334}
335
336
337svn_error_t *
338svn_checksum_deserialize(const svn_checksum_t **checksum,
339                         const char *data,
340                         apr_pool_t *result_pool,
341                         apr_pool_t *scratch_pool)
342{
343  svn_checksum_kind_t kind;
344  svn_checksum_t *parsed_checksum;
345
346  /* All prefixes have the same length. */
347  apr_size_t prefix_len = strlen(ckind_str[0]);
348
349  /* "$md5 $...", "$sha1$..." or ... */
350  if (strlen(data) <= prefix_len)
351    return svn_error_createf(SVN_ERR_BAD_CHECKSUM_PARSE, NULL,
352                             _("Invalid prefix in checksum '%s'"),
353                             data);
354
355  for (kind = svn_checksum_md5; kind <= svn_checksum_fnv1a_32x4; ++kind)
356    if (strncmp(ckind_str[kind], data, prefix_len) == 0)
357      {
358        SVN_ERR(svn_checksum_parse_hex(&parsed_checksum, kind,
359                                       data + prefix_len, result_pool));
360        *checksum = parsed_checksum;
361        return SVN_NO_ERROR;
362      }
363
364  return svn_error_createf(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
365                           "Unknown checksum kind in '%s'", data);
366}
367
368
369svn_error_t *
370svn_checksum_parse_hex(svn_checksum_t **checksum,
371                       svn_checksum_kind_t kind,
372                       const char *hex,
373                       apr_pool_t *pool)
374{
375  apr_size_t i, len;
376  char is_nonzero = '\0';
377  char *digest;
378  static const char xdigitval[256] =
379    {
380      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
381      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
382      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
383       0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,   /* 0-9 */
384      -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,   /* A-F */
385      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
386      -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,   /* a-f */
387      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
388      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
389      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
390      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
391      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
392      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
393      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
394      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
395      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
396    };
397
398  if (hex == NULL)
399    {
400      *checksum = NULL;
401      return SVN_NO_ERROR;
402    }
403
404  SVN_ERR(validate_kind(kind));
405
406  *checksum = svn_checksum_create(kind, pool);
407  digest = (char *)(*checksum)->digest;
408  len = DIGESTSIZE(kind);
409
410  for (i = 0; i < len; i++)
411    {
412      char x1 = xdigitval[(unsigned char)hex[i * 2]];
413      char x2 = xdigitval[(unsigned char)hex[i * 2 + 1]];
414      if (x1 == (char)-1 || x2 == (char)-1)
415        return svn_error_create(SVN_ERR_BAD_CHECKSUM_PARSE, NULL, NULL);
416
417      digest[i] = (char)((x1 << 4) | x2);
418      is_nonzero |= (char)((x1 << 4) | x2);
419    }
420
421  if (!is_nonzero)
422    *checksum = NULL;
423
424  return SVN_NO_ERROR;
425}
426
427svn_checksum_t *
428svn_checksum_dup(const svn_checksum_t *checksum,
429                 apr_pool_t *pool)
430{
431  /* The duplicate of a NULL checksum is a NULL... */
432  if (checksum == NULL)
433    return NULL;
434
435  /* Without this check on valid checksum kind a NULL svn_checksum_t
436   * pointer is returned which could cause a core dump at an
437   * indeterminate time in the future because callers are not
438   * expecting a NULL pointer.  This commit forces an early abort() so
439   * it's easier to track down where the issue arose. */
440  switch (checksum->kind)
441    {
442      case svn_checksum_md5:
443      case svn_checksum_sha1:
444      case svn_checksum_fnv1a_32:
445      case svn_checksum_fnv1a_32x4:
446        return checksum_create(checksum->kind, checksum->digest, pool);
447
448      default:
449        SVN_ERR_MALFUNCTION_NO_RETURN();
450        break;
451    }
452}
453
454svn_error_t *
455svn_checksum(svn_checksum_t **checksum,
456             svn_checksum_kind_t kind,
457             const void *data,
458             apr_size_t len,
459             apr_pool_t *pool)
460{
461  apr_sha1_ctx_t sha1_ctx;
462
463  SVN_ERR(validate_kind(kind));
464  *checksum = svn_checksum_create(kind, pool);
465
466  switch (kind)
467    {
468      case svn_checksum_md5:
469        apr_md5((unsigned char *)(*checksum)->digest, data, len);
470        break;
471
472      case svn_checksum_sha1:
473        apr_sha1_init(&sha1_ctx);
474        apr_sha1_update(&sha1_ctx, data, (unsigned int)len);
475        apr_sha1_final((unsigned char *)(*checksum)->digest, &sha1_ctx);
476        break;
477
478      case svn_checksum_fnv1a_32:
479        *(apr_uint32_t *)(*checksum)->digest
480          = htonl(svn__fnv1a_32(data, len));
481        break;
482
483      case svn_checksum_fnv1a_32x4:
484        *(apr_uint32_t *)(*checksum)->digest
485          = htonl(svn__fnv1a_32x4(data, len));
486        break;
487
488      default:
489        /* We really shouldn't get here, but if we do... */
490        return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
491    }
492
493  return SVN_NO_ERROR;
494}
495
496
497svn_checksum_t *
498svn_checksum_empty_checksum(svn_checksum_kind_t kind,
499                            apr_pool_t *pool)
500{
501  switch (kind)
502    {
503      case svn_checksum_md5:
504      case svn_checksum_sha1:
505      case svn_checksum_fnv1a_32:
506      case svn_checksum_fnv1a_32x4:
507        return checksum_create(kind, empty_string_digests[kind], pool);
508
509      default:
510        /* We really shouldn't get here, but if we do... */
511        SVN_ERR_MALFUNCTION_NO_RETURN();
512    }
513}
514
515struct svn_checksum_ctx_t
516{
517  void *apr_ctx;
518  svn_checksum_kind_t kind;
519};
520
521svn_checksum_ctx_t *
522svn_checksum_ctx_create(svn_checksum_kind_t kind,
523                        apr_pool_t *pool)
524{
525  svn_checksum_ctx_t *ctx = apr_palloc(pool, sizeof(*ctx));
526
527  ctx->kind = kind;
528  switch (kind)
529    {
530      case svn_checksum_md5:
531        ctx->apr_ctx = apr_palloc(pool, sizeof(apr_md5_ctx_t));
532        apr_md5_init(ctx->apr_ctx);
533        break;
534
535      case svn_checksum_sha1:
536        ctx->apr_ctx = apr_palloc(pool, sizeof(apr_sha1_ctx_t));
537        apr_sha1_init(ctx->apr_ctx);
538        break;
539
540      case svn_checksum_fnv1a_32:
541        ctx->apr_ctx = svn_fnv1a_32__context_create(pool);
542        break;
543
544      case svn_checksum_fnv1a_32x4:
545        ctx->apr_ctx = svn_fnv1a_32x4__context_create(pool);
546        break;
547
548      default:
549        SVN_ERR_MALFUNCTION_NO_RETURN();
550    }
551
552  return ctx;
553}
554
555svn_error_t *
556svn_checksum_update(svn_checksum_ctx_t *ctx,
557                    const void *data,
558                    apr_size_t len)
559{
560  switch (ctx->kind)
561    {
562      case svn_checksum_md5:
563        apr_md5_update(ctx->apr_ctx, data, len);
564        break;
565
566      case svn_checksum_sha1:
567        apr_sha1_update(ctx->apr_ctx, data, (unsigned int)len);
568        break;
569
570      case svn_checksum_fnv1a_32:
571        svn_fnv1a_32__update(ctx->apr_ctx, data, len);
572        break;
573
574      case svn_checksum_fnv1a_32x4:
575        svn_fnv1a_32x4__update(ctx->apr_ctx, data, len);
576        break;
577
578      default:
579        /* We really shouldn't get here, but if we do... */
580        return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
581    }
582
583  return SVN_NO_ERROR;
584}
585
586svn_error_t *
587svn_checksum_final(svn_checksum_t **checksum,
588                   const svn_checksum_ctx_t *ctx,
589                   apr_pool_t *pool)
590{
591  *checksum = svn_checksum_create(ctx->kind, pool);
592
593  switch (ctx->kind)
594    {
595      case svn_checksum_md5:
596        apr_md5_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx);
597        break;
598
599      case svn_checksum_sha1:
600        apr_sha1_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx);
601        break;
602
603      case svn_checksum_fnv1a_32:
604        *(apr_uint32_t *)(*checksum)->digest
605          = htonl(svn_fnv1a_32__finalize(ctx->apr_ctx));
606        break;
607
608      case svn_checksum_fnv1a_32x4:
609        *(apr_uint32_t *)(*checksum)->digest
610          = htonl(svn_fnv1a_32x4__finalize(ctx->apr_ctx));
611        break;
612
613      default:
614        /* We really shouldn't get here, but if we do... */
615        return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
616    }
617
618  return SVN_NO_ERROR;
619}
620
621apr_size_t
622svn_checksum_size(const svn_checksum_t *checksum)
623{
624  return DIGESTSIZE(checksum->kind);
625}
626
627svn_error_t *
628svn_checksum_mismatch_err(const svn_checksum_t *expected,
629                          const svn_checksum_t *actual,
630                          apr_pool_t *scratch_pool,
631                          const char *fmt,
632                          ...)
633{
634  va_list ap;
635  const char *desc;
636
637  va_start(ap, fmt);
638  desc = apr_pvsprintf(scratch_pool, fmt, ap);
639  va_end(ap);
640
641  return svn_error_createf(SVN_ERR_CHECKSUM_MISMATCH, NULL,
642                           _("%s:\n"
643                             "   expected:  %s\n"
644                             "     actual:  %s\n"),
645                desc,
646                svn_checksum_to_cstring_display(expected, scratch_pool),
647                svn_checksum_to_cstring_display(actual, scratch_pool));
648}
649
650svn_boolean_t
651svn_checksum_is_empty_checksum(svn_checksum_t *checksum)
652{
653  /* By definition, the NULL checksum matches all others, including the
654     empty one. */
655  if (!checksum)
656    return TRUE;
657
658  switch (checksum->kind)
659    {
660      case svn_checksum_md5:
661      case svn_checksum_sha1:
662      case svn_checksum_fnv1a_32:
663      case svn_checksum_fnv1a_32x4:
664        return svn__digests_match(checksum->digest,
665                                  svn__empty_string_digest(checksum->kind),
666                                  digest_sizes[checksum->kind]);
667
668      default:
669        /* We really shouldn't get here, but if we do... */
670        SVN_ERR_MALFUNCTION_NO_RETURN();
671    }
672}
673
674/* Checksum calculating stream wrappers.
675 */
676
677/* Baton used by write_handler and close_handler to calculate the checksum
678 * and return the result to the stream creator.  It accommodates the data
679 * needed by svn_checksum__wrap_write_stream_fnv1a_32x4 as well as
680 * svn_checksum__wrap_write_stream.
681 */
682typedef struct stream_baton_t
683{
684  /* Stream we are wrapping. Forward write() and close() operations to it. */
685  svn_stream_t *inner_stream;
686
687  /* Build the checksum data in here. */
688  svn_checksum_ctx_t *context;
689
690  /* Write the final checksum here. May be NULL. */
691  svn_checksum_t **checksum;
692
693  /* Copy the digest of the final checksum. May be NULL. */
694  unsigned char *digest;
695
696  /* Allocate the resulting checksum here. */
697  apr_pool_t *pool;
698} stream_baton_t;
699
700/* Implement svn_write_fn_t.
701 * Update checksum and pass data on to inner stream.
702 */
703static svn_error_t *
704write_handler(void *baton,
705              const char *data,
706              apr_size_t *len)
707{
708  stream_baton_t *b = baton;
709
710  SVN_ERR(svn_checksum_update(b->context, data, *len));
711  SVN_ERR(svn_stream_write(b->inner_stream, data, len));
712
713  return SVN_NO_ERROR;
714}
715
716/* Implement svn_close_fn_t.
717 * Finalize checksum calculation and write results. Close inner stream.
718 */
719static svn_error_t *
720close_handler(void *baton)
721{
722  stream_baton_t *b = baton;
723  svn_checksum_t *local_checksum;
724
725  /* Ensure we can always write to *B->CHECKSUM. */
726  if (!b->checksum)
727    b->checksum = &local_checksum;
728
729  /* Get the final checksum. */
730  SVN_ERR(svn_checksum_final(b->checksum, b->context, b->pool));
731
732  /* Extract digest, if wanted. */
733  if (b->digest)
734    {
735      apr_size_t digest_size = DIGESTSIZE((*b->checksum)->kind);
736      memcpy(b->digest, (*b->checksum)->digest, digest_size);
737    }
738
739  /* Done here.  Now, close the underlying stream as well. */
740  return svn_error_trace(svn_stream_close(b->inner_stream));
741}
742
743/* Common constructor function for svn_checksum__wrap_write_stream and
744 * svn_checksum__wrap_write_stream_fnv1a_32x4, taking the superset of their
745 * respecting parameters.
746 *
747 * In the current usage, either CHECKSUM or DIGEST will be NULL but this
748 * function does not enforce any such restriction.  Also, the caller must
749 * make sure that DIGEST refers to a buffer of sufficient length.
750 */
751static svn_stream_t *
752wrap_write_stream(svn_checksum_t **checksum,
753                  unsigned char *digest,
754                  svn_stream_t *inner_stream,
755                  svn_checksum_kind_t kind,
756                  apr_pool_t *pool)
757{
758  svn_stream_t *outer_stream;
759
760  stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
761  baton->inner_stream = inner_stream;
762  baton->context = svn_checksum_ctx_create(kind, pool);
763  baton->checksum = checksum;
764  baton->digest = digest;
765  baton->pool = pool;
766
767  outer_stream = svn_stream_create(baton, pool);
768  svn_stream_set_write(outer_stream, write_handler);
769  svn_stream_set_close(outer_stream, close_handler);
770
771  return outer_stream;
772}
773
774svn_stream_t *
775svn_checksum__wrap_write_stream(svn_checksum_t **checksum,
776                                svn_stream_t *inner_stream,
777                                svn_checksum_kind_t kind,
778                                apr_pool_t *pool)
779{
780  return wrap_write_stream(checksum, NULL, inner_stream, kind, pool);
781}
782
783/* Implement svn_close_fn_t.
784 * For FNV-1a-like checksums, we want the checksum as 32 bit integer instead
785 * of a big endian 4 byte sequence.  This simply wraps close_handler adding
786 * the digest conversion.
787 */
788static svn_error_t *
789close_handler_fnv1a_32x4(void *baton)
790{
791  stream_baton_t *b = baton;
792  SVN_ERR(close_handler(baton));
793
794  *(apr_uint32_t *)b->digest = ntohl(*(apr_uint32_t *)b->digest);
795  return SVN_NO_ERROR;
796}
797
798svn_stream_t *
799svn_checksum__wrap_write_stream_fnv1a_32x4(apr_uint32_t *digest,
800                                           svn_stream_t *inner_stream,
801                                           apr_pool_t *pool)
802{
803  svn_stream_t *result
804    = wrap_write_stream(NULL, (unsigned char *)digest, inner_stream,
805                        svn_checksum_fnv1a_32x4, pool);
806  svn_stream_set_close(result, close_handler_fnv1a_32x4);
807
808  return result;
809}
810