/* * Copyright (c) 2011 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CN_NAME "cn" #define ENCODE_DEFAULT_WIDTH 64 #define PRINT(...) if (context->verbose) { fprintf(context->out_file, __VA_ARGS__); } #define CN_ITEM(item) { #item, item } typedef struct _cnItem { const char *name; uint32_t alg; } cnItem; static cnItem crcMap[] = { CN_ITEM(kCN_CRC_8), CN_ITEM(kCN_CRC_8_ICODE), CN_ITEM(kCN_CRC_8_ITU), CN_ITEM(kCN_CRC_8_ROHC), CN_ITEM(kCN_CRC_8_WCDMA), CN_ITEM(kCN_CRC_16), CN_ITEM(kCN_CRC_16_CCITT_TRUE), CN_ITEM(kCN_CRC_16_CCITT_FALSE), CN_ITEM(kCN_CRC_16_USB), CN_ITEM(kCN_CRC_16_XMODEM), CN_ITEM(kCN_CRC_16_DECT_R), CN_ITEM(kCN_CRC_16_DECT_X), CN_ITEM(kCN_CRC_16_ICODE), CN_ITEM(kCN_CRC_16_VERIFONE), CN_ITEM(kCN_CRC_16_A), CN_ITEM(kCN_CRC_16_B), CN_ITEM(kCN_CRC_16_Fletcher), CN_ITEM(kCN_CRC_32_Adler), CN_ITEM(kCN_CRC_32), CN_ITEM(kCN_CRC_32_CASTAGNOLI), CN_ITEM(kCN_CRC_32_BZIP2), CN_ITEM(kCN_CRC_32_MPEG_2), CN_ITEM(kCN_CRC_32_POSIX), CN_ITEM(kCN_CRC_32_XFER), CN_ITEM(kCN_CRC_64_ECMA_182) }; static cnItem basexxMap[] = { CN_ITEM(kCNEncodingBase64), CN_ITEM(kCNEncodingBase32), CN_ITEM(kCNEncodingBase32Recovery), CN_ITEM(kCNEncodingBase32HEX), CN_ITEM(kCNEncodingBase16), }; enum { cmdOpCRC = 1, cmdOpEncode, cmdOpDecode }; typedef uint32_t cmdOp; //operation typedef struct _cnCmd { const char * name; cmdOp op; const char * options; const char * description; const char * usage; uint32_t algDefault; } cnCmd, *cnCmdPtr; typedef struct _cnContext { cnCmdPtr cmd; FILE *out_file; int fd; const char *file; const char **files; uint32_t filesCount; const char *string; uint32_t alg; bool showDecimal; bool dumpTable; int pageSize; int width; uint64_t totalBytes; bool verbose; } cnContext, *cnContextPtr; static cnCmd cmdMap[] = { { .name = "crc", .op = cmdOpCRC, .options = "a:ds:Th?v", .description = "Generate a checksum CRC", .usage = "[file ...]\n", .algDefault = kCN_CRC_64_ECMA_182, }, { .name = "encode", .op = cmdOpEncode, .options = "a:s:h?v", .description = "Encode using BaseXX representation", .usage = "[file ...]\n", .algDefault = kCNEncodingBase64, }, { .name = "decode", .op = cmdOpDecode, .options = "a:s:h?v", .description = "Decode using BaseXX representation", .usage = "[file ...]\n", .algDefault = kCNEncodingBase64, } }; static cnCmdPtr getcmd(const char * str) { if (!str) return NULL; int num = sizeof(cmdMap)/sizeof(cnCmd); size_t cmdlen = strlen(str); int count = 0; cnCmdPtr foundCmd = NULL; for (int x = 0; x < num; x++) { if (strncasecmp(cmdMap[x].name, str, cmdlen) == 0) { count++; foundCmd = &cmdMap[x]; } } if (count > 1) { fprintf(stderr, "error: %s is ambiguous found %d matches\n", str, count); return NULL; } else { return foundCmd; } } static uint32_t getalg(cnContextPtr context, const char * name) { if (!name) return 0; const cnItem * algMap = NULL; int num; switch (context->cmd->op) { case cmdOpCRC: algMap = crcMap; num = sizeof(crcMap)/sizeof(cnItem); break; case cmdOpDecode: case cmdOpEncode: algMap = basexxMap; num = sizeof(basexxMap)/sizeof(cnItem); break; default: return 0; } size_t nlen = strlen(name); uint32_t alg = 0; for (int x = 0; x < num; x++) { if (strncasecmp(algMap[x].name, name, nlen) == 0) { alg = algMap[x].alg; break; } } return alg; } #define USAGE_SPACE "16" static void usage(cnContextPtr context) { if (!context) return; if (context->cmd == NULL) { fprintf(stderr, "usage: cn [command] [opt ...]\n\n"); fprintf(stderr, "'cn command -h' for more info\n\n"); fprintf(stderr, "commands:\n"); int num = sizeof(cmdMap)/sizeof(cnCmd); for (int x = 0; x < num; x++) { if (cmdMap[x].description != NULL) { fprintf(stderr, " %-20s%-s\n", cmdMap[x].name, cmdMap[x].description); } } } else { cnCmdPtr cmd = context->cmd; char * optBuf = (char*)malloc(strlen(cmd->options)); char * cur = optBuf; const char * pos = cmd->options; while (*pos != '\0') { if (*pos != ':' && *pos != '?') { *cur = *pos; cur++; } pos++; } *cur = '\0'; if (cmd->usage) { fprintf(stderr, "usage: %s [-%s] %s\n", cmd->name, optBuf, cmd->usage); } else { fprintf(stderr, "usage: %s [-%s]\n", cmd->name, optBuf); } free(optBuf); const char * ch = cmd->options; while (*ch != '\0') { switch (*ch) { case 'a': fprintf(stderr, " %-"USAGE_SPACE"s%-s\n", "-a ", "Operate with a specific Algorithm"); break; case 'd': fprintf(stderr, " %-"USAGE_SPACE"s%-s\n", "-d", "Display CRC in decimal"); break; case 'h': fprintf(stderr, " %-"USAGE_SPACE"s%-s\n", "-h, -?", "Show help"); break; case 's': fprintf(stderr, " %-"USAGE_SPACE"s%-s\n", "-s ", "Operate on a specified string"); break; case 'v': fprintf(stderr, " %-"USAGE_SPACE"s%-s\n", "-v", "verbose"); break; default: break; } ch++; } switch (cmd->op) { case cmdOpCRC: { fprintf(stderr, "\nCRC Algorithms\n"); int c = sizeof(crcMap)/sizeof(cnItem); for (int x = 0; x < c; x++) { fprintf(stderr, "%s %-1i - %s\n", crcMap[x].alg == context->alg ? "*" : " ", crcMap[x].alg, crcMap[x].name); } } break; case cmdOpDecode: case cmdOpEncode: { fprintf(stderr, "\nBaseXX Algorithms\n"); int c = sizeof(basexxMap)/sizeof(cnItem); for (int x = 0; x < c; x++) { fprintf(stderr, "%s %-1i - %s\n", basexxMap[x].alg == context->alg ? "*" : " ", basexxMap[x].alg, basexxMap[x].name); } } break; default: break; } } } static int getEnvInt(const char * str) { if (!str) return 0; const char * env = getenv(str); return env ? atoi(env) : 0; } static uint32_t parseAlg(cnContextPtr context, const char * str) { uint32_t alg = 0; if (!str) return 0; alg = atoi(str); if (!alg) alg = getalg(context, str); if (!alg) fprintf(stderr, "Algorithm not found %s\n", str); return alg; } static bool parseArgs(int argc, const char * argv[], cnContextPtr context) { bool result = false; int ch; char * cmd = NULL; int envInt; cmd = basename((char*)argv[0]); if (strcasecmp(CN_NAME, cmd) == 0) { cmd = NULL; } require_quiet(cmd || argc > 1, done); context->cmd = getcmd(cmd ? cmd : argv[1]); require_quiet(context->cmd != NULL, done); if (!cmd) { argc -= 1; argv += 1; } context->alg = parseAlg(context, getenv("CN_ALG")); if (!context->alg) { context->alg = context->cmd->algDefault; } context->out_file = fdopen(STDOUT_FILENO, "a"); while ((ch = getopt(argc, (char**)argv,context->cmd->options)) != -1) { switch (ch) { case 'a': context->alg = parseAlg(context, optarg); break; case 'd': context->showDecimal = true; break; case 'T': context->dumpTable = true; context->string = ""; break; case 's': context->string = optarg; break; case 'v': context->verbose = true; break; case '?': case 'h': default: goto done; break; } } envInt = getEnvInt("CN_READ_SIZE"); context->pageSize = envInt ? envInt : getpagesize(); envInt = getEnvInt("CN_WIDTH"); context->width = envInt ? envInt : ENCODE_DEFAULT_WIDTH; argc -= optind; argv += optind; if (argc > 0) { context->filesCount = argc; context->files = calloc(1u,argc*sizeof(char*)+1); for (int i = 0; i < argc; i++) { context->files[i] = argv[i]; } } result = true; done: if (!result) { usage(context); } return result; } static void pcrc(cnContextPtr context, uint64_t crc) { if (context->showDecimal) { fprintf(context->out_file, "%llu", crc); } else { fprintf(context->out_file, "%llx", crc); } PRINT(" %llu", context->totalBytes); if (context->file) PRINT(" %s", context->file); fprintf(context->out_file, "\n"); } static CNStatus crcOp(cnContextPtr context) { CNStatus status = kCNSuccess; CNCRCRef crcRef = NULL; uint64_t crc = 0; if(context->dumpTable) { status = CNCRCDumpTable(context->alg); require_noerr(status, done); return status; } else if (context->string) { size_t sLen = strlen(context->string); status = CNCRC(context->alg, context->string, sLen, &crc); require_noerr(status, done); context->totalBytes = sLen; } else { status = CNCRCInit(context->alg, &crcRef); require_noerr(status, done); char buf[context->pageSize]; size_t nr; while ((nr = read(context->fd, buf, sizeof(buf))) > 0) { status = CNCRCUpdate(crcRef, buf, nr); require_noerr(status, done); context->totalBytes += nr; } status = CNCRCFinal(crcRef, &crc); require_noerr(status, done); } pcrc(context, crc); done: if (crcRef) { CNCRCRelease(crcRef); } return status; } static void pbase(cnContextPtr context, CNEncodingDirection direction, uint8_t * buf, size_t buf_len) { switch(direction) { case kCNEncode: { size_t pos = 0, size; while(buf_len) { size = buf_len > (size_t) context->width ? context->width : buf_len; fwrite(&buf[pos], size, 1, context->out_file); fprintf(context->out_file, "\n"); buf_len -= size; pos += size; } } break; case kCNDecode: fwrite(buf, buf_len, 1, context->out_file); fflush(context->out_file); break; default: break; } } static void _getBlockReadSize(cnContextPtr context, size_t * readSize, size_t * encodeSize) { size_t encode, in, out; CNEncoderBlocksize(context->alg, &in, &out); encode = (context->pageSize / in) * out; encode = encode - (encode % context->width); *readSize = (encode / out) * in; *encodeSize = encode; } static void _sanitizeBuf(uint8_t * buf, size_t bufLen, uint8_t * outBuf, size_t * outLen) { uint8_t * cur = outBuf; size_t len = bufLen; for (size_t i = 0; i < bufLen; i++) { if (buf[i] != '\n') { *cur = buf[i]; cur++; } else { len--; } } *outLen = len; } static CNStatus basexxOp(cnContextPtr context) { CNStatus status = kCNSuccess; CNEncoderRef encoder = NULL; uint8_t * encodeBuf = NULL; uint8_t * sanitizedBuf = NULL; size_t encodedSize = 0; CNEncodingDirection direction = context->cmd->op == cmdOpEncode ? kCNEncode : kCNDecode; if (context->string) { size_t sLen = strlen(context->string); encodedSize = CNEncoderGetOutputLengthFromEncoding(context->alg, direction, sLen); encodeBuf = calloc(1u, encodedSize); require(encodeBuf != NULL, done); CNEncode(context->alg, direction, context->string, sLen, encodeBuf, &encodedSize); require_noerr_action(status, done, status = kCNDecodeError); pbase(context, direction, encodeBuf, encodedSize); context->totalBytes = sLen; } else { uint8_t readBuf[context->pageSize]; size_t bytesRead; size_t pos; size_t encodeLen = 0; status = CNEncoderCreate(context->alg, direction, &encoder); require_noerr_action(status, done, status = kCNDecodeError); size_t readBufSize = context->pageSize, expectedSize = 0; if (direction == kCNEncode) { _getBlockReadSize(context, &readBufSize, &expectedSize); // align reads to context->width assert(readBufSize <= (size_t) context->pageSize); } encodeLen = CNEncoderGetOutputLength(encoder, readBufSize); encodeBuf = calloc(1u, encodeLen); require(encodeBuf != NULL, done); while ((bytesRead = read(context->fd, readBuf, readBufSize)) > 0) { encodedSize = encodeLen; if (direction == kCNDecode) { if (!sanitizedBuf) { sanitizedBuf = calloc(1u, context->pageSize); } size_t sanitizedSize = 0; _sanitizeBuf(readBuf, bytesRead, sanitizedBuf, &sanitizedSize); status = CNEncoderUpdate(encoder, sanitizedBuf, sanitizedSize, encodeBuf, &encodedSize); expectedSize = encodedSize; } else { status = CNEncoderUpdate(encoder, readBuf, bytesRead, encodeBuf, &encodedSize); } require_noerr_action(status, done, status = kCNDecodeError); // during encode only print if we got the full expected encode size (we might need to add padding) // during decode print all decoded bytes if (encodedSize == expectedSize) { pbase(context, direction, encodeBuf, encodedSize); } context->totalBytes += bytesRead; } pos = encodedSize; encodedSize = encodeLen - pos; status = CNEncoderFinal(encoder, &encodeBuf[pos], &encodedSize); require_noerr_action(status, done, status = kCNDecodeError); // If we had to add padding or didn't print the full buffer from above do so now if (direction == kCNEncode && (encodedSize || pos < expectedSize)) { size_t left = pos < expectedSize ? pos + encodedSize : encodedSize; // if pos < expectedSize start from 0 else just print the padding from pos pbase(context, direction, &encodeBuf[pos < expectedSize ? 0 : pos], left); } } PRINT("\n%llu", context->totalBytes); if (context->file) PRINT(" %s", context->file); PRINT("\n"); done: if (encoder) { CNEncoderRelease(&encoder); } if (encodeBuf) { free(encodeBuf); } if (sanitizedBuf) { free(sanitizedBuf); } return status; } static void cnContextFree(cnContextPtr context) { if (context->files) { free(context->files); } if (context->out_file) { fclose(context->out_file); } free(context); } typedef CNStatus (*op_f)(cnContextPtr context); int main(int argc, const char * argv[]) { int rc = kCNSuccess; op_f op = NULL; cnContextPtr context = calloc(1u, sizeof(cnContext)); context->fd = STDIN_FILENO; require_action_quiet(parseArgs(argc, argv, context) == true, done, rc = kCNParamError); switch (context->cmd->op) { case cmdOpCRC: op = crcOp; break; case cmdOpEncode: case cmdOpDecode: op = basexxOp; break; default: break; } require_action(op != NULL, done, rc = kCNParamError); const char ** pos = context->files; do { context->totalBytes = 0; if (!context->string && pos) { context->file = *pos++; if ((context->fd = open(context->file, O_RDONLY, 0)) < 0) { fprintf(stderr, "failed to open %s\n", context->file); rc = kCNFailure; continue; } } if (op(context) != kCNSuccess) { rc = kCNFailure; } close(context->fd); } while (!context->string && pos && *pos); done: cnContextFree(context); return rc; }