Change PACKAGE to PROGRAM_NAME.
[asdcplib.git] / src / kmfilegen.cpp
1 /*
2 Copyright (c) 2005-2008, 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$
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-2008 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, ASDCP::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   const char* filename;  // filename to be processed
127   const char* 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), version_flag(false), help_flag(false),
134     filename(""), write_filename(""), chunk_count(0), mode(MMT_VALIDATE)
135   {
136     //    order = "rand";
137
138     for ( int i = 1; i < argc; i++ )
139       {
140
141         if ( (strcmp( argv[i], "-help") == 0) )
142           {
143             help_flag = true;
144             continue;
145           }
146      
147         if ( argv[i][0] == '-' && isalpha(argv[i][1]) && argv[i][2] == 0 )
148           {
149             switch ( argv[i][1] )
150               {
151               case 'c':
152                 mode = MMT_CREATE;
153                 TEST_EXTRA_ARG(i, 'c');
154                 chunk_count = atoi(argv[i]);
155                 break;
156                 
157               case 'V': version_flag = true; break;
158               case 'h': help_flag = true; break;
159               case 'v': verbose_flag = true; break;
160
161               case 'o':
162                 TEST_EXTRA_ARG(i, 'o');
163                 order = argv[i];
164
165                 if ( strcmp(order, "fwd" ) != 0 
166                      && strcmp(order, "rev" ) != 0
167                      && strcmp(order, "rand" ) != 0 )
168                   {
169                     fprintf(stderr, "Unexpected order token: %s, expecting fwd|rev|rand\n", order);
170                     return;
171                   }
172
173                 break;
174
175               case 'w':
176                 mode = MMT_VAL_WRITE;
177                 TEST_EXTRA_ARG(i, 'w');
178                 write_filename = argv[i];
179                 break;
180                     
181               default:
182                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
183                 return;
184               }
185           }
186         else
187           {
188             if (argv[i][0] != '-' )
189               {
190                 if ( filename != "" )
191                   {
192                     fprintf(stderr, "Extra filename found: %s\n", argv[i]);
193                     return;
194                   }
195                 else
196                   filename = argv[i];
197               }
198             else
199               {
200                 fprintf(stderr, "Unrecognized option: %s\n", argv[i]);
201                 return;
202               }
203           }
204       }
205     
206     if ( help_flag || version_flag )
207       return;
208     
209     if ( strlen ( filename ) == 0 )
210       {
211         fprintf(stderr, "Filename required.\n");
212         return;
213       }
214     
215     if ( mode != MMT_VALIDATE && strcmp(order, "") != 0 )
216       {
217         fprintf(stderr, "-o option not valid with -c or -w options.\n");
218         return;
219       }
220     else
221       if ( strcmp(order, "") == 0 )
222         order = "rand";
223
224     if ( strcmp ( filename, write_filename ) == 0 )
225       {
226         fprintf(stderr, "Output and input files must be different.\n");
227         return;
228       }
229     
230     error_flag = false;
231   }
232 };
233
234 //------------------------------------------------------------------------------------------
235
236
237 //
238 #pragma pack(4)
239 class CTR_Setup
240 {
241   AES_KEY  m_Context;
242   byte_t   m_key[RNG_KEY_SIZE];
243   byte_t   m_preamble[8];
244   ui32_t   m_nonce;
245   ui32_t   m_ctr;
246
247   KM_NO_COPY_CONSTRUCT(CTR_Setup);
248
249 public:
250   CTR_Setup() {}
251   ~CTR_Setup() {}
252
253   inline ui32_t Nonce()     { return KM_i32_LE(m_nonce); }
254   inline ui32_t WriteSize() { return ( sizeof(m_key) + sizeof(m_preamble)
255                                        + sizeof(m_nonce) + sizeof(m_ctr) ); }
256
257   //
258   void SetupWrite(byte_t* buf)
259   {
260     assert(buf);
261     s_RNG.FillRandom(m_key, WriteSize());
262     assert(s_Nonce > 0);
263     m_nonce = KM_i32_LE(s_Nonce--);
264     m_ctr &= KM_i32_LE(0x7fffffff); // make sure we have 2GB headroom
265     memcpy(buf, m_key, WriteSize());
266     AES_set_encrypt_key(m_key, RNG_KEY_SIZE_BITS, &m_Context);
267   }
268
269   //
270   void SetupRead(const byte_t* buf)
271   {
272     assert(buf);
273     memcpy(m_key, buf, WriteSize());
274     AES_set_encrypt_key(m_key, RNG_KEY_SIZE_BITS, &m_Context);
275   }
276
277   //
278   void FillRandom(byte_t* buf, ui32_t buf_len)
279   {
280     ui32_t gen_count = 0;
281     while ( gen_count + RNG_BLOCK_SIZE <= buf_len )
282       {
283         AES_encrypt(m_preamble, buf + gen_count, &m_Context);
284         m_ctr = KM_i32_LE(KM_i32_LE(m_ctr) + 1);
285         gen_count += RNG_BLOCK_SIZE;
286       }
287   }
288 };
289
290 //
291 Result_t
292 CreateLargeFile(CommandOptions& Options)
293 {
294   ui32_t write_total = 0;
295   ui32_t write_count = 0;
296   FileWriter  Writer;
297   ByteString  FB;
298
299   FB.Capacity(Megabyte);
300   assert(FB.Capacity() == Megabyte);
301
302   fprintf(stderr, "Writing %u chunks:\n", Options.chunk_count);
303   s_Nonce = Options.chunk_count;
304   Result_t result = Writer.OpenWrite(Options.filename);
305
306   while ( KM_SUCCESS(result) && write_total < Options.chunk_count )
307     {
308       if ( KM_SUCCESS(result))
309         {
310           CTR_Setup CTR;
311           CTR.SetupWrite(FB.Data());
312           CTR.FillRandom(FB.Data() + CTR.WriteSize(), Megabyte - CTR.WriteSize());
313           result = Writer.Write(FB.RoData(), Megabyte, &write_count);
314           assert(write_count == Megabyte);
315           fprintf(stderr, "\r%8u ", ++write_total);
316         }
317     }
318   
319   fputs("\n", stderr);
320
321   return result;
322 }
323
324 //
325 Result_t
326 validate_chunk(ByteString& FB, ByteString& CB, ui32_t* nonce_value)
327 {
328   assert(nonce_value);
329   CTR_Setup CTR;
330   CTR.SetupRead(FB.RoData());
331
332   CTR.FillRandom(CB.Data() + CTR.WriteSize(),
333                  Megabyte - CTR.WriteSize());
334
335   if ( memcmp(FB.RoData() + CTR.WriteSize(),
336               CB.RoData() + CTR.WriteSize(),
337               Megabyte - CTR.WriteSize()) != 0 )
338     {
339       fprintf(stderr, "Check data mismatched in chunk\n");
340       return RESULT_FAIL;
341     }
342
343   *nonce_value = CTR.Nonce();
344
345   return RESULT_OK;
346 }
347
348 //
349 struct read_list_t
350 {
351   ui32_t nonce;
352   Kumu::fpos_t position;
353 };
354
355 //
356 void
357 randomize_list(read_list_t* read_list, ui32_t check_total)
358 {
359   static ui32_t tmp_ints[4];
360   static ui32_t seq = 0;
361
362   for ( ui32_t j = 0; j < check_total; j++ )
363     {
364       if ( seq > 3 )
365         seq = 0;
366
367       if ( seq == 0 )
368         s_RNG.FillRandom((byte_t*)tmp_ints, 16);
369
370       ui32_t i = tmp_ints[seq++] % (check_total - 1);
371
372       if ( i == j )
373         continue;
374
375       read_list_t t = read_list[i];
376       read_list[i] = read_list[j];
377       read_list[j] = t;
378     }
379 }
380
381 //
382 Result_t
383 ReadValidateWriteLargeFile(CommandOptions& Options)
384 {
385   assert(Options.write_filename);
386   ui32_t  check_total = 0;
387   ui32_t  write_total = 0;
388   ui32_t  read_count = 0;
389   ui32_t write_count = 0;
390   FileReader  Reader;
391   FileWriter  Writer;
392   ByteString  FB, CB; // Frame Buffer and Check Buffer
393
394
395   FB.Capacity(Megabyte);
396   assert(FB.Capacity() == Megabyte);
397   CB.Capacity(Megabyte);
398   assert(CB.Capacity() == Megabyte);
399
400   Result_t result = Reader.OpenRead(Options.filename);
401
402   if ( KM_SUCCESS(result) )
403     result = Writer.OpenWrite(Options.write_filename);
404
405   // read the first chunk and get set up
406   while ( KM_SUCCESS(result) )
407     {
408       result = Reader.Read(FB.Data(), Megabyte, &read_count);
409
410       if ( KM_SUCCESS(result) )
411         {
412           if ( read_count < Megabyte )
413             {
414               fprintf(stderr, "Read() returned short buffer: %u\n", read_count);
415               result = RESULT_FAIL;
416             }
417
418           result = validate_chunk(FB, CB, &check_total);
419
420           if ( KM_SUCCESS(result) )
421             {
422               result = Writer.Write(FB.RoData(), Megabyte, &write_count);
423               assert(write_count == Megabyte);
424               fprintf(stderr, "\r%8u ", ++write_total);
425             }
426         }
427       else if ( result == RESULT_ENDOFFILE )
428         {
429           result = RESULT_OK;
430           break;
431         }
432     }
433
434   fputs("\n", stderr);
435   return result;
436 }
437
438
439 //
440 Result_t
441 ValidateLargeFile(CommandOptions& Options)
442 {
443   ui32_t  check_total = 0;
444   ui32_t  read_count = 0;
445   ui32_t  read_list_i = 0;
446   read_list_t* read_list = 0;
447   FileReader Reader;
448   ByteString FB, CB; // Frame Buffer and Check Buffer
449
450   FB.Capacity(Megabyte);
451   assert(FB.Capacity() == Megabyte);
452   CB.Capacity(Megabyte);
453   assert(CB.Capacity() == Megabyte);
454
455   Result_t result = Reader.OpenRead(Options.filename);
456
457   // read the first chunk and get set up
458   if ( KM_SUCCESS(result) )
459     {
460       result = Reader.Read(FB.Data(), Megabyte, &read_count);
461
462       if ( read_count < Megabyte )
463         {
464           fprintf(stderr, "Read() returned short buffer: %u\n", read_count);
465           result = RESULT_FAIL;
466         }
467       else if ( KM_SUCCESS(result) )
468         result = validate_chunk(FB, CB, &check_total);
469
470       if ( KM_SUCCESS(result) )
471         {
472           fprintf(stderr, "Validating %u chunk%s in %s order:\n",
473                   check_total, (check_total == 1 ? "" : "s"), Options.order);
474           assert(read_list == 0);
475           read_list = (read_list_t*)malloc(check_total * sizeof(read_list_t));
476           assert(read_list);
477
478           // Set up an index to the chunks. The chunks are written
479           // to the file in order of descending nonce value.
480           if ( strcmp(Options.order, "fwd") == 0 )
481             {
482               for ( ui32_t i = 0; i < check_total; i++ )
483                 {
484                   read_list[i].nonce = check_total - i;
485                   Kumu::fpos_t ofst = check_total - read_list[i].nonce;
486                   read_list[i].position = ofst * (Kumu::fpos_t)Megabyte;
487                 }
488             }
489           else
490             {
491               for ( ui32_t i = 0; i < check_total; i++ )
492                 {
493                   read_list[i].nonce = i + 1;
494                   Kumu::fpos_t ofst = check_total - read_list[i].nonce;
495                   read_list[i].position = ofst * (Kumu::fpos_t)Megabyte;
496                 }
497
498               if ( strcmp(Options.order, "rand") == 0 )
499                 randomize_list(read_list, check_total); // this makes it random
500             }
501         }
502     }
503
504   if ( KM_SUCCESS(result) )
505     {
506       assert(read_list);
507       ui32_t nonce = 0;
508
509       for ( read_list_i = 0;
510             read_list_i < check_total && KM_SUCCESS(result);
511             read_list_i++ )
512         {
513           fprintf(stderr, "\r%8u [%8u] ", read_list_i+1, read_list[read_list_i].nonce);
514           result = Reader.Seek(read_list[read_list_i].position);
515
516           if ( KM_SUCCESS(result) )
517             result = Reader.Read(FB.Data(), Megabyte, &read_count);
518
519           if ( result == RESULT_ENDOFFILE )
520             break;
521
522           else if ( read_count < Megabyte )
523             {
524               fprintf(stderr, "Read() returned short buffer: %u\n", read_count);
525               result = RESULT_FAIL;
526             }
527           else if ( KM_SUCCESS(result) )
528             {
529               result = validate_chunk(FB, CB, &nonce);
530               
531               if ( nonce != read_list[read_list_i].nonce )
532                 {
533                   fprintf(stderr, "Nonce mismatch: expecting %u, got %u\n",
534                           nonce, read_list[read_list_i].nonce);
535
536                   return RESULT_FAIL;
537                 }
538             }
539         }
540     }
541
542   fputs("\n", stderr);
543
544   if ( result == RESULT_ENDOFFILE )
545     {
546       if ( check_total == read_list_i )
547         result = RESULT_OK;
548       else
549         {
550           fprintf(stderr, "Unexpected chunk count, got %u, wanted %u\n",
551                   read_list_i, check_total);
552           result = RESULT_FAIL;
553         }
554     }
555
556   return result;
557 }
558
559 //
560 int
561 main(int argc, const char **argv)
562 {
563   Result_t result = RESULT_FAIL;
564   CommandOptions Options(argc, argv);
565
566   if ( Options.version_flag )
567     banner();
568
569   if ( Options.help_flag )
570     usage();
571
572   if ( Options.version_flag || Options.help_flag )
573     return 0;
574
575   if ( Options.error_flag )
576     {
577       fprintf(stderr, "There was a problem. Type %s -h for help.\n", PROGRAM_NAME);
578       return 3;
579     }
580
581   switch ( Options.mode )
582     {
583
584     case MMT_CREATE:
585       result = CreateLargeFile(Options);
586       break;
587
588     case MMT_VALIDATE:
589       result = ValidateLargeFile(Options);
590       break;
591
592     case MMT_VAL_WRITE:
593       result = ReadValidateWriteLargeFile(Options);
594       break;
595     }
596
597   if ( result != RESULT_OK )
598     {
599       fputs("Program stopped on error.\n", stderr);
600
601       if ( result != RESULT_FAIL )
602         {
603           fputs(result.Label(), stderr);
604           fputc('\n', stderr);
605         }
606
607       return 1;
608     }
609
610   return 0;
611 }
612
613
614 //
615 // end kmfilegen.cpp
616 //