diff --git a/yang/JSON_CBOR.env b/yang/JSON_CBOR.env new file mode 100644 index 00000000..7c7bf949 --- /dev/null +++ b/yang/JSON_CBOR.env @@ -0,0 +1,7 @@ +pushd $YDIR +python3 -m venv venv +. venv/bin/activate +pip3 install yangson cbor +popd + +export PYTHONPATH="/home/maria/yang/yangson:$PYTHONPATH" diff --git a/yang/JSON_CBOR.py b/yang/JSON_CBOR.py index fc7b8da0..a212dfe2 100644 --- a/yang/JSON_CBOR.py +++ b/yang/JSON_CBOR.py @@ -1,20 +1,72 @@ from yangson import DataModel +from yangson.exceptions import InstanceValueError +from xml.etree.ElementTree import ElementTree + +import ipaddress import cbor import json +from xml.etree.ElementTree import ElementTree import os import sys +class IPv6ClassTag(cbor.ClassTag): + def __init__(self, tag_number=54, class_type=ipaddress.IPv6Address): + super().__init__(tag_number, class_type, lambda ip: ip.packed, lambda data: ipaddress.IPv6Address(data)) + +cbor_transcoder = cbor.TagMapper([ + IPv6ClassTag(), + ]) + class Message: def __init__(self, filename, _type): self.filename = filename with open(self.filename, "rb") as sf: - self.raw = { "json": json, "cbor": cbor }[_type].load(sf) + if _type == "json": + self.raw = json.load(sf) + elif _type == "cbor": + self.raw = cbor_transcoder.load(sf) + elif _type == "xml": + self.xml = ElementTree() + self.xml.parse(sf) + else: + raise Exception() cwd = os.getcwd() os.chdir(os.path.dirname(sys.modules[__name__].__file__)) - self.dm = DataModel.from_file('yang-library.json') +# self.dm = DataModel.from_file('yang-library.json', [".", "/usr/share/yang/modules/libyang/"]) + self.dm = DataModel.from_file('yang-library.json', ["."]) + print(self.dm, type(self.dm)) + at = self.dm.get_schema_node("test_ip:message/address").type + ft = self.dm.get_schema_node("test_ip:message/foo").type + print(at, type(at), at.types) + print(ft, type(ft)) os.chdir(cwd) - self.data = self.dm.from_raw(self.raw) + if _type == "xml": + self.data = self.dm.from_xml(self.xml.getroot()) + else: + self.data = self.dm.from_raw(self.raw) + print(self.data, type(self.data)) + t = self.data["test_ip:message"]["address"] + print(t, type(t)) self.data.validate() + + def unnodify(cls, what): + print("unnodify", what, type(what)) + try: + return { i: cls.unnodify(what[i]) for i in what } + except InstanceValueError as e: + if str(e).endswith(" is a scalar instance"): + return what.value + raise e + + def dump_cbor(self, file, **kwargs): + cbor_transcoder.dump(self.unnodify(self.data), file, **kwargs) + + def dump_json(self, file, **kwargs): + json.dump(self.data.raw_value(), file, **kwargs) + + def dump_xml(self, file): + et = ElementTree(self.data.to_xml()) + et.write(file, encoding="unicode") diff --git a/yang/cbor-json-yang b/yang/cbor-json-yang index 857008cd..431f4467 100755 --- a/yang/cbor-json-yang +++ b/yang/cbor-json-yang @@ -1,9 +1,4 @@ YDIR=$(dirname $0) - -pushd $YDIR -python3 -m venv venv -. venv/bin/activate -pip3 install yangson cbor -popd +. "$YDIR"/JSON_CBOR.env python3 $YDIR/cbor-json-yang.py "$@" diff --git a/yang/cbor-json-yang.py b/yang/cbor-json-yang.py index 2b4f79fe..401305b3 100644 --- a/yang/cbor-json-yang.py +++ b/yang/cbor-json-yang.py @@ -8,6 +8,6 @@ import JSON_CBOR msg = JSON_CBOR.Message(sys.argv[1], "cbor") with open(sys.argv[2], "w") as of: - json.dump(msg.raw, of, indent=2) + msg.dump_json(of, indent=2) print("OK") diff --git a/yang/cbor-xml-yang b/yang/cbor-xml-yang new file mode 100755 index 00000000..784e7117 --- /dev/null +++ b/yang/cbor-xml-yang @@ -0,0 +1,4 @@ +YDIR=$(dirname $0) +. "$YDIR"/JSON_CBOR.env + +python3 $YDIR/cbor-xml-yang.py "$@" diff --git a/yang/cbor-xml-yang.py b/yang/cbor-xml-yang.py new file mode 100644 index 00000000..a9992e33 --- /dev/null +++ b/yang/cbor-xml-yang.py @@ -0,0 +1,13 @@ +import cbor +#import json +import sys + +import JSON_CBOR + +#print(sys.argv[1]) + +msg = JSON_CBOR.Message(sys.argv[1], "cbor") +with open(sys.argv[2], "w") as of: + msg.dump_xml(of) + +print("OK") diff --git a/yang/cbor.yang b/yang/cbor.yang new file mode 100644 index 00000000..85adc3c4 --- /dev/null +++ b/yang/cbor.yang @@ -0,0 +1,7 @@ +module cbor { + prefix "cbor"; + extension tag { + description "Use this tag while encoding to cbor"; + argument "number"; + } +} diff --git a/yang/ietf-inet-types@2024-01-04.yang b/yang/ietf-inet-types@2024-01-04.yang new file mode 100644 index 00000000..1cf9729a --- /dev/null +++ b/yang/ietf-inet-types@2024-01-04.yang @@ -0,0 +1,474 @@ +module ietf-inet-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types"; + prefix "inet"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types for Internet addresses and related things. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2024-01-04 { + description + "This revision adds CBOR encoding tags"; + reference + "Not even a draft"; + } + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - ip-address-no-zone + - ipv4-address-no-zone + - ipv6-address-no-zone"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + import cbor { + prefix "cbor"; + } + + /*** collection of types related to protocol fields ***/ + + typedef ip-version { + type enumeration { + enum unknown { + value "0"; + description + "An unknown or unspecified version of the Internet + protocol."; + } + enum ipv4 { + value "1"; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum ipv6 { + value "2"; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + + In the value set and its semantics, this type is equivalent + to the InetVersion textual convention of the SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "The dscp type represents a Differentiated Services Code Point + that may be used for marking packets in a traffic stream. + In the value set and its semantics, this type is equivalent + to the Dscp textual convention of the SMIv2."; + reference + "RFC 3289: Management Information Base for the Differentiated + Services Architecture + RFC 2474: Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers + RFC 2780: IANA Allocation Guidelines For Values In + the Internet Protocol and Related Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The ipv6-flow-label type represents the flow identifier or Flow + Label in an IPv6 packet header that may be used to + discriminate traffic flows. + + In the value set and its semantics, this type is equivalent + to the IPv6FlowLabel textual convention of the SMIv2."; + reference + "RFC 3595: Textual Conventions for IPv6 Flow Label + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16 { + range "0..65535"; + } + description + "The port-number type represents a 16-bit port number of an + Internet transport-layer protocol such as UDP, TCP, DCCP, or + SCTP. Port numbers are assigned by IANA. A current list of + all assignments is available from . + + Note that the port number value zero is reserved by IANA. In + situations where the value zero does not make sense, it can + be excluded by subtyping the port-number type. + In the value set and its semantics, this type is equivalent + to the InetPortNumber textual convention of the SMIv2."; + reference + "RFC 768: User Datagram Protocol + RFC 793: Transmission Control Protocol + RFC 4960: Stream Control Transmission Protocol + RFC 4340: Datagram Congestion Control Protocol (DCCP) + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of types related to autonomous systems ***/ + + typedef as-number { + type uint32; + description + "The as-number type represents autonomous system numbers + which identify an Autonomous System (AS). An AS is a set + of routers under a single technical administration, using + an interior gateway protocol and common metrics to route + packets within the AS, and using an exterior gateway + protocol to route packets to other ASes. IANA maintains + the AS number space and has delegated large parts to the + regional registries. + + Autonomous system numbers were originally limited to 16 + bits. BGP extensions have enlarged the autonomous system + number space to 32 bits. This type therefore uses an uint32 + base type without a range restriction in order to support + a larger autonomous system number space. + + In the value set and its semantics, this type is equivalent + to the InetAutonomousSystemNumber textual convention of + the SMIv2."; + reference + "RFC 1930: Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271: A Border Gateway Protocol 4 (BGP-4) + RFC 4001: Textual Conventions for Internet Network Addresses + RFC 6793: BGP Support for Four-Octet Autonomous System (AS) + Number Space"; + } + + /*** collection of types related to IP addresses and hostnames ***/ + + typedef ip-address { + type union { + type inet:ipv4-address; + type inet:ipv6-address; + } + description + "The ip-address type represents an IP address and is IP + version neutral. The format of the textual representation + implies the IP version. This type supports scoped addresses + by allowing zone identifiers in the address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + cbor:tag "52"; + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + + typedef ipv6-address { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(%[\p{N}\p{L}]+)?'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(%.+)?'; + } + cbor:tag "54"; + description + "The ipv6-address type represents an IPv6 address in full, + mixed, shortened, and shortened-mixed notation. The IPv6 + address may include a zone index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + + + The canonical format of IPv6 addresses uses the textual + representation defined in Section 4 of RFC 5952. The + canonical format for the zone index is the numerical + format as described in Section 11.2 of RFC 4007."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-address-no-zone { + type union { + type inet:ipv4-address-no-zone; + type inet:ipv6-address-no-zone; + } + description + "The ip-address-no-zone type represents an IP address and is + IP version neutral. The format of the textual representation + implies the IP version. This type does not support scoped + addresses since it does not allow zone identifiers in the + address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address-no-zone { + type inet:ipv4-address { + pattern '[0-9\.]*'; + } + description + "An IPv4 address without a zone index. This type, derived from + ipv4-address, may be used in situations where the zone is + known from the context and hence no zone index is needed."; + } + + typedef ipv6-address-no-zone { + type inet:ipv6-address { + pattern '[0-9a-fA-F:\.]*'; + } + description + "An IPv6 address without a zone index. This type, derived from + ipv6-address, may be used in situations where the zone is + known from the context and hence no zone index is needed."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-prefix { + type union { + type inet:ipv4-prefix; + type inet:ipv6-prefix; + } + description + "The ip-prefix type represents an IP prefix and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-prefix type represents an IPv4 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv4 prefix has all bits of + the IPv4 address set to zero that are not part of the + IPv4 prefix."; + } + + typedef ipv6-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + + + description + "The ipv6-prefix type represents an IPv6 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The IPv6 address should have all bits that do not belong + to the prefix set to zero. + + The canonical format of an IPv6 prefix has all bits of + the IPv6 address set to zero that are not part of the + IPv6 prefix. Furthermore, the IPv6 address is represented + as defined in Section 4 of RFC 5952."; + reference + "RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + /*** collection of domain name and URI types ***/ + + typedef domain-name { + type string { + pattern + '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + length "1..253"; + } + description + "The domain-name type represents a DNS domain name. The + name SHOULD be fully qualified whenever possible. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + The description clause of schema nodes using the domain-name + type MUST describe when and how these names are resolved to + IP addresses. Note that the resolution of a domain-name value + may require to query multiple DNS records (e.g., A for IPv4 + and AAAA for IPv6). The order of the resolution process and + which DNS record takes precedence can either be defined + explicitly or may depend on the configuration of the + resolver. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be A-labels as per RFC 5890."; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1034: Domain Names - Concepts and Facilities + RFC 1123: Requirements for Internet Hosts -- Application + and Support + RFC 2782: A DNS RR for specifying the location of services + (DNS SRV) + RFC 5890: Internationalized Domain Names in Applications + (IDNA): Definitions and Document Framework"; + } + + typedef host { + type union { + type inet:ip-address; + type inet:domain-name; + } + description + "The host type represents either an IP address or a DNS + domain name."; + } + + typedef uri { + type string; + description + "The uri type represents a Uniform Resource Identifier + (URI) as defined by STD 66. + + Objects using the uri type MUST be in US-ASCII encoding, + and MUST be normalized as described by RFC 3986 Sections + 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary + percent-encoding is removed, and all case-insensitive + characters are set to lowercase except for hexadecimal + digits, which are normalized to uppercase as described in + Section 6.2.2.1. + + The purpose of this normalization is to help provide + unique URIs. Note that this normalization is not + sufficient to provide uniqueness. Two URIs that are + textually distinct after this normalization may still be + equivalent. + + Objects using the uri type may restrict the schemes that + they permit. For example, 'data:' and 'urn:' schemes + might not be appropriate. + + A zero-length URI is not a valid URI. This can be used to + express 'URI absent' where required. + + In the value set and its semantics, this type is equivalent + to the Uri SMIv2 textual convention defined in RFC 5017."; + reference + "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax + RFC 3305: Report from the Joint W3C/IETF URI Planning Interest + Group: Uniform Resource Identifiers (URIs), URLs, + and Uniform Resource Names (URNs): Clarifications + and Recommendations + RFC 5017: MIB Textual Conventions for Uniform Resource + Identifiers (URIs)"; + } + +} diff --git a/yang/json-cbor-yang b/yang/json-cbor-yang index 6f08f9c5..e46bd486 100755 --- a/yang/json-cbor-yang +++ b/yang/json-cbor-yang @@ -1,9 +1,4 @@ YDIR=$(dirname $0) - -pushd $YDIR -python3 -m venv venv -. venv/bin/activate -pip3 install yangson cbor -popd +. "$YDIR"/JSON_CBOR.env python3 $YDIR/json-cbor-yang.py "$@" diff --git a/yang/json-cbor-yang.py b/yang/json-cbor-yang.py index f613303b..805ab5d7 100644 --- a/yang/json-cbor-yang.py +++ b/yang/json-cbor-yang.py @@ -1,5 +1,3 @@ -import cbor -import json import sys import JSON_CBOR @@ -8,6 +6,6 @@ import JSON_CBOR msg = JSON_CBOR.Message(sys.argv[1], "json") with open(sys.argv[2], "wb") as of: - cbor.dump(msg.raw, of) + msg.dump_cbor(of) print("OK") diff --git a/yang/test_ip.cbor b/yang/test_ip.cbor new file mode 100644 index 00000000..fad7c064 Binary files /dev/null and b/yang/test_ip.cbor differ diff --git a/yang/test_ip.json b/yang/test_ip.json new file mode 100644 index 00000000..a3fbdcb8 --- /dev/null +++ b/yang/test_ip.json @@ -0,0 +1,6 @@ +{ + "test_ip:message": { + "address": "2001:db8::dead:beef", + "foo": 42 + } +} diff --git a/yang/test_ip.xml b/yang/test_ip.xml new file mode 100644 index 00000000..e305bd16 --- /dev/null +++ b/yang/test_ip.xml @@ -0,0 +1 @@ +
2001:db8::dead:beef
42
\ No newline at end of file diff --git a/yang/test_ip.yang b/yang/test_ip.yang new file mode 100644 index 00000000..188c6125 --- /dev/null +++ b/yang/test_ip.yang @@ -0,0 +1,21 @@ +module test_ip { + + namespace "https://bird.nic.cz/yang/v2.15/cli-debug"; + + prefix "test_ip"; + + description "testing IP address formatting"; + + import ietf-inet-types { + prefix "inet"; + } + + container message { + leaf address { + type inet:ip-address; + } + leaf foo { + type int8; + } + } +} diff --git a/yang/xml-cbor-yang b/yang/xml-cbor-yang new file mode 100755 index 00000000..e7e5a1be --- /dev/null +++ b/yang/xml-cbor-yang @@ -0,0 +1,4 @@ +YDIR=$(dirname $0) +. "$YDIR"/JSON_CBOR.env + +python3 $YDIR/xml-cbor-yang.py "$@" diff --git a/yang/xml-cbor-yang.py b/yang/xml-cbor-yang.py new file mode 100644 index 00000000..0122a4f2 --- /dev/null +++ b/yang/xml-cbor-yang.py @@ -0,0 +1,11 @@ +import sys + +import JSON_CBOR + +#print(sys.argv[1]) + +msg = JSON_CBOR.Message(sys.argv[1], "xml") +with open(sys.argv[2], "wb") as of: + msg.dump_cbor(of) + +print("OK") diff --git a/yang/yang-library.json b/yang/yang-library.json index 611d149c..0d32c4ca 100644 --- a/yang/yang-library.json +++ b/yang/yang-library.json @@ -2,6 +2,18 @@ "ietf-yang-library:modules-state": { "module-set-id": "12345678abcdef1cefd14622032c432fa598540e", "module": [ + { + "name": "cbor", + "revision": "", + "namespace": "tmp", + "conformance-type": "import" + }, + { + "name": "ietf-inet-types", + "revision": "2024-01-04", + "namespace": "urn:ietf:params:xml:ns:yang:ietf-inet-types", + "conformance-type": "import" + }, { "name": "show_memory", "namespace": "https://bird.nic.cz/yang/v2.15/cli-debug", @@ -14,12 +26,6 @@ "revision": "", "conformance-type": "implement" }, - { - "name": "show_symbols", - "namespace": "https://bird.nic.cz/yang/v2.15/cli-debug", - "revision": "", - "conformance-type": "implement" - }, { "name": "show_ospf", "namespace": "https://bird.nic.cz/yang/v2.15/cli-debug", @@ -37,6 +43,12 @@ "namespace": "https://bird.nic.cz/yang/v2.15/cli-debug", "revision": "", "conformance-type": "implement" + }, + { + "name": "test_ip", + "namespace": "https://bird.nic.cz/yang/v2.15/cli-debug", + "revision": "", + "conformance-type": "implement" } ] }