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