Include 'sys/sysmacros.h' for GCC-9
[platform/upstream/cryptsetup.git] / lib / utils_benchmark.c
1 /*
2  * libcryptsetup - cryptsetup library, cipher bechmark
3  *
4  * Copyright (C) 2012, Red Hat, Inc. All rights reserved.
5  * Copyright (C) 2012-2013, Milan Broz
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <stdlib.h>
23 #include <errno.h>
24 #include <time.h>
25
26 #include "internal.h"
27
28 /*
29  * This is not simulating storage, so using disk block causes extreme overhead.
30  * Let's use some fixed block size where results are more reliable...
31  */
32 #define CIPHER_BLOCK_BYTES 65536
33
34 /*
35  * If the measured value is lower, encrypted buffer is probably too small
36  * and calculated values are not reliable.
37  */
38 #define CIPHER_TIME_MIN_MS 0.001
39
40 /*
41  * The whole test depends on Linux kernel usermode crypto API for now.
42  * (The same implementations are used in dm-crypt though.)
43  */
44
45 struct cipher_perf {
46         char name[32];
47         char mode[32];
48         char *key;
49         size_t key_length;
50         char *iv;
51         size_t iv_length;
52         size_t buffer_size;
53 };
54
55 static int time_ms(struct timespec *start, struct timespec *end, double *ms)
56 {
57         double start_ms, end_ms;
58
59         start_ms = start->tv_sec * 1000.0 + start->tv_nsec / (1000.0 * 1000);
60         end_ms   = end->tv_sec * 1000.0 + end->tv_nsec / (1000.0 * 1000);
61
62         *ms = end_ms - start_ms;
63         return 0;
64 }
65
66 static int cipher_perf_one(struct cipher_perf *cp, char *buf,
67                            size_t buf_size, int enc)
68 {
69         struct crypt_cipher *cipher = NULL;
70         size_t done = 0, block = CIPHER_BLOCK_BYTES;
71         int r;
72
73         if (buf_size < block)
74                 block = buf_size;
75
76         r = crypt_cipher_init(&cipher, cp->name, cp->mode, cp->key, cp->key_length);
77         if (r < 0) {
78                 log_dbg("Cannot initialise cipher %s, mode %s.", cp->name, cp->mode);
79                 return r;
80         }
81
82         while (done < buf_size) {
83                 if ((done + block) > buf_size)
84                         block = buf_size - done;
85
86                 if (enc)
87                         r = crypt_cipher_encrypt(cipher, &buf[done], &buf[done],
88                                                  block, cp->iv, cp->iv_length);
89                 else
90                         r = crypt_cipher_decrypt(cipher, &buf[done], &buf[done],
91                                                  block, cp->iv, cp->iv_length);
92                 if (r < 0)
93                         break;
94
95                 done += block;
96         }
97
98         crypt_cipher_destroy(cipher);
99
100         return r;
101 }
102 static int cipher_measure(struct cipher_perf *cp, char *buf,
103                           size_t buf_size, int encrypt, double *ms)
104 {
105         struct timespec start, end;
106         int r;
107
108         /*
109          * Using getrusage would be better here but the precision
110          * is not adequate, so better stick with CLOCK_MONOTONIC
111          */
112         if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
113                 return -EINVAL;
114
115         r = cipher_perf_one(cp, buf, buf_size, encrypt);
116         if (r < 0)
117                 return r;
118
119         if (clock_gettime(CLOCK_MONOTONIC, &end) < 0)
120                 return -EINVAL;
121
122         r = time_ms(&start, &end, ms);
123         if (r < 0)
124                 return r;
125
126         if (*ms < CIPHER_TIME_MIN_MS) {
127                 log_dbg("Measured cipher runtime (%1.6f) is too low.", *ms);
128                 return -ERANGE;
129         }
130
131         return 0;
132 }
133
134 static double speed_mbs(unsigned long bytes, double ms)
135 {
136         double speed = bytes, s = ms / 1000.;
137
138         return speed / (1024 * 1024) / s;
139 }
140
141 static int cipher_perf(struct cipher_perf *cp,
142         double *encryption_mbs, double *decryption_mbs)
143 {
144         double ms_enc, ms_dec, ms;
145         int r, repeat_enc, repeat_dec;
146         void *buf = NULL;
147
148         if (posix_memalign(&buf, crypt_getpagesize(), cp->buffer_size))
149                 return -ENOMEM;
150
151         ms_enc = 0.0;
152         repeat_enc = 1;
153         while (ms_enc < 1000.0) {
154                 r = cipher_measure(cp, buf, cp->buffer_size, 1, &ms);
155                 if (r < 0) {
156                         free(buf);
157                         return r;
158                 }
159                 ms_enc += ms;
160                 repeat_enc++;
161         }
162
163         ms_dec = 0.0;
164         repeat_dec = 1;
165         while (ms_dec < 1000.0) {
166                 r = cipher_measure(cp, buf, cp->buffer_size, 0, &ms);
167                 if (r < 0) {
168                         free(buf);
169                         return r;
170                 }
171                 ms_dec += ms;
172                 repeat_dec++;
173         }
174
175         free(buf);
176
177         *encryption_mbs = speed_mbs(cp->buffer_size * repeat_enc, ms_enc);
178         *decryption_mbs = speed_mbs(cp->buffer_size * repeat_dec, ms_dec);
179
180         return  0;
181 }
182
183 int crypt_benchmark(struct crypt_device *cd,
184         const char *cipher,
185         const char *cipher_mode,
186         size_t volume_key_size,
187         size_t iv_size,
188         size_t buffer_size,
189         double *encryption_mbs,
190         double *decryption_mbs)
191 {
192         struct cipher_perf cp = {
193                 .key_length = volume_key_size,
194                 .iv_length = iv_size,
195                 .buffer_size = buffer_size,
196         };
197         char *c;
198         int r;
199
200         if (!cipher || !cipher_mode || !volume_key_size)
201                 return -EINVAL;
202
203         r = init_crypto(cd);
204         if (r < 0)
205                 return r;
206
207         r = -ENOMEM;
208         if (iv_size) {
209                 cp.iv = malloc(iv_size);
210                 if (!cp.iv)
211                         goto out;
212                 crypt_random_get(cd, cp.iv, iv_size, CRYPT_RND_NORMAL);
213         }
214
215         cp.key = malloc(volume_key_size);
216         if (!cp.key)
217                 goto out;
218
219         crypt_random_get(cd, cp.key, volume_key_size, CRYPT_RND_NORMAL);
220         strncpy(cp.name, cipher, sizeof(cp.name)-1);
221         strncpy(cp.mode, cipher_mode, sizeof(cp.mode)-1);
222
223         /* Ignore IV generator */
224         if ((c  = strchr(cp.mode, '-')))
225                 *c = '\0';
226
227         r = cipher_perf(&cp, encryption_mbs, decryption_mbs);
228 out:
229         free(cp.key);
230         free(cp.iv);
231         return r;
232 }
233
234 int crypt_benchmark_kdf(struct crypt_device *cd,
235         const char *kdf,
236         const char *hash,
237         const char *password,
238         size_t password_size,
239         const char *salt,
240         size_t salt_size,
241         uint64_t *iterations_sec)
242 {
243         int r;
244
245         if (!iterations_sec)
246                 return -EINVAL;
247
248         r = init_crypto(cd);
249         if (r < 0)
250                 return r;
251
252         if (!strncmp(kdf, "pbkdf2", 6))
253                 r = crypt_pbkdf_check(kdf, hash, password, password_size,
254                                       salt, salt_size, iterations_sec);
255         else
256                 r = -EINVAL;
257
258         if (!r)
259                 log_dbg("KDF %s, hash %s: %" PRIu64 " iterations per second.",
260                         kdf, hash, *iterations_sec);
261         return r;
262 }