Fix a type-punning warning.
[asdcplib-cth.git] / src / kmfilegen.cpp
1 /*
2 Copyright (c) 2005-2009, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    kmfilegen.cpp
28     \version $Id: kmfilegen.cpp,v 1.10 2015/10/16 16:55:33 jhurst Exp $
29     \brief   large file test program
30 */
31
32
33 #include "AS_DCP.h"
34 #include <iostream>
35 #include <KM_fileio.h>
36 #include <KM_prng.h>
37 #include <openssl/aes.h>
38 #include <assert.h>
39
40 using namespace Kumu;
41
42 // constants
43 static const char* PROGRAM_NAME = "kmfilegen";  // program name for messages
44 const ui32_t RNG_KEY_SIZE = 16;
45 const ui32_t RNG_KEY_SIZE_BITS = 128;
46 const ui32_t RNG_BLOCK_SIZE = 16;
47
48 // globals
49 ui32_t      s_Nonce = 0;
50 FortunaRNG  s_RNG;
51
52
53 //------------------------------------------------------------------------------------------
54 //
55 // command line option parser class
56
57 // Increment the iterator, test for an additional non-option command line argument.
58 // Causes the caller to return if there are no remaining arguments or if the next
59 // argument begins with '-'.
60 #define TEST_EXTRA_ARG(i,c)    if ( ++i >= argc || argv[(i)][0] == '-' ) \
61                                  { \
62                                    fprintf(stderr, "Argument not found for option -%c.\n", (c)); \
63                                    return; \
64                                  }
65
66 //
67 void
68 banner(FILE* stream = stdout)
69 {
70   fprintf(stream, "\n\
71 %s (asdcplib %s)\n\n\
72 Copyright (c) 2005-2009 John Hurst\n\
73 %s is part of the asdcplib DCP tools package.\n\
74 asdcplib may be copied only under the terms of the license found at\n\
75 the top of every file in the asdcplib distribution kit.\n\n\
76 Specify the -h (help) option for further information about %s\n\n",
77           PROGRAM_NAME, Kumu::Version(), PROGRAM_NAME, PROGRAM_NAME);
78 }
79
80
81 //
82 void
83 usage(FILE* stream = stdout)
84 {
85   fprintf(stream, "\
86 USAGE: %s [-c <file-size>] [-v] <output-file>\n\
87 \n\
88        %s [-o <fwd|rev|rand>] [-v] <input-file>\n\
89 \n\
90        %s [-w <output-file>] [-v] <input-file>\n\
91 \n\
92        %s [-h|-help] [-V]\n\
93 \n\
94   -c <file-size>     - Create test file containing <file-size> megabytes of data\n\
95   -h | -help         - Show help\n\
96   -o <fwd|rev|rand>  - Specify order used when validating a file.\n\
97                        One of fwd|rev|rand, default is rand\n\
98   -v                 - Verbose. Prints informative messages to stderr\n\
99   -V                 - Show version information\n\
100   -w <output-file>   - Read-Validate-Write - file is written to <output-file>\n\
101                        (sequential read only)\n\
102 \n\
103   NOTES: o There is no option grouping, all options must be distinct arguments.\n\
104          o All option arguments must be separated from the option by whitespace.\n\
105 \n", PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME);
106 }
107
108 enum MajorMode_t {
109   MMT_NONE,
110   MMT_CREATE,
111   MMT_VALIDATE,
112   MMT_VAL_WRITE
113 };
114
115 //
116 class CommandOptions
117 {
118   CommandOptions();
119
120 public:
121   bool   error_flag;     // true if the given options are in error or not complete
122   const char* order;     // one of fwd|rev|rand
123   bool   verbose_flag;   // true if the verbose option was selected
124   bool   version_flag;   // true if the version display option was selected
125   bool   help_flag;      // true if the help display option was selected
126   std::string filename;  // filename to be processed
127   std::string write_filename;  // filename to write with val_write_flag
128   ui32_t chunk_count;
129   MajorMode_t mode;      // MajorMode selector
130
131   //
132   CommandOptions(int argc, const char** argv) :
133     error_flag(true), order(""), verbose_flag(false),
134     version_flag(false), help_flag(false),
135     chunk_count(0), mode(MMT_VALIDATE)
136   {
137     //    order = "rand";
138
139     for ( int i = 1; i < argc; i++ )
140       {
141
142         if ( (strcmp( argv[i], "-help") == 0) )
143           {
144             help_flag = true;
145             continue;
146           }
147      
148         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
149           {
150             switch ( argv[i][1] )
151               {
152               case 'c':
153                 mode = MMT_CREATE;
154                 TEST_EXTRA_ARG(i, 'c');
155                 chunk_count = Kumu::xabs(strtol(argv[i], 0, 10));
156                 break;
157                 
158               case 'V': version_flag = true; break;
159               case 'h': help_flag = true; break;
160               case 'v': verbose_flag = true; break;
161
162               case 'o':
163                 TEST_EXTRA_ARG(i, 'o');
164                 order = argv[i];
165
166                 if ( strcmp(order, "fwd" ) != 0 
167                      && strcmp(order, "rev" ) != 0
168                      && strcmp(order, "rand" ) != 0 )
169                   {
170                     fprintf(stderr, "Unexpected order token: %s, expecting fwd|rev|rand\n", order);
171                     return;
172                   }
173
174                 break;
175
176               case 'w':
177                 mode = MMT_VAL_WRITE;
178                 TEST_EXTRA_ARG(i, 'w');
179                 write_filename = argv[i];
180                 break;
181                     
182               default:
183                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
184                 return;
185               }
186           }
187         else
188           {
189             if (argv[i][0] != '-' )
190               {
191                 if ( ! filename.empty() )
192                   {
193                     fprintf(stderr, "Extra filename found: %s\n", argv[i]);
194                     return;
195                   }
196                 else
197                   filename = argv[i];
198               }
199             else
200               {
201                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
202                 return;
203               }
204           }
205       }
206     
207     if ( help_flag || version_flag )
208       return;
209     
210     if ( filename.empty() )
211       {
212         fprintf(stderr, "Filename required.\n");
213         return;
214       }
215     
216     if ( mode != MMT_VALIDATE && strcmp(order, "") != 0 )
217       {
218         fprintf(stderr, "-o option not valid with -c or -w options.\n");
219         return;
220       }
221     else
222       if ( strcmp(order, "") == 0 )
223         order = "rand";
224
225     if ( filename == write_filename )
226       {
227         fprintf(stderr, "Output and input files must be different.\n");
228         return;
229       }
230     
231     error_flag = false;
232   }
233 };
234
235 //------------------------------------------------------------------------------------------
236
237
238 //
239 #pragma pack(4)
240 class CTR_Setup
241 {
242   AES_KEY  m_Context;
243   byte_t   m_key[RNG_KEY_SIZE];
244   byte_t   m_preamble[8];
245   ui32_t   m_nonce;
246   ui32_t   m_ctr;
247
248   KM_NO_COPY_CONSTRUCT(CTR_Setup);
249
250 public:
251   CTR_Setup() {}
252   ~CTR_Setup() {}
253
254   inline ui32_t Nonce()     { return KM_i32_LE(m_nonce); }
255   inline ui32_t WriteSize() { return ( sizeof(m_key) + sizeof(m_preamble)
256                                        + sizeof(m_nonce) + sizeof(m_ctr) ); }
257
258   //
259   void SetupWrite(byte_t* buf)
260   {
261     assert(buf);
262     s_RNG.FillRandom(m_key, WriteSize());
263     assert(s_Nonce > 0);
264     m_nonce = KM_i32_LE(s_Nonce--);
265     m_ctr &= KM_i32_LE(0x7fffffff); // make sure we have 2GB headroom
266     memcpy(buf, m_key, WriteSize());
267     AES_set_encrypt_key(m_key, RNG_KEY_SIZE_BITS, &m_Context);
268   }
269
270   //
271   void SetupRead(const byte_t* buf)
272   {
273     assert(buf);
274     memcpy(m_key, buf, WriteSize());
275     AES_set_encrypt_key(m_key, RNG_KEY_SIZE_BITS, &m_Context);
276   }
277
278   //
279   void FillRandom(byte_t* buf, ui32_t buf_len)
280   {
281     ui32_t gen_count = 0;
282     while ( gen_count + RNG_BLOCK_SIZE <= buf_len )
283       {
284         AES_encrypt(m_preamble, buf + gen_count, &m_Context);
285         m_ctr = KM_i32_LE(KM_i32_LE(m_ctr) + 1);
286         gen_count += RNG_BLOCK_SIZE;
287       }
288   }
289 };
290
291 //
292 Result_t
293 CreateLargeFile(CommandOptions& Options)
294 {
295   ui32_t write_total = 0;
296   ui32_t write_count = 0;
297   FileWriter  Writer;
298   ByteString  FB;
299
300   FB.Capacity(Megabyte);
301   assert(FB.Capacity() == Megabyte);
302
303   fprintf(stderr, "Writing %u chunks:\n", Options.chunk_count);
304   s_Nonce = Options.chunk_count;
305   Result_t result = Writer.OpenWrite(Options.filename);
306
307   while ( KM_SUCCESS(result) && write_total < Options.chunk_count )
308     {
309       if ( KM_SUCCESS(result))
310         {
311           CTR_Setup CTR;
312           CTR.SetupWrite(FB.Data());
313           CTR.FillRandom(FB.Data() + CTR.WriteSize(), Megabyte - CTR.WriteSize());
314           result = Writer.Write(FB.RoData(), Megabyte, &write_count);
315           assert(write_count == Megabyte);
316           fprintf(stderr, "\r%8u ", ++write_total);
317         }
318     }
319   
320   fputs("\n", stderr);
321
322   return result;
323 }
324
325 //
326 Result_t
327 validate_chunk(ByteString& FB, ByteString& CB, ui32_t* nonce_value)
328 {
329   assert(nonce_value);
330   CTR_Setup CTR;
331   CTR.SetupRead(FB.RoData());
332
333   CTR.FillRandom(CB.Data() + CTR.WriteSize(),
334                  Megabyte - CTR.WriteSize());
335
336   if ( memcmp(FB.RoData() + CTR.WriteSize(),
337               CB.RoData() + CTR.WriteSize(),
338               Megabyte - CTR.WriteSize()) != 0 )
339     {
340       fprintf(stderr, "Check data mismatched in chunk\n");
341       return RESULT_FAIL;
342     }
343
344   *nonce_value = CTR.Nonce();
345
346   return RESULT_OK;
347 }
348
349 //
350 struct read_list_t
351 {
352   ui32_t nonce;
353   Kumu::fpos_t position;
354 };
355
356 //
357 void
358 randomize_list(read_list_t* read_list, ui32_t check_total)
359 {
360   static ui32_t tmp_ints[4];
361   static ui32_t seq = 0;
362
363   for ( ui32_t j = 0; j < check_total; j++ )
364     {
365       if ( seq > 3 )
366         seq = 0;
367
368       if ( seq == 0 )
369         s_RNG.FillRandom((byte_t*)tmp_ints, 16);
370
371       ui32_t i = tmp_ints[seq++] % (check_total - 1);
372
373       if ( i == j )
374         continue;
375
376       read_list_t t = read_list[i];
377       read_list[i] = read_list[j];
378       read_list[j] = t;
379     }
380 }
381
382 //
383 Result_t
384 ReadValidateWriteLargeFile(CommandOptions& Options)
385 {
386   assert(!Options.write_filename.empty());
387   ui32_t  check_total = 0;
388   ui32_t  write_total = 0;
389   ui32_t  read_count = 0;
390   ui32_t write_count = 0;
391   FileReader  Reader;
392   FileWriter  Writer;
393   ByteString  FB, CB; // Frame Buffer and Check Buffer
394
395
396   FB.Capacity(Megabyte);
397   assert(FB.Capacity() == Megabyte);
398   CB.Capacity(Megabyte);
399   assert(CB.Capacity() == Megabyte);
400
401   Result_t result = Reader.OpenRead(Options.filename);
402
403   if ( KM_SUCCESS(result) )
404     result = Writer.OpenWrite(Options.write_filename);
405
406   // read the first chunk and get set up
407   while ( KM_SUCCESS(result) )
408     {
409       result = Reader.Read(FB.Data(), Megabyte, &read_count);
410
411       if ( KM_SUCCESS(result) )
412         {
413           if ( read_count < Megabyte )
414             {
415               fprintf(stderr, "Read() returned short buffer: %u\n", read_count);
416               result = RESULT_FAIL;
417             }
418
419           result = validate_chunk(FB, CB, &check_total);
420
421           if ( KM_SUCCESS(result) )
422             {
423               result = Writer.Write(FB.RoData(), Megabyte, &write_count);
424               assert(write_count == Megabyte);
425               fprintf(stderr, "\r%8u ", ++write_total);
426             }
427         }
428       else if ( result == RESULT_ENDOFFILE )
429         {
430           result = RESULT_OK;
431           break;
432         }
433     }
434
435   fputs("\n", stderr);
436   return result;
437 }
438
439
440 //
441 Result_t
442 ValidateLargeFile(CommandOptions& Options)
443 {
444   ui32_t  check_total = 0;
445   ui32_t  read_count = 0;
446   ui32_t  read_list_i = 0;
447   read_list_t* read_list = 0;
448   FileReader Reader;
449   ByteString FB, CB; // Frame Buffer and Check Buffer
450
451   FB.Capacity(Megabyte);
452   assert(FB.Capacity() == Megabyte);
453   CB.Capacity(Megabyte);
454   assert(CB.Capacity() == Megabyte);
455
456   Result_t result = Reader.OpenRead(Options.filename);
457
458   // read the first chunk and get set up
459   if ( KM_SUCCESS(result) )
460     {
461       result = Reader.Read(FB.Data(), Megabyte, &read_count);
462
463       if ( read_count < Megabyte )
464         {
465           fprintf(stderr, "Read() returned short buffer: %u\n", read_count);
466           result = RESULT_FAIL;
467         }
468       else if ( KM_SUCCESS(result) )
469         result = validate_chunk(FB, CB, &check_total);
470
471       if ( KM_SUCCESS(result) )
472         {
473           fprintf(stderr, "Validating %u chunk%s in %s order:\n",
474                   check_total, (check_total == 1 ? "" : "s"), Options.order);
475           assert(read_list == 0);
476           read_list = (read_list_t*)malloc(check_total * sizeof(read_list_t));
477           assert(read_list);
478
479           // Set up an index to the chunks. The chunks are written
480           // to the file in order of descending nonce value.
481           if ( strcmp(Options.order, "fwd") == 0 )
482             {
483               for ( ui32_t i = 0; i < check_total; i++ )
484                 {
485                   read_list[i].nonce = check_total - i;
486                   Kumu::fpos_t ofst = check_total - read_list[i].nonce;
487                   read_list[i].position = ofst * (Kumu::fpos_t)Megabyte;
488                 }
489             }
490           else
491             {
492               for ( ui32_t i = 0; i < check_total; i++ )
493                 {
494                   read_list[i].nonce = i + 1;
495                   Kumu::fpos_t ofst = check_total - read_list[i].nonce;
496                   read_list[i].position = ofst * (Kumu::fpos_t)Megabyte;
497                 }
498
499               if ( strcmp(Options.order, "rand") == 0 )
500                 randomize_list(read_list, check_total); // this makes it random
501             }
502         }
503     }
504
505   if ( KM_SUCCESS(result) )
506     {
507       assert(read_list);
508       ui32_t nonce = 0;
509
510       for ( read_list_i = 0;
511             read_list_i < check_total && KM_SUCCESS(result);
512             read_list_i++ )
513         {
514           fprintf(stderr, "\r%8u [%8u] ", read_list_i+1, read_list[read_list_i].nonce);
515           result = Reader.Seek(read_list[read_list_i].position);
516
517           if ( KM_SUCCESS(result) )
518             result = Reader.Read(FB.Data(), Megabyte, &read_count);
519
520           if ( result == RESULT_ENDOFFILE )
521             break;
522
523           else if ( read_count < Megabyte )
524             {
525               fprintf(stderr, "Read() returned short buffer: %u\n", read_count);
526               result = RESULT_FAIL;
527             }
528           else if ( KM_SUCCESS(result) )
529             {
530               result = validate_chunk(FB, CB, &nonce);
531               
532               if ( nonce != read_list[read_list_i].nonce )
533                 {
534                   fprintf(stderr, "Nonce mismatch: expecting %u, got %u\n",
535                           nonce, read_list[read_list_i].nonce);
536
537                   return RESULT_FAIL;
538                 }
539             }
540         }
541     }
542
543   fputs("\n", stderr);
544
545   if ( result == RESULT_ENDOFFILE )
546     {
547       if ( check_total == read_list_i )
548         result = RESULT_OK;
549       else
550         {
551           fprintf(stderr, "Unexpected chunk count, got %u, wanted %u\n",
552                   read_list_i, check_total);
553           result = RESULT_FAIL;
554         }
555     }
556
557   return result;
558 }
559
560 //
561 int
562 main(int argc, const char **argv)
563 {
564   Result_t result = RESULT_FAIL;
565   CommandOptions Options(argc, argv);
566
567   if ( Options.version_flag )
568     banner();
569
570   if ( Options.help_flag )
571     usage();
572
573   if ( Options.version_flag || Options.help_flag )
574     return 0;
575
576   if ( Options.error_flag )
577     {
578       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
579       return 3;
580     }
581
582   switch ( Options.mode )
583     {
584
585     case MMT_CREATE:
586       result = CreateLargeFile(Options);
587       break;
588
589     case MMT_VALIDATE:
590       result = ValidateLargeFile(Options);
591       break;
592
593     case MMT_VAL_WRITE:
594       result = ReadValidateWriteLargeFile(Options);
595       break;
596
597     default:
598       break;
599     }
600
601   if ( result != RESULT_OK )
602     {
603       fputs("Program stopped on error.\n", stderr);
604
605       if ( result != RESULT_FAIL )
606         {
607           fputs(result.Label(), stderr);
608           fputc('\n', stderr);
609         }
610
611       return 1;
612     }
613
614   return 0;
615 }
616
617
618 //
619 // end kmfilegen.cpp
620 //