mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-08-06 17:03:17 +00:00
Establish the Lua dissector action/device/etc tables as authoritative
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pre-commit: keep the C++ Device/Action enums in sync with the Lua dissector
|
||||
# (scripts/sync_avclan_enums.py in its default lint + drift-check mode). A
|
||||
# non-zero exit blocks the commit; fix with `scripts/sync_avclan_enums.py --fix`.
|
||||
#
|
||||
# Installed by the top-level CMake configure step, which points core.hooksPath
|
||||
# at this directory. Bypass a single commit with `git commit --no-verify`.
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(git rev-parse --show-toplevel)"
|
||||
exec python3 "$repo_root/scripts/sync_avclan_enums.py"
|
||||
@@ -21,6 +21,31 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Install the repository's git hooks (see .githooks/) by pointing core.hooksPath
|
||||
# at them at configure time.
|
||||
# Opt out with -DINSTALL_GIT_HOOKS=OFF; a custom core.hooksPath is left untouched,
|
||||
# and `git commit --no-verify` bypasses a single commit.
|
||||
option(INSTALL_GIT_HOOKS "Point git core.hooksPath at .githooks/" ON)
|
||||
if(INSTALL_GIT_HOOKS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
|
||||
find_program(GIT_EXECUTABLE git)
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" config --get core.hooksPath
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE _git_hooks_path
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET)
|
||||
if(_git_hooks_path STREQUAL "" OR _git_hooks_path STREQUAL ".githooks")
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" config core.hooksPath .githooks
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
message(STATUS "git hooks: core.hooksPath -> .githooks")
|
||||
else()
|
||||
message(STATUS "git hooks: core.hooksPath already '${_git_hooks_path}'; leaving as-is")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Hardware target (port) selection. The chosen target/<name>/ directory supplies
|
||||
# the port implementation sources, the per-target include path, and all
|
||||
# hardware-specific build configuration (compile defs/options, device-pack
|
||||
|
||||
@@ -219,7 +219,7 @@ local known_devices = {
|
||||
-- [0x02] = "COM_EXT", -- @GadgetNutt
|
||||
[0x11] = "COMMUNICATION v1", -- @GadgetNutt reads 0x11 as COM_MASTER
|
||||
[0x12] = "COMMUNICATION v2",
|
||||
[0x21] = "SW", -- @GadgetNutt reads 0x21 as SW_AUDIO
|
||||
[0x21] = "SW_AUDIO", -- @GadgetNutt name
|
||||
[0x23] = "SW_NAME", -- @GadgetNutt reads 0x23 as SW_SHIFT
|
||||
[0x24] = "SW_CONVERTING",
|
||||
[0x25] = "CMD_SW", -- @GadgetNutt reads 0x25 as SW
|
||||
@@ -244,7 +244,7 @@ local known_devices = {
|
||||
[0x5F] = "TRIP_INFO_DRAWING",
|
||||
[0x60] = "TUNER",
|
||||
[0x61] = "TAPE_DECK",
|
||||
[0x62] = "CD",
|
||||
[0x62] = "CD_SINGLE",
|
||||
[0x63] = "CD_CHANGER",
|
||||
-- [0x64] = "MD", -- @GadgetNutt (MiniDisc)
|
||||
-- [0x65] = "MD_CH",
|
||||
|
||||
Executable
+541
@@ -0,0 +1,541 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the C++ Device/Action enums in sync with the Lua dissector tables.
|
||||
|
||||
The Wireshark dissector in scripts/packet-analysis/avclan_plugin.lua is the
|
||||
authoritative source of truth for AVC-LAN device and action names. Its
|
||||
`known_devices` / `known_actions` tables and the firmware's C++ enums
|
||||
(`enum class Device` in src/avclan/device.hpp, `enum ... Action` in
|
||||
src/avclan/avclan.h) encode the same numeric values and drift apart by hand.
|
||||
|
||||
This tool reconciles them, matching entries *by value* so a value renamed in
|
||||
the dissector is propagated into C++ (and to every reference under src/), not
|
||||
just added/removed.
|
||||
|
||||
Default (no flags) lint the dissector names AND report enum drift; write
|
||||
nothing; exit non-zero if either has problems. This is
|
||||
what the pre-commit hook runs.
|
||||
--fix lint first (never fold an invalid name into C++), then
|
||||
apply: rewrite the enum bodies and search-replace the
|
||||
renamed members across src/.
|
||||
--lint validate the dissector names only.
|
||||
--verbose list every reference-site replacement made by --fix.
|
||||
|
||||
Reconciliation rules (see the project plan for the full rationale):
|
||||
|
||||
* Match by value. Names are compared with a case/separator-insensitive
|
||||
canonical key, so a case-only difference (LIST_FUNCTIONS_REQ vs
|
||||
List_Functions_Req) is NOT a change -- the existing C++ spelling is kept.
|
||||
* A beyond-case difference is a rename: the C++ member is renamed to the
|
||||
house-style form of the dissector name and all references are updated.
|
||||
* Full mirror: every active dissector entry missing from C++ is added; if C++
|
||||
carries a value the dissector lacks, that is an error (reported, not
|
||||
dropped). Commented-out ("pencil-in") Lua entries are ignored.
|
||||
* New/renamed members follow each enum's house style: Device -> UPPER_SNAKE,
|
||||
Action -> Title_Case (with a small acronym allowlist kept uppercase).
|
||||
* New members are inserted at their position in the Lua table (adjacent to
|
||||
their nearest Lua neighbour that already exists in C++).
|
||||
|
||||
The dissector is expected to keep *approximately* valid identifiers: spaces and
|
||||
punctuation are silently converted to single underscores. Only two hard rules
|
||||
are enforced by --lint (a name must not start with a digit, and must be at
|
||||
least 3 characters); the 3-char floor also keeps the reference search-replace
|
||||
from colliding on 2-character tokens.
|
||||
|
||||
Runs as a pre-commit hook via .githooks/pre-commit; that directory is wired up
|
||||
by the top-level CMake configure step (core.hooksPath). `git commit --no-verify`
|
||||
still bypasses it.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# ---- locations -------------------------------------------------------------
|
||||
|
||||
LUA_REL = os.path.join("scripts", "packet-analysis", "avclan_plugin.lua")
|
||||
SRC_REL = "src"
|
||||
SRC_EXTS = (".c", ".cc", ".h", ".hpp")
|
||||
|
||||
# Enum specs are matched to a Lua table by NAME; addresses are auto-detected
|
||||
# separately (no Address enum exists yet). `style` picks the identifier casing,
|
||||
# `hexfmt` the literal format for freshly added members (matched per file).
|
||||
ENUM_SPECS = [
|
||||
{
|
||||
"name": "Action",
|
||||
"path": os.path.join("src", "avclan", "avclan.h"),
|
||||
"lua_table": "known_actions",
|
||||
"style": "action",
|
||||
"hexfmt": "0x%02x",
|
||||
},
|
||||
{
|
||||
"name": "Device",
|
||||
"path": os.path.join("src", "avclan", "device.hpp"),
|
||||
"lua_table": "known_devices",
|
||||
"style": "device",
|
||||
"hexfmt": "0x%02X",
|
||||
},
|
||||
]
|
||||
|
||||
# Tokens kept fully uppercase when Title-casing an Action name, matching the
|
||||
# existing hand-written style (CD_Enable_Repeat, Report_TOC). Note LAN -> Lan.
|
||||
ACTION_ACRONYMS = {"CD", "TOC"}
|
||||
|
||||
# ---- name helpers ----------------------------------------------------------
|
||||
|
||||
|
||||
def sanitize(name):
|
||||
"""Convert an approximate identifier to a valid one: non-word chars become
|
||||
underscores and runs of underscores collapse to one."""
|
||||
s = re.sub(r"[^0-9A-Za-z_]", "_", name)
|
||||
s = re.sub(r"_+", "_", s)
|
||||
return s.strip("_")
|
||||
|
||||
|
||||
def canonical(name):
|
||||
"""Case/separator-insensitive key used to match names by identity."""
|
||||
return re.sub(r"[^0-9A-Za-z]", "", name).upper()
|
||||
|
||||
|
||||
def style_device(name):
|
||||
return sanitize(name).upper()
|
||||
|
||||
|
||||
def style_action(name):
|
||||
parts = [p for p in sanitize(name).split("_") if p]
|
||||
out = []
|
||||
for p in parts:
|
||||
if p.upper() in ACTION_ACRONYMS:
|
||||
out.append(p.upper())
|
||||
else:
|
||||
out.append(p[:1].upper() + p[1:].lower())
|
||||
return "_".join(out)
|
||||
|
||||
|
||||
STYLERS = {"device": style_device, "action": style_action}
|
||||
|
||||
# ---- parsing ---------------------------------------------------------------
|
||||
|
||||
# An active Lua entry: ` [0x11] = "NAME",` with no leading `--`. The regex
|
||||
# anchors `[` right after the indentation, so commented `-- [0x..]` lines and
|
||||
# prose comment lines never match. The name is taken from inside the quotes,
|
||||
# so trailing `-- ...` comments are ignored.
|
||||
LUA_ENTRY_RE = re.compile(r'^\s*\[\s*0x([0-9A-Fa-f]+)\s*\]\s*=\s*"([^"]*)"')
|
||||
LUA_TABLE_OPEN = "local %s = {"
|
||||
LUA_TABLE_CLOSE_RE = re.compile(r"^\s*\}")
|
||||
|
||||
# A C++ enum member: ` Name = 0x11,` (or decimal). `//`-commented members are
|
||||
# skipped by the caller, so they count as absent.
|
||||
CPP_MEMBER_RE = re.compile(r"^\s*([A-Za-z_]\w*)\s*=\s*(0x[0-9A-Fa-f]+|\d+)")
|
||||
|
||||
|
||||
class LuaEntry:
|
||||
def __init__(self, value, name, lineno):
|
||||
self.value = value
|
||||
self.name = name
|
||||
self.lineno = lineno
|
||||
|
||||
|
||||
class CppMember:
|
||||
def __init__(self, value, name, idx):
|
||||
self.value = value
|
||||
self.name = name
|
||||
self.idx = idx # 0-based index into the file's line list
|
||||
|
||||
|
||||
def read_lines(path):
|
||||
with open(path, "r") as f:
|
||||
return f.readlines()
|
||||
|
||||
|
||||
def parse_lua_table(lines, table_name):
|
||||
"""Return the active entries of a Lua table, in file order."""
|
||||
open_marker = LUA_TABLE_OPEN % table_name
|
||||
start = None
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith(open_marker):
|
||||
start = i
|
||||
break
|
||||
if start is None:
|
||||
return None
|
||||
entries = []
|
||||
for j in range(start + 1, len(lines)):
|
||||
line = lines[j]
|
||||
if LUA_TABLE_CLOSE_RE.match(line):
|
||||
break
|
||||
if line.lstrip().startswith("--"):
|
||||
continue
|
||||
m = LUA_ENTRY_RE.match(line)
|
||||
if m:
|
||||
entries.append(LuaEntry(int(m.group(1), 16), m.group(2), j + 1))
|
||||
return entries
|
||||
|
||||
|
||||
def find_enum_body(lines, enum_name):
|
||||
"""Locate `enum [macro] <name> ... {` and its closing brace. Returns
|
||||
(open_idx, close_idx) as line indices, or None."""
|
||||
open_re = re.compile(r"^\s*enum\b[^{;]*\b%s\b[^{;]*\{" % re.escape(enum_name))
|
||||
for i, line in enumerate(lines):
|
||||
if open_re.match(line):
|
||||
for j in range(i + 1, len(lines)):
|
||||
if LUA_TABLE_CLOSE_RE.match(lines[j]): # `^\s*}` works for C++ too
|
||||
return i, j
|
||||
return i, len(lines) - 1
|
||||
return None
|
||||
|
||||
|
||||
def parse_cpp_enum(lines, enum_name):
|
||||
"""Return (members, open_idx, close_idx, indent) for a C++ enum, ignoring
|
||||
commented-out members."""
|
||||
body = find_enum_body(lines, enum_name)
|
||||
if body is None:
|
||||
return None
|
||||
open_idx, close_idx = body
|
||||
members = []
|
||||
indent = " "
|
||||
for j in range(open_idx + 1, close_idx):
|
||||
stripped = lines[j].lstrip()
|
||||
if stripped.startswith("//") or not stripped:
|
||||
continue
|
||||
m = CPP_MEMBER_RE.match(lines[j])
|
||||
if m:
|
||||
token = m.group(2)
|
||||
value = int(token, 16) if token.lower().startswith("0x") else int(token)
|
||||
members.append(CppMember(value, m.group(1), j))
|
||||
indent = lines[j][: len(lines[j]) - len(lines[j].lstrip())]
|
||||
return members, open_idx, close_idx, indent
|
||||
|
||||
|
||||
def find_address_spec(root):
|
||||
"""Auto-detect an `enum ... Address ... {` under src/, so addresses sync
|
||||
automatically once such an enum is introduced. None today."""
|
||||
addr_re = re.compile(r"^\s*enum\b[^{;]*\bAddress\b[^{;]*\{")
|
||||
for path in iter_src_files(root):
|
||||
if not path.endswith((".h", ".hpp")):
|
||||
continue
|
||||
with open(path, "r") as f:
|
||||
for line in f:
|
||||
if addr_re.match(line):
|
||||
return {
|
||||
"name": "Address",
|
||||
"path": os.path.relpath(path, root),
|
||||
"lua_table": "known_addresses",
|
||||
"style": "device",
|
||||
"hexfmt": "0x%03X",
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def iter_src_files(root):
|
||||
for dirpath, _dirs, files in os.walk(os.path.join(root, SRC_REL)):
|
||||
for fn in sorted(files):
|
||||
if fn.endswith(SRC_EXTS):
|
||||
yield os.path.join(dirpath, fn)
|
||||
|
||||
# ---- lint ------------------------------------------------------------------
|
||||
|
||||
|
||||
def lint_table(entries, path):
|
||||
"""Return a list of (lineno, name, reason) for dissector names that break a
|
||||
hard rule."""
|
||||
problems = []
|
||||
seen = {}
|
||||
for e in entries:
|
||||
s = sanitize(e.name)
|
||||
if not s or s[0].isdigit():
|
||||
problems.append((e.lineno, e.name, "starts with a digit"))
|
||||
continue
|
||||
if len(s) < 3:
|
||||
problems.append((e.lineno, e.name, "shorter than 3 characters"))
|
||||
key = s.upper()
|
||||
if key in seen:
|
||||
problems.append((e.lineno, e.name,
|
||||
"collides with '%s' (line %d) after sanitize"
|
||||
% (seen[key][0], seen[key][1])))
|
||||
else:
|
||||
seen[key] = (e.name, e.lineno)
|
||||
return problems
|
||||
|
||||
# ---- reconciliation --------------------------------------------------------
|
||||
|
||||
|
||||
class Drift:
|
||||
def __init__(self, spec):
|
||||
self.spec = spec
|
||||
self.renames = [] # (old_name, new_name, value)
|
||||
self.adds = [] # (value, new_name)
|
||||
self.cpp_only = [] # (value, name)
|
||||
|
||||
def clean(self):
|
||||
return not (self.renames or self.adds or self.cpp_only)
|
||||
|
||||
|
||||
def reconcile(spec, lua_entries, members):
|
||||
stylefn = STYLERS[spec["style"]]
|
||||
drift = Drift(spec)
|
||||
cpp_by_val = {m.value: m for m in members}
|
||||
lua_vals = set()
|
||||
for e in lua_entries:
|
||||
lua_vals.add(e.value)
|
||||
target = stylefn(e.name)
|
||||
if e.value in cpp_by_val:
|
||||
cur = cpp_by_val[e.value].name
|
||||
if canonical(cur) != canonical(e.name):
|
||||
drift.renames.append((cur, target, e.value))
|
||||
else:
|
||||
drift.adds.append((e.value, target))
|
||||
for m in members:
|
||||
if m.value not in lua_vals:
|
||||
drift.cpp_only.append((m.value, m.name))
|
||||
return drift
|
||||
|
||||
# ---- fix: rewrite enum bodies + references ---------------------------------
|
||||
|
||||
|
||||
def compute_insertions(adds, lua_order, present_idx, close_idx, indent, hexfmt):
|
||||
"""Map each added value to the line index it should be inserted before,
|
||||
mirroring the Lua table order. Returns {index: [rendered_line, ...]}."""
|
||||
groups = {}
|
||||
order_pos = {v: i for i, v in enumerate(lua_order)}
|
||||
for value, name in adds: # adds are already in Lua order
|
||||
pos = order_pos[value]
|
||||
ins = None
|
||||
for j in range(pos - 1, -1, -1):
|
||||
if lua_order[j] in present_idx:
|
||||
ins = present_idx[lua_order[j]] + 1
|
||||
break
|
||||
if ins is None:
|
||||
for j in range(pos + 1, len(lua_order)):
|
||||
if lua_order[j] in present_idx:
|
||||
ins = present_idx[lua_order[j]]
|
||||
break
|
||||
if ins is None:
|
||||
ins = close_idx
|
||||
line = "%s%s = %s,\n" % (indent, name, hexfmt % value)
|
||||
groups.setdefault(ins, []).append(line)
|
||||
return groups
|
||||
|
||||
|
||||
def apply_additions(path, spec, lua_entries, drift):
|
||||
if not drift.adds:
|
||||
return
|
||||
lines = read_lines(path)
|
||||
parsed = parse_cpp_enum(lines, spec["name"])
|
||||
members, _open_idx, close_idx, indent = parsed
|
||||
present_idx = {m.value: m.idx for m in members}
|
||||
lua_order = [e.value for e in lua_entries]
|
||||
groups = compute_insertions(drift.adds, lua_order, present_idx,
|
||||
close_idx, indent, spec["hexfmt"])
|
||||
for ins in sorted(groups, reverse=True):
|
||||
lines[ins:ins] = groups[ins]
|
||||
with open(path, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
def replace_in_code(content, pattern, repl):
|
||||
"""Apply `pattern` only to code, leaving comments, string literals and char
|
||||
literals untouched -- a bare token like a renamed enum member must not be
|
||||
rewritten where it merely appears in prose (e.g. `CSMA/CD` in a comment)."""
|
||||
out = []
|
||||
i = 0
|
||||
n = len(content)
|
||||
while i < n:
|
||||
c = content[i]
|
||||
nxt = content[i + 1] if i + 1 < n else ""
|
||||
if c == "/" and nxt == "/":
|
||||
j = content.find("\n", i)
|
||||
j = n if j == -1 else j
|
||||
out.append(content[i:j])
|
||||
i = j
|
||||
elif c == "/" and nxt == "*":
|
||||
j = content.find("*/", i + 2)
|
||||
j = n if j == -1 else j + 2
|
||||
out.append(content[i:j])
|
||||
i = j
|
||||
elif c == '"' or (c == "'" and not (i > 0 and (content[i - 1].isalnum()
|
||||
or content[i - 1] == "_"))):
|
||||
# string, or a char literal (not a C++ digit separator like 1'000)
|
||||
quote = c
|
||||
j = i + 1
|
||||
while j < n:
|
||||
if content[j] == "\\":
|
||||
j += 2
|
||||
continue
|
||||
if content[j] == quote:
|
||||
j += 1
|
||||
break
|
||||
j += 1
|
||||
out.append(content[i:j])
|
||||
i = j
|
||||
else:
|
||||
j = i
|
||||
while j < n:
|
||||
cj = content[j]
|
||||
cj1 = content[j + 1] if j + 1 < n else ""
|
||||
if cj == '"':
|
||||
break
|
||||
if cj == "'" and not (content[j - 1].isalnum() or content[j - 1] == "_"):
|
||||
break
|
||||
if cj == "/" and (cj1 == "/" or cj1 == "*"):
|
||||
break
|
||||
j += 1
|
||||
out.append(pattern.sub(repl, content[i:j]))
|
||||
i = j
|
||||
return "".join(out)
|
||||
|
||||
|
||||
# A token right after one of these keywords is *introducing a name* (a type,
|
||||
# enum, etc.), not referencing our enum member -- skip it so a member name that
|
||||
# collides with an unrelated type (e.g. `enum CD { ... }`) is left alone.
|
||||
DECL_KEYWORD_RE = re.compile(r"\b(?:enum|class|struct|union|namespace|typedef)\s+$")
|
||||
|
||||
|
||||
def apply_renames(root, renames, verbose):
|
||||
"""Replace whole-word code occurrences of every renamed member across src/.
|
||||
A single simultaneous pass (alternation) avoids A->B->C chaining."""
|
||||
if not renames:
|
||||
return
|
||||
mapping = {old: new for old, new, _v in renames}
|
||||
pattern = re.compile(r"\b(%s)\b" % "|".join(re.escape(o) for o in mapping))
|
||||
for path in iter_src_files(root):
|
||||
with open(path, "r") as f:
|
||||
content = f.read()
|
||||
count = [0]
|
||||
|
||||
def repl(m):
|
||||
if DECL_KEYWORD_RE.search(m.string[: m.start()]):
|
||||
return m.group(0) # a declaration, not a member reference
|
||||
count[0] += 1
|
||||
return mapping[m.group(1)]
|
||||
|
||||
new_content = replace_in_code(content, pattern, repl)
|
||||
if new_content != content:
|
||||
with open(path, "w") as f:
|
||||
f.write(new_content)
|
||||
if verbose:
|
||||
print(" %s: %d replacement(s)" % (os.path.relpath(path, root), count[0]))
|
||||
|
||||
# ---- reporting -------------------------------------------------------------
|
||||
|
||||
|
||||
def print_lint(spec, path, problems):
|
||||
for lineno, name, reason in problems:
|
||||
print(" %s:%d: %r -- %s" % (path, lineno, name, reason))
|
||||
|
||||
|
||||
def print_drift(drift):
|
||||
spec = drift.spec
|
||||
for old, new, value in drift.renames:
|
||||
print(" rename %s -> %s (0x%02X)" % (old, new, value))
|
||||
for value, name in drift.adds:
|
||||
print(" add %s = 0x%02X" % (name, value))
|
||||
for value, name in drift.cpp_only:
|
||||
print(" ERROR %s (0x%02X) is in C++ %s but not the dissector"
|
||||
% (name, value, spec["name"]))
|
||||
|
||||
# ---- driver ----------------------------------------------------------------
|
||||
|
||||
|
||||
def build_specs(root):
|
||||
specs = list(ENUM_SPECS)
|
||||
addr = find_address_spec(root)
|
||||
if addr:
|
||||
specs.append(addr)
|
||||
return specs, addr is not None
|
||||
|
||||
|
||||
def load(root, spec, lua_lines):
|
||||
entries = parse_lua_table(lua_lines, spec["lua_table"])
|
||||
if entries is None:
|
||||
sys.exit("error: Lua table '%s' not found" % spec["lua_table"])
|
||||
parsed = parse_cpp_enum(read_lines(os.path.join(root, spec["path"])), spec["name"])
|
||||
if parsed is None:
|
||||
sys.exit("error: C++ enum '%s' not found in %s" % (spec["name"], spec["path"]))
|
||||
return entries, parsed[0]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--fix", action="store_true",
|
||||
help="apply the sync (rewrite enums + references)")
|
||||
parser.add_argument("--lint", action="store_true",
|
||||
help="validate dissector names only")
|
||||
parser.add_argument("--verbose", action="store_true",
|
||||
help="list every reference replacement made by --fix")
|
||||
parser.add_argument("--root",
|
||||
help="repo root (default: inferred from this script)")
|
||||
args = parser.parse_args()
|
||||
|
||||
root = args.root or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
lua_path = os.path.join(root, LUA_REL)
|
||||
lua_lines = read_lines(lua_path)
|
||||
specs, have_address = build_specs(root)
|
||||
if not have_address:
|
||||
print("note: no Address enum found in src/ -- addresses not synced")
|
||||
|
||||
# ---- lint (all modes) ----
|
||||
lint_failed = False
|
||||
for spec in specs:
|
||||
entries = parse_lua_table(lua_lines, spec["lua_table"])
|
||||
if entries is None:
|
||||
sys.exit("error: Lua table '%s' not found" % spec["lua_table"])
|
||||
problems = lint_table(entries, LUA_REL)
|
||||
if problems:
|
||||
lint_failed = True
|
||||
print("dissector name problems in %s (%s):" % (spec["lua_table"], LUA_REL))
|
||||
print_lint(spec, LUA_REL, problems)
|
||||
|
||||
if args.lint:
|
||||
if lint_failed:
|
||||
print("lint: FAILED")
|
||||
return 1
|
||||
print("lint: ok")
|
||||
return 0
|
||||
|
||||
# ---- reconcile ----
|
||||
drifts = []
|
||||
for spec in specs:
|
||||
entries, members = load(root, spec, lua_lines)
|
||||
drifts.append((spec, entries, reconcile(spec, entries, members)))
|
||||
|
||||
has_cpp_only = any(d.cpp_only for _s, _e, d in drifts)
|
||||
has_drift = any(not d.clean() for _s, _e, d in drifts)
|
||||
|
||||
if args.fix:
|
||||
if lint_failed:
|
||||
print("fix aborted: resolve the dissector name problems above first")
|
||||
return 1
|
||||
if has_cpp_only:
|
||||
for _s, _e, d in drifts:
|
||||
if d.cpp_only:
|
||||
print("%s:" % d.spec["name"])
|
||||
print_drift(d)
|
||||
print("fix aborted: C++ has values the dissector lacks (see ERRORs)")
|
||||
return 1
|
||||
all_renames = []
|
||||
for spec, entries, drift in drifts:
|
||||
apply_additions(os.path.join(root, spec["path"]), spec, entries, drift)
|
||||
all_renames.extend(drift.renames)
|
||||
apply_renames(root, all_renames, args.verbose)
|
||||
for spec, entries, drift in drifts:
|
||||
if not drift.clean():
|
||||
print("%s: %d rename(s), %d addition(s)"
|
||||
% (spec["name"], len(drift.renames), len(drift.adds)))
|
||||
print("fix: applied" if has_drift else "fix: already in sync")
|
||||
return 0
|
||||
|
||||
# ---- default: report ----
|
||||
for spec, _entries, drift in drifts:
|
||||
if not drift.clean():
|
||||
print("%s drift:" % spec["name"])
|
||||
print_drift(drift)
|
||||
if lint_failed or has_drift:
|
||||
print("out of sync -- run: scripts/sync_avclan_enums.py --fix")
|
||||
return 1
|
||||
print("enums in sync with the dissector")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+15
-12
@@ -31,7 +31,8 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
|
||||
// LAN related
|
||||
List_Functions_Req = 0x00,
|
||||
List_Functions_Resp = 0x10,
|
||||
Restart_Lan = 0x01,
|
||||
Lan_Init = 0x01,
|
||||
Lan_Init_Complete = 0x58,
|
||||
// Lan_Startup_Complete = 0x58,
|
||||
Lancheck_End_Req = 0x08,
|
||||
Lancheck_End_Resp = 0x18,
|
||||
@@ -50,7 +51,7 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
|
||||
Disable_Function_Req = 0x43,
|
||||
Disable_Function_Resp = 0x53,
|
||||
|
||||
Current_Function = 0x45,
|
||||
Advertise_Function = 0x45,
|
||||
General_Query = 0x46,
|
||||
|
||||
// Events
|
||||
@@ -60,6 +61,7 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
|
||||
// Physical interface
|
||||
Backlight_Adjust = 0x59,
|
||||
Beep = 0x60,
|
||||
Screen_Press = 0x78,
|
||||
Eject = 0x80,
|
||||
Disc_Up = 0x90,
|
||||
Disc_Down = 0x91,
|
||||
@@ -69,6 +71,7 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
|
||||
Track_Rewind = 0x99,
|
||||
Pwrvol_Knob_Righthand_Turn = 0x9c,
|
||||
Pwrvol_Knob_Lefthand_Turn = 0x9d,
|
||||
Tape_Not_Ready = 0x9f,
|
||||
CD_Enable_Repeat = 0xa0,
|
||||
CD_Disable_Repeat = 0xa1,
|
||||
CD_Enable_Disk_Repeat = 0xa3,
|
||||
@@ -83,21 +86,21 @@ enum AVCLAN_ENUM_CLASS Action : uint8_t {
|
||||
CD_Disable_Disk_Random = 0xb4,
|
||||
|
||||
// Requests and Response pairs
|
||||
Initial_Report_Request = 0xe0,
|
||||
Initial_Report_Response = 0xf0,
|
||||
Initial_Report_Req = 0xe0,
|
||||
Initial_Report_Resp = 0xf0,
|
||||
|
||||
Playback_Request = 0xe2,
|
||||
Playback_Report = 0xf2,
|
||||
Playback_Req = 0xe2,
|
||||
Playback_Resp = 0xf2,
|
||||
|
||||
Loading_Request2 = 0xe4,
|
||||
Loading_Response2 = 0xf4,
|
||||
Loading_Req = 0xe4,
|
||||
Loading_Resp = 0xf4,
|
||||
|
||||
Request_Track_Name = 0xed,
|
||||
Report_Track_Name = 0xfd,
|
||||
Track_Name_Req = 0xed,
|
||||
Track_Name_Resp = 0xfd,
|
||||
|
||||
// Reports
|
||||
Status_Report = 0xf1, // Typically unprompted, sent to Device::STATUS
|
||||
Loading_Status_Report = 0xf3, // Typically unprompted, sent to Device::STATUS
|
||||
Playback_Status = 0xf1, // Typically unprompted, sent to Device::STATUS
|
||||
Loading_Status = 0xf3, // Typically unprompted, sent to Device::STATUS
|
||||
Report_TOC = 0xf9,
|
||||
};
|
||||
|
||||
|
||||
+10
-10
@@ -19,7 +19,7 @@ using namespace avclan;
|
||||
constexpr uint8_t cdloading_resp[] = {
|
||||
to_underlying(Device::CD_CHANGER),
|
||||
to_underlying(Device::STATUS),
|
||||
to_underlying(Action::Loading_Status_Report),
|
||||
to_underlying(Action::Loading_Status),
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
@@ -116,14 +116,14 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Initial_Report_Request: {
|
||||
case Initial_Report_Req: {
|
||||
out->is_unicast = true;
|
||||
// No knowledge/understanding of field meaning/interpretation
|
||||
const uint8_t cdinitreport_resp[] = {
|
||||
0x00,
|
||||
to_underlying(Device::CD_CHANGER),
|
||||
to_underlying(from),
|
||||
to_underlying(Initial_Report_Response),
|
||||
to_underlying(Initial_Report_Resp),
|
||||
0x01,
|
||||
0x31,
|
||||
0x10,
|
||||
@@ -134,22 +134,22 @@ void CDChanger::handle(const Frame *in, Frame *out) {
|
||||
out->reaction = r_SendOnly;
|
||||
break;
|
||||
}
|
||||
case Playback_Request:
|
||||
case Playback_Req:
|
||||
out->data[0] = 0x00;
|
||||
out->data[1] = to_underlying(Device::CD_CHANGER);
|
||||
out->data[2] = to_underlying(from);
|
||||
out->data[3] = to_underlying(Playback_Report);
|
||||
out->data[3] = to_underlying(Playback_Resp);
|
||||
out->length = WIRE_SIZE + 4;
|
||||
serialize(&out->data[4]);
|
||||
out->is_unicast = true;
|
||||
out->reaction = r_SendOnly;
|
||||
break;
|
||||
case Loading_Request2:
|
||||
case Loading_Req:
|
||||
out->data[0] = 0x00;
|
||||
out->length = sizeof(cdloading_resp) + 1;
|
||||
memcpy(&out->data[1], cdloading_resp, sizeof(cdloading_resp));
|
||||
out->data[2] = to_underlying(from);
|
||||
out->data[3] = to_underlying(Loading_Response2);
|
||||
out->data[3] = to_underlying(Loading_Resp);
|
||||
out->is_unicast = true;
|
||||
out->reaction = r_SendOnly;
|
||||
break;
|
||||
@@ -276,7 +276,7 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
|
||||
case r_Ejection: {
|
||||
const uint8_t play[] = {0x00,
|
||||
to_underlying(Device::COMM_CTRL),
|
||||
to_underlying(Device::COMM_v1),
|
||||
to_underlying(Device::COMMUNICATION_V1),
|
||||
to_underlying(Action::Insertion),
|
||||
to_underlying(Device::CD_CHANGER),
|
||||
0x01};
|
||||
@@ -291,7 +291,7 @@ void CDChanger::react(Frame *out, detail::Error::Send err) {
|
||||
out->length = sizeof(cdloading_resp) + 1;
|
||||
memcpy(out->data, cdloading_resp, sizeof(cdloading_resp));
|
||||
out->data[1] = to_underlying(Device::STATUS);
|
||||
out->data[2] = to_underlying(Action::Loading_Status_Report);
|
||||
out->data[2] = to_underlying(Action::Loading_Status);
|
||||
out->reaction = r_SendOnly;
|
||||
break;
|
||||
case r_TrackChange:
|
||||
@@ -411,7 +411,7 @@ void CDChanger::generateStatus(Frame *status, bool is_unicast,
|
||||
*data++ = 0x00;
|
||||
*data++ = to_underlying(Device::CD_CHANGER);
|
||||
*data++ = to_underlying(to);
|
||||
*data++ = to_underlying(Action::Status_Report);
|
||||
*data++ = to_underlying(Action::Playback_Status);
|
||||
serialize(data);
|
||||
}
|
||||
|
||||
|
||||
+19
-4
@@ -14,20 +14,35 @@ namespace avclan {
|
||||
enum class Device : uint8_t {
|
||||
LAN = 0x00,
|
||||
COMM_CTRL = 0x01,
|
||||
COMM_v1 = 0x11,
|
||||
COMM_v2 = 0x12,
|
||||
SW = 0x21,
|
||||
COMMUNICATION_V1 = 0x11,
|
||||
COMMUNICATION_V2 = 0x12,
|
||||
SW_AUDIO = 0x21,
|
||||
SW_NAME = 0x23,
|
||||
SW_CONVERTING = 0x24,
|
||||
CMD_SW = 0x25,
|
||||
STATUS = 0x31,
|
||||
INFO_DISPLAY2 = 0x32,
|
||||
BEEP_HU = 0x28,
|
||||
BEEP_SPEAKERS = 0x29,
|
||||
FRONT_PSNG_MONITOR = 0x34,
|
||||
CD_CHANGER2 = 0x43,
|
||||
BLUETOOTH_TEL = 0x55,
|
||||
INFO_DRAWING = 0x56,
|
||||
NAV_ECU = 0x58,
|
||||
CAMERA = 0x5C,
|
||||
CLIMATE_DRAWING = 0x5D,
|
||||
AUDIO_DRAWING = 0x5E,
|
||||
TRIP_INFO_DRAWING = 0x5F,
|
||||
TUNER = 0x60,
|
||||
TAPE_DECK = 0x61,
|
||||
CD = 0x62,
|
||||
CD_SINGLE = 0x62,
|
||||
CD_CHANGER = 0x63,
|
||||
AUDIO_AMP = 0x74,
|
||||
GPS = 0x80,
|
||||
VOICE_CTRL = 0x85,
|
||||
XM_TUNER = 0xC0,
|
||||
CLIMATE_CTRL_DEV = 0xE0,
|
||||
TRIP_INFO = 0xE5,
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
||||
@@ -87,19 +87,19 @@ public:
|
||||
out->data[3] = to_underlying(Lancheck_End_Resp);
|
||||
out->reaction = 1;
|
||||
break;
|
||||
case PACK3(COMM_v1, COMM_CTRL, to_underlying(Current_Function)):
|
||||
case PACK3(COMM_v2, COMM_CTRL, to_underlying(Current_Function)):
|
||||
case PACK3(COMMUNICATION_V1, COMM_CTRL, to_underlying(Advertise_Function)):
|
||||
case PACK3(COMMUNICATION_V2, COMM_CTRL, to_underlying(Advertise_Function)):
|
||||
((Devs::id == static_cast<Device>(b3)
|
||||
? std::get<Devs>(devices_).enable(out),
|
||||
0 : 0),
|
||||
...);
|
||||
break;
|
||||
case PACK3(COMM_v1, COMM_CTRL, to_underlying(Ping_Req)):
|
||||
case PACK3(COMM_v2, COMM_CTRL, to_underlying(Ping_Req)): {
|
||||
case PACK3(COMMUNICATION_V1, COMM_CTRL, to_underlying(Ping_Req)):
|
||||
case PACK3(COMMUNICATION_V2, COMM_CTRL, to_underlying(Ping_Req)): {
|
||||
out->is_unicast = true;
|
||||
const uint8_t ping_resp[] = {0x00,
|
||||
to_underlying(COMM_CTRL),
|
||||
to_underlying(COMM_v1),
|
||||
to_underlying(COMMUNICATION_V1),
|
||||
to_underlying(Ping_Resp),
|
||||
0xFF,
|
||||
b3};
|
||||
@@ -108,13 +108,13 @@ public:
|
||||
out->reaction = 1;
|
||||
break;
|
||||
}
|
||||
case PACK3(COMM_v1, COMM_CTRL, to_underlying(List_Functions_Req)):
|
||||
case PACK3(COMM_v2, COMM_CTRL, to_underlying(List_Functions_Req)): {
|
||||
case PACK3(COMMUNICATION_V1, COMM_CTRL, to_underlying(List_Functions_Req)):
|
||||
case PACK3(COMMUNICATION_V2, COMM_CTRL, to_underlying(List_Functions_Req)): {
|
||||
controller_ = in->controller_addr;
|
||||
out->peripheral_addr = controller_;
|
||||
out->is_unicast = true;
|
||||
const uint8_t list_functions_resp[] = {
|
||||
0x00, to_underlying(COMM_CTRL), to_underlying(COMM_v1),
|
||||
0x00, to_underlying(COMM_CTRL), to_underlying(COMMUNICATION_V1),
|
||||
to_underlying(List_Functions_Resp), to_underlying(CD_CHANGER)};
|
||||
out->length = sizeof(list_functions_resp);
|
||||
memcpy(out->data, list_functions_resp, sizeof(list_functions_resp));
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ int main() {
|
||||
{
|
||||
const uint8_t play[] = {0x00,
|
||||
to_underlying(COMM_CTRL),
|
||||
to_underlying(COMM_v1),
|
||||
to_underlying(COMMUNICATION_V1),
|
||||
to_underlying(Ejection),
|
||||
to_underlying(CD_CHANGER),
|
||||
0x01};
|
||||
|
||||
Reference in New Issue
Block a user