blob: e4e24039207303c413a3fbb1909e44a4297a2a75 [file] [log] [blame]
# Copyright 2013, Big Switch Networks, Inc.
#
# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
# the following special exception:
#
# LOXI Exception
#
# As a special exception to the terms of the EPL, you may distribute libraries
# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
# that copyright and licensing notices generated by LoxiGen are not altered or removed
# from the LoxiGen Libraries and the notice provided below is (i) included in
# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
# documentation for the LoxiGen Libraries, if distributed in binary form.
#
# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
#
# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
# a copy of the EPL at:
#
# http://www.eclipse.org/legal/epl-v10.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# EPL for the specific language governing permissions and limitations
# under the EPL.
"""
@file code_gen.py
Code generation functions for LOCI
"""
import sys
import c_gen.of_g_legacy as of_g
import c_match
from generic_utils import *
from c_gen import flags, type_maps, c_type_maps
import c_gen.loxi_utils_legacy as loxi_utils
import loxi_globals
import c_gen.identifiers as identifiers
# 'property' is for queues. Could be trouble
################################################################
#
# Misc helper functions
#
################################################################
def h_file_to_define(name):
"""
Convert a .h file name to the define used for the header
"""
h_name = name[:-2].upper()
h_name = "_" + h_name + "_H_"
return h_name
def enum_name(cls):
"""
Return the name used for an enum identifier for the given class
@param cls The class name
"""
return loxi_utils.enum_name(cls)
# TODO serialize match outside accessor?
def accessor_return_type(a_type, m_type):
if loxi_utils.accessor_returns_error(a_type, m_type):
return "int WARN_UNUSED_RESULT"
else:
return "void"
def accessor_return_success(a_type, m_type):
if loxi_utils.accessor_returns_error(a_type, m_type):
return "OF_ERROR_NONE"
else:
return ""
################################################################
#
# Per-file generators, mapped to jump table below
#
################################################################
def base_h_gen(out, name):
"""
Generate code for base header file
@param out The file handle to write to
@param name The name of the file
"""
common_top_matter(out, name)
base_h_content(out)
gen_object_enum(out)
out.write("""
/****************************************************************
*
* Experimenter IDs
*
****************************************************************/
""")
for name, val in of_g.experimenter_name_to_id.items():
out.write("#define OF_EXPERIMENTER_ID_%s 0x%08x\n" %
(name.upper(), val))
out.write("""
/****************************************************************
*
* OpenFlow Match version specific and generic defines
*
****************************************************************/
""")
c_match.gen_v4_match_compat(out)
c_match.gen_match_macros(out)
c_match.gen_oxm_defines(out)
out.write("\n#endif /* Base header file */\n")
def identifiers_gen(out, filename):
"""
Generate the macros for LOCI identifiers
@param out The file handle to write to
@param filename The name of the file
"""
common_top_matter(out, filename)
out.write("""
/**
* For each identifier from an OpenFlow header file, a Loxi version
* of the identifier is generated. For example, ofp_port_flood becomes
* OF_PORT_DEST_FLOOD. Loxi provides the following macros related to
* OpenFlow identifiers (using OF_IDENT_ as an example below):
* OF_IDENT_BY_VERSION(version) Get the value for the specific version
* OF_IDENT_SUPPORTED(version) Boolean: Is OF_IDENT defined for version
* OF_IDENT The common value across all versions if defined
* OF_IDENT_GENERIC A unique value across all OF identifiers
*
* For identifiers marked as flags, the following are also defined
* OF_IDENT_SET(flags, version)
* OF_IDENT_CLEAR(flags, version)
* OF_IDENT_TEST(flags, version)
*
* Notes:
*
* OF_IDENT_BY_VERSION(version) returns an undefined value
* if the passed version does not define OF_IDENT. It does not generate an
* error, nor record anything to the log file. If the value is the same
* across all defined versions, the version is ignored.
*
* OF_IDENT is only defined if the value is the same across all
* target LOXI versions FOR WHICH IT IS DEFINED. No error checking is
* done. This allows code to be written without requiring the version
* to be known or referenced when it doesn't matter. It does mean
* that when porting to a new version of OpenFlow, compile errors may
* occur. However, this is an indication that the existing code must
* be updated to account for a change in the semantics with the newly
* supported OpenFlow version.
*
* @fixme Currently we do not handle multi-bit flags or field values; for
* example, OF_TABLE_CONFIG_TABLE_MISS_CONTROLLER is the meaning for
* a zero value in the bits indicated by OF_TABLE_CONFIG_TABLE_MISS_MASK.
*
* @fixme Need to decide (or make a code gen option) on the requirement
* for defining OF_IDENT: Is it that all target versions define it and
* the agree? Or only that the versions which define it agree?
*/
""")
# Build value-by-version parameters and c_code
if len(of_g.target_version_list) > 1: # Supporting more than one version
vbv_params = []
vbv_code = ""
first = True
for version in of_g.target_version_list:
vbv_params.append("value_%s" % of_g.short_version_names[version])
if not first:
vbv_code += "\\\n "
else:
first = False
last_value = "value_%s" % of_g.short_version_names[version]
vbv_code += "((version) == %s) ? (%s) : " % \
(of_g.of_version_wire2name[version], last_value)
# @todo Using last value, can optimize out last ?
vbv_code += "(%s)" % last_value
out.write("""
/**
* @brief True for the special case of all versions supported
*/
#define OF_IDENT_IN_ALL_VERSIONS 1 /* Indicates identifier in all versions */
/**
* @brief General macro to map version to value where values given as params
*
* If unknown version is passed, use the latest version's value
*/
#define OF_VALUE_BY_VERSION(version, %s) \\
(%s)
/**
* @brief Generic set a flag
*/
#define OF_FLAG_SET(flags, mask) (flags) = (flags) | (mask)
/**
* @brief Generic test if a flag is set
*/
#define OF_FLAG_CLEAR(flags, mask) (flags) = (flags) & ~(mask)
/**
* @brief Generic test if a flag is set
*/
#define OF_FLAG_TEST(flags, mask) ((flags) & (mask) ? 1 : 0)
/**
* @brief Set a flag where the value is an enum indication of bit shift
*/
#define OF_FLAG_ENUM_SET(flags, e_val) OF_FLAG_SET(flags, 1 << (e_val))
/**
* @brief Clear a flag where the value is an enum indication of bit shift
*/
#define OF_FLAG_ENUM_CLEAR(flags, e_val) OF_FLAG_CLEAR(flags, 1 << (e_val))
/**
* @brief Test a flag where the value is an enum indication of bit shift
*/
#define OF_FLAG_ENUM_TEST(flags, e_val) OF_FLAG_TEST(flags, 1 << (e_val))
""" % (", ".join(vbv_params), vbv_code))
# For each group of identifiers, bunch ident defns
count = 1
keys = of_g.identifiers_by_group.keys()
keys.sort()
for group in keys:
idents = of_g.identifiers_by_group[group]
idents.sort()
out.write("""
/****************************************************************
* Identifiers from %s
*****************************************************************/
""" % group)
for ident in idents:
info = of_g.identifiers[ident]
keys = info["values_by_version"].keys()
keys.sort()
out.write("""
/*
* Defines for %(ident)s
* Original name %(ofp_name)s
*/
""" % dict(ident=ident, ofp_name=info["ofp_name"]))
# Generate supported versions macro
if len(keys) == len(of_g.target_version_list): # Defined for all
out.write("""\
#define %(ident)s_SUPPORTED(version) OF_IDENT_IN_ALL_VERSIONS
""" % dict(ident=ident))
else: # Undefined for some version
sup_list = []
for version in keys:
sup_list.append("((version) == %s)" %
of_g.of_version_wire2name[version])
out.write("""\
#define %(ident)s_SUPPORTED(version) \\
(%(sup_str)s)
""" % dict(ident=ident, sup_str=" || \\\n ".join(sup_list)))
# Generate value macro
if identifiers.defined_versions_agree(of_g.identifiers,
of_g.target_version_list,
ident):
out.write("""\
#define %(ident)s (%(value)#x)
#define %(ident)s_BY_VERSION(version) (%(value)#x)
""" % dict(ident=ident,value=info["common_value"]))
else: # Values differ between versions
# Generate version check and value by version
val_list = []
# Order of params matters
for version in of_g.target_version_list:
if version in info["values_by_version"]:
value = info["values_by_version"][version]
else:
value = identifiers.UNDEFINED_IDENT_VALUE
val_list.append("%#x" % value)
out.write("""\
#define %(ident)s_BY_VERSION(version) \\
OF_VALUE_BY_VERSION(version, %(val_str)s)
""" % dict(ident=ident, val_str=", ".join(val_list)))
if flags.ident_is_flag(ident):
log("Treating %s as a flag" % ident)
out.write("""
#define %(ident)s_SET(flags, version) \\
OF_FLAG_SET(flags, %(ident)s_BY_VERSION(version))
#define %(ident)s_TEST(flags, version) \\
OF_FLAG_TEST(flags, %(ident)s_BY_VERSION(version))
#define %(ident)s_CLEAR(flags, version) \\
OF_FLAG_CLEAR(flags, %(ident)s_BY_VERSION(version))
""" % dict(ident=ident))
out.write("#define %(ident)s_GENERIC %(count)d\n"
% dict(ident=ident, count=count))
count += 1 # This count should probably be promoted higher
log("Generated %d identifiers" % (count - 1))
out.write("\n#endif /* Loci identifiers header file */\n")
def match_h_gen(out, name):
"""
Generate code for
@param out The file handle to write to
@param name The name of the file
"""
c_match.match_h_top_matter(out, name)
c_match.gen_match_struct(out)
c_match.gen_match_comp(out)
out.write("\n#endif /* Match header file */\n")
def top_h_gen(out, name):
"""
Generate code for
@param out The file handle to write to
@param name The name of the file
"""
external_h_top_matter(out, name)
out.write("""
typedef enum loci_log_level {
LOCI_LOG_LEVEL_TRACE,
LOCI_LOG_LEVEL_VERBOSE,
LOCI_LOG_LEVEL_INFO,
LOCI_LOG_LEVEL_WARN,
LOCI_LOG_LEVEL_ERROR,
LOCI_LOG_LEVEL_MSG
} loci_log_level_t;
/**
* @brief Output a log message.
* @param level The log level.
* @param fname The function name.
* @param file The file name.
* @param line The line number.
* @param format The message format string.
*/
typedef int (*loci_logger_f)(loci_log_level_t level,
const char *fname, const char *file, int line,
const char *format, ...);
/*
* This variable should be set by the user of the library to redirect logs to
* their log infrastructure. The default drops all logs.
*/
extern loci_logger_f loci_logger;
/**
* Map a generic object to the underlying wire buffer
*
* Treat as private
*/
#define OF_OBJECT_TO_MESSAGE(obj) \\
((of_message_t)(WBUF_BUF((obj)->wbuf)))
/**
* Macro for the fixed length part of an object
*
* @param obj The object whose extended length is being calculated
* @returns length in bytes of non-variable part of the object
*/
#define OF_OBJECT_FIXED_LENGTH(obj) \\
(of_object_fixed_len[(obj)->version][(obj)->object_id])
/**
* Return the length of the object beyond its fixed length
*
* @param obj The object whose extended length is being calculated
* @returns length in bytes of non-variable part of the object
*
* Most variable length fields are alone at the end of a structure.
* Their length is a simple calculation, just the total length of
* the parent minus the length of the non-variable part of the
* parent's class type.
*/
#define OF_OBJECT_VARIABLE_LENGTH(obj) \\
((obj)->length - OF_OBJECT_FIXED_LENGTH(obj))
extern int of_wire_buffer_of_match_get(of_object_t *obj, int offset,
of_match_t *match);
extern int of_wire_buffer_of_match_set(of_object_t *obj, int offset,
of_match_t *match, int cur_len);
""")
# gen_base_types(out)
out.write("""
/****************************************************************
*
* Declarations of maps between on-the-wire type values and LOCI identifiers
*
****************************************************************/
int of_object_wire_init(of_object_t *obj, of_object_id_t base_object_id, int max_len);
extern const int *const of_object_fixed_len[OF_VERSION_ARRAY_MAX];
extern const int *const of_object_extra_len[OF_VERSION_ARRAY_MAX];
""")
c_type_maps.gen_type_data_header(out)
c_match.gen_declarations(out)
# @fixme Move debug stuff to own fn
out.write("""
/**
* Macro to check consistency of length for top level objects
*
* If the object has no parent then its length should match the
* underlying wire buffer's current bytes.
*/
#define OF_LENGTH_CHECK_ASSERT(obj) \\
LOCI_ASSERT(((obj)->parent != NULL) || \\
((obj)->wbuf == NULL) || \\
(WBUF_CURRENT_BYTES((obj)->wbuf) == (obj)->length))
#define OF_DEBUG_DUMP
#if defined(OF_DEBUG_DUMP)
extern void dump_match(of_match_t *match);
#endif /* OF_DEBUG_DUMP */
""")
out.write("\n#endif /* Top header file */\n")
def match_c_gen(out, name):
"""
Generate code for
@param out The file handle to write to
@param name The name of the file
"""
c_match.match_c_top_matter(out, name)
c_match.gen_match_conversions(out)
c_match.gen_serialize(out)
c_match.gen_deserialize(out)
################################################################
# Top Matter
################################################################
def common_top_matter(out, name):
loxi_utils.gen_c_copy_license(out)
out.write("""\
/****************************************************************
* File: %s
*
* DO NOT EDIT
*
* This file is automatically generated
*
****************************************************************/
""" % name)
if name[-2:] == ".h":
out.write("""
#if !defined(%(h)s)
#define %(h)s
""" % dict(h=h_file_to_define(name)))
def base_h_content(out):
"""
Generate base header file content
@param out The output file object
"""
# @fixme Supported version should be generated based on input to LoxiGen
out.write("""
/*
* Base OpenFlow definitions. These depend only on standard C headers
*/
#include <string.h>
#include <stdint.h>
/* g++ requires this to pick up PRI, etc.
* See http://gcc.gnu.org/ml/gcc-help/2006-10/msg00223.html
*/
#if !defined(__STDC_FORMAT_MACROS)
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#include <stdlib.h>
#include <assert.h>
#include <loci/loci_idents.h>
/**
* Macro to enable debugging for LOCI.
*
* This enables debug output to stdout.
*/
#define OF_DEBUG_ENABLE
#if defined(OF_DEBUG_ENABLE)
#include <stdio.h> /* Currently for debugging */
#define FIXME(str) do { \\
fprintf(stderr, "%s\\n", str); \\
exit(1); \\
} while (0)
#define debug printf
#else
#define FIXME(str)
#define debug(str, ...)
#endif /* OF_DEBUG_ENABLE */
/**
* The type of a function used by the LOCI dump/show functions to
* output text. Essentially the same signature as fprintf. May
* be called many times per invocation of e.g. of_object_show().
*/
typedef int (*loci_writer_f)(void *cookie, const char *fmt, ...);
/**
* Check if a version is supported
*/
#define OF_VERSION_OKAY(v) ((v) >= OF_VERSION_1_0 && (v) <= OF_VERSION_1_3)
""")
gen_version_enum(out)
out.write("\n")
# for c_name in of_g.ofp_constants:
# val = str(of_g.ofp_constants[c_name])
# out.write("#define %s %s\n" % (c_name, val))
# out.write("\n")
out.write("""
typedef enum of_error_codes_e {
OF_ERROR_NONE = 0,
OF_ERROR_RESOURCE = -1, /* Could not allocate space */
OF_ERROR_PARAM = -2, /* Bad parameter */
OF_ERROR_VERSION = -3, /* Version not supported */
OF_ERROR_RANGE = -4, /* End of list indication */
OF_ERROR_COMPAT = -5, /* Incompatible assignment */
OF_ERROR_PARSE = -6, /* Error in parsing data */
OF_ERROR_INIT = -7, /* Uninitialized data */
OF_ERROR_UNKNOWN = -8 /* Unknown error */
} of_error_codes_t;
#define OF_ERROR_STRINGS "none", \\
"resource", \\
"parameter", \\
"version", \\
"range", \\
"incompatible", \\
"parse", \\
"init", \\
"unknown"
extern const char *const of_error_strings[];
#ifdef __GNUC__
#define LOCI_NORETURN_ATTR __attribute__((__noreturn__))
#else
#define LOCI_NORETURN_ATTR
#endif
extern void loci_assert_fail(
const char *cond,
const char *file,
unsigned int line) LOCI_NORETURN_ATTR;
#ifndef NDEBUG
#define LOCI_ASSERT(val) ((val) ? (void)0 : loci_assert_fail(#val, __FILE__, __LINE__))
#else
#define LOCI_ASSERT(val)
#endif
/*
* Some LOCI object accessors can fail, and it's easy to forget to check.
* On certain compilers we can trigger a warning if the error code
* is ignored.
*/
#ifndef DISABLE_WARN_UNUSED_RESULT
#ifdef __GNUC__
#define WARN_UNUSED_RESULT __attribute__ ((warn_unused_result))
#else
#define WARN_UNUSED_RESULT
#endif
#else
#define WARN_UNUSED_RESULT
#endif
typedef union of_generic_u of_generic_t;
typedef struct of_object_s of_object_t;
/* Define ipv4 address as uint32 */
typedef uint32_t of_ipv4_t;
/* Table ID is the OF standard uint8 */
typedef uint8_t of_table_id_t;
#define OF_MAC_ADDR_BYTES 6
typedef struct of_mac_addr_s {
uint8_t addr[OF_MAC_ADDR_BYTES];
} of_mac_addr_t;
#define OF_IPV6_BYTES 16
typedef struct of_ipv6_s {
uint8_t addr[OF_IPV6_BYTES];
} of_ipv6_t;
extern const of_mac_addr_t of_mac_addr_all_ones;
extern const of_mac_addr_t of_mac_addr_all_zeros;
extern const of_ipv6_t of_ipv6_all_ones;
extern const of_ipv6_t of_ipv6_all_zeros;
/**
* Generic zero and all-ones values of size 16 bytes.
*
* IPv6 is longest data type we worry about for comparisons
*/
#define of_all_zero_value of_ipv6_all_zeros
#define of_all_ones_value of_ipv6_all_ones
/**
* Non-zero/all ones check for arbitrary type of size <= 16 bytes
*/
#define OF_VARIABLE_IS_NON_ZERO(_ptr) \\
(MEMCMP(&of_all_zero_value, (_ptr), sizeof(*(_ptr))))
#define OF_VARIABLE_IS_ALL_ONES(_ptr) \\
(!MEMCMP(&of_all_ones_value, (_ptr), sizeof(*(_ptr))))
/* The octets object is a struct holding pointer and length */
typedef struct of_octets_s {
uint8_t *data;
int bytes;
} of_octets_t;
/* Macro to convert an octet object to a pointer; currently trivial */
#define OF_OCTETS_POINTER_GET(octet_ptr) ((octet_ptr)->data)
#define OF_OCTETS_POINTER_SET(octet_ptr, ptr) (octet_ptr)->data = (ptr)
#define OF_OCTETS_BYTES_GET(octet_ptr) ((octet_ptr)->bytes)
#define OF_OCTETS_BYTES_SET(octet_ptr, bytes) (octet_ptr)->bytes = (bytes)
/* Currently these are categorized as scalars */
typedef char of_port_name_t[OF_MAX_PORT_NAME_LEN];
typedef char of_table_name_t[OF_MAX_TABLE_NAME_LEN];
typedef char of_desc_str_t[OF_DESC_STR_LEN];
typedef char of_serial_num_t[OF_SERIAL_NUM_LEN];
typedef char of_str64_t[64];
typedef struct of_bitmap_128_s {
uint64_t hi;
uint64_t lo;
} of_bitmap_128_t;
typedef struct of_checksum_128_s {
uint64_t hi;
uint64_t lo;
} of_checksum_128_t;
/* These are types which change across versions. */
typedef uint32_t of_port_no_t;
typedef uint16_t of_fm_cmd_t;
typedef uint64_t of_wc_bmap_t;
typedef uint64_t of_match_bmap_t;
#define MEMMOVE(dest, src, bytes) memmove(dest, src, bytes)
#define MEMSET(dest, val, bytes) memset(dest, val, bytes)
#define MEMCPY(dest, src, bytes) memcpy(dest, src, bytes)
#define MEMCMP(a, b, bytes) memcmp(a, b, bytes)
#define MALLOC(bytes) malloc(bytes)
#define FREE(ptr) free(ptr)
/** Try an operation and return on failure. */
#define OF_TRY(op) do { \\
int _rv; \\
if ((_rv = (op)) < 0) { \\
LOCI_LOG_ERROR("ERROR %d at %s:%d\\n", _rv, __FILE__, __LINE__); \\
return _rv; \\
} \\
} while (0)
/* The extent of an OF match object is determined by its length field, but
* aligned to 8 bytes
*/
#define OF_MATCH_BYTES(length) (((length) + 7) & 0xfff8)
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define U16_NTOH(val) (val)
#define U32_NTOH(val) (val)
#define U64_NTOH(val) (val)
#define IPV6_NTOH(dst, src) /* NOTE different syntax; currently no-op */
#define U16_HTON(val) (val)
#define U32_HTON(val) (val)
#define U64_HTON(val) (val)
#define IPV6_HTON(dst, src) /* NOTE different syntax; currently no-op */
#else /* Little Endian */
#define U16_NTOH(val) (((val) >> 8) | (((val) & 0xff) << 8))
#define U32_NTOH(val) ((((val) & 0xff000000) >> 24) | \\
(((val) & 0x00ff0000) >> 8) | \\
(((val) & 0x0000ff00) << 8) | \\
(((val) & 0x000000ff) << 24))
#define U64_NTOH(val) ((((val) & 0xff00000000000000LL) >> 56) | \\
(((val) & 0x00ff000000000000LL) >> 40) | \\
(((val) & 0x0000ff0000000000LL) >> 24) | \\
(((val) & 0x000000ff00000000LL) >> 8) | \\
(((val) & 0x00000000ff000000LL) << 8) | \\
(((val) & 0x0000000000ff0000LL) << 24) | \\
(((val) & 0x000000000000ff00LL) << 40) | \\
(((val) & 0x00000000000000ffLL) << 56))
#define IPV6_NTOH(dst, src) /* NOTE different syntax; currently no-op */
#define U16_HTON(val) U16_NTOH(val)
#define U32_HTON(val) U32_NTOH(val)
#define U64_HTON(val) U64_NTOH(val)
#define IPV6_HTON(dst, src) /* NOTE different syntax; currently no-op */
#endif
/****************************************************************
*
* The following are internal definitions used by the automatically
* generated code. Users should not reference these definitions
* as they may change between versions of this code
*
****************************************************************/
#define OF_MESSAGE_IN_MATCH_POINTER(obj) \\
(WIRE_BUF_POINTER(&((obj)->wire_buffer), OF_MESSAGE_IN_MATCH_OFFSET))
#define OF_MESSAGE_IN_MATCH_LEN(ptr) BUF_U16_GET(&ptr[2])
#define OF_MESSAGE_IN_DATA_OFFSET(obj) \\
(FIXED_LEN + OF_MESSAGE_IN_MATCH_LEN(OF_MESSAGE_IN_MATCH_POINTER(obj)) + 2)
#define OF_MESSAGE_OUT_DATA_OFFSET(obj) \\
(FIXED_LEN + of_message_out_actions_len_get(obj))
""")
def external_h_top_matter(out, name):
"""
Generate top matter for external header file
@param name The name of the output file
@param out The output file object
"""
common_top_matter(out, name)
out.write("""
#include <loci/loci_base.h>
#include <loci/of_message.h>
#include <loci/of_match.h>
#include <loci/of_object.h>
#include <loci/loci_classes.h>
#include <loci/loci_class_metadata.h>
/****************************************************************
*
* This file is divided into the following sections.
*
* A few object specific macros
* Class typedefs (no struct definitions)
* Per-data type accessor function typedefs
* Per-class new/delete function typedefs
* Per-class static delete functions
* Per-class, per-member accessor declarations
* Per-class structure definitions
* Generic union (inheritance) definitions
* Pointer set function declarations
* Some special case macros
*
****************************************************************/
""")
################################################################
#
################################################################
def gen_version_enum(out):
"""
Generate the enumerated type for versions in LoxiGen
@param out The file object to which to write the decs
This just uses the wire versions for now
"""
out.write("""
/**
* Enumeration of OpenFlow versions
*
* The wire protocol numbers are currently used for values of the corresponding
* version identifiers.
*/
typedef enum of_version_e {
OF_VERSION_UNKNOWN = 0,
""")
is_first = True
max = 0
for v in of_g.wire_ver_map:
if is_first:
is_first = False
else:
out.write(",\n")
if v > max:
max = v
out.write(" %s = %d" % (of_g.wire_ver_map[v], v))
out.write("""
} of_version_t;
/**
* @brief Use this when declaring arrays indexed by wire version
*/
#define OF_VERSION_ARRAY_MAX %d
""" % (max + 1))
def gen_object_enum(out):
"""
Generate the enumerated type for object identification in LoxiGen
@param out The file object to which to write the decs
"""
out.write("""
/**
* Enumeration of OpenFlow objects
*
* We enumerate the OpenFlow objects used internally. Note that some
* message types are determined both by an outer type (message type like
* stats_request) and an inner type (port stats). These are different
* messages in ofC.
*
* These values are for internal use only. They will change with
* different versions of ofC.
*/
typedef enum of_object_id_e {
/* Root object type */
OF_OBJECT_INVALID = -1, /* "invalid" return value for mappings */
OF_OBJECT = 0, /* Generic, untyped object */
/* OpenFlow message objects */
""")
last = 0
msg_count = 0
for cls in of_g.ordered_messages:
out.write(" %s = %d,\n" % (enum_name(cls),
of_g.unified[cls]["object_id"]))
msg_count = of_g.unified[cls]["object_id"] + 1
out.write("\n /* Non-message objects */\n")
for cls in of_g.ordered_non_messages:
out.write(" %s = %d,\n" % (enum_name(cls),
of_g.unified[cls]["object_id"]))
last = of_g.unified[cls]["object_id"]
out.write("\n /* List objects */\n")
for cls in of_g.ordered_list_objects:
out.write(" %s = %d,\n" % (enum_name(cls),
of_g.unified[cls]["object_id"]))
last = of_g.unified[cls]["object_id"]
out.write("""
OF_OBJECT_COUNT = %d
} of_object_id_t;
extern const char *const of_object_id_str[];
#define OF_MESSAGE_OBJECT_COUNT %d
""" % ((last + 1), msg_count))
# Generate object type range checking for inheritance classes
# @fixme These should be determined algorithmicly
out.write("""
/*
* Macros to check if an object ID is within an inheritance class range
*/
""")
# Alphabetical order for 'last'
last_ids = dict(of_action="OF_ACTION_STRIP_VLAN",
of_oxm="OF_OXM_VLAN_VID_MASKED",
of_instruction="OF_INSTRUCTION_WRITE_METADATA",
of_queue_prop="OF_QUEUE_PROP_MIN_RATE",
of_table_feature_prop="OF_TABLE_FEATURE_PROP_WRITE_SETFIELD_MISS",
# @FIXME add meter_band ?
)
for cls, last in last_ids.items():
out.write("""
#define %(enum)s_FIRST_ID (%(enum)s + 1)
#define %(enum)s_LAST_ID %(last)s
#define %(enum)s_VALID_ID(id) \\
((id) >= %(enum)s_FIRST_ID && \\
(id) <= %(enum)s_LAST_ID)
""" % dict(enum=enum_name(cls), last=last))
out.write("""
/**
* Function to check a wire ID
* @param object_id The ID to check
* @param base_object_id The inheritance parent, if applicable
* @returns boolean: If base_object_id is an inheritance class, check if
* object_id is valid as a subclass. Otherwise return 1.
*
* Note: Could check that object_id == base_object_id in the
* second case.
*/
static inline int
of_wire_id_valid(int object_id, int base_object_id) {
switch (base_object_id) {
case OF_ACTION:
return OF_ACTION_VALID_ID(object_id);
case OF_OXM:
return OF_OXM_VALID_ID(object_id);
case OF_QUEUE_PROP:
return OF_QUEUE_PROP_VALID_ID(object_id);
case OF_TABLE_FEATURE_PROP:
return OF_TABLE_FEATURE_PROP_VALID_ID(object_id);
case OF_INSTRUCTION:
return OF_INSTRUCTION_VALID_ID(object_id);
default:
break;
}
return 1;
}
""")
################################################################
#
# Internal Utility Functions
#
################################################################
def acc_name(cls, m_name):
"""
Generate the root name of an accessor function for typedef
@param cls The class name
@param m_name The member name
"""
(m_type, get_rv) = get_acc_rv(cls, m_name)
return "%s_%s" % (cls, m_type)
def get_acc_rv(cls, m_name):
"""
Determine the data type and return type for a get accessor.
The return type may be "void" or it may be the accessor type
depending on the system configuration and on the data type.
@param cls The class name
@param m_name The member name
@return A pair (m_type, rv) where m_type is the unified type of the
member and rv is the get_accessor return type
"""
member = of_g.unified[cls]["union"][m_name]
m_type = member["m_type"]
rv = "int"
if m_type[-2:] == "_t":
m_type = m_type[:-2]
return (m_type, rv)
def param_list(cls, m_name, a_type):
"""
Generate the parameter list (no parens) for an a_type accessor
@param cls The class name
@param m_name The member name
@param a_type One of "set" or "get" or TBD
"""
member = of_g.unified[cls]["union"][m_name]
m_type = member["m_type"]
params = ["%s_t *obj" % cls]
if a_type == "set":
if loxi_utils.type_is_scalar(m_type):
params.append("%s %s" % (m_type, m_name))
else:
params.append("%s *%s" % (m_type, m_name))
elif a_type in ["get", "bind"]:
params.append("%s *%s" % (m_type, m_name))
else:
debug("Class %s, name %s Bad param list a_type: %s" %
(cls, m_name, a_type))
sys.exit(1)
return params
def field_ver_get(cls, m_name):
"""
Generate a dict, indexed by wire version, giving a pair (type, offset)
@param cls The class name
@param m_name The name of the class member
If offset is not known for m_name, the type
A dict is used for more convenient indexing.
"""
result = {}
for ver in of_g.unified[cls]:
if type(ver) == type(0): # It's a version
if "use_version" in of_g.unified[cls][ver]: # deref version ref
ref_ver = of_g.unified[cls][ver]["use_version"]
members = of_g.unified[cls][ref_ver]["members"]
else:
members = of_g.unified[cls][ver]["members"]
idx = loxi_utils.member_to_index(m_name, members)
if (idx < 0):
continue # Member not in this version
m_type = members[idx]["m_type"]
offset = members[idx]["offset"]
# If m_type is mixed, get wire version type from global data
if m_type in of_g.of_mixed_types and \
ver in of_g.of_mixed_types[m_type]:
m_type = of_g.of_mixed_types[m_type][ver]
# add version to result list
result[ver] = (m_type, offset)
return result
def v3_match_offset_get(cls):
"""
Return the offset of an OF 1.2 match in an object if it has such;
otherwise return -1
"""
result = field_ver_get(cls, "match")
if of_g.VERSION_1_2 in result:
if result[of_g.VERSION_1_2][0] == "of_match_v3_t":
return result[of_g.VERSION_1_2][1]
return -1
################################################################
#
# OpenFlow Object Definitions
#
################################################################
def gen_generics(out):
for (cls, subclasses) in type_maps.inheritance_map.items():
out.write("""
/**
* Inheritance super class for %(cls)s
*
* This class is the union of %(cls)s classes. You can refer
* to it untyped by refering to the member 'header' whose structure
* is common across all sub-classes.
*/
union %(cls)s_u {
%(cls)s_header_t header; /* Generic instance */
""" % dict(cls=cls))
for subcls in sorted(subclasses):
out.write(" %s_%s_t %s;\n" % (cls, subcls, subcls))
out.write("};\n")
def gen_struct_typedefs(out):
"""
Generate typedefs for all struct objects
@param out The file for output, already open
"""
out.write("\n/* LOCI inheritance parent typedefs */\n")
for cls in type_maps.inheritance_map:
out.write("typedef union %(cls)s_u %(cls)s_t;\n" % dict(cls=cls))
out.write("\n/* LOCI object typedefs */\n")
for cls in of_g.standard_class_order:
if cls in type_maps.inheritance_map:
continue
template = "typedef of_object_t %(cls)s_t;\n"
out.write(template % dict(cls=cls))
out.write("""
/****************************************************************
*
* Additional of_object defines
* These are needed for some static inline ops, so we put them here.
*
****************************************************************/
/* Delete an OpenFlow object without reference to its type */
extern void of_object_delete(of_object_t *obj);
""")
################################################################
#
# List accessor code generation
#
# Currently these all implement copy on read semantics
#
################################################################
def init_call(e_type, obj, ver, length, cw):
"""
Generate the init call given the strings for params
"""
hdr = "" # If inheritance type, coerce to hdr object
obj_name = obj
if e_type in type_maps.inheritance_map:
hdr = "_header"
obj_name = "(%s_header_t *)" % e_type + obj
return """\
%(e_type)s%(hdr)s_init(%(obj_name)s,
%(ver)s, %(length)s, %(cw)s)\
""" % dict(e_type=e_type, hdr=hdr, obj_name=obj_name, ver=ver,
length=length, cw=cw)
def gen_list_first(out, cls, e_type):
"""
Generate the body of a list_first operation
@param cls The class name for which code is being generated
@param e_type The element type of the list
@param out The file to which to write
"""
i_call = init_call(e_type, "obj", "list->version", "0", "1")
if e_type in type_maps.inheritance_map:
len_str = "obj->header.length"
else:
len_str = "obj->length"
out.write("""
/**
* Associate an iterator with a list
* @param list The list to iterate over
* @param obj The list entry iteration pointer
* @return OF_ERROR_RANGE if the list is empty (end of list)
*
* The obj instance is completely initialized. The caller is responsible
* for cleaning up any wire buffers associated with obj before this call
*/
int
%(cls)s_first(%(cls)s_t *list,
%(e_type)s_t *obj)
{
int rv;
%(i_call)s;
if ((rv = of_list_first((of_object_t *)list, (of_object_t *)obj)) < 0) {
return rv;
}
""" % dict(cls=cls, e_type=e_type, i_call=i_call))
# Special case flow_stats_entry lists
out.write("""
of_object_wire_init((of_object_t *) obj, %(u_type)s,
list->length);
if (%(len_str)s == 0) {
return OF_ERROR_PARSE;
}
return rv;
}
""" % dict(cls=cls, e_type=e_type, u_type=enum_name(e_type), len_str=len_str))
def gen_list_next(out, cls, e_type):
"""
Generate the body of a list_next operation
@param cls The class name for which code is being generated
@param e_type The element type of the list
@param out The file to which to write
"""
if e_type in type_maps.inheritance_map:
len_str = "obj->header.length"
else:
len_str = "obj->length"
out.write("""
/**
* Advance an iterator to the next element in a list
* @param list The list being iterated
* @param obj The list entry iteration pointer
* @return OF_ERROR_RANGE if already at the last entry on the list
*
*/
int
%(cls)s_next(%(cls)s_t *list,
%(e_type)s_t *obj)
{
int rv;
if ((rv = of_list_next((of_object_t *)list, (of_object_t *)obj)) < 0) {
return rv;
}
rv = of_object_wire_init((of_object_t *) obj, %(u_type)s,
list->length);
if ((rv == OF_ERROR_NONE) && (%(len_str)s == 0)) {
return OF_ERROR_PARSE;
}
return rv;
}
""" % dict(cls=cls, e_type=e_type, u_type=enum_name(e_type), len_str=len_str))
def gen_list_append(out, cls, e_type):
"""
Generate the body of a list append functions
@param cls The class name for which code is being generated
@param e_type The element type of the list
@param out The file to which to write
"""
out.write("""
/**
* Set up to append an object of type %(e_type)s to an %(cls)s.
* @param list The list that is prepared for append
* @param obj Pointer to object to hold data to append
*
* The obj instance is completely initialized. The caller is responsible
* for cleaning up any wire buffers associated with obj before this call.
*
* See the generic documentation for of_list_append_bind.
*/
int
%(cls)s_append_bind(%(cls)s_t *list,
%(e_type)s_t *obj)
{
return of_list_append_bind((of_object_t *)list, (of_object_t *)obj);
}
/**
* Append an item to a %(cls)s list.
*
* This copies data from item and leaves item untouched.
*
* See the generic documentation for of_list_append.
*/
int
%(cls)s_append(%(cls)s_t *list,
%(e_type)s_t *item)
{
return of_list_append((of_object_t *)list, (of_object_t *)item);
}
""" % dict(cls=cls, e_type=e_type))
def gen_list_accessors(out, cls):
e_type = loxi_utils.list_to_entry_type(cls)
gen_list_first(out, cls, e_type)
gen_list_next(out, cls, e_type)
gen_list_append(out, cls, e_type)
################################################################
#
# Accessor Functions
#
################################################################
def gen_accessor_declarations(out):
"""
Generate the declaration of each version independent accessor
@param out The file to which to write the decs
"""
out.write("""
/****************************************************************
*
* Unified, per-member accessor function declarations
*
****************************************************************/
""")
for cls in of_g.standard_class_order:
if cls in type_maps.inheritance_map:
continue
out.write("\n/* Unified accessor functions for %s */\n" % cls)
for m_name in of_g.ordered_members[cls]:
if m_name in of_g.skip_members:
continue
m_type = loxi_utils.member_base_type(cls, m_name)
base_name = "%s_%s" % (cls, m_name)
gparams = ",\n ".join(param_list(cls, m_name, "get"))
get_ret_type = accessor_return_type("get", m_type)
sparams = ",\n ".join(param_list(cls, m_name, "set"))
set_ret_type = accessor_return_type("set", m_type)
bparams = ",\n ".join(param_list(cls, m_name, "bind"))
bind_ret_type = accessor_return_type("bind", m_type)
if loxi_utils.type_is_of_object(m_type):
# Generate bind accessors, but not get accessor
out.write("""
extern %(set_ret_type)s %(base_name)s_set(
%(sparams)s);
extern %(bind_ret_type)s %(base_name)s_bind(
%(bparams)s);
extern %(m_type)s *%(cls)s_%(m_name)s_get(
%(cls)s_t *obj);
""" % dict(base_name=base_name, sparams=sparams, bparams=bparams,
m_name=m_name, m_type=m_type, cls=cls,
set_ret_type=set_ret_type, bind_ret_type=bind_ret_type))
else:
out.write("""
extern %(set_ret_type)s %(base_name)s_set(
%(sparams)s);
extern %(get_ret_type)s %(base_name)s_get(
%(gparams)s);
""" % dict(base_name=base_name, gparams=gparams, sparams=sparams,
get_ret_type=get_ret_type, set_ret_type=set_ret_type))
if loxi_utils.class_is_list(cls):
e_type = loxi_utils.list_to_entry_type(cls)
out.write("""
extern int %(cls)s_first(
%(cls)s_t *list, %(e_type)s_t *obj);
extern int %(cls)s_next(
%(cls)s_t *list, %(e_type)s_t *obj);
extern int %(cls)s_append_bind(
%(cls)s_t *list, %(e_type)s_t *obj);
extern int %(cls)s_append(
%(cls)s_t *list, %(e_type)s_t *obj);
/**
* Iteration macro for list of type %(cls)s
* @param list Pointer to the list being iterated over of
* type %(cls)s
* @param elt Pointer to an element of type %(e_type)s
* @param rv On exiting the loop will have the value OF_ERROR_RANGE.
*/
#define %(u_cls)s_ITER(list, elt, rv) \\
for ((rv) = %(cls)s_first((list), (elt)); \\
(rv) == OF_ERROR_NONE; \\
(rv) = %(cls)s_next((list), (elt)))
""" % dict(u_cls=cls.upper(), cls=cls, e_type=e_type))
def wire_accessor(m_type, a_type):
"""
Returns the name of the a_type accessor for low level wire buff offset
@param m_type The member type
@param a_type The accessor type (set or get)
"""
# Strip off _t if present
if m_type in of_g.of_base_types:
m_type = of_g.of_base_types[m_type]["short_name"]
if m_type in of_g.of_mixed_types:
m_type = of_g.of_mixed_types[m_type]["short_name"]
if m_type[-2:] == "_t":
m_type = m_type[:-2]
if m_type == "octets":
m_type = "octets_data"
return "of_wire_buffer_%s_%s" % (m_type, a_type)
def get_len_macro(cls, m_name, m_type, version):
"""
Get the length macro for m_type in cls
"""
if m_type.find("of_match") == 0:
return "_WIRE_MATCH_PADDED_LEN(obj, offset)"
if m_type.find("of_list_oxm") == 0:
return "wire_match_len(obj, 0) - 4"
if loxi_utils.class_is_tlv16(m_type):
return "_TLV16_LEN(obj, offset)"
if cls == "of_packet_out" and m_type == "of_list_action_t":
return "_PACKET_OUT_ACTION_LEN(obj)"
if cls == "of_bsn_gentable_entry_add" and m_name == "key":
return "of_object_u16_get(obj, 18)"
if cls == "of_bsn_gentable_entry_desc_stats_entry" and m_name == "key":
return "of_object_u16_get(obj, 2)"
if cls == "of_bsn_gentable_entry_stats_entry" and m_name == "key":
return "of_object_u16_get(obj, 2)"
# Default is everything to the end of the object
return "_END_LEN(obj, offset)"
def gen_accessor_offsets(out, cls, m_name, version, a_type, m_type, offset):
"""
Generate the sub-object offset and length calculations for accessors
@param out File being written
@param m_name Name of member
@param version Wire version being processed
@param a_type The accessor type (set or get)
@param m_type The original member type
@param offset The offset of the object or -1 if not fixed
"""
# determine offset
o_str = "%d" % offset # Default is fixed length
if offset == -1:
# There are currently 4 special cases for this
# In general, get offset and length of predecessor
if (loxi_utils.cls_is_flow_mod(cls) and m_name == "instructions"):
pass
elif (cls == "of_flow_stats_entry" and m_name == "instructions"):
pass
elif (cls == "of_packet_in" and m_name == "data"):
pass
elif (cls == "of_packet_out" and m_name == "data"):
pass
elif (cls == "of_bsn_gentable_entry_add" and m_name == "value"):
pass
elif (cls == "of_bsn_gentable_entry_desc_stats_entry" and m_name == "value"):
pass
elif (cls == "of_bsn_gentable_entry_stats_entry" and m_name == "stats"):
pass
else:
debug("Error: Unknown member with offset == -1")
debug(" cls %s, m_name %s, version %d" % (cls, m_name, version))
sys.exit(1)
o_str = "_%s_%s_OFFSET(obj)" % (cls.upper()[3:], m_name.upper())
out.write("""\
offset = %s;
""" % o_str);
# This could be moved to main body but for version check
if not loxi_utils.type_is_scalar(m_type):
if loxi_utils.class_is_var_len(m_type[:-2], version) or \
m_type == "of_match_t":
len_macro = get_len_macro(cls, m_name, m_type, version)
else:
len_macro = "%d" % of_g.base_length[(m_type[:-2], version)]
out.write(" cur_len = %s;\n" % len_macro)
out.write(" break;\n")
def length_of(m_type, version):
"""
Return the length of a type based on the version
"""
if m_type in of_g.of_mixed_types:
m_type = of_g.of_mixed_types[m_type][version]
if m_type in of_g.of_base_types:
return of_g.of_base_types[m_type]["bytes"]
if (m_type[:-2], version) in of_g.base_length:
return of_g.base_length[(m_type[:-2], version)]
print "Unknown length request", m_type, version
sys.exit(1)
def gen_get_accessor_body(out, cls, m_type, m_name):
"""
Generate the common operations for a get accessor
"""
if loxi_utils.type_is_scalar(m_type):
ver = "" # See if version required for scalar update
if m_type in of_g.of_mixed_types:
ver = "ver, "
out.write("""\
%(wa)s(%(ver)swbuf, abs_offset, %(m_name)s);
""" % dict(wa=wire_accessor(m_type, "get"), ver=ver, m_name=m_name))
if m_type == "of_port_no_t":
out.write(" OF_PORT_NO_VALUE_CHECK(*%s, ver);\n" % m_name)
elif m_type == "of_octets_t":
out.write("""\
LOCI_ASSERT(cur_len + abs_offset <= WBUF_CURRENT_BYTES(wbuf));
%(m_name)s->bytes = cur_len;
%(m_name)s->data = OF_WIRE_BUFFER_INDEX(wbuf, abs_offset);
""" % dict(m_name=m_name))
elif m_type == "of_match_t":
out.write("""
LOCI_ASSERT(cur_len + abs_offset <= WBUF_CURRENT_BYTES(wbuf));
match_octets.bytes = cur_len;
match_octets.data = OF_OBJECT_BUFFER_INDEX(obj, offset);
OF_TRY(of_match_deserialize(ver, %(m_name)s, &match_octets));
""" % dict(m_name=m_name))
elif m_type == "of_oxm_header_t":
out.write("""
/* Initialize child */
%(m_type)s_init(%(m_name)s, obj->version, 0, 1);
/* Attach to parent */
%(m_name)s->parent = (of_object_t *)obj;
%(m_name)s->wbuf = obj->wbuf;
%(m_name)s->obj_offset = abs_offset;
%(m_name)s->length = cur_len;
of_object_wire_init(%(m_name)s, OF_OXM, 0);
""" % dict(m_type=m_type[:-2], m_name=m_name))
elif m_type == "of_bsn_vport_header_t":
out.write("""
/* Initialize child */
%(m_type)s_init(%(m_name)s, obj->version, 0, 1);
/* Attach to parent */
%(m_name)s->parent = (of_object_t *)obj;
%(m_name)s->wbuf = obj->wbuf;
%(m_name)s->obj_offset = abs_offset;
%(m_name)s->length = cur_len;
of_object_wire_init(%(m_name)s, OF_BSN_VPORT, 0);
""" % dict(m_type=m_type[:-2], m_name=m_name))
else:
out.write("""
/* Initialize child */
%(m_type)s_init(%(m_name)s, obj->version, 0, 1);
/* Attach to parent */
%(m_name)s->parent = (of_object_t *)obj;
%(m_name)s->wbuf = obj->wbuf;
%(m_name)s->obj_offset = abs_offset;
%(m_name)s->length = cur_len;
""" % dict(m_type=m_type[:-2], m_name=m_name))
def gen_set_accessor_body(out, cls, m_type, m_name):
"""
Generate the contents of a set accessor
"""
if loxi_utils.type_is_scalar(m_type) or m_type == "of_octets_t":
ver = "" # See if version required for scalar update
if m_type in of_g.of_mixed_types:
ver = "ver, "
cur_len = "" # See if version required for scalar update
if m_type == "of_octets_t":
cur_len = ", cur_len"
out.write("""\
new_len = %(m_name)s->bytes;
of_wire_buffer_grow(wbuf, abs_offset + (new_len - cur_len));
""" % dict(m_name=m_name))
out.write("""\
%(wa)s(%(ver)swbuf, abs_offset, %(m_name)s%(cur_len)s);
""" % dict(wa=wire_accessor(m_type, "set"), ver=ver, cur_len=cur_len,
m_name=m_name))
elif m_type == "of_match_t":
out.write("""
/* Match object */
OF_TRY(of_match_serialize(ver, %(m_name)s, &match_octets));
new_len = match_octets.bytes;
of_wire_buffer_replace_data(wbuf, abs_offset, cur_len,
match_octets.data, new_len);
/* Free match serialized octets */
FREE(match_octets.data);
""" % dict(m_name=m_name))
else: # Other object type
out.write("\n /* LOCI object type */")
# Need to special case OXM list
out.write("""
new_len = %(m_name)s->length;
/* If underlying buffer already shared; nothing to do */
if (obj->wbuf == %(m_name)s->wbuf) {
of_wire_buffer_grow(wbuf, abs_offset + new_len);
/* Verify that the offsets are correct */
LOCI_ASSERT(abs_offset == OF_OBJECT_ABSOLUTE_OFFSET(%(m_name)s, 0));
/* LOCI_ASSERT(new_len == cur_len); */ /* fixme: may fail for OXM lists */
return %(ret_success)s;
}
/* Otherwise, replace existing object in data buffer */
of_wire_buffer_replace_data(wbuf, abs_offset, cur_len,
OF_OBJECT_BUFFER_INDEX(%(m_name)s, 0), new_len);
""" % dict(m_name=m_name, ret_success=accessor_return_success("set", m_type)))
if not loxi_utils.type_is_scalar(m_type):
if cls == "of_packet_out" and m_type == "of_list_action_t":
out.write("""
/* Special case for setting action lengths */
_PACKET_OUT_ACTION_LEN_SET(obj, %(m_name)s->length);
""" % dict(m_name=m_name))
elif cls == "of_bsn_gentable_entry_add" and m_name == "key":
out.write("""
/* Special case for setting key length */
of_object_u16_set(obj, 18, %(m_name)s->length);
""" % dict(m_name=m_name))
elif cls in ["of_bsn_gentable_entry_desc_stats_entry", "of_bsn_gentable_entry_stats_entry"] and m_name == "key":
out.write("""
/* Special case for setting key length */
of_object_u16_set(obj, 2, %(m_name)s->length);
""" % dict(m_name=m_name))
elif m_type not in ["of_match_t", "of_octets_t"]:
out.write("""
/* @fixme Shouldn't this precede copying value's data to buffer? */
of_object_wire_length_set((of_object_t *)%(m_name)s, %(m_name)s->length);
""" % dict(m_name=m_name))
out.write("""
/* Not scalar, update lengths if needed */
delta = new_len - cur_len;
if (delta != 0) {
/* Update parent(s) */
of_object_parent_length_update((of_object_t *)obj, delta);
}
""")
def obj_assert_check(cls):
"""
The body of the assert check for an accessor
We allow all versions of add/delete/modify to use the same accessors
"""
if cls in ["of_flow_modify", "of_flow_modify_strict",
"of_flow_delete", "of_flow_delete_strict",
"of_flow_add"]:
return "IS_FLOW_MOD_SUBTYPE(obj->object_id)"
else:
return "obj->object_id == %s" % cls.upper()
def gen_of_object_get(out, cls, m_name, m_type):
sub_cls = m_type[:-2]
out.write("""
/**
* Create a copy of %(m_name)s into a new variable of type %(m_type)s from
* a %(cls)s instance.
*
* @param obj Pointer to the source of type %(cls)s_t
* @returns A pointer to a new instance of type %(m_type)s whose contents
* match that of %(m_name)s from source
* @returns NULL if an error occurs
*/
%(m_type)s *
%(cls)s_%(m_name)s_get(%(cls)s_t *obj) {
%(m_type)s _%(m_name)s;
%(m_type)s *_%(m_name)s_ptr;
%(cls)s_%(m_name)s_bind(obj, &_%(m_name)s);
_%(m_name)s_ptr = (%(m_type)s *)of_object_dup(&_%(m_name)s);
return _%(m_name)s_ptr;
}
""" % dict(m_name=m_name, m_type=m_type, cls=cls, sub_cls=sub_cls))
def gen_unified_acc_body(out, cls, m_name, ver_type_map, a_type, m_type):
"""
Generate the body of a set or get accessor function
@param out The file to which to write the decs
@param cls The class name
@param m_name The member name
@param ver_type_map Maps (type, offset) pairs to a list of versions
@param a_type The accessor type, set or get
@param m_type The original member type
The type values in ver_type_map are now ignored as we've pushed down
the type munging to the lower level.
This is unified because the version switch case processing is the
same for both set and get
"""
out.write("""{
of_wire_buffer_t *wbuf;
int offset = 0; /* Offset of value relative to the start obj */
int abs_offset; /* Offset of value relative to start of wbuf */
of_version_t ver;
""")
if not loxi_utils.type_is_scalar(m_type):
out.write("""\
int cur_len = 0; /* Current length of object data */
""")
if a_type == "set":
out.write("""\
int new_len, delta; /* For set, need new length and delta */
""")
# For match, need octet string for set/get
if m_type == "of_match_t":
out.write("""\
of_octets_t match_octets; /* Serialized string for match */
""")
out.write("""
LOCI_ASSERT(%(assert_str)s);
ver = obj->version;
wbuf = OF_OBJECT_TO_WBUF(obj);
LOCI_ASSERT(wbuf != NULL);
/* By version, determine offset and current length (where needed) */
switch (ver) {
""" % dict(assert_str=obj_assert_check(cls)))
for first in sorted(ver_type_map):
(prev_t, prev_o) = ver_type_map[first]
prev_len = length_of(prev_t, first)
prev = first
out.write(" case %s:\n" % of_g.wire_ver_map[first])
break
for next in sorted(ver_type_map):
if next == first:
continue
(t, o) = ver_type_map[next]
cur_len = length_of(t, next)
if o == prev_o and cur_len == prev_len:
out.write(" case %s:\n" % of_g.wire_ver_map[next])
continue
gen_accessor_offsets(out, cls, m_name, prev, a_type, m_type, prev_o)
out.write(" case %s:\n" % of_g.wire_ver_map[next])
(prev_t, prev_o, prev_len, prev) = (t, o, cur_len, next)
gen_accessor_offsets(out, cls, m_name, next, a_type, m_type, prev_o)
out.write("""\
default:
LOCI_ASSERT(0);
}
abs_offset = OF_OBJECT_ABSOLUTE_OFFSET(obj, offset);
LOCI_ASSERT(abs_offset >= 0);
""")
if not loxi_utils.type_is_scalar(m_type):
out.write(" LOCI_ASSERT(cur_len >= 0 && cur_len < 64 * 1024);\n")
# Now generate the common accessor code
if a_type in ["get", "bind"]:
gen_get_accessor_body(out, cls, m_type, m_name)
else:
gen_set_accessor_body(out, cls, m_type, m_name)
out.write("""
OF_LENGTH_CHECK_ASSERT(obj);
return %s;
}
""" % accessor_return_success(a_type, m_type))
def gen_of_obj_bind(out, cls, m_name, m_type, ver_type_map):
"""
For generating the bind call for OF sub-objects
"""
params = ",\n ".join(param_list(cls, m_name, "bind"))
out.write("""
/**
* Bind an object of type %(m_type)s to the parent of type %(cls)s for
* member %(m_name)s
* @param obj Pointer to an object of type %(cls)s.
* @param %(m_name)s Pointer to the child object of type
* %(m_type)s to be filled out.
* \ingroup %(cls)s
*
* The parameter %(m_name)s is filled out to point to the same underlying
* wire buffer as its parent.
*
*/
""" % dict(m_name=m_name, cls=cls, m_type=m_type))
ret_type = accessor_return_type("bind", m_type)
out.write("%s\n%s_%s_bind(\n %s)\n" % (ret_type, cls, m_name, params))
gen_unified_acc_body(out, cls, m_name, ver_type_map, "bind", m_type)
def gen_get_accessor(out, cls, m_name, m_type, ver_type_map):
"""
For generating the get call for non- OF sub-objects
"""
params = ",\n ".join(param_list(cls, m_name, "get"))
out.write("""
/**
* Get %(m_name)s from an object of type %(cls)s.
* @param obj Pointer to an object of type %(cls)s.
* @param %(m_name)s Pointer to the child object of type
* %(m_type)s to be filled out.
*
*/
""" % dict(m_name=m_name, cls=cls, m_type=m_type))
ret_type = accessor_return_type("get", m_type)
out.write("%s\n%s_%s_get(\n %s)\n" % (ret_type, cls, m_name, params))
gen_unified_acc_body(out, cls, m_name, ver_type_map, "get", m_type)
def gen_accessor_definitions(out, cls):
for m_name in of_g.ordered_members[cls]:
if m_name in of_g.skip_members:
continue
m_type = loxi_utils.member_base_type(cls, m_name)
ver_type_map = field_ver_get(cls, m_name)
# Generate get/bind pending on member type
# FIXME: Does this do the right thing for match?
if loxi_utils.type_is_of_object(m_type):
gen_of_obj_bind(out, cls, m_name, m_type, ver_type_map)
gen_of_object_get(out, cls, m_name, m_type)
else:
gen_get_accessor(out, cls, m_name, m_type, ver_type_map)
# Now generate set accessor for all objects
params = ",\n ".join(param_list(cls, m_name, "set"))
out.write("""
/**
* Set %(m_name)s in an object of type %(cls)s.
* @param obj Pointer to an object of type %(cls)s.
""" % dict(m_name=m_name, cls=cls, m_type=m_type))
if loxi_utils.type_is_scalar(m_type) or m_type == "of_octets_t":
out.write("""\
* @param %(m_name)s The value to write into the object
*/
""" % dict(m_name=m_name, cls=cls, m_type=m_type))
else:
out.write("""\
* @param %(m_name)s Pointer to the child of type %(m_type)s.
*
* If the child's wire buffer is the same as the parent's, then
* nothing is done as the changes have already been registered in the
* parent. Otherwise, the data in the child's wire buffer is inserted
* into the parent's and the appropriate lengths are updated.
*/
""" % dict(m_name=m_name, cls=cls, m_type=m_type))
ret_type = accessor_return_type("set", m_type)
out.write("%s\n%s_%s_set(\n %s)\n" % (ret_type, cls, m_name, params))
gen_unified_acc_body(out, cls, m_name, ver_type_map, "set", m_type)
################################################################
#
# New/Delete Function Definitions
#
################################################################
################################################################
# Routines to generate the body of new/delete functions
################################################################
def gen_init_fn_body(cls, out):
"""
Generate function body for init function
@param cls The class name for the function
@param out The file to which to write
"""
if cls in type_maps.inheritance_map:
param = "obj_p"
else:
param = "obj"
out.write("""
/**
* Initialize an object of type %(cls)s.
*
* @param obj Pointer to the object to initialize
* @param version The wire version to use for the object
* @param bytes How many bytes in the object
* @param clean_wire Boolean: If true, clear the wire object control struct
*
* If bytes < 0, then the default fixed length is used for the object
*
* This is a "coerce" function that sets up the pointers for the
* accessors properly.
*
* If anything other than 0 is passed in for the buffer size, the underlying
* wire buffer will have 'grow' called.
*/
void
%(cls)s_init(%(cls)s_t *%(param)s,
of_version_t version, int bytes, int clean_wire)
{
""" % dict(cls=cls, param=param))
# Use an extra pointer to deal with inheritance classes
if cls in type_maps.inheritance_map:
out.write("""\
%s_header_t *obj;
obj = &obj_p->header; /* Need instantiable subclass */
""" % cls)
out.write("""
LOCI_ASSERT(of_object_fixed_len[version][%(enum)s] >= 0);
if (clean_wire) {
MEMSET(obj, 0, sizeof(*obj));
}
if (bytes < 0) {
bytes = of_object_fixed_len[version][%(enum)s] + of_object_extra_len[version][%(enum)s];
}
obj->version = version;
obj->length = bytes;
obj->object_id = %(enum)s;
""" % dict(cls=cls, enum=enum_name(cls)))
out.write("""
/* Grow the wire buffer */
if (obj->wbuf != NULL) {
int tot_bytes;
tot_bytes = bytes + obj->obj_offset;
of_wire_buffer_grow(obj->wbuf, tot_bytes);
}
}
""")
## @fixme This should also be updated once there is a map from
# class instance to wire length/type accessors
def gen_wire_push_fn(cls, out):
"""
Generate the calls to push values into the wire buffer
"""
if type_maps.class_is_virtual(cls):
print "Push fn gen called for virtual class " + cls
return
out.write("""
/**
* Helper function to push values into the wire buffer
*/
static inline int
%(cls)s_push_wire_values(%(cls)s_t *obj)
{
""" % dict(cls=cls))
import loxi_globals
uclass = loxi_globals.unified.class_by_name(cls)
if uclass and not uclass.virtual and uclass.has_type_members:
out.write("""
%(cls)s_push_wire_types(obj);
""" % dict(cls=cls))
if loxi_utils.class_is_message(cls):
out.write("""
/* Message obj; set length */
of_message_t msg;
if ((msg = OF_OBJECT_TO_MESSAGE(obj)) != NULL) {
of_message_length_set(msg, obj->length);
}
""" % dict(name = enum_name(cls)))
else: # Not a message
if loxi_utils.class_is_tlv16(cls):
out.write("""
/* TLV obj; set length */
of_tlv16_wire_length_set((of_object_t *)obj, obj->length);
""" % dict(enum=enum_name(cls)))
if loxi_utils.class_is_u16_len(cls) or cls == "of_packet_queue":
out.write("""
of_object_wire_length_set((of_object_t *)obj, obj->length);
""")
if cls == "of_meter_stats":
out.write("""
of_meter_stats_wire_length_set((of_object_t *)obj, obj->length);
""" % dict(enum=enum_name(cls)))
out.write("""
return OF_ERROR_NONE;
}
""")
def gen_new_fn_body(cls, out):
"""
Generate function body for new function
@param cls The class name for the function
@param out The file to which to write
"""
out.write("""
/**
* \\defgroup %(cls)s %(cls)s
*/
""" % dict(cls=cls))
if not type_maps.class_is_virtual(cls):
gen_wire_push_fn(cls, out)
uclass = loxi_globals.unified.class_by_name(cls)
is_fixed_length = uclass and uclass.is_fixed_length
max_length = is_fixed_length and "bytes" or "OF_WIRE_BUFFER_MAX_LENGTH"
out.write("""
/**
* Create a new %(cls)s object
*
* @param version The wire version to use for the object
* @return Pointer to the newly create object or NULL on error
*
* Initializes the new object with it's default fixed length associating
* a new underlying wire buffer.
*
* Use new_from_message to bind an existing message to a message object,
* or a _get function for non-message objects.
*
* \\ingroup %(cls)s
*/
%(cls)s_t *
%(cls)s_new(of_version_t version)
{
%(cls)s_t *obj;
int bytes;
bytes = of_object_fixed_len[version][%(enum)s] + of_object_extra_len[version][%(enum)s];
if ((obj = (%(cls)s_t *)of_object_new(%(max_length)s)) == NULL) {
return NULL;
}
%(cls)s_init(obj, version, bytes, 0);
""" % dict(cls=cls, enum=enum_name(cls), max_length=max_length))
if not type_maps.class_is_virtual(cls):
out.write("""
if (%(cls)s_push_wire_values(obj) < 0) {
FREE(obj);
return NULL;
}
""" % dict(cls=cls))
match_offset = v3_match_offset_get(cls)
if match_offset >= 0:
# Init length field for match object
out.write("""
/* Initialize match TLV for 1.2 */
/* FIXME: Check 1.3 below */
if ((version == OF_VERSION_1_2) || (version == OF_VERSION_1_3)) {
of_object_u16_set((of_object_t *)obj, %(match_offset)d + 2, 4);
}
""" % dict(match_offset=match_offset))
out.write("""
return obj;
}
""" % dict(cls=cls))
def gen_from_message_fn_body(cls, out):
"""
Generate function body for from_message function
@param cls The class name for the function
@param out The file to which to write
"""
out.write("""
/**
* Create a new %(cls)s object and bind it to an existing message
*
* @param msg The message to bind the new object to
* @return Pointer to the newly create object or NULL on error
*
* \ingroup %(cls)s
*/
%(cls)s_t *
%(cls)s_new_from_message(of_message_t msg)
{
%(cls)s_t *obj = NULL;
of_version_t version;
int length;
if (msg == NULL) return NULL;
version = of_message_version_get(msg);
if (!OF_VERSION_OKAY(version)) return NULL;
length = of_message_length_get(msg);
if ((obj = (%(cls)s_t *)of_object_new(-1)) == NULL) {
return NULL;
}
%(cls)s_init(obj, version, 0, 0);
if ((of_object_buffer_bind((of_object_t *)obj, OF_MESSAGE_TO_BUFFER(msg),
length, OF_MESSAGE_FREE_FUNCTION)) < 0) {
FREE(obj);
return NULL;
}
obj->length = length;
obj->version = version;
return obj;
}
""" % dict(cls=cls))
################################################################
# Now the top level generator functions
################################################################
def gen_new_function_declarations(out):
"""
Gerenate the header file declarations for new operators for all classes
@param out The file to which to write the decs
"""
out.write("""
/****************************************************************
*
* New operator declarations
*
* _new: Create a new object for writing; includes init
* _new_from_message: Create a new instance of the object and bind the
* message data to the object
* _init: Initialize and optionally allocate buffer space for an
* automatic instance
*
* _new and _from_message require a delete operation to be called
* on the object.
*
****************************************************************/
""")
for cls in of_g.standard_class_order:
out.write("""
extern %(cls)s_t *
%(cls)s_new(of_version_t version);
""" % dict(cls=cls))
if loxi_utils.class_is_message(cls):
out.write("""extern %(cls)s_t *
%(cls)s_new_from_message(of_message_t msg);
""" % dict(cls=cls))
out.write("""extern void %(cls)s_init(
%(cls)s_t *obj, of_version_t version, int bytes, int clean_wire);
""" % dict(cls=cls))
out.write("""
/****************************************************************
*
* Delete operator static inline definitions.
* These are here for type checking purposes only
*
****************************************************************/
""")
for cls in of_g.standard_class_order:
# if cls in type_maps.inheritance_map:
# continue
out.write("""
/**
* Delete an object of type %(cls)s_t
* @param obj An instance of type %(cls)s_t
*
* \ingroup %(cls)s
*/
static inline void
%(cls)s_delete(%(cls)s_t *obj) {
of_object_delete((of_object_t *)(obj));
}
""" % dict(cls=cls))
out.write("""
typedef void (*of_object_init_f)(of_object_t *obj, of_version_t version,
int bytes, int clean_wire);
extern const of_object_init_f of_object_init_map[];
""")
out.write("""
/****************************************************************
*
* Function pointer initialization functions
* These are part of the "coerce" type casting for objects
*
****************************************************************/
""")
def gen_new_function_definitions(out, cls):
"""
Generate the new operator for all classes
@param out The file to which to write the functions
"""
gen_new_fn_body(cls, out)
gen_init_fn_body(cls, out)
if loxi_utils.class_is_message(cls):
gen_from_message_fn_body(cls, out)
"""
Document generation functions
The main reason this is here is to generate documentation per
accessor that indicates the versions that support the interface.
"""
def gen_accessor_doc(out, name):
"""
Generate documentation for each accessor function that indicates
the versions supporting the accessor.
"""
common_top_matter(out, name)
out.write("/* DOCUMENTATION ONLY */\n")
for cls in of_g.standard_class_order:
if cls in type_maps.inheritance_map:
pass # Check this
out.write("""
/**
* Structure for %(cls)s object. Get/set
* accessors available in all versions unless noted otherwise
*
""" % dict(cls=cls))
if loxi_utils.class_is_list(cls):
out.write("""\
* @param first Function of type %(cls)s_first_f.
* Setup a TBD class object to the first entry in the list
* @param next Function of type %(cls)s_next_f.
* Advance a TBD class object to the next entry in the list
* @param append_bind Function of type %(cls)s_append_bind_f
* Setup a TBD class object for append to the end of the current list
* @param append Function of type @ref %(cls)s_append_f.
* Copy an item to the end of a list
""" % dict(cls=cls))
for m_name in of_g.ordered_members[cls]:
if m_name in of_g.skip_members:
continue
ver_type_map = field_ver_get(cls, m_name)
(m_type, get_rv) = get_acc_rv(cls, m_name)
if len(ver_type_map) == 3:
# ver_string = "Available in all versions"
ver_string = ""
else:
ver_string = "("
for ver in sorted(ver_type_map):
ver_string += " " + of_g.short_version_names[ver]
ver_string += ")."
f_name = acc_name(cls, m_name)
out.write("""\
* @param %(m_name)s_get/set %(ver_string)s
* Accessors for %(m_name)s, a variable of type %(m_type)s. Functions
* are of type %(f_name)s_get_f and _set_f.
*
""" % dict(f_name=f_name, m_name=m_name, ver_string=ver_string, m_type=m_type))
out.write("""\
*/
typedef struct %(cls)s_s %(cls)s_t;
""" % dict(cls=cls))
out.write("#endif /* _LOCI_DOC_H_ */\n")