1/*
2 * Copyright (c) 2011 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <stdint.h>
27#include <getopt.h>
28#include <string.h>
29#include <stdbool.h>
30#include <fcntl.h>
31#include <unistd.h>
32#include <assert.h>
33#include <libgen.h>
34#include <ctype.h>
35#include <AssertMacros.h>
36#include <CommonNumerics/CommonCRC.h>
37#include <CommonNumerics/CommonBaseXX.h>
38
39#define CN_NAME "cn"
40#define ENCODE_DEFAULT_WIDTH 64
41
42#define PRINT(...)  if (context->verbose) { fprintf(context->out_file, __VA_ARGS__); }
43
44#define CN_ITEM(item)   { #item, item }
45
46typedef struct _cnItem
47{
48    const char  *name;
49    uint32_t    alg;
50} cnItem;
51
52static cnItem crcMap[] = {
53    CN_ITEM(kCN_CRC_8),
54    CN_ITEM(kCN_CRC_8_ICODE),
55    CN_ITEM(kCN_CRC_8_ITU),
56    CN_ITEM(kCN_CRC_8_ROHC),
57    CN_ITEM(kCN_CRC_8_WCDMA),
58    CN_ITEM(kCN_CRC_16),
59    CN_ITEM(kCN_CRC_16_CCITT_TRUE),
60    CN_ITEM(kCN_CRC_16_CCITT_FALSE),
61    CN_ITEM(kCN_CRC_16_USB),
62    CN_ITEM(kCN_CRC_16_XMODEM),
63    CN_ITEM(kCN_CRC_16_DECT_R),
64    CN_ITEM(kCN_CRC_16_DECT_X),
65    CN_ITEM(kCN_CRC_16_ICODE),
66    CN_ITEM(kCN_CRC_16_VERIFONE),
67    CN_ITEM(kCN_CRC_16_A),
68    CN_ITEM(kCN_CRC_16_B),
69    CN_ITEM(kCN_CRC_16_Fletcher),
70    CN_ITEM(kCN_CRC_32_Adler),
71    CN_ITEM(kCN_CRC_32),
72    CN_ITEM(kCN_CRC_32_CASTAGNOLI),
73    CN_ITEM(kCN_CRC_32_BZIP2),
74    CN_ITEM(kCN_CRC_32_MPEG_2),
75    CN_ITEM(kCN_CRC_32_POSIX),
76    CN_ITEM(kCN_CRC_32_XFER),
77    CN_ITEM(kCN_CRC_64_ECMA_182)
78};
79
80static cnItem basexxMap[] = {
81    CN_ITEM(kCNEncodingBase64),
82    CN_ITEM(kCNEncodingBase32),
83    CN_ITEM(kCNEncodingBase32Recovery),
84    CN_ITEM(kCNEncodingBase32HEX),
85    CN_ITEM(kCNEncodingBase16),
86};
87
88enum {
89    cmdOpCRC = 1,
90    cmdOpEncode,
91    cmdOpDecode
92};
93typedef uint32_t cmdOp; //operation
94
95typedef struct _cnCmd
96{
97    const char * name;
98    cmdOp op;
99    const char * options;
100    const char * description;
101    const char * usage;
102    uint32_t    algDefault;
103} cnCmd, *cnCmdPtr;
104
105typedef struct _cnContext
106{
107    cnCmdPtr    cmd;
108    FILE        *out_file;
109    int         fd;
110    const char  *file;
111    const char  **files;
112    uint32_t    filesCount;
113    const char  *string;
114    uint32_t    alg;
115    bool        showDecimal;
116    bool        dumpTable;
117    int         pageSize;
118    int         width;
119    uint64_t    totalBytes;
120    bool        verbose;
121} cnContext, *cnContextPtr;
122
123static cnCmd cmdMap[] =
124{
125    {   .name = "crc",
126        .op = cmdOpCRC,
127        .options = "a:ds:Th?v",
128        .description = "Generate a checksum CRC",
129        .usage =    "[file ...]\n",
130        .algDefault = kCN_CRC_64_ECMA_182,
131    },
132    {   .name = "encode",
133        .op = cmdOpEncode,
134        .options = "a:s:h?v",
135        .description = "Encode using BaseXX representation",
136        .usage = "[file ...]\n",
137        .algDefault = kCNEncodingBase64,
138    },
139    {   .name = "decode",
140        .op = cmdOpDecode,
141        .options = "a:s:h?v",
142        .description = "Decode using BaseXX representation",
143        .usage = "[file ...]\n",
144        .algDefault = kCNEncodingBase64,
145    }
146};
147
148static cnCmdPtr getcmd(const char * str)
149{
150    if (!str)
151        return NULL;
152
153    int num = sizeof(cmdMap)/sizeof(cnCmd);
154    size_t cmdlen = strlen(str);
155    int count = 0;
156    cnCmdPtr foundCmd = NULL;
157    for (int x = 0; x < num; x++) {
158        if (strncasecmp(cmdMap[x].name, str, cmdlen) == 0) {
159            count++;
160            foundCmd = &cmdMap[x];
161        }
162    }
163
164    if (count > 1) {
165        fprintf(stderr, "error: %s is ambiguous found %d matches\n", str, count);
166        return NULL;
167    } else {
168        return foundCmd;
169    }
170}
171
172static uint32_t getalg(cnContextPtr context, const char * name)
173{
174    if (!name)
175        return 0;
176    const cnItem * algMap = NULL;
177    int num;
178
179    switch (context->cmd->op) {
180        case cmdOpCRC:
181            algMap = crcMap;
182            num = sizeof(crcMap)/sizeof(cnItem);
183            break;
184        case cmdOpDecode:
185        case cmdOpEncode:
186            algMap = basexxMap;
187            num = sizeof(basexxMap)/sizeof(cnItem);
188            break;
189        default:
190            return 0;
191    }
192
193    size_t nlen = strlen(name);
194    uint32_t alg = 0;
195    for (int x = 0; x < num; x++) {
196        if (strncasecmp(algMap[x].name, name, nlen) == 0) {
197            alg = algMap[x].alg;
198            break;
199        }
200    }
201
202    return alg;
203}
204
205#define USAGE_SPACE "16"
206
207static void usage(cnContextPtr context)
208{
209    if (!context)
210        return;
211
212    if (context->cmd == NULL) {
213        fprintf(stderr, "usage: cn [command] [opt ...]\n\n");
214        fprintf(stderr, "'cn command -h' for more info\n\n");
215        fprintf(stderr, "commands:\n");
216        int num = sizeof(cmdMap)/sizeof(cnCmd);
217        for (int x = 0; x < num; x++) {
218            if (cmdMap[x].description != NULL) {
219                fprintf(stderr, "  %-20s%-s\n", cmdMap[x].name, cmdMap[x].description);
220            }
221        }
222    } else {
223        cnCmdPtr cmd = context->cmd;
224        char * optBuf = (char*)malloc(strlen(cmd->options));
225        char * cur = optBuf;
226        const char * pos = cmd->options;
227        while (*pos != '\0') {
228            if (*pos != ':' && *pos != '?') {
229                *cur = *pos;
230                cur++;
231            }
232            pos++;
233        }
234        *cur = '\0';
235
236        if (cmd->usage) {
237            fprintf(stderr, "usage: %s [-%s] %s\n", cmd->name, optBuf, cmd->usage);
238        } else {
239            fprintf(stderr, "usage: %s [-%s]\n", cmd->name, optBuf);
240        }
241        free(optBuf);
242
243        const char * ch = cmd->options;
244        while (*ch != '\0') {
245            switch (*ch) {
246                case 'a':
247                    fprintf(stderr, "  %-"USAGE_SPACE"s%-s\n", "-a <num|string>", "Operate with a specific Algorithm");
248                    break;
249                case 'd':
250                    fprintf(stderr, "  %-"USAGE_SPACE"s%-s\n", "-d", "Display CRC in decimal");
251                    break;
252                case 'h':
253                    fprintf(stderr, "  %-"USAGE_SPACE"s%-s\n", "-h, -?", "Show help");
254                    break;
255                case 's':
256                    fprintf(stderr, "  %-"USAGE_SPACE"s%-s\n", "-s <string>", "Operate on a specified string");
257                    break;
258                case 'v':
259                    fprintf(stderr, "  %-"USAGE_SPACE"s%-s\n", "-v", "verbose");
260                    break;
261                default:
262                    break;
263            }
264            ch++;
265        }
266
267        switch (cmd->op) {
268            case cmdOpCRC:
269                {
270                    fprintf(stderr, "\nCRC Algorithms\n");
271                    int c = sizeof(crcMap)/sizeof(cnItem);
272                    for (int x = 0; x < c; x++) {
273                        fprintf(stderr, "%s %-1i - %s\n", crcMap[x].alg == context->alg ? "*" : " ", crcMap[x].alg, crcMap[x].name);
274                    }
275                }
276                break;
277            case cmdOpDecode:
278            case cmdOpEncode:
279                {
280                    fprintf(stderr, "\nBaseXX Algorithms\n");
281                    int c = sizeof(basexxMap)/sizeof(cnItem);
282                    for (int x = 0; x < c; x++) {
283                        fprintf(stderr, "%s %-1i - %s\n", basexxMap[x].alg == context->alg ? "*" : " ", basexxMap[x].alg, basexxMap[x].name);
284                    }
285                }
286                break;
287            default:
288                break;
289        }
290    }
291
292}
293
294static int getEnvInt(const char * str)
295{
296    if (!str)
297        return 0;
298
299    const char * env = getenv(str);
300    return env ? atoi(env) : 0;
301}
302
303static uint32_t parseAlg(cnContextPtr context, const char * str)
304{
305    uint32_t alg = 0;
306
307    if (!str)
308        return 0;
309
310    alg = atoi(str);
311    if (!alg)
312        alg = getalg(context, str);
313    if (!alg)
314        fprintf(stderr, "Algorithm not found %s\n", str);
315
316    return alg;
317}
318
319static bool parseArgs(int argc, const char * argv[], cnContextPtr context)
320{
321    bool result = false;
322    int ch;
323    char * cmd = NULL;
324    int envInt;
325
326    cmd = basename((char*)argv[0]);
327    if (strcasecmp(CN_NAME, cmd) == 0) {
328        cmd = NULL;
329    }
330    require_quiet(cmd || argc > 1, done);
331
332    context->cmd = getcmd(cmd ? cmd : argv[1]);
333    require_quiet(context->cmd != NULL, done);
334
335    if (!cmd) {
336        argc -= 1;
337        argv += 1;
338    }
339
340    context->alg = parseAlg(context, getenv("CN_ALG"));
341
342    if (!context->alg) {
343        context->alg = context->cmd->algDefault;
344    }
345
346    context->out_file = fdopen(STDOUT_FILENO, "a");
347
348    while ((ch = getopt(argc, (char**)argv,context->cmd->options)) != -1) {
349        switch (ch) {
350            case 'a':
351                context->alg = parseAlg(context, optarg);
352                break;
353            case 'd':
354                context->showDecimal = true;
355                break;
356            case 'T':
357                context->dumpTable = true;
358                context->string = "";
359                break;
360            case 's':
361                context->string = optarg;
362                break;
363            case 'v':
364                context->verbose = true;
365                break;
366            case '?':
367            case 'h':
368            default:
369                goto done;
370                break;
371        }
372    }
373
374    envInt = getEnvInt("CN_READ_SIZE");
375    context->pageSize = envInt ? envInt : getpagesize();
376
377    envInt = getEnvInt("CN_WIDTH");
378    context->width = envInt ? envInt : ENCODE_DEFAULT_WIDTH;
379
380    argc -= optind;
381    argv += optind;
382
383    if (argc > 0) {
384        context->filesCount = argc;
385        context->files = calloc(1u,argc*sizeof(char*)+1);
386        for (int i = 0; i < argc; i++) {
387            context->files[i] = argv[i];
388        }
389    }
390
391    result = true;
392
393done:
394    if (!result) {
395        usage(context);
396    }
397    return result;
398}
399
400static void pcrc(cnContextPtr context, uint64_t crc)
401{
402    if (context->showDecimal) {
403        fprintf(context->out_file, "%llu", crc);
404    } else {
405        fprintf(context->out_file, "%llx", crc);
406    }
407    PRINT(" %llu", context->totalBytes);
408    if (context->file)
409        PRINT(" %s", context->file);
410    fprintf(context->out_file, "\n");
411}
412
413static CNStatus crcOp(cnContextPtr context)
414{
415    CNStatus status = kCNSuccess;
416    CNCRCRef crcRef = NULL;
417    uint64_t crc = 0;
418
419    if(context->dumpTable) {
420        status = CNCRCDumpTable(context->alg);
421        require_noerr(status, done);
422        return status;
423    } else  if (context->string) {
424        size_t sLen = strlen(context->string);
425        status = CNCRC(context->alg, context->string, sLen, &crc);
426        require_noerr(status, done);
427
428        context->totalBytes = sLen;
429    } else {
430        status = CNCRCInit(context->alg, &crcRef);
431        require_noerr(status, done);
432
433        char buf[context->pageSize];
434        size_t nr;
435
436        while ((nr = read(context->fd, buf, sizeof(buf))) > 0) {
437            status = CNCRCUpdate(crcRef, buf, nr);
438            require_noerr(status, done);
439
440            context->totalBytes += nr;
441        }
442
443        status = CNCRCFinal(crcRef, &crc);
444        require_noerr(status, done);
445    }
446
447    pcrc(context, crc);
448
449done:
450    if (crcRef) {
451        CNCRCRelease(crcRef);
452    }
453    return status;
454}
455
456static void pbase(cnContextPtr context, CNEncodingDirection direction, uint8_t * buf, size_t buf_len)
457{
458    switch(direction) {
459        case kCNEncode:
460            {
461                size_t pos = 0, size;
462                while(buf_len) {
463                    size = buf_len > (size_t) context->width ? context->width : buf_len;
464                    fwrite(&buf[pos], size, 1, context->out_file);
465                    fprintf(context->out_file, "\n");
466                    buf_len -= size;
467                    pos += size;
468                }
469            }
470            break;
471
472        case kCNDecode:
473            fwrite(buf, buf_len, 1, context->out_file);
474            fflush(context->out_file);
475            break;
476        default:
477            break;
478    }
479}
480
481static void _getBlockReadSize(cnContextPtr context, size_t * readSize, size_t * encodeSize)
482{
483    size_t encode, in, out;
484
485    CNEncoderBlocksize(context->alg, &in, &out);
486
487    encode = (context->pageSize / in) * out;
488    encode = encode - (encode % context->width);
489    *readSize = (encode / out) * in;
490    *encodeSize = encode;
491}
492
493static void _sanitizeBuf(uint8_t * buf, size_t bufLen, uint8_t * outBuf, size_t * outLen)
494{
495    uint8_t * cur = outBuf;
496    size_t len = bufLen;
497    for (size_t i = 0; i < bufLen; i++) {
498        if (buf[i] != '\n') {
499            *cur = buf[i];
500            cur++;
501        } else {
502            len--;
503        }
504    }
505    *outLen = len;
506}
507
508static CNStatus basexxOp(cnContextPtr context)
509{
510    CNStatus status = kCNSuccess;
511    CNEncoderRef encoder = NULL;
512    uint8_t * encodeBuf = NULL;
513    uint8_t * sanitizedBuf = NULL;
514    size_t encodedSize = 0;
515    CNEncodingDirection direction = context->cmd->op == cmdOpEncode ? kCNEncode : kCNDecode;
516
517    if (context->string) {
518        size_t sLen = strlen(context->string);
519        encodedSize = CNEncoderGetOutputLengthFromEncoding(context->alg, direction, sLen);
520
521        encodeBuf = calloc(1u, encodedSize);
522        require(encodeBuf != NULL, done);
523
524        CNEncode(context->alg, direction, context->string, sLen, encodeBuf, &encodedSize);
525        require_noerr_action(status, done, status = kCNDecodeError);
526
527        pbase(context, direction, encodeBuf, encodedSize);
528
529        context->totalBytes = sLen;
530    } else {
531        uint8_t readBuf[context->pageSize];
532        size_t bytesRead;
533        size_t pos;
534        size_t encodeLen = 0;
535
536        status = CNEncoderCreate(context->alg, direction, &encoder);
537        require_noerr_action(status, done, status = kCNDecodeError);
538
539        size_t readBufSize = context->pageSize, expectedSize = 0;
540        if (direction == kCNEncode) {
541            _getBlockReadSize(context, &readBufSize, &expectedSize); // align reads to context->width
542            assert(readBufSize <= (size_t) context->pageSize);
543        }
544
545        encodeLen = CNEncoderGetOutputLength(encoder, readBufSize);
546        encodeBuf = calloc(1u, encodeLen);
547        require(encodeBuf != NULL, done);
548
549        while ((bytesRead = read(context->fd, readBuf, readBufSize)) > 0) {
550            encodedSize = encodeLen;
551
552            if (direction == kCNDecode) {
553                if (!sanitizedBuf) {
554                    sanitizedBuf = calloc(1u, context->pageSize);
555                }
556                size_t sanitizedSize = 0;
557                _sanitizeBuf(readBuf, bytesRead, sanitizedBuf, &sanitizedSize);
558                status = CNEncoderUpdate(encoder, sanitizedBuf, sanitizedSize, encodeBuf, &encodedSize);
559                expectedSize = encodedSize;
560            } else {
561                status = CNEncoderUpdate(encoder, readBuf, bytesRead, encodeBuf, &encodedSize);
562            }
563            require_noerr_action(status, done, status = kCNDecodeError);
564
565            // during encode only print if we got the full expected encode size (we might need to add padding)
566            // during decode print all decoded bytes
567            if (encodedSize == expectedSize) {
568                pbase(context, direction, encodeBuf, encodedSize);
569            }
570
571            context->totalBytes += bytesRead;
572        }
573
574        pos = encodedSize;
575        encodedSize = encodeLen - pos;
576
577        status = CNEncoderFinal(encoder, &encodeBuf[pos], &encodedSize);
578        require_noerr_action(status, done, status = kCNDecodeError);
579
580        // If we had to add padding or didn't print the full buffer from above do so now
581        if (direction == kCNEncode && (encodedSize || pos < expectedSize)) {
582            size_t left = pos < expectedSize ?  pos + encodedSize : encodedSize;
583            // if pos < expectedSize start from 0 else just print the padding from pos
584            pbase(context, direction, &encodeBuf[pos < expectedSize ? 0 : pos], left);
585        }
586    }
587
588    PRINT("\n%llu", context->totalBytes);
589    if (context->file)
590        PRINT(" %s", context->file);
591    PRINT("\n");
592
593done:
594    if (encoder) {
595        CNEncoderRelease(&encoder);
596    }
597    if (encodeBuf) {
598        free(encodeBuf);
599    }
600    if (sanitizedBuf) {
601        free(sanitizedBuf);
602    }
603
604    return status;
605}
606
607static void cnContextFree(cnContextPtr context) {
608    if (context->files) {
609        free(context->files);
610    }
611    if (context->out_file) {
612        fclose(context->out_file);
613    }
614
615    free(context);
616}
617
618typedef CNStatus (*op_f)(cnContextPtr context);
619
620int main(int argc, const char * argv[])
621{
622    int rc = kCNSuccess;
623    op_f op = NULL;
624
625    cnContextPtr context = calloc(1u, sizeof(cnContext));
626
627    context->fd = STDIN_FILENO;
628
629    require_action_quiet(parseArgs(argc, argv, context) == true, done, rc = kCNParamError);
630
631    switch (context->cmd->op) {
632        case cmdOpCRC:
633            op = crcOp;
634            break;
635        case cmdOpEncode:
636        case cmdOpDecode:
637            op = basexxOp;
638            break;
639        default:
640            break;
641    }
642
643    require_action(op != NULL, done, rc = kCNParamError);
644
645    const char ** pos = context->files;
646    do {
647        context->totalBytes = 0;
648
649        if (!context->string && pos) {
650            context->file = *pos++;
651
652            if ((context->fd = open(context->file, O_RDONLY, 0)) < 0) {
653                fprintf(stderr, "failed to open %s\n", context->file);
654                rc = kCNFailure;
655                continue;
656            }
657        }
658        if (op(context) != kCNSuccess) {
659            rc = kCNFailure;
660        }
661        close(context->fd);
662
663    } while (!context->string && pos && *pos);
664
665
666done:
667    cnContextFree(context);
668    return rc;
669}
670
671