/ 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.