Added cipher acceleration to SILC Accelerator. Added cipher softacc.
[crypto.git] / lib / silcacc / softacc_cipher.c
1 /*
2
3   softacc_cipher.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2008 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 /* #define SILC_SOFTACC_DEBUG_ON 1 */
21
22 #include "silccrypto.h"
23 #include "softacc.h"
24 #include "softacc_i.h"
25 #include "aes_internal.h"
26
27 /* Version 1.0 */
28
29 /* Cipher accelerator accelerates ciphers using counter mode by precomputing
30    the CTR key stream in threads.  Encryption and decryption uses the
31    precomputed key stream and gets significant speed improvement in the
32    process.  The threads are reserved from the thread pool and they remain
33    reserved as long as the cipher is accelerated.
34
35    As a queue we use SilcThreadQueue from SRT which handles locking and
36    waiting automatically and supports multiple pipes for multiple key
37    streams, so it makes this whole thing very simple.
38
39    This can accelerate any cipher but AES is especially optimized.
40
41    Problems:
42
43    To get the absolutely maximum performance out one must assign lots of
44    RAM to softacc.
45
46 */
47
48 /*
49   Benchmarks (version 1.0):
50
51   4-core: 2 x dual-core Xeon 5160 3GHz (Woodcrest), 4 GB RAM
52   -----------------------------------------------------------------------
53   cipher_threads = 4, cipher_blocks = 65536, cipher_streams = 32:
54   aes-128-ctr:     728042.34 KB   710.98 MB   5687.83 Mbit / sec
55   aes-192-ctr:     634662.85 KB   619.79 MB   4958.30 Mbit / sec
56   aes-256-ctr:     555215.22 KB   542.20 MB   4337.62 Mbit / sec
57
58   default settings, cipher_threads = 4:
59   aes-128-ctr:     625568.94 KB   610.91 MB   4887.26 Mbit / sec
60   aes-192-ctr:     572719.08 KB   559.30 MB   4474.37 Mbit / sec
61   aes-256-ctr:     506930.88 KB   495.05 MB   3960.40 Mbit / sec
62
63   8-core: 2 x quad-core Xeon E5345 2.33GHz (Clovertown), 4 GB RAM
64   -----------------------------------------------------------------------
65   cipher_threads = 8, cipher_blocks = 65536, cipher_streams = 64:
66   aes-128-ctr:    1162373.93 KB  1135.13 MB   9081.05 Mbit / sec
67   aes-192-ctr:     994808.64 KB   971.49 MB   7771.94 Mbit / sec
68   aes-256-ctr:     874370.93 KB   853.88 MB   6831.02 Mbit / sec
69
70   default settings, cipher_threads = 8:
71   aes-128-ctr:     805157.74 KB   786.29 MB   6290.29 Mbit / sec
72   aes-192-ctr:     733164.28 KB   715.98 MB   5727.85 Mbit / sec
73   aes-256-ctr:     664677.98 KB   649.10 MB   5192.80 Mbit / sec
74
75   Test setup:
76   - Linux 2.6.20 x86-64
77   - GCC 4.1.2
78   - Yasm 0.5.0.1591
79   - nice -n -20 lib/silcacc/tests/test_softacc_cipher
80
81 */
82
83 /************************** Types and definitions ***************************/
84
85 /* Software accelerator cipher operations */
86 const SilcCipherObject softacc_cipher[] =
87 {
88   /* AES */
89   {
90     "aes", "aes",
91     silc_softacc_cipher_aes_set_key,
92     silc_softacc_cipher_aes_set_iv,
93     silc_softacc_cipher_aes_encrypt,
94     silc_softacc_cipher_aes_encrypt,
95     silc_softacc_cipher_init,
96     silc_softacc_cipher_uninit,
97     0, 0, 0,
98     SILC_CIPHER_MODE_CTR,       /* Only CTR mode can be accelerated */
99   },
100
101   /* All other ciphers */
102   {
103     "any", "any",
104     silc_softacc_cipher_set_key,
105     silc_softacc_cipher_set_iv,
106     silc_softacc_cipher_encrypt,
107     silc_softacc_cipher_encrypt,
108     silc_softacc_cipher_init,
109     silc_softacc_cipher_uninit,
110     0, 0, 0,
111     SILC_CIPHER_MODE_CTR,       /* Only CTR mode can be accelerated */
112   },
113
114   {
115     NULL, NULL, NULL, NULL, NULL,
116     NULL, NULL, 0, 0, 0, 0,
117   }
118 };
119
120 /* Block size */
121 #define SILC_KEYSTREAM_BLOCK SILC_CIPHER_MAX_IV_SIZE
122
123 /* Thread stop signal */
124 #define SILC_KEYSTREAM_STOP (void *)0x01
125
126 /* Key stream context */
127 typedef struct {
128   SilcUInt32 key_index;                       /* Key index in queue */
129   unsigned char ctr[SILC_CIPHER_MAX_IV_SIZE]; /* Counter */
130   unsigned char key[0];                       /* Key stream begins here */
131 } *SilcSoftaccCipherKeyStream;
132
133 /* Accelerator cipher context */
134 typedef struct SilcSoftaccCipherStruct {
135   union {
136     AesContext aes;                           /* AES */
137     SilcCipher ecb;                           /* Other ciphers in ECB mode */
138   } c;
139
140   SilcThreadQueue queue;                      /* Key stream queue */
141   unsigned char iv[SILC_CIPHER_MAX_IV_SIZE];  /* Current counter */
142   SilcSoftaccCipherKeyStream *key_stream;     /* Key streams */
143   SilcSoftaccCipherKeyStream cur;             /* Current key stream */
144   SilcUInt32 cur_block;                       /* Current block in key stream */
145   SilcUInt32 cur_index;                       /* Current key stream index */
146   SilcUInt32 pad;                             /* Partial block offset */
147   SilcUInt32 num_key_stream;                  /* Number of key streams */
148   SilcUInt32 cipher_blocks;                   /* Number of cipher blocks */
149   unsigned int cipher_threads : 31;           /* Number of cipher threads */
150   unsigned int key_set : 1;                   /* Set when key is set */
151 } *SilcSoftaccCipher;
152
153 /************************** Static utility functions ************************/
154
155 /* Add value to MSB ordered counter. */
156
157 static inline
158 void silc_softacc_add_ctr(unsigned char *ctr, SilcUInt32 block_len,
159                           SilcUInt32 val)
160 {
161   SilcUInt16 q = 0;
162   int i;
163
164   if (!val)
165     return;
166
167   for (i = block_len - 1; i >= 0; i--) {
168     q += ctr[i] + (val & 0xff);
169     ctr[i] = (q & 0xff);
170     val >>= 8;
171     q >>= 8;
172     if (!val && !q)
173       return;
174   }
175 }
176
177 /*********************************** AES ************************************/
178
179 #define SILC_AES_BLOCK 16
180
181 /* Thread destructor */
182
183 static SILC_TASK_CALLBACK(silc_softacc_cipher_aes_completion)
184 {
185   SilcSoftaccCipher c = context;
186   int i;
187
188   /* Disconnect from key stream queue */
189   if (silc_thread_queue_disconnect(c->queue))
190     return;
191
192   for (i = 0; i < c->num_key_stream; i++)
193     silc_free(c->key_stream[i]);
194   silc_free(c->key_stream);
195   memset(c, 0, sizeof(*c));
196   silc_free(c);
197 }
198
199 /* Key stream computation thread */
200
201 void silc_softacc_cipher_aes_thread(SilcSchedule schedule, void *context)
202 {
203   SilcSoftaccCipher c = context;
204   SilcThreadQueue queue = c->queue;
205   SilcSoftaccCipherKeyStream key;
206   SilcUInt32 i, num_key_stream = c->num_key_stream;
207   SilcUInt32 cipher_blocks = c->cipher_blocks;
208   SilcInt32 k;
209   unsigned char *enc_ctr;
210
211   SILC_SOFTACC_DEBUG(("Start CTR precomputation thread"));
212
213   /* Connect to the key stream queue */
214   silc_thread_queue_connect(queue);
215
216   /* Process key streams.  We wait for empty key streams to come from the
217      last pipe in the queue.  Here we precompute the key stream and put them
218      back to the queue. */
219   while (1) {
220     key = silc_thread_queue_pop(queue, num_key_stream, TRUE);
221     if (key == SILC_KEYSTREAM_STOP)
222       break;
223
224     SILC_SOFTACC_DEBUG(("Precompute key stream %p, index %d", key,
225                         key->key_index));
226
227     /* Encrypt */
228     enc_ctr = key->key;
229     for (i = 0; i < cipher_blocks; i++) {
230       for (k = SILC_AES_BLOCK - 1; k >= 0; k--)
231         if (++key->ctr[k])
232           break;
233       aes_encrypt(key->ctr, enc_ctr, &c->c.aes.u.enc);
234       enc_ctr += SILC_AES_BLOCK;
235     }
236
237     SILC_SOFTACC_DEBUG(("Precomputed key stream %p, index %d", key,
238                         key->key_index));
239
240     /* Update counter */
241     silc_softacc_add_ctr(key->ctr, SILC_AES_BLOCK,
242                          (num_key_stream - 1) * cipher_blocks);
243
244     /* Put it back to queue */
245     silc_thread_queue_push(queue, key->key_index, key, FALSE);
246   }
247
248   SILC_SOFTACC_DEBUG(("End CTR precomputation thread"));
249 }
250
251 /* Set IV.  Also, reset current block, discarding any remaining unused bits in
252    the current key block. */
253
254 SILC_CIPHER_API_SET_IV(softacc_cipher_aes)
255 {
256   SilcSoftaccCipher c = context;
257   SilcSoftaccCipherKeyStream key;
258   SilcUInt32 i;
259
260   /* If IV is NULL we start new block */
261   if (!iv) {
262     SILC_SOFTACC_DEBUG(("Start new block"));
263
264     if (c->pad < SILC_AES_BLOCK) {
265       c->pad = SILC_AES_BLOCK;
266
267       /* Start new block */
268       if (++c->cur_block == c->cipher_blocks) {
269         SILC_SOFTACC_DEBUG(("Push empty key stream %p index %d back to queue",
270                             c->cur, c->cur->key_index));
271         silc_thread_queue_push(c->queue, c->num_key_stream, c->cur, FALSE);
272         c->cur_index = (c->cur_index + 1) % c->cipher_blocks;
273         c->cur_block = 0;
274         c->cur = NULL;
275       }
276     }
277   } else {
278     /* Start new IV */
279     SILC_SOFTACC_DEBUG(("Start new counter"));
280
281     memcpy(c->iv, iv, SILC_AES_BLOCK);
282
283     if (!c->key_set)
284       return;
285
286     /* Push current key stream back to queue.  We need all of them there
287        below. */
288     if (c->cur)
289       silc_thread_queue_push(c->queue, c->cur->key_index, c->cur, FALSE);
290
291     /* We must get all key streams and update them */
292     for (i = 0; i < c->num_key_stream; i++) {
293       key = silc_thread_queue_pop(c->queue, i, TRUE);
294       memcpy(key->ctr, c->iv, SILC_AES_BLOCK);
295       silc_softacc_add_ctr(key->ctr, SILC_AES_BLOCK, i * c->cipher_blocks);
296       silc_thread_queue_push(c->queue, c->num_key_stream, key, FALSE);
297     }
298
299     c->cur = NULL;
300     c->cur_index = 0;
301     c->cur_block = 0;
302     c->pad = SILC_AES_BLOCK;
303   }
304 }
305
306 /* Accelerate cipher */
307
308 SILC_CIPHER_API_SET_KEY(softacc_cipher_aes)
309 {
310   SilcSoftaccCipher c = context;
311   SilcSoftacc sa;
312   SilcUInt32 i;
313
314   /* If key is present set it.  If it is NULL this is initialization call. */
315   if (key) {
316     SILC_SOFTACC_DEBUG(("Set key for accelerator %s %p", ops->alg_name, c));
317
318     aes_encrypt_key(key, keylen, &c->c.aes.u.enc);
319     c->key_set = TRUE;
320
321     /* Set the counters for each key stream and push them to the queue for
322        precompuptation. */
323     for (i = 0; i < c->num_key_stream; i++) {
324       memcpy(c->key_stream[i]->ctr, c->iv, SILC_AES_BLOCK);
325       silc_softacc_add_ctr(c->key_stream[i]->ctr, SILC_AES_BLOCK,
326                            i * c->cipher_blocks);
327       silc_thread_queue_push(c->queue, c->num_key_stream, c->key_stream[i],
328                              FALSE);
329     }
330
331     return TRUE;
332   }
333
334   /* Initialize the accelerator for this cipher */
335   SILC_LOG_DEBUG(("Initialize accelerator for %s %p", ops->alg_name, c));
336
337   sa = silc_global_get_var("softacc", FALSE);
338   if (!sa) {
339     SILC_LOG_ERROR(("Software accelerator not initialized"));
340     return FALSE;
341   }
342
343   /* Start the queue with sa->cipher_blocks many key streams.  One extra pipe
344      in the queue is used as a return pipe for empty key streams. */
345   c->cipher_blocks = sa->cipher_blocks;
346   c->cipher_threads = sa->cipher_threads;
347   c->num_key_stream = sa->cipher_streams;
348   c->key_stream = silc_calloc(c->num_key_stream, sizeof(*c->key_stream));
349   if (!c->key_stream)
350     return FALSE;
351   for (i = 0; i < c->num_key_stream; i++) {
352     c->key_stream[i] = silc_malloc(sizeof(**c->key_stream) +
353                                    (c->cipher_blocks * SILC_AES_BLOCK));
354     if (!c->key_stream[i])
355       return FALSE;
356     c->key_stream[i]->key_index = i;
357   }
358   c->queue = silc_thread_queue_alloc(c->num_key_stream + 1, TRUE);
359   if (!c->queue)
360     return FALSE;
361
362   /* Start the threads.  If thread starting fails, we can't accelerate the
363      cipher.  The uninit operation will clean up any started threads. */
364   for (i = 0; i < sa->cipher_threads; i++)
365     if (!silc_thread_pool_run(sa->tp, FALSE, NULL,
366                               silc_softacc_cipher_aes_thread,
367                               c, silc_softacc_cipher_aes_completion, c))
368       return FALSE;
369
370   return TRUE;
371 }
372
373 /* Accelerated encryption/decryption in CTR mode */
374
375 SILC_CIPHER_API_ENCRYPT(softacc_cipher_aes)
376 {
377   SilcSoftaccCipher c = context;
378   SilcSoftaccCipherKeyStream key;
379   SilcUInt32 pad = c->pad, block = c->cur_block;
380   SilcUInt32 blocks, cipher_blocks = c->cipher_blocks;
381   unsigned char *enc_ctr;
382
383   key = c->cur;
384   if (!key) {
385     c->cur = key = silc_thread_queue_pop(c->queue, c->cur_index, TRUE);
386     SILC_SOFTACC_DEBUG(("Got key stream %p, index %d", key, key->key_index));
387   }
388
389   enc_ctr = key->key + (block << 4);
390
391   /* Compute partial block */
392   if (pad < SILC_AES_BLOCK) {
393     while (len-- > 0) {
394       *dst++ = *src++ ^ enc_ctr[pad++];
395       if (pad == SILC_AES_BLOCK) {
396         enc_ctr += SILC_AES_BLOCK;
397         if (++block == cipher_blocks) {
398           /* Push the used up key stream back to the queue */
399           SILC_SOFTACC_DEBUG(("Push empty key stream %p index %d back to queue",
400                               key, key->key_index));
401           silc_thread_queue_push(c->queue, c->num_key_stream, key, FALSE);
402
403           /* Get new key stream from queue */
404           c->cur_index = (c->cur_index + 1) % c->num_key_stream;
405           c->cur = key = silc_thread_queue_pop(c->queue, c->cur_index, TRUE);
406           SILC_SOFTACC_DEBUG(("Got key stream %p, index %d", key,
407                               key->key_index));
408           enc_ctr = key->key;
409           block = 0;
410         }
411         break;
412       }
413     }
414   }
415
416   /* Compute full blocks */
417   blocks = len >> 4;
418   len -= (blocks << 4);
419   while (blocks--) {
420     /* CTR mode */
421 #ifndef WORDS_BIGENDIAN
422     *(SilcUInt64 *)dst = (*(SilcUInt64 *)src ^
423                           *(SilcUInt64 *)enc_ctr);
424     *(SilcUInt64 *)(dst + 8) = (*(SilcUInt64 *)(src + 8) ^
425                                 *(SilcUInt64 *)(enc_ctr + 8));
426 #else
427     SilcUInt64 dst_tmp, src_tmp, enc_ctr_tmp;
428
429     SILC_GET64_MSB(src_tmp, src);
430     SILC_GET64_MSB(enc_ctr_tmp, enc_ctr);
431     dst_tmp = src_tmp ^ enc_ctr_tmp;
432     SILC_PUT64_MSB(dst_tmp, dst);
433
434     SILC_GET64_MSB(src_tmp, src + 8);
435     SILC_GET64_MSB(enc_ctr_tmp, enc_ctr + 8);
436     dst_tmp = src_tmp ^ enc_ctr_tmp;
437     SILC_PUT64_MSB(dst_tmp, dst + 8);
438 #endif /* !WORDS_BIGENDIAN */
439
440     src += SILC_AES_BLOCK;
441     dst += SILC_AES_BLOCK;
442     enc_ctr += SILC_AES_BLOCK;
443
444     if (++block == cipher_blocks) {
445       /* Push the used up key stream back to the queue */
446       SILC_SOFTACC_DEBUG(("Push empty key stream %p index %d back to queue",
447                           key, key->key_index));
448       silc_thread_queue_push(c->queue, c->num_key_stream, key, FALSE);
449
450       /* Get new key stream from queue */
451       c->cur_index = (c->cur_index + 1) % c->num_key_stream;
452       c->cur = key = silc_thread_queue_pop(c->queue, c->cur_index, TRUE);
453       SILC_SOFTACC_DEBUG(("Got key stream %p, index %d", key, key->key_index));
454       enc_ctr = key->key;
455       block = 0;
456     }
457   }
458
459   /* Compute partial block */
460   if (len > 0) {
461     pad = 0;
462     while (len-- > 0)
463       *dst++ = *src++ ^ enc_ctr[pad++];
464   }
465
466   c->cur_block = block;
467   c->pad = pad;
468
469   return TRUE;
470 }
471
472 /****************************** Other ciphers *******************************/
473
474 /* Thread destructor */
475
476 static SILC_TASK_CALLBACK(silc_softacc_cipher_completion)
477 {
478   SilcSoftaccCipher c = context;
479   int i;
480
481   /* Disconnect from key stream queue */
482   if (silc_thread_queue_disconnect(c->queue))
483     return;
484
485   silc_cipher_free(c->c.ecb);
486   for (i = 0; i < c->num_key_stream; i++)
487     silc_free(c->key_stream[i]);
488   silc_free(c->key_stream);
489   memset(c, 0, sizeof(*c));
490   silc_free(c);
491 }
492
493 /* Key stream computation thread */
494
495 void silc_softacc_cipher_thread(SilcSchedule schedule, void *context)
496 {
497   SilcSoftaccCipher c = context;
498   SilcThreadQueue queue = c->queue;
499   SilcSoftaccCipherKeyStream key = NULL;
500   SilcUInt32 i, block_len, num_key_stream = c->num_key_stream;
501   SilcUInt32 cipher_blocks = c->cipher_blocks;
502   SilcInt32 k;
503   unsigned char *enc_ctr;
504
505   SILC_SOFTACC_DEBUG(("Start CTR precomputation thread"));
506
507   block_len = silc_cipher_get_block_len(c->c.ecb);
508
509   /* Connect to the key stream queue */
510   silc_thread_queue_connect(queue);
511
512   /* Process key streams.  We wait for empty key streams to come from the
513      last pipe in the queue.  Here we precompute the key stream and put them
514      back to the queue. */
515   while (1) {
516     key = silc_thread_queue_pop(queue, num_key_stream, TRUE);
517     if (key == SILC_KEYSTREAM_STOP)
518       break;
519
520     SILC_SOFTACC_DEBUG(("Precompute key stream %p, index %d", key,
521                         key->key_index));
522
523     /* Encrypt */
524     enc_ctr = key->key;
525     for (i = 0; i < cipher_blocks; i++) {
526       for (k = block_len - 1; k >= 0; k--)
527         if (++key->ctr[k])
528           break;
529       c->c.ecb->cipher->encrypt(c->c.ecb, c->c.ecb->cipher, c->c.ecb->context,
530                                 key->ctr, enc_ctr, block_len, NULL);
531       enc_ctr += block_len;
532     }
533
534     SILC_SOFTACC_DEBUG(("Precomputed key stream %p, index %d", key,
535                         key->key_index));
536
537     /* Update counter */
538     silc_softacc_add_ctr(key->ctr, block_len,
539                          (num_key_stream - 1) * cipher_blocks);
540
541     /* Put it back to queue */
542     silc_thread_queue_push(queue, key->key_index, key, FALSE);
543   }
544
545   SILC_SOFTACC_DEBUG(("End CTR precomputation thread"));
546 }
547
548 /* Accelerate cipher */
549
550 SILC_CIPHER_API_SET_KEY(softacc_cipher)
551 {
552   SilcSoftaccCipher c = context;
553   SilcSoftacc sa;
554   SilcUInt32 i;
555
556   /* If key is present set it.  If it is NULL this is initialization call. */
557   if (key) {
558     SILC_SOFTACC_DEBUG(("Set key for accelerator %s %p", ops->alg_name, c));
559
560     SILC_VERIFY(c->c.ecb && c->queue);
561
562     if (!silc_cipher_set_key(c->c.ecb, key, keylen, TRUE))
563       return FALSE;
564     c->key_set = TRUE;
565
566     /* Set the counters for each key stream and push them to the queue for
567        precompuptation. */
568     for (i = 0; i < c->num_key_stream; i++) {
569       memcpy(c->key_stream[i]->ctr, c->iv, silc_cipher_get_iv_len(c->c.ecb));
570       silc_softacc_add_ctr(c->key_stream[i]->ctr,
571                            silc_cipher_get_block_len(c->c.ecb),
572                            i * c->cipher_blocks);
573       silc_thread_queue_push(c->queue, c->num_key_stream, c->key_stream[i],
574                              FALSE);
575     }
576
577     return TRUE;
578   }
579
580   /* Initialize the accelerator for this cipher */
581   SILC_LOG_DEBUG(("Initialize accelerator for %s %p", ops->alg_name, c));
582
583   sa = silc_global_get_var("softacc", FALSE);
584   if (!sa) {
585     SILC_LOG_ERROR(("Software accelerator not initialized"));
586     return FALSE;
587   }
588
589   /* Allocate cipher in ECB mode.  It is used to encrypt the key stream. */
590   if (!silc_cipher_alloc_full(ops->alg_name, ops->key_len,
591                               SILC_CIPHER_MODE_ECB, &c->c.ecb))
592     return FALSE;
593
594   /* Start the queue with sa->cipher_blocks many key streams.  One extra pipe
595      in the queue is used as a return pipe for empty key streams. */
596   c->cipher_blocks = sa->cipher_blocks;
597   c->cipher_threads = sa->cipher_threads;
598   c->num_key_stream = sa->cipher_streams;
599   c->key_stream = silc_calloc(c->num_key_stream, sizeof(*c->key_stream));
600   if (!c->key_stream)
601     return FALSE;
602   for (i = 0; i < c->num_key_stream; i++) {
603     c->key_stream[i] = silc_malloc(sizeof(**c->key_stream) +
604                                    (c->cipher_blocks *
605                                     silc_cipher_get_block_len(c->c.ecb)));
606     if (!c->key_stream[i])
607       return FALSE;
608     c->key_stream[i]->key_index = i;
609   }
610   c->queue = silc_thread_queue_alloc(c->num_key_stream + 1, TRUE);
611   if (!c->queue)
612     return FALSE;
613
614   /* Start the threads.  If thread starting fails, we can't accelerate the
615      cipher.  The uninit operation will clean up any started threads. */
616   for (i = 0; i < sa->cipher_threads; i++)
617     if (!silc_thread_pool_run(sa->tp, FALSE, NULL, silc_softacc_cipher_thread,
618                               c, silc_softacc_cipher_completion, c))
619       return FALSE;
620
621   return TRUE;
622 }
623
624 /* Set IV.  Also, reset current block, discarding any remaining unused bits in
625    the current key block. */
626
627 SILC_CIPHER_API_SET_IV(softacc_cipher)
628 {
629   SilcSoftaccCipher c = context;
630   SilcSoftaccCipherKeyStream key;
631   SilcUInt32 i, block_len, iv_len;
632
633   block_len = silc_cipher_get_block_len(c->c.ecb);
634   iv_len = silc_cipher_get_iv_len(c->c.ecb);
635
636   if (c->pad > block_len)
637     c->pad = block_len;
638
639   /* If IV is NULL we start new block */
640   if (!iv) {
641     SILC_SOFTACC_DEBUG(("Start new block"));
642
643     if (c->pad < block_len) {
644       c->pad = block_len;
645
646       /* Start new block */
647       if (++c->cur_block == c->cipher_blocks) {
648         SILC_SOFTACC_DEBUG(("Push empty key stream %p index %d back to queue",
649                             c->cur, c->cur->key_index));
650         silc_thread_queue_push(c->queue, c->num_key_stream, c->cur, FALSE);
651         c->cur_index = (c->cur_index + 1) % c->cipher_blocks;
652         c->cur_block = 0;
653         c->cur = NULL;
654       }
655     }
656   } else {
657     /* Start new IV */
658     SILC_SOFTACC_DEBUG(("Start new counter"));
659
660     memcpy(c->iv, iv, iv_len);
661
662     if (!c->key_set)
663       return;
664
665     /* Push current key stream back to queue.  We need all of them there
666        below. */
667     if (c->cur)
668       silc_thread_queue_push(c->queue, c->cur->key_index, c->cur, FALSE);
669
670     /* We must get all key streams and update them. */
671     for (i = 0; i < c->num_key_stream; i++) {
672       key = silc_thread_queue_pop(c->queue, i, TRUE);
673       memcpy(key->ctr, c->iv, iv_len);
674       silc_softacc_add_ctr(key->ctr, iv_len, i * c->cipher_blocks);
675       silc_thread_queue_push(c->queue, c->num_key_stream, key, FALSE);
676     }
677
678     c->cur = NULL;
679     c->cur_index = 0;
680     c->cur_block = 0;
681     c->pad = block_len;
682   }
683 }
684
685 SILC_CIPHER_API_ENCRYPT(softacc_cipher)
686 {
687   SilcSoftaccCipher c = context;
688   SilcSoftaccCipherKeyStream key;
689   SilcUInt32 pad = c->pad, block = c->cur_block;
690   SilcUInt32 cipher_blocks = c->cipher_blocks;
691   SilcUInt32 blocks, block_len, i;
692   unsigned char *enc_ctr;
693
694   key = c->cur;
695   if (!key) {
696     c->cur = key = silc_thread_queue_pop(c->queue, c->cur_index, TRUE);
697     SILC_SOFTACC_DEBUG(("Got key stream %p, index %d", key, key->key_index));
698   }
699
700   block_len = c->c.ecb->cipher->block_len;
701   enc_ctr = key->key + (block * block_len);
702
703   /* Compute partial block */
704   if (pad < block_len) {
705     while (len-- > 0) {
706       *dst++ = *src++ ^ enc_ctr[pad++];
707       if (pad == block_len) {
708         enc_ctr += block_len;
709         if (++block == cipher_blocks) {
710           /* Push the used up key stream back to the queue */
711           SILC_SOFTACC_DEBUG(("Push empty key stream %p index %d back to queue",
712                               key, key->key_index));
713           silc_thread_queue_push(c->queue, c->num_key_stream, key, FALSE);
714
715           /* Get new key stream from queue */
716           c->cur_index = (c->cur_index + 1) % c->num_key_stream;
717           c->cur = key = silc_thread_queue_pop(c->queue, c->cur_index, TRUE);
718           SILC_SOFTACC_DEBUG(("Got key stream %p, index %d", key,
719                               xskey->key_index));
720           enc_ctr = key->key;
721           block = 0;
722         }
723         break;
724       }
725     }
726   }
727
728   /* Compute full blocks */
729   blocks = (len / block_len);
730   len -= (blocks * block_len);
731   while (blocks--) {
732     /* CTR mode */
733 #ifndef WORDS_BIGENDIAN
734     for (i = 0; i < block_len / sizeof(SilcUInt64); i++)
735       *(SilcUInt64 *)(dst + (i * sizeof(SilcUInt64))) =
736         *(SilcUInt64 *)(src + (i * sizeof(SilcUInt64))) ^
737         *(SilcUInt64 *)(enc_ctr + (i * sizeof(SilcUInt64)));
738 #else
739     SilcUInt64 dst_tmp, src_tmp, enc_ctr_tmp;
740
741     for (i = 0; i < block_len / sizeof(SilcUInt64); i++) {
742       SILC_GET64_MSB(src_tmp, src + (i * sizeof(SilcUInt64)));
743       SILC_GET64_MSB(enc_ctr_tmp, enc_ctr + (i * sizeof(SilcUInt64)));
744       dst_tmp = src_tmp ^ enc_ctr_tmp;
745       SILC_PUT64_MSB(dst_tmp, dst + (i * sizeof(SilcUInt64)));
746     }
747 #endif /* !WORDS_BIGENDIAN */
748
749     src += block_len;
750     dst += block_len;
751     enc_ctr += block_len;
752
753     if (++block == cipher_blocks) {
754       /* Push the used up key stream back to the queue */
755       SILC_SOFTACC_DEBUG(("Push empty key stream %p index %d back to queue",
756                           key, key->key_index));
757       silc_thread_queue_push(c->queue, c->num_key_stream, key, FALSE);
758
759       /* Get new key stream from queue */
760       c->cur_index = (c->cur_index + 1) % c->num_key_stream;
761       c->cur = key = silc_thread_queue_pop(c->queue, c->cur_index, TRUE);
762       SILC_SOFTACC_DEBUG(("Got key stream %p, index %d", key,
763                           key->key_index));
764       enc_ctr = key->key;
765       block = 0;
766     }
767   }
768
769   /* Compute partial block */
770   if (len > 0) {
771     pad = 0;
772     while (len-- > 0)
773       *dst++ = *src++ ^ enc_ctr[pad++];
774   }
775
776   c->cur_block = block;
777   c->pad = pad;
778
779   return TRUE;
780 }
781
782 /* Return accelerator cipher context */
783
784 SILC_CIPHER_API_INIT(softacc_cipher)
785 {
786   SilcSoftaccCipher c = silc_calloc(1, sizeof(*c));
787
788   if (!c)
789     return NULL;
790
791   c->pad = 16;
792
793   return c;
794 }
795
796 /* Uninitialize the cipher accelerator */
797
798 SILC_CIPHER_API_UNINIT(softacc_cipher)
799 {
800   SilcSoftaccCipher c = context;
801   int i;
802
803   /* Stop threads */
804   if (c->queue) {
805     for (i = 0; i < c->cipher_threads; i++)
806       silc_thread_queue_push(c->queue, c->num_key_stream,
807                              SILC_KEYSTREAM_STOP, FALSE);
808
809     /* Disconnect from key stream queue */
810     if (silc_thread_queue_disconnect(c->queue))
811       return;
812   }
813
814   silc_free(c->key_stream);
815   memset(c, 0, sizeof(*c));
816   silc_free(c);
817 }