From nobody Thu Dec 18 00:30:37 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) client-ip=170.10.129.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1678293610; cv=none; d=zohomail.com; s=zohoarc; b=FjOZ+QSstI8rj/ZAIeRD6dGMb94lB8Hm+mwjl18hZoNexijFpPOyUnuENGdYxPuo6Vb7crM1xYHTCm9bhv4IX3ZMkx0MJzzHouMfEiQWUA0sO9tMODX68HR6673ZNpU0GJEocGVF0gAkSyG6lAvcLAwqcBguDTfDNh3rveg3XI0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1678293610; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=4Qc9cWHNrhx1RAeXJaeh3mYyyTua5IPWQo2/v6/4vB8=; b=Hr3kIq+fsbNdSd7Gqyl466NBv79D3kcj/ivNcC1k1wiAK68DpkUspL2J8hCLm3KaDNhcDomEi1ff3UeKkWb0Eka1T6Y9REMBI3atLcyXCkGYbXOeEYYUeUKikc16j0gapTV7k+IhxUWih5iFTPtqOJHdL2colkO7a8Wn7S4im8A= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by mx.zohomail.com with SMTPS id 1678293610981798.9353704587098; Wed, 8 Mar 2023 08:40:10 -0800 (PST) Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-208-A4x4wZ3lMambj4uS8dwC9Q-1; Wed, 08 Mar 2023 11:39:36 -0500 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 4A80A101A52E; Wed, 8 Mar 2023 16:39:26 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (unknown [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id 2E6F447CEF; Wed, 8 Mar 2023 16:39:26 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id A42451948665; Wed, 8 Mar 2023 16:39:22 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id BB3541946A5E for ; Wed, 8 Mar 2023 16:39:18 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id 9DBD51121330; Wed, 8 Mar 2023 16:39:18 +0000 (UTC) Received: from virtlab420.virt.lab.eng.bos.redhat.com (virtlab420.virt.lab.eng.bos.redhat.com [10.19.152.148]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7E7691121314; Wed, 8 Mar 2023 16:39:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1678293609; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=4Qc9cWHNrhx1RAeXJaeh3mYyyTua5IPWQo2/v6/4vB8=; b=XnImi2ypmXJoEdgOzJXew85/BjLkIkf+Cn4jwn+WYLZ5HdYy9Dan4SuodRlbhiE/gr+D73 WfLmnOk+PEuErsaDx/KgKUKj4OzqiRYcsOf8z9b6KtGfsEwZsnTyC1BjXrgX1dRIo2vxwA DkHkZnjwiGdP3OHv/PH5lv9xtQdjh3k= X-MC-Unique: A4x4wZ3lMambj4uS8dwC9Q-1 X-Original-To: libvir-list@listman.corp.redhat.com From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: libvir-list@redhat.com Subject: [PATCH 06/16] rpcgen: add an XDR protocol parser Date: Wed, 8 Mar 2023 11:39:03 -0500 Message-Id: <20230308163913.338952-7-berrange@redhat.com> In-Reply-To: <20230308163913.338952-1-berrange@redhat.com> References: <20230308163913.338952-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.3 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 3.1 on 10.11.54.5 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1678293612832100001 This adds a parser capable of handling the XDR protocol files. The parsing grammar requirements are detailed in https://www.rfc-editor.org/rfc/rfc4506#section-6.3 Signed-off-by: Daniel P. Berrang=C3=A9 --- scripts/rpcgen/rpcgen/parser.py | 497 ++++++++++++++++++++++++++++ scripts/rpcgen/tests/meson.build | 1 + scripts/rpcgen/tests/test_parser.py | 91 +++++ 3 files changed, 589 insertions(+) create mode 100644 scripts/rpcgen/rpcgen/parser.py create mode 100644 scripts/rpcgen/tests/test_parser.py diff --git a/scripts/rpcgen/rpcgen/parser.py b/scripts/rpcgen/rpcgen/parser= .py new file mode 100644 index 0000000000..7efbe5468e --- /dev/null +++ b/scripts/rpcgen/rpcgen/parser.py @@ -0,0 +1,497 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +from .lexer import ( + XDRLexer, + XDRTokenPunctuation, + XDRTokenIdentifier, + XDRTokenCEscape, + XDRTokenConstant, +) +from .ast import ( + XDRSpecification, + XDRDefinitionConstant, + XDRDefinitionTypedef, + XDRDefinitionEnum, + XDRDefinitionStruct, + XDRDefinitionUnion, + XDRDefinitionCEscape, + XDRDeclarationScalar, + XDRDeclarationPointer, + XDRDeclarationFixedArray, + XDRDeclarationVariableArray, + XDRTypeVoid, + XDRTypeChar, + XDRTypeUnsignedChar, + XDRTypeShort, + XDRTypeUnsignedShort, + XDRTypeInt, + XDRTypeUnsignedInt, + XDRTypeHyper, + XDRTypeUnsignedHyper, + XDRTypeFloat, + XDRTypeDouble, + XDRTypeBool, + XDRTypeOpaque, + XDRTypeString, + XDRTypeCustom, + XDREnumValue, + XDREnumBody, + XDRTypeEnum, + XDRStructBody, + XDRTypeStruct, + XDRUnionCase, + XDRUnionBody, + XDRTypeUnion, +) + + +# We are parsing (approximately the following grammar +# from RFC 4506 #6.3: +# +# declaration: +# type-specifier identifier +# | type-specifier identifier "[" value "]" +# | type-specifier identifier "<" [ value ] ">" +# | "opaque" identifier "[" value "]" +# | "opaque" identifier "<" [ value ] ">" +# | "string" identifier "<" [ value ] ">" +# | type-specifier "*" identifier +# | "void" +# +# value: +# constant +# | identifier +# +# constant: +# decimal-constant | hexadecimal-constant | octal-constant +# +# type-specifier: +# [ "unsigned" ] "int" +# | [ "unsigned" ] "hyper" +# | "float" +# | "double" +# | "quadruple" /* We're skipping this one */ +# | "bool" +# | enum-type-spec +# | struct-type-spec +# | union-type-spec +# | identifier +# +# enum-type-spec: +# "enum" enum-body +# +# enum-body: +# "{" +# ( identifier "=3D" value ) +# ( "," identifier "=3D" value )* +# "}" +# +# struct-type-spec: +# "struct" struct-body +# +# struct-body: +# "{" +# ( declaration ";" ) +# ( declaration ";" )* +# "}" +# +# union-type-spec: +# "union" union-body +# +# union-body: +# "switch" "(" declaration ")" "{" +# case-spec +# case-spec * +# [ "default" ":" declaration ";" ] +# "}" +# +# case-spec: +# ( "case" value ":") +# ( "case" value ":") * +# declaration ";" +# +# constant-def: +# "const" identifier "=3D" constant ";" +# +# type-def: +# "typedef" declaration ";" +# | "enum" identifier enum-body ";" +# | "struct" identifier struct-body ";" +# | "union" identifier union-body ";" +# +# definition: +# type-def +# | constant-def +# +# specification: +# definition * +# +# Notable divergance: +# +# - In 'type-decl' we allow 'char' and 'short' +# in signed and unsigned variants +# +# - In 'definition' we allow '%...' as escape C code +# to passthrough to the header output +# +# - In 'enum-type-spec' we allow a bare enum name +# instead of enum body +# +# - In 'struct-type-spec' we allow a bare struct name +# instead of struct body +# +# - In 'union-type-spec' we allow a bare union name +# instead of union body +# +class XDRParser: + def __init__(self, fp): + self.lexer =3D XDRLexer(fp) + self.typedefs =3D {} + + def parse(self): + spec =3D XDRSpecification() + while True: + definition =3D self.parse_definition() + if definition is None: + break + spec.definitions.append(definition) + return spec + + def parse_definition(self): + token =3D self.lexer.next() + if token is None: + return None + + if type(token) =3D=3D XDRTokenCEscape: + return XDRDefinitionCEscape(token.value[1:]) + + if type(token) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % token) + + defs =3D { + "const": XDRDefinitionConstant, + "typedef": XDRDefinitionTypedef, + "enum": XDRDefinitionEnum, + "struct": XDRDefinitionStruct, + "union": XDRDefinitionUnion, + } + + if token.value not in defs: + raise Exception("Unexpected identifier %s" % token) + + funcname =3D "parse_definition_" + token.value + func =3D getattr(self, funcname) + assert func is not None + + definition =3D func() + + semi =3D self.lexer.next() + if type(semi) !=3D XDRTokenPunctuation or semi.value !=3D ";": + raise Exception("Expected ';', but got %s" % semi) + + return definition + + def parse_definition_const(self): + ident =3D self.lexer.next() + if type(ident) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % ident) + + assign =3D self.lexer.next() + if type(assign) !=3D XDRTokenPunctuation or assign.value !=3D "=3D= ": + raise Exception("Expected '=3D', but got %s" % assign) + + const =3D self.lexer.next() + if type(const) not in [XDRTokenConstant, XDRTokenIdentifier]: + raise Exception("Expected constant, but got %s" % const) + + return XDRDefinitionConstant(ident.value, const.value) + + def parse_definition_typedef(self): + decl =3D self.parse_declaration() + if decl.identifier in self.typedefs: + raise Exception("Type '%s' already defined" % decl.identifier) + + definition =3D XDRDefinitionTypedef(decl) + self.typedefs[decl.identifier] =3D definition + return definition + + def parse_definition_enum(self): + name =3D self.lexer.next() + if type(name) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % name) + + body =3D self.parse_enum_body() + + if name.value in self.typedefs: + raise Exception("Type '%s' already defined" % name.value) + + definition =3D XDRDefinitionEnum(name.value, body) + self.typedefs[name.value] =3D definition + return definition + + def parse_definition_struct(self): + name =3D self.lexer.next() + if type(name) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % name) + + body =3D self.parse_struct_body() + + if name.value in self.typedefs: + raise Exception("Type '%s' already defined" % name.value) + + definition =3D XDRDefinitionStruct(name.value, body) + self.typedefs[name.value] =3D definition + return definition + + def parse_definition_union(self): + name =3D self.lexer.next() + if type(name) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % name) + + body =3D self.parse_union_body() + + if name.value in self.typedefs: + raise Exception("Type '%s' already defined" % name.value) + + definition =3D XDRDefinitionUnion(name.value, body) + self.typedefs[name.value] =3D definition + return definition + + def parse_declaration(self): + typ =3D self.parse_type() + + if type(typ) =3D=3D XDRTypeVoid: + return XDRDeclarationScalar(typ, None) + + ident =3D self.lexer.next() + + pointer =3D False + if type(ident) =3D=3D XDRTokenPunctuation: + if ident.value !=3D "*": + raise Exception("Expected '*' or identifer, but got %s" % = ident) + if type(typ) =3D=3D XDRTypeString or type(typ) =3D=3D XDRTypeO= paque: + raise Exception("Pointer invalid for 'string' and 'opaque'= types") + + pointer =3D True + ident =3D self.lexer.next() + + bracket =3D self.lexer.peek() + if type(bracket) =3D=3D XDRTokenPunctuation: + if bracket.value =3D=3D "[": + _ =3D self.lexer.next() + value =3D self.lexer.next() + if type(value) not in [XDRTokenConstant, XDRTokenIdentifie= r]: + raise Exception("Expected constant, but got %s" % valu= e) + + close =3D self.lexer.next() + if type(close) !=3D XDRTokenPunctuation or close.value != =3D "]": + raise Exception("Expected ']', but got %s" % value) + + if type(typ) =3D=3D XDRTypeString: + raise Exception("Fixed array invalid for 'string' type= ") + return XDRDeclarationFixedArray(typ, ident.value, value.va= lue) + elif bracket.value =3D=3D "<": + _ =3D self.lexer.next() + maybeValue =3D self.lexer.peek() + value =3D None + if type(maybeValue) in [XDRTokenConstant, XDRTokenIdentifi= er]: + value =3D self.lexer.next().value + + close =3D self.lexer.next() + if type(close) !=3D XDRTokenPunctuation or close.value != =3D ">": + raise Exception("Expected '>', but got %s" % close) + + return XDRDeclarationVariableArray(typ, ident.value, value) + + if pointer: + return XDRDeclarationPointer(typ, ident.value) + else: + return XDRDeclarationScalar(typ, ident.value) + + def parse_type(self): + typ =3D self.lexer.next() + if type(typ) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % typ) + + if typ.value =3D=3D "unsigned": + typ =3D self.lexer.peek() + if type(typ) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % typ) + + if typ.value =3D=3D "char": + _ =3D self.lexer.next() + return XDRTypeUnsignedChar() + elif typ.value =3D=3D "short": + _ =3D self.lexer.next() + return XDRTypeUnsignedShort() + elif typ.value =3D=3D "int": + _ =3D self.lexer.next() + return XDRTypeUnsignedInt() + elif typ.value =3D=3D "hyper": + _ =3D self.lexer.next() + return XDRTypeUnsignedHyper() + else: + # Bare 'unsigned' isn't allowed by 'type-specifier' + # grammer in RFC 1014, but rpcgen allows it + return XDRTypeUnsignedInt() + + if typ.value =3D=3D "void": + return XDRTypeVoid() + elif typ.value =3D=3D "char": + return XDRTypeChar() + elif typ.value =3D=3D "short": + return XDRTypeShort() + elif typ.value =3D=3D "int": + return XDRTypeInt() + elif typ.value =3D=3D "hyper": + return XDRTypeHyper() + elif typ.value =3D=3D "float": + return XDRTypeFloat() + elif typ.value =3D=3D "double": + return XDRTypeDouble() + elif typ.value =3D=3D "bool": + return XDRTypeBool() + elif typ.value =3D=3D "enum": + return self.parse_type_enum() + elif typ.value =3D=3D "struct": + return self.parse_type_struct() + elif typ.value =3D=3D "union": + return self.parse_type_union() + elif typ.value =3D=3D "opaque": + return XDRTypeOpaque() + elif typ.value =3D=3D "string": + return XDRTypeString() + else: + return XDRTypeCustom(typ.value, self.typedefs.get(typ.value, N= one)) + + def parse_enum_body(self): + body =3D self.lexer.next() + if type(body) !=3D XDRTokenPunctuation or body.value !=3D "{": + raise Exception("Expected '{', but got %s" % body) + + values =3D [] + while True: + ident =3D self.lexer.next() + if type(ident) !=3D XDRTokenIdentifier: + raise Exception("Expected identifier, but got %s" % ident) + + equal =3D self.lexer.next() + if type(equal) !=3D XDRTokenPunctuation or equal.value !=3D "= =3D": + raise Exception("Expected '=3D', but got %s" % ident) + + value =3D self.lexer.next() + if type(value) !=3D XDRTokenConstant: + raise Exception("Expected constant, but got %s" % ident) + + separator =3D self.lexer.next() + if type(separator) !=3D XDRTokenPunctuation and separator.valu= e not in [ + "}", + ",", + ]: + raise Exception("Expected '}' or ',', but got %s" % separa= tor) + + values.append(XDREnumValue(ident.value, value.value)) + + if separator.value =3D=3D "}": + break + + return XDREnumBody(values) + + def parse_type_enum(self): + body =3D self.parse_enum_body() + return XDRTypeEnum(body) + + def parse_struct_body(self): + body =3D self.lexer.next() + if type(body) !=3D XDRTokenPunctuation or body.value !=3D "{": + raise Exception("Expected '{', but got %s" % body) + + fields =3D [] + while True: + field =3D self.parse_declaration() + fields.append(field) + + separator =3D self.lexer.next() + if type(separator) !=3D XDRTokenPunctuation and separator.valu= e !=3D ";": + raise Exception("Expected ';', but got %s" % separator) + + end =3D self.lexer.peek() + if type(end) =3D=3D XDRTokenPunctuation and end.value =3D=3D "= }": + break + + # discard the '}' we peeked at to end the loop + _ =3D self.lexer.next() + return XDRStructBody(fields) + + def parse_type_struct(self): + body =3D self.parse_struct_body() + return XDRTypeStruct(body) + + def parse_union_body(self): + ident =3D self.lexer.next() + if type(ident) !=3D XDRTokenIdentifier or ident.value !=3D "switch= ": + raise Exception("Expected 'switch', but got %s" % ident) + + bracket =3D self.lexer.next() + if type(bracket) !=3D XDRTokenPunctuation or bracket.value !=3D "(= ": + raise Exception("Expected '(', but got %s" % bracket) + + discriminator =3D self.parse_declaration() + + bracket =3D self.lexer.next() + if type(bracket) !=3D XDRTokenPunctuation or bracket.value !=3D ")= ": + raise Exception("Expected ')', but got %s" % bracket) + + bracket =3D self.lexer.next() + if type(bracket) !=3D XDRTokenPunctuation or bracket.value !=3D "{= ": + raise Exception("Expected '{', but got %s" % bracket) + + default =3D None + cases =3D [] + while True: + ident =3D self.lexer.next() + if type(ident) !=3D XDRTokenIdentifier or ident.value not in [ + "default", + "case", + ]: + raise Exception("Expected 'default' or 'case', but got %s"= % ident) + + value =3D None + if ident.value =3D=3D "case": + value =3D self.lexer.next() + if type(value) not in [XDRTokenConstant, XDRTokenIdentifie= r]: + raise Exception("Expected constant, but got %s" % valu= e) + + sep =3D self.lexer.next() + if type(sep) !=3D XDRTokenPunctuation or sep.value !=3D ":= ": + raise Exception("Expected ':', but got %s" % value) + + decl =3D self.parse_declaration() + + case =3D XDRUnionCase(value.value, decl) + cases.append(case) + else: + if default is not None: + raise Exception("Duplicate 'default' clause") + + sep =3D self.lexer.next() + if type(sep) !=3D XDRTokenPunctuation or sep.value !=3D ":= ": + raise Exception("Expected ':', but got %s" % value) + + default =3D self.parse_declaration() + + separator =3D self.lexer.next() + if type(separator) !=3D XDRTokenPunctuation and separator.valu= e !=3D ";": + raise Exception("Expected ';', but got %s" % bracket) + + end =3D self.lexer.peek() + if type(end) =3D=3D XDRTokenPunctuation and end.value =3D=3D "= }": + break + + # discard the '}' we peeked at to end the loop + _ =3D self.lexer.next() + return XDRUnionBody(discriminator, cases, default) + + def parse_type_union(self): + body =3D self.parse_union_body() + return XDRTypeUnion(body) diff --git a/scripts/rpcgen/tests/meson.build b/scripts/rpcgen/tests/meson.= build index 9162412d31..4b1ea308ce 100644 --- a/scripts/rpcgen/tests/meson.build +++ b/scripts/rpcgen/tests/meson.build @@ -1,3 +1,4 @@ rpcgen_tests =3D files([ 'test_lexer.py', + 'test_parser.py', ]) diff --git a/scripts/rpcgen/tests/test_parser.py b/scripts/rpcgen/tests/tes= t_parser.py new file mode 100644 index 0000000000..8527b8d6e2 --- /dev/null +++ b/scripts/rpcgen/tests/test_parser.py @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +from pathlib import Path + +from rpcgen.ast import ( + XDRSpecification, + XDRDefinitionConstant, + XDRDefinitionEnum, + XDRDefinitionUnion, + XDRDefinitionStruct, + XDRDeclarationScalar, + XDRDeclarationVariableArray, + XDREnumValue, + XDREnumBody, + XDRStructBody, + XDRUnionCase, + XDRUnionBody, + XDRTypeCustom, + XDRTypeVoid, + XDRTypeString, + XDRTypeOpaque, +) +from rpcgen.parser import XDRParser + + +def test_parser(): + p =3D Path(Path(__file__).parent, "simple.x") + with p.open("r") as fp: + parser =3D XDRParser(fp) + + got =3D parser.parse() + + enum =3D XDRDefinitionEnum( + "filekind", + XDREnumBody( + [ + XDREnumValue("TEXT", "0"), + XDREnumValue("DATA", "1"), + XDREnumValue("EXEC", "2"), + ], + ), + ) + + union =3D XDRDefinitionUnion( + "filetype", + XDRUnionBody( + XDRDeclarationScalar(XDRTypeCustom("filekind", enum), "kind"), + [ + XDRUnionCase("TEXT", XDRDeclarationScalar(XDRTypeVoid(), N= one)), + XDRUnionCase( + "DATA", + XDRDeclarationVariableArray( + XDRTypeString(), "creator", "MAXNAMELEN" + ), + ), + XDRUnionCase( + "EXEC", + XDRDeclarationVariableArray( + XDRTypeString(), "interpretor", "MAXNAMELEN" + ), + ), + ], + None, + ), + ) + + struct =3D XDRDefinitionStruct( + "file", + XDRStructBody( + [ + XDRDeclarationVariableArray(XDRTypeString(), "filename", "= MAXNAMELEN"), + XDRDeclarationScalar(XDRTypeCustom("filetype", union), "ty= pe"), + XDRDeclarationVariableArray(XDRTypeString(), "owner", "MAX= USERNAME"), + XDRDeclarationVariableArray(XDRTypeOpaque(), "data", "MAXF= ILELEN"), + ] + ), + ) + + want =3D XDRSpecification() + want.definitions.extend( + [ + XDRDefinitionConstant("MAXUSERNAME", "32"), + XDRDefinitionConstant("MAXFILELEN", "65535"), + XDRDefinitionConstant("MAXNAMELEN", "255"), + enum, + union, + struct, + ] + ) + + assert str(got) =3D=3D str(want) --=20 2.39.1