/ story-12-Parameters.org
story-12-Parameters.org
1 #+TITLE: Story 12 - Parameters 2 #+OPTIONS: author:nil date:nil 3 4 Operations will need to handle diverse parameters, and the caller had better 5 know exactly how to pass them! 6 7 This is all well and good, but... how it the caller supposed to know, 8 especially since they're defined with the operation, not by the caller? 9 10 This has already been implemented in diverse ways: 11 12 - with ioctl(2), the caller does it by passing a known request number and 13 pointers to parameters of the exact correct type, in the exact correct 14 order, all in one ioctl() call. The exact type for each of the parameters 15 is decided entirely by documentation. The unfortunate thing is that the 16 caller must hard-code everything. 17 18 - OpenSSL has a somewhat different approach, where parameters are passed in 19 form of an array of this tuple: 20 21 < param name, data type, data pointer, data size > 22 23 The idea with that array is that it's a bag of parameters to be passed 24 around, for the recipient to pilfer what it needs from. This puts a 25 heavier load on the recipient to look up parameters it needs by name, and 26 in some cases, to convert the received data into the form the recipient 27 needs. 28 29 OpenSSL also offers functions where parameter recipients can return an 30 array of parameter descriptions, for discoverability. 31 32 Both of them have their benefits and impediments, in terms of strictness, 33 control and discoverability. 34 35 How about blending these models together? 36 37 * The parameter setting / getting call 38 39 Let's start with setting and getting parameters. To retain some modicum of 40 control, the parameter data should also include the size, and in some cases, 41 it's also interesting to get a size back. We may also have cases where an 42 array of parameter data need to be passed. 43 44 The parameter indicator itself is a simple number, similar to ioctl(2)'s 45 request number. The parameter data is passed using this simple structure: 46 47 #+begin_src C 48 typedef struct LSC_param_st { 49 void *data; 50 size_t size; 51 size_t *len; 52 } LSC_param_t; 53 #+end_src 54 55 - ~data~ is the parameter data itself. 56 - ~size~ is The exact size of the memory block ~data~ points at. When 57 setting a parameter, this must be the size of the data. 58 - ~len~ can be used to receive the exact size of ~data~ that was used, or 59 that would be used. This may be NULL if this isn't interesting for the 60 caller. 61 62 With this, setting or getting a single parameter could look like this: 63 64 #+begin_src C 65 LSC_set_OP_param 66 (O, idx, (LSC_param_t[]){{buf, sizeof(buf), NULL}, 67 {NULL, 0, NULL}}); 68 69 LSC_get_OP_param 70 (O, idx, (LSC_param_t[]){{buf, sizeof(buf), &buflen}, 71 {NULL, 0, NULL}}); 72 #+end_src 73 74 It's arguable if the terminating NULL / zero element is really needed. It 75 may be prudent, in case the recipient of the call is extra meticulous. 76 77 Setting and getting a single parameter that takes an array isn't much 78 different, the passed parameter array simply has more than just one element: 79 80 #+begin_src C 81 LSC_set_OP_param 82 (O, idx, (LSC_param_t[]){{buf1, sizeof(buf1), NULL}, 83 {buf2, sizeof(buf2), NULL}, 84 {NULL, 0, NULL}}); 85 86 LSC_get_OP_param 87 (O, idx, (LSC_param_t[]){{buf1, sizeof(buf1), &buflen1}, 88 {buf2, sizeof(buf2), &buflen2}, 89 {NULL, 0, NULL}}); 90 #+end_src 91 92 In all these examples, ~O~ is the operation or object, and ~idx~ is the 93 parameter indicator. Also, in these function names, =OP= is expected to be 94 a name related to the type for ~O~. 95 96 * The parameter declaration 97 98 Setting and getting parameters as shown above requires that the caller has 99 all the knowledge it needs. In simple cases, documentation and hard-coding 100 may be sufficient, but it may not be so easy in more complex cases, where 101 functions may not know the exact implementation for an object or operation, 102 and what that implementation requires for each parameter, or even exactly 103 what parameters it supports. 104 105 So, how about having the object or operation declare their parameters? The 106 implementations would have to support two functions, one to declare 107 settable parameters and one to declare gettable parameters. 108 109 The declaration comes as an array of parameter descriptors, each of which 110 includes a parameter data descriptor. They have these type names: 111 112 #+begin_src C 113 typedef struct LSC_param_desc_st LSC_param_desc_t; 114 typedef struct LSC_data_desc_st LSC_data_desc_t; 115 #+end_src 116 117 ** The parameter descriptor 118 119 The parameter description would be an array of this structure: 120 121 #+begin_src C 122 struct LSC_param_desc_st { 123 const char *p_name; /* Parameter name */ 124 uint8_t p_id; /* (impl. specific) numeric parameter id */ 125 LSC_data_desc_t p_data; /* Parameter data description */ 126 }; 127 #+end_src 128 129 The idea is that the caller can then look up parameters by name in this 130 array to figure out how to pass them. 131 132 ** The parameter data descriptor 133 134 The parameter data descriptor is the more complex part, as it covers many 135 types of data. The idea is that all data can essentially be represented as 136 a byte string, i.e. a memory block, or multiple memory blocks with links 137 between them. The trick is to inform others how that memory block should be 138 organised. 139 140 The data descriptor below currently only supports single memory blocks, but 141 should be possible to extend to support multiple blocks linked as a tree of 142 memory blocks. 143 144 #+begin_src C 145 struct LSC_data_desc_st { 146 enum { 147 LSC_DT_integer = 1, 148 LSC_DT_unsigned_integer = 2, 149 LSC_DT_real = 3, 150 LSC_DT_utf8_string = 4, 151 LSC_DT_octet_string = 5, 152 LSC_DT_bit_string = 6, 153 LSC_DT_ordered_array_of = 28, /* ordered array of data desc */ 154 LSC_DT_unordered_array_of = 29, /* unordered array of data desc */ 155 LSC_DT_sequence = 30, /* ordered array of param desc */ 156 LSC_DT_object = 31, /* unordered array of param desc */ 157 LSC_DT__user_datatype_start = 32 158 } d_type:6; /* Parameter type */ 159 160 struct { 161 uint8_t min; /* Minimum number of elements */ 162 uint8_t max; /* Maximum number of elements */ 163 } d_elems; 164 165 union { 166 struct { 167 _Bool d_separate_sign_byte; /* 0 no, 1 yes */ 168 169 enum { 170 LSC_DTi_host_order = 0, 171 LSC_DTi_lsw_first_order, 172 LSC_DTi_msw_first_order, 173 } d_word_order; 174 175 uint8_t d_word_size; 176 177 enum { 178 LSC_DTiw_host_order = 0, 179 LSC_DTiw_lsb_first_order, 180 LSC_DTiw_msb_first_order, 181 } d_byte_order; 182 } d_integer_desc; 183 184 LSC_data_desc_t *d_array_element_desc; 185 186 LSC_param_desc_t *d_object_desc; 187 } d_auxiliary; 188 189 void *d_private; 190 }; 191 #+end_src 192 193 Some commentary on some structure fields: 194 195 - ~d_elems~ :: 196 197 For ~LSC_DT_integer~, ~LSC_DT_unsigned_integer~, ~LSC_DT_real~, 198 ~LSC_DT_utf8_string~ and ~LSC_DT_octet_string~, the element is a byte. 199 200 For ~LSC_DT_bit_string~, the element is the bit. 201 202 For ~LSC_DT_ordered_array_of~ and ~LSC_DT_unordered_array_of~, the 203 element is the array item, the type of which is further described 204 in ~.d_auxilliary->array_element_desc~. 205 206 For ~LSC_DT_sequence~ and ~LSC_DT_object~, ~min~ and ~max~ are not 207 relevant. 208 They are further described in ~.p_auxilliary->object_desc~, 209 which is a NULL-terminated array. 210 211 - ~d_auxiliary~ :: 212 213 This is a =union= of different auxiliary descriptors. Which one to use 214 depends on the data type (~d_type~). 215 216 - ~d_integer_desc~ :: 217 218 This is for ~LSC_DT_integer~ and ~LSC_DT_unsigned_integer~, to fine tune 219 their representation in memory. When everything here is zero, the 220 memory representation is considered a host order bit string with 2's 221 complement semantics. 222 223 Large numbers may have to be divided into words, which have their own 224 specifications. Bignum libraries may call these words other things, 225 like "limb". 226 227 - ~d_separate_sign_byte~ :: 228 229 If ~d_type~ is ~LSC_DT_integer~ and there is a separate sign byte 230 (which must come first), the number must be represented in a 231 sign-magnitude manner, i.e. the rest of the memory block must be 232 considered an ~LSC_DT_unsigned_integer~. If there isn't a sign byte, 233 then the number is represented in a 2's complement manner. 234 235 - ~d_word_order~ :: 236 237 The order of words, see more on words below. 238 239 - ~d_word_size~ :: 240 241 The word size. 242 243 If it's 0 or 1, the word size is the same as a byte size, and because 244 that's the smallest unit on most processors today, the detailed word 245 specification can be ignored in that case. 246 247 To be noted is that words are always considered unsigned. 248 249 - ~d_byte_order~ :: 250 251 The order of bytes within a word. 252 253 - ~d_array_element_desc~ :: 254 255 For ~LSC_DT_ordered_array_of~ and ~LSC_DT_unordered_array_of~, this 256 must contain a description of the type of the elements. 257 258 This points to /one/ data descriptor only, i.e. all array elements must 259 fit that description, but since ~LSC_param_t~ has a size, each parameter 260 is allowed to be of different sizes. 261 262 - ~d_object_desc~ :: 263 264 For ~LSC_DT_sequence~ and ~LSC_DT_object~, this must contain a pointer 265 to a NULL-terminated array of parameter descriptors that describe the 266 object. 267 268 - ~d_private~ :: 269 270 Extra pointer that implementations may use in whatever manner they like. 271 272 * Other ways to declare parameters 273 274 Parameters could potentially be declared through other means. For example, 275 aspects of the parameters could be declared as macros that are shared by the 276 different piece of software that will communicate parameters to each other. 277 278 The thinking for the moment is that this sort of macros would be published 279 alongside plugins that define them, as development headers. In such as 280 case, it's on the plugin author to figure out how their declarations should 281 be structured and named. This is not in scope for Le'Sec.