/* ========================================================================= 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 README.md 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() { /* ------------ 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; }