blob: 69bd8d3c179875e9185998846944628348c8da19 [file] [log] [blame]
Rich Lanea06d0c32013-03-25 08:52:03 -07001:: # Copyright 2013, Big Switch Networks, Inc.
2:: #
3:: # LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
4:: # the following special exception:
5:: #
6:: # LOXI Exception
7:: #
8:: # As a special exception to the terms of the EPL, you may distribute libraries
9:: # generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
10:: # that copyright and licensing notices generated by LoxiGen are not altered or removed
11:: # from the LoxiGen Libraries and the notice provided below is (i) included in
12:: # the LoxiGen Libraries, if distributed in source code form and (ii) included in any
13:: # documentation for the LoxiGen Libraries, if distributed in binary form.
14:: #
15:: # Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
16:: #
17:: # You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
18:: # a copy of the EPL at:
19:: #
20:: # http://www.eclipse.org/legal/epl-v10.html
21:: #
22:: # Unless required by applicable law or agreed to in writing, software
23:: # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24:: # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
25:: # EPL for the specific language governing permissions and limitations
26:: # under the EPL.
27::
Rich Laned983aa52013-06-13 11:48:37 -070028:: include('_copyright.c')
Rich Lanea06d0c32013-03-25 08:52:03 -070029
30/****************************************************************
31 *
32 * of_object.c
33 *
34 * These are the low level object constructor/destructor operators.
35 *
36 ****************************************************************/
37
38#include "loci_log.h"
39#include <loci/loci.h>
40#include <loci/loci_validator.h>
41
Rich Lanea06d0c32013-03-25 08:52:03 -070042/**
43 * Create a generic new object and possibly underlying wire buffer
44 * @param bytes The number of bytes to allocate in the underlying buffer
45 *
46 * If bytes <= 0, do not allocate a wire buffer.
47 *
48 * Note that this is an internal function. The class specific
49 * new functions should be called to properly initialize and track an
50 * OF object.
51 */
52
53of_object_t *
54of_object_new(int bytes)
55{
56 of_object_t *obj;
57
Rich Lane671e7722013-12-15 16:48:54 -080058 if ((obj = (of_object_t *)MALLOC(sizeof(*obj))) == NULL) {
Rich Lanea06d0c32013-03-25 08:52:03 -070059 return NULL;
60 }
Rich Lane671e7722013-12-15 16:48:54 -080061 MEMSET(obj, 0, sizeof(*obj));
Rich Lanea06d0c32013-03-25 08:52:03 -070062
63 if (bytes > 0) {
64 if ((obj->wire_object.wbuf = of_wire_buffer_new(bytes)) == NULL) {
65 FREE(obj);
66 return NULL;
67 }
68 obj->wire_object.owned = 1;
69 }
70
71 return obj;
72}
73
74/**
75 * The delete function for LOCI objects
76 *
77 * @param obj Pointer to the object to be deleted
78 *
79 * This can be called on any LOCI object; it should not need to be
80 * overridden.
81 */
82
83void
84of_object_delete(of_object_t *obj)
85{
86 if (obj == NULL) {
87 return;
88 }
89
Rich Lanea06d0c32013-03-25 08:52:03 -070090 /*
91 * Make callback if present
92 */
93 if (obj->track_info.delete_cb != NULL) {
94 obj->track_info.delete_cb(obj);
95 }
96
97 if (obj->wire_object.owned) {
98 of_wire_buffer_free(obj->wire_object.wbuf);
99 }
100
101 FREE(obj);
102}
103
104/**
105 * Duplicate an object
106 * @param src The object to be duplicated
107 * @returns Pointer to the duplicate or NULL on error. Caller is responsible
108 * for freeing the returned object.
109 */
110
111of_object_t *
Rich Lanecd6ef152013-12-15 16:42:18 -0800112of_object_dup(of_object_t *src)
Rich Lanea06d0c32013-03-25 08:52:03 -0700113{
114 of_object_t *dst;
115 of_object_init_f init_fn;
116
Rich Lane671e7722013-12-15 16:48:54 -0800117 if ((dst = (of_object_t *)MALLOC(sizeof(*dst))) == NULL) {
Rich Lanea06d0c32013-03-25 08:52:03 -0700118 return NULL;
119 }
120
121 MEMSET(dst, 0, sizeof(*dst));
122
123 /* Allocate a minimal wire buffer assuming we will not write to it. */
124 if ((dst->wire_object.wbuf = of_wire_buffer_new(src->length)) == NULL) {
125 FREE(dst);
126 return NULL;
127 }
128
129 dst->wire_object.owned = 1;
130
131 init_fn = of_object_init_map[src->object_id];
132 init_fn(dst, src->version, src->length, 0);
133
134 MEMCPY(OF_OBJECT_BUFFER_INDEX(dst, 0),
135 OF_OBJECT_BUFFER_INDEX(src, 0),
136 src->length);
137
138 return dst;
139}
140
Rich Lanea06d0c32013-03-25 08:52:03 -0700141/**
142 * Generic new from message call
143 */
144
145of_object_t *
146of_object_new_from_message(of_message_t msg, int len)
147{
148 of_object_id_t object_id;
149 of_object_t *obj;
150 of_version_t version;
151
152 version = of_message_version_get(msg);
153 if (!OF_VERSION_OKAY(version)) {
154 return NULL;
155 }
156
157 if (of_validate_message(msg, len) != 0) {
158 LOCI_LOG_ERROR("message validation failed\n");
159 return NULL;
160 }
161
Rich Lanea06d0c32013-03-25 08:52:03 -0700162 if ((obj = of_object_new(-1)) == NULL) {
163 return NULL;
164 }
165
Rich Lanea06d0c32013-03-25 08:52:03 -0700166 if (of_object_buffer_bind(obj, OF_MESSAGE_TO_BUFFER(msg), len,
167 OF_MESSAGE_FREE_FUNCTION) < 0) {
168 FREE(obj);
169 return NULL;
170 }
Rich Lanea06d0c32013-03-25 08:52:03 -0700171 obj->version = version;
172
Rich Lane76f181e2014-03-04 23:23:36 -0800173 of_header_wire_object_id_get(obj, &object_id);
174 of_object_init_map[object_id](obj, version, len, 0);
175
Rich Lanea06d0c32013-03-25 08:52:03 -0700176 return obj;
177}
178
179/**
Rich Lanec73680c2014-02-22 10:44:28 -0800180 * Parse a message without allocating memory
181 *
182 * @param storage Pointer to an uninitialized of_object_storage_t
183 * @param buf Pointer to the buffer
184 * @param length Length of buf
185 * @returns Pointer to an initialized of_object_t
186 *
187 * The lifetime of the returned object is the minimum of the lifetimes of
188 * 'buf' and 'storage'.
189 */
190
191of_object_t *
192of_object_new_from_message_preallocated(of_object_storage_t *storage,
193 uint8_t *buf, int len)
194{
195 of_object_t *obj = &storage->obj;
196 of_wire_buffer_t *wbuf = &storage->wbuf;
197 of_message_t msg = buf;
198 of_version_t version;
199 of_object_id_t object_id;
200
201 memset(storage, 0, sizeof(*storage));
202
203 version = of_message_version_get(msg);
204 if (!OF_VERSION_OKAY(version)) {
205 return NULL;
206 }
207
208 if (of_validate_message(msg, len) != 0) {
209 LOCI_LOG_ERROR("message validation failed\n");
210 return NULL;
211 }
212
Rich Lane76f181e2014-03-04 23:23:36 -0800213 obj->version = version;
Rich Lanec73680c2014-02-22 10:44:28 -0800214 obj->wire_object.wbuf = wbuf;
215 wbuf->buf = msg;
216 wbuf->alloc_bytes = len;
217 wbuf->current_bytes = len;
218
Rich Lane76f181e2014-03-04 23:23:36 -0800219 of_header_wire_object_id_get(obj, &object_id);
220 of_object_init_map[object_id](obj, version, len, 0);
221
Rich Lanec73680c2014-02-22 10:44:28 -0800222 return obj;
223}
224
225/**
Rich Lanea06d0c32013-03-25 08:52:03 -0700226 * Bind an existing buffer to an LOCI object
227 *
228 * @param obj Pointer to the object to be updated
229 * @param buf Pointer to the buffer to bind to obj
230 * @param bytes Length of buf
231 * @param buf_free An optional free function to be applied to
232 * buf on deallocation
233 *
234 * This can be called on any LOCI object; it should not need to be
235 * overridden.
236 */
237
238int
239of_object_buffer_bind(of_object_t *obj, uint8_t *buf, int bytes,
240 of_buffer_free_f buf_free)
241{
242 of_wire_object_t *wobj;
243 of_wire_buffer_t *wbuf;
244
Rich Lanee57f0432014-02-19 10:31:53 -0800245 LOCI_ASSERT(buf != NULL);
246 LOCI_ASSERT(bytes > 0);
247 // LOCI_ASSERT(wobj is not bound);
Rich Lanea06d0c32013-03-25 08:52:03 -0700248
249 wobj = &obj->wire_object;
250 MEMSET(wobj, 0, sizeof(*wobj));
251
252 wbuf = of_wire_buffer_new_bind(buf, bytes, buf_free);
253 if (wbuf == NULL) {
254 return OF_ERROR_RESOURCE;
255 }
256
257 wobj->wbuf = wbuf;
258 wobj->owned = 1;
259 obj->length = bytes;
260
261 return OF_ERROR_NONE;
262}
263
264/**
265 * Connect a child to a parent at the wire buffer level
266 *
267 * @param parent The top level object to bind to
268 * @param child The sub-object connecting to the parent
269 * @param offset The offset at which to attach the child RELATIVE
270 * TO THE PARENT in the buffer
271 * @param bytes The amount of the buffer dedicated to the child; see below
272 * @param inc_ref_count Should the ref count of the parent be incremented
273 *
274 * This is used for 'get' accessors for composite types as well as
275 * iterator functions for lists, both read (first/next) and write
276 * (append_init, append_advance).
277 *
278 * Connect a child object to a parent by setting up the child's
279 * wire_object to point to the parent's underlying buffer. The value
280 * of the parameter bytes is important in determining how the child
281 * is initialized:
282 * @li If bytes <= 0, the length and type of the child are not modified;
283 * no additional space is added to the buffer.
284 * @li If bytes > 0, the current wire buffer is grown to
285 * accomodate this many bytes. This is to support append operations.
286 *
287 * If an error is returned, future references to the child object
288 * (until it is reinitialized) are undefined.
289 */
290static void
291object_child_attach(of_object_t *parent, of_object_t *child,
292 int offset, int bytes)
293{
294 of_wire_object_t *c_wobj; /* Pointer to child's wire object */
295 of_wire_buffer_t *wbuf; /* Pointer to common wire buffer manager */
296
297 child->parent = parent;
298 wbuf = parent->wire_object.wbuf;
299
300 /* Set up the child's wire buf to point to same as parent */
301 c_wobj = &child->wire_object;
302 c_wobj->wbuf = wbuf;
303 c_wobj->obj_offset = parent->wire_object.obj_offset + offset;
304 c_wobj->owned = 0;
305
306 /*
307 * bytes determines if this is a read or write setup.
308 * If > 0, grow the buffer to accomodate the space
309 * Otherwise do nothing
310 */
311 if (bytes > 0) { /* Set internal length, request buffer space */
312 int tot_bytes; /* Total bytes to request for buffer if updated */
313
314 /* Set up space for the child in the parent's buffer */
315 tot_bytes = parent->wire_object.obj_offset + offset + bytes;
316
317 of_wire_buffer_grow(wbuf, tot_bytes);
318 child->length = bytes;
319 }
320 /* if bytes == 0 don't do anything */
321}
322
323/**
324 * Check for room in an object's wire buffer.
325 * @param obj The object being checked
326 * @param new_len The desired length
327 * @return Boolean
328 */
329
330int
331of_object_can_grow(of_object_t *obj, int new_len)
332{
333 return OF_OBJECT_ABSOLUTE_OFFSET(obj, new_len) <=
334 WBUF_ALLOC_BYTES(obj->wire_object.wbuf);
335}
336
337/**
338 * Set the xid of a message object
339 * @param obj The object being accessed
340 * @param xid The xid value to store in the wire buffer
341 * @return OF_ERROR_
342 * Since the XID is common across all versions, this is used
343 * for all XID accessors.
344 */
345
346int
347of_object_xid_set(of_object_t *obj, uint32_t xid)
348{
349 of_wire_buffer_t *wbuf;
350
351 if ((wbuf = OF_OBJECT_TO_WBUF(obj)) == NULL) {
352 return OF_ERROR_PARAM;
353 }
354 of_wire_buffer_u32_set(wbuf,
355 OF_OBJECT_ABSOLUTE_OFFSET(obj, OF_MESSAGE_XID_OFFSET), xid);
356 return OF_ERROR_NONE;
357}
358
359/**
360 * Get the xid of a message object
361 * @param obj The object being accessed
362 * @param xid Pointer to where to store the xid value
363 * @return OF_ERROR_
364 * Since the XID is common across all versions, this is used
365 * for all XID accessors.
366 */
367
368int
369of_object_xid_get(of_object_t *obj, uint32_t *xid)
370{
371 of_wire_buffer_t *wbuf;
372
373 if ((wbuf = OF_OBJECT_TO_WBUF(obj)) == NULL) {
374 return OF_ERROR_PARAM;
375 }
376 of_wire_buffer_u32_get(wbuf,
377 OF_OBJECT_ABSOLUTE_OFFSET(obj, OF_MESSAGE_XID_OFFSET), xid);
378 return OF_ERROR_NONE;
379}
380
381/****************************************************************
382 *
383 * Generic list operation implementations
384 *
385 ****************************************************************/
386
387/**
388 * Set up a child for appending to a parent list
389 * @param parent The parent; must be a list object
390 * @param child The child object; must be of type list element
391 * @return OF_ERROR_
392 *
393 * Attaches the wire buffer of the parent to the child by pointing
394 * the child to the end of the parent.
395 *
396 * Set the wire length and type from the child.
397 * Update the parent length adding the current child length
398 *
399 * After calling this function, the child object may be updated
400 * resulting in changes to the parent's wire buffer
401 *
402 */
403
404int
405of_list_append_bind(of_object_t *parent, of_object_t *child)
406{
407 if (parent == NULL || child == NULL ||
408 parent->wire_object.wbuf == NULL) {
409 return OF_ERROR_PARAM;
410 }
411
412 if (!of_object_can_grow(parent, parent->length + child->length)) {
413 return OF_ERROR_RESOURCE;
414 }
415
416 object_child_attach(parent, child, parent->length,
417 child->length);
418
419 /* Update the wire length and type if needed */
420 if (child->wire_length_set) {
421 child->wire_length_set(child, child->length);
422 }
423
424 if (child->wire_type_set) {
Rich Lane92feca82013-12-10 15:57:13 -0800425 child->wire_type_set(child);
Rich Lanea06d0c32013-03-25 08:52:03 -0700426 }
427
428 /* Update the parent's length */
429 of_object_parent_length_update(parent, child->length);
430
431 OF_LENGTH_CHECK_ASSERT(parent);
432
433 return OF_ERROR_NONE;
434}
435
436/**
437 * Generic atomic list append operation
438 * @param list The list to which an item is being appended
439 * @param item THe item to append to the list
440 *
441 * The contents of the item are copied to the end of the list.
442 * Currently assumes the list is at the end of its parent.
443 */
444int
445of_list_append(of_object_t *list, of_object_t *item)
446{
447 int new_len;
448
449 new_len = list->length + item->length;
450
451 if (!of_object_can_grow(list, new_len)) {
452 return OF_ERROR_RESOURCE;
453 }
454
455 of_wire_buffer_grow(list->wire_object.wbuf,
456 OF_OBJECT_ABSOLUTE_OFFSET(list, new_len));
457
458 MEMCPY(OF_OBJECT_BUFFER_INDEX(list, list->length),
459 OF_OBJECT_BUFFER_INDEX(item, 0), item->length);
460
461 /* Update the list's length */
462 of_object_parent_length_update(list, item->length);
463
464 OF_LENGTH_CHECK_ASSERT(list);
465
466 return OF_ERROR_NONE;
467}
468
469/**
470 * Generic list first function
471 * @param parent The parent; must be a list object
472 * @param child The child object; must be of type list element
473 * @return OF_ERROR_RANGE if list is empty
474 * @return OF_ERROR_
475 *
476 * Sets up the child to point to the first element in the list
477 *
478 * Child init must be called before this is called.
479 *
480 * @note TREAT AS PRIVATE
481 * Does not fully initialized object
482 */
483int
484of_list_first(of_object_t *parent, of_object_t *child)
485{
486 if (parent->length == 0) { /* Empty list */
487 return OF_ERROR_RANGE;
488 }
489
490 object_child_attach(parent, child, 0, 0);
491
492 return OF_ERROR_NONE;
493}
494
495/**
496 * Return boolean indicating if child is pointing to last entry in parent
497 * @param parent The parent; must be a list object
498 * @param child The child object; must be of type list element
499 * @return OF_ERROR_RANGE if list is empty
500 * @return OF_ERROR_
501 *
502 */
503static int
504of_list_is_last(of_object_t *parent, of_object_t *child)
505{
506 if (child->wire_object.obj_offset + child->length >=
507 parent->wire_object.obj_offset + parent->length) {
508 return 1;
509 }
510
511 return 0;
512}
513
514/**
515 * Generic list next function
516 * @param parent The parent; must be a list object
517 * @param child The child object; must be of type list element
518 * @return OF_ERROR_RANGE if at end of list
519 * @return OF_ERROR_
520 *
521 * Advances the child to point to the subsequent element in the list.
522 * The wire buffer object must not have been modified since the
523 * previous call to _first or _next.
524 *
525 * @note TREAT AS PRIVATE
526 * Does not fully initialized object
527 */
528int
529of_list_next(of_object_t *parent, of_object_t *child)
530{
531 int offset;
532
Rich Lanee57f0432014-02-19 10:31:53 -0800533 LOCI_ASSERT(child->length > 0);
Rich Lanea06d0c32013-03-25 08:52:03 -0700534
535 /* Get offset of parent */
536 if (of_list_is_last(parent, child)) {
537 return OF_ERROR_RANGE; /* We were on the last object */
538 }
539
540 /* Offset is relative to parent start */
541 offset = (child->wire_object.obj_offset - parent->wire_object.obj_offset) +
542 child->length;
543 object_child_attach(parent, child, offset, 0);
544
545 return OF_ERROR_NONE;
546}
547
548void
549of_object_wire_buffer_steal(of_object_t *obj, uint8_t **buffer)
550{
Rich Lanee57f0432014-02-19 10:31:53 -0800551 LOCI_ASSERT(obj != NULL);
Rich Lanea06d0c32013-03-25 08:52:03 -0700552 of_wire_buffer_steal(obj->wire_object.wbuf, buffer);
553 obj->wire_object.wbuf = NULL;
554}
555
Rich Lane50aa5942013-12-15 16:20:38 -0800556#define _MAX_PARENT_ITERATIONS 4
557/**
558 * Iteratively update parent lengths thru hierarchy
559 * @param obj The object whose length is being updated
560 * @param delta The difference between the current and new lengths
561 *
562 * Note that this includes updating the object itself. It will
563 * iterate thru parents.
564 *
565 * Assumes delta > 0.
566 */
567void
568of_object_parent_length_update(of_object_t *obj, int delta)
569{
570#ifndef NDEBUG
571 int count = 0;
572 of_wire_buffer_t *wbuf; /* For debug asserts only */
573#endif
574
575 while (obj != NULL) {
Rich Lanee57f0432014-02-19 10:31:53 -0800576 LOCI_ASSERT(count++ < _MAX_PARENT_ITERATIONS);
Rich Lane50aa5942013-12-15 16:20:38 -0800577 obj->length += delta;
578 if (obj->wire_length_set != NULL) {
579 obj->wire_length_set(obj, obj->length);
580 }
581#ifndef NDEBUG
582 wbuf = obj->wire_object.wbuf;
583#endif
584
585 /* Asserts for wire length checking */
Rich Lanee57f0432014-02-19 10:31:53 -0800586 LOCI_ASSERT(obj->length + obj->wire_object.obj_offset <=
Rich Lane50aa5942013-12-15 16:20:38 -0800587 WBUF_CURRENT_BYTES(wbuf));
588 if (obj->parent == NULL) {
Rich Lanee57f0432014-02-19 10:31:53 -0800589 LOCI_ASSERT(obj->length + obj->wire_object.obj_offset ==
Rich Lane50aa5942013-12-15 16:20:38 -0800590 WBUF_CURRENT_BYTES(wbuf));
591 }
592
593 obj = obj->parent;
594 }
595}
596
Rich Lanec0e20ff2013-12-15 23:40:31 -0800597/**
598 * Use the type/length from the wire buffer and init the object
599 * @param obj The object being initialized
600 * @param base_object_id If > 0, this indicates the base object
601 * @param max_len If > 0, the max length to expect for the obj
602 * type for inheritance checking
603 * @return OF_ERROR_
604 *
605 * Used for inheritance type objects such as actions and OXMs
606 * The type is checked and if valid, the object is initialized.
607 * Then the length is taken from the buffer.
608 *
609 * Note that the object version must already be properly set.
610 */
611int
612of_object_wire_init(of_object_t *obj, of_object_id_t base_object_id,
613 int max_len)
614{
615 if (obj->wire_type_get != NULL) {
616 of_object_id_t id;
617 obj->wire_type_get(obj, &id);
618 if (!of_wire_id_valid(id, base_object_id)) {
619 return OF_ERROR_PARSE;
620 }
621 obj->object_id = id;
622 /* Call the init function for this object type; do not push to wire */
623 of_object_init_map[id]((of_object_t *)(obj), obj->version, -1, 0);
624 }
625 if (obj->wire_length_get != NULL) {
626 int length;
627 obj->wire_length_get(obj, &length);
628 if (length < 0 || (max_len > 0 && length > max_len)) {
629 return OF_ERROR_PARSE;
630 }
631 obj->length = length;
632 } else {
633 /* @fixme Does this cover everything else? */
634 obj->length = of_object_fixed_len[obj->version][base_object_id];
635 }
636
637 return OF_ERROR_NONE;
638}
639
Rich Lanea06d0c32013-03-25 08:52:03 -0700640/*
641 * Set member:
642 * get_wbuf_extent
643 * find offset of start of member
644 * if offset is at wbuf_extent (append new data)
645 * copy data at extent
646 * update parent length
647 * else
648 * find length of current entry
649 * move from end of current to extent to create (or remove) space
650 * copy data to offset
651 * update my length -- NEED LOCAL INFO TO DO THIS for some cases
652 */
653
654/* Also need: get offset of member for all combinations */
655/* Also need: get length of member for all combinations */
656#if 0
657/**
658 * Append the wire buffer data from src to the end of dst's wire buffer
659 */
660int
661of_object_append_buffer(of_object_t *dst, of_object_t *src)
662{
663 of_wire_buffer_t *s_wbuf, *d_wbuf;
664 int orig_len, dst_offset, src_offset;
665
666 d_wbuf = OF_OBJECT_TO_WBUF(dst);
667 s_wbuf = OF_OBJECT_TO_WBUF(src);
668 dst_offset = dst->wire_object.obj_offset + dst_length;
669 src_offset = src->wire_object.obj_offset;
670 OF_WIRE_BUFFER_INIT_CHECK(d_wbuf, dst_offset + src->length);
671 MEMCPY(OF_WBUF_BUFFER_POINTER(d_wbuf, dst_offset),
672 OF_WBUF_BUFFER_POINTER(s_wbuf, 0), src->length);
673
674 orig_len = dst->length;
675 dst->length += src->length;
676
677 return OF_ERROR_NONE;
678}
679
680/**
681 * Set the length of the actions object in a packet_in object
682 */
683
684int
685of_packet_out_actions_length_set(of_packet_t *obj, int len)
686{
687 if (obj == NULL || obj->object_id != OF_PACKET_IN ||
688 obj->wire_object.wbuf == NULL) {
689 return OF_ERROR_PARAM;
690 }
691
692 obj->actions_len_set(obj, len);
693}
694
695int
696_packet_out_data_offset_get(of_packet_t *obj)
697{
698 if (obj == NULL || obj->object_id != OF_PACKET_IN ||
699 obj->wire_object.wbuf == NULL) {
700 return -1;
701 }
702
703 return OF_PACKET_OUT_FIXED_LENGTH + _packet_out_actions_length_get(obj);
704}
705
706
707/**
708 * Simple length derivation function
709 *
710 * Most variable length fields are alone at the end of a structure.
711 * Their length is a simple calculation, just the total length of
712 * the parent minus the length of the non-variable part of the
713 * parent's class type.
714 *
715 * @param parent The parent object
716 * @param length (out) Where to store the length of the final
717 * variable length member
718 */
719int
720of_object_simple_length_derive(of_object_t *obj, int *length)
721{
722
723}
724#endif