blob: 71bb26b2763a598caf493c6ec003d9dc023df8a4 [file] [log] [blame]
Laurence Lundblade501d3f82022-04-08 14:52:55 -06001//
2// ub-example.c
3// QCBOR
4//
5// Created by Laurence Lundblade on 4/8/22.
6// Copyright © 2022 Laurence Lundblade. All rights reserved.
7//
8
9#include "ub-example.h"
10
11#include "UsefulBuf.h"
12
13
14/*
15 A large number of the security issues with C code come from mistakes
16 made with a pointer and length for a buffer or some binary data.
17 UsefulBuf adopts a convention that a pointer and length *always*
18 go together to migitigate this. With UsefulBuf there are never
19 pointers without lengths so you always know how big the buffer
20 or the data is.
21
22 C99 allows passing structures so a structure is used. Compilers
23 are smart these days so the object code produced is no different
24 than passing two separate parameters. Passing structures also
25 makes the interfaces prettier. Assignments of structures also
26 can make code prettier.
27
28 There are a bunch of (tested!) functions to manipulate UsefulBuf's so
29 code using it may have no pointer manipulation at all!
30
31 In this example the buffers that are filled in with data
32 are const and the ones that are to-be-filled in are not
33 const. Keeping const distinct from non-const is helpful
34 when reading the code and helps avoid some coding mistakes.
35 See this:
36 https://stackoverflow.com/questions/117293/use-of-const-for-function-parameters
37
38 This contrived example copies data from input to output
39 expanding bytes with the value 'x' to 'xx'.
40
41 Input -- This is the pointer and length of the input, the
42 bytes to copy. Note that UsefulBufC.ptr is a const void *
43 indicates that input data won't be changed by this function.
44 There is a "C" in UsefulBufC to indicate the value is const.
45 The length here is the length of the valid input data. Note
46 also that the parameter Input is const, so this is fully
47 const and clearly an [in] parameter.
48
49 Output -- This is a pointer and length of
50 the memory to used to store the output. The correct length
51 here is critical for code security. Note that UsefulBuf.ptr
52 is void *, it is not const indicating data can be written to
53 it. Note that the parameter itself *is* const indicating
54 that the code below will not point this to some other buffer
55 or change the length and clearly marked as an [in] parameter.
56
57 Output -- This is the interesting and unusual one. To stay
58 consistent with always paring and a length and for
59 a pointer to valid data to always be const, this is returned as
60 a UsefulBufC. Note that the parameter is a pointer to a
61 UsefulBufC, a *place* to return a UsefulBufC.
62
63 In this case and most cases the pointer in Output->ptr
64 will be the same as OutputBuffer.ptr. This may seem
65 redundant, but there's a few reasons for it. First,
66 is the goal of always pairing a pointer and a length.
67 Second is being more strict with constness. Third
68 is the code hygene and clarity of having
69 variables for to-be-filled buffers be distinct from those
70 containing valid data. Fourth, there are no [in,out]
71 parameters, only [in] parameters and [out] parameters
72 (the to-be-filled-in buffer is considered an [in]
73 parameter).
74
75 Note that the compiler will be smart about all
76 this and should generate pretty much the same code
77 as for a traditional interface with the
78 length parameter. On x86 with gcc-11 and no stack guards,
79 the UB code is 81 bytes and the traditional code is 77 bytes.
80
81 This supports computing of the would-be output
82 without actually doing any outputing by making
83 the OutputBuffer have a NULL pointer and a very
84 large length, e.g., {NULL, SIZE_MAX}.
85
86 */
87int
88ExpandUB(const UsefulBufC Input,
89 const UsefulBuf OutputBuffer,
90 UsefulBufC *Output)
91{
92 size_t nInputPosition;
93 size_t nOutputPosition;
94
95 nOutputPosition = 0;
96
97 /* Loop over all the bytes in Input */
98 for(nInputPosition = 0; nInputPosition < Input.len; nInputPosition++) {
99 const uint8_t nInputByte = ((uint8_t*)Input.ptr)[nInputPosition];
100
101 /* Copy every byte */
102 if(OutputBuffer.ptr != NULL) {
103 ((uint8_t *)OutputBuffer.ptr)[nOutputPosition] = nInputByte;
104 }
105 nOutputPosition++;
106 if(nOutputPosition >= OutputBuffer.len) {
107 return -1l;
108 }
109
110 /* Double output 'x' because that is what this contrived example does */
111 if(nInputByte== 'x') {
112 if(OutputBuffer.ptr != NULL) {
113 ((uint8_t *)OutputBuffer.ptr)[nOutputPosition] = 'x';
114 }
115 nOutputPosition++;
116 if(nOutputPosition >= OutputBuffer.len) {
117 return -1l;
118 }
119 }
120 }
121
122 *Output = (UsefulBufC){OutputBuffer.ptr, nOutputPosition};
123
124 return 0; /* success */
125}
126
127
128/* This is the more tradional way to implement this. */
129int ExpandTraditional(const uint8_t *pInputPointer,
130 const size_t uInputLength,
131 uint8_t *pOutputBuffer,
132 const size_t uOutputBufferLength,
133 size_t *puOutputLength)
134{
135 size_t nInputPosition;
136 size_t nOutputPosition;
137
138 nOutputPosition = 0;
139
140 /* Loop over all the bytes in Input */
141 for(nInputPosition = 0; nInputPosition < uInputLength; nInputPosition++) {
142 const uint8_t nInputByte = ((uint8_t*)pInputPointer)[nInputPosition];
143
144 /* Copy every byte */
145 if(pOutputBuffer != NULL) {
146 ((uint8_t *)pOutputBuffer)[nOutputPosition] = nInputByte;
147 }
148 nOutputPosition++;
149 if(nOutputPosition >= uOutputBufferLength) {
150 return -1l;
151 }
152
153 /* Double output 'x' because that is what this contrived example does */
154 if(nInputByte== 'x') {
155 if(pOutputBuffer != NULL) {
156 ((uint8_t *)pOutputBuffer)[nOutputPosition] = 'x';
157 }
158 nOutputPosition++;
159 if(nOutputPosition >= uOutputBufferLength) {
160 return -1l;
161 }
162 }
163 }
164
165 *puOutputLength = nOutputPosition;
166
167 return 0; /* success */
168}
169
170
171/*
172 Here's an example of going from a traditional interface
173 interface to a UsefulBuf interface.
174 */
175int ExpandTraditionalAdapted(const uint8_t *pInputPointer,
176 size_t uInputLength,
177 uint8_t *pOutputBuffer,
178 size_t uOutputBufferLength,
179 size_t *puOutputLength)
180{
181 UsefulBufC Input;
182 UsefulBuf OutputBuffer;
183 UsefulBufC Output;
184 int nReturn;
185
186 Input = (UsefulBufC){pInputPointer, uInputLength};
187 OutputBuffer = (UsefulBuf){pOutputBuffer, uOutputBufferLength};
188
189 nReturn = ExpandUB(Input, OutputBuffer, &Output);
190
191 *puOutputLength = Output.len;
192
193 return nReturn;
194}
195
196
197/* Here's an example for going from a UsefulBuf interface
198 to a traditional interface. */
199int
200ExpandUBAdapted(const UsefulBufC Input,
201 const UsefulBuf OutputBuffer,
202 UsefulBufC *Output)
203{
204 Output->ptr = OutputBuffer.ptr;
205
206 return ExpandTraditional(Input.ptr, Input.len,
207 OutputBuffer.ptr, OutputBuffer.len,
208 &(Output->len));
209}
210
211
212
213#define INPUT "xyz123xyz"
214
215int32_t RunUsefulBufExample()
216{
217 /* ------------ UsefulBuf examples ------------- */
218 UsefulBufC Input = UsefulBuf_FROM_SZ_LITERAL(INPUT);
219
220 /* This macros makes a 20 byte buffer on the stack. It also makes
221 * a UsefulBuf on the stack. It sets up the UsefulBuf to point to
222 * the 20 byte buffer and sets it's length to 20 bytes. This
223 * is the empty, to-be-filled in memory for the output. It is not
224 * const. */
225 MakeUsefulBufOnStack(OutBuf, sizeof(INPUT) * 2);
226
227 /* This is were the pointer and the length of the completed output
228 * will be placed. Output.ptr is a pointer to const bytes. */
229 UsefulBufC Output;
230
231 ExpandUB(Input, OutBuf, &Output);
232
233 ExpandUBAdapted(Input, OutBuf, &Output);
234
235
236
237 /* ------ Get Size example -------- */
238 ExpandUB(Input, (UsefulBuf){NULL, SIZE_MAX}, &Output);
239
240 /* Size is in Output.len */
241
242
243
244 /* ---------- Traditional examples (for comparison) --------- */
245 uint8_t puBuffer[sizeof(INPUT) * 2];
246 size_t uOutputSize;
247
248 ExpandTraditional((const uint8_t *)INPUT, sizeof(INPUT),
249 puBuffer, sizeof(puBuffer),
250 &uOutputSize);
251
252
253 ExpandTraditionalAdapted((const uint8_t *)INPUT, sizeof(INPUT),
254 puBuffer, sizeof(puBuffer),
255 &uOutputSize);
256
257 return 0;
258}