Whamcloud - gitweb
LU-1201 checksum: add libcfs crypto hash
[fs/lustre-release.git] / libcfs / libcfs / user-crypto.c
1 /* GPL HEADER START
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 only,
7  * as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License version 2 for more details (a copy is included
13  * in the LICENSE file that accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License
16  * version 2 along with this program; If not, see http://www.gnu.org/licenses
17  *
18  * Please  visit http://www.xyratex.com/contact if you need additional
19  * information or have any questions.
20  *
21  * GPL HEADER END
22  */
23
24 /*
25  * Copyright 2012 Xyratex Technology Limited
26  */
27
28 /*
29  * Libcfs crypto hash interfaces for user mode.
30  */
31
32 #include <libcfs/libcfs.h>
33 #include <libcfs/posix/posix-crypto.h>
34
35 static int cfs_crypto_hash_speeds[CFS_HASH_ALG_MAX];
36
37 struct __hash_alg {
38         /**
39          * Initialization of algorithm
40          */
41         int (*init)(void);
42         /**
43          * Start function for the hash instance
44          */
45         int (*start)(void *ctx, unsigned char *p, unsigned int len);
46         /**
47          * Partial update checksum
48          */
49         int (*update)(void *ctx, const unsigned char *p, unsigned int len);
50         /**
51          * Final function for the instance destroy context and copy digest
52          */
53         int (*final)(void *ctx, unsigned char *p, unsigned int len);
54         /**
55          * Destroy algorithm
56          */
57         void (*fini)(void);
58         unsigned int    ha_ctx_size;    /**< size of context */
59         unsigned int    ha_priority;    /**< implementation priority
60                                              defined by developer
61                                              to get one from equal algorithm */
62         unsigned char   ha_id;    /**< algorithm identifier */
63 };
64
65 struct hash_desc {
66         const struct __hash_alg *hd_hash;
67         unsigned char   hd_ctx[0];
68 };
69
70 static int crc32_update_wrapper(void *ctx, const unsigned char *p,
71                                 unsigned int len)
72 {
73         unsigned int crc = *(unsigned int *)ctx;
74
75         crc = crc32_le(crc, p, len);
76
77         *(unsigned int *)ctx = crc;
78         return 0;
79 }
80
81 static int adler_wrapper(void *ctx, const unsigned char *p,
82                                 unsigned int len)
83 {
84         unsigned int cksum = *(unsigned int *)ctx;
85
86         cksum = zlib_adler32(cksum, p, len);
87
88         *(unsigned int *)ctx = cksum;
89         return 0;
90 }
91
92 static int start_generic(void *ctx, unsigned char *key,
93                                          unsigned int key_len)
94 {
95         const struct cfs_crypto_hash_type       *type;
96         struct hash_desc        *hd = container_of(ctx, struct hash_desc,
97                                                    hd_ctx);
98         type = cfs_crypto_hash_type(hd->hd_hash->ha_id);
99         LASSERT(type != NULL);
100
101         /* copy key to context */
102         if (key && key_len == hd->hd_hash->ha_ctx_size) {
103                 memcpy(ctx, key, key_len);
104         } else if (type->cht_key != 0) {
105                 memcpy(ctx, &type->cht_key, type->cht_size);
106         } else {
107                 CWARN("Invalid key or key_len, zero context\n");
108                 memset(ctx, 0, hd->hd_hash->ha_ctx_size);
109         }
110         return 0;
111 }
112
113 static int final_generic(void *ctx, unsigned char *hash,
114                                          unsigned int hash_len)
115 {
116         const struct cfs_crypto_hash_type       *type;
117         struct hash_desc        *hd = container_of(ctx, struct hash_desc,
118                                                    hd_ctx);
119         type = cfs_crypto_hash_type(hd->hd_hash->ha_id);
120         LASSERT(type != NULL);
121          /* copy context to out hash */
122         LASSERT(hd->hd_hash->ha_ctx_size == type->cht_size);
123         memcpy(hash, ctx, hd->hd_hash->ha_ctx_size);
124
125
126         return 0;
127 }
128
129 static struct __hash_alg crypto_hash[] = {
130                                           {.ha_id = CFS_HASH_ALG_CRC32,
131                                            .ha_ctx_size = sizeof(unsigned int),
132                                            .ha_priority = 10,
133                                            .init = crc32init_le,
134                                            .update = crc32_update_wrapper,
135                                            .start = start_generic,
136                                            .final = final_generic,
137                                            .fini = NULL},
138                                           {.ha_id = CFS_HASH_ALG_ADLER32,
139                                            .ha_ctx_size = sizeof(unsigned int),
140                                            .ha_priority = 10,
141                                            .init = NULL,
142                                            .update = adler_wrapper,
143                                            .start = start_generic,
144                                            .final = final_generic,
145                                            .fini = NULL} };
146
147 /**
148  * Go through hashes to find the hash with  max priority
149  * for the alg_id algorithm. This is done for different  implementation
150  * of the same algorithm. Priotity is staticaly defined by developer, and
151  * can be zeroed if initialization of algo is unsuccessfull.
152  */
153 static const struct __hash_alg *cfs_crypto_hash_best_alg(unsigned char alg_id)
154 {
155         int max_priority = 0;
156         const struct __hash_alg *alg = NULL;
157         int i;
158
159         for (i = 0; i < ARRAY_SIZE(crypto_hash); i++) {
160                 if (alg_id == crypto_hash[i].ha_id &&
161                     max_priority < crypto_hash[i].ha_priority) {
162                         max_priority = crypto_hash[i].ha_priority;
163                         alg = &crypto_hash[i];
164                 }
165         }
166
167         return alg;
168 }
169
170 struct cfs_crypto_hash_desc
171         *cfs_crypto_hash_init(unsigned char alg,
172                               unsigned char *key, unsigned int key_len)
173 {
174         struct hash_desc                        *hdesc = NULL;
175         const struct cfs_crypto_hash_type       *type;
176         const struct __hash_alg                 *ha = NULL;
177         int                                     err;
178
179         type = cfs_crypto_hash_type(alg);
180         if (type == NULL) {
181                 CWARN("Unsupported hash algorithm id = %d, max id is %d\n",
182                       alg, CFS_HASH_ALG_MAX);
183                 return ERR_PTR(-EINVAL);
184         }
185
186         ha = cfs_crypto_hash_best_alg(alg);
187         if (ha == NULL) {
188                 CERROR("Failed to get hash algorithm\n");
189                 return ERR_PTR(-ENODEV);
190         }
191
192         hdesc = cfs_alloc(sizeof(*hdesc) + ha->ha_ctx_size, 0);
193         if (hdesc == NULL)
194                 return ERR_PTR(-ENOMEM);
195
196         hdesc->hd_hash = ha;
197
198         if (ha->start != NULL) {
199                 err = ha->start(hdesc->hd_ctx, key, key_len);
200                 if (err == 0) {
201                         return (struct cfs_crypto_hash_desc *) hdesc;
202                 } else {
203                         cfs_free(hdesc);
204                         return ERR_PTR(err);
205                 }
206         }
207
208         return (struct cfs_crypto_hash_desc *) hdesc;
209 }
210
211 int cfs_crypto_hash_update(struct cfs_crypto_hash_desc *desc, const void *buf,
212                            unsigned int buf_len)
213 {
214         struct hash_desc *d = (struct hash_desc *)desc;
215         return d->hd_hash->update(d->hd_ctx, buf, buf_len);
216 }
217
218 int cfs_crypto_hash_update_page(struct cfs_crypto_hash_desc *desc,
219                                 cfs_page_t *page, unsigned int offset,
220                                 unsigned int len)
221 {
222         const void *p = page->addr + offset;
223
224         return cfs_crypto_hash_update(desc, p, len);
225 }
226
227 /**
228  *      To get final hash and destroy cfs_crypto_hash_desc, caller
229  *      should use valid hash buffer with enougth len for hash.
230  *      If hash_len pointer is NULL - destroy descriptor.
231  */
232 int cfs_crypto_hash_final(struct cfs_crypto_hash_desc *desc,
233                           unsigned char *hash, unsigned int *hash_len)
234 {
235         struct hash_desc        *d = (struct hash_desc *)desc;
236         int     size = (cfs_crypto_hash_type(d->hd_hash->ha_id))->cht_size;
237         int     err;
238
239         if (hash_len == NULL) {
240                 cfs_free(d);
241                 return 0;
242         }
243         if (hash == NULL || *hash_len < size) {
244                 *hash_len = d->hd_hash->ha_ctx_size;
245                 return -ENOMEM;
246         }
247
248         LASSERT(d->hd_hash->final != NULL);
249         err = d->hd_hash->final(d->hd_ctx, hash, *hash_len);
250         if (err == 0) {
251                   /* If get final digest success free hash descriptor */
252                   cfs_free(d);
253         }
254
255         return err;
256 }
257
258 int cfs_crypto_hash_digest(unsigned char alg,
259                            const void *buf, unsigned int buf_len,
260                            unsigned char *key, unsigned int key_len,
261                            unsigned char *hash, unsigned int *hash_len)
262 {
263         struct cfs_crypto_hash_desc      *desc;
264         int                          err;
265
266         desc = cfs_crypto_hash_init(alg, key, key_len);
267
268         if (IS_ERR(desc))
269                 return PTR_ERR(desc);
270
271         err = cfs_crypto_hash_update(desc, buf, buf_len);
272         if (err) {
273                 cfs_crypto_hash_final(desc, NULL, NULL);
274                 return err;
275         }
276         err = cfs_crypto_hash_final(desc, hash, hash_len);
277         if (err != 0)
278                 cfs_crypto_hash_final(desc, NULL, NULL);
279         return err;
280 }
281
282
283 static void cfs_crypto_start_timer(struct timeval *start)
284 {
285         gettimeofday(start, NULL);
286         return;
287 }
288
289 /** return usec */
290 static long cfs_crypto_get_sec(struct timeval *start)
291 {
292         struct timeval  end;
293
294         gettimeofday(&end, NULL);
295
296         return cfs_timeval_sub(&end, start, NULL);
297 }
298
299 static void cfs_crypto_performance_test(unsigned char alg_id,
300                                         const unsigned char *buf,
301                                         unsigned int buf_len)
302 {
303         struct timeval            start;
304         int                          bcount, err, msec;
305         int                          iteration = 400; /* do test 400 times */
306         unsigned char              hash[64];
307         unsigned int                hash_len = 64;
308
309         cfs_crypto_start_timer(&start);
310         for (bcount = 0; bcount < iteration; bcount++) {
311                 err = cfs_crypto_hash_digest(alg_id, buf, buf_len, NULL, 0,
312                                              hash, &hash_len);
313                 if (err)
314                         break;
315
316         }
317
318         msec = (int)(cfs_crypto_get_sec(&start) / 1000.0);
319         if (err) {
320                 cfs_crypto_hash_speeds[alg_id] =  -1;
321                 CDEBUG(D_INFO, "Crypto hash algorithm err = %d\n", err);
322         } else {
323                 long tmp;
324                 tmp =  ((bcount * buf_len / msec) * 1000) / (1024 * 1024);
325                 cfs_crypto_hash_speeds[alg_id] = (int)tmp;
326         }
327         CDEBUG(D_INFO, "Crypto hash algorithm %s speed = %d MB/s\n",
328                cfs_crypto_hash_name(alg_id), cfs_crypto_hash_speeds[alg_id]);
329 }
330
331 int cfs_crypto_hash_speed(unsigned char hash_alg)
332 {
333         if (hash_alg < CFS_HASH_ALG_MAX)
334                 return cfs_crypto_hash_speeds[hash_alg];
335         else
336                 return -1;
337 }
338
339 /**
340  * Do performance test for all hash algorithms.
341  */
342 static int cfs_crypto_test_hashes(void)
343 {
344         unsigned char      i;
345         unsigned char      *data;
346         unsigned int        j, data_len = 1024 * 1024;
347
348         data = cfs_alloc(data_len, 0);
349         if (data == NULL) {
350                 CERROR("Failed to allocate mem\n");
351                 return -ENOMEM;
352         }
353         for (j = 0; j < data_len; j++)
354                 data[j] = j & 0xff;
355
356         for (i = 0; i < CFS_HASH_ALG_MAX; i++)
357                 cfs_crypto_performance_test(i, data, data_len);
358
359         cfs_free(data);
360         return 0;
361 }
362
363 /**
364  *      Register crypto hash algorithms
365  */
366 int cfs_crypto_register(void)
367 {
368         int i, err;
369         for (i = 0; i < ARRAY_SIZE(crypto_hash); i++) {
370                 if (crypto_hash[i].init == NULL)
371                         continue;
372                 err = crypto_hash[i].init();
373                 if (err < 0) {
374                         crypto_hash[i].ha_priority = 0;
375                         CWARN("Failed to initialize hash %s, error %d\n",
376                               cfs_crypto_hash_name(crypto_hash[i].ha_id), err);
377                 }
378         }
379
380         cfs_crypto_test_hashes();
381         return 0;
382 }
383
384 /**
385  *      Unregister
386  */
387 void cfs_crypto_unregister(void)
388 {
389         int i;
390         for (i = 0; i < ARRAY_SIZE(crypto_hash); i++) {
391                 if (crypto_hash[i].fini == NULL)
392                         continue;
393                 if (crypto_hash[i].ha_priority > 0)
394                         crypto_hash[i].fini();
395         }
396 }