blob: 8fd0aaba17aacae9384a518f624842ab9e94c678 [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
162 object_id = of_message_to_object_id(msg, len);
Rich Lanee57f0432014-02-19 10:31:53 -0800163 LOCI_ASSERT(object_id != OF_OBJECT_INVALID);
Rich Lanea06d0c32013-03-25 08:52:03 -0700164
165 if ((obj = of_object_new(-1)) == NULL) {
166 return NULL;
167 }
168
169 of_object_init_map[object_id](obj, version, 0, 0);
170
171 if (of_object_buffer_bind(obj, OF_MESSAGE_TO_BUFFER(msg), len,
172 OF_MESSAGE_FREE_FUNCTION) < 0) {
173 FREE(obj);
174 return NULL;
175 }
176 obj->length = len;
177 obj->version = version;
178
Rich Lanea06d0c32013-03-25 08:52:03 -0700179 return obj;
180}
181
182/**
Rich Lanec73680c2014-02-22 10:44:28 -0800183 * Parse a message without allocating memory
184 *
185 * @param storage Pointer to an uninitialized of_object_storage_t
186 * @param buf Pointer to the buffer
187 * @param length Length of buf
188 * @returns Pointer to an initialized of_object_t
189 *
190 * The lifetime of the returned object is the minimum of the lifetimes of
191 * 'buf' and 'storage'.
192 */
193
194of_object_t *
195of_object_new_from_message_preallocated(of_object_storage_t *storage,
196 uint8_t *buf, int len)
197{
198 of_object_t *obj = &storage->obj;
199 of_wire_buffer_t *wbuf = &storage->wbuf;
200 of_message_t msg = buf;
201 of_version_t version;
202 of_object_id_t object_id;
203
204 memset(storage, 0, sizeof(*storage));
205
206 version = of_message_version_get(msg);
207 if (!OF_VERSION_OKAY(version)) {
208 return NULL;
209 }
210
211 if (of_validate_message(msg, len) != 0) {
212 LOCI_LOG_ERROR("message validation failed\n");
213 return NULL;
214 }
215
216 object_id = of_message_to_object_id(msg, len);
217 /* Already validated */
218 LOCI_ASSERT(object_id != OF_OBJECT_INVALID);
219
220 of_object_init_map[object_id](obj, version, len, 0);
221
222 obj->wire_object.wbuf = wbuf;
223 wbuf->buf = msg;
224 wbuf->alloc_bytes = len;
225 wbuf->current_bytes = len;
226
227 return obj;
228}
229
230/**
Rich Lanea06d0c32013-03-25 08:52:03 -0700231 * Bind an existing buffer to an LOCI object
232 *
233 * @param obj Pointer to the object to be updated
234 * @param buf Pointer to the buffer to bind to obj
235 * @param bytes Length of buf
236 * @param buf_free An optional free function to be applied to
237 * buf on deallocation
238 *
239 * This can be called on any LOCI object; it should not need to be
240 * overridden.
241 */
242
243int
244of_object_buffer_bind(of_object_t *obj, uint8_t *buf, int bytes,
245 of_buffer_free_f buf_free)
246{
247 of_wire_object_t *wobj;
248 of_wire_buffer_t *wbuf;
249
Rich Lanee57f0432014-02-19 10:31:53 -0800250 LOCI_ASSERT(buf != NULL);
251 LOCI_ASSERT(bytes > 0);
252 // LOCI_ASSERT(wobj is not bound);
Rich Lanea06d0c32013-03-25 08:52:03 -0700253
254 wobj = &obj->wire_object;
255 MEMSET(wobj, 0, sizeof(*wobj));
256
257 wbuf = of_wire_buffer_new_bind(buf, bytes, buf_free);
258 if (wbuf == NULL) {
259 return OF_ERROR_RESOURCE;
260 }
261
262 wobj->wbuf = wbuf;
263 wobj->owned = 1;
264 obj->length = bytes;
265
266 return OF_ERROR_NONE;
267}
268
269/**
270 * Connect a child to a parent at the wire buffer level
271 *
272 * @param parent The top level object to bind to
273 * @param child The sub-object connecting to the parent
274 * @param offset The offset at which to attach the child RELATIVE
275 * TO THE PARENT in the buffer
276 * @param bytes The amount of the buffer dedicated to the child; see below
277 * @param inc_ref_count Should the ref count of the parent be incremented
278 *
279 * This is used for 'get' accessors for composite types as well as
280 * iterator functions for lists, both read (first/next) and write
281 * (append_init, append_advance).
282 *
283 * Connect a child object to a parent by setting up the child's
284 * wire_object to point to the parent's underlying buffer. The value
285 * of the parameter bytes is important in determining how the child
286 * is initialized:
287 * @li If bytes <= 0, the length and type of the child are not modified;
288 * no additional space is added to the buffer.
289 * @li If bytes > 0, the current wire buffer is grown to
290 * accomodate this many bytes. This is to support append operations.
291 *
292 * If an error is returned, future references to the child object
293 * (until it is reinitialized) are undefined.
294 */
295static void
296object_child_attach(of_object_t *parent, of_object_t *child,
297 int offset, int bytes)
298{
299 of_wire_object_t *c_wobj; /* Pointer to child's wire object */
300 of_wire_buffer_t *wbuf; /* Pointer to common wire buffer manager */
301
302 child->parent = parent;
303 wbuf = parent->wire_object.wbuf;
304
305 /* Set up the child's wire buf to point to same as parent */
306 c_wobj = &child->wire_object;
307 c_wobj->wbuf = wbuf;
308 c_wobj->obj_offset = parent->wire_object.obj_offset + offset;
309 c_wobj->owned = 0;
310
311 /*
312 * bytes determines if this is a read or write setup.
313 * If > 0, grow the buffer to accomodate the space
314 * Otherwise do nothing
315 */
316 if (bytes > 0) { /* Set internal length, request buffer space */
317 int tot_bytes; /* Total bytes to request for buffer if updated */
318
319 /* Set up space for the child in the parent's buffer */
320 tot_bytes = parent->wire_object.obj_offset + offset + bytes;
321
322 of_wire_buffer_grow(wbuf, tot_bytes);
323 child->length = bytes;
324 }
325 /* if bytes == 0 don't do anything */
326}
327
328/**
329 * Check for room in an object's wire buffer.
330 * @param obj The object being checked
331 * @param new_len The desired length
332 * @return Boolean
333 */
334
335int
336of_object_can_grow(of_object_t *obj, int new_len)
337{
338 return OF_OBJECT_ABSOLUTE_OFFSET(obj, new_len) <=
339 WBUF_ALLOC_BYTES(obj->wire_object.wbuf);
340}
341
342/**
343 * Set the xid of a message object
344 * @param obj The object being accessed
345 * @param xid The xid value to store in the wire buffer
346 * @return OF_ERROR_
347 * Since the XID is common across all versions, this is used
348 * for all XID accessors.
349 */
350
351int
352of_object_xid_set(of_object_t *obj, uint32_t xid)
353{
354 of_wire_buffer_t *wbuf;
355
356 if ((wbuf = OF_OBJECT_TO_WBUF(obj)) == NULL) {
357 return OF_ERROR_PARAM;
358 }
359 of_wire_buffer_u32_set(wbuf,
360 OF_OBJECT_ABSOLUTE_OFFSET(obj, OF_MESSAGE_XID_OFFSET), xid);
361 return OF_ERROR_NONE;
362}
363
364/**
365 * Get the xid of a message object
366 * @param obj The object being accessed
367 * @param xid Pointer to where to store the xid value
368 * @return OF_ERROR_
369 * Since the XID is common across all versions, this is used
370 * for all XID accessors.
371 */
372
373int
374of_object_xid_get(of_object_t *obj, uint32_t *xid)
375{
376 of_wire_buffer_t *wbuf;
377
378 if ((wbuf = OF_OBJECT_TO_WBUF(obj)) == NULL) {
379 return OF_ERROR_PARAM;
380 }
381 of_wire_buffer_u32_get(wbuf,
382 OF_OBJECT_ABSOLUTE_OFFSET(obj, OF_MESSAGE_XID_OFFSET), xid);
383 return OF_ERROR_NONE;
384}
385
386/****************************************************************
387 *
388 * Generic list operation implementations
389 *
390 ****************************************************************/
391
392/**
393 * Set up a child for appending to a parent list
394 * @param parent The parent; must be a list object
395 * @param child The child object; must be of type list element
396 * @return OF_ERROR_
397 *
398 * Attaches the wire buffer of the parent to the child by pointing
399 * the child to the end of the parent.
400 *
401 * Set the wire length and type from the child.
402 * Update the parent length adding the current child length
403 *
404 * After calling this function, the child object may be updated
405 * resulting in changes to the parent's wire buffer
406 *
407 */
408
409int
410of_list_append_bind(of_object_t *parent, of_object_t *child)
411{
412 if (parent == NULL || child == NULL ||
413 parent->wire_object.wbuf == NULL) {
414 return OF_ERROR_PARAM;
415 }
416
417 if (!of_object_can_grow(parent, parent->length + child->length)) {
418 return OF_ERROR_RESOURCE;
419 }
420
421 object_child_attach(parent, child, parent->length,
422 child->length);
423
424 /* Update the wire length and type if needed */
425 if (child->wire_length_set) {
426 child->wire_length_set(child, child->length);
427 }
428
429 if (child->wire_type_set) {
Rich Lane92feca82013-12-10 15:57:13 -0800430 child->wire_type_set(child);
Rich Lanea06d0c32013-03-25 08:52:03 -0700431 }
432
433 /* Update the parent's length */
434 of_object_parent_length_update(parent, child->length);
435
436 OF_LENGTH_CHECK_ASSERT(parent);
437
438 return OF_ERROR_NONE;
439}
440
441/**
442 * Generic atomic list append operation
443 * @param list The list to which an item is being appended
444 * @param item THe item to append to the list
445 *
446 * The contents of the item are copied to the end of the list.
447 * Currently assumes the list is at the end of its parent.
448 */
449int
450of_list_append(of_object_t *list, of_object_t *item)
451{
452 int new_len;
453
454 new_len = list->length + item->length;
455
456 if (!of_object_can_grow(list, new_len)) {
457 return OF_ERROR_RESOURCE;
458 }
459
460 of_wire_buffer_grow(list->wire_object.wbuf,
461 OF_OBJECT_ABSOLUTE_OFFSET(list, new_len));
462
463 MEMCPY(OF_OBJECT_BUFFER_INDEX(list, list->length),
464 OF_OBJECT_BUFFER_INDEX(item, 0), item->length);
465
466 /* Update the list's length */
467 of_object_parent_length_update(list, item->length);
468
469 OF_LENGTH_CHECK_ASSERT(list);
470
471 return OF_ERROR_NONE;
472}
473
474/**
475 * Generic list first function
476 * @param parent The parent; must be a list object
477 * @param child The child object; must be of type list element
478 * @return OF_ERROR_RANGE if list is empty
479 * @return OF_ERROR_
480 *
481 * Sets up the child to point to the first element in the list
482 *
483 * Child init must be called before this is called.
484 *
485 * @note TREAT AS PRIVATE
486 * Does not fully initialized object
487 */
488int
489of_list_first(of_object_t *parent, of_object_t *child)
490{
491 if (parent->length == 0) { /* Empty list */
492 return OF_ERROR_RANGE;
493 }
494
495 object_child_attach(parent, child, 0, 0);
496
497 return OF_ERROR_NONE;
498}
499
500/**
501 * Return boolean indicating if child is pointing to last entry in parent
502 * @param parent The parent; must be a list object
503 * @param child The child object; must be of type list element
504 * @return OF_ERROR_RANGE if list is empty
505 * @return OF_ERROR_
506 *
507 */
508static int
509of_list_is_last(of_object_t *parent, of_object_t *child)
510{
511 if (child->wire_object.obj_offset + child->length >=
512 parent->wire_object.obj_offset + parent->length) {
513 return 1;
514 }
515
516 return 0;
517}
518
519/**
520 * Generic list next function
521 * @param parent The parent; must be a list object
522 * @param child The child object; must be of type list element
523 * @return OF_ERROR_RANGE if at end of list
524 * @return OF_ERROR_
525 *
526 * Advances the child to point to the subsequent element in the list.
527 * The wire buffer object must not have been modified since the
528 * previous call to _first or _next.
529 *
530 * @note TREAT AS PRIVATE
531 * Does not fully initialized object
532 */
533int
534of_list_next(of_object_t *parent, of_object_t *child)
535{
536 int offset;
537
Rich Lanee57f0432014-02-19 10:31:53 -0800538 LOCI_ASSERT(child->length > 0);
Rich Lanea06d0c32013-03-25 08:52:03 -0700539
540 /* Get offset of parent */
541 if (of_list_is_last(parent, child)) {
542 return OF_ERROR_RANGE; /* We were on the last object */
543 }
544
545 /* Offset is relative to parent start */
546 offset = (child->wire_object.obj_offset - parent->wire_object.obj_offset) +
547 child->length;
548 object_child_attach(parent, child, offset, 0);
549
550 return OF_ERROR_NONE;
551}
552
553void
554of_object_wire_buffer_steal(of_object_t *obj, uint8_t **buffer)
555{
Rich Lanee57f0432014-02-19 10:31:53 -0800556 LOCI_ASSERT(obj != NULL);
Rich Lanea06d0c32013-03-25 08:52:03 -0700557 of_wire_buffer_steal(obj->wire_object.wbuf, buffer);
558 obj->wire_object.wbuf = NULL;
559}
560
Rich Lane50aa5942013-12-15 16:20:38 -0800561#define _MAX_PARENT_ITERATIONS 4
562/**
563 * Iteratively update parent lengths thru hierarchy
564 * @param obj The object whose length is being updated
565 * @param delta The difference between the current and new lengths
566 *
567 * Note that this includes updating the object itself. It will
568 * iterate thru parents.
569 *
570 * Assumes delta > 0.
571 */
572void
573of_object_parent_length_update(of_object_t *obj, int delta)
574{
575#ifndef NDEBUG
576 int count = 0;
577 of_wire_buffer_t *wbuf; /* For debug asserts only */
578#endif
579
580 while (obj != NULL) {
Rich Lanee57f0432014-02-19 10:31:53 -0800581 LOCI_ASSERT(count++ < _MAX_PARENT_ITERATIONS);
Rich Lane50aa5942013-12-15 16:20:38 -0800582 obj->length += delta;
583 if (obj->wire_length_set != NULL) {
584 obj->wire_length_set(obj, obj->length);
585 }
586#ifndef NDEBUG
587 wbuf = obj->wire_object.wbuf;
588#endif
589
590 /* Asserts for wire length checking */
Rich Lanee57f0432014-02-19 10:31:53 -0800591 LOCI_ASSERT(obj->length + obj->wire_object.obj_offset <=
Rich Lane50aa5942013-12-15 16:20:38 -0800592 WBUF_CURRENT_BYTES(wbuf));
593 if (obj->parent == NULL) {
Rich Lanee57f0432014-02-19 10:31:53 -0800594 LOCI_ASSERT(obj->length + obj->wire_object.obj_offset ==
Rich Lane50aa5942013-12-15 16:20:38 -0800595 WBUF_CURRENT_BYTES(wbuf));
596 }
597
598 obj = obj->parent;
599 }
600}
601
Rich Lanec0e20ff2013-12-15 23:40:31 -0800602/**
603 * Use the type/length from the wire buffer and init the object
604 * @param obj The object being initialized
605 * @param base_object_id If > 0, this indicates the base object
606 * @param max_len If > 0, the max length to expect for the obj
607 * type for inheritance checking
608 * @return OF_ERROR_
609 *
610 * Used for inheritance type objects such as actions and OXMs
611 * The type is checked and if valid, the object is initialized.
612 * Then the length is taken from the buffer.
613 *
614 * Note that the object version must already be properly set.
615 */
616int
617of_object_wire_init(of_object_t *obj, of_object_id_t base_object_id,
618 int max_len)
619{
620 if (obj->wire_type_get != NULL) {
621 of_object_id_t id;
622 obj->wire_type_get(obj, &id);
623 if (!of_wire_id_valid(id, base_object_id)) {
624 return OF_ERROR_PARSE;
625 }
626 obj->object_id = id;
627 /* Call the init function for this object type; do not push to wire */
628 of_object_init_map[id]((of_object_t *)(obj), obj->version, -1, 0);
629 }
630 if (obj->wire_length_get != NULL) {
631 int length;
632 obj->wire_length_get(obj, &length);
633 if (length < 0 || (max_len > 0 && length > max_len)) {
634 return OF_ERROR_PARSE;
635 }
636 obj->length = length;
637 } else {
638 /* @fixme Does this cover everything else? */
639 obj->length = of_object_fixed_len[obj->version][base_object_id];
640 }
641
642 return OF_ERROR_NONE;
643}
644
Rich Lanea06d0c32013-03-25 08:52:03 -0700645/*
646 * Set member:
647 * get_wbuf_extent
648 * find offset of start of member
649 * if offset is at wbuf_extent (append new data)
650 * copy data at extent
651 * update parent length
652 * else
653 * find length of current entry
654 * move from end of current to extent to create (or remove) space
655 * copy data to offset
656 * update my length -- NEED LOCAL INFO TO DO THIS for some cases
657 */
658
659/* Also need: get offset of member for all combinations */
660/* Also need: get length of member for all combinations */
661#if 0
662/**
663 * Append the wire buffer data from src to the end of dst's wire buffer
664 */
665int
666of_object_append_buffer(of_object_t *dst, of_object_t *src)
667{
668 of_wire_buffer_t *s_wbuf, *d_wbuf;
669 int orig_len, dst_offset, src_offset;
670
671 d_wbuf = OF_OBJECT_TO_WBUF(dst);
672 s_wbuf = OF_OBJECT_TO_WBUF(src);
673 dst_offset = dst->wire_object.obj_offset + dst_length;
674 src_offset = src->wire_object.obj_offset;
675 OF_WIRE_BUFFER_INIT_CHECK(d_wbuf, dst_offset + src->length);
676 MEMCPY(OF_WBUF_BUFFER_POINTER(d_wbuf, dst_offset),
677 OF_WBUF_BUFFER_POINTER(s_wbuf, 0), src->length);
678
679 orig_len = dst->length;
680 dst->length += src->length;
681
682 return OF_ERROR_NONE;
683}
684
685/**
686 * Set the length of the actions object in a packet_in object
687 */
688
689int
690of_packet_out_actions_length_set(of_packet_t *obj, int len)
691{
692 if (obj == NULL || obj->object_id != OF_PACKET_IN ||
693 obj->wire_object.wbuf == NULL) {
694 return OF_ERROR_PARAM;
695 }
696
697 obj->actions_len_set(obj, len);
698}
699
700int
701_packet_out_data_offset_get(of_packet_t *obj)
702{
703 if (obj == NULL || obj->object_id != OF_PACKET_IN ||
704 obj->wire_object.wbuf == NULL) {
705 return -1;
706 }
707
708 return OF_PACKET_OUT_FIXED_LENGTH + _packet_out_actions_length_get(obj);
709}
710
711
712/**
713 * Simple length derivation function
714 *
715 * Most variable length fields are alone at the end of a structure.
716 * Their length is a simple calculation, just the total length of
717 * the parent minus the length of the non-variable part of the
718 * parent's class type.
719 *
720 * @param parent The parent object
721 * @param length (out) Where to store the length of the final
722 * variable length member
723 */
724int
725of_object_simple_length_derive(of_object_t *obj, int *length)
726{
727
728}
729#endif