2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 09:58:01 +00:00
ovs/python/ovs_build_helpers/extract_ofp_fields.py
Robin Jarry bb0dd1135b python: Rename build related code to ovs_build_helpers.
The python/build folder contents are completely unrelated to the ovs
python bindings. These files are only used during the build for various
subsystems (docs, man pages, code generation, etc.).

Having that folder in that location prevents from running:

  cd python && python3 -m build

Which is a way to generate PEP517 compatible source archives and binary
wheel packages.

Rename that folder to ovs_build_helpers which is more explicit. Update
all imports accordingly.

Link: https://peps.python.org/pep-0517/
Link: https://pypi.org/project/build/
Signed-off-by: Robin Jarry <rjarry@redhat.com>
Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2023-08-25 22:05:40 +02:00

422 lines
12 KiB
Python

import sys
import re
line = ""
# Maps from user-friendly version number to its protocol encoding.
VERSION = {
"1.0": 0x01,
"1.1": 0x02,
"1.2": 0x03,
"1.3": 0x04,
"1.4": 0x05,
"1.5": 0x06,
}
VERSION_REVERSE = dict((v, k) for k, v in VERSION.items())
TYPES = {
"u8": (1, False),
"be16": (2, False),
"be32": (4, False),
"MAC": (6, False),
"be64": (8, False),
"be128": (16, False),
"tunnelMD": (124, True),
}
FORMATTING = {
"decimal": ("MFS_DECIMAL", 1, 8),
"hexadecimal": ("MFS_HEXADECIMAL", 1, 127),
"ct state": ("MFS_CT_STATE", 4, 4),
"Ethernet": ("MFS_ETHERNET", 6, 6),
"IPv4": ("MFS_IPV4", 4, 4),
"IPv6": ("MFS_IPV6", 16, 16),
"OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
"OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
"frag": ("MFS_FRAG", 1, 1),
"tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
"TCP flags": ("MFS_TCP_FLAGS", 2, 2),
"packet type": ("MFS_PACKET_TYPE", 4, 4),
}
PREREQS = {
"none": "MFP_NONE",
"Ethernet": "MFP_ETHERNET",
"ARP": "MFP_ARP",
"VLAN VID": "MFP_VLAN_VID",
"IPv4": "MFP_IPV4",
"IPv6": "MFP_IPV6",
"IPv4/IPv6": "MFP_IP_ANY",
"NSH": "MFP_NSH",
"CT": "MFP_CT_VALID",
"MPLS": "MFP_MPLS",
"TCP": "MFP_TCP",
"UDP": "MFP_UDP",
"SCTP": "MFP_SCTP",
"ICMPv4": "MFP_ICMPV4",
"ICMPv6": "MFP_ICMPV6",
"ND": "MFP_ND",
"ND solicit": "MFP_ND_SOLICIT",
"ND advert": "MFP_ND_ADVERT",
}
# Maps a name prefix into an (experimenter ID, class) pair, so:
#
# - Standard OXM classes are written as (0, <oxm_class>)
#
# - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
#
# If a name matches more than one prefix, the longest one is used.
OXM_CLASSES = {
"NXM_OF_": (0, 0x0000, "extension"),
"NXM_NX_": (0, 0x0001, "extension"),
"NXOXM_NSH_": (0x005AD650, 0xFFFF, "extension"),
"OXM_OF_": (0, 0x8000, "standard"),
"OXM_OF_PKT_REG": (0, 0x8001, "standard"),
"ONFOXM_ET_": (0x4F4E4600, 0xFFFF, "standard"),
"ERICOXM_OF_": (0, 0x1000, "extension"),
# This is the experimenter OXM class for Nicira, which is the
# one that OVS would be using instead of NXM_OF_ and NXM_NX_
# if OVS didn't have those grandfathered in. It is currently
# used only to test support for experimenter OXM, since there
# are barely any real uses of experimenter OXM in the wild.
"NXOXM_ET_": (0x00002320, 0xFFFF, "extension"),
}
def oxm_name_to_class(name):
prefix = ""
class_ = None
for p, c in OXM_CLASSES.items():
if name.startswith(p) and len(p) > len(prefix):
prefix = p
class_ = c
return class_
def is_standard_oxm(name):
oxm_vendor, oxm_class, oxm_class_type = oxm_name_to_class(name)
return oxm_class_type == "standard"
def get_line():
global line
global line_number
line = input_file.readline()
line_number += 1
if line == "":
fatal("unexpected end of input")
n_errors = 0
def error(msg):
global n_errors
sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
n_errors += 1
def fatal(msg):
error(msg)
sys.exit(1)
def parse_oxms(s, prefix, n_bytes):
if s == "none":
return ()
return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(","))
match_types = dict()
def parse_oxm(s, prefix, n_bytes):
global match_types
m = re.match(
r"([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$", # noqa: E501
s,
)
if not m:
fatal("%s: syntax error parsing %s" % (s, prefix))
name, oxm_type, of_version, ovs_version = m.groups()
class_ = oxm_name_to_class(name)
if class_ is None:
fatal("unknown OXM class for %s" % name)
oxm_vendor, oxm_class, oxm_class_type = class_
if class_ in match_types:
if oxm_type in match_types[class_]:
fatal(
"duplicate match type for %s (conflicts with %s)"
% (name, match_types[class_][oxm_type])
)
else:
match_types[class_] = dict()
match_types[class_][oxm_type] = name
# Normally the oxm_length is the size of the field, but for experimenter
# OXMs oxm_length also includes the 4-byte experimenter ID.
oxm_length = n_bytes
if oxm_class == 0xFFFF:
oxm_length += 4
header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
if of_version:
if oxm_class_type == "extension":
fatal("%s: OXM extension can't have OpenFlow version" % name)
if of_version not in VERSION:
fatal("%s: unknown OpenFlow version %s" % (name, of_version))
of_version_nr = VERSION[of_version]
if of_version_nr < VERSION["1.2"]:
fatal("%s: claimed version %s predates OXM" % (name, of_version))
else:
if oxm_class_type == "standard":
fatal("%s: missing OpenFlow version number" % name)
of_version_nr = 0
return (header, name, of_version_nr, ovs_version)
def parse_field(mff, comment):
f = {"mff": mff}
# First line of comment is the field name.
m = re.match(
r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0]
)
if not m:
fatal("%s lacks field name" % mff)
f["name"], f["extra_name"] = m.groups()
# Find the last blank line the comment. The field definitions
# start after that.
blank = None
for i in range(len(comment)):
if not comment[i]:
blank = i
if not blank:
fatal("%s: missing blank line in comment" % mff)
d = {}
for key in (
"Type",
"Maskable",
"Formatting",
"Prerequisites",
"Access",
"Prefix lookup member",
"OXM",
"NXM",
"OF1.0",
"OF1.1",
):
d[key] = None
for fline in comment[blank + 1 :]:
m = re.match(r"([^:]+):\s+(.*)\.$", fline)
if not m:
fatal(
"%s: syntax error parsing key-value pair as part of %s"
% (fline, mff)
)
key, value = m.groups()
if key not in d:
fatal("%s: unknown key" % key)
elif key == "Code point":
d[key] += [value]
elif d[key] is not None:
fatal("%s: duplicate key" % key)
d[key] = value
for key, value in d.items():
if not value and key not in (
"OF1.0",
"OF1.1",
"Prefix lookup member",
"Notes",
):
fatal("%s: missing %s" % (mff, key))
m = re.match(r"([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$", d["Type"])
if not m:
fatal("%s: syntax error in type" % mff)
type_ = m.group(1)
if type_ not in TYPES:
fatal("%s: unknown type %s" % (mff, d["Type"]))
f["n_bytes"] = TYPES[type_][0]
if m.group(2):
f["n_bits"] = int(m.group(2))
if f["n_bits"] > f["n_bytes"] * 8:
fatal(
"%s: more bits (%d) than field size (%d)"
% (mff, f["n_bits"], 8 * f["n_bytes"])
)
else:
f["n_bits"] = 8 * f["n_bytes"]
f["variable"] = TYPES[type_][1]
if d["Maskable"] == "no":
f["mask"] = "MFM_NONE"
elif d["Maskable"] == "bitwise":
f["mask"] = "MFM_FULLY"
else:
fatal("%s: unknown maskable %s" % (mff, d["Maskable"]))
fmt = FORMATTING.get(d["Formatting"])
if not fmt:
fatal("%s: unknown format %s" % (mff, d["Formatting"]))
f["formatting"] = d["Formatting"]
if f["n_bytes"] < fmt[1] or f["n_bytes"] > fmt[2]:
fatal(
"%s: %d-byte field can't be formatted as %s"
% (mff, f["n_bytes"], d["Formatting"])
)
f["string"] = fmt[0]
f["prereqs"] = d["Prerequisites"]
if f["prereqs"] not in PREREQS:
fatal("%s: unknown prerequisites %s" % (mff, d["Prerequisites"]))
if d["Access"] == "read-only":
f["writable"] = False
elif d["Access"] == "read/write":
f["writable"] = True
else:
fatal("%s: unknown access %s" % (mff, d["Access"]))
f["OF1.0"] = d["OF1.0"]
if not d["OF1.0"] in (None, "exact match", "CIDR mask"):
fatal("%s: unknown OF1.0 match type %s" % (mff, d["OF1.0"]))
f["OF1.1"] = d["OF1.1"]
if not d["OF1.1"] in (None, "exact match", "bitwise mask"):
fatal("%s: unknown OF1.1 match type %s" % (mff, d["OF1.1"]))
f["OXM"] = parse_oxms(d["OXM"], "OXM", f["n_bytes"]) + parse_oxms(
d["NXM"], "NXM", f["n_bytes"]
)
f["prefix"] = d["Prefix lookup member"]
return f
def extract_ofp_fields(fn):
global file_name
global input_file
global line_number
global line
file_name = fn
input_file = open(file_name)
line_number = 0
fields = []
while True:
get_line()
if re.match("enum.*mf_field_id", line):
break
while True:
get_line()
if (
line.startswith("/*")
or line.startswith(" *")
or line.startswith("#")
or not line
or line.isspace()
):
continue
elif re.match(r"}", line) or re.match(r"\s+MFF_N_IDS", line):
break
# Parse the comment preceding an MFF_ constant into 'comment',
# one line to an array element.
line = line.strip()
if not line.startswith("/*"):
fatal("unexpected syntax between fields")
line = line[1:]
comment = []
end = False
while not end:
line = line.strip()
if line.startswith("*/"):
get_line()
break
if not line.startswith("*"):
fatal("unexpected syntax within field")
line = line[1:]
if line.startswith(" "):
line = line[1:]
if line.startswith(" ") and comment:
continuation = True
line = line.lstrip()
else:
continuation = False
if line.endswith("*/"):
line = line[:-2].rstrip()
end = True
else:
end = False
if continuation:
comment[-1] += " " + line
else:
comment += [line]
get_line()
# Drop blank lines at each end of comment.
while comment and not comment[0]:
comment = comment[1:]
while comment and not comment[-1]:
comment = comment[:-1]
# Parse the MFF_ constant(s).
mffs = []
while True:
m = re.match(r"\s+(MFF_[A-Z0-9_]+),?\s?$", line)
if not m:
break
mffs += [m.group(1)]
get_line()
if not mffs:
fatal("unexpected syntax looking for MFF_ constants")
if len(mffs) > 1 or "<N>" in comment[0]:
for mff in mffs:
# Extract trailing integer.
m = re.match(".*[^0-9]([0-9]+)$", mff)
if not m:
fatal("%s lacks numeric suffix in register group" % mff)
n = m.group(1)
# Search-and-replace <N> within the comment,
# and drop lines that have <x> for x != n.
instance = []
for x in comment:
y = x.replace("<N>", n)
if re.search("<[0-9]+>", y):
if ("<%s>" % n) not in y:
continue
y = re.sub("<[0-9]+>", "", y)
instance += [y.strip()]
fields += [parse_field(mff, instance)]
else:
fields += [parse_field(mffs[0], comment)]
continue
input_file.close()
if n_errors:
sys.exit(1)
return fields