* This file is part of the openHiTLS project.
*
* openHiTLS is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "app_opt.h"
#include <stdint.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <sys/stat.h>
#include <stdbool.h>
#include "app_errno.h"
#include "bsl_sal.h"
#include "app_print.h"
#include "bsl_uio.h"
#include "bsl_errno.h"
#include "bsl_base64.h"
#define MAX_HITLS_APP_OPT_NAME_WIDTH 40
#define MAX_HITLS_APP_OPT_LINE_WIDTH 80
typedef struct {
int32_t optIndex;
int32_t argc;
char *valueStr;
char progName[128];
char **argv;
const HITLS_CmdOption *opts;
} HITLS_CmdOptState;
static HITLS_CmdOptState g_cmdOptState = {0};
static const HITLS_CmdOption *g_unKnownOpt = NULL;
static char *g_unKownName = NULL;
static int32_t AppIsDir(const char *path)
{
struct stat st = {0};
if (path == NULL) {
return HITLS_APP_OPT_VALUE_INVALID;
}
if (stat(path, &st) != 0) {
return HITLS_APP_INTERNAL_EXCEPTION;
}
if (S_ISDIR(st.st_mode)) {
return HITLS_APP_SUCCESS;
}
return HITLS_APP_OPT_VALUE_INVALID;
}
const char *HITLS_APP_OptGetUnKownOptName(void)
{
return g_unKownName;
}
static void GetProgName(const char *filePath)
{
const char *p = NULL;
for (p = filePath + strlen(filePath); --p > filePath;) {
if (*p == '/') {
p++;
break;
}
}
if (p != g_cmdOptState.progName) {
strncpy(g_cmdOptState.progName, p, sizeof(g_cmdOptState.progName) - 1);
}
g_cmdOptState.progName[sizeof(g_cmdOptState.progName) - 1] = '\0';
}
static void CmdOptStateInit(int32_t index, int32_t argc, char **argv, const HITLS_CmdOption *opts)
{
g_cmdOptState.optIndex = index;
g_cmdOptState.argc = argc;
g_cmdOptState.argv = argv;
g_cmdOptState.opts = opts;
memset(g_cmdOptState.progName, 0, sizeof(g_cmdOptState.progName));
}
static void CmdOptStateClear(void)
{
g_cmdOptState.optIndex = 0;
g_cmdOptState.argc = 0;
g_cmdOptState.argv = NULL;
g_cmdOptState.opts = NULL;
memset(g_cmdOptState.progName, 0, sizeof(g_cmdOptState.progName));
}
char *HITLS_APP_GetProgName(void)
{
return g_cmdOptState.progName;
}
int32_t HITLS_APP_OptBegin(int32_t argc, char **argv, const HITLS_CmdOption *opts)
{
if (argc == 0 || argv == NULL || opts == NULL) {
AppPrintError("incorrect command \n");
return HITLS_APP_OPT_UNKOWN;
}
CmdOptStateInit(1, argc, argv, opts);
GetProgName(argv[0]);
g_unKnownOpt = NULL;
const HITLS_CmdOption *opt = opts;
for (; opt->name != NULL; ++opt) {
if ((strlen(opt->name) == 0) && (opt->valueType == HITLS_APP_OPT_VALUETYPE_NO_VALUE)) {
g_unKnownOpt = opt;
} else if ((strlen(opt->name) == 0) || (opt->name[0] == '-')) {
AppPrintError("Invalid optname %s \n", opt->name);
return HITLS_APP_OPT_NAME_INVALID;
}
if (opt->valueType <= HITLS_APP_OPT_VALUETYPE_NONE || opt->valueType >= HITLS_APP_OPT_VALUETYPE_MAX) {
return HITLS_APP_OPT_VALUETYPE_INVALID;
}
if (opt->valueType == HITLS_APP_OPT_VALUETYPE_PARAMTERS && opt->optType != HITLS_APP_OPT_PARAM) {
return HITLS_APP_OPT_TYPE_INVALID;
}
for (const HITLS_CmdOption *nextOpt = opt + 1; nextOpt->name != NULL; ++nextOpt) {
if (strcmp(opt->name, nextOpt->name) == 0) {
AppPrintError("Invalid duplicate name : %s\n", opt->name);
return HITLS_APP_OPT_NAME_INVALID;
}
}
}
return HITLS_APP_SUCCESS;
}
char *HITLS_APP_OptGetValueStr(void)
{
return g_cmdOptState.valueStr;
}
int32_t HITLS_APP_OptGetLong(const char *valueS, long *valueL)
{
char *endPtr = NULL;
errno = 0;
long l = strtol(valueS, &endPtr, 0);
if (strlen(endPtr) > 0 || endPtr == valueS || (l == LONG_MAX || l == LONG_MIN) || errno == ERANGE ||
(l == 0 && errno != 0)) {
AppPrintError("The parameter: %s is not a number or out of range\n", valueS);
return HITLS_APP_OPT_VALUE_INVALID;
}
*valueL = l;
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_OptGetInt(const char *valueS, int32_t *valueI)
{
long valueL = 0;
if (HITLS_APP_OptGetLong(valueS, &valueL) != HITLS_APP_SUCCESS) {
return HITLS_APP_OPT_VALUE_INVALID;
}
*valueI = (int32_t)valueL;
if ((long)(*valueI) != valueL) {
AppPrintError("The number %ld out the int bound \n", valueL);
return HITLS_APP_OPT_VALUE_INVALID;
}
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_OptGetUint32(const char *valueS, uint32_t *valueU)
{
long valueL = 0;
if (HITLS_APP_OptGetLong(valueS, &valueL) != HITLS_APP_SUCCESS) {
return HITLS_APP_OPT_VALUE_INVALID;
}
*valueU = (uint32_t)valueL;
if ((long)(*valueU) != valueL) {
AppPrintError("The number %ld out the int bound \n", valueL);
return HITLS_APP_OPT_VALUE_INVALID;
}
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_OptGetFormatType(const char *valueS, HITLS_ValueType type, BSL_ParseFormat *formatType)
{
if (type != HITLS_APP_OPT_VALUETYPE_FMT_PEMDER && type != HITLS_APP_OPT_VALUETYPE_FMT_ANY) {
AppPrintError("Invalid Format Type\n");
return HITLS_APP_OPT_VALUE_INVALID;
}
if (strcasecmp(valueS, "DER") == 0) {
*formatType = BSL_FORMAT_ASN1;
return HITLS_APP_SUCCESS;
} else if (strcasecmp(valueS, "PEM") == 0) {
*formatType = BSL_FORMAT_PEM;
return HITLS_APP_SUCCESS;
}
AppPrintError("Invalid format \"%s\".\n", valueS);
return HITLS_APP_OPT_VALUE_INVALID;
}
int32_t HITLS_APP_GetRestOptNum(void)
{
return g_cmdOptState.argc - g_cmdOptState.optIndex;
}
char **HITLS_APP_GetRestOpt(void)
{
return &g_cmdOptState.argv[g_cmdOptState.optIndex];
}
static int32_t ClassifyByValue(HITLS_ValueType value)
{
switch (value) {
case HITLS_APP_OPT_VALUETYPE_IN_FILE:
case HITLS_APP_OPT_VALUETYPE_OUT_FILE:
case HITLS_APP_OPT_VALUETYPE_STRING:
case HITLS_APP_OPT_VALUETYPE_PARAMTERS:
return HITLS_APP_OPT_VALUECLASS_STR;
case HITLS_APP_OPT_VALUETYPE_DIR:
return HITLS_APP_OPT_VALUECLASS_DIR;
case HITLS_APP_OPT_VALUETYPE_INT:
case HITLS_APP_OPT_VALUETYPE_UINT:
case HITLS_APP_OPT_VALUETYPE_POSITIVE_INT:
return HITLS_APP_OPT_VALUECLASS_INT;
case HITLS_APP_OPT_VALUETYPE_LONG:
case HITLS_APP_OPT_VALUETYPE_ULONG:
return HITLS_APP_OPT_VALUECLASS_LONG;
case HITLS_APP_OPT_VALUETYPE_FMT_PEMDER:
case HITLS_APP_OPT_VALUETYPE_FMT_ANY:
return HITLS_APP_OPT_VALUECLASS_FMT;
default:
return HITLS_APP_OPT_VALUECLASS_NO_VALUE;
}
return HITLS_APP_OPT_VALUECLASS_NONE;
}
static int32_t CheckOptValueType(const HITLS_CmdOption *opt, const char *valStr)
{
int32_t valueClass = ClassifyByValue(opt->valueType);
switch (valueClass) {
case HITLS_APP_OPT_VALUECLASS_STR:
break;
case HITLS_APP_OPT_VALUECLASS_DIR: {
if (AppIsDir(valStr) != HITLS_APP_SUCCESS) {
AppPrintError("%s: Invalid dir \"%s\" for -%s\n", g_cmdOptState.progName, valStr, opt->name);
return HITLS_APP_OPT_VALUE_INVALID;
}
break;
}
case HITLS_APP_OPT_VALUECLASS_INT: {
int32_t valueI = 0;
if (HITLS_APP_OptGetInt(valStr, &valueI) != HITLS_APP_SUCCESS ||
(opt->valueType == HITLS_APP_OPT_VALUETYPE_UINT && valueI < 0) ||
(opt->valueType == HITLS_APP_OPT_VALUETYPE_POSITIVE_INT && valueI < 0)) {
AppPrintError("%s: Invalid number \"%s\" for -%s\n", g_cmdOptState.progName, valStr, opt->name);
return HITLS_APP_OPT_VALUE_INVALID;
}
break;
}
case HITLS_APP_OPT_VALUECLASS_LONG: {
long valueL = 0;
if (HITLS_APP_OptGetLong(valStr, &valueL) != HITLS_APP_SUCCESS ||
(opt->valueType == HITLS_APP_OPT_VALUETYPE_LONG && valueL < 0)) {
AppPrintError("%s: Invalid number \"%s\" for -%s\n", g_cmdOptState.progName, valStr, opt->name);
return HITLS_APP_OPT_VALUE_INVALID;
}
break;
}
case HITLS_APP_OPT_VALUECLASS_FMT: {
BSL_ParseFormat formatType = 0;
if (HITLS_APP_OptGetFormatType(valStr, opt->valueType, &formatType) != HITLS_APP_SUCCESS) {
AppPrintError("%s: Invalid format \"%s\" for -%s\n", g_cmdOptState.progName, valStr, opt->name);
return HITLS_APP_OPT_VALUE_INVALID;
}
break;
}
default:
AppPrintError("%s: Invalid arg \"%s\" for -%s\n", g_cmdOptState.progName, valStr, opt->name);
return HITLS_APP_OPT_VALUE_INVALID;
}
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_OptNext(void)
{
if (g_cmdOptState.optIndex >= g_cmdOptState.argc) {
return HITLS_APP_OPT_EOF;
}
char *optName = g_cmdOptState.argv[g_cmdOptState.optIndex];
if (optName == NULL || *optName != '-') {
return HITLS_APP_OPT_EOF;
}
g_cmdOptState.optIndex++;
if (strcmp(optName, "-") == 0 || strcmp(optName, "--") == 0) {
return HITLS_APP_OPT_ERR;
}
if (*(++optName) == '-') {
optName++;
}
g_cmdOptState.valueStr = strchr(optName, '=');
if (g_cmdOptState.valueStr != NULL) {
return HITLS_APP_OPT_ERR;
}
for (const HITLS_CmdOption *opt = g_cmdOptState.opts; opt->name; ++opt) {
if (strcmp(optName, opt->name) != 0) {
continue;
}
if (opt->valueType == HITLS_APP_OPT_VALUETYPE_NO_VALUE) {
if (g_cmdOptState.valueStr != NULL) {
AppPrintError("%s does not take a value\n", opt->name);
return HITLS_APP_OPT_ERR;
}
return opt->optType;
}
if (g_cmdOptState.valueStr == NULL) {
if (g_cmdOptState.optIndex > g_cmdOptState.argc || g_cmdOptState.argv[g_cmdOptState.optIndex] == NULL) {
AppPrintError("%s needs a value\n", opt->name);
return HITLS_APP_OPT_ERR;
}
g_cmdOptState.valueStr = g_cmdOptState.argv[g_cmdOptState.optIndex];
g_cmdOptState.optIndex++;
}
if (CheckOptValueType(opt, g_cmdOptState.valueStr) != HITLS_APP_SUCCESS) {
return HITLS_APP_OPT_ERR;
}
return opt->optType;
}
if (g_unKnownOpt != NULL) {
g_unKownName = optName;
return g_unKnownOpt->optType;
}
AppPrintError("%s: Unknown option: -%s\n", g_cmdOptState.progName, optName);
return HITLS_APP_OPT_ERR;
}
struct {
HITLS_ValueType type;
char *param;
} g_valTypeParam[] = {
{HITLS_APP_OPT_VALUETYPE_IN_FILE, "infile"},
{HITLS_APP_OPT_VALUETYPE_OUT_FILE, "outfile"},
{HITLS_APP_OPT_VALUETYPE_STRING, "val"},
{HITLS_APP_OPT_VALUETYPE_PARAMTERS, ""},
{HITLS_APP_OPT_VALUETYPE_DIR, "dir"},
{HITLS_APP_OPT_VALUETYPE_INT, "int"},
{HITLS_APP_OPT_VALUETYPE_UINT, "uint"},
{HITLS_APP_OPT_VALUETYPE_POSITIVE_INT, "uint(>0)"},
{HITLS_APP_OPT_VALUETYPE_LONG, "long"},
{HITLS_APP_OPT_VALUETYPE_ULONG, "ulong"},
{HITLS_APP_OPT_VALUETYPE_FMT_PEMDER, "PEM|DER"},
{HITLS_APP_OPT_VALUETYPE_FMT_ANY, "format"}
};
static const char *ValueType2Param(HITLS_ValueType type)
{
for (int i = 0; i < (int)sizeof(g_valTypeParam) / (int)sizeof(g_valTypeParam[0]); i++) {
if (type == g_valTypeParam[i].type)
return g_valTypeParam[i].param;
}
return "";
}
static void OptPrint(const HITLS_CmdOption *opt, int width)
{
const char *help = opt->help ? opt->help : "";
char start[MAX_HITLS_APP_OPT_LINE_WIDTH + 1] = {0};
memset(start, ' ', sizeof(start) - 1);
start[sizeof(start) - 1] = '\0';
int pos = 0;
start[pos++] = ' ';
if (opt->valueType != HITLS_APP_OPT_VALUETYPE_PARAMTERS) {
start[pos++] = '-';
} else {
start[pos++] = '[';
}
if (strlen(opt->name) > 0) {
size_t nameLen = strlen(opt->name);
size_t dstSpace = sizeof(start) - pos;
if (nameLen < dstSpace - 1) {
memcpy(&start[pos], opt->name, nameLen);
start[pos + nameLen] = '\0';
pos += (int)nameLen;
}
memset(&start[pos + 1], ' ', sizeof(start) - 1 - pos - 1);
} else {
start[pos++] = '*';
}
if (opt->valueType == HITLS_APP_OPT_VALUETYPE_PARAMTERS) {
start[pos++] = ']';
}
if (opt->valueType != HITLS_APP_OPT_VALUETYPE_NO_VALUE) {
start[pos++] = ' ';
const char *param = ValueType2Param(opt->valueType);
size_t paramLen = strlen(param);
size_t dstSpace = sizeof(start) - pos;
if (paramLen < dstSpace - 1) {
strncpy(&start[pos], param, dstSpace);
start[pos + paramLen] = '\0';
pos += (int)paramLen;
}
memset(&start[pos + 1], ' ', sizeof(start) - 1 - pos - 1);
}
start[pos++] = ' ';
if (pos >= MAX_HITLS_APP_OPT_NAME_WIDTH) {
start[pos] = '\0';
AppPrintError("%s\n", start);
memset(start, ' ', sizeof(start) - 1);
}
start[width] = '\0';
AppPrintError("%s %s\n", start, help);
}
void HITLS_APP_OptHelpPrint(const HITLS_CmdOption *opts)
{
int width = 5;
int len = 0;
const HITLS_CmdOption *opt;
for (opt = opts; opt->name != NULL; opt++) {
len = 1 + (int)strlen(opt->name) + 1;
if (opt->valueType != HITLS_APP_OPT_VALUETYPE_NO_VALUE) {
len += 1 + strlen(ValueType2Param(opt->valueType));
}
if (len < MAX_HITLS_APP_OPT_NAME_WIDTH && len > width) {
width = len;
}
}
AppPrintError("Usage: %s \n", g_cmdOptState.progName);
for (opt = opts; opt->name != NULL; opt++) {
(void)OptPrint(opt, width);
}
}
void HITLS_APP_OptEnd(void)
{
CmdOptStateClear();
}
BSL_UIO *HITLS_APP_UioOpen(const char *filename, char mode, int32_t flag)
{
if (mode != 'w' && mode != 'r' && mode != 'a') {
AppPrintError("Invalid mode, only support a/w/r\n");
return NULL;
}
if (flag != 0 && flag != 1) {
AppPrintError("Invalid flag, only support 0/1\n");
return NULL;
}
BSL_UIO *uio = BSL_UIO_New(BSL_UIO_FileMethod());
if (uio == NULL) {
return uio;
}
int32_t cmd = 0;
int32_t larg = 0;
void *parg = NULL;
if (filename == NULL) {
cmd = BSL_UIO_FILE_PTR;
larg = 0;
switch (mode) {
case 'w': parg = (void *)stdout;
break;
case 'r': parg = (void *)stdin;
break;
default:
BSL_UIO_Free(uio);
AppPrintError("Only standard I/O is supported\n");
return NULL;
}
} else {
parg = (void *)(uintptr_t)filename;
cmd = BSL_UIO_FILE_OPEN;
switch (mode) {
case 'w': larg = BSL_UIO_FILE_WRITE;
break;
case 'r': larg = BSL_UIO_FILE_READ;
break;
case 'a': larg = BSL_UIO_FILE_APPEND;
break;
default:
BSL_UIO_Free(uio);
AppPrintError("Only standard I/O is supported\n");
return NULL;
}
}
int32_t ctrlRet = BSL_UIO_Ctrl(uio, cmd, larg, parg);
if (ctrlRet != BSL_SUCCESS) {
AppPrintError("Failed to bind the filepath\n");
BSL_UIO_Free(uio);
uio = NULL;
} else if (filename != NULL) {
BSL_UIO_SetIsUnderlyingClosedByUio(uio, flag == 1);
}
return uio;
}
int32_t HITLS_APP_OptToBase64(uint8_t *inBuf, uint32_t inBufLen, char *outBuf, uint32_t outBufLen)
{
if (inBuf == NULL || outBuf == NULL || inBufLen == 0 || outBufLen == 0) {
return HITLS_APP_INTERNAL_EXCEPTION;
}
int32_t encodeRet = BSL_BASE64_Encode(inBuf, inBufLen, outBuf, &outBufLen);
if (encodeRet != BSL_SUCCESS) {
AppPrintError("Failed to convert to Base64 format\n");
return HITLS_APP_ENCODE_FAIL;
}
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_BytesToHex(const uint8_t *bytes, uint32_t bytesLen, char *hexStr, uint32_t hexStrSize)
{
int32_t hexCharSize = 2;
if (bytes == NULL || hexStr == NULL || bytesLen == 0 || hexStrSize < hexCharSize * bytesLen + 1) {
AppPrintError("opt: Invalid input buffer or output buffer.\n");
return HITLS_APP_INTERNAL_EXCEPTION;
}
const char *hexChars = "0123456789abcdef";
size_t pos = 0;
for (size_t i = 0; i < bytesLen; ++i) {
hexStr[pos++] = hexChars[(bytes[i] >> 4) & 0xF];
hexStr[pos++] = hexChars[bytes[i] & 0xF];
}
hexStr[pos] = '\0';
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_OptWriteUio(BSL_UIO *uio, uint8_t *buf, uint32_t bufLen, int32_t format)
{
if (buf == NULL || uio == NULL || bufLen == 0) {
return HITLS_APP_INTERNAL_EXCEPTION;
}
uint32_t outBufLen = 0;
uint32_t writeLen = 0;
switch (format) {
case HITLS_APP_FORMAT_BASE64:
of the data in the Base64 format must be at least (Length of the original data + 2)/3 x 4.
The original data length plus 2 is used to ensure that
the remainder of buflen divided by 3 after rounding down is not lost. */
outBufLen = (bufLen + 2) / 3 * 4;
break;
case HITLS_APP_FORMAT_HEX:
outBufLen = bufLen * 2;
break;
default:
outBufLen = bufLen;
}
char *outBuf = (char *)BSL_SAL_Calloc(outBufLen + 1, 1);
if (outBuf == NULL) {
AppPrintError("Failed to read the UIO content to calloc space\n");
return HITLS_APP_MEM_ALLOC_FAIL;
}
int32_t outRet = HITLS_APP_SUCCESS;
switch (format) {
case HITLS_APP_FORMAT_BASE64:
outRet = HITLS_APP_OptToBase64(buf, bufLen, outBuf, outBufLen + 1);
break;
case HITLS_APP_FORMAT_HEX:
outRet = HITLS_APP_BytesToHex(buf, bufLen, outBuf, outBufLen + 1);
break;
default:
if (bufLen > outBufLen) {
outRet = HITLS_APP_INTERNAL_EXCEPTION;
} else {
memcpy(outBuf, buf, bufLen);
outRet = HITLS_APP_SUCCESS;
}
break;
}
if (outRet != HITLS_APP_SUCCESS) {
BSL_SAL_ClearFree(outBuf, outBufLen + 1);
return outRet;
}
int32_t writeRet = BSL_UIO_Write(uio, outBuf, outBufLen, &writeLen);
BSL_SAL_ClearFree(outBuf, outBufLen + 1);
if (writeRet != BSL_SUCCESS || outBufLen != writeLen) {
AppPrintError("Failed to output the content.\n");
return HITLS_APP_UIO_FAIL;
}
(void)BSL_UIO_Ctrl(uio, BSL_UIO_FLUSH, 0, NULL);
return HITLS_APP_SUCCESS;
}
int32_t HITLS_APP_OptReadUio(BSL_UIO *uio, uint8_t **readBuf, uint64_t *readBufLen, uint64_t maxBufLen)
{
if (uio == NULL) {
return HITLS_APP_INTERNAL_EXCEPTION;
}
int32_t readRet = BSL_UIO_Ctrl(uio, BSL_UIO_PENDING, sizeof(*readBufLen), readBufLen);
if (readRet != BSL_SUCCESS) {
AppPrintError("Failed to obtain the content length\n");
return HITLS_APP_UIO_FAIL;
}
if (*readBufLen == 0 || *readBufLen > maxBufLen || *readBufLen > UINT32_MAX - 1) {
AppPrintError("Invalid content length\n");
return HITLS_APP_UIO_FAIL;
}
uint32_t bufLen = (uint32_t)*readBufLen;
uint8_t *buf = (uint8_t *)BSL_SAL_Calloc(bufLen + 1, 1);
if (buf == NULL) {
AppPrintError("Failed to create the space.\n");
return HITLS_APP_MEM_ALLOC_FAIL;
}
uint32_t readLen = 0;
readRet = BSL_UIO_Read(uio, buf, bufLen, &readLen);
if (readRet != BSL_SUCCESS || bufLen != readLen) {
BSL_SAL_FREE(buf);
AppPrintError("Failed to read UIO content.\n");
return HITLS_APP_UIO_FAIL;
}
buf[bufLen] = '\0';
*readBuf = buf;
return HITLS_APP_SUCCESS;
}