diff --git a/src/include.am b/src/include.am index 776b7c65..8d1b44a2 100644 --- a/src/include.am +++ b/src/include.am @@ -9,6 +9,7 @@ wolfssl_SOURCES = src/clu_main.c \ src/tools/clu_funcs.c \ src/tools/clu_hex_to_bin.c \ src/tools/clu_rand.c \ + src/tools/clu_io.c \ src/crypto/clu_crypto_setup.c \ src/crypto/clu_encrypt.c \ src/crypto/clu_decrypt.c \ diff --git a/src/tools/clu_base64.c b/src/tools/clu_base64.c index 20a9d8b3..1cab67f1 100644 --- a/src/tools/clu_base64.c +++ b/src/tools/clu_base64.c @@ -49,14 +49,15 @@ static void wolfCLU_Base64Help(void) int wolfCLU_Base64Setup(int argc, char** argv) { #if !defined(WOLFCLU_NO_FILESYSTEM) && !defined(NO_CODING) - WOLFSSL_BIO *bioIn = NULL; - WOLFSSL_BIO *bioOut = NULL; + char *inFile = NULL; + char *outFile = NULL; byte* input = NULL; byte* output = NULL; int ret = WOLFCLU_SUCCESS; int decode = 0; int isPEM = 0; - sword32 inputSz = 8000; + /* set by wolfCLU_ReadIo */ + word32 inputSz = 0; word32 outputSz = 0; int option; int longIndex = 1; @@ -75,20 +76,23 @@ int wolfCLU_Base64Setup(int argc, char** argv) break; case WOLFCLU_INFILE: - bioIn = wolfSSL_BIO_new_file(optarg, "rb"); - if (bioIn == NULL) { - wolfCLU_LogError("unable to open file %s", optarg); + if (optarg == NULL) { + wolfCLU_LogError("-in expected a value"); ret = WOLFCLU_FATAL_ERROR; } + else { + inFile = optarg; + } break; case WOLFCLU_OUTFILE: - bioOut = wolfSSL_BIO_new_file(optarg, "wb"); - if (bioOut == NULL) { - wolfCLU_LogError("unable to open output file %s", - optarg); + if (optarg == NULL) { + wolfCLU_LogError("-out expected a value"); ret = WOLFCLU_FATAL_ERROR; } + else { + outFile = optarg; + } break; case 'd': @@ -97,12 +101,6 @@ int wolfCLU_Base64Setup(int argc, char** argv) case WOLFCLU_HELP: wolfCLU_Base64Help(); - if (bioIn != NULL) { - wolfSSL_BIO_free(bioIn); - } - if (bioOut != NULL) { - wolfSSL_BIO_free(bioOut); - } return WOLFCLU_SUCCESS; case ':': @@ -118,56 +116,38 @@ int wolfCLU_Base64Setup(int argc, char** argv) } } - if (ret == WOLFCLU_SUCCESS && bioIn == NULL) { - bioIn = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); - if (bioIn != NULL) - wolfSSL_BIO_set_fp(bioIn, stdin, BIO_NOCLOSE); - } - else if (ret == WOLFCLU_SUCCESS) { - /* get data size using raw FILE pointer and seek */ - XFILE f; - if (wolfSSL_BIO_get_fp(bioIn, &f) != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Unable to get raw file pointer"); - ret = WOLFCLU_FATAL_ERROR; - } - - if (ret == WOLFCLU_SUCCESS && XFSEEK(f, 0, XSEEK_END) != 0) { - wolfCLU_LogError("Unable to seek end of file"); - ret = WOLFCLU_FATAL_ERROR; - } - - if (ret == WOLFCLU_SUCCESS) { - inputSz = (sword32)XFTELL(f); - wolfSSL_BIO_reset(bioIn); - } - } - if (ret == WOLFCLU_SUCCESS) { - input = (byte*)XMALLOC(inputSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (input == NULL) { - wolfCLU_LogError("Memory allocation error for input buffer"); - ret = MEMORY_E; + if (inFile == NULL) { + ret = wolfCLU_ReadIo(WOLFCLU_IO_READABLE_STREAM, stdin, &input, + &inputSz); } else { - inputSz = wolfSSL_BIO_read(bioIn, input, inputSz); - - if (inputSz < 0) { - wolfCLU_LogError("Could not read input."); + XFILE fp = XFOPEN(inFile, "rb"); + if (fp == XBADFILE) { + wolfCLU_LogError("Could not open file %s", inFile); ret = WOLFCLU_FATAL_ERROR; } - /* For decoding, check if input is in PEM format */ - else if (decode && inputSz > 11) { - /* Check if the input starts with a PEM header */ - if (XMEMCMP(input, "-----BEGIN", 10) == 0) { - isPEM = 1; - } + else { + ret = wolfCLU_ReadIo(WOLFCLU_IO_READABLE_FILE, fp, + &input, &inputSz); + XFCLOSE(fp); } + } + } + /* when decoding, check for a PEM header on the input */ + if (ret == WOLFCLU_SUCCESS && decode && inputSz >= 10) { + if (XMEMCMP(input, "-----BEGIN", 10) == 0) { + isPEM = 1; } } /* Perform encoding/decoding */ - if (ret == WOLFCLU_SUCCESS && decode) { + if (ret == WOLFCLU_SUCCESS && inputSz == 0) { + /* empty input produces empty output, matching 'openssl base64' */ + outputSz = 0; + } + else if (ret == WOLFCLU_SUCCESS && decode) { if (isPEM) { #ifdef WOLFSSL_PEM_TO_DER /* Try different PEM types */ @@ -259,45 +239,34 @@ int wolfCLU_Base64Setup(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { - if (Base64_Encode(input, inputSz, output, &outputSz) < 0) { - wolfCLU_LogError("Base64 encode failed: %d", ret); + int encRet = Base64_Encode(input, inputSz, output, &outputSz); + if (encRet < 0) { + wolfCLU_LogError("Base64 encode failed: %d", encRet); ret = WOLFCLU_FATAL_ERROR; } - else { - ret = WOLFCLU_SUCCESS; - } } } - if (ret == WOLFCLU_SUCCESS && bioOut != NULL) { - /* Write output */ - ret = wolfSSL_BIO_write(bioOut, output, outputSz); - if (ret <= 0) { - wolfCLU_LogError("Failed to write output data: %d", ret); - ret = WOLFCLU_FATAL_ERROR; + if (ret == WOLFCLU_SUCCESS) { + if (outFile == NULL) { + ret = wolfCLU_WriteIo(WOLFCLU_IO_WRITABLE_STREAM, stdout, output, + outputSz); } else { - ret = WOLFCLU_SUCCESS; - } - } - else if (ret == WOLFCLU_SUCCESS) { - /* Write to stdout */ - bioOut = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); - if (bioOut != NULL) { - wolfSSL_BIO_set_fp(bioOut, stdout, BIO_NOCLOSE); - ret = wolfSSL_BIO_write(bioOut, output, outputSz); - if (ret <= 0) { - wolfCLU_LogError("Failed to write to stdout: %d", ret); + XFILE fp = XFOPEN(outFile, "wb"); + if (fp == XBADFILE) { + wolfCLU_LogError("Could not open file %s", outFile); ret = WOLFCLU_FATAL_ERROR; } else { - ret = WOLFCLU_SUCCESS; + ret = wolfCLU_WriteIo(WOLFCLU_IO_WRITABLE_FILE, fp, + output, outputSz); + if (XFCLOSE(fp) != 0 && ret == WOLFCLU_SUCCESS) { + wolfCLU_LogError("Could not write file %s", outFile); + ret = WOLFCLU_FATAL_ERROR; + } } } - else { - wolfCLU_LogError("Failed to create stdout BIO"); - ret = MEMORY_E; - } } /* Clean up */ @@ -312,12 +281,6 @@ int wolfCLU_Base64Setup(int argc, char** argv) wc_FreeDer(&der); } #endif - if (bioIn != NULL) { - wolfSSL_BIO_free(bioIn); - } - if (bioOut != NULL) { - wolfSSL_BIO_free(bioOut); - } return ret; #else diff --git a/src/tools/clu_io.c b/src/tools/clu_io.c new file mode 100644 index 00000000..295eed74 --- /dev/null +++ b/src/tools/clu_io.c @@ -0,0 +1,244 @@ +/* clu_io.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* Windows opens stdout and stdin in text mode, translating 0x0A <-> 0x0D 0x0A. + * We don't want that */ +#if defined(_WIN32) + #include + #include +#endif + +typedef struct WOLFCLU_IO_BUFFER { + byte* outBuf; + int len; + int cap; +} WOLFCLU_IO_BUFFER; + +/* move the data to a new allocation of newCap bytes, wiping the old one so no + * stale copies are left on the heap */ +static int ResizeBuffer(WOLFCLU_IO_BUFFER* buffer, int newCap) +{ + byte* tmp = (byte*)XMALLOC(newCap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + wolfCLU_LogError("Could not allocate space for io read."); + return WOLFCLU_FATAL_ERROR; + } + if (buffer->outBuf != NULL) { + XMEMCPY(tmp, buffer->outBuf, buffer->len); + wolfCLU_ForceZero(buffer->outBuf, buffer->len); + XFREE(buffer->outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + buffer->outBuf = tmp; + buffer->cap = newCap; + return WOLFCLU_SUCCESS; +} + +/* read a stream of unknown length, growing the buffer as we go */ +static int StreamRead(XFILE fp, WOLFCLU_IO_BUFFER* buffer) +{ + sword32 bytesRead = 0; + int newCap; + int ret = WOLFCLU_SUCCESS; + while (1) { + if (buffer->cap == buffer->len) { + if (buffer->cap == INT_MAX) { + /* try to read one more byte to make sure that if the + * one more byte is EOF we break out with a full buffer */ + char lookOneMore; + if (XFREAD(&lookOneMore, 1, 1, fp) == 0 && !XFERROR(fp)) + break; + + wolfCLU_LogError("input too big needs to be %d " + "bytes or less", INT_MAX); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (buffer->cap > ((INT_MAX - 1024) / 2)) { + newCap = INT_MAX; + } + else { + newCap = buffer->cap * 2 + 1024; + } + ret = ResizeBuffer(buffer, newCap); + if (ret != WOLFCLU_SUCCESS) { + break; + } + } + bytesRead = (sword32)XFREAD(buffer->outBuf + buffer->len, + sizeof(*buffer->outBuf), buffer->cap - buffer->len, fp); + /* check for errors first, ports without ferror define XFERROR as 0 */ + if (bytesRead < 0 || XFERROR(fp)) { + wolfCLU_LogError("Error while reading input stream."); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (bytesRead == 0) { + break; /* EOF */ + } + + buffer->len += bytesRead; + } + + return ret; +} + +/* seek to get the length, then read it in one allocation. Inputs that can't + * be sized this way, like pipes and devices, are read as a stream */ +static int FileRead(XFILE fp, WOLFCLU_IO_BUFFER* buffer) +{ + sword32 bytesRead = 0; + long fileSize = 0; + if (XFSEEK(fp, 0, XSEEK_END) != 0) { + /* not seekable, e.g. a pipe */ + return StreamRead(fp, buffer); + } + if ((fileSize = XFTELL(fp)) < 0) { + wolfCLU_LogError("Could not get length of file"); + return WOLFCLU_FATAL_ERROR; + } + else if (fileSize > INT_MAX) { + wolfCLU_LogError("File is too large max is %d bytes", INT_MAX); + return WOLFCLU_FATAL_ERROR; + } + if (XFSEEK(fp, 0, XSEEK_SET) != 0) { + wolfCLU_LogError("Could not seek input file"); + return WOLFCLU_FATAL_ERROR; + } + if (fileSize == 0) { + /* devices report a size of 0, an empty file just reads nothing */ + return StreamRead(fp, buffer); + } + buffer->len = (int)fileSize; + buffer->outBuf = (byte*)XMALLOC(buffer->len, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (buffer->outBuf == NULL) { + wolfCLU_LogError("Could not allocate space for io " + "read."); + return WOLFCLU_FATAL_ERROR; + } + buffer->cap = buffer->len; + bytesRead = (sword32)XFREAD(buffer->outBuf, sizeof(*buffer->outBuf), + buffer->len, fp); + if (bytesRead != buffer->len) { + wolfCLU_LogError("Could not read all of the file data"); + return WOLFCLU_FATAL_ERROR; + } + return WOLFCLU_SUCCESS; +} + +int wolfCLU_ReadIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, byte** buf, + word32* len) +{ + int ret = WOLFCLU_SUCCESS; + WOLFCLU_IO_BUFFER buffer = {0}; + + if (fp == XBADFILE || buf == NULL || len == NULL) { + wolfCLU_LogError("Bad arg passed to wolfCLU_ReadIo"); + return BAD_FUNC_ARG; + } + + *buf = NULL; + *len = 0; + if (!(ioType & WOLFCLU_IO_READABLE)) { + wolfCLU_LogError("Invalid ioType passed to wolfCLU_ReadIo"); + return BAD_FUNC_ARG; + } +#ifdef WOLFCLU_NO_FILESYSTEM + if (ioType & WOLFCLU_IO_RW_FILE) { + wolfCLU_LogError("Cannot read files when compiled with " + "WOLFCLU_NO_FILESYSTEM"); + return NOT_COMPILED_IN; + } +#endif +#ifdef _WIN32 + /* binary mode, see note at top of file */ + (void)_setmode(_fileno(fp), _O_BINARY); +#endif + + if (ioType & WOLFCLU_IO_READABLE_FILE) { + ret = FileRead(fp, &buffer); + } + else { + ret = StreamRead(fp, &buffer); + } + + if (ret != WOLFCLU_SUCCESS) { + if (buffer.outBuf != NULL) { + wolfCLU_ForceZero(buffer.outBuf, buffer.len); + XFREE(buffer.outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + return ret; + } + else { + if (buffer.len == 0 && buffer.outBuf != NULL) { + XFREE(buffer.outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + buffer.outBuf = NULL; + } + *len = buffer.len; + *buf = buffer.outBuf; + return ret; + } +} + +int wolfCLU_WriteIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, + const byte* buf, word32 len) +{ + int ret = WOLFCLU_SUCCESS; + if (fp == XBADFILE || (buf == NULL && len > 0) || len > INT_MAX) { + wolfCLU_LogError("Bad arg passed to wolfCLU_WriteIo"); + return BAD_FUNC_ARG; + } + + if (!(ioType & WOLFCLU_IO_WRITABLE)) { + wolfCLU_LogError("Invalid ioType passed to wolfCLU_WriteIo"); + return BAD_FUNC_ARG; + } +#ifdef WOLFCLU_NO_FILESYSTEM + if (ioType & WOLFCLU_IO_RW_FILE) { + wolfCLU_LogError("Cannot write out to a file when compiled with " + "WOLFCLU_NO_FILESYSTEM"); + return NOT_COMPILED_IN; + } +#endif +#ifdef _WIN32 + /* binary mode, see note at top of file */ + (void)_setmode(_fileno(fp), _O_BINARY); +#endif + + if (len > 0 && XFWRITE(buf, sizeof(*buf), (int)len, fp) != len) { + wolfCLU_LogError("Could not write buffer out to target"); + ret = WOLFCLU_FATAL_ERROR; + } +#ifdef XFFLUSH + /* the write may only be buffered, flush so errors like a full disk are + * caught here */ + if (ret == WOLFCLU_SUCCESS && XFFLUSH(fp) != 0) { + wolfCLU_LogError("Could not flush buffer out to target"); + ret = WOLFCLU_FATAL_ERROR; + } +#endif + return ret; +} + diff --git a/tests/base64/base64-test.py b/tests/base64/base64-test.py index c955ac71..911ad601 100644 --- a/tests/base64/base64-test.py +++ b/tests/base64/base64-test.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 """Base64 encode/decode tests for wolfCLU.""" +import base64 import filecmp import os +import random import subprocess import sys import unittest @@ -12,6 +14,13 @@ from wolfclu_test import WOLFSSL_BIN, CERTS_DIR, run_wolfssl, test_main +def pem_lines(data): + """Base64 encode data as 64 character lines, the way wolfssl writes it.""" + encoded = base64.b64encode(data) + return b"".join(encoded[i:i + 64] + b"\n" + for i in range(0, len(encoded), 64)) + + class Base64Test(unittest.TestCase): @classmethod @@ -103,6 +112,162 @@ def test_stdin_input(self): self.assertEqual(result.returncode, 0, "Couldn't parse input from stdin") + def test_stdin_input_long(self): + """Encode 100,000 bytes from stdin, spanning several buffer grows.""" + data = random.Random(0).randbytes(100000) + + result = subprocess.run( + [WOLFSSL_BIN, "base64"], + input=data, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.replace(b"\n", b""), + base64.b64encode(data), + "stdin encode does not match python base64") + + def test_stdin_decode_long(self): + """Decode more than the old 8000 byte stdin limit.""" + data = random.Random(1).randbytes(100000) + + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-d"], + input=pem_lines(data), + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, data, + "stdin decode does not match the original data") + + @unittest.skipUnless(os.path.exists("/dev/stdin"), "needs /dev/stdin") + def test_pipe_input_file(self): + """-in on a pipe, which can't be sized with seek, reads like stdin.""" + data = random.Random(2).randbytes(5000) + + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-in", "/dev/stdin"], + input=data, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.replace(b"\n", b""), + base64.b64encode(data), + "pipe input encode does not match python base64") + + def test_empty_stdin(self): + """Empty stdin produces empty output, matching 'openssl base64'.""" + result = subprocess.run( + [WOLFSSL_BIN, "base64"], + input=b"", + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, b"", "empty input should give no output") + + def test_empty_stdin_decode(self): + """Empty stdin with -d produces empty output, like 'openssl base64'.""" + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-d"], + input=b"", + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, b"", "empty input should give no output") + + def test_empty_file(self): + """An empty input file produces an empty output file.""" + empty_file = "test-b64-empty.txt" + out_file = "test-b64-empty.b64" + self.addCleanup(lambda: os.remove(empty_file) + if os.path.exists(empty_file) else None) + self.addCleanup(lambda: os.remove(out_file) + if os.path.exists(out_file) else None) + + with open(empty_file, "wb"): + pass + + result = run_wolfssl("base64", "-in", empty_file, "-out", out_file) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(os.path.getsize(out_file), 0, + "empty input should give an empty output file") + + def test_missing_input_file(self): + """A missing -in file gives a non-zero exit.""" + result = run_wolfssl("base64", "-in", "test-b64-does-not-exist.bin") + self.assertNotEqual(result.returncode, 0, + "missing input file should fail") + + def test_output_dir_missing(self): + """An -out path in a missing directory gives a non-zero exit.""" + result = run_wolfssl("base64", "-in", + os.path.join(CERTS_DIR, "server-key.der"), + "-out", os.path.join("test-b64-no-such-dir", + "out.b64")) + self.assertNotEqual(result.returncode, 0, + "output in a missing directory should fail") + + def test_failed_decode_keeps_output(self): + """A failed -d does not create or truncate the -out file.""" + out_file = "test-b64-keep.txt" + self.addCleanup(lambda: os.remove(out_file) + if os.path.exists(out_file) else None) + + with open(out_file, "wb") as f: + f.write(b"keep") + + result = run_wolfssl("base64", "-d", "-out", out_file, + stdin_data="@@@@") + self.assertNotEqual(result.returncode, 0, "bad base64 should fail") + with open(out_file, "rb") as f: + self.assertEqual(f.read(), b"keep", + "failed decode should leave -out unchanged") + + def test_in_place(self): + """-in and -out naming the same file encodes it in place.""" + work_file = "test-b64-inplace.bin" + self.addCleanup(lambda: os.remove(work_file) + if os.path.exists(work_file) else None) + + with open(os.path.join(CERTS_DIR, "server-key.der"), "rb") as f: + original = f.read() + with open(work_file, "wb") as f: + f.write(original) + + result = run_wolfssl("base64", "-in", work_file, "-out", work_file) + self.assertEqual(result.returncode, 0, result.stderr) + with open(work_file, "rb") as f: + self.assertEqual(f.read().replace(b"\n", b""), + base64.b64encode(original), + "in place encode does not match python base64") + + @unittest.skipUnless(os.path.exists("/dev/full"), "needs /dev/full") + def test_stdout_write_error(self): + """A failed write to stdout gives a non-zero exit.""" + with open("/dev/full", "wb") as full: + result = subprocess.run( + [WOLFSSL_BIN, "base64", "-in", + os.path.join(CERTS_DIR, "server-key.der")], + stdout=full, + stderr=subprocess.PIPE, + timeout=60, + ) + self.assertNotEqual(result.returncode, 0, + "write to a full device should fail") + + @unittest.skipUnless(os.path.exists("/dev/full"), "needs /dev/full") + def test_output_file_write_error(self): + """A failed write to the -out file gives a non-zero exit.""" + result = run_wolfssl("base64", "-in", + os.path.join(CERTS_DIR, "server-key.der"), + "-out", "/dev/full") + self.assertNotEqual(result.returncode, 0, + "write to a full device should fail") + def test_help(self): """ Test help flag """ result = subprocess.run( diff --git a/wolfCLU.vcxproj b/wolfCLU.vcxproj index 8faba25e..b20e3335 100644 --- a/wolfCLU.vcxproj +++ b/wolfCLU.vcxproj @@ -177,6 +177,7 @@ + diff --git a/wolfCLU.vcxproj.filters b/wolfCLU.vcxproj.filters index 9c43d2c1..9d76d5bc 100644 --- a/wolfCLU.vcxproj.filters +++ b/wolfCLU.vcxproj.filters @@ -102,6 +102,9 @@ Source Files + + Source Files + Source Files diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index b3fa9c93..480343bf 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -851,6 +851,60 @@ int wolfCLU_ReadCertDer(const char* filename, byte** outDer); */ int wolfCLU_GetStdinPassword(byte* password, word32* passwordSz); +/** + * @brief IO types passed to the IO functions below + */ +enum WOLFCLU_IO_TYPE { + /* base types */ + WOLFCLU_IO_READABLE_STREAM = 1 << 0, + WOLFCLU_IO_WRITABLE_STREAM = 1 << 1, + WOLFCLU_IO_READABLE_FILE = 1 << 2, + WOLFCLU_IO_WRITABLE_FILE = 1 << 3, + + /* type groups */ + WOLFCLU_IO_RW_STREAM = + WOLFCLU_IO_READABLE_STREAM | + WOLFCLU_IO_WRITABLE_STREAM, + + WOLFCLU_IO_RW_FILE = + WOLFCLU_IO_READABLE_FILE | + WOLFCLU_IO_WRITABLE_FILE, + + WOLFCLU_IO_READABLE = + WOLFCLU_IO_READABLE_STREAM | + WOLFCLU_IO_READABLE_FILE, + + WOLFCLU_IO_WRITABLE = + WOLFCLU_IO_WRITABLE_STREAM | + WOLFCLU_IO_WRITABLE_FILE, +}; + +/** + * @brief read all of fp into a new buffer + * @param ioType a readable stream or file type. Streams are read until EOF, + * files are sized with seek first and read as a stream if that fails + * @param fp file pointer to read from + * @param buf pointer to store the buffer, NULL if empty. The caller frees it, + * wiping it first with wolfCLU_ForceZero if it holds secrets + * @param len pointer to store the number of bytes read + * @return WOLFCLU_SUCCESS on success, negative on error + */ +int wolfCLU_ReadIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, byte** buf, + word32* len); + +/** + * @brief write len bytes of buf to fp and flush it + * @param ioType a writable stream or file type + * @param fp file pointer to write to + * @param buf buffer to write, may be NULL when len is 0 + * @param len number of bytes to write + * @return WOLFCLU_SUCCESS on success, negative on error + */ +int wolfCLU_WriteIo(enum WOLFCLU_IO_TYPE ioType, XFILE fp, const byte* buf, + word32 len); + + + #ifdef __cplusplus } #endif