blob: ec776dca366af78473fa3a2d09617053fc6d70f7 [file] [log] [blame]
/* =========================================================================
ub-example.c -- Example code for UsefulBuf
Copyright (c) 2022, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in file named "LICENSE"
Created on 4/8/22
========================================================================== */
#include "ub-example.h"
#include "UsefulBuf.h"
/*
* A considerable number of the security issues with C code come from
* mistakes made with pointers and lengths. UsefulBuf adopts a
* convention that a pointer and length *always* go together to help
* mitigate this. With UsefulBuf there are never pointers without
* lengths, so you always know how big a buffer or some binary data
* is.
*
* C99 allows passing structures so a structure is used. Compilers are
* smart these days so the object code produced is little different
* than passing two separate parameters. Passing structures also makes
* the interfaces prettier. Assignments of structures also can make
* code prettier.
*
* ALong with the UsefulBuf structure, there are a bunch of (tested!)
* functions to manipulate them so code using it may have no pointer
* manipulation at all.
*
* Constness is also a useful and desirous thing. See
* https://stackoverflow.com/questions/117293/use-of-const-for-function-parameters
* Keeping const distinct from non-const is helpful when reading the
* code and helps avoid some coding mistakes. In this example the
* buffers filled in with data are const and the ones that are
* to-be-filled in are not const.
*
* This contrived example copies data from input to output expanding
* bytes with the value 'x' to 'xx'.
*
* Input -- This is the pointer and length of the input, the bytes to
* copy. Note that UsefulBufC.ptr is a const void * indicating that
* input data won't be changed by this function. There is a "C" in
* "UsefulBufC "to indicate the value is const. The length here is
* the length of the valid input data. Note also that the parameter
* Input is const, so this is fully const and clearly an [in]
* parameter.
*
* OutputBuffer -- This is a pointer and length of the memory to be
* used to store the output. The correct length here is critical for
* code security. Note that UsefulBuf.ptr is void *, it is not const
* indicating data can be written to it. Note that the parameter
* itself *is* const indicating that the code below will not point
* this to some other buffer or change the length and clearly marking
* it as an [in] parameter.
*
* Output -- This is the interesting and unusual one. To stay
* consistent with always pairing a length and a pointer, this is
* returned as a UsefulBuC. Also, to stay consistent with valid data
* being const, it is a UsefulBufC, not a UsefulBuf. It is however, an
* [out] parameter so the parameter is a pointer to a UsefulBufC.
*
* In this case and most cases, the pointer in Output->ptr will be the
* same as OutputBuffer.ptr. This may seem redundant, but there are a
* few reasons for it. First, is the goal of always pairing a pointer
* and a length. Second is being more strict and correct with
* constness. Third is the code hygiene and clarity of having
* variables for to-be-filled buffers be distinct from those
* containing valid data. Fourth, there are no [in,out] parameters,
* only [in] parameters and [out] parameters (the to-be-filled-in
* buffer is considered an [in] parameter).
*
* Note that the compiler will be smart and should generate pretty
* much the same code as for a traditional interface. On x86 with
* gcc-11 and no stack guards, the UB code is 81 bytes and the
* traditional code is 77 bytes.
*
* Finally, this supports computing of the length of the would-be
* output without actually doing any outputting. Pass {NULL, SIZE_MAX}
* for the OutputBuffer and the length will be returned in Output.
*/
int
ExpandxUB(const UsefulBufC Input,
const UsefulBuf OutputBuffer,
UsefulBufC *Output)
{
size_t uInputPosition;
size_t uOutputPosition;
uOutputPosition = 0;
/* Loop over all the bytes in Input */
for(uInputPosition = 0; uInputPosition < Input.len; uInputPosition++) {
const uint8_t uInputByte = ((const uint8_t*)Input.ptr)[uInputPosition];
/* Copy every byte */
if(OutputBuffer.ptr != NULL) {
((uint8_t *)OutputBuffer.ptr)[uOutputPosition] = uInputByte;
}
uOutputPosition++;
if(uOutputPosition >= OutputBuffer.len) {
return -1;
}
/* Double output 'x' because that is what this contrived example does */
if(uInputByte== 'x') {
if(OutputBuffer.ptr != NULL) {
((uint8_t *)OutputBuffer.ptr)[uOutputPosition] = 'x';
}
uOutputPosition++;
if(uOutputPosition >= OutputBuffer.len) {
return -1;
}
}
}
*Output = (UsefulBufC){OutputBuffer.ptr, uOutputPosition};
return 0; /* success */
}
/* This is the more tradional way to implement this. */
int
ExpandxTraditional(const uint8_t *pInputPointer,
const size_t uInputLength,
uint8_t *pOutputBuffer,
const size_t uOutputBufferLength,
size_t *puOutputLength)
{
size_t uInputPosition;
size_t uOutputPosition;
uOutputPosition = 0;
/* Loop over all the bytes in Input */
for(uInputPosition = 0; uInputPosition < uInputLength; uInputPosition++) {
const uint8_t uInputByte = ((const uint8_t*)pInputPointer)[uInputPosition];
/* Copy every byte */
if(pOutputBuffer != NULL) {
((uint8_t *)pOutputBuffer)[uOutputPosition] = uInputByte;
}
uOutputPosition++;
if(uOutputPosition >= uOutputBufferLength) {
return -1;
}
/* Double output 'x' because that is what this contrived example does */
if(uInputByte== 'x') {
if(pOutputBuffer != NULL) {
((uint8_t *)pOutputBuffer)[uOutputPosition] = 'x';
}
uOutputPosition++;
if(uOutputPosition >= uOutputBufferLength) {
return -1;
}
}
}
*puOutputLength = uOutputPosition;
return 0; /* success */
}
/*
* Here's an example of going from a traditional interface
* interface to a UsefulBuf interface.
*/
int
ExpandxTraditionalAdaptor(const uint8_t *pInputPointer,
size_t uInputLength,
uint8_t *pOutputBuffer,
size_t uOutputBufferLength,
size_t *puOutputLength)
{
UsefulBufC Input;
UsefulBuf OutputBuffer;
UsefulBufC Output;
int nReturn;
Input = (UsefulBufC){pInputPointer, uInputLength};
OutputBuffer = (UsefulBuf){pOutputBuffer, uOutputBufferLength};
nReturn = ExpandxUB(Input, OutputBuffer, &Output);
*puOutputLength = Output.len;
return nReturn;
}
/* Here's an example for going from a UsefulBuf interface
to a traditional interface. */
int
ExpandxUBAdaptor(const UsefulBufC Input,
const UsefulBuf OutputBuffer,
UsefulBufC *Output)
{
Output->ptr = OutputBuffer.ptr;
return ExpandxTraditional(Input.ptr, Input.len,
OutputBuffer.ptr, OutputBuffer.len,
&(Output->len));
}
#define INPUT "xyz123xyz"
int32_t RunUsefulBufExample(void)
{
/* ------------ UsefulBuf examples ------------- */
UsefulBufC Input = UsefulBuf_FROM_SZ_LITERAL(INPUT);
/* This macros makes a 20 byte buffer on the stack. It also makes
* a UsefulBuf on the stack. It sets up the UsefulBuf to point to
* the 20 byte buffer and sets it's length to 20 bytes. This
* is the empty, to-be-filled in memory for the output. It is not
* const. */
MakeUsefulBufOnStack(OutBuf, sizeof(INPUT) * 2);
/* This is were the pointer and the length of the completed output
* will be placed. Output.ptr is a pointer to const bytes. */
UsefulBufC Output;
ExpandxUB(Input, OutBuf, &Output);
ExpandxUBAdaptor(Input, OutBuf, &Output);
/* ------ Get Size example -------- */
ExpandxUB(Input, (UsefulBuf){NULL, SIZE_MAX}, &Output);
/* Size is in Output.len */
/* ---------- Traditional examples (for comparison) --------- */
uint8_t puBuffer[sizeof(INPUT) * 2];
size_t uOutputSize;
ExpandxTraditional((const uint8_t *)INPUT, sizeof(INPUT),
puBuffer, sizeof(puBuffer),
&uOutputSize);
ExpandxTraditionalAdaptor((const uint8_t *)INPUT, sizeof(INPUT),
puBuffer, sizeof(puBuffer),
&uOutputSize);
return 0;
}