├── .bazelversion ├── .gitignore ├── AUTHORS ├── BUILD.bazel ├── LICENSE ├── MODULE.bazel ├── MODULE.bazel.lock ├── README.md ├── cmd └── xdr_compiler │ ├── BUILD.bazel │ └── main.go ├── go.mod ├── go.sum ├── internal └── mock │ ├── BUILD.bazel │ ├── aliases │ ├── BUILD.bazel │ └── aliases.go │ └── dummy.go ├── patches ├── com_github_golang_mock │ └── mocks-for-funcs.diff └── rules_antlr │ ├── antlr-4.10.diff │ └── bzlmod.diff ├── pkg ├── compiler │ ├── model │ │ ├── BUILD.bazel │ │ ├── bool_type.go │ │ ├── constant_definition.go │ │ ├── constant_resolving_value_visitor.go │ │ ├── constant_value.go │ │ ├── declaration.go │ │ ├── definition.go │ │ ├── emitting_integer_value_visitor.go │ │ ├── enum_type.go │ │ ├── fixed_length_array_type.go │ │ ├── fixed_length_opaque_type.go │ │ ├── floating_point_type.go │ │ ├── foreign_identifier.go │ │ ├── identifier.go │ │ ├── identifier_factory.go │ │ ├── identifier_type.go │ │ ├── identifier_value.go │ │ ├── integer_type.go │ │ ├── named_declaration.go │ │ ├── optional_type.go │ │ ├── procedure.go │ │ ├── program_definition.go │ │ ├── registry.go │ │ ├── resolver.go │ │ ├── root_identifier_factory.go │ │ ├── sink.go │ │ ├── string_type.go │ │ ├── struct_type.go │ │ ├── top_level.go │ │ ├── type.go │ │ ├── type_definition.go │ │ ├── union_type.go │ │ ├── value.go │ │ ├── value_visitor.go │ │ ├── variable_length_array_type.go │ │ ├── variable_length_opaque_type.go │ │ ├── version.go │ │ └── void_declaration.go │ ├── parser │ │ ├── BUILD.bazel │ │ ├── XDR.g4 │ │ ├── xdr_base_listener.go │ │ ├── xdr_lexer.go │ │ ├── xdr_listener.go │ │ └── xdr_parser.go │ └── tests │ │ ├── BUILD.bazel │ │ ├── diff.bzl │ │ ├── nested │ │ ├── BUILD.bazel │ │ ├── nested.go │ │ └── nested.x │ │ └── rfc4506 │ │ ├── BUILD.bazel │ │ ├── rfc4506.go │ │ ├── rfc4506.x │ │ └── rfc4506_test.go ├── protocols │ ├── darwin_nfs_sys_prot │ │ ├── BUILD.bazel │ │ ├── darwin_nfs_sys_prot.x │ │ └── darwin_nfs_sys_prot_xdr.go │ ├── mount │ │ ├── BUILD.bazel │ │ ├── mount.x │ │ └── mount_xdr.go │ ├── nfsv3 │ │ ├── BUILD.bazel │ │ ├── nfsv3.x │ │ └── nfsv3_xdr.go │ ├── nfsv4 │ │ ├── BUILD.bazel │ │ ├── nfsv4.x │ │ └── nfsv4_xdr.go │ ├── nlm │ │ ├── BUILD.bazel │ │ ├── nlm.x │ │ └── nlm_xdr.go │ └── rpcv2 │ │ ├── BUILD.bazel │ │ ├── rpcv2.x │ │ └── rpcv2_xdr.go ├── rpcserver │ ├── BUILD.bazel │ ├── allow_authenticator.go │ ├── authenticator.go │ ├── server.go │ └── server_test.go └── runtime │ ├── BUILD.bazel │ ├── ascii_string.go │ ├── ascii_string_test.go │ ├── bool.go │ ├── bool_test.go │ ├── integer.go │ ├── integer_test.go │ ├── opaque.go │ ├── opaque_test.go │ ├── utf8_string.go │ └── utf8_string_test.go ├── tools ├── BUILD.bazel └── deps.go └── xdr.bzl /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.1.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | /node_modules 3 | 4 | # Emacs 5 | *~ 6 | .\#* 7 | \#*\# 8 | 9 | # Vim 10 | [._]*.s[a-v][a-z] 11 | [._]*.sw[a-p] 12 | [._]s[a-rt-v][a-z] 13 | [._]ss[a-gi-z] 14 | [._]sw[a-p] 15 | [._]*.un~ 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ed Schouten 2 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@gazelle//:def.bzl", "gazelle") 2 | 3 | # gazelle:prefix github.com/buildbarn/go-xdr 4 | gazelle( 5 | name = "gazelle", 6 | ) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module(name = "com_github_buildbarn_go_xdr") 2 | 3 | bazel_dep(name = "gazelle", version = "0.35.0") 4 | bazel_dep(name = "rules_antlr") 5 | bazel_dep(name = "rules_go", version = "0.46.0") 6 | 7 | git_override( 8 | module_name = "rules_antlr", 9 | commit = "89a29cca479363a5aee53e203719510bdc6be6ff", 10 | init_submodules = True, 11 | patches = [ 12 | "//:patches/rules_antlr/antlr-4.10.diff", 13 | "//:patches/rules_antlr/bzlmod.diff", 14 | ], 15 | remote = "https://github.com/marcohu/rules_antlr.git", 16 | ) 17 | 18 | antlr = use_extension("@rules_antlr//antlr:extensions.bzl", "antlr") 19 | antlr.download(version = "4.10") 20 | 21 | go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") 22 | go_sdk.download(version = "1.22.1") 23 | 24 | go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") 25 | go_deps.from_file(go_mod = "//:go.mod") 26 | use_repo( 27 | go_deps, 28 | "cc_mvdan_gofumpt", 29 | "com_github_antlr_antlr4_runtime_go_antlr", 30 | "com_github_golang_mock", 31 | "com_github_gordonklaus_ineffassign", 32 | "com_github_stretchr_testify", 33 | "org_golang_x_sync", 34 | ) 35 | 36 | go_deps_dev = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True) 37 | go_deps_dev.module_override( 38 | patches = ["//:patches/com_github_golang_mock/mocks-for-funcs.diff"], 39 | path = "github.com/golang/mock", 40 | ) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # An XDR to Go compiler 2 | 3 | For [Buildbarn Remote Execution](https://github.com/buildbarn/bb-remote-execution) 4 | we have implemented a userspace [NFSv4](https://en.wikipedia.org/wiki/Network_File_System) 5 | server. NFS makes use of [XDR (External Data Representation)](https://en.wikipedia.org/wiki/External_Data_Representation) 6 | to describe its wire format. The XDR description for NFSv4 can be found 7 | in [RFC 7531](https://datatracker.ietf.org/doc/html/rfc7531). 8 | 9 | This repository provides a compiler that is capable of converting the 10 | XDR description of NFSv4, and other protocols, to Go code. For each data 11 | type in the XDR description, a Go language equivalent is created. Each 12 | data type in Go implements the `io.ReaderFrom` and `io.WriterTo` 13 | interfaces, allowing instances of the data type to be deserialized and 14 | serialized, respectively. 15 | 16 | Furthermore, the XDR to Go compiler is capable of emitting interfaces 17 | for ONC RPCv2 ("Sun RPC") program definitions, as described in 18 | [RFC 5531](https://datatracker.ietf.org/doc/html/rfc5531). A simple RPC 19 | server implementation is also provided. 20 | 21 | ## Grammar accepted by the XDR to Go compiler 22 | 23 | The grammar supported by the XDR to Go compiler should match with what's 24 | described in RFCs 4506 and 5531. Minor extensions have been made: 25 | 26 | - Every XDR description file should start with a `package` statement, 27 | containing the full name of the resulting Go package. 28 | - After the `package` statement, there may be `import` statements. These 29 | can be used to express dependencies between XDR description files, 30 | similar to Protobuf schema files can also depend on each other. 31 | - As XDR's string type only permits ASCII characters, an `utf8string` 32 | type has been added for UTF-8. 33 | 34 | Please refer to the `pkg/compiler/tests` and `pkg/protocols` directories 35 | for examples. 36 | 37 | ## Bundled protocols 38 | 39 | The `pkg/protocols` directory includes pregenerated copies of commonly 40 | used XDR descriptions, such as ONC RPCv2 and NFSv4. Also included is a 41 | copy of the NFS mount arguments protocol used by macOS. 42 | 43 | ## Why not use... 44 | 45 | There are already a couple of other XDR libraries for Go. Examples 46 | include: 47 | 48 | - [github.com/davecgh/go-xdr](https://github.com/davecgh/go-xdr) 49 | - [github.com/stellar/go/xdr](https://github.com/stellar/go/tree/master/xdr) 50 | - [github.com/xdrpp/goxdr](https://github.com/xdrpp/goxdr) 51 | 52 | All of these were considered, but turned out to be unsuitable for 53 | Buildbarn's use case for one or more of the following reasons: 54 | 55 | - They depend on runtime reflection, 56 | - They don't include an XDR description compiler, requiring definitions 57 | to be translated to Go code manually, 58 | - They don't support ONC RPCv2 `program` definitions, 59 | - They can't compile XDR descriptions that depend on definitions coming 60 | from other protocols (e.g., NFSv4's dependency on ONC RPCv2). 61 | 62 | ## License 63 | 64 | The XDR to Go compiler is Apache v2 licensed, just like other Buildbarn 65 | components. **Protocols that are bundled under `pkg/protocols` are 66 | subject to their original license terms.** 67 | -------------------------------------------------------------------------------- /cmd/xdr_compiler/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_library( 4 | name = "xdr_compiler_lib", 5 | srcs = ["main.go"], 6 | importpath = "github.com/buildbarn/go-xdr/cmd/xdr_compiler", 7 | visibility = ["//visibility:private"], 8 | deps = [ 9 | "//pkg/compiler/model", 10 | "//pkg/compiler/parser", 11 | "@com_github_antlr_antlr4_runtime_go_antlr//:antlr", 12 | ], 13 | ) 14 | 15 | go_binary( 16 | name = "xdr_compiler", 17 | embed = [":xdr_compiler_lib"], 18 | visibility = ["//visibility:public"], 19 | ) 20 | -------------------------------------------------------------------------------- /cmd/xdr_compiler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/antlr/antlr4/runtime/Go/antlr" 14 | "github.com/buildbarn/go-xdr/pkg/compiler/model" 15 | "github.com/buildbarn/go-xdr/pkg/compiler/parser" 16 | ) 17 | 18 | type simpleSink struct { 19 | w *bufio.Writer 20 | } 21 | 22 | type typeInfo struct { 23 | pkg string 24 | t model.Type 25 | } 26 | 27 | type simpleResolver struct { 28 | constants map[string]*big.Int 29 | types map[string]typeInfo 30 | pendingImports []string 31 | } 32 | 33 | func (r *simpleResolver) RegisterConstant(name string, c *big.Int) error { 34 | if _, ok := r.constants[name]; ok { 35 | return fmt.Errorf("Constant defined twice: %#v", name) 36 | } 37 | r.constants[name] = c 38 | return nil 39 | } 40 | 41 | func (r *simpleResolver) RegisterType(name, pkg string, t model.Type) error { 42 | if _, ok := r.types[name]; ok { 43 | return fmt.Errorf("Type defined twice: %#v", name) 44 | } 45 | r.types[name] = typeInfo{ 46 | pkg: pkg, 47 | t: t, 48 | } 49 | return nil 50 | } 51 | 52 | func (r *simpleResolver) RegisterImport(path string) error { 53 | r.pendingImports = append(r.pendingImports, path) 54 | return nil 55 | } 56 | 57 | func (r *simpleResolver) ResolveConstant(name string) (*big.Int, error) { 58 | c, ok := r.constants[name] 59 | if !ok { 60 | return nil, fmt.Errorf("Unknown constant: %#v", name) 61 | } 62 | return c, nil 63 | } 64 | 65 | func (r *simpleResolver) ResolveType(name string) (string, model.Type, error) { 66 | ti, ok := r.types[name] 67 | if !ok { 68 | return "", nil, fmt.Errorf("Unknown type: %#v", name) 69 | } 70 | return ti.pkg, ti.t, nil 71 | } 72 | 73 | type errorDetector struct { 74 | base antlr.ErrorListener 75 | gotErrors bool 76 | } 77 | 78 | func (ed *errorDetector) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { 79 | ed.gotErrors = true 80 | ed.base.SyntaxError(recognizer, offendingSymbol, line, column, msg, e) 81 | } 82 | 83 | func (ed *errorDetector) ReportAmbiguity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, exact bool, ambigAlts *antlr.BitSet, configs antlr.ATNConfigSet) { 84 | ed.gotErrors = true 85 | ed.base.ReportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs) 86 | } 87 | 88 | func (ed *errorDetector) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, conflictingAlts *antlr.BitSet, configs antlr.ATNConfigSet) { 89 | ed.gotErrors = true 90 | ed.ReportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs) 91 | } 92 | 93 | func (ed *errorDetector) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex, prediction int, configs antlr.ATNConfigSet) { 94 | ed.gotErrors = true 95 | ed.ReportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs) 96 | } 97 | 98 | type stringList []string 99 | 100 | func (sl *stringList) String() string { 101 | return strings.Join(*sl, ":") 102 | } 103 | 104 | func (sl *stringList) Set(v string) error { 105 | *sl = append(*sl, v) 106 | return nil 107 | } 108 | 109 | func main() { 110 | var importPaths stringList 111 | flag.Var(&importPaths, "I", "Add search path for source files") 112 | flag.Parse() 113 | args := flag.Args() 114 | if len(args) != 2 { 115 | log.Fatal("Usage: [-I import_path] in.x out.go") 116 | } 117 | 118 | r := simpleResolver{ 119 | constants: map[string]*big.Int{}, 120 | types: map[string]typeInfo{}, 121 | pendingImports: []string{args[0]}, 122 | } 123 | 124 | processedImports := map[string]struct{}{} 125 | var inputTopLevel *model.TopLevel 126 | for len(r.pendingImports) > 0 { 127 | path := r.pendingImports[0] 128 | r.pendingImports = r.pendingImports[1:] 129 | 130 | // Prevent duplicate processing of the same source file. 131 | if _, ok := processedImports[path]; ok { 132 | continue 133 | } 134 | processedImports[path] = struct{}{} 135 | 136 | var input *antlr.FileStream 137 | for _, importPath := range importPaths { 138 | var err error 139 | input, err = antlr.NewFileStream(filepath.Join(importPath, path)) 140 | if err == nil { 141 | goto FoundSourceFile 142 | } 143 | if !os.IsNotExist(err) { 144 | log.Fatalf("Failed to open file %#v: %s", path, err) 145 | } 146 | } 147 | log.Fatalf("File %#v not found in any of the import paths", path) 148 | FoundSourceFile: 149 | p := parser.NewXDRParser( 150 | antlr.NewCommonTokenStream( 151 | parser.NewXDRLexer(input), 152 | 0)) 153 | 154 | errorDetector := errorDetector{ 155 | base: antlr.NewDiagnosticErrorListener(true), 156 | } 157 | p.AddErrorListener(&errorDetector) 158 | topLevel := p.TopLevel().GetV() 159 | if errorDetector.gotErrors { 160 | os.Exit(1) 161 | } 162 | if inputTopLevel == nil { 163 | inputTopLevel = &topLevel 164 | } 165 | 166 | if err := topLevel.Register(&r); err != nil { 167 | log.Fatal(err) 168 | } 169 | } 170 | 171 | // Emit the resulting Go source file. 172 | f, err := os.OpenFile(args[1], os.O_CREATE|os.O_WRONLY, 0o666) 173 | if err != nil { 174 | log.Fatal("Failed to open output file: ", err) 175 | } 176 | 177 | w := bufio.NewWriter(f) 178 | if err := inputTopLevel.Emit(w, &r); err != nil { 179 | log.Fatal(err) 180 | } 181 | if err := w.Flush(); err != nil { 182 | log.Fatal("Failed to write to output file: ", err) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/buildbarn/go-xdr 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 7 | github.com/golang/mock v1.6.0 8 | github.com/gordonklaus/ineffassign v0.1.0 9 | github.com/stretchr/testify v1.9.0 10 | golang.org/x/sync v0.7.0 11 | mvdan.cc/gofumpt v0.6.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/google/go-cmp v0.6.0 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | golang.org/x/mod v0.17.0 // indirect 19 | golang.org/x/tools v0.21.0 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= 2 | github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 6 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 7 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 8 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 10 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 11 | github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= 12 | github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= 13 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 14 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 15 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 16 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 20 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 21 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 22 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 23 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 24 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 25 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 26 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 27 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 28 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 29 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 30 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 31 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 35 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 36 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 42 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 43 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 44 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 45 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 46 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 47 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 48 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 49 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 50 | golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= 51 | golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 52 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 56 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 58 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 59 | mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= 60 | mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= 61 | -------------------------------------------------------------------------------- /internal/mock/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//extras:gomock.bzl", "gomock") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | gomock( 5 | name = "aliases", 6 | out = "aliases.go", 7 | interfaces = [ 8 | "Context", 9 | "Reader", 10 | "Writer", 11 | ], 12 | library = "//internal/mock/aliases", 13 | package = "mock", 14 | ) 15 | 16 | gomock( 17 | name = "rpcserver", 18 | out = "rpcserver.go", 19 | interfaces = [ 20 | "Authenticator", 21 | "Service", 22 | ], 23 | library = "//pkg/rpcserver", 24 | package = "mock", 25 | ) 26 | 27 | go_library( 28 | name = "mock", 29 | srcs = [ 30 | "aliases.go", 31 | "rpcserver.go", 32 | ], 33 | importpath = "github.com/buildbarn/go-xdr/internal/mock", 34 | visibility = ["//:__subpackages__"], 35 | # keep 36 | deps = [ 37 | "//pkg/protocols/rpcv2", 38 | "@com_github_golang_mock//gomock", 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /internal/mock/aliases/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "aliases", 5 | srcs = ["aliases.go"], 6 | importpath = "github.com/buildbarn/go-xdr/internal/mock/aliases", 7 | visibility = ["//:__subpackages__"], 8 | ) 9 | -------------------------------------------------------------------------------- /internal/mock/aliases/aliases.go: -------------------------------------------------------------------------------- 1 | package aliases 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | // This file contains aliases for some of the interfaces provided by the 9 | // Go standard library. The only reason this file exists is to allow the 10 | // gomock() Bazel rule to emit mocks for them, as that rule is only 11 | // capable of emitting mocks for interfaces built through a 12 | // go_library(). 13 | 14 | // Context is an alias of context.Context. 15 | type Context = context.Context 16 | 17 | // Reader is an alias of io.Reader. 18 | type Reader = io.Reader 19 | 20 | // Writer is an alias of io.Writer. 21 | type Writer = io.Writer 22 | -------------------------------------------------------------------------------- /internal/mock/dummy.go: -------------------------------------------------------------------------------- 1 | package mock 2 | -------------------------------------------------------------------------------- /patches/com_github_golang_mock/mocks-for-funcs.diff: -------------------------------------------------------------------------------- 1 | diff --git mockgen/model/model.go mockgen/model/model.go 2 | index 94d7f4b..abc0cfd 100644 3 | --- mockgen/model/model.go 4 | +++ mockgen/model/model.go 5 | @@ -335,28 +335,44 @@ func (tp *TypeParametersType) addImports(im map[string]bool) { 6 | 7 | // The following code is intended to be called by the program generated by ../reflect.go. 8 | 9 | -// InterfaceFromInterfaceType returns a pointer to an interface for the 10 | -// given reflection interface type. 11 | -func InterfaceFromInterfaceType(it reflect.Type) (*Interface, error) { 12 | - if it.Kind() != reflect.Interface { 13 | - return nil, fmt.Errorf("%v is not an interface", it) 14 | - } 15 | +// InterfaceFromInterfaceOrFuncType returns a pointer to an interface for the 16 | +// given reflection interface or function type. 17 | +func InterfaceFromInterfaceOrFuncType(it reflect.Type) (*Interface, error) { 18 | intf := &Interface{} 19 | 20 | - for i := 0; i < it.NumMethod(); i++ { 21 | - mt := it.Method(i) 22 | - // TODO: need to skip unexported methods? or just raise an error? 23 | + switch it.Kind() { 24 | + case reflect.Interface: 25 | + for i := 0; i < it.NumMethod(); i++ { 26 | + mt := it.Method(i) 27 | + // TODO: need to skip unexported methods? or just raise an error? 28 | + m := &Method{ 29 | + Name: mt.Name, 30 | + } 31 | + 32 | + var err error 33 | + m.In, m.Variadic, m.Out, err = funcArgsFromType(mt.Type) 34 | + if err != nil { 35 | + return nil, err 36 | + } 37 | + 38 | + intf.AddMethod(m) 39 | + } 40 | + case reflect.Func: 41 | + // For function types, generate an interface having a single 42 | + // "Call" method. 43 | m := &Method{ 44 | - Name: mt.Name, 45 | + Name: "Call", 46 | } 47 | 48 | var err error 49 | - m.In, m.Variadic, m.Out, err = funcArgsFromType(mt.Type) 50 | + m.In, m.Variadic, m.Out, err = funcArgsFromType(it) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | intf.AddMethod(m) 56 | + default: 57 | + return nil, fmt.Errorf("%v is not an interface or function type", it) 58 | } 59 | 60 | return intf, nil 61 | diff --git mockgen/parse.go mockgen/parse.go 62 | index 21c0d70..98ce222 100644 63 | --- mockgen/parse.go 64 | +++ mockgen/parse.go 65 | @@ -201,8 +201,8 @@ func (p *fileParser) parseAuxFiles(auxFiles string) error { 66 | } 67 | 68 | func (p *fileParser) addAuxInterfacesFromFile(pkg string, file *ast.File) { 69 | - for ni := range iterInterfaces(file) { 70 | - p.auxInterfaces.Set(pkg, ni.name.Name, ni) 71 | + for _, ni := range extractInterfaces(file) { 72 | + p.auxInterfaces.Set(pkg, ni.name.Name, &ni) 73 | } 74 | } 75 | 76 | @@ -228,8 +228,22 @@ func (p *fileParser) parseFile(importPath string, file *ast.File) (*model.Packag 77 | } 78 | 79 | var is []*model.Interface 80 | - for ni := range iterInterfaces(file) { 81 | - i, err := p.parseInterface(ni.name.String(), importPath, ni) 82 | + for _, ni := range extractInterfaces(file) { 83 | + i, err := p.parseInterface(ni.name.String(), importPath, ni.typeParams, ni.it.Methods.List) 84 | + if err != nil { 85 | + return nil, err 86 | + } 87 | + is = append(is, i) 88 | + } 89 | + for _, ni := range extractFuncs(file) { 90 | + // For function types, generate an interface having a single 91 | + // "Call" method. 92 | + i, err := p.parseInterface(ni.name.String(), importPath, nil, []*ast.Field{ 93 | + { 94 | + Names: []*ast.Ident{{Name: "Call"}}, 95 | + Type: ni.ft, 96 | + }, 97 | + }) 98 | if err != nil { 99 | return nil, err 100 | } 101 | @@ -263,8 +277,8 @@ func (p *fileParser) parsePackage(path string) (*fileParser, error) { 102 | 103 | for _, pkg := range pkgs { 104 | file := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates|ast.FilterUnassociatedComments|ast.FilterImportDuplicates) 105 | - for ni := range iterInterfaces(file) { 106 | - newP.importedInterfaces.Set(path, ni.name.Name, ni) 107 | + for _, ni := range extractInterfaces(file) { 108 | + newP.importedInterfaces.Set(path, ni.name.Name, &ni) 109 | } 110 | imports, _ := importsOfFile(file) 111 | for pkgName, pkgI := range imports { 112 | @@ -274,11 +288,11 @@ func (p *fileParser) parsePackage(path string) (*fileParser, error) { 113 | return newP, nil 114 | } 115 | 116 | -func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*model.Interface, error) { 117 | +func (p *fileParser) parseInterface(name, pkg string, typeParams []*ast.Field, fields []*ast.Field) (*model.Interface, error) { 118 | iface := &model.Interface{Name: name} 119 | tps := make(map[string]bool) 120 | 121 | - tp, err := p.parseFieldList(pkg, it.typeParams, tps) 122 | + tp, err := p.parseFieldList(pkg, typeParams, tps) 123 | if err != nil { 124 | return nil, fmt.Errorf("unable to parse interface type parameters: %v", name) 125 | } 126 | @@ -287,7 +301,7 @@ func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*mode 127 | tps[v.Name] = true 128 | } 129 | 130 | - for _, field := range it.it.Methods.List { 131 | + for _, field := range fields { 132 | switch v := field.Type.(type) { 133 | case *ast.FuncType: 134 | if nn := len(field.Names); nn != 1 { 135 | @@ -312,7 +326,7 @@ func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*mode 136 | var embeddedIface *model.Interface 137 | if embeddedIfaceType != nil { 138 | var err error 139 | - embeddedIface, err = p.parseInterface(v.String(), pkg, embeddedIfaceType) 140 | + embeddedIface, err = p.parseInterface(v.String(), pkg, embeddedIfaceType.typeParams, embeddedIfaceType.it.Methods.List) 141 | if err != nil { 142 | return nil, err 143 | } 144 | @@ -330,7 +344,7 @@ func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*mode 145 | return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", pkg, v.String()) 146 | } 147 | 148 | - embeddedIface, err = ip.parseInterface(v.String(), pkg, embeddedIfaceType) 149 | + embeddedIface, err = ip.parseInterface(v.String(), pkg, embeddedIfaceType.typeParams, embeddedIfaceType.it.Methods.List) 150 | if err != nil { 151 | return nil, err 152 | } 153 | @@ -352,7 +366,7 @@ func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*mode 154 | var err error 155 | embeddedIfaceType := p.auxInterfaces.Get(filePkg, sel) 156 | if embeddedIfaceType != nil { 157 | - embeddedIface, err = p.parseInterface(sel, filePkg, embeddedIfaceType) 158 | + embeddedIface, err = p.parseInterface(sel, filePkg, embeddedIfaceType.typeParams, embeddedIfaceType.it.Methods.List) 159 | if err != nil { 160 | return nil, err 161 | } 162 | @@ -373,7 +387,7 @@ func (p *fileParser) parseInterface(name, pkg string, it *namedInterface) (*mode 163 | if embeddedIfaceType = parser.importedInterfaces.Get(path, sel); embeddedIfaceType == nil { 164 | return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", path, sel) 165 | } 166 | - embeddedIface, err = parser.parseInterface(sel, path, embeddedIfaceType) 167 | + embeddedIface, err = parser.parseInterface(sel, path, embeddedIfaceType.typeParams, embeddedIfaceType.it.Methods.List) 168 | if err != nil { 169 | return nil, err 170 | } 171 | @@ -662,31 +676,54 @@ type namedInterface struct { 172 | typeParams []*ast.Field 173 | } 174 | 175 | -// Create an iterator over all interfaces in file. 176 | -func iterInterfaces(file *ast.File) <-chan *namedInterface { 177 | - ch := make(chan *namedInterface) 178 | - go func() { 179 | - for _, decl := range file.Decls { 180 | - gd, ok := decl.(*ast.GenDecl) 181 | - if !ok || gd.Tok != token.TYPE { 182 | +// Extracts all interfaces from a file. 183 | +func extractInterfaces(file *ast.File) (interfaces []namedInterface) { 184 | + for _, decl := range file.Decls { 185 | + gd, ok := decl.(*ast.GenDecl) 186 | + if !ok || gd.Tok != token.TYPE { 187 | + continue 188 | + } 189 | + for _, spec := range gd.Specs { 190 | + ts, ok := spec.(*ast.TypeSpec) 191 | + if !ok { 192 | + continue 193 | + } 194 | + it, ok := ts.Type.(*ast.InterfaceType) 195 | + if !ok { 196 | continue 197 | } 198 | - for _, spec := range gd.Specs { 199 | - ts, ok := spec.(*ast.TypeSpec) 200 | - if !ok { 201 | - continue 202 | - } 203 | - it, ok := ts.Type.(*ast.InterfaceType) 204 | - if !ok { 205 | - continue 206 | - } 207 | 208 | - ch <- &namedInterface{ts.Name, it, getTypeSpecTypeParams(ts)} 209 | + interfaces = append(interfaces, namedInterface{ts.Name, it, getTypeSpecTypeParams(ts)}) 210 | + } 211 | + } 212 | + return 213 | +} 214 | + 215 | +type namedFunc struct { 216 | + name *ast.Ident 217 | + ft *ast.FuncType 218 | +} 219 | + 220 | +// Extracts all function types from a file. 221 | +func extractFuncs(file *ast.File) (funcs []namedFunc) { 222 | + for _, decl := range file.Decls { 223 | + gd, ok := decl.(*ast.GenDecl) 224 | + if !ok || gd.Tok != token.TYPE { 225 | + continue 226 | + } 227 | + for _, spec := range gd.Specs { 228 | + ts, ok := spec.(*ast.TypeSpec) 229 | + if !ok { 230 | + continue 231 | } 232 | + ft, ok := ts.Type.(*ast.FuncType) 233 | + if !ok { 234 | + continue 235 | + } 236 | + funcs = append(funcs, namedFunc{ts.Name, ft}) 237 | } 238 | - close(ch) 239 | - }() 240 | - return ch 241 | + } 242 | + return 243 | } 244 | 245 | // isVariadic returns whether the function is variadic. 246 | diff --git mockgen/reflect.go mockgen/reflect.go 247 | index 4f86a15..c84c49f 100644 248 | --- mockgen/reflect.go 249 | +++ mockgen/reflect.go 250 | @@ -224,7 +224,7 @@ func main() { 251 | } 252 | 253 | for _, it := range its { 254 | - intf, err := model.InterfaceFromInterfaceType(it.typ) 255 | + intf, err := model.InterfaceFromInterfaceOrFuncType(it.typ) 256 | if err != nil { 257 | fmt.Fprintf(os.Stderr, "Reflection: %v\n", err) 258 | os.Exit(1) 259 | -------------------------------------------------------------------------------- /patches/rules_antlr/antlr-4.10.diff: -------------------------------------------------------------------------------- 1 | diff --git antlr/repositories.bzl antlr/repositories.bzl 2 | index e2525ab..12f79f0 100644 3 | --- antlr/repositories.bzl 4 | +++ antlr/repositories.bzl 5 | @@ -3,13 +3,18 @@ 6 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_jar") 7 | load(":lang.bzl", "C", "CPP", "GO", "JAVA", "OBJC", "PYTHON", "PYTHON2", "PYTHON3", supportedLanguages = "supported") 8 | 9 | -v4 = [4, "4.7.1", "4.7.2", "4.8"] 10 | +v4 = [4, "4.7.1", "4.7.2", "4.8", "4.10"] 11 | v4_opt = [4, "4.7.1", "4.7.2", "4.7.3", "4.7.4"] 12 | v3 = [3, "3.5.2"] 13 | v2 = [2, "2.7.7"] 14 | 15 | PACKAGES = { 16 | "antlr": { 17 | + "4.10": { 18 | + "url": "https://github.com/antlr/antlr4/archive/4.10.tar.gz", 19 | + "prefix": "antlr4-4.10", 20 | + "sha256": "39b2604fc75fa77323bd7046f2fb750c818cf11fcce2cd6cca06b6697f60ffbb", 21 | + }, 22 | "4.8": { 23 | "url": "https://github.com/antlr/antlr4/archive/4.8.tar.gz", 24 | "prefix": "antlr4-4.8", 25 | @@ -38,6 +43,10 @@ PACKAGES = { 26 | }, 27 | }, 28 | "antlr4_runtime": { 29 | + "4.10": { 30 | + "path": "org/antlr/antlr4-runtime/4.10/antlr4-runtime-4.10.jar", 31 | + "sha256": "4663a38f88e1935ea612336cbf34f702f10bd0af8e62715a9e959629f141654e", 32 | + }, 33 | "4.8": { 34 | "path": "org/antlr/antlr4-runtime/4.8/antlr4-runtime-4.8.jar", 35 | "sha256": "2337df5d81e715b39aeea07aac46ad47e4f1f9e9cd7c899f124f425913efdcf8", 36 | @@ -68,6 +77,10 @@ PACKAGES = { 37 | }, 38 | }, 39 | "antlr4_tool": { 40 | + "4.10": { 41 | + "path": "org/antlr/antlr4/4.10/antlr4-4.10.jar", 42 | + "sha256": "f32485cfdf114295a58cd2005af9463706c5fd43d900118126eb3a9ac36bfec3", 43 | + }, 44 | "4.8": { 45 | "path": "org/antlr/antlr4/4.8/antlr4-4.8.jar", 46 | "sha256": "6e4477689371f237d4d8aa40642badbb209d4628ccdd81234d90f829a743bac8", 47 | @@ -179,7 +192,9 @@ def rules_antlr_dependencies(*versionsAndLanguages): 48 | languages = [JAVA] 49 | 50 | for version in sorted(versions, key = _toString): 51 | - if version == 4 or version == "4.8": 52 | + if version == 4 or version == "4.10": 53 | + _antlr410_dependencies(languages) 54 | + elif version == "4.8": 55 | _antlr48_dependencies(languages) 56 | elif version == "4.7.2": 57 | _antlr472_dependencies(languages) 58 | @@ -217,6 +232,19 @@ def rules_antlr_optimized_dependencies(version): 59 | else: 60 | fail('Unsupported ANTLR version provided: "{0}". Currently supported are: {1}'.format(version, v4_opt), attr = "version") 61 | 62 | +def _antlr410_dependencies(languages): 63 | + _antlr4_dependencies( 64 | + "4.10", 65 | + languages, 66 | + { 67 | + "antlr4_runtime": "4.10", 68 | + "antlr4_tool": "4.10", 69 | + "antlr3_runtime": "3.5.2", 70 | + "stringtemplate4": "4.3", 71 | + "javax_json": "1.0.4", 72 | + }, 73 | + ) 74 | + 75 | def _antlr48_dependencies(languages): 76 | _antlr4_dependencies( 77 | "4.8", 78 | -------------------------------------------------------------------------------- /patches/rules_antlr/bzlmod.diff: -------------------------------------------------------------------------------- 1 | diff --git MODULE.bazel MODULE.bazel 2 | new file mode 100644 3 | index 0000000..eb12741 4 | --- /dev/null 5 | +++ MODULE.bazel 6 | @@ -0,0 +1,14 @@ 7 | +module(name = "rules_antlr") 8 | + 9 | +bazel_dep(name = "rules_java", version = "7.5.0") 10 | + 11 | +antlr = use_extension("//antlr:extensions.bzl", "antlr") 12 | +antlr.download(version = "4.8") 13 | +use_repo( 14 | + antlr, 15 | + "antlr3_runtime", 16 | + "antlr4_runtime", 17 | + "antlr4_tool", 18 | + "javax_json", 19 | + "stringtemplate4", 20 | +) 21 | diff --git antlr/extensions.bzl antlr/extensions.bzl 22 | new file mode 100644 23 | index 0000000..3151e01 24 | --- /dev/null 25 | +++ antlr/extensions.bzl 26 | @@ -0,0 +1,17 @@ 27 | +load("//antlr:repositories.bzl", "rules_antlr_dependencies") 28 | + 29 | +download = tag_class(attrs = {"version": attr.string()}) 30 | + 31 | +def _antlr(module_ctx): 32 | + rules_antlr_dependencies( 33 | + max([ 34 | + ([int(part) for part in download.version.split(".")], download.version) 35 | + for mod in module_ctx.modules 36 | + for download in mod.tags.download 37 | + ])[1], 38 | + ) 39 | + 40 | +antlr = module_extension( 41 | + implementation = _antlr, 42 | + tag_classes = {"download": download}, 43 | +) 44 | -------------------------------------------------------------------------------- /pkg/compiler/model/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "model", 5 | srcs = [ 6 | "bool_type.go", 7 | "constant_definition.go", 8 | "constant_resolving_value_visitor.go", 9 | "constant_value.go", 10 | "declaration.go", 11 | "definition.go", 12 | "emitting_integer_value_visitor.go", 13 | "enum_type.go", 14 | "fixed_length_array_type.go", 15 | "fixed_length_opaque_type.go", 16 | "floating_point_type.go", 17 | "foreign_identifier.go", 18 | "identifier.go", 19 | "identifier_factory.go", 20 | "identifier_type.go", 21 | "identifier_value.go", 22 | "integer_type.go", 23 | "named_declaration.go", 24 | "optional_type.go", 25 | "procedure.go", 26 | "program_definition.go", 27 | "registry.go", 28 | "resolver.go", 29 | "root_identifier_factory.go", 30 | "sink.go", 31 | "string_type.go", 32 | "struct_type.go", 33 | "top_level.go", 34 | "type.go", 35 | "type_definition.go", 36 | "union_type.go", 37 | "value.go", 38 | "value_visitor.go", 39 | "variable_length_array_type.go", 40 | "variable_length_opaque_type.go", 41 | "version.go", 42 | "void_declaration.go", 43 | ], 44 | importpath = "github.com/buildbarn/go-xdr/pkg/compiler/model", 45 | visibility = ["//visibility:public"], 46 | ) 47 | -------------------------------------------------------------------------------- /pkg/compiler/model/bool_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type emittingBoolValueVisitor struct { 9 | s sink 10 | } 11 | 12 | func (vv *emittingBoolValueVisitor) visitConstant(constant *big.Int) error { 13 | return errors.New("Booleans can only be compared against identifiers FALSE and TRUE") 14 | } 15 | 16 | func (vv *emittingBoolValueVisitor) visitIdentifier(name string) error { 17 | switch name { 18 | case "FALSE": 19 | vv.s.emitString("false") 20 | return nil 21 | case "TRUE": 22 | vv.s.emitString("true") 23 | return nil 24 | default: 25 | return errors.New("Booleans can only be compared against identifiers FALSE and TRUE") 26 | } 27 | } 28 | 29 | type boolType struct { 30 | sizeBits int 31 | } 32 | 33 | func (t *boolType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 34 | return &emittingBoolValueVisitor{s: s}, nil 35 | } 36 | 37 | func (t *boolType) emitDeclaration(s sink, r Resolver, i identifier) error { 38 | s.emitString("bool") 39 | return nil 40 | } 41 | 42 | func (t *boolType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 43 | return emitDeclarationsStructural(s, r, i, t, true, true) 44 | } 45 | 46 | func (t *boolType) emitReadFrom(s sink, r Resolver, i identifier) error { 47 | s.emitString("m, nField, err = ") 48 | s.emitPackageNamePrefix(xdrRuntimePackage) 49 | s.emitString("ReadBool(r)\n") 50 | emitReadWriteErrorHandling(s) 51 | return nil 52 | } 53 | 54 | func (t *boolType) emitWriteTo(s sink, r Resolver, i identifier) error { 55 | s.emitString("nField, err = ") 56 | s.emitPackageNamePrefix(xdrRuntimePackage) 57 | s.emitString("WriteBool(w, m)\n") 58 | emitReadWriteErrorHandling(s) 59 | return nil 60 | } 61 | 62 | func (t *boolType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 63 | panic("Booleans have a fixed size") 64 | } 65 | 66 | func (t *boolType) isLarge(r Resolver) (bool, error) { 67 | return false, nil 68 | } 69 | 70 | var boolFixedEncodingSizeBytes = big.NewInt(4) 71 | 72 | func (t *boolType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 73 | return boolFixedEncodingSizeBytes, nil 74 | } 75 | 76 | // BoolType corresponds to the boolean type as described in RFC 4506, 77 | // section 4.4. It gets translated to Go's 'bool' type. 78 | var BoolType Type = &boolType{} 79 | -------------------------------------------------------------------------------- /pkg/compiler/model/constant_definition.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type constantDefinition struct { 8 | name string 9 | constant *big.Int 10 | } 11 | 12 | // NewConstantDefinition creates a new named constant definition, as 13 | // described in RFC 4506, section 4.17. It ends up generating a single 14 | // constant definition in Go as well. 15 | func NewConstantDefinition(name string, constant *big.Int) Definition { 16 | return &constantDefinition{ 17 | name: name, 18 | constant: constant, 19 | } 20 | } 21 | 22 | func (d *constantDefinition) register(r Registry, pkg string) error { 23 | return r.RegisterConstant(d.name, d.constant) 24 | } 25 | 26 | func (d *constantDefinition) emitAllDefinitions(s sink, r Resolver) error { 27 | s.emitString("\n") 28 | s.emitString("const ") 29 | emitMacroCase(s, d.name) 30 | s.emitString(" = ") 31 | s.emitString(d.constant.String()) 32 | s.emitString("\n") 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/compiler/model/constant_resolving_value_visitor.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type constantResolvingValueVisitor struct { 8 | r Resolver 9 | constant *big.Int 10 | } 11 | 12 | func newConstantResolvingValueVisitor(r Resolver) *constantResolvingValueVisitor { 13 | return &constantResolvingValueVisitor{r: r} 14 | } 15 | 16 | func (vv *constantResolvingValueVisitor) visitConstant(constant *big.Int) error { 17 | vv.constant = constant 18 | return nil 19 | } 20 | 21 | func (vv *constantResolvingValueVisitor) visitIdentifier(name string) error { 22 | v, err := vv.r.ResolveConstant(name) 23 | if err != nil { 24 | return err 25 | } 26 | vv.constant = v 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/compiler/model/constant_value.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type constantValue struct { 8 | constant *big.Int 9 | } 10 | 11 | // NewConstantValue creates a value that corresponds to a constant. 12 | // These objects are, for example, instantiated when a fixed length 13 | // array is declared: 14 | // 15 | // typedef int mytype[10]; 16 | // ^^ NewConstantValue(10) 17 | func NewConstantValue(constant *big.Int) Value { 18 | return &constantValue{ 19 | constant: constant, 20 | } 21 | } 22 | 23 | func (v *constantValue) visit(vv valueVisitor) error { 24 | return vv.visitConstant(v.constant) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/compiler/model/declaration.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Declaration of a named type, or void. 8 | type Declaration interface { 9 | emitAllDeclarations(s sink, r Resolver, f identifierFactory) error 10 | emitStructField(s sink, r Resolver, f identifierFactory) error 11 | emitStructFieldReadFrom(s sink, r Resolver, f identifierFactory) error 12 | emitStructFieldWriteTo(s sink, r Resolver, f identifierFactory) error 13 | maybeEmitStructFieldGetVariableEncodedSizeBytes(s sink, r Resolver, f identifierFactory) error 14 | getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/compiler/model/definition.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Definition of a type, constant or program. 4 | type Definition interface { 5 | register(r Registry, pkg string) error 6 | emitAllDefinitions(s sink, r Resolver) error 7 | } 8 | -------------------------------------------------------------------------------- /pkg/compiler/model/emitting_integer_value_visitor.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type integerEmittingValueVisitor struct { 8 | s sink 9 | r Resolver 10 | } 11 | 12 | func newIntegerEmittingValueVisitor(s sink, r Resolver) valueVisitor { 13 | return &integerEmittingValueVisitor{ 14 | s: s, 15 | r: r, 16 | } 17 | } 18 | 19 | func (vv *integerEmittingValueVisitor) visitConstant(constant *big.Int) error { 20 | vv.s.emitString(constant.String()) 21 | return nil 22 | } 23 | 24 | func (vv *integerEmittingValueVisitor) visitIdentifier(name string) error { 25 | v, err := vv.r.ResolveConstant(name) 26 | if err != nil { 27 | return err 28 | } 29 | vv.s.emitString(v.String()) 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /pkg/compiler/model/enum_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | "sort" 8 | ) 9 | 10 | type enumEmittingValueVisitor struct { 11 | s sink 12 | r Resolver 13 | elements map[string]Value 14 | } 15 | 16 | func (vv *enumEmittingValueVisitor) visitConstant(constant *big.Int) error { 17 | return errors.New("Enumerations can only be compared against enumeration values") 18 | } 19 | 20 | func (vv *enumEmittingValueVisitor) visitIdentifier(name string) error { 21 | v, ok := vv.elements[name] 22 | if !ok { 23 | return fmt.Errorf("%#v is not a valid enumeration value", name) 24 | } 25 | return v.visit(newIntegerEmittingValueVisitor(vv.s, vv.r)) 26 | } 27 | 28 | type enumType struct { 29 | elements map[string]Value 30 | names []string 31 | } 32 | 33 | // NewEnumType creates an enumeration type, as described in RFC 4506, 34 | // section 4.3. It translates to a type definition of an integer in Go. 35 | // A constant is declared for every enumeration value. 36 | func NewEnumType(elements map[string]Value) Type { 37 | names := make([]string, 0, len(elements)) 38 | for name := range elements { 39 | names = append(names, name) 40 | } 41 | sort.Strings(names) 42 | 43 | return &enumType{ 44 | elements: elements, 45 | names: names, 46 | } 47 | } 48 | 49 | func (t *enumType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 50 | return &enumEmittingValueVisitor{ 51 | s: s, 52 | r: r, 53 | elements: t.elements, 54 | }, nil 55 | } 56 | 57 | func (t *enumType) emitDeclaration(s sink, r Resolver, i identifier) error { 58 | i.emit(s, false) 59 | return nil 60 | } 61 | 62 | func (t *enumType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 63 | s.emitString("\n") 64 | s.emitString("type ") 65 | i.emit(s, false) 66 | s.emitString(" int32\n") 67 | 68 | if err := emitAdditionalDeclarationsNominal(s, r, i, t); err != nil { 69 | return err 70 | } 71 | 72 | vv := newIntegerEmittingValueVisitor(s, r) 73 | for _, name := range t.names { 74 | s.emitString("\n") 75 | s.emitString("const ") 76 | emitMacroCase(s, name) 77 | s.emitString(" ") 78 | i.emit(s, false) 79 | s.emitString(" = ") 80 | if err := t.elements[name].visit(vv); err != nil { 81 | return err 82 | } 83 | s.emitString("\n") 84 | } 85 | 86 | s.emitString("\nvar ") 87 | i.emit(s, false) 88 | s.emitString("_name = map[") 89 | i.emit(s, false) 90 | s.emitString("]string{\n") 91 | for _, name := range t.names { 92 | if err := t.elements[name].visit(vv); err != nil { 93 | return err 94 | } 95 | s.emitString(": \"") 96 | s.emitString(name) 97 | s.emitString("\",\n") 98 | } 99 | s.emitString("}\n") 100 | return nil 101 | } 102 | 103 | func (t *enumType) emitReadFrom(s sink, r Resolver, i identifier) error { 104 | s.emitString("*(*int32)(&m), nField, err = ") 105 | s.emitPackageNamePrefix(xdrRuntimePackage) 106 | s.emitString("ReadInt(r)\n") 107 | emitReadWriteErrorHandling(s) 108 | return nil 109 | } 110 | 111 | func (t *enumType) emitWriteTo(s sink, r Resolver, i identifier) error { 112 | s.emitString("nField, err = ") 113 | s.emitPackageNamePrefix(xdrRuntimePackage) 114 | s.emitString("WriteInt(w, int32(m))\n") 115 | emitReadWriteErrorHandling(s) 116 | return nil 117 | } 118 | 119 | func (t *enumType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 120 | panic("Enums have a fixed size") 121 | } 122 | 123 | func (t *enumType) isLarge(r Resolver) (bool, error) { 124 | return false, nil 125 | } 126 | 127 | var enumFixedEncodingSizeBytes = big.NewInt(4) 128 | 129 | func (t *enumType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 130 | return enumFixedEncodingSizeBytes, nil 131 | } 132 | -------------------------------------------------------------------------------- /pkg/compiler/model/fixed_length_array_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type fixedLengthArrayType struct { 9 | specifier Type 10 | sizeElements Value 11 | } 12 | 13 | // NewFixedLengthArrayType creates a type that corresponds to a 14 | // fixed-length array, as described in RFC 4506, section 4.12. It 15 | // translates to a fixed-length array in Go. 16 | func NewFixedLengthArrayType(specifier Type, sizeElements Value) Type { 17 | return &fixedLengthArrayType{ 18 | specifier: specifier, 19 | sizeElements: sizeElements, 20 | } 21 | } 22 | 23 | func (t *fixedLengthArrayType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 24 | return nil, errors.New("Array constants are not supported") 25 | } 26 | 27 | func (t *fixedLengthArrayType) emitDeclaration(s sink, r Resolver, i identifier) error { 28 | s.emitString("[") 29 | if err := t.sizeElements.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 30 | return err 31 | } 32 | s.emitString("]") 33 | return t.specifier.emitDeclaration(s, r, i.taint()) 34 | } 35 | 36 | func (t *fixedLengthArrayType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 37 | if err := emitDeclarationsStructural(s, r, i, t, true, true); err != nil { 38 | return err 39 | } 40 | return t.specifier.emitAllDeclarations(s, r, i.taint()) 41 | } 42 | 43 | func (t *fixedLengthArrayType) emitReadFrom(s sink, r Resolver, i identifier) error { 44 | if i.hasReadFromWriteTo() { 45 | return emitForwardReadFromStructural(s, r, i, true) 46 | } 47 | 48 | iChild := i.taint() 49 | s.emitString("mParent := m\n") 50 | s.emitString("for i := 0; i < len(m); i++ {\n") 51 | s.emitString("var m ") 52 | if err := t.specifier.emitDeclaration(s, r, iChild); err != nil { 53 | } 54 | s.emitString("\n") 55 | if err := t.specifier.emitReadFrom(s, r, iChild); err != nil { 56 | return err 57 | } 58 | s.emitString("mParent[i] = m\n") 59 | s.emitString("}\n") 60 | return nil 61 | } 62 | 63 | func (t *fixedLengthArrayType) emitWriteTo(s sink, r Resolver, i identifier) error { 64 | if i.hasReadFromWriteTo() { 65 | emitForwardWriteToStructural(s, i) 66 | } else { 67 | isLarge, err := t.specifier.isLarge(r) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if isLarge { 73 | s.emitString("for i := 0; i < len(m); i++ {\n") 74 | s.emitString("m := &m[i]\n") 75 | } else { 76 | s.emitString("for _, m := range m {\n") 77 | } 78 | if err := t.specifier.emitWriteTo(s, r, i.taint()); err != nil { 79 | return err 80 | } 81 | s.emitString("}\n") 82 | } 83 | return nil 84 | } 85 | 86 | func (t *fixedLengthArrayType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 87 | panic("TODO") 88 | } 89 | 90 | func (t *fixedLengthArrayType) isLarge(r Resolver) (bool, error) { 91 | return true, nil 92 | } 93 | 94 | func (t *fixedLengthArrayType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 95 | elementSizeBytes, err := t.specifier.getFixedEncodedSizeBytes(r) 96 | if err != nil || elementSizeBytes == nil { 97 | return elementSizeBytes, err 98 | } 99 | vv := newConstantResolvingValueVisitor(r) 100 | if err := t.sizeElements.visit(vv); err != nil { 101 | return nil, err 102 | } 103 | var size big.Int 104 | return size.Mul(elementSizeBytes, vv.constant), nil 105 | } 106 | -------------------------------------------------------------------------------- /pkg/compiler/model/fixed_length_opaque_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type fixedLengthOpaqueType struct { 9 | sizeBytes Value 10 | } 11 | 12 | // NewFixedLengthOpaqueType creates a fixed-length opaque data type, as 13 | // described in RFC 4506, section 4.9. It translates to a fixed-length byte 14 | // array in Go. 15 | func NewFixedLengthOpaqueType(sizeBytes Value) Type { 16 | return &fixedLengthOpaqueType{ 17 | sizeBytes: sizeBytes, 18 | } 19 | } 20 | 21 | func (t *fixedLengthOpaqueType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 22 | return nil, errors.New("Opaque constants are not supported") 23 | } 24 | 25 | func (t *fixedLengthOpaqueType) emitDeclaration(s sink, r Resolver, i identifier) error { 26 | s.emitString("[") 27 | if err := t.sizeBytes.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 28 | return err 29 | } 30 | s.emitString("]byte") 31 | return nil 32 | } 33 | 34 | func (t *fixedLengthOpaqueType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 35 | return emitDeclarationsStructural(s, r, i, t, true, true) 36 | } 37 | 38 | func (t *fixedLengthOpaqueType) emitReadFrom(s sink, r Resolver, i identifier) error { 39 | s.emitString("nField, err = ") 40 | s.emitPackageNamePrefix(xdrRuntimePackage) 41 | s.emitString("ReadFixedLengthOpaque(r, m[:])\n") 42 | emitReadWriteErrorHandling(s) 43 | return nil 44 | } 45 | 46 | func (t *fixedLengthOpaqueType) emitWriteTo(s sink, r Resolver, i identifier) error { 47 | s.emitString("nField, err = ") 48 | s.emitPackageNamePrefix(xdrRuntimePackage) 49 | s.emitString("WriteFixedLengthOpaque(w, m[:])\n") 50 | emitReadWriteErrorHandling(s) 51 | return nil 52 | } 53 | 54 | func (t *fixedLengthOpaqueType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 55 | panic("Fixed length opaque types have a fixed size") 56 | } 57 | 58 | func (t *fixedLengthOpaqueType) isLarge(r Resolver) (bool, error) { 59 | return true, nil 60 | } 61 | 62 | var paddingMask = big.NewInt(0x3) 63 | 64 | func (t *fixedLengthOpaqueType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 65 | vv := newConstantResolvingValueVisitor(r) 66 | if err := t.sizeBytes.visit(vv); err != nil { 67 | return nil, err 68 | } 69 | var withPadding big.Int 70 | withPadding.Add(vv.constant, paddingMask) 71 | return withPadding.AndNot(vv.constant, paddingMask), nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/compiler/model/floating_point_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | "strconv" 7 | ) 8 | 9 | type floatingPointType struct { 10 | sizeBits int 11 | xdrType string 12 | fixedEncodedSizeBytes *big.Int 13 | } 14 | 15 | func (t *floatingPointType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 16 | return nil, errors.New("Floating point constants are not supported") 17 | } 18 | 19 | func (t *floatingPointType) emitDeclaration(s sink, r Resolver, i identifier) error { 20 | s.emitString("float") 21 | s.emitString(strconv.FormatInt(int64(t.sizeBits), 10)) 22 | return nil 23 | } 24 | 25 | func (t *floatingPointType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 26 | return emitDeclarationsStructural(s, r, i, t, true, true) 27 | } 28 | 29 | func (t *floatingPointType) emitReadFrom(s sink, r Resolver, i identifier) error { 30 | s.emitString("m, nField, err = ") 31 | s.emitPackageNamePrefix(xdrRuntimePackage) 32 | s.emitString("Read") 33 | s.emitString(t.xdrType) 34 | s.emitString("(r)\n") 35 | emitReadWriteErrorHandling(s) 36 | return nil 37 | } 38 | 39 | func (t *floatingPointType) emitWriteTo(s sink, r Resolver, i identifier) error { 40 | s.emitString("nField, err = ") 41 | s.emitPackageNamePrefix(xdrRuntimePackage) 42 | s.emitString("Write") 43 | s.emitString(t.xdrType) 44 | s.emitString("(w, m)\n") 45 | emitReadWriteErrorHandling(s) 46 | return nil 47 | } 48 | 49 | func (t *floatingPointType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 50 | panic("Floating points have a fixed size") 51 | } 52 | 53 | func (t *floatingPointType) isLarge(r Resolver) (bool, error) { 54 | return false, nil 55 | } 56 | 57 | func (t *floatingPointType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 58 | return t.fixedEncodedSizeBytes, nil 59 | } 60 | 61 | // FloatType corresponds to a single-precision floating-point type as 62 | // described in RFC 4506, section 4.6. 63 | var FloatType Type = &floatingPointType{ 64 | sizeBits: 32, 65 | xdrType: "Float", 66 | fixedEncodedSizeBytes: big.NewInt(4), 67 | } 68 | 69 | // DoubleType corresponds to a double-precision floating-point type as 70 | // described in RFC 4506, section 4.7. 71 | var DoubleType Type = &floatingPointType{ 72 | sizeBits: 64, 73 | xdrType: "Double", 74 | fixedEncodedSizeBytes: big.NewInt(8), 75 | } 76 | 77 | // QuadrupleType corresponds to a quadruple-precision floating-point 78 | // type as described in RFC 4506, section 4.8. 79 | var QuadrupleType Type = &floatingPointType{ 80 | sizeBits: 128, 81 | xdrType: "Quadruple", 82 | fixedEncodedSizeBytes: big.NewInt(16), 83 | } 84 | -------------------------------------------------------------------------------- /pkg/compiler/model/foreign_identifier.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type foreignIdentifier struct { 4 | pkg string 5 | name string 6 | } 7 | 8 | func newForeignIdentifier(pkg, name string) identifier { 9 | return &foreignIdentifier{ 10 | pkg: pkg, 11 | name: name, 12 | } 13 | } 14 | 15 | func (i *foreignIdentifier) emitPackageNamePrefix(s sink) { 16 | s.emitPackageNamePrefix(i.pkg) 17 | } 18 | 19 | func (i *foreignIdentifier) emit(s sink, forcePublic bool) { 20 | i.emitPackageNamePrefix(s) 21 | emitMixedCase(s, i.name, true) 22 | } 23 | 24 | func (i *foreignIdentifier) emitOriginalName(s sink) { 25 | s.emitString(i.name) 26 | } 27 | 28 | func (i *foreignIdentifier) emitStructuralFunctionName(s sink, functionName string) { 29 | i.emitPackageNamePrefix(s) 30 | s.emitString(functionName) 31 | emitMixedCase(s, i.name, true) 32 | } 33 | 34 | func (i *foreignIdentifier) hasTypeDefinition(isNominal bool) bool { 35 | return true 36 | } 37 | 38 | func (i *foreignIdentifier) hasReadFromWriteTo() bool { 39 | return true 40 | } 41 | 42 | func (i *foreignIdentifier) inside() identifier { 43 | panic("TODO: Do we need this?") 44 | } 45 | 46 | func (i *foreignIdentifier) resolve(pkg, name string) identifier { 47 | return &foreignIdentifier{ 48 | pkg: pkg, 49 | name: name, 50 | } 51 | } 52 | 53 | func (i *foreignIdentifier) append(typeUsedMultipleTimes, readFromWriteToUsedMultipleTimes bool) identifierFactory { 54 | panic("TODO: Do we need this?") 55 | } 56 | 57 | func (i *foreignIdentifier) taint() identifier { 58 | return taintedForeignIdentifier{} 59 | } 60 | 61 | type taintedForeignIdentifier struct{} 62 | 63 | func (i taintedForeignIdentifier) emit(s sink, forcePublic bool) { 64 | panic("TODO") 65 | } 66 | 67 | func (i taintedForeignIdentifier) emitOriginalName(s sink) { 68 | panic("TODO") 69 | } 70 | 71 | func (i taintedForeignIdentifier) emitStructuralFunctionName(s sink, functionName string) { 72 | panic("TODO") 73 | } 74 | 75 | func (i taintedForeignIdentifier) hasTypeDefinition(isNominal bool) bool { 76 | panic("TODO") 77 | } 78 | 79 | func (i taintedForeignIdentifier) hasReadFromWriteTo() bool { 80 | panic("TODO") 81 | } 82 | 83 | func (i taintedForeignIdentifier) inside() identifier { 84 | panic("TODO") 85 | } 86 | 87 | func (i taintedForeignIdentifier) resolve(pkg, name string) identifier { 88 | return &foreignIdentifier{ 89 | pkg: pkg, 90 | name: name, 91 | } 92 | } 93 | 94 | func (i taintedForeignIdentifier) append(typeUsedMultipleTimes, readFromWriteToUsedMultipleTimes bool) identifierFactory { 95 | panic("TODO") 96 | } 97 | 98 | func (i taintedForeignIdentifier) taint() identifier { 99 | panic("TODO") 100 | } 101 | -------------------------------------------------------------------------------- /pkg/compiler/model/identifier.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type identifier interface { 4 | emit(s sink, forcePublic bool) 5 | emitOriginalName(s sink) 6 | emitStructuralFunctionName(s sink, functionName string) 7 | 8 | hasTypeDefinition(isNominal bool) bool 9 | hasReadFromWriteTo() bool 10 | 11 | resolve(pkg, name string) identifier 12 | append(typeUsedMultipleTimes, readFromWriteToUsedMultipleTimes bool) identifierFactory 13 | inside() identifier 14 | taint() identifier 15 | } 16 | -------------------------------------------------------------------------------- /pkg/compiler/model/identifier_factory.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type identifierFactory interface { 4 | newIdentifier(name string) identifier 5 | } 6 | -------------------------------------------------------------------------------- /pkg/compiler/model/identifier_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type identifierType struct { 8 | identifier string 9 | } 10 | 11 | // NewIdentifierType creates a type that corresponds to an identifier. 12 | // It merely forwards all methods to the underlying type of that 13 | // identifier after resolving it. This can be used to simply alias 14 | // types, or to compose them: 15 | // 16 | // typedef int t1; 17 | // ^^^ IntType 18 | // typedef t1 t2; 19 | // ^^ NewIdentifierType("t1") 20 | func NewIdentifierType(identifier string) Type { 21 | return &identifierType{ 22 | identifier: identifier, 23 | } 24 | } 25 | 26 | func (t *identifierType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 27 | _, tNext, err := r.ResolveType(t.identifier) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return tNext.getEmittingValueVisitor(s, r) 32 | } 33 | 34 | func (t *identifierType) emitDeclaration(s sink, r Resolver, i identifier) error { 35 | pkg, tNext, err := r.ResolveType(t.identifier) 36 | if err != nil { 37 | return err 38 | } 39 | return tNext.emitDeclaration(s, r, i.resolve(pkg, t.identifier)) 40 | } 41 | 42 | func (t *identifierType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 43 | return emitDeclarationsStructural(s, r, i, t, true, true) 44 | } 45 | 46 | func (t *identifierType) emitReadFrom(s sink, r Resolver, i identifier) error { 47 | pkg, tNext, err := r.ResolveType(t.identifier) 48 | if err != nil { 49 | return err 50 | } 51 | return tNext.emitReadFrom(s, r, i.resolve(pkg, t.identifier)) 52 | } 53 | 54 | func (t *identifierType) emitWriteTo(s sink, r Resolver, i identifier) error { 55 | pkg, tNext, err := r.ResolveType(t.identifier) 56 | if err != nil { 57 | return err 58 | } 59 | return tNext.emitWriteTo(s, r, i.resolve(pkg, t.identifier)) 60 | } 61 | 62 | func (t *identifierType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 63 | pkg, tNext, err := r.ResolveType(t.identifier) 64 | if err != nil { 65 | return err 66 | } 67 | return tNext.emitGetVariableEncodedSizeBytes(s, r, i.resolve(pkg, t.identifier)) 68 | } 69 | 70 | func (t *identifierType) isLarge(r Resolver) (bool, error) { 71 | _, tNext, err := r.ResolveType(t.identifier) 72 | if err != nil { 73 | return false, err 74 | } 75 | return tNext.isLarge(r) 76 | } 77 | 78 | func (t *identifierType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 79 | _, tNext, err := r.ResolveType(t.identifier) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return tNext.getFixedEncodedSizeBytes(r) 84 | } 85 | -------------------------------------------------------------------------------- /pkg/compiler/model/identifier_value.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type identifierValue struct { 4 | identifier string 5 | } 6 | 7 | // NewIdentifierValue creates a value that corresponds to the name of 8 | // another identifier. These objects are, for example, instantiated when 9 | // a fixed length array is declared: 10 | // 11 | // const LENGTH = 10; 12 | // typedef int mytype[LENGTH]; 13 | // ^^^^^^ NewIdentifierValue("LENGTH") 14 | func NewIdentifierValue(identifier string) Value { 15 | return &identifierValue{ 16 | identifier: identifier, 17 | } 18 | } 19 | 20 | func (v *identifierValue) visit(vv valueVisitor) error { 21 | return vv.visitIdentifier(v.identifier) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/compiler/model/integer_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type integerType struct { 8 | nativeType string 9 | xdrType string 10 | fixedEncodedSizeBytes *big.Int 11 | } 12 | 13 | func (t *integerType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 14 | return newIntegerEmittingValueVisitor(s, r), nil 15 | } 16 | 17 | func (t *integerType) emitDeclaration(s sink, r Resolver, i identifier) error { 18 | s.emitString(t.nativeType) 19 | return nil 20 | } 21 | 22 | func (t *integerType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 23 | return emitDeclarationsStructural(s, r, i, t, true, true) 24 | } 25 | 26 | func (t *integerType) emitReadFrom(s sink, r Resolver, i identifier) error { 27 | s.emitString("m, nField, err = ") 28 | s.emitPackageNamePrefix(xdrRuntimePackage) 29 | s.emitString("Read") 30 | s.emitString(t.xdrType) 31 | s.emitString("(r)\n") 32 | emitReadWriteErrorHandling(s) 33 | return nil 34 | } 35 | 36 | func (t *integerType) emitWriteTo(s sink, r Resolver, i identifier) error { 37 | s.emitString("nField, err = ") 38 | s.emitPackageNamePrefix(xdrRuntimePackage) 39 | s.emitString("Write") 40 | s.emitString(t.xdrType) 41 | s.emitString("(w, m)\n") 42 | emitReadWriteErrorHandling(s) 43 | return nil 44 | } 45 | 46 | func (t *integerType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 47 | panic("Integers have a fixed size") 48 | } 49 | 50 | func (t *integerType) isLarge(r Resolver) (bool, error) { 51 | return false, nil 52 | } 53 | 54 | func (t *integerType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 55 | return t.fixedEncodedSizeBytes, nil 56 | } 57 | 58 | // IntType corresponds to a 32-bit signed integer type as described in 59 | // RFC 4506, section 4.1. 60 | var IntType Type = &integerType{ 61 | nativeType: "int32", 62 | xdrType: "Int", 63 | fixedEncodedSizeBytes: big.NewInt(4), 64 | } 65 | 66 | // UnsignedIntType corresponds to a 32-bit unsigned integer type as 67 | // described in RFC 4506, section 4.2. 68 | var UnsignedIntType Type = &integerType{ 69 | nativeType: "uint32", 70 | xdrType: "UnsignedInt", 71 | fixedEncodedSizeBytes: big.NewInt(4), 72 | } 73 | 74 | // HyperType corresponds to a 64-bit signed integer type as described in 75 | // RFC 4506, section 4.5. 76 | var HyperType Type = &integerType{ 77 | nativeType: "int64", 78 | xdrType: "Hyper", 79 | fixedEncodedSizeBytes: big.NewInt(8), 80 | } 81 | 82 | // UnsignedHyperType corresponds to a 64-bit unsigned integer type as 83 | // described in RFC 4506, section 4.5. 84 | var UnsignedHyperType Type = &integerType{ 85 | nativeType: "uint64", 86 | xdrType: "UnsignedHyper", 87 | fixedEncodedSizeBytes: big.NewInt(8), 88 | } 89 | -------------------------------------------------------------------------------- /pkg/compiler/model/named_declaration.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // NamedDeclaration corresponds to a declaration of a type under a given 8 | // name. Named declarations are either top-level type definitions, or 9 | // declarations of named fields inside structs or unions. 10 | type NamedDeclaration struct { 11 | name string 12 | declarationType Type 13 | } 14 | 15 | // NewNamedDeclaration creates a new named declaration. 16 | func NewNamedDeclaration(name string, declarationType Type) NamedDeclaration { 17 | return NamedDeclaration{ 18 | name: name, 19 | declarationType: declarationType, 20 | } 21 | } 22 | 23 | func (nd NamedDeclaration) emitAllDeclarations(s sink, r Resolver, f identifierFactory) error { 24 | return nd.declarationType.emitAllDeclarations(s, r, f.newIdentifier(nd.name)) 25 | } 26 | 27 | func (nd NamedDeclaration) emitStructField(s sink, r Resolver, f identifierFactory) error { 28 | emitMixedCase(s, nd.name, true) 29 | s.emitString(" ") 30 | if err := nd.declarationType.emitDeclaration(s, r, f.newIdentifier(nd.name)); err != nil { 31 | return err 32 | } 33 | s.emitString("\n") 34 | return nil 35 | } 36 | 37 | func (nd NamedDeclaration) emitStructFieldReadFrom(s sink, r Resolver, f identifierFactory) error { 38 | tField := nd.declarationType 39 | isLarge, err := tField.isLarge(r) 40 | if err != nil { 41 | return err 42 | } 43 | iChild := f.newIdentifier(nd.name) 44 | 45 | s.emitString("{\n") 46 | if isLarge { 47 | s.emitString("m := &m.") 48 | emitMixedCase(s, nd.name, true) 49 | s.emitString("\n") 50 | if err := tField.emitReadFrom(s, r, iChild); err != nil { 51 | return err 52 | } 53 | } else { 54 | s.emitString("mSave := &m.") 55 | emitMixedCase(s, nd.name, true) 56 | s.emitString("\n") 57 | s.emitString("var m ") 58 | if err := tField.emitDeclaration(s, r, iChild); err != nil { 59 | return err 60 | } 61 | s.emitString("\n") 62 | if err := tField.emitReadFrom(s, r, iChild); err != nil { 63 | return err 64 | } 65 | s.emitString("*mSave = m\n") 66 | } 67 | s.emitString("}\n") 68 | return nil 69 | } 70 | 71 | func (nd NamedDeclaration) emitStructFieldWriteTo(s sink, r Resolver, f identifierFactory) error { 72 | isLarge, err := nd.declarationType.isLarge(r) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | s.emitString("{\n") 78 | if isLarge { 79 | s.emitString("m := &m.") 80 | } else { 81 | s.emitString("m := m.") 82 | } 83 | emitMixedCase(s, nd.name, true) 84 | s.emitString("\n") 85 | if err := nd.declarationType.emitWriteTo(s, r, f.newIdentifier(nd.name)); err != nil { 86 | return err 87 | } 88 | s.emitString("}\n") 89 | return nil 90 | } 91 | 92 | func (nd NamedDeclaration) emitStructFieldGetVariableEncodedSizeBytes(s sink, r Resolver, f identifierFactory) error { 93 | isLarge, err := nd.declarationType.isLarge(r) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | s.emitString("{\n") 99 | if isLarge { 100 | s.emitString("m := &m.") 101 | } else { 102 | s.emitString("m := m.") 103 | } 104 | emitMixedCase(s, nd.name, true) 105 | s.emitString("\n") 106 | if err := nd.declarationType.emitGetVariableEncodedSizeBytes(s, r, f.newIdentifier(nd.name)); err != nil { 107 | return err 108 | } 109 | s.emitString("}\n") 110 | return nil 111 | } 112 | 113 | func (nd NamedDeclaration) maybeEmitStructFieldGetVariableEncodedSizeBytes(s sink, r Resolver, f identifierFactory) error { 114 | size, err := nd.declarationType.getFixedEncodedSizeBytes(r) 115 | if err != nil { 116 | return err 117 | } 118 | if size == nil { 119 | return nd.emitStructFieldGetVariableEncodedSizeBytes(s, r, f) 120 | } 121 | s.emitString("nTotal += ") 122 | s.emitString(size.String()) 123 | s.emitString("\n") 124 | return nil 125 | } 126 | 127 | func (nd NamedDeclaration) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 128 | return nd.declarationType.getFixedEncodedSizeBytes(r) 129 | } 130 | -------------------------------------------------------------------------------- /pkg/compiler/model/optional_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type optionalType struct { 9 | base Type 10 | } 11 | 12 | // NewOptionalType creates an optional-data type as described in RFC 13 | // 4506, section 4.19. It translates to a pointer type in Go. 14 | func NewOptionalType(base Type) Type { 15 | return &optionalType{ 16 | base: base, 17 | } 18 | } 19 | 20 | func (t *optionalType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 21 | return nil, errors.New("Optional constants are not supported") 22 | } 23 | 24 | func (t *optionalType) emitDeclaration(s sink, r Resolver, i identifier) error { 25 | s.emitString("*") 26 | return t.base.emitDeclaration(s, r, i.taint()) 27 | } 28 | 29 | func (t *optionalType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 30 | if err := emitDeclarationsStructural(s, r, i, t, true, true); err != nil { 31 | return err 32 | } 33 | return t.base.emitAllDeclarations(s, r, i.taint()) 34 | } 35 | 36 | func (t *optionalType) emitReadFrom(s sink, r Resolver, i identifier) error { 37 | childNP := i.taint() 38 | 39 | s.emitString("var isSet bool\n") 40 | s.emitString("isSet, nField, err = ") 41 | s.emitPackageNamePrefix(xdrRuntimePackage) 42 | s.emitString("ReadBool(r)\n") 43 | emitReadWriteErrorHandling(s) 44 | s.emitString("if isSet {\n") 45 | s.emitString("mParent := &m\n") 46 | s.emitString("var m ") 47 | if err := t.base.emitDeclaration(s, r, childNP); err != nil { 48 | return err 49 | } 50 | s.emitString("\n") 51 | if err := t.base.emitReadFrom(s, r, childNP); err != nil { 52 | return err 53 | } 54 | s.emitString("*mParent = &m\n") 55 | s.emitString("}\n") 56 | return nil 57 | } 58 | 59 | func (t *optionalType) emitWriteTo(s sink, r Resolver, i identifier) error { 60 | isLarge, err := t.base.isLarge(r) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | s.emitString("if m == nil {\n") 66 | s.emitString("nField, err = ") 67 | s.emitPackageNamePrefix(xdrRuntimePackage) 68 | s.emitString("WriteBool(w, false)\n") 69 | emitReadWriteErrorHandling(s) 70 | s.emitString("} else {\n") 71 | s.emitString("nField, err = ") 72 | s.emitPackageNamePrefix(xdrRuntimePackage) 73 | s.emitString("WriteBool(w, true)\n") 74 | emitReadWriteErrorHandling(s) 75 | s.emitString("{\n") 76 | if !isLarge { 77 | s.emitString("m := *m\n") 78 | } 79 | if err := t.base.emitWriteTo(s, r, i.taint()); err != nil { 80 | return err 81 | } 82 | s.emitString("}\n") 83 | s.emitString("}\n") 84 | return nil 85 | } 86 | 87 | func (t *optionalType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 88 | s.emitString("nTotal += 4\n") 89 | s.emitString("if m != nil {\n") 90 | size, err := t.base.getFixedEncodedSizeBytes(r) 91 | if err != nil { 92 | return err 93 | } else if size != nil { 94 | s.emitString("nTotal += ") 95 | s.emitString(size.String()) 96 | s.emitString("\n") 97 | } else { 98 | isLarge, err := t.base.isLarge(r) 99 | if err != nil { 100 | return err 101 | } 102 | if !isLarge { 103 | s.emitString("m := *m\n") 104 | } 105 | if err := t.base.emitGetVariableEncodedSizeBytes(s, r, i.taint()); err != nil { 106 | return err 107 | } 108 | } 109 | s.emitString("}\n") 110 | return nil 111 | } 112 | 113 | func (t *optionalType) isLarge(r Resolver) (bool, error) { 114 | return false, nil 115 | } 116 | 117 | func (t *optionalType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 118 | return nil, nil 119 | } 120 | -------------------------------------------------------------------------------- /pkg/compiler/model/procedure.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Procedure of an RPC program, as described in RFC 5531, chapter 12. 8 | type Procedure struct { 9 | name string 10 | argumentTypes []Type 11 | returnType Type 12 | procedureNumber *big.Int 13 | } 14 | 15 | // NewProcedure creates a new procedure of an RPC program. 16 | func NewProcedure(name string, argumentTypes []Type, returnType Type, procedureNumber *big.Int) Procedure { 17 | return Procedure{ 18 | name: name, 19 | argumentTypes: argumentTypes, 20 | returnType: returnType, 21 | procedureNumber: procedureNumber, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/compiler/model/program_definition.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | "strconv" 6 | ) 7 | 8 | const rpcv2Package = "github.com/buildbarn/go-xdr/pkg/protocols/rpcv2" 9 | 10 | type programDefinition struct { 11 | name string 12 | versions []Version 13 | programNumber *big.Int 14 | } 15 | 16 | // NewProgramDefinition creates a definition of an RPC program, as 17 | // described in RFC 5531, chapter 12. 18 | func NewProgramDefinition(name string, versions []Version, programNumber *big.Int) Definition { 19 | return programDefinition{ 20 | name: name, 21 | versions: versions, 22 | programNumber: programNumber, 23 | } 24 | } 25 | 26 | func (d programDefinition) register(r Registry, pkg string) error { 27 | // TODO: This should iterate over versions! 28 | return nil 29 | } 30 | 31 | func (d programDefinition) emitAllDefinitions(s sink, r Resolver) error { 32 | s.emitString("\n") 33 | s.emitString("const ") 34 | emitMacroCase(s, d.name) 35 | s.emitString("_PROGRAM_NUMBER uint32 = ") 36 | s.emitString(d.programNumber.String()) 37 | s.emitString("\n") 38 | 39 | i := rootIdentifierFactory{}.newIdentifier("TODO") 40 | 41 | s.emitString("\n") 42 | s.emitString("type ") 43 | emitMixedCase(s, d.name, true) 44 | s.emitString(" interface {\n") 45 | for _, version := range d.versions { 46 | for _, procedure := range version.procedures { 47 | emitMixedCase(s, version.name, true) 48 | emitMixedCase(s, procedure.name, true) 49 | s.emitString("(") 50 | s.emitPackageNamePrefix("context") 51 | s.emitString("Context") 52 | for _, t := range procedure.argumentTypes { 53 | s.emitString(", ") 54 | if large, err := t.isLarge(r); err != nil { 55 | return err 56 | } else if large { 57 | s.emitString("*") 58 | } 59 | if err := t.emitDeclaration(s, r, i); err != nil { 60 | return err 61 | } 62 | } 63 | s.emitString(")") 64 | if t := procedure.returnType; t == nil { 65 | s.emitString(" error\n") 66 | } else { 67 | s.emitString(" (") 68 | if large, err := t.isLarge(r); err != nil { 69 | return err 70 | } else if large { 71 | s.emitString("*") 72 | } 73 | if err := t.emitDeclaration(s, r, i); err != nil { 74 | return err 75 | } 76 | s.emitString(", error)\n") 77 | } 78 | } 79 | } 80 | s.emitString("}\n") 81 | 82 | s.emitString("\n") 83 | s.emitString("func New") 84 | emitMixedCase(s, d.name, true) 85 | s.emitString("Service(p ") 86 | emitMixedCase(s, d.name, true) 87 | s.emitString(") func(") 88 | s.emitPackageNamePrefix("context") 89 | s.emitString("Context, uint32, uint32, ") 90 | s.emitPackageNamePrefix("io") 91 | s.emitString("ReadCloser, ") 92 | s.emitPackageNamePrefix("io") 93 | s.emitString("Writer) (") 94 | s.emitPackageNamePrefix(rpcv2Package) 95 | s.emitString("AcceptedReplyData, error) {\n") 96 | s.emitString("return func(ctx ") 97 | s.emitPackageNamePrefix("context") 98 | s.emitString("Context, vers uint32, proc uint32, r ") 99 | s.emitPackageNamePrefix("io") 100 | s.emitString("ReadCloser, w ") 101 | s.emitPackageNamePrefix("io") 102 | s.emitString("Writer) (") 103 | s.emitPackageNamePrefix(rpcv2Package) 104 | s.emitString("AcceptedReplyData, error) {\nvar err error\nswitch vers {\n") 105 | needsErrorHandling := false 106 | for _, version := range d.versions { 107 | s.emitString("case ") 108 | s.emitString(version.versionNumber.String()) 109 | s.emitString(":\nswitch proc {\n") 110 | for _, procedure := range version.procedures { 111 | s.emitString("case ") 112 | s.emitString(procedure.procedureNumber.String()) 113 | s.emitString(":\n") 114 | 115 | for index, argumentType := range procedure.argumentTypes { 116 | indexStr := strconv.FormatInt(int64(index), 10) 117 | s.emitString("var a") 118 | s.emitString(indexStr) 119 | s.emitString(" ") 120 | if err := argumentType.emitDeclaration(s, r, i); err != nil { 121 | return err 122 | } 123 | s.emitString("\n{\n") 124 | large, err := argumentType.isLarge(r) 125 | if err != nil { 126 | return err 127 | } 128 | if large { 129 | s.emitString("m := &a") 130 | s.emitString(indexStr) 131 | } else { 132 | s.emitString("var m ") 133 | if err := argumentType.emitDeclaration(s, r, i); err != nil { 134 | return err 135 | } 136 | } 137 | s.emitString("\nvar nField, nTotal int64\n") 138 | if err := argumentType.emitReadFrom(s, r, i); err != nil { 139 | return err 140 | } 141 | if !large { 142 | s.emitString("a") 143 | s.emitString(indexStr) 144 | s.emitString(" = m\n") 145 | } 146 | s.emitString("}\n") 147 | 148 | needsErrorHandling = true 149 | } 150 | s.emitString("r.Close()\nr = nil\n") 151 | if procedure.returnType != nil { 152 | s.emitString("m, ") 153 | } 154 | s.emitString("errProc := p.") 155 | emitMixedCase(s, version.name, true) 156 | emitMixedCase(s, procedure.name, true) 157 | s.emitString("(ctx") 158 | for index, argumentType := range procedure.argumentTypes { 159 | s.emitString(", ") 160 | if large, err := argumentType.isLarge(r); err != nil { 161 | return err 162 | } else if large { 163 | s.emitString("&") 164 | } 165 | s.emitString("a") 166 | s.emitString(strconv.FormatInt(int64(index), 10)) 167 | } 168 | s.emitString(")\nif errProc != nil {\nreturn nil, errProc\n}\n") 169 | if returnType := procedure.returnType; returnType != nil { 170 | s.emitString("{\nvar nField, nTotal int64\n") 171 | if err := returnType.emitWriteTo(s, r, i); err != nil { 172 | return err 173 | } 174 | s.emitString("}\n") 175 | } 176 | } 177 | s.emitString("default:\nr.Close()\nreturn &") 178 | s.emitPackageNamePrefix(rpcv2Package) 179 | s.emitString("AcceptedReplyData_default{Stat: ") 180 | s.emitPackageNamePrefix(rpcv2Package) 181 | s.emitString("PROC_UNAVAIL}, nil\n}\n") 182 | } 183 | s.emitString("default:\nr.Close()\nvar replyData ") 184 | s.emitPackageNamePrefix(rpcv2Package) 185 | minVersion, maxVersion := d.versions[0].versionNumber, d.versions[0].versionNumber 186 | for _, version := range d.versions[1:] { 187 | if minVersion.Cmp(version.versionNumber) > 0 { 188 | minVersion = version.versionNumber 189 | } 190 | if maxVersion.Cmp(version.versionNumber) < 0 { 191 | maxVersion = version.versionNumber 192 | } 193 | } 194 | s.emitString("AcceptedReplyData_PROG_MISMATCH\nreplyData.MismatchInfo.Low = ") 195 | s.emitString(minVersion.String()) 196 | s.emitString("\nreplyData.MismatchInfo.High = ") 197 | s.emitString(maxVersion.String()) 198 | s.emitString("\nreturn &replyData, nil\n") 199 | s.emitString("}\nreturn &") 200 | s.emitPackageNamePrefix(rpcv2Package) 201 | s.emitString("AcceptedReplyData_SUCCESS{}, nil\n") 202 | if needsErrorHandling { 203 | s.emitString("done:\nif r != nil {\nr.Close()\n}\nreturn nil, err\n") 204 | } 205 | s.emitString("}\n") 206 | s.emitString("}\n") 207 | 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /pkg/compiler/model/registry.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Registry can be implemented by consumers of this package to extract 8 | // constants, types and imports from a TopLevel. 9 | type Registry interface { 10 | RegisterConstant(name string, c *big.Int) error 11 | RegisterType(name, pkg string, t Type) error 12 | RegisterImport(path string) error 13 | } 14 | -------------------------------------------------------------------------------- /pkg/compiler/model/resolver.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Resolver needs to be provided to TopLevel.Emit() to resolve constants 8 | // and types. 9 | type Resolver interface { 10 | ResolveConstant(name string) (*big.Int, error) 11 | ResolveType(name string) (string, Type, error) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/compiler/model/root_identifier_factory.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type rootIdentifierFactory struct{} 8 | 9 | func (f rootIdentifierFactory) newIdentifier(name string) identifier { 10 | return &localIdentifier{ 11 | name: name, 12 | hasTypeDefinitionNominal: true, 13 | hasTypeDefinitionStructural: true, 14 | readFromWriteTo: true, 15 | } 16 | } 17 | 18 | type localIdentifier struct { 19 | parent *localIdentifier 20 | name string 21 | isTainted bool 22 | hasTypeDefinitionNominal bool 23 | hasTypeDefinitionStructural bool 24 | readFromWriteTo bool 25 | } 26 | 27 | func (i *localIdentifier) emitUntainted(s sink, isPublic bool) { 28 | if i.parent == nil { 29 | emitMixedCase(s, i.name, isPublic) 30 | } else { 31 | i.parent.emitUntainted(s, isPublic) 32 | emitMixedCase(s, i.name, true) 33 | } 34 | } 35 | 36 | func (i *localIdentifier) emit(s sink, forcePublic bool) { 37 | i.emitUntainted(s, !i.isTainted && (i.parent == nil || forcePublic)) 38 | if i.isTainted { 39 | s.emitString("Impl") 40 | } 41 | } 42 | 43 | func (i *localIdentifier) emitOriginalName(s sink) { 44 | if i.parent != nil { 45 | i.parent.emitOriginalName(s) 46 | s.emitString(".") 47 | } 48 | s.emitString(i.name) 49 | } 50 | 51 | func (i *localIdentifier) emitStructuralFunctionName(s sink, functionName string) { 52 | if !i.isTainted && i.parent == nil { 53 | s.emitString(functionName) 54 | } else { 55 | s.emitString(strings.ToLower(functionName)) 56 | } 57 | i.emitUntainted(s, true) 58 | } 59 | 60 | func (i *localIdentifier) hasTypeDefinition(isNominal bool) bool { 61 | if isNominal { 62 | return i.hasTypeDefinitionNominal 63 | } 64 | return i.hasTypeDefinitionStructural 65 | } 66 | 67 | func (i *localIdentifier) hasReadFromWriteTo() bool { 68 | return i.readFromWriteTo 69 | } 70 | 71 | func (i *localIdentifier) inside() identifier { 72 | iInside := *i 73 | iInside.hasTypeDefinitionNominal = false 74 | iInside.hasTypeDefinitionStructural = false 75 | iInside.readFromWriteTo = false 76 | return &iInside 77 | } 78 | 79 | func (i *localIdentifier) resolve(pkg, name string) identifier { 80 | return newForeignIdentifier(pkg, name) 81 | } 82 | 83 | func (i *localIdentifier) append(typeUsedMultipleTimes, readFromWriteToUsedMultipleTimes bool) identifierFactory { 84 | return &localIdentifierFactory{ 85 | parent: i, 86 | typeUsedMultipleTimes: typeUsedMultipleTimes, 87 | readFromWriteToUsedMultipleTimes: readFromWriteToUsedMultipleTimes, 88 | } 89 | } 90 | 91 | func (i *localIdentifier) taint() identifier { 92 | return &localIdentifier{ 93 | parent: i.parent, 94 | name: i.name, 95 | isTainted: true, 96 | } 97 | } 98 | 99 | type localIdentifierFactory struct { 100 | parent *localIdentifier 101 | typeUsedMultipleTimes bool 102 | readFromWriteToUsedMultipleTimes bool 103 | } 104 | 105 | func (f *localIdentifierFactory) newIdentifier(name string) identifier { 106 | return &localIdentifier{ 107 | parent: f.parent, 108 | name: name, 109 | hasTypeDefinitionNominal: f.typeUsedMultipleTimes, 110 | readFromWriteTo: f.readFromWriteToUsedMultipleTimes, 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /pkg/compiler/model/sink.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | type sink interface { 10 | emitString(v string) 11 | emitPackageNamePrefix(pkg string) 12 | } 13 | 14 | const xdrRuntimePackage = "github.com/buildbarn/go-xdr/pkg/runtime" 15 | 16 | func emitMacroCase(s sink, name string) { 17 | s.emitString(strings.ToUpper(name)) 18 | } 19 | 20 | func emitMixedCase(s sink, name string, isPublic bool) { 21 | conversion := unicode.ToLower 22 | if isPublic { 23 | conversion = unicode.ToUpper 24 | } 25 | 26 | for _, field := range strings.FieldsFunc(name, func(r rune) bool { return r == '_' }) { 27 | isFirst := true 28 | s.emitString(strings.Map(func(r rune) rune { 29 | if isFirst { 30 | isFirst = false 31 | return conversion(r) 32 | } 33 | return unicode.ToLower(r) 34 | }, field)) 35 | conversion = unicode.ToUpper 36 | } 37 | } 38 | 39 | func emitConstantFixedEncodedSizeBytes(s sink, i identifier, size *big.Int) { 40 | s.emitString("const ") 41 | i.emit(s, false) 42 | s.emitString("EncodedSizeBytes = ") 43 | s.emitString(size.String()) 44 | s.emitString("\n") 45 | } 46 | 47 | func emitDeclarationsStructural(s sink, r Resolver, i identifier, t Type, useTypeEquals, allMethods bool) error { 48 | if i.hasTypeDefinition(false) { 49 | s.emitString("\n") 50 | s.emitString("type ") 51 | i.emit(s, false) 52 | if useTypeEquals { 53 | s.emitString(" = ") 54 | } else { 55 | s.emitString(" ") 56 | } 57 | if err := t.emitDeclaration(s, r, i.inside()); err != nil { 58 | return err 59 | } 60 | s.emitString("\n") 61 | } 62 | 63 | if i.hasReadFromWriteTo() { 64 | isLarge, err := t.isLarge(r) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | s.emitString("\n") 70 | if isLarge { 71 | s.emitString("func ") 72 | i.emitStructuralFunctionName(s, "Read") 73 | s.emitString("(r ") 74 | s.emitPackageNamePrefix("io") 75 | s.emitString("Reader, m *") 76 | if err := t.emitDeclaration(s, r, i); err != nil { 77 | return err 78 | } 79 | s.emitString(") (nTotal int64, err error) {\n") 80 | } else { 81 | s.emitString("func ") 82 | i.emitStructuralFunctionName(s, "Read") 83 | s.emitString("(r ") 84 | s.emitPackageNamePrefix("io") 85 | s.emitString("Reader) (m ") 86 | if err := t.emitDeclaration(s, r, i); err != nil { 87 | return err 88 | } 89 | s.emitString(", nTotal int64, err error) {\n") 90 | } 91 | s.emitString("var nField int64\n") 92 | if err := t.emitReadFrom(s, r, i.inside()); err != nil { 93 | return err 94 | } 95 | s.emitString("done:\n") 96 | s.emitString("return\n") 97 | s.emitString("}\n") 98 | 99 | if allMethods { 100 | s.emitString("\n") 101 | s.emitString("func ") 102 | i.emitStructuralFunctionName(s, "Write") 103 | s.emitString("(w ") 104 | s.emitPackageNamePrefix("io") 105 | s.emitString("Writer, m ") 106 | if isLarge { 107 | s.emitString("*") 108 | } 109 | if err := t.emitDeclaration(s, r, i); err != nil { 110 | return err 111 | } 112 | s.emitString(") (nTotal int64, err error) {\n") 113 | s.emitString("var nField int64\n") 114 | if err := t.emitWriteTo(s, r, i.inside()); err != nil { 115 | return err 116 | } 117 | s.emitString("done:\n") 118 | s.emitString("return\n") 119 | s.emitString("}\n") 120 | 121 | s.emitString("\n") 122 | if size, err := t.getFixedEncodedSizeBytes(r); err != nil { 123 | return err 124 | } else if size != nil { 125 | emitConstantFixedEncodedSizeBytes(s, i, size) 126 | } else { 127 | s.emitString("func Get") 128 | i.emit(s, true) 129 | s.emitString("EncodedSizeBytes(m ") 130 | if isLarge { 131 | s.emitString("*") 132 | } 133 | if err := t.emitDeclaration(s, r, i); err != nil { 134 | return err 135 | } 136 | s.emitString(") (nTotal int) {\n") 137 | if err := t.emitGetVariableEncodedSizeBytes(s, r, i.inside()); err != nil { 138 | return err 139 | } 140 | s.emitString("return\n") 141 | s.emitString("}\n") 142 | } 143 | } 144 | } 145 | return nil 146 | } 147 | 148 | func emitForwardReadFromStructural(s sink, r Resolver, i identifier, isLarge bool) error { 149 | if isLarge { 150 | s.emitString("nField, err = ") 151 | i.emitStructuralFunctionName(s, "Read") 152 | s.emitString("(r, m)\n") 153 | } else { 154 | s.emitString("m, nField, err = ") 155 | i.emitStructuralFunctionName(s, "Read") 156 | s.emitString("(r)\n") 157 | } 158 | emitReadWriteErrorHandling(s) 159 | return nil 160 | } 161 | 162 | func emitForwardWriteToStructural(s sink, i identifier) { 163 | s.emitString("nField, err = ") 164 | i.emitStructuralFunctionName(s, "Write") 165 | s.emitString("(w, m)\n") 166 | emitReadWriteErrorHandling(s) 167 | } 168 | 169 | func emitForwardGetVariableEncodedSizeBytesStructural(s sink, i identifier) { 170 | s.emitString("nTotal += ") 171 | i.emitStructuralFunctionName(s, "Get") 172 | s.emitString("EncodedSizeBytes(m)\n") 173 | } 174 | 175 | func emitAdditionalDeclarationsNominal(s sink, r Resolver, i identifier, t Type) error { 176 | if i.hasReadFromWriteTo() { 177 | isLarge, err := t.isLarge(r) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | s.emitString("\n") 183 | if isLarge { 184 | s.emitString("func (m *") 185 | } else { 186 | s.emitString("func (mParent *") 187 | } 188 | i.emit(s, false) 189 | s.emitString(") ReadFrom(r ") 190 | s.emitPackageNamePrefix("io") 191 | s.emitString("Reader) (nTotal int64, err error) {\n") 192 | s.emitString("var nField int64\n") 193 | if isLarge { 194 | if err := t.emitReadFrom(s, r, i.inside()); err != nil { 195 | return err 196 | } 197 | } else { 198 | s.emitString("var m ") 199 | i.emit(s, false) 200 | s.emitString("\n") 201 | if err := t.emitReadFrom(s, r, i.inside()); err != nil { 202 | return err 203 | } 204 | s.emitString("*mParent = m\n") 205 | } 206 | s.emitString("done:\n") 207 | s.emitString("return\n") 208 | s.emitString("}\n") 209 | 210 | s.emitString("\n") 211 | s.emitString("func (m ") 212 | if isLarge { 213 | s.emitString("*") 214 | } 215 | i.emit(s, false) 216 | s.emitString(") WriteTo(w ") 217 | s.emitPackageNamePrefix("io") 218 | s.emitString("Writer) (nTotal int64, err error) {\n") 219 | s.emitString("var nField int64\n") 220 | if err := t.emitWriteTo(s, r, i.inside()); err != nil { 221 | return err 222 | } 223 | s.emitString("done:\n") 224 | s.emitString("return\n") 225 | s.emitString("}\n") 226 | 227 | s.emitString("\n") 228 | if size, err := t.getFixedEncodedSizeBytes(r); err != nil { 229 | return err 230 | } else if size != nil { 231 | emitConstantFixedEncodedSizeBytes(s, i, size) 232 | } else { 233 | s.emitString("func (m ") 234 | if isLarge { 235 | s.emitString("*") 236 | } 237 | i.emit(s, true) 238 | s.emitString(") GetEncodedSizeBytes() (nTotal int) {\n") 239 | if err := t.emitGetVariableEncodedSizeBytes(s, r, i.inside()); err != nil { 240 | return err 241 | } 242 | s.emitString("return\n") 243 | s.emitString("}\n") 244 | } 245 | } 246 | 247 | return nil 248 | } 249 | 250 | func emitForwardReadFromNominal(s sink) { 251 | s.emitString("nField, err = m.ReadFrom(r)\n") 252 | emitReadWriteErrorHandling(s) 253 | } 254 | 255 | func emitForwardWriteToNominal(s sink) { 256 | s.emitString("nField, err = m.WriteTo(w)\n") 257 | emitReadWriteErrorHandling(s) 258 | } 259 | 260 | func emitForwardGetVariableEncodedSizeBytesNominal(s sink) { 261 | s.emitString("nTotal += m.GetEncodedSizeBytes()\n") 262 | } 263 | 264 | func emitReadWriteErrorHandling(s sink) { 265 | s.emitString("nTotal += nField\n") 266 | s.emitString("if err != nil {\n") 267 | s.emitString("goto done\n") 268 | s.emitString("}\n") 269 | } 270 | -------------------------------------------------------------------------------- /pkg/compiler/model/string_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type stringType struct { 9 | maximumSizeBytes Value 10 | stringType string 11 | } 12 | 13 | // NewASCIIStringType creates a new ASCII string type as described in 14 | // RFC 4506, section 4.11. 15 | func NewASCIIStringType(maximumSizeBytes Value) Type { 16 | return &stringType{ 17 | maximumSizeBytes: maximumSizeBytes, 18 | stringType: "ASCII", 19 | } 20 | } 21 | 22 | // NewUTF8StringType creates a new UTF-8 string type. This is an 23 | // extension to RFC 4506, which only supports ASCII strings. 24 | func NewUTF8StringType(maximumSizeBytes Value) Type { 25 | return &stringType{ 26 | maximumSizeBytes: maximumSizeBytes, 27 | stringType: "UTF8", 28 | } 29 | } 30 | 31 | func (t *stringType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 32 | return nil, errors.New("String constants are not supported") 33 | } 34 | 35 | func (t *stringType) emitDeclaration(s sink, r Resolver, i identifier) error { 36 | s.emitString("string") 37 | return nil 38 | } 39 | 40 | func (t *stringType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 41 | return emitDeclarationsStructural(s, r, i, t, true, true) 42 | } 43 | 44 | func (t *stringType) emitReadFrom(s sink, r Resolver, i identifier) error { 45 | s.emitString("m, nField, err = ") 46 | s.emitPackageNamePrefix(xdrRuntimePackage) 47 | s.emitString("Read") 48 | s.emitString(t.stringType) 49 | s.emitString("String(r, ") 50 | if err := t.maximumSizeBytes.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 51 | return err 52 | } 53 | s.emitString(")\n") 54 | emitReadWriteErrorHandling(s) 55 | return nil 56 | } 57 | 58 | func (t *stringType) emitWriteTo(s sink, r Resolver, i identifier) error { 59 | s.emitString("nField, err = ") 60 | s.emitPackageNamePrefix(xdrRuntimePackage) 61 | s.emitString("Write") 62 | s.emitString(t.stringType) 63 | s.emitString("String(w, ") 64 | if err := t.maximumSizeBytes.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 65 | return err 66 | } 67 | s.emitString(", m)\n") 68 | emitReadWriteErrorHandling(s) 69 | return nil 70 | } 71 | 72 | func (t *stringType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 73 | s.emitString("nTotal += (len(m) + 7) &^ 3\n") 74 | return nil 75 | } 76 | 77 | func (t *stringType) isLarge(r Resolver) (bool, error) { 78 | return false, nil 79 | } 80 | 81 | func (t *stringType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 82 | return nil, nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/compiler/model/struct_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type structType struct { 9 | declarations []NamedDeclaration 10 | } 11 | 12 | // NewStructType creates a structure type, as described in RFC 4506, 13 | // section 4.14. It translates to a struct in Go. 14 | func NewStructType(declarations []NamedDeclaration) Type { 15 | return &structType{ 16 | declarations: declarations, 17 | } 18 | } 19 | 20 | func (t *structType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 21 | return nil, errors.New("Struct constants are not supported") 22 | } 23 | 24 | func (t *structType) emitDeclaration(s sink, r Resolver, i identifier) error { 25 | if i.hasTypeDefinition(true) { 26 | i.emit(s, false) 27 | } else { 28 | s.emitString("struct {\n") 29 | for _, nd := range t.declarations { 30 | if err := nd.emitStructField(s, r, i.append(false, false)); err != nil { 31 | return err 32 | } 33 | } 34 | s.emitString("}") 35 | } 36 | return nil 37 | } 38 | 39 | func (t *structType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 40 | if i.hasTypeDefinition(true) { 41 | s.emitString("\n") 42 | s.emitString("type ") 43 | i.emit(s, false) 44 | s.emitString(" struct {\n") 45 | for _, nd := range t.declarations { 46 | if err := nd.emitStructField(s, r, i.append(false, false)); err != nil { 47 | return err 48 | } 49 | } 50 | s.emitString("}\n") 51 | } 52 | 53 | if err := emitAdditionalDeclarationsNominal(s, r, i, t); err != nil { 54 | return err 55 | } 56 | 57 | for _, nd := range t.declarations { 58 | if err := nd.emitAllDeclarations(s, r, i.append(false, false)); err != nil { 59 | return err 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | func (t *structType) emitReadFrom(s sink, r Resolver, i identifier) error { 66 | if i.hasReadFromWriteTo() { 67 | emitForwardReadFromNominal(s) 68 | } else { 69 | for _, nd := range t.declarations { 70 | if err := nd.emitStructFieldReadFrom(s, r, i.append(false, false)); err != nil { 71 | return err 72 | } 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | func (t *structType) emitWriteTo(s sink, r Resolver, i identifier) error { 79 | if i.hasReadFromWriteTo() { 80 | emitForwardWriteToNominal(s) 81 | } else { 82 | for _, nd := range t.declarations { 83 | if err := nd.emitStructFieldWriteTo(s, r, i.append(false, false)); err != nil { 84 | return err 85 | } 86 | } 87 | } 88 | return nil 89 | } 90 | 91 | func (t *structType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 92 | if i.hasReadFromWriteTo() { 93 | emitForwardGetVariableEncodedSizeBytesNominal(s) 94 | } else { 95 | for _, nd := range t.declarations { 96 | if err := nd.maybeEmitStructFieldGetVariableEncodedSizeBytes(s, r, i.append(false, false)); err != nil { 97 | return err 98 | } 99 | } 100 | } 101 | return nil 102 | } 103 | 104 | func (t *structType) isLarge(r Resolver) (bool, error) { 105 | return true, nil 106 | } 107 | 108 | func (t *structType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 109 | var total big.Int 110 | for _, nd := range t.declarations { 111 | size, err := nd.declarationType.getFixedEncodedSizeBytes(r) 112 | if err != nil || size == nil { 113 | return nil, err 114 | } 115 | total.Add(&total, size) 116 | } 117 | return &total, nil 118 | } 119 | -------------------------------------------------------------------------------- /pkg/compiler/model/top_level.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path" 7 | "sort" 8 | ) 9 | 10 | // TopLevel contains all of the information declared in an XDR schema 11 | // file. As an extension, this implementation requires that all schema 12 | // files contain a package name and import directives. 13 | type TopLevel struct { 14 | pkg string 15 | imports []string 16 | definitions []Definition 17 | } 18 | 19 | // NewTopLevel constructs a TopLevel based on parsed definitions. 20 | func NewTopLevel(pkg string, imports []string, definitions []Definition) TopLevel { 21 | return TopLevel{ 22 | pkg: pkg, 23 | imports: imports, 24 | definitions: definitions, 25 | } 26 | } 27 | 28 | // Emit an XDR schema file as Go code. 29 | func (tl TopLevel) Emit(w io.StringWriter, r Resolver) error { 30 | if _, err := w.WriteString(fmt.Sprintf("// Code generated by go-xdr. DO NOT EDIT.\npackage %s\n", path.Base(tl.pkg))); err != nil { 31 | return err 32 | } 33 | 34 | importCapturingSink := importCapturingSink{ 35 | imports: map[string]struct{}{}, 36 | pkg: tl.pkg, 37 | } 38 | for _, definition := range tl.definitions { 39 | if err := definition.emitAllDefinitions(&importCapturingSink, r); err != nil { 40 | return err 41 | } 42 | } 43 | 44 | if imports := importCapturingSink.imports; len(imports) > 0 { 45 | importURLs := make([]string, 0, len(imports)) 46 | for importURL := range imports { 47 | importURLs = append(importURLs, importURL) 48 | } 49 | sort.Strings(importURLs) 50 | 51 | if _, err := w.WriteString("\nimport (\n"); err != nil { 52 | return err 53 | } 54 | for _, importURL := range importURLs { 55 | if _, err := w.WriteString(fmt.Sprintf("%#v\n", importURL)); err != nil { 56 | return err 57 | } 58 | } 59 | if _, err := w.WriteString(")\n"); err != nil { 60 | return err 61 | } 62 | } 63 | 64 | writingSink := writingSink{ 65 | pkg: tl.pkg, 66 | w: w, 67 | } 68 | for _, definition := range tl.definitions { 69 | if err := definition.emitAllDefinitions(&writingSink, r); err != nil { 70 | return err 71 | } 72 | } 73 | return writingSink.err 74 | } 75 | 76 | // Register all imports and definitions in an XDR schema file into a 77 | // Registry, so that definitions in multiple XDR schema files can be 78 | // tied together. 79 | func (tl TopLevel) Register(r Registry) error { 80 | for _, path := range tl.imports { 81 | if err := r.RegisterImport(path); err != nil { 82 | return err 83 | } 84 | } 85 | for _, definition := range tl.definitions { 86 | if err := definition.register(r, tl.pkg); err != nil { 87 | return err 88 | } 89 | } 90 | return nil 91 | } 92 | 93 | type importCapturingSink struct { 94 | imports map[string]struct{} 95 | pkg string 96 | } 97 | 98 | func (s *importCapturingSink) emitString(v string) {} 99 | 100 | func (s *importCapturingSink) emitPackageNamePrefix(pkg string) { 101 | if pkg != s.pkg { 102 | s.imports[pkg] = struct{}{} 103 | } 104 | } 105 | 106 | type writingSink struct { 107 | pkg string 108 | w io.StringWriter 109 | err error 110 | } 111 | 112 | func (s *writingSink) emitString(v string) { 113 | if s.err == nil { 114 | _, s.err = s.w.WriteString(v) 115 | } 116 | } 117 | 118 | func (s *writingSink) emitPackageNamePrefix(pkg string) { 119 | if pkg != s.pkg { 120 | s.emitString(path.Base(pkg)) 121 | s.emitString(".") 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /pkg/compiler/model/type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Type that is used as part of a type or program definition. 8 | type Type interface { 9 | getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) 10 | emitDeclaration(s sink, r Resolver, i identifier) error 11 | emitAllDeclarations(s sink, r Resolver, i identifier) error 12 | emitReadFrom(s sink, r Resolver, i identifier) error 13 | emitWriteTo(s sink, r Resolver, i identifier) error 14 | emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error 15 | isLarge(r Resolver) (bool, error) 16 | getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/compiler/model/type_definition.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type typeDefinition struct { 4 | declaration NamedDeclaration 5 | } 6 | 7 | // NewTypeDefinition creates a new named type definition, as described 8 | // in RFC 4506, section 4.18. 9 | func NewTypeDefinition(declaration NamedDeclaration) Definition { 10 | return &typeDefinition{ 11 | declaration: declaration, 12 | } 13 | } 14 | 15 | func (d *typeDefinition) register(r Registry, pkg string) error { 16 | nd := d.declaration 17 | return r.RegisterType(nd.name, pkg, nd.declarationType) 18 | } 19 | 20 | func (d *typeDefinition) emitAllDefinitions(s sink, r Resolver) error { 21 | s.emitString("\n") 22 | s.emitString("// Type definition \"") 23 | s.emitString(d.declaration.name) 24 | s.emitString("\".\n") 25 | return d.declaration.emitAllDeclarations(s, r, rootIdentifierFactory{}) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/compiler/model/value.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Value that is either a constant or an identifier. 4 | type Value interface { 5 | visit(ve valueVisitor) error 6 | } 7 | -------------------------------------------------------------------------------- /pkg/compiler/model/value_visitor.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type valueVisitor interface { 8 | visitConstant(constant *big.Int) error 9 | visitIdentifier(name string) error 10 | } 11 | -------------------------------------------------------------------------------- /pkg/compiler/model/variable_length_array_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type variableLengthArrayType struct { 9 | base Type 10 | maximumSizeElements Value 11 | } 12 | 13 | // NewVariableLengthArrayType creates a type that corresponds to a 14 | // variable-length array, as described in RFC 4506, section 4.13. It 15 | // translates to a slice in Go. 16 | func NewVariableLengthArrayType(base Type, maximumSizeElements Value) Type { 17 | return &variableLengthArrayType{ 18 | base: base, 19 | maximumSizeElements: maximumSizeElements, 20 | } 21 | } 22 | 23 | func (t *variableLengthArrayType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 24 | return nil, errors.New("Array constants are not supported") 25 | } 26 | 27 | func (t *variableLengthArrayType) emitDeclaration(s sink, r Resolver, i identifier) error { 28 | s.emitString("[]") 29 | return t.base.emitDeclaration(s, r, i.taint()) 30 | } 31 | 32 | func (t *variableLengthArrayType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 33 | if err := emitDeclarationsStructural(s, r, i, t, true, true); err != nil { 34 | return err 35 | } 36 | return t.base.emitAllDeclarations(s, r, i.taint()) 37 | } 38 | 39 | func (t *variableLengthArrayType) emitReadFrom(s sink, r Resolver, i identifier) error { 40 | childNP := i.taint() 41 | 42 | // Read the number of elements in the array. 43 | s.emitString("var nElements uint32\n") 44 | s.emitString("nElements, nField, err = ") 45 | s.emitPackageNamePrefix(xdrRuntimePackage) 46 | s.emitString("ReadUnsignedInt(r)\n") 47 | emitReadWriteErrorHandling(s) 48 | s.emitString("if nElements > ") 49 | if err := t.maximumSizeElements.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 50 | return err 51 | } 52 | s.emitString(" {\nerr = ") 53 | s.emitPackageNamePrefix("fmt") 54 | s.emitString("Errorf(\"size of %d elements exceeds ") 55 | i.emitOriginalName(s) 56 | s.emitString("'s maximum of ") 57 | if err := t.maximumSizeElements.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 58 | return err 59 | } 60 | s.emitString(" elements\", nElements)\ngoto done\n}\n") 61 | 62 | isLarge, err := t.base.isLarge(r) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | // Read the elements in the array. Ideally we would call 68 | // make([]T, nElements) here, but this has the downside that the 69 | // caller of ReadFrom() cannot limit memory usage through 70 | // io.LimitReader. 71 | s.emitString("for nElements > 0 {\n") 72 | s.emitString("nElements--\n") 73 | if isLarge { 74 | s.emitString("m = append(m, ") 75 | if err := t.base.emitDeclaration(s, r, childNP); err != nil { 76 | return err 77 | } 78 | s.emitString("{})\n") 79 | s.emitString("m := &m[len(m)-1]\n") 80 | if err := t.base.emitReadFrom(s, r, childNP); err != nil { 81 | return err 82 | } 83 | } else { 84 | s.emitString("mParent := &m\n") 85 | s.emitString("var m ") 86 | if err := t.base.emitDeclaration(s, r, childNP); err != nil { 87 | return err 88 | } 89 | s.emitString("\n") 90 | if err := t.base.emitReadFrom(s, r, childNP); err != nil { 91 | return err 92 | } 93 | s.emitString("*mParent = append(*mParent, m)\n") 94 | } 95 | s.emitString("}\n") 96 | return nil 97 | } 98 | 99 | func (t *variableLengthArrayType) emitWriteTo(s sink, r Resolver, i identifier) error { 100 | s.emitString("if uint(len(m)) > ") 101 | if err := t.maximumSizeElements.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 102 | return err 103 | } 104 | s.emitString(" {\nerr = ") 105 | s.emitPackageNamePrefix("fmt") 106 | s.emitString("Errorf(\"size of %d elements exceeds ") 107 | i.emitOriginalName(s) 108 | s.emitString("'s maximum of ") 109 | if err := t.maximumSizeElements.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 110 | return err 111 | } 112 | s.emitString(" elements\", len(m))\ngoto done\n}\n") 113 | 114 | // Write the number of elements in the array. 115 | s.emitString("nField, err = ") 116 | s.emitPackageNamePrefix(xdrRuntimePackage) 117 | s.emitString("WriteUnsignedInt(w, uint32(len(m)))\n") 118 | emitReadWriteErrorHandling(s) 119 | 120 | // Write the elements in the array. 121 | s.emitString("for _, m := range m {\n") 122 | if err := t.base.emitWriteTo(s, r, i.taint()); err != nil { 123 | return err 124 | } 125 | s.emitString("}\n") 126 | return nil 127 | } 128 | 129 | func (t *variableLengthArrayType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 130 | elementSizeBytes, err := t.base.getFixedEncodedSizeBytes(r) 131 | if err != nil { 132 | return err 133 | } 134 | if elementSizeBytes != nil { 135 | s.emitString("nTotal += 4 + ") 136 | s.emitString(elementSizeBytes.String()) 137 | s.emitString(" * len(m)\n") 138 | } else { 139 | s.emitString("nTotal += 4\n") 140 | s.emitString("for _, m := range m {\n") 141 | if err := t.base.emitGetVariableEncodedSizeBytes(s, r, i.taint()); err != nil { 142 | return err 143 | } 144 | s.emitString("}\n") 145 | } 146 | return nil 147 | } 148 | 149 | func (t *variableLengthArrayType) isComplexToReadOrWrite(r Resolver) (bool, error) { 150 | return true, nil 151 | } 152 | 153 | func (t *variableLengthArrayType) isLarge(r Resolver) (bool, error) { 154 | return false, nil 155 | } 156 | 157 | func (t *variableLengthArrayType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 158 | return nil, nil 159 | } 160 | -------------------------------------------------------------------------------- /pkg/compiler/model/variable_length_opaque_type.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | ) 7 | 8 | type variableLengthOpaqueType struct { 9 | maximumSizeBytes Value 10 | } 11 | 12 | // NewVariableLengthOpaqueType creates a variable-length opaque data 13 | // type, as described in RFC 4506, section 4.10. It translates to a byte 14 | // slice in Go. 15 | func NewVariableLengthOpaqueType(maximumSizeBytes Value) Type { 16 | return &variableLengthOpaqueType{ 17 | maximumSizeBytes: maximumSizeBytes, 18 | } 19 | } 20 | 21 | func (t *variableLengthOpaqueType) getEmittingValueVisitor(s sink, r Resolver) (valueVisitor, error) { 22 | return nil, errors.New("Opaque constants are not supported") 23 | } 24 | 25 | func (t *variableLengthOpaqueType) emitDeclaration(s sink, r Resolver, i identifier) error { 26 | s.emitString("[]byte") 27 | return nil 28 | } 29 | 30 | func (t *variableLengthOpaqueType) emitAllDeclarations(s sink, r Resolver, i identifier) error { 31 | return emitDeclarationsStructural(s, r, i, t, true, true) 32 | } 33 | 34 | func (t *variableLengthOpaqueType) emitReadFrom(s sink, r Resolver, i identifier) error { 35 | s.emitString("m, nField, err = ") 36 | s.emitPackageNamePrefix(xdrRuntimePackage) 37 | s.emitString("ReadVariableLengthOpaque(r, ") 38 | if err := t.maximumSizeBytes.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 39 | return err 40 | } 41 | s.emitString(")\n") 42 | emitReadWriteErrorHandling(s) 43 | return nil 44 | } 45 | 46 | func (t *variableLengthOpaqueType) emitWriteTo(s sink, r Resolver, i identifier) error { 47 | s.emitString("nField, err = ") 48 | s.emitPackageNamePrefix(xdrRuntimePackage) 49 | s.emitString("WriteVariableLengthOpaque(w, ") 50 | if err := t.maximumSizeBytes.visit(newIntegerEmittingValueVisitor(s, r)); err != nil { 51 | return err 52 | } 53 | s.emitString(", m)\n") 54 | emitReadWriteErrorHandling(s) 55 | return nil 56 | } 57 | 58 | func (t *variableLengthOpaqueType) emitGetVariableEncodedSizeBytes(s sink, r Resolver, i identifier) error { 59 | s.emitString("nTotal += (len(m) + 7) &^ 3\n") 60 | return nil 61 | } 62 | 63 | func (t *variableLengthOpaqueType) isComplexToReadOrWrite(r Resolver) (bool, error) { 64 | return false, nil 65 | } 66 | 67 | func (t *variableLengthOpaqueType) isLarge(r Resolver) (bool, error) { 68 | return false, nil 69 | } 70 | 71 | func (t *variableLengthOpaqueType) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 72 | return nil, nil 73 | } 74 | -------------------------------------------------------------------------------- /pkg/compiler/model/version.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // Version of an RPC program, as described in RFC 5531, chapter 12. 8 | type Version struct { 9 | name string 10 | procedures []Procedure 11 | versionNumber *big.Int 12 | } 13 | 14 | // NewVersion creates a new version of an RPC program. 15 | func NewVersion(name string, procedures []Procedure, versionNumber *big.Int) Version { 16 | return Version{ 17 | name: name, 18 | procedures: procedures, 19 | versionNumber: versionNumber, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/compiler/model/void_declaration.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type voidDeclaration struct{} 8 | 9 | func (d voidDeclaration) emitAllDeclarations(s sink, r Resolver, f identifierFactory) error { 10 | return nil 11 | } 12 | 13 | func (d voidDeclaration) emitStructField(s sink, r Resolver, f identifierFactory) error { 14 | return nil 15 | } 16 | 17 | func (d voidDeclaration) emitStructFieldReadFrom(s sink, r Resolver, f identifierFactory) error { 18 | s.emitString("_ = m\n") 19 | return nil 20 | } 21 | 22 | func (d voidDeclaration) emitStructFieldWriteTo(s sink, r Resolver, f identifierFactory) error { 23 | return nil 24 | } 25 | 26 | func (d voidDeclaration) maybeEmitStructFieldGetVariableEncodedSizeBytes(s sink, r Resolver, f identifierFactory) error { 27 | return nil 28 | } 29 | 30 | var voidFixedEncodingSizeBytes = big.NewInt(0) 31 | 32 | func (d voidDeclaration) getFixedEncodedSizeBytes(r Resolver) (*big.Int, error) { 33 | return voidFixedEncodingSizeBytes, nil 34 | } 35 | 36 | // VoidDeclaration is declaration of type void, as described in RFC 37 | // 4506, section 4.16. It is typically only used inside discriminated 38 | // unions to denote arms that do not accept a value. 39 | var VoidDeclaration Declaration = voidDeclaration{} 40 | -------------------------------------------------------------------------------- /pkg/compiler/parser/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_antlr//antlr:antlr4.bzl", "antlr") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | antlr( 5 | name = "xdr", 6 | srcs = ["XDR.g4"], 7 | language = "Go", 8 | ) 9 | 10 | go_library( 11 | name = "parser", 12 | srcs = [ 13 | ":xdr", # keep 14 | ], 15 | importpath = "github.com/buildbarn/go-xdr/pkg/compiler/parser", # keep 16 | visibility = ["//visibility:public"], 17 | deps = [ 18 | "//pkg/compiler/model", # keep 19 | "@com_github_antlr_antlr4_runtime_go_antlr//:antlr", # keep 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /pkg/compiler/parser/XDR.g4: -------------------------------------------------------------------------------- 1 | grammar XDR; 2 | 3 | @parser::header { 4 | import ( 5 | "math" 6 | "math/big" 7 | 8 | "github.com/buildbarn/go-xdr/pkg/compiler/model" 9 | ) 10 | 11 | var defaultArraySize = model.NewConstantValue(big.NewInt(math.MaxUint32)) 12 | } 13 | 14 | // Top-level structure 15 | // 16 | // Below is custom syntax that is inspired by Protobuf that can be used 17 | // to let XDR files include each other. 18 | 19 | topLevel returns [model.TopLevel v] 20 | : 'package' stringConstant ';' imports specification EOF { 21 | $v = model.NewTopLevel($stringConstant.v, $imports.v, $specification.v) 22 | } 23 | ; 24 | 25 | imports returns [[]string v] 26 | : ( 27 | 'import' stringConstant ';' { 28 | $v = append($v, $stringConstant.v) 29 | } 30 | )* 31 | ; 32 | 33 | stringConstant returns [string v] 34 | : STRING_CONSTANT { 35 | s := $STRING_CONSTANT.text 36 | $v = s[1:len(s)-1] 37 | } 38 | ; 39 | 40 | // RFC 4506: XDR: External Data Representation Standard 41 | // https://tools.ietf.org/html/rfc4506 42 | 43 | // Section 6.3: Syntax Information 44 | 45 | // Deviation from the RFC: declaration has been decomposed into 46 | // namedDeclaration. The RFC allows definitions as follows: 47 | // 48 | // typedef void; 49 | // struct empty { void; }; 50 | // 51 | // These definitions are meaningless and are also rejected by tools such 52 | // as rpcgen. 53 | // 54 | // An 'utf8string' type has been added. This type is normally declared as part 55 | // of NFSv4 as follows: 56 | // 57 | // typedef opaque utf8string<>; 58 | // 59 | // We declare it as a native type, so that we don't need to work with 60 | // byte slices. 61 | 62 | namedDeclaration returns [model.NamedDeclaration v] 63 | : typeSpecifier IDENTIFIER { 64 | $v = model.NewNamedDeclaration($IDENTIFIER.text, $typeSpecifier.v) 65 | } 66 | | typeSpecifier IDENTIFIER '[' value ']' { 67 | $v = model.NewNamedDeclaration( 68 | $IDENTIFIER.text, 69 | model.NewFixedLengthArrayType($typeSpecifier.v, $value.v)) 70 | } 71 | | typeSpecifier IDENTIFIER '<' { 72 | $v = model.NewNamedDeclaration( 73 | $IDENTIFIER.text, 74 | model.NewVariableLengthArrayType($typeSpecifier.v, defaultArraySize)) 75 | } 76 | ( 77 | value { 78 | $v = model.NewNamedDeclaration( 79 | $IDENTIFIER.text, 80 | model.NewVariableLengthArrayType($typeSpecifier.v, $value.v)) 81 | } 82 | )? '>' 83 | | 'opaque' IDENTIFIER '[' value ']' { 84 | $v = model.NewNamedDeclaration( 85 | $IDENTIFIER.text, 86 | model.NewFixedLengthOpaqueType($value.v)) 87 | } 88 | | 'opaque' IDENTIFIER '<' { 89 | $v = model.NewNamedDeclaration( 90 | $IDENTIFIER.text, 91 | model.NewVariableLengthOpaqueType(defaultArraySize)) 92 | } 93 | ( 94 | value { 95 | $v = model.NewNamedDeclaration( 96 | $IDENTIFIER.text, 97 | model.NewVariableLengthOpaqueType($value.v)) 98 | } 99 | )? '>' 100 | | 'string' IDENTIFIER '<' { 101 | $v = model.NewNamedDeclaration( 102 | $IDENTIFIER.text, 103 | model.NewASCIIStringType(defaultArraySize)) 104 | } 105 | ( 106 | value { 107 | $v = model.NewNamedDeclaration( 108 | $IDENTIFIER.text, 109 | model.NewASCIIStringType($value.v)) 110 | } 111 | )? '>' 112 | | 'utf8string' IDENTIFIER { 113 | $v = model.NewNamedDeclaration( 114 | $IDENTIFIER.text, 115 | model.NewUTF8StringType(defaultArraySize)) 116 | } 117 | | typeSpecifier '*' IDENTIFIER { 118 | $v = model.NewNamedDeclaration( 119 | $IDENTIFIER.text, 120 | model.NewOptionalType($typeSpecifier.v)) 121 | } 122 | ; 123 | 124 | declaration returns [model.Declaration v] 125 | : namedDeclaration { $v = $namedDeclaration.v } 126 | | 'void' { $v = model.VoidDeclaration } 127 | ; 128 | 129 | value returns [model.Value v] 130 | : constant { $v = model.NewConstantValue($constant.v) } 131 | | IDENTIFIER { $v = model.NewIdentifierValue($IDENTIFIER.text) } 132 | ; 133 | 134 | constant returns [*big.Int v] 135 | : s=(DECIMAL_CONSTANT | HEXADECIMAL_CONSTANT | OCTAL_CONSTANT) { 136 | i := big.NewInt(0) 137 | if _, ok := i.SetString($s.text, 0); !ok { 138 | panic("Grammar guarantees integers are well-formed") 139 | } 140 | $v = i 141 | } 142 | ; 143 | 144 | typeSpecifier returns [model.Type v] 145 | : { $v = model.IntType } 146 | ( 147 | 'unsigned' { $v = model.UnsignedIntType } 148 | )? 149 | 'int' 150 | | { $v = model.HyperType } 151 | ( 152 | 'unsigned' { $v = model.UnsignedHyperType } 153 | )? 154 | 'hyper' 155 | | 'float' { $v = model.FloatType } 156 | | 'double' { $v = model.DoubleType } 157 | | 'quadruple' { $v = model.QuadrupleType } 158 | | 'bool' { $v = model.BoolType } 159 | | enumTypeSpec { $v = $enumTypeSpec.v } 160 | | structTypeSpec { $v = $structTypeSpec.v } 161 | | unionTypeSpec { $v = $unionTypeSpec.v } 162 | | IDENTIFIER { $v = model.NewIdentifierType($IDENTIFIER.text) } 163 | ; 164 | 165 | enumTypeSpec returns [model.Type v] 166 | : 'enum' enumBody { $v = $enumBody.v } 167 | ; 168 | 169 | enumBody returns [model.Type v] locals [map[string]model.Value elements] 170 | : '{' 171 | IDENTIFIER '=' value { 172 | $elements = map[string]model.Value{ 173 | $IDENTIFIER.text: $value.v, 174 | } 175 | } 176 | ( 177 | ',' IDENTIFIER '=' value { 178 | // TODO: Throw an error when there are duplicates! 179 | $elements[$IDENTIFIER.text] = $value.v 180 | } 181 | )* 182 | '}' { $v = model.NewEnumType($elements) } 183 | ; 184 | 185 | structTypeSpec returns [model.Type v] 186 | : 'struct' structBody { $v = $structBody.v } 187 | ; 188 | 189 | structBody returns [model.Type v] locals [[]model.NamedDeclaration declarations] 190 | : '{' 191 | namedDeclaration ';' { 192 | $declarations = append($declarations, $namedDeclaration.v) 193 | } 194 | ( 195 | namedDeclaration ';' { 196 | $declarations = append($declarations, $namedDeclaration.v) 197 | } 198 | )* 199 | '}' { $v = model.NewStructType($declarations) } 200 | ; 201 | 202 | unionTypeSpec returns [model.Type v] 203 | : 'union' unionBody { $v = $unionBody.v } 204 | ; 205 | 206 | unionBody returns [model.Type v] locals [[]model.CaseSpec cases, model.Declaration defaultCase] 207 | : 'switch' '(' namedDeclaration ')' '{' 208 | caseSpec { $cases = append($cases, $caseSpec.v) } 209 | ( 210 | caseSpec { $cases = append($cases, $caseSpec.v) } 211 | )* 212 | ( 213 | 'default' ':' declaration ';' { 214 | $defaultCase = $declaration.v 215 | } 216 | )? 217 | '}' { $v = model.NewUnionType($namedDeclaration.v, $cases, $defaultCase) } 218 | ; 219 | 220 | caseSpec returns [model.CaseSpec v] locals [[]model.Value values] 221 | : 'case' value ':' { $values = append($values, $value.v) } 222 | ( 223 | 'case' value ':' { $values = append($values, $value.v) } 224 | )* 225 | declaration ';' { $v = model.NewCaseSpec($values, $declaration.v) } 226 | ; 227 | 228 | constantDef returns [model.Definition v] 229 | : 'const' IDENTIFIER '=' constant ';' { 230 | $v = model.NewConstantDefinition($IDENTIFIER.text, $constant.v) 231 | } 232 | ; 233 | 234 | typeDef returns [model.NamedDeclaration v] 235 | : 'typedef' namedDeclaration ';' { 236 | $v = $namedDeclaration.v 237 | } 238 | | 'enum' IDENTIFIER enumBody ';' { 239 | $v = model.NewNamedDeclaration($IDENTIFIER.text, $enumBody.v) 240 | } 241 | | 'struct' IDENTIFIER structBody ';' { 242 | $v = model.NewNamedDeclaration($IDENTIFIER.text, $structBody.v) 243 | } 244 | | 'union' IDENTIFIER unionBody ';' { 245 | $v = model.NewNamedDeclaration($IDENTIFIER.text, $unionBody.v) 246 | } 247 | ; 248 | 249 | definition returns [model.Definition v] 250 | : typeDef { 251 | $v = model.NewTypeDefinition($typeDef.v) 252 | } 253 | | constantDef { $v = $constantDef.v } 254 | | programDef { $v = $programDef.v } 255 | ; 256 | 257 | specification returns [[]model.Definition v] 258 | : ( 259 | definition { 260 | $v = append($v, $definition.v) 261 | } 262 | )* 263 | ; 264 | 265 | // RFC 5531: RPC: Remote Procedure Call Protocol specification Version 2 266 | // https://tools.ietf.org/html/rfc5531 267 | 268 | // Section 12.2: The RPC Language specification 269 | 270 | programDef returns [model.Definition v] locals [[]model.Version versions] 271 | : 'program' IDENTIFIER '{' 272 | versionDef { $versions = append($versions, $versionDef.v) } 273 | ( 274 | versionDef { $versions = append($versions, $versionDef.v) } 275 | )* 276 | '}' '=' constant ';' { 277 | $v = model.NewProgramDefinition($IDENTIFIER.text, $versions, $constant.v) 278 | } 279 | ; 280 | 281 | versionDef returns [model.Version v] locals [[]model.Procedure procedures] 282 | : 'version' IDENTIFIER '{' 283 | procedureDef { $procedures = append($procedures, $procedureDef.v) } 284 | ( 285 | procedureDef { $procedures = append($procedures, $procedureDef.v) } 286 | )* 287 | '}' '=' constant ';' { 288 | $v = model.NewVersion($IDENTIFIER.text, $procedures, $constant.v) 289 | } 290 | ; 291 | 292 | procedureDef returns [model.Procedure v] locals [[]model.Type arguments] 293 | : procReturn IDENTIFIER '(' 294 | procFirstArg { 295 | if $procFirstArg.v != nil { 296 | $arguments = append($arguments, $procFirstArg.v) 297 | } 298 | } 299 | ( 300 | ',' typeSpecifier { 301 | $arguments = append($arguments, $typeSpecifier.v) 302 | } 303 | )* ')' '=' constant ';' { 304 | $v = model.NewProcedure($IDENTIFIER.text, $arguments, $procReturn.v, $constant.v) 305 | } 306 | ; 307 | 308 | procReturn returns [model.Type v] 309 | : 'void' 310 | | typeSpecifier 311 | { $v = $typeSpecifier.v } 312 | ; 313 | 314 | procFirstArg returns [model.Type v] 315 | : 'void' 316 | | typeSpecifier 317 | { $v = $typeSpecifier.v } 318 | ; 319 | 320 | // RFC 4506: XDR: External Data Representation Standard 321 | 322 | // Section 6.2: Lexical Notes 323 | 324 | COMMENT : '/*' .*? '*/' -> skip ; 325 | 326 | WHITE_SPACE : [ \t\n\r] + -> skip ; 327 | 328 | C_DIRECTIVE : '%' ~[\r\n]* -> skip ; 329 | 330 | IDENTIFIER : [a-zA-Z] [a-zA-Z0-9_]* ; 331 | 332 | DECIMAL_CONSTANT : '-'? [1-9] [0-9]* ; 333 | 334 | HEXADECIMAL_CONSTANT : '0x' [A-Fa-f0-9]+ ; 335 | 336 | OCTAL_CONSTANT : '0' [0-7]* ; 337 | 338 | STRING_CONSTANT : '"' ~["\\\u0000-\u001F]* '"' ; 339 | -------------------------------------------------------------------------------- /pkg/compiler/parser/xdr_base_listener.go: -------------------------------------------------------------------------------- 1 | // Code generated from pkg/compiler/parser/XDR.g4 by ANTLR 4.10. DO NOT EDIT. 2 | 3 | package parser // XDR 4 | 5 | import "github.com/antlr/antlr4/runtime/Go/antlr" 6 | 7 | // BaseXDRListener is a complete listener for a parse tree produced by XDRParser. 8 | type BaseXDRListener struct{} 9 | 10 | var _ XDRListener = &BaseXDRListener{} 11 | 12 | // VisitTerminal is called when a terminal node is visited. 13 | func (s *BaseXDRListener) VisitTerminal(node antlr.TerminalNode) {} 14 | 15 | // VisitErrorNode is called when an error node is visited. 16 | func (s *BaseXDRListener) VisitErrorNode(node antlr.ErrorNode) {} 17 | 18 | // EnterEveryRule is called when any rule is entered. 19 | func (s *BaseXDRListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} 20 | 21 | // ExitEveryRule is called when any rule is exited. 22 | func (s *BaseXDRListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} 23 | 24 | // EnterTopLevel is called when production topLevel is entered. 25 | func (s *BaseXDRListener) EnterTopLevel(ctx *TopLevelContext) {} 26 | 27 | // ExitTopLevel is called when production topLevel is exited. 28 | func (s *BaseXDRListener) ExitTopLevel(ctx *TopLevelContext) {} 29 | 30 | // EnterImports is called when production imports is entered. 31 | func (s *BaseXDRListener) EnterImports(ctx *ImportsContext) {} 32 | 33 | // ExitImports is called when production imports is exited. 34 | func (s *BaseXDRListener) ExitImports(ctx *ImportsContext) {} 35 | 36 | // EnterStringConstant is called when production stringConstant is entered. 37 | func (s *BaseXDRListener) EnterStringConstant(ctx *StringConstantContext) {} 38 | 39 | // ExitStringConstant is called when production stringConstant is exited. 40 | func (s *BaseXDRListener) ExitStringConstant(ctx *StringConstantContext) {} 41 | 42 | // EnterNamedDeclaration is called when production namedDeclaration is entered. 43 | func (s *BaseXDRListener) EnterNamedDeclaration(ctx *NamedDeclarationContext) {} 44 | 45 | // ExitNamedDeclaration is called when production namedDeclaration is exited. 46 | func (s *BaseXDRListener) ExitNamedDeclaration(ctx *NamedDeclarationContext) {} 47 | 48 | // EnterDeclaration is called when production declaration is entered. 49 | func (s *BaseXDRListener) EnterDeclaration(ctx *DeclarationContext) {} 50 | 51 | // ExitDeclaration is called when production declaration is exited. 52 | func (s *BaseXDRListener) ExitDeclaration(ctx *DeclarationContext) {} 53 | 54 | // EnterValue is called when production value is entered. 55 | func (s *BaseXDRListener) EnterValue(ctx *ValueContext) {} 56 | 57 | // ExitValue is called when production value is exited. 58 | func (s *BaseXDRListener) ExitValue(ctx *ValueContext) {} 59 | 60 | // EnterConstant is called when production constant is entered. 61 | func (s *BaseXDRListener) EnterConstant(ctx *ConstantContext) {} 62 | 63 | // ExitConstant is called when production constant is exited. 64 | func (s *BaseXDRListener) ExitConstant(ctx *ConstantContext) {} 65 | 66 | // EnterTypeSpecifier is called when production typeSpecifier is entered. 67 | func (s *BaseXDRListener) EnterTypeSpecifier(ctx *TypeSpecifierContext) {} 68 | 69 | // ExitTypeSpecifier is called when production typeSpecifier is exited. 70 | func (s *BaseXDRListener) ExitTypeSpecifier(ctx *TypeSpecifierContext) {} 71 | 72 | // EnterEnumTypeSpec is called when production enumTypeSpec is entered. 73 | func (s *BaseXDRListener) EnterEnumTypeSpec(ctx *EnumTypeSpecContext) {} 74 | 75 | // ExitEnumTypeSpec is called when production enumTypeSpec is exited. 76 | func (s *BaseXDRListener) ExitEnumTypeSpec(ctx *EnumTypeSpecContext) {} 77 | 78 | // EnterEnumBody is called when production enumBody is entered. 79 | func (s *BaseXDRListener) EnterEnumBody(ctx *EnumBodyContext) {} 80 | 81 | // ExitEnumBody is called when production enumBody is exited. 82 | func (s *BaseXDRListener) ExitEnumBody(ctx *EnumBodyContext) {} 83 | 84 | // EnterStructTypeSpec is called when production structTypeSpec is entered. 85 | func (s *BaseXDRListener) EnterStructTypeSpec(ctx *StructTypeSpecContext) {} 86 | 87 | // ExitStructTypeSpec is called when production structTypeSpec is exited. 88 | func (s *BaseXDRListener) ExitStructTypeSpec(ctx *StructTypeSpecContext) {} 89 | 90 | // EnterStructBody is called when production structBody is entered. 91 | func (s *BaseXDRListener) EnterStructBody(ctx *StructBodyContext) {} 92 | 93 | // ExitStructBody is called when production structBody is exited. 94 | func (s *BaseXDRListener) ExitStructBody(ctx *StructBodyContext) {} 95 | 96 | // EnterUnionTypeSpec is called when production unionTypeSpec is entered. 97 | func (s *BaseXDRListener) EnterUnionTypeSpec(ctx *UnionTypeSpecContext) {} 98 | 99 | // ExitUnionTypeSpec is called when production unionTypeSpec is exited. 100 | func (s *BaseXDRListener) ExitUnionTypeSpec(ctx *UnionTypeSpecContext) {} 101 | 102 | // EnterUnionBody is called when production unionBody is entered. 103 | func (s *BaseXDRListener) EnterUnionBody(ctx *UnionBodyContext) {} 104 | 105 | // ExitUnionBody is called when production unionBody is exited. 106 | func (s *BaseXDRListener) ExitUnionBody(ctx *UnionBodyContext) {} 107 | 108 | // EnterCaseSpec is called when production caseSpec is entered. 109 | func (s *BaseXDRListener) EnterCaseSpec(ctx *CaseSpecContext) {} 110 | 111 | // ExitCaseSpec is called when production caseSpec is exited. 112 | func (s *BaseXDRListener) ExitCaseSpec(ctx *CaseSpecContext) {} 113 | 114 | // EnterConstantDef is called when production constantDef is entered. 115 | func (s *BaseXDRListener) EnterConstantDef(ctx *ConstantDefContext) {} 116 | 117 | // ExitConstantDef is called when production constantDef is exited. 118 | func (s *BaseXDRListener) ExitConstantDef(ctx *ConstantDefContext) {} 119 | 120 | // EnterTypeDef is called when production typeDef is entered. 121 | func (s *BaseXDRListener) EnterTypeDef(ctx *TypeDefContext) {} 122 | 123 | // ExitTypeDef is called when production typeDef is exited. 124 | func (s *BaseXDRListener) ExitTypeDef(ctx *TypeDefContext) {} 125 | 126 | // EnterDefinition is called when production definition is entered. 127 | func (s *BaseXDRListener) EnterDefinition(ctx *DefinitionContext) {} 128 | 129 | // ExitDefinition is called when production definition is exited. 130 | func (s *BaseXDRListener) ExitDefinition(ctx *DefinitionContext) {} 131 | 132 | // EnterSpecification is called when production specification is entered. 133 | func (s *BaseXDRListener) EnterSpecification(ctx *SpecificationContext) {} 134 | 135 | // ExitSpecification is called when production specification is exited. 136 | func (s *BaseXDRListener) ExitSpecification(ctx *SpecificationContext) {} 137 | 138 | // EnterProgramDef is called when production programDef is entered. 139 | func (s *BaseXDRListener) EnterProgramDef(ctx *ProgramDefContext) {} 140 | 141 | // ExitProgramDef is called when production programDef is exited. 142 | func (s *BaseXDRListener) ExitProgramDef(ctx *ProgramDefContext) {} 143 | 144 | // EnterVersionDef is called when production versionDef is entered. 145 | func (s *BaseXDRListener) EnterVersionDef(ctx *VersionDefContext) {} 146 | 147 | // ExitVersionDef is called when production versionDef is exited. 148 | func (s *BaseXDRListener) ExitVersionDef(ctx *VersionDefContext) {} 149 | 150 | // EnterProcedureDef is called when production procedureDef is entered. 151 | func (s *BaseXDRListener) EnterProcedureDef(ctx *ProcedureDefContext) {} 152 | 153 | // ExitProcedureDef is called when production procedureDef is exited. 154 | func (s *BaseXDRListener) ExitProcedureDef(ctx *ProcedureDefContext) {} 155 | 156 | // EnterProcReturn is called when production procReturn is entered. 157 | func (s *BaseXDRListener) EnterProcReturn(ctx *ProcReturnContext) {} 158 | 159 | // ExitProcReturn is called when production procReturn is exited. 160 | func (s *BaseXDRListener) ExitProcReturn(ctx *ProcReturnContext) {} 161 | 162 | // EnterProcFirstArg is called when production procFirstArg is entered. 163 | func (s *BaseXDRListener) EnterProcFirstArg(ctx *ProcFirstArgContext) {} 164 | 165 | // ExitProcFirstArg is called when production procFirstArg is exited. 166 | func (s *BaseXDRListener) ExitProcFirstArg(ctx *ProcFirstArgContext) {} 167 | -------------------------------------------------------------------------------- /pkg/compiler/parser/xdr_listener.go: -------------------------------------------------------------------------------- 1 | // Code generated from pkg/compiler/parser/XDR.g4 by ANTLR 4.10. DO NOT EDIT. 2 | 3 | package parser // XDR 4 | 5 | import "github.com/antlr/antlr4/runtime/Go/antlr" 6 | 7 | // XDRListener is a complete listener for a parse tree produced by XDRParser. 8 | type XDRListener interface { 9 | antlr.ParseTreeListener 10 | 11 | // EnterTopLevel is called when entering the topLevel production. 12 | EnterTopLevel(c *TopLevelContext) 13 | 14 | // EnterImports is called when entering the imports production. 15 | EnterImports(c *ImportsContext) 16 | 17 | // EnterStringConstant is called when entering the stringConstant production. 18 | EnterStringConstant(c *StringConstantContext) 19 | 20 | // EnterNamedDeclaration is called when entering the namedDeclaration production. 21 | EnterNamedDeclaration(c *NamedDeclarationContext) 22 | 23 | // EnterDeclaration is called when entering the declaration production. 24 | EnterDeclaration(c *DeclarationContext) 25 | 26 | // EnterValue is called when entering the value production. 27 | EnterValue(c *ValueContext) 28 | 29 | // EnterConstant is called when entering the constant production. 30 | EnterConstant(c *ConstantContext) 31 | 32 | // EnterTypeSpecifier is called when entering the typeSpecifier production. 33 | EnterTypeSpecifier(c *TypeSpecifierContext) 34 | 35 | // EnterEnumTypeSpec is called when entering the enumTypeSpec production. 36 | EnterEnumTypeSpec(c *EnumTypeSpecContext) 37 | 38 | // EnterEnumBody is called when entering the enumBody production. 39 | EnterEnumBody(c *EnumBodyContext) 40 | 41 | // EnterStructTypeSpec is called when entering the structTypeSpec production. 42 | EnterStructTypeSpec(c *StructTypeSpecContext) 43 | 44 | // EnterStructBody is called when entering the structBody production. 45 | EnterStructBody(c *StructBodyContext) 46 | 47 | // EnterUnionTypeSpec is called when entering the unionTypeSpec production. 48 | EnterUnionTypeSpec(c *UnionTypeSpecContext) 49 | 50 | // EnterUnionBody is called when entering the unionBody production. 51 | EnterUnionBody(c *UnionBodyContext) 52 | 53 | // EnterCaseSpec is called when entering the caseSpec production. 54 | EnterCaseSpec(c *CaseSpecContext) 55 | 56 | // EnterConstantDef is called when entering the constantDef production. 57 | EnterConstantDef(c *ConstantDefContext) 58 | 59 | // EnterTypeDef is called when entering the typeDef production. 60 | EnterTypeDef(c *TypeDefContext) 61 | 62 | // EnterDefinition is called when entering the definition production. 63 | EnterDefinition(c *DefinitionContext) 64 | 65 | // EnterSpecification is called when entering the specification production. 66 | EnterSpecification(c *SpecificationContext) 67 | 68 | // EnterProgramDef is called when entering the programDef production. 69 | EnterProgramDef(c *ProgramDefContext) 70 | 71 | // EnterVersionDef is called when entering the versionDef production. 72 | EnterVersionDef(c *VersionDefContext) 73 | 74 | // EnterProcedureDef is called when entering the procedureDef production. 75 | EnterProcedureDef(c *ProcedureDefContext) 76 | 77 | // EnterProcReturn is called when entering the procReturn production. 78 | EnterProcReturn(c *ProcReturnContext) 79 | 80 | // EnterProcFirstArg is called when entering the procFirstArg production. 81 | EnterProcFirstArg(c *ProcFirstArgContext) 82 | 83 | // ExitTopLevel is called when exiting the topLevel production. 84 | ExitTopLevel(c *TopLevelContext) 85 | 86 | // ExitImports is called when exiting the imports production. 87 | ExitImports(c *ImportsContext) 88 | 89 | // ExitStringConstant is called when exiting the stringConstant production. 90 | ExitStringConstant(c *StringConstantContext) 91 | 92 | // ExitNamedDeclaration is called when exiting the namedDeclaration production. 93 | ExitNamedDeclaration(c *NamedDeclarationContext) 94 | 95 | // ExitDeclaration is called when exiting the declaration production. 96 | ExitDeclaration(c *DeclarationContext) 97 | 98 | // ExitValue is called when exiting the value production. 99 | ExitValue(c *ValueContext) 100 | 101 | // ExitConstant is called when exiting the constant production. 102 | ExitConstant(c *ConstantContext) 103 | 104 | // ExitTypeSpecifier is called when exiting the typeSpecifier production. 105 | ExitTypeSpecifier(c *TypeSpecifierContext) 106 | 107 | // ExitEnumTypeSpec is called when exiting the enumTypeSpec production. 108 | ExitEnumTypeSpec(c *EnumTypeSpecContext) 109 | 110 | // ExitEnumBody is called when exiting the enumBody production. 111 | ExitEnumBody(c *EnumBodyContext) 112 | 113 | // ExitStructTypeSpec is called when exiting the structTypeSpec production. 114 | ExitStructTypeSpec(c *StructTypeSpecContext) 115 | 116 | // ExitStructBody is called when exiting the structBody production. 117 | ExitStructBody(c *StructBodyContext) 118 | 119 | // ExitUnionTypeSpec is called when exiting the unionTypeSpec production. 120 | ExitUnionTypeSpec(c *UnionTypeSpecContext) 121 | 122 | // ExitUnionBody is called when exiting the unionBody production. 123 | ExitUnionBody(c *UnionBodyContext) 124 | 125 | // ExitCaseSpec is called when exiting the caseSpec production. 126 | ExitCaseSpec(c *CaseSpecContext) 127 | 128 | // ExitConstantDef is called when exiting the constantDef production. 129 | ExitConstantDef(c *ConstantDefContext) 130 | 131 | // ExitTypeDef is called when exiting the typeDef production. 132 | ExitTypeDef(c *TypeDefContext) 133 | 134 | // ExitDefinition is called when exiting the definition production. 135 | ExitDefinition(c *DefinitionContext) 136 | 137 | // ExitSpecification is called when exiting the specification production. 138 | ExitSpecification(c *SpecificationContext) 139 | 140 | // ExitProgramDef is called when exiting the programDef production. 141 | ExitProgramDef(c *ProgramDefContext) 142 | 143 | // ExitVersionDef is called when exiting the versionDef production. 144 | ExitVersionDef(c *VersionDefContext) 145 | 146 | // ExitProcedureDef is called when exiting the procedureDef production. 147 | ExitProcedureDef(c *ProcedureDefContext) 148 | 149 | // ExitProcReturn is called when exiting the procReturn production. 150 | ExitProcReturn(c *ProcReturnContext) 151 | 152 | // ExitProcFirstArg is called when exiting the procFirstArg production. 153 | ExitProcFirstArg(c *ProcFirstArgContext) 154 | } 155 | -------------------------------------------------------------------------------- /pkg/compiler/tests/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buildbarn/go-xdr/236788cf9e8948a1e8875c7dd243e2be180cdfd7/pkg/compiler/tests/BUILD.bazel -------------------------------------------------------------------------------- /pkg/compiler/tests/diff.bzl: -------------------------------------------------------------------------------- 1 | def _diff_test_impl(ctx): 2 | sh = ctx.actions.declare_file(ctx.attr.name) 3 | ctx.actions.write( 4 | sh, 5 | "#!/bin/sh -e\n%s -s -extra %s > after\ndiff -u %s after\n" % ( 6 | ctx.executable._gofumpt.short_path, 7 | ctx.file.after.short_path, 8 | ctx.file.before.short_path, 9 | ), 10 | ) 11 | return [DefaultInfo( 12 | executable = sh, 13 | runfiles = ctx.runfiles(files = [ 14 | ctx.executable._gofumpt, 15 | ctx.file.before, 16 | ctx.file.after, 17 | ]), 18 | )] 19 | 20 | diff_test = rule( 21 | implementation = _diff_test_impl, 22 | attrs = { 23 | "_gofumpt": attr.label( 24 | default = "@cc_mvdan_gofumpt//:gofumpt", 25 | executable = True, 26 | cfg = "host", 27 | ), 28 | "before": attr.label(allow_single_file = True), 29 | "after": attr.label(allow_single_file = True), 30 | }, 31 | test = True, 32 | ) 33 | -------------------------------------------------------------------------------- /pkg/compiler/tests/nested/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("//pkg/compiler/tests:diff.bzl", "diff_test") 3 | load("@rules_go//go:def.bzl", "go_library") 4 | 5 | go_xdr_library( 6 | name = "nested_generated", 7 | src = "nested.x", 8 | ) 9 | 10 | diff_test( 11 | name = "diff_test", 12 | after = ":nested_generated", 13 | before = "nested.go", 14 | ) 15 | 16 | go_library( 17 | name = "nested", 18 | srcs = ["nested.go"], 19 | importpath = "github.com/buildbarn/go-xdr/pkg/compiler/tests/nested", 20 | visibility = ["//visibility:public"], 21 | deps = ["//pkg/runtime"], 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/compiler/tests/nested/nested.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-xdr. DO NOT EDIT. 2 | package nested 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | 8 | "github.com/buildbarn/go-xdr/pkg/runtime" 9 | ) 10 | 11 | // Type definition "a". 12 | 13 | type A struct { 14 | B struct { 15 | C struct { 16 | D aBCD 17 | E interface { 18 | isABCE() 19 | GetI() aBCEI 20 | io.WriterTo 21 | } 22 | } 23 | } 24 | } 25 | 26 | func (m *A) ReadFrom(r io.Reader) (nTotal int64, err error) { 27 | var nField int64 28 | { 29 | m := &m.B 30 | { 31 | m := &m.C 32 | { 33 | mSave := &m.D 34 | var m aBCD 35 | *(*int32)(&m), nField, err = runtime.ReadInt(r) 36 | nTotal += nField 37 | if err != nil { 38 | goto done 39 | } 40 | *mSave = m 41 | } 42 | { 43 | mSave := &m.E 44 | var m interface { 45 | isABCE() 46 | GetI() aBCEI 47 | io.WriterTo 48 | } 49 | var discriminant aBCEI 50 | { 51 | var m aBCEI 52 | *(*int32)(&m), nField, err = runtime.ReadInt(r) 53 | nTotal += nField 54 | if err != nil { 55 | goto done 56 | } 57 | discriminant = m 58 | } 59 | switch discriminant { 60 | case 3: 61 | var mArm ABCE_ABRA 62 | { 63 | m := &mArm 64 | { 65 | mSave := &m.J 66 | var m aBCEJ 67 | *(*int32)(&m), nField, err = runtime.ReadInt(r) 68 | nTotal += nField 69 | if err != nil { 70 | goto done 71 | } 72 | *mSave = m 73 | } 74 | } 75 | m = &mArm 76 | case 4: 77 | var mArm ABCE_CADABRA 78 | { 79 | m := &mArm 80 | { 81 | m := &m.K 82 | { 83 | mSave := &m.L 84 | var m int32 85 | m, nField, err = runtime.ReadInt(r) 86 | nTotal += nField 87 | if err != nil { 88 | goto done 89 | } 90 | *mSave = m 91 | } 92 | } 93 | } 94 | m = &mArm 95 | default: 96 | err = fmt.Errorf("discriminant a.b.c.e.i has unknown value %d", discriminant) 97 | goto done 98 | } 99 | *mSave = m 100 | } 101 | } 102 | } 103 | done: 104 | return 105 | } 106 | 107 | func (m *A) WriteTo(w io.Writer) (nTotal int64, err error) { 108 | var nField int64 109 | { 110 | m := &m.B 111 | { 112 | m := &m.C 113 | { 114 | m := m.D 115 | nField, err = runtime.WriteInt(w, int32(m)) 116 | nTotal += nField 117 | if err != nil { 118 | goto done 119 | } 120 | } 121 | { 122 | m := m.E 123 | nField, err = m.WriteTo(w) 124 | nTotal += nField 125 | if err != nil { 126 | goto done 127 | } 128 | } 129 | } 130 | } 131 | done: 132 | return 133 | } 134 | 135 | const AEncodedSizeBytes = 12 136 | 137 | type aBCD int32 138 | 139 | const BAR aBCD = 2 140 | 141 | const FOO aBCD = 1 142 | 143 | var aBCD_name = map[aBCD]string{ 144 | 2: "BAR", 145 | 1: "FOO", 146 | } 147 | 148 | type aBCEI int32 149 | 150 | func (mParent *aBCEI) ReadFrom(r io.Reader) (nTotal int64, err error) { 151 | var nField int64 152 | var m aBCEI 153 | *(*int32)(&m), nField, err = runtime.ReadInt(r) 154 | nTotal += nField 155 | if err != nil { 156 | goto done 157 | } 158 | *mParent = m 159 | done: 160 | return 161 | } 162 | 163 | func (m aBCEI) WriteTo(w io.Writer) (nTotal int64, err error) { 164 | var nField int64 165 | nField, err = runtime.WriteInt(w, int32(m)) 166 | nTotal += nField 167 | if err != nil { 168 | goto done 169 | } 170 | done: 171 | return 172 | } 173 | 174 | const aBCEIEncodedSizeBytes = 4 175 | 176 | const ABRA aBCEI = 3 177 | 178 | const CADABRA aBCEI = 4 179 | 180 | var aBCEI_name = map[aBCEI]string{ 181 | 3: "ABRA", 182 | 4: "CADABRA", 183 | } 184 | 185 | type aBCEJ int32 186 | 187 | const BOAT aBCEJ = 2 188 | 189 | const CAR aBCEJ = 1 190 | 191 | var aBCEJ_name = map[aBCEJ]string{ 192 | 2: "BOAT", 193 | 1: "CAR", 194 | } 195 | 196 | type ABCE_ABRA struct { 197 | J aBCEJ 198 | } 199 | 200 | func (m *ABCE_ABRA) isABCE() {} 201 | 202 | func (m *ABCE_ABRA) GetI() aBCEI { 203 | return 3 204 | } 205 | 206 | func (m *ABCE_ABRA) WriteTo(w io.Writer) (nTotal int64, err error) { 207 | var nField int64 208 | { 209 | var m aBCEI = 3 210 | nField, err = runtime.WriteInt(w, int32(m)) 211 | nTotal += nField 212 | if err != nil { 213 | goto done 214 | } 215 | } 216 | { 217 | m := m.J 218 | nField, err = runtime.WriteInt(w, int32(m)) 219 | nTotal += nField 220 | if err != nil { 221 | goto done 222 | } 223 | } 224 | done: 225 | return 226 | } 227 | 228 | type ABCE_CADABRA struct { 229 | K struct { 230 | L int32 231 | } 232 | } 233 | 234 | func (m *ABCE_CADABRA) isABCE() {} 235 | 236 | func (m *ABCE_CADABRA) GetI() aBCEI { 237 | return 4 238 | } 239 | 240 | func (m *ABCE_CADABRA) WriteTo(w io.Writer) (nTotal int64, err error) { 241 | var nField int64 242 | { 243 | var m aBCEI = 4 244 | nField, err = runtime.WriteInt(w, int32(m)) 245 | nTotal += nField 246 | if err != nil { 247 | goto done 248 | } 249 | } 250 | { 251 | m := &m.K 252 | { 253 | m := m.L 254 | nField, err = runtime.WriteInt(w, m) 255 | nTotal += nField 256 | if err != nil { 257 | goto done 258 | } 259 | } 260 | } 261 | done: 262 | return 263 | } 264 | 265 | // Type definition "pointer_to_enum". 266 | 267 | type PointerToEnum = *pointerToEnumImpl 268 | 269 | func ReadPointerToEnum(r io.Reader) (m *pointerToEnumImpl, nTotal int64, err error) { 270 | var nField int64 271 | var isSet bool 272 | isSet, nField, err = runtime.ReadBool(r) 273 | nTotal += nField 274 | if err != nil { 275 | goto done 276 | } 277 | if isSet { 278 | mParent := &m 279 | var m pointerToEnumImpl 280 | *(*int32)(&m), nField, err = runtime.ReadInt(r) 281 | nTotal += nField 282 | if err != nil { 283 | goto done 284 | } 285 | *mParent = &m 286 | } 287 | done: 288 | return 289 | } 290 | 291 | func WritePointerToEnum(w io.Writer, m *pointerToEnumImpl) (nTotal int64, err error) { 292 | var nField int64 293 | if m == nil { 294 | nField, err = runtime.WriteBool(w, false) 295 | nTotal += nField 296 | if err != nil { 297 | goto done 298 | } 299 | } else { 300 | nField, err = runtime.WriteBool(w, true) 301 | nTotal += nField 302 | if err != nil { 303 | goto done 304 | } 305 | { 306 | m := *m 307 | nField, err = runtime.WriteInt(w, int32(m)) 308 | nTotal += nField 309 | if err != nil { 310 | goto done 311 | } 312 | } 313 | } 314 | done: 315 | return 316 | } 317 | 318 | func GetPointerToEnumEncodedSizeBytes(m *pointerToEnumImpl) (nTotal int) { 319 | nTotal += 4 320 | if m != nil { 321 | nTotal += 4 322 | } 323 | return 324 | } 325 | 326 | type pointerToEnumImpl int32 327 | 328 | const CAT pointerToEnumImpl = 1 329 | 330 | const DOG pointerToEnumImpl = 0 331 | 332 | var pointerToEnumImpl_name = map[pointerToEnumImpl]string{ 333 | 1: "CAT", 334 | 0: "DOG", 335 | } 336 | -------------------------------------------------------------------------------- /pkg/compiler/tests/nested/nested.x: -------------------------------------------------------------------------------- 1 | package "github.com/buildbarn/go-xdr/pkg/compiler/tests/nested"; 2 | 3 | struct a { 4 | struct { 5 | struct { 6 | enum { 7 | FOO = 1, 8 | BAR = 2 9 | } d; 10 | union switch(enum { 11 | ABRA = 3, 12 | CADABRA = 4 13 | } i) { 14 | case ABRA: enum { 15 | CAR = 1, 16 | BOAT = 2 17 | } j; 18 | case CADABRA: struct { 19 | int l; 20 | } k; 21 | } e; 22 | } c; 23 | } b; 24 | }; 25 | 26 | typedef enum { 27 | DOG = 0, 28 | CAT = 1 29 | } *pointer_to_enum; 30 | -------------------------------------------------------------------------------- /pkg/compiler/tests/rfc4506/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("//pkg/compiler/tests:diff.bzl", "diff_test") 3 | load("@rules_go//go:def.bzl", "go_library", "go_test") 4 | 5 | go_xdr_library( 6 | name = "rfc4506_generated", 7 | src = "rfc4506.x", 8 | ) 9 | 10 | diff_test( 11 | name = "diff_test", 12 | after = ":rfc4506_generated", 13 | before = "rfc4506.go", 14 | ) 15 | 16 | go_library( 17 | name = "rfc4506", 18 | srcs = ["rfc4506.go"], 19 | importpath = "github.com/buildbarn/go-xdr/pkg/compiler/tests/rfc4506", 20 | visibility = ["//visibility:public"], 21 | deps = ["//pkg/runtime"], 22 | ) 23 | 24 | go_test( 25 | name = "rfc4506_test", 26 | srcs = ["rfc4506_test.go"], 27 | deps = [ 28 | ":rfc4506", 29 | "@com_github_stretchr_testify//require", 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /pkg/compiler/tests/rfc4506/rfc4506.x: -------------------------------------------------------------------------------- 1 | package "github.com/buildbarn/go-xdr/pkg/compiler/tests/rfc4506"; 2 | 3 | /* 4 | * Code from RFC 4506. 5 | */ 6 | 7 | /* Section 4.17. */ 8 | 9 | const DOZEN = 12; 10 | 11 | /* Section 4.18. */ 12 | 13 | typedef int egg; 14 | typedef egg eggbox[DOZEN]; 15 | 16 | struct eggs { 17 | eggbox fresheggs1; 18 | egg fresheggs2[DOZEN]; 19 | }; 20 | 21 | /* Section 4.19. */ 22 | 23 | struct stringentry1 { 24 | string item<>; 25 | stringentry1 *next; 26 | }; 27 | 28 | typedef stringentry1 *stringlist1; 29 | 30 | union stringlist2 switch (bool opted) { 31 | case TRUE: 32 | struct { 33 | string item<>; 34 | stringlist2 next; 35 | } element; 36 | case FALSE: 37 | void; 38 | }; 39 | 40 | struct stringentry3 { 41 | string item<>; 42 | stringentry3 next<1>; 43 | }; 44 | 45 | typedef stringentry3 stringlist3<1>; 46 | 47 | /* Chapter 7. */ 48 | 49 | const MAXUSERNAME = 32; /* max length of a user name */ 50 | const MAXFILELEN = 65535; /* max length of a file */ 51 | const MAXNAMELEN = 255; /* max length of a file name */ 52 | 53 | /* 54 | * Types of files: 55 | */ 56 | enum filekind { 57 | TEXT = 0, /* ascii data */ 58 | DATA = 1, /* raw data */ 59 | EXEC = 2 /* executable */ 60 | }; 61 | 62 | /* 63 | * File information, per kind of file: 64 | */ 65 | union filetype switch (filekind kind) { 66 | case TEXT: 67 | void; /* no extra information */ 68 | case DATA: 69 | string creator; /* data creator */ 70 | case EXEC: 71 | string interpretor; /* program interpretor */ 72 | }; 73 | 74 | /* 75 | * A complete file: 76 | */ 77 | struct file { 78 | string filename; /* name of file */ 79 | filetype type; /* info about file */ 80 | string owner; /* owner of file */ 81 | opaque data; /* file data */ 82 | }; 83 | -------------------------------------------------------------------------------- /pkg/compiler/tests/rfc4506/rfc4506_test.go: -------------------------------------------------------------------------------- 1 | package rfc4506_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/buildbarn/go-xdr/pkg/compiler/tests/rfc4506" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestRFC4506Chapter7(t *testing.T) { 12 | // Example taken from RFC 4506, chapter 7. 13 | m := rfc4506.File{ 14 | Filename: "sillyprog", 15 | Type: &rfc4506.Filetype_EXEC{ 16 | Interpretor: "lisp", 17 | }, 18 | Owner: "john", 19 | Data: []byte("(quit)"), 20 | } 21 | b := []byte{ 22 | 0x00, 0x00, 0x00, 0x09, // length of filename = 9 23 | 0x73, 0x69, 0x6c, 0x6c, // filename characters 24 | 0x79, 0x70, 0x72, 0x6f, // ... and more characters ... 25 | 0x67, 0x00, 0x00, 0x00, // ... and 3 zero-bytes of fill 26 | 0x00, 0x00, 0x00, 0x02, // filekind is EXEC = 2 27 | 0x00, 0x00, 0x00, 0x04, // length of interpretor = 4 28 | 0x6c, 0x69, 0x73, 0x70, // interpretor characters 29 | 0x00, 0x00, 0x00, 0x04, // length of owner = 4 30 | 0x6a, 0x6f, 0x68, 0x6e, // owner characters 31 | 0x00, 0x00, 0x00, 0x06, // length of file data = 6 32 | 0x28, 0x71, 0x75, 0x69, // file data bytes ... 33 | 0x74, 0x29, 0x00, 0x00, // ... and 2 zero-bytes of fill 34 | } 35 | 36 | t.Run("ReadFrom", func(t *testing.T) { 37 | var m2 rfc4506.File 38 | n, err := m2.ReadFrom(bytes.NewBuffer(b)) 39 | require.NoError(t, err) 40 | require.Equal(t, int64(len(b)), n) 41 | require.Equal(t, m, m2) 42 | }) 43 | 44 | t.Run("WriteTo", func(t *testing.T) { 45 | buf := bytes.NewBuffer(nil) 46 | n, err := m.WriteTo(buf) 47 | require.NoError(t, err) 48 | require.Equal(t, int64(len(b)), n) 49 | require.Equal(t, b, buf.Bytes()) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/protocols/darwin_nfs_sys_prot/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | go_xdr_library( 5 | name = "darwin_nfs_sys_prot_xdr", 6 | src = "darwin_nfs_sys_prot.x", 7 | deps = ["//pkg/protocols/nfsv4:nfsv4_xdr"], 8 | ) 9 | 10 | go_library( 11 | name = "darwin_nfs_sys_prot", 12 | srcs = [ 13 | ":darwin_nfs_sys_prot_xdr", # keep 14 | ], 15 | importpath = "github.com/buildbarn/go-xdr/pkg/protocols/darwin_nfs_sys_prot", # keep 16 | visibility = ["//visibility:public"], 17 | deps = [ 18 | "//pkg/runtime", # keep 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /pkg/protocols/mount/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | go_xdr_library( 5 | name = "mount_xdr", 6 | src = "mount.x", 7 | visibility = ["//visibility:public"], 8 | deps = ["//pkg/protocols/rpcv2:rpcv2_xdr"], 9 | ) 10 | 11 | go_library( 12 | name = "mount", 13 | srcs = [ 14 | ":mount_xdr", # keep 15 | ], 16 | importpath = "github.com/buildbarn/go-xdr/pkg/protocols/mount", # keep 17 | visibility = ["//visibility:public"], 18 | deps = [ 19 | "//pkg/protocols/rpcv2", # keep 20 | "//pkg/runtime", # keep 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/protocols/mount/mount.x: -------------------------------------------------------------------------------- 1 | package "github.com/buildbarn/go-xdr/pkg/protocols/mount"; 2 | 3 | /* Definitions copied from RFC 1094, appendix A. */ 4 | 5 | const FHSIZE = 32; 6 | 7 | typedef opaque fhandle[FHSIZE]; 8 | 9 | union fhstatus switch (unsigned int status) { 10 | case 0: 11 | fhandle directory; 12 | default: 13 | void; 14 | }; 15 | 16 | /* Definitions copied from RFC 1813, appendix I. */ 17 | 18 | /* 5.1.4 Sizes. */ 19 | 20 | const MNTPATHLEN = 1024; /* Maximum bytes in a path name */ 21 | const MNTNAMLEN = 255; /* Maximum bytes in a name */ 22 | const FHSIZE3 = 64; /* Maximum bytes in a V3 file handle */ 23 | 24 | /* 5.1.5 Basic Data Types. */ 25 | 26 | typedef opaque fhandle3; 27 | typedef string dirpath; 28 | typedef string name; 29 | 30 | enum mountstat3 { 31 | MNT3_OK = 0, /* no error */ 32 | MNT3ERR_PERM = 1, /* Not owner */ 33 | MNT3ERR_NOENT = 2, /* No such file or directory */ 34 | MNT3ERR_IO = 5, /* I/O error */ 35 | MNT3ERR_ACCES = 13, /* Permission denied */ 36 | MNT3ERR_NOTDIR = 20, /* Not a directory */ 37 | MNT3ERR_INVAL = 22, /* Invalid argument */ 38 | MNT3ERR_NAMETOOLONG = 63, /* Filename too long */ 39 | MNT3ERR_NOTSUPP = 10004, /* Operation not supported */ 40 | MNT3ERR_SERVERFAULT = 10006 /* A failure on the server */ 41 | }; 42 | 43 | /* 5.2 Server Procedures. */ 44 | 45 | program MOUNT_PROGRAM { 46 | version MOUNT_V1 { 47 | void MOUNTPROC_NULL(void) = 0; 48 | fhstatus MOUNTPROC_MNT(dirpath) = 1; 49 | mountbody MOUNTPROC_DUMP(void) = 2; 50 | void MOUNTPROC_UMNT(dirpath) = 3; 51 | void MOUNTPROC_UMNTALL(void) = 4; 52 | exportnode MOUNTPROC_EXPORT(void) = 5; 53 | } = 1; 54 | 55 | version MOUNT_V3 { 56 | void MOUNTPROC3_NULL(void) = 0; 57 | mountres3 MOUNTPROC3_MNT(dirpath) = 1; 58 | mountlist MOUNTPROC3_DUMP(void) = 2; 59 | void MOUNTPROC3_UMNT(dirpath) = 3; 60 | void MOUNTPROC3_UMNTALL(void) = 4; 61 | exports MOUNTPROC3_EXPORT(void) = 5; 62 | } = 3; 63 | } = 100005; 64 | 65 | /* 5.2.1 Procedure 1: MNT - Add mount entry. */ 66 | 67 | struct mountres3_ok { 68 | fhandle3 fhandle; 69 | int auth_flavors<>; 70 | }; 71 | 72 | union mountres3 switch (mountstat3 fhs_status) { 73 | case MNT3_OK: 74 | mountres3_ok mountinfo; 75 | default: 76 | void; 77 | }; 78 | 79 | /* 5.2.2 Procedure 2: DUMP - Return mount entries. */ 80 | 81 | typedef mountbody *mountlist; 82 | 83 | struct mountbody { 84 | name ml_hostname; 85 | dirpath ml_directory; 86 | mountlist ml_next; 87 | }; 88 | 89 | /* 5.2.5 Procedure 5: EXPORT - Return export list. */ 90 | 91 | typedef groupnode *groups; 92 | 93 | struct groupnode { 94 | name gr_name; 95 | groups gr_next; 96 | }; 97 | 98 | typedef exportnode *exports; 99 | 100 | struct exportnode { 101 | dirpath ex_dir; 102 | groups ex_groups; 103 | exports ex_next; 104 | }; 105 | -------------------------------------------------------------------------------- /pkg/protocols/nfsv3/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | go_xdr_library( 5 | name = "nfsv3_xdr", 6 | src = "nfsv3.x", 7 | visibility = ["//visibility:public"], 8 | deps = ["//pkg/protocols/rpcv2:rpcv2_xdr"], 9 | ) 10 | 11 | go_library( 12 | name = "nfsv3", 13 | srcs = [ 14 | ":nfsv3_xdr", # keep 15 | ], 16 | importpath = "github.com/buildbarn/go-xdr/pkg/protocols/nfsv3", # keep 17 | visibility = ["//visibility:public"], 18 | deps = [ 19 | "//pkg/protocols/rpcv2", # keep 20 | "//pkg/runtime", # keep 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/protocols/nfsv4/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | go_xdr_library( 5 | name = "nfsv4_xdr", 6 | src = "nfsv4.x", 7 | visibility = ["//visibility:public"], 8 | deps = ["//pkg/protocols/rpcv2:rpcv2_xdr"], 9 | ) 10 | 11 | go_library( 12 | name = "nfsv4", 13 | srcs = [ 14 | ":nfsv4_xdr", # keep 15 | ], 16 | importpath = "github.com/buildbarn/go-xdr/pkg/protocols/nfsv4", # keep 17 | visibility = ["//visibility:public"], 18 | deps = [ 19 | "//pkg/protocols/rpcv2", # keep 20 | "//pkg/runtime", # keep 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/protocols/nlm/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | go_xdr_library( 5 | name = "nlm_xdr", 6 | src = "nlm.x", 7 | visibility = ["//visibility:public"], 8 | deps = ["//pkg/protocols/rpcv2:rpcv2_xdr"], 9 | ) 10 | 11 | go_library( 12 | name = "nlm", 13 | srcs = [ 14 | ":nlm_xdr", # keep 15 | ], 16 | importpath = "github.com/buildbarn/go-xdr/pkg/protocols/nlm", # keep 17 | visibility = ["//visibility:public"], 18 | deps = [ 19 | "//pkg/protocols/rpcv2", # keep 20 | "//pkg/runtime", # keep 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /pkg/protocols/nlm/nlm.x: -------------------------------------------------------------------------------- 1 | package "github.com/buildbarn/go-xdr/pkg/protocols/nlm"; 2 | 3 | /* Definitions copied from RFC 1813, appendix II. */ 4 | 5 | /* 6.1.4 Basic Data Types. */ 6 | 7 | typedef unsigned hyper uint64; 8 | typedef hyper int64; 9 | typedef unsigned int uint32; 10 | typedef int int32; 11 | 12 | enum nlm4_stats { 13 | NLM4_GRANTED = 0, 14 | NLM4_DENIED = 1, 15 | NLM4_DENIED_NOLOCKS = 2, 16 | NLM4_BLOCKED = 3, 17 | NLM4_DENIED_GRACE_PERIOD = 4, 18 | NLM4_DEADLCK = 5, 19 | NLM4_ROFS = 6, 20 | NLM4_STALE_FH = 7, 21 | NLM4_FBIG = 8, 22 | NLM4_FAILED = 9 23 | }; 24 | 25 | struct nlm4_holder { 26 | bool exclusive; 27 | int32 svid; 28 | netobj oh; 29 | uint64 l_offset; 30 | uint64 l_len; 31 | }; 32 | 33 | struct nlm4_lock { 34 | string caller_name; 35 | netobj fh; 36 | netobj oh; 37 | int32 svid; 38 | uint64 l_offset; 39 | uint64 l_len; 40 | }; 41 | 42 | struct nlm4_share { 43 | string caller_name; 44 | netobj fh; 45 | netobj oh; 46 | fsh4_mode mode; 47 | fsh4_access access; 48 | }; 49 | 50 | /* 6.2 NLM Procedures. */ 51 | 52 | program NLM_PROG { 53 | version NLM4_VERS { 54 | void 55 | NLMPROC4_NULL(void) = 0; 56 | 57 | nlm4_testres 58 | NLMPROC4_TEST(nlm4_testargs) = 1; 59 | 60 | nlm4_res 61 | NLMPROC4_LOCK(nlm4_lockargs) = 2; 62 | 63 | nlm4_res 64 | NLMPROC4_CANCEL(nlm4_cancargs) = 3; 65 | 66 | nlm4_res 67 | NLMPROC4_UNLOCK(nlm4_unlockargs) = 4; 68 | 69 | nlm4_res 70 | NLMPROC4_GRANTED(nlm4_testargs) = 5; 71 | 72 | void 73 | NLMPROC4_TEST_MSG(nlm4_testargs) = 6; 74 | 75 | void 76 | NLMPROC4_LOCK_MSG(nlm4_lockargs) = 7; 77 | 78 | void 79 | NLMPROC4_CANCEL_MSG(nlm4_cancargs) = 8; 80 | 81 | void 82 | NLMPROC4_UNLOCK_MSG(nlm4_unlockargs) = 9; 83 | 84 | void 85 | NLMPROC4_GRANTED_MSG(nlm4_testargs) = 10; 86 | 87 | void 88 | NLMPROC4_TEST_RES(nlm4_testres) = 11; 89 | 90 | void 91 | NLMPROC4_LOCK_RES(nlm4_res) = 12; 92 | 93 | void 94 | NLMPROC4_CANCEL_RES(nlm4_res) = 13; 95 | 96 | void 97 | NLMPROC4_UNLOCK_RES(nlm4_res) = 14; 98 | 99 | void 100 | NLMPROC4_GRANTED_RES(nlm4_res) = 15; 101 | 102 | nlm4_shareres 103 | NLMPROC4_SHARE(nlm4_shareargs) = 20; 104 | 105 | nlm4_shareres 106 | NLMPROC4_UNSHARE(nlm4_shareargs) = 21; 107 | 108 | nlm4_res 109 | NLMPROC4_NM_LOCK(nlm4_lockargs) = 22; 110 | 111 | void 112 | NLMPROC4_FREE_ALL(nlm4_notify) = 23; 113 | 114 | } = 4; 115 | } = 100021; 116 | 117 | /* https://pubs.opengroup.org/onlinepubs/9629799/chap10.htm */ 118 | 119 | const LM_MAXSTRLEN = 1024; 120 | const LM_MAXNAMELEN = 1025; 121 | const MAXNETOBJ_SZ = 1024; 122 | 123 | typedef opaque netobj; 124 | 125 | struct nlm4_stat { 126 | nlm4_stats stat; 127 | }; 128 | 129 | struct nlm4_res { 130 | netobj cookie; 131 | nlm4_stat stat; 132 | }; 133 | 134 | union nlm4_testrply switch (nlm4_stats stat) { 135 | case NLM4_DENIED: 136 | nlm4_holder holder; /* holder of the lock */ 137 | default: 138 | void; 139 | }; 140 | 141 | struct nlm4_testres { 142 | netobj cookie; 143 | nlm4_testrply test_stat; 144 | }; 145 | 146 | struct nlm4_lockargs { 147 | netobj cookie; 148 | bool block; /* Flag to indicate blocking behaviour. */ 149 | bool exclusive; /* If exclusive access is desired. */ 150 | nlm4_lock alock; /* The actual lock data (see above) */ 151 | bool reclaim; /* used for recovering locks */ 152 | int state; /* specify local NSM state */ 153 | }; 154 | 155 | struct nlm4_cancargs { 156 | netobj cookie; 157 | bool block; 158 | bool exclusive; 159 | nlm4_lock alock; 160 | }; 161 | 162 | struct nlm4_testargs { 163 | netobj cookie; 164 | bool exclusive; 165 | nlm4_lock alock; 166 | }; 167 | 168 | struct nlm4_unlockargs { 169 | netobj cookie; 170 | nlm4_lock alock; 171 | }; 172 | 173 | enum fsh4_mode { 174 | fsm_DN = 0, /* deny none */ 175 | fsm_DR = 1, /* deny read */ 176 | fsm_DW = 2, /* deny write */ 177 | fsm_DRW = 3 /* deny read/write */ 178 | }; 179 | 180 | enum fsh4_access { 181 | fsa_NONE = 0, /* for completeness */ 182 | fsa_R = 1, /* read-only */ 183 | fsa_W = 2, /* write-only */ 184 | fsa_RW = 3 /* read/write */ 185 | }; 186 | 187 | struct nlm4_shareargs { 188 | netobj cookie; 189 | nlm4_share share; /* actual share data */ 190 | bool reclaim; /* used for recovering shares */ 191 | }; 192 | 193 | struct nlm4_shareres { 194 | netobj cookie; 195 | nlm4_stats stat; 196 | int sequence; 197 | }; 198 | 199 | struct nlm4_notify { 200 | string name; 201 | int state; 202 | }; 203 | -------------------------------------------------------------------------------- /pkg/protocols/rpcv2/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//:xdr.bzl", "go_xdr_library") 2 | load("@rules_go//go:def.bzl", "go_library") 3 | 4 | go_xdr_library( 5 | name = "rpcv2_xdr", 6 | src = "rpcv2.x", 7 | visibility = ["//visibility:public"], 8 | ) 9 | 10 | go_library( 11 | name = "rpcv2", 12 | srcs = [ 13 | ":rpcv2_xdr", # keep 14 | ], 15 | importpath = "github.com/buildbarn/go-xdr/pkg/protocols/rpcv2", # keep 16 | visibility = ["//visibility:public"], 17 | deps = [ 18 | "//pkg/runtime", # keep 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /pkg/protocols/rpcv2/rpcv2.x: -------------------------------------------------------------------------------- 1 | package "github.com/buildbarn/go-xdr/pkg/protocols/rpcv2"; 2 | 3 | /* Definitions copied from RFC 5531. */ 4 | 5 | enum auth_flavor { 6 | AUTH_NONE = 0, 7 | AUTH_SYS = 1, 8 | AUTH_SHORT = 2, 9 | AUTH_DH = 3, 10 | RPCSEC_GSS = 6 11 | /* and more to be defined */ 12 | }; 13 | 14 | struct opaque_auth { 15 | auth_flavor flavor; 16 | opaque body<400>; 17 | }; 18 | 19 | enum msg_type { 20 | CALL = 0, 21 | REPLY = 1 22 | }; 23 | 24 | enum reply_stat { 25 | MSG_ACCEPTED = 0, 26 | MSG_DENIED = 1 27 | }; 28 | 29 | enum accept_stat { 30 | SUCCESS = 0, /* RPC executed successfully */ 31 | PROG_UNAVAIL = 1, /* remote hasn't exported program */ 32 | PROG_MISMATCH = 2, /* remote can't support version # */ 33 | PROC_UNAVAIL = 3, /* program can't support procedure */ 34 | GARBAGE_ARGS = 4, /* procedure can't decode params */ 35 | SYSTEM_ERR = 5 /* e.g. memory allocation failure */ 36 | }; 37 | 38 | enum reject_stat { 39 | RPC_MISMATCH = 0, /* RPC version number != 2 */ 40 | AUTH_ERROR = 1 /* remote can't authenticate caller */ 41 | }; 42 | 43 | enum auth_stat { 44 | AUTH_OK = 0, /* success */ 45 | /* 46 | * failed at remote end 47 | */ 48 | AUTH_BADCRED = 1, /* bad credential (seal broken) */ 49 | AUTH_REJECTEDCRED = 2, /* client must begin new session */ 50 | AUTH_BADVERF = 3, /* bad verifier (seal broken) */ 51 | AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */ 52 | AUTH_TOOWEAK = 5, /* rejected for security reasons */ 53 | /* 54 | * failed locally 55 | */ 56 | AUTH_INVALIDRESP = 6, /* bogus response verifier */ 57 | AUTH_FAILED = 7, /* reason unknown */ 58 | /* 59 | * AUTH_KERB errors; deprecated. See [RFC2695] 60 | */ 61 | AUTH_KERB_GENERIC = 8, /* kerberos generic error */ 62 | AUTH_TIMEEXPIRE = 9, /* time of credential expired */ 63 | AUTH_TKT_FILE = 10, /* problem with ticket file */ 64 | AUTH_DECODE = 11, /* can't decode authenticator */ 65 | AUTH_NET_ADDR = 12, /* wrong net address in ticket */ 66 | /* 67 | * RPCSEC_GSS GSS related errors 68 | */ 69 | RPCSEC_GSS_CREDPROBLEM = 13, /* no credentials for user */ 70 | RPCSEC_GSS_CTXPROBLEM = 14 /* problem with context */ 71 | }; 72 | 73 | struct rpc_msg { 74 | unsigned int xid; 75 | union switch (msg_type mtype) { 76 | case CALL: 77 | call_body cbody; 78 | case REPLY: 79 | reply_body rbody; 80 | } body; 81 | }; 82 | 83 | struct call_body { 84 | unsigned int rpcvers; /* must be equal to two (2) */ 85 | unsigned int prog; 86 | unsigned int vers; 87 | unsigned int proc; 88 | opaque_auth cred; 89 | opaque_auth verf; 90 | /* procedure-specific parameters start here */ 91 | }; 92 | 93 | union reply_body switch (reply_stat stat) { 94 | case MSG_ACCEPTED: 95 | accepted_reply areply; 96 | case MSG_DENIED: 97 | rejected_reply rreply; 98 | }; 99 | 100 | struct accepted_reply { 101 | opaque_auth verf; 102 | accepted_reply_data reply_data; 103 | }; 104 | 105 | union accepted_reply_data switch (accept_stat stat) { 106 | case SUCCESS: 107 | opaque results[0]; 108 | /* 109 | * procedure-specific results start here 110 | */ 111 | case PROG_MISMATCH: 112 | struct { 113 | unsigned int low; 114 | unsigned int high; 115 | } mismatch_info; 116 | default: 117 | /* 118 | * Void. Cases include PROG_UNAVAIL, PROC_UNAVAIL, 119 | * GARBAGE_ARGS, and SYSTEM_ERR. 120 | */ 121 | void; 122 | }; 123 | 124 | union rejected_reply switch (reject_stat stat) { 125 | case RPC_MISMATCH: 126 | struct { 127 | unsigned int low; 128 | unsigned int high; 129 | } mismatch_info; 130 | case AUTH_ERROR: 131 | auth_stat stat; 132 | }; 133 | 134 | struct authsys_parms { 135 | unsigned int stamp; 136 | string machinename<255>; 137 | unsigned int uid; 138 | unsigned int gid; 139 | unsigned int gids<16>; 140 | }; 141 | -------------------------------------------------------------------------------- /pkg/rpcserver/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "rpcserver", 5 | srcs = [ 6 | "allow_authenticator.go", 7 | "authenticator.go", 8 | "server.go", 9 | ], 10 | importpath = "github.com/buildbarn/go-xdr/pkg/rpcserver", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//pkg/protocols/rpcv2", 14 | "@org_golang_x_sync//errgroup", 15 | ], 16 | ) 17 | 18 | go_test( 19 | name = "rpcserver_test", 20 | srcs = ["server_test.go"], 21 | deps = [ 22 | ":rpcserver", 23 | "//internal/mock", 24 | "//pkg/protocols/rpcv2", 25 | "@com_github_golang_mock//gomock", 26 | "@com_github_stretchr_testify//require", 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /pkg/rpcserver/allow_authenticator.go: -------------------------------------------------------------------------------- 1 | package rpcserver 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/buildbarn/go-xdr/pkg/protocols/rpcv2" 7 | ) 8 | 9 | type allowAuthenticator struct{} 10 | 11 | func (allowAuthenticator) Authenticate(ctx context.Context, credentials, verifier *rpcv2.OpaqueAuth) (context.Context, rpcv2.OpaqueAuth, rpcv2.AuthStat) { 12 | return ctx, rpcv2.OpaqueAuth{Flavor: rpcv2.AUTH_NONE}, rpcv2.AUTH_OK 13 | } 14 | 15 | // AllowAuthenticator is an implementation of Authenticator that permits 16 | // the execution of all requests, regardless of the credentials and 17 | // verifier they provide. 18 | var AllowAuthenticator Authenticator = allowAuthenticator{} 19 | -------------------------------------------------------------------------------- /pkg/rpcserver/authenticator.go: -------------------------------------------------------------------------------- 1 | package rpcserver 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/buildbarn/go-xdr/pkg/protocols/rpcv2" 7 | ) 8 | 9 | // Authenticator of requests against an RPC server. 10 | type Authenticator interface { 11 | Authenticate(ctx context.Context, credentials, verifier *rpcv2.OpaqueAuth) (context.Context, rpcv2.OpaqueAuth, rpcv2.AuthStat) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/runtime/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "runtime", 5 | srcs = [ 6 | "ascii_string.go", 7 | "bool.go", 8 | "integer.go", 9 | "opaque.go", 10 | "utf8_string.go", 11 | ], 12 | importpath = "github.com/buildbarn/go-xdr/pkg/runtime", 13 | visibility = ["//visibility:public"], 14 | ) 15 | 16 | go_test( 17 | name = "runtime_test", 18 | srcs = [ 19 | "ascii_string_test.go", 20 | "bool_test.go", 21 | "integer_test.go", 22 | "opaque_test.go", 23 | "utf8_string_test.go", 24 | ], 25 | deps = [ 26 | ":runtime", 27 | "//internal/mock", 28 | "@com_github_golang_mock//gomock", 29 | "@com_github_stretchr_testify//require", 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /pkg/runtime/ascii_string.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | func validateASCII(s string) error { 11 | for i := 0; i < len(s); i++ { 12 | if c := s[i]; c > unicode.MaxASCII { 13 | return fmt.Errorf("character at offset %d has non-ASCII value %#x", i, c) 14 | } 15 | } 16 | return nil 17 | } 18 | 19 | // ReadASCIIString reads an XDR encoded ASCII string value. 20 | func ReadASCIIString(r io.Reader, maximumSizeBytes uint32) (string, int64, error) { 21 | var builder strings.Builder 22 | n, err := copyVariableLengthOpaque(r, &builder, maximumSizeBytes) 23 | if err != nil { 24 | return "", n, err 25 | } 26 | s := builder.String() 27 | if err := validateASCII(s); err != nil { 28 | return "", n, err 29 | } 30 | return s, n, err 31 | } 32 | 33 | // WriteASCIIString writes an XDR encoded ASCII string value. 34 | func WriteASCIIString(w io.Writer, maximumSizeBytes uint32, s string) (int64, error) { 35 | if err := validateASCII(s); err != nil { 36 | return 0, err 37 | } 38 | return WriteVariableLengthOpaque(w, maximumSizeBytes, []byte(s)) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/runtime/ascii_string_test.go: -------------------------------------------------------------------------------- 1 | package runtime_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/buildbarn/go-xdr/internal/mock" 9 | "github.com/buildbarn/go-xdr/pkg/runtime" 10 | "github.com/golang/mock/gomock" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestReadASCIIString(t *testing.T) { 15 | ctrl := gomock.NewController(t) 16 | 17 | t.Run("IOFailure", func(t *testing.T) { 18 | r := mock.NewMockReader(ctrl) 19 | r.EXPECT().Read(gomock.Any()).Return(0, errors.New("disk failure")) 20 | 21 | _, n, err := runtime.ReadASCIIString(r, 10) 22 | require.Equal(t, errors.New("disk failure"), err) 23 | require.Equal(t, int64(0), n) 24 | }) 25 | 26 | t.Run("InvalidCharacters", func(t *testing.T) { 27 | _, n, err := runtime.ReadASCIIString( 28 | bytes.NewBuffer([]byte{0, 0, 0, 5, 0x48, 0x65, 0x8a, 0x6c, 0x6f, 0, 0, 0}), 29 | 10) 30 | require.Equal(t, errors.New("character at offset 2 has non-ASCII value 0x8a"), err) 31 | require.Equal(t, int64(12), n) 32 | }) 33 | 34 | t.Run("Success", func(t *testing.T) { 35 | s, n, err := runtime.ReadASCIIString( 36 | bytes.NewBuffer([]byte{0, 0, 0, 5, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0, 0, 0}), 37 | 10) 38 | require.NoError(t, err) 39 | require.Equal(t, int64(12), n) 40 | require.Equal(t, "Hello", s) 41 | }) 42 | } 43 | 44 | func TestWriteASCIIString(t *testing.T) { 45 | ctrl := gomock.NewController(t) 46 | 47 | t.Run("IOFailure", func(t *testing.T) { 48 | w := mock.NewMockWriter(ctrl) 49 | w.EXPECT().Write(gomock.Any()).Return(0, errors.New("disk failure")) 50 | 51 | n, err := runtime.WriteASCIIString(w, 10, "Hello") 52 | require.Equal(t, errors.New("disk failure"), err) 53 | require.Equal(t, int64(0), n) 54 | }) 55 | 56 | t.Run("InvalidCharacters", func(t *testing.T) { 57 | b := bytes.NewBuffer(nil) 58 | n, err := runtime.WriteASCIIString(b, 30, "Hello, 世界") 59 | require.Equal(t, errors.New("character at offset 7 has non-ASCII value 0xe4"), err) 60 | require.Equal(t, int64(0), n) 61 | }) 62 | 63 | t.Run("Success", func(t *testing.T) { 64 | b := bytes.NewBuffer(nil) 65 | n, err := runtime.WriteASCIIString(b, 10, "Hello") 66 | require.NoError(t, err) 67 | require.Equal(t, int64(12), n) 68 | require.Equal(t, []byte{0, 0, 0, 5, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0, 0, 0}, b.Bytes()) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/runtime/bool.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | ) 7 | 8 | var ( 9 | marshaledFalse = [4]byte{0, 0, 0, 0} 10 | marshaledTrue = [4]byte{0, 0, 0, 1} 11 | ) 12 | 13 | var errInvalidBoolean = errors.New("boolean value not zero or one") 14 | 15 | // ReadBool reads an XDR encoded boolean value. 16 | func ReadBool(r io.Reader) (bool, int64, error) { 17 | var b [4]byte 18 | n, err := io.ReadFull(r, b[:]) 19 | if err != nil { 20 | return false, int64(n), err 21 | } 22 | switch b { 23 | case marshaledFalse: 24 | return false, int64(n), nil 25 | case marshaledTrue: 26 | return true, int64(n), nil 27 | default: 28 | return false, int64(n), errInvalidBoolean 29 | } 30 | } 31 | 32 | // WriteBool writes an XDR encoded boolean value. 33 | func WriteBool(w io.Writer, v bool) (int64, error) { 34 | if v { 35 | n, err := w.Write(marshaledTrue[:]) 36 | return int64(n), err 37 | } 38 | n, err := w.Write(marshaledFalse[:]) 39 | return int64(n), err 40 | } 41 | -------------------------------------------------------------------------------- /pkg/runtime/bool_test.go: -------------------------------------------------------------------------------- 1 | package runtime_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "testing" 8 | 9 | "github.com/buildbarn/go-xdr/internal/mock" 10 | "github.com/buildbarn/go-xdr/pkg/runtime" 11 | "github.com/golang/mock/gomock" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestReadBool(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | 18 | t.Run("IOFailure", func(t *testing.T) { 19 | r := mock.NewMockReader(ctrl) 20 | r.EXPECT().Read(gomock.Any()).Return(0, errors.New("disk failure")) 21 | 22 | _, n, err := runtime.ReadBool(r) 23 | require.Equal(t, errors.New("disk failure"), err) 24 | require.Equal(t, int64(0), n) 25 | }) 26 | 27 | t.Run("EOF", func(t *testing.T) { 28 | _, n, err := runtime.ReadBool(bytes.NewBuffer(nil)) 29 | require.Equal(t, io.EOF, err) 30 | require.Equal(t, int64(0), n) 31 | }) 32 | 33 | t.Run("UnexpectedEOF", func(t *testing.T) { 34 | input := [...]byte{1, 2, 3} 35 | for i := 1; i <= len(input); i++ { 36 | _, n, err := runtime.ReadBool(bytes.NewBuffer(input[:i])) 37 | require.Equal(t, io.ErrUnexpectedEOF, err) 38 | require.Equal(t, int64(i), n) 39 | } 40 | }) 41 | 42 | t.Run("InvalidValue", func(t *testing.T) { 43 | _, n, err := runtime.ReadBool(bytes.NewBuffer([]byte{0, 0, 0, 2})) 44 | require.Equal(t, errors.New("boolean value not zero or one"), err) 45 | require.Equal(t, int64(4), n) 46 | }) 47 | 48 | t.Run("False", func(t *testing.T) { 49 | v, n, err := runtime.ReadBool(bytes.NewBuffer([]byte{0, 0, 0, 0})) 50 | require.NoError(t, err) 51 | require.Equal(t, int64(4), n) 52 | require.False(t, v) 53 | }) 54 | 55 | t.Run("True", func(t *testing.T) { 56 | v, n, err := runtime.ReadBool(bytes.NewBuffer([]byte{0, 0, 0, 1})) 57 | require.NoError(t, err) 58 | require.Equal(t, int64(4), n) 59 | require.True(t, v) 60 | }) 61 | } 62 | 63 | func TestWriteBool(t *testing.T) { 64 | ctrl := gomock.NewController(t) 65 | 66 | t.Run("IOFailure", func(t *testing.T) { 67 | w := mock.NewMockWriter(ctrl) 68 | w.EXPECT().Write(gomock.Any()).Return(0, errors.New("disk failure")) 69 | 70 | n, err := runtime.WriteBool(w, true) 71 | require.Equal(t, errors.New("disk failure"), err) 72 | require.Equal(t, int64(0), n) 73 | }) 74 | 75 | t.Run("False", func(t *testing.T) { 76 | b := bytes.NewBuffer(nil) 77 | n, err := runtime.WriteBool(b, false) 78 | require.NoError(t, err) 79 | require.Equal(t, int64(4), n) 80 | require.Equal(t, []byte{0, 0, 0, 0}, b.Bytes()) 81 | }) 82 | 83 | t.Run("True", func(t *testing.T) { 84 | b := bytes.NewBuffer(nil) 85 | n, err := runtime.WriteBool(b, true) 86 | require.NoError(t, err) 87 | require.Equal(t, int64(4), n) 88 | require.Equal(t, []byte{0, 0, 0, 1}, b.Bytes()) 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/runtime/integer.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // ReadInt reads an XDR encoded 32-bit signed integer value. 9 | func ReadInt(r io.Reader) (int32, int64, error) { 10 | var b [4]byte 11 | n, err := io.ReadFull(r, b[:]) 12 | return int32(binary.BigEndian.Uint32(b[:])), int64(n), err 13 | } 14 | 15 | // WriteInt writes an XDR encoded 32-bit signed integer value. 16 | func WriteInt(w io.Writer, i int32) (int64, error) { 17 | var b [4]byte 18 | binary.BigEndian.PutUint32(b[:], uint32(i)) 19 | n, err := w.Write(b[:]) 20 | return int64(n), err 21 | } 22 | 23 | // ReadUnsignedInt reads an XDR encoded 32-bit unsigned integer value. 24 | func ReadUnsignedInt(r io.Reader) (uint32, int64, error) { 25 | var b [4]byte 26 | n, err := io.ReadFull(r, b[:]) 27 | return binary.BigEndian.Uint32(b[:]), int64(n), err 28 | } 29 | 30 | // WriteUnsignedInt writes an XDR encoded 32-bit unsigned integer value. 31 | func WriteUnsignedInt(w io.Writer, i uint32) (int64, error) { 32 | var b [4]byte 33 | binary.BigEndian.PutUint32(b[:], i) 34 | n, err := w.Write(b[:]) 35 | return int64(n), err 36 | } 37 | 38 | // ReadHyper reads an XDR encoded 64-bit signed integer value. 39 | func ReadHyper(r io.Reader) (int64, int64, error) { 40 | var b [8]byte 41 | n, err := io.ReadFull(r, b[:]) 42 | return int64(binary.BigEndian.Uint64(b[:])), int64(n), err 43 | } 44 | 45 | // WriteHyper writes an XDR encoded 64-bit signed integer value. 46 | func WriteHyper(w io.Writer, i int64) (int64, error) { 47 | var b [8]byte 48 | binary.BigEndian.PutUint64(b[:], uint64(i)) 49 | n, err := w.Write(b[:]) 50 | return int64(n), err 51 | } 52 | 53 | // ReadUnsignedHyper reads an XDR encoded 64-bit unsigned integer value. 54 | func ReadUnsignedHyper(r io.Reader) (uint64, int64, error) { 55 | var b [8]byte 56 | n, err := io.ReadFull(r, b[:]) 57 | return binary.BigEndian.Uint64(b[:]), int64(n), err 58 | } 59 | 60 | // WriteUnsignedHyper writes an XDR encoded 64-bit unsigned integer value. 61 | func WriteUnsignedHyper(w io.Writer, i uint64) (int64, error) { 62 | var b [8]byte 63 | binary.BigEndian.PutUint64(b[:], i) 64 | n, err := w.Write(b[:]) 65 | return int64(n), err 66 | } 67 | -------------------------------------------------------------------------------- /pkg/runtime/integer_test.go: -------------------------------------------------------------------------------- 1 | package runtime_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "testing" 8 | 9 | "github.com/buildbarn/go-xdr/internal/mock" 10 | "github.com/buildbarn/go-xdr/pkg/runtime" 11 | "github.com/golang/mock/gomock" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestReadInt(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | 18 | t.Run("IOFailure", func(t *testing.T) { 19 | r := mock.NewMockReader(ctrl) 20 | r.EXPECT().Read(gomock.Any()).Return(0, errors.New("disk failure")) 21 | 22 | _, n, err := runtime.ReadInt(r) 23 | require.Equal(t, errors.New("disk failure"), err) 24 | require.Equal(t, int64(0), n) 25 | }) 26 | 27 | t.Run("EOF", func(t *testing.T) { 28 | _, n, err := runtime.ReadInt(bytes.NewBuffer(nil)) 29 | require.Equal(t, io.EOF, err) 30 | require.Equal(t, int64(0), n) 31 | }) 32 | 33 | t.Run("UnexpectedEOF", func(t *testing.T) { 34 | input := [...]byte{1, 2, 3} 35 | for i := 1; i <= len(input); i++ { 36 | _, n, err := runtime.ReadInt(bytes.NewBuffer(input[:i])) 37 | require.Equal(t, io.ErrUnexpectedEOF, err) 38 | require.Equal(t, int64(i), n) 39 | } 40 | }) 41 | 42 | t.Run("Success", func(t *testing.T) { 43 | v, n, err := runtime.ReadInt(bytes.NewBuffer([]byte{0x2d, 0x11, 0x8b, 0xa4})) 44 | require.NoError(t, err) 45 | require.Equal(t, int64(4), n) 46 | require.Equal(t, int32(0x2d118ba4), v) 47 | }) 48 | } 49 | 50 | func TestWriteInt(t *testing.T) { 51 | ctrl := gomock.NewController(t) 52 | 53 | t.Run("IOFailure", func(t *testing.T) { 54 | w := mock.NewMockWriter(ctrl) 55 | w.EXPECT().Write(gomock.Any()).Return(0, errors.New("disk failure")) 56 | 57 | n, err := runtime.WriteInt(w, 0x6e3895ee) 58 | require.Equal(t, errors.New("disk failure"), err) 59 | require.Equal(t, int64(0), n) 60 | }) 61 | 62 | t.Run("Success", func(t *testing.T) { 63 | b := bytes.NewBuffer(nil) 64 | n, err := runtime.WriteInt(b, 0x7475cd45) 65 | require.NoError(t, err) 66 | require.Equal(t, int64(4), n) 67 | require.Equal(t, []byte{0x74, 0x75, 0xcd, 0x45}, b.Bytes()) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/runtime/opaque.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | func readPadding(r io.Reader, nTotal int64) (int64, error) { 10 | paddingToSkip := nTotal % 4 11 | if paddingToSkip == 0 { 12 | return nTotal, nil 13 | } 14 | 15 | var padding [4]byte 16 | paddingSlice := padding[paddingToSkip:] 17 | nPadding, err := io.ReadFull(r, paddingSlice) 18 | nTotal += int64(nPadding) 19 | if err != nil { 20 | if err == io.EOF { 21 | return nTotal, io.ErrUnexpectedEOF 22 | } 23 | return nTotal, err 24 | } 25 | if padding != [4]byte{} { 26 | return nTotal, fmt.Errorf("padding contains non-zero values %v", paddingSlice) 27 | } 28 | return nTotal, nil 29 | } 30 | 31 | var zeroPadding [4]byte 32 | 33 | func writePadding(w io.Writer, n int64) (int64, error) { 34 | paddingToSkip := n % 4 35 | if paddingToSkip == 0 { 36 | return n, nil 37 | } 38 | nPadding, err := w.Write(zeroPadding[paddingToSkip:]) 39 | return n + int64(nPadding), err 40 | } 41 | 42 | // ReadFixedLengthOpaque reads an XDR encoded fixed length opaque value. 43 | func ReadFixedLengthOpaque(r io.Reader, b []byte) (int64, error) { 44 | nData, err := io.ReadFull(r, b) 45 | if err != nil { 46 | return int64(nData), err 47 | } 48 | return readPadding(r, int64(nData)) 49 | } 50 | 51 | // WriteFixedLengthOpaque writes an XDR encoded fixed length opaque value. 52 | func WriteFixedLengthOpaque(w io.Writer, b []byte) (int64, error) { 53 | nData, err := w.Write(b) 54 | if err != nil { 55 | return int64(nData), err 56 | } 57 | return writePadding(w, int64(nData)) 58 | } 59 | 60 | func copyVariableLengthOpaque(r io.Reader, w io.Writer, maximumSizeBytes uint32) (int64, error) { 61 | length, nTotal, err := ReadUnsignedInt(r) 62 | if err != nil || length == 0 { 63 | return nTotal, err 64 | } 65 | if length > maximumSizeBytes { 66 | return nTotal, fmt.Errorf("size of %d bytes exceeds field's maximum of %d bytes", length, maximumSizeBytes) 67 | } 68 | 69 | nCopied, err := io.CopyN(w, r, int64(length)) 70 | nTotal += nCopied 71 | if err != nil { 72 | if err == io.EOF { 73 | return nTotal, io.ErrUnexpectedEOF 74 | } 75 | return nTotal, err 76 | } 77 | return readPadding(r, nTotal) 78 | } 79 | 80 | // ReadVariableLengthOpaque reads an XDR encoded variable length opaque value. 81 | func ReadVariableLengthOpaque(r io.Reader, maximumSizeBytes uint32) ([]byte, int64, error) { 82 | b := bytes.NewBuffer(nil) 83 | n, err := copyVariableLengthOpaque(r, b, maximumSizeBytes) 84 | if err != nil { 85 | return nil, n, err 86 | } 87 | return b.Bytes(), n, nil 88 | } 89 | 90 | // WriteVariableLengthOpaque writes an XDR encoded variable length opaque value. 91 | func WriteVariableLengthOpaque(w io.Writer, maximumSizeBytes uint32, b []byte) (int64, error) { 92 | if uint64(len(b)) > uint64(maximumSizeBytes) { 93 | return 0, fmt.Errorf("size of %d bytes exceeds field's maximum of %d bytes", len(b), maximumSizeBytes) 94 | } 95 | 96 | nTotal, err := WriteUnsignedInt(w, uint32(len(b))) 97 | if err != nil { 98 | return nTotal, err 99 | } 100 | 101 | nString, err := w.Write(b) 102 | nTotal += int64(nString) 103 | if err != nil { 104 | return nTotal, err 105 | } 106 | return writePadding(w, nTotal) 107 | } 108 | -------------------------------------------------------------------------------- /pkg/runtime/opaque_test.go: -------------------------------------------------------------------------------- 1 | package runtime_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "testing" 8 | 9 | "github.com/buildbarn/go-xdr/internal/mock" 10 | "github.com/buildbarn/go-xdr/pkg/runtime" 11 | "github.com/golang/mock/gomock" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestReadFixedLengthOpaque(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | 18 | t.Run("IOFailure", func(t *testing.T) { 19 | r := mock.NewMockReader(ctrl) 20 | r.EXPECT().Read(gomock.Any()).Return(0, errors.New("disk failure")) 21 | 22 | var b [10]byte 23 | n, err := runtime.ReadFixedLengthOpaque(r, b[:]) 24 | require.Equal(t, errors.New("disk failure"), err) 25 | require.Equal(t, int64(0), n) 26 | }) 27 | 28 | t.Run("EOF", func(t *testing.T) { 29 | var b [10]byte 30 | n, err := runtime.ReadFixedLengthOpaque(bytes.NewBuffer(nil), b[:]) 31 | require.Equal(t, io.EOF, err) 32 | require.Equal(t, int64(0), n) 33 | }) 34 | 35 | t.Run("UnexpectedEOF", func(t *testing.T) { 36 | input := [...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} 37 | for i := 1; i <= len(input); i++ { 38 | var b [10]byte 39 | n, err := runtime.ReadFixedLengthOpaque(bytes.NewBuffer(input[:i]), b[:]) 40 | require.Equal(t, io.ErrUnexpectedEOF, err) 41 | require.Equal(t, int64(i), n) 42 | } 43 | }) 44 | 45 | t.Run("InvalidPadding", func(t *testing.T) { 46 | var b [2]byte 47 | n, err := runtime.ReadFixedLengthOpaque(bytes.NewBuffer([]byte{5, 2, 3, 7}), b[:]) 48 | require.Equal(t, errors.New("padding contains non-zero values [3 7]"), err) 49 | require.Equal(t, int64(4), n) 50 | }) 51 | 52 | t.Run("SuccessPadding0Bytes", func(t *testing.T) { 53 | var b [4]byte 54 | n, err := runtime.ReadFixedLengthOpaque( 55 | bytes.NewBuffer([]byte{0x2d, 0x11, 0x8b, 0xa4}), 56 | b[:]) 57 | require.NoError(t, err) 58 | require.Equal(t, int64(4), n) 59 | require.Equal(t, [...]byte{0x2d, 0x11, 0x8b, 0xa4}, b) 60 | }) 61 | 62 | t.Run("SuccessPadding1Byte", func(t *testing.T) { 63 | var b [5]byte 64 | n, err := runtime.ReadFixedLengthOpaque( 65 | bytes.NewBuffer([]byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x00, 0x00, 0x00}), 66 | b[:]) 67 | require.NoError(t, err) 68 | require.Equal(t, int64(8), n) 69 | require.Equal(t, [...]byte{0x2d, 0x11, 0x8b, 0xa4, 0x72}, b) 70 | }) 71 | 72 | t.Run("SuccessPadding2Bytes", func(t *testing.T) { 73 | var b [6]byte 74 | n, err := runtime.ReadFixedLengthOpaque( 75 | bytes.NewBuffer([]byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35, 0x00, 0x00}), 76 | b[:]) 77 | require.NoError(t, err) 78 | require.Equal(t, int64(8), n) 79 | require.Equal(t, [...]byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35}, b) 80 | }) 81 | 82 | t.Run("SuccessPadding3Bytes", func(t *testing.T) { 83 | var b [7]byte 84 | n, err := runtime.ReadFixedLengthOpaque( 85 | bytes.NewBuffer([]byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35, 0xaa, 0x00}), 86 | b[:]) 87 | require.NoError(t, err) 88 | require.Equal(t, int64(8), n) 89 | require.Equal(t, [...]byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35, 0xaa}, b) 90 | }) 91 | } 92 | 93 | func TestWriteFixedLengthOpaque(t *testing.T) { 94 | ctrl := gomock.NewController(t) 95 | 96 | t.Run("IOFailure", func(t *testing.T) { 97 | w := mock.NewMockWriter(ctrl) 98 | w.EXPECT().Write(gomock.Any()).Return(0, errors.New("disk failure")) 99 | 100 | n, err := runtime.WriteFixedLengthOpaque( 101 | w, 102 | []byte{1, 2, 3, 4, 5, 6, 7, 8}) 103 | require.Equal(t, errors.New("disk failure"), err) 104 | require.Equal(t, int64(0), n) 105 | }) 106 | 107 | t.Run("SuccessPadding0Bytes", func(t *testing.T) { 108 | b := bytes.NewBuffer(nil) 109 | n, err := runtime.WriteFixedLengthOpaque( 110 | b, 111 | []byte{1, 2, 3, 4}) 112 | require.NoError(t, err) 113 | require.Equal(t, int64(4), n) 114 | require.Equal(t, []byte{1, 2, 3, 4}, b.Bytes()) 115 | }) 116 | 117 | t.Run("SuccessPadding1Byte", func(t *testing.T) { 118 | b := bytes.NewBuffer(nil) 119 | n, err := runtime.WriteFixedLengthOpaque( 120 | b, 121 | []byte{1, 2, 3, 4, 5}) 122 | require.NoError(t, err) 123 | require.Equal(t, int64(8), n) 124 | require.Equal(t, []byte{1, 2, 3, 4, 5, 0, 0, 0}, b.Bytes()) 125 | }) 126 | 127 | t.Run("SuccessPadding2Bytes", func(t *testing.T) { 128 | b := bytes.NewBuffer(nil) 129 | n, err := runtime.WriteFixedLengthOpaque( 130 | b, 131 | []byte{1, 2, 3, 4, 5, 6}) 132 | require.NoError(t, err) 133 | require.Equal(t, int64(8), n) 134 | require.Equal(t, []byte{1, 2, 3, 4, 5, 6, 0, 0}, b.Bytes()) 135 | }) 136 | 137 | t.Run("SuccessPadding3Bytes", func(t *testing.T) { 138 | b := bytes.NewBuffer(nil) 139 | n, err := runtime.WriteFixedLengthOpaque( 140 | b, 141 | []byte{1, 2, 3, 4, 5, 6, 7}) 142 | require.NoError(t, err) 143 | require.Equal(t, int64(8), n) 144 | require.Equal(t, []byte{1, 2, 3, 4, 5, 6, 7, 0}, b.Bytes()) 145 | }) 146 | } 147 | 148 | func TestReadVariableLengthOpaque(t *testing.T) { 149 | ctrl := gomock.NewController(t) 150 | 151 | t.Run("IOFailure", func(t *testing.T) { 152 | r := mock.NewMockReader(ctrl) 153 | r.EXPECT().Read(gomock.Any()).Return(0, errors.New("disk failure")) 154 | 155 | _, n, err := runtime.ReadVariableLengthOpaque(r, 20) 156 | require.Equal(t, errors.New("disk failure"), err) 157 | require.Equal(t, int64(0), n) 158 | }) 159 | 160 | t.Run("EOF", func(t *testing.T) { 161 | _, n, err := runtime.ReadVariableLengthOpaque(bytes.NewBuffer(nil), 20) 162 | require.Equal(t, io.EOF, err) 163 | require.Equal(t, int64(0), n) 164 | }) 165 | 166 | t.Run("UnexpectedEOF", func(t *testing.T) { 167 | input := [...]byte{0, 0, 0, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} 168 | for i := 1; i <= len(input); i++ { 169 | _, n, err := runtime.ReadVariableLengthOpaque(bytes.NewBuffer(input[:i]), 20) 170 | require.Equal(t, io.ErrUnexpectedEOF, err) 171 | require.Equal(t, int64(i), n) 172 | } 173 | }) 174 | 175 | t.Run("TooSmall", func(t *testing.T) { 176 | _, n, err := runtime.ReadVariableLengthOpaque( 177 | bytes.NewBuffer([]byte{0, 0, 0, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), 178 | 10) 179 | require.Equal(t, errors.New("size of 11 bytes exceeds field's maximum of 10 bytes"), err) 180 | require.Equal(t, int64(4), n) 181 | }) 182 | 183 | t.Run("InvalidPadding", func(t *testing.T) { 184 | _, n, err := runtime.ReadVariableLengthOpaque( 185 | bytes.NewBuffer([]byte{0, 0, 0, 2, 5, 2, 3, 7}), 186 | 20) 187 | require.Equal(t, errors.New("padding contains non-zero values [3 7]"), err) 188 | require.Equal(t, int64(8), n) 189 | }) 190 | 191 | t.Run("SuccessZero", func(t *testing.T) { 192 | // Reads of empty buffers should not cause allocations. 193 | b, n, err := runtime.ReadVariableLengthOpaque( 194 | bytes.NewBuffer([]byte{0, 0, 0, 0}), 195 | 20) 196 | require.NoError(t, err) 197 | require.Equal(t, int64(4), n) 198 | require.Nil(t, b) 199 | }) 200 | 201 | t.Run("SuccessPadding0Bytes", func(t *testing.T) { 202 | b, n, err := runtime.ReadVariableLengthOpaque( 203 | bytes.NewBuffer([]byte{0, 0, 0, 4, 0x2d, 0x11, 0x8b, 0xa4}), 204 | 20) 205 | require.NoError(t, err) 206 | require.Equal(t, int64(8), n) 207 | require.Equal(t, []byte{0x2d, 0x11, 0x8b, 0xa4}, b) 208 | }) 209 | 210 | t.Run("SuccessPadding1Byte", func(t *testing.T) { 211 | b, n, err := runtime.ReadVariableLengthOpaque( 212 | bytes.NewBuffer([]byte{0, 0, 0, 5, 0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x00, 0x00, 0x00}), 213 | 20) 214 | require.NoError(t, err) 215 | require.Equal(t, int64(12), n) 216 | require.Equal(t, []byte{0x2d, 0x11, 0x8b, 0xa4, 0x72}, b) 217 | }) 218 | 219 | t.Run("SuccessPadding2Bytes", func(t *testing.T) { 220 | b, n, err := runtime.ReadVariableLengthOpaque( 221 | bytes.NewBuffer([]byte{0, 0, 0, 6, 0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35, 0x00, 0x00}), 222 | 20) 223 | require.NoError(t, err) 224 | require.Equal(t, int64(12), n) 225 | require.Equal(t, []byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35}, b) 226 | }) 227 | 228 | t.Run("SuccessPadding3Bytes", func(t *testing.T) { 229 | b, n, err := runtime.ReadVariableLengthOpaque( 230 | bytes.NewBuffer([]byte{0, 0, 0, 7, 0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35, 0xaa, 0x00}), 231 | 20) 232 | require.NoError(t, err) 233 | require.Equal(t, int64(12), n) 234 | require.Equal(t, []byte{0x2d, 0x11, 0x8b, 0xa4, 0x72, 0x35, 0xaa}, b) 235 | }) 236 | } 237 | 238 | func TestWriteVariableLengthOpaque(t *testing.T) { 239 | ctrl := gomock.NewController(t) 240 | 241 | t.Run("IOFailure", func(t *testing.T) { 242 | w := mock.NewMockWriter(ctrl) 243 | w.EXPECT().Write(gomock.Any()).Return(0, errors.New("disk failure")) 244 | 245 | n, err := runtime.WriteVariableLengthOpaque( 246 | w, 247 | 16, 248 | []byte{1, 2, 3, 4, 5, 6, 7, 8}) 249 | require.Equal(t, errors.New("disk failure"), err) 250 | require.Equal(t, int64(0), n) 251 | }) 252 | 253 | t.Run("TooLarge", func(t *testing.T) { 254 | w := mock.NewMockWriter(ctrl) 255 | 256 | n, err := runtime.WriteVariableLengthOpaque( 257 | w, 258 | 7, 259 | []byte{1, 2, 3, 4, 5, 6, 7, 8}) 260 | require.Equal(t, errors.New("size of 8 bytes exceeds field's maximum of 7 bytes"), err) 261 | require.Equal(t, int64(0), n) 262 | }) 263 | 264 | t.Run("SuccessPadding0Bytes", func(t *testing.T) { 265 | b := bytes.NewBuffer(nil) 266 | n, err := runtime.WriteVariableLengthOpaque( 267 | b, 268 | 16, 269 | []byte{1, 2, 3, 4}) 270 | require.NoError(t, err) 271 | require.Equal(t, int64(8), n) 272 | require.Equal(t, []byte{0, 0, 0, 4, 1, 2, 3, 4}, b.Bytes()) 273 | }) 274 | 275 | t.Run("SuccessPadding1Byte", func(t *testing.T) { 276 | b := bytes.NewBuffer(nil) 277 | n, err := runtime.WriteVariableLengthOpaque( 278 | b, 279 | 16, 280 | []byte{1, 2, 3, 4, 5}) 281 | require.NoError(t, err) 282 | require.Equal(t, int64(12), n) 283 | require.Equal(t, []byte{0, 0, 0, 5, 1, 2, 3, 4, 5, 0, 0, 0}, b.Bytes()) 284 | }) 285 | 286 | t.Run("SuccessPadding2Bytes", func(t *testing.T) { 287 | b := bytes.NewBuffer(nil) 288 | n, err := runtime.WriteVariableLengthOpaque( 289 | b, 290 | 16, 291 | []byte{1, 2, 3, 4, 5, 6}) 292 | require.NoError(t, err) 293 | require.Equal(t, int64(12), n) 294 | require.Equal(t, []byte{0, 0, 0, 6, 1, 2, 3, 4, 5, 6, 0, 0}, b.Bytes()) 295 | }) 296 | 297 | t.Run("SuccessPadding3Bytes", func(t *testing.T) { 298 | b := bytes.NewBuffer(nil) 299 | n, err := runtime.WriteVariableLengthOpaque( 300 | b, 301 | 16, 302 | []byte{1, 2, 3, 4, 5, 6, 7}) 303 | require.NoError(t, err) 304 | require.Equal(t, int64(12), n) 305 | require.Equal(t, []byte{0, 0, 0, 7, 1, 2, 3, 4, 5, 6, 7, 0}, b.Bytes()) 306 | }) 307 | } 308 | -------------------------------------------------------------------------------- /pkg/runtime/utf8_string.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "strings" 7 | "unicode/utf8" 8 | ) 9 | 10 | var errBadUTF8 = errors.New("string is not valid UTF-8") 11 | 12 | // ReadUTF8String reads an XDR encoded UTF-8 string value. 13 | // 14 | // UTF-8 strings aren't part of RFC 4506, but are used by protocols such 15 | // as NFSv4, that it warrants supporting it as a native type. 16 | func ReadUTF8String(r io.Reader, maximumSizeBytes uint32) (string, int64, error) { 17 | var builder strings.Builder 18 | n, err := copyVariableLengthOpaque(r, &builder, maximumSizeBytes) 19 | if err != nil { 20 | return "", n, err 21 | } 22 | s := builder.String() 23 | if !utf8.ValidString(s) { 24 | return "", n, errBadUTF8 25 | } 26 | return s, n, err 27 | } 28 | 29 | // WriteUTF8String writes an XDR encoded UTF-8 string value. 30 | // 31 | // UTF-8 strings aren't part of RFC 4506, but are used by protocols such 32 | // as NFSv4, that it warrants supporting it as a native type. 33 | func WriteUTF8String(w io.Writer, maximumSizeBytes uint32, s string) (int64, error) { 34 | if !utf8.ValidString(s) { 35 | return 0, errBadUTF8 36 | } 37 | return WriteVariableLengthOpaque(w, maximumSizeBytes, []byte(s)) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/runtime/utf8_string_test.go: -------------------------------------------------------------------------------- 1 | package runtime_test 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/buildbarn/go-xdr/internal/mock" 9 | "github.com/buildbarn/go-xdr/pkg/runtime" 10 | "github.com/golang/mock/gomock" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestReadUTF8String(t *testing.T) { 15 | ctrl := gomock.NewController(t) 16 | 17 | t.Run("IOFailure", func(t *testing.T) { 18 | r := mock.NewMockReader(ctrl) 19 | r.EXPECT().Read(gomock.Any()).Return(0, errors.New("disk failure")) 20 | 21 | _, n, err := runtime.ReadUTF8String(r, 10) 22 | require.Equal(t, errors.New("disk failure"), err) 23 | require.Equal(t, int64(0), n) 24 | }) 25 | 26 | t.Run("InvalidCharacters", func(t *testing.T) { 27 | _, n, err := runtime.ReadUTF8String( 28 | bytes.NewBuffer([]byte{0, 0, 0, 5, 0x48, 0x65, 0x8a, 0x6c, 0x6f, 0, 0, 0}), 29 | 10) 30 | require.Equal(t, errors.New("string is not valid UTF-8"), err) 31 | require.Equal(t, int64(12), n) 32 | }) 33 | 34 | t.Run("Success", func(t *testing.T) { 35 | s, n, err := runtime.ReadUTF8String( 36 | bytes.NewBuffer([]byte{0, 0, 0, 5, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0, 0, 0}), 37 | 10) 38 | require.NoError(t, err) 39 | require.Equal(t, int64(12), n) 40 | require.Equal(t, "Hello", s) 41 | }) 42 | } 43 | 44 | func TestWriteUTF8String(t *testing.T) { 45 | ctrl := gomock.NewController(t) 46 | 47 | t.Run("IOFailure", func(t *testing.T) { 48 | w := mock.NewMockWriter(ctrl) 49 | w.EXPECT().Write(gomock.Any()).Return(0, errors.New("disk failure")) 50 | 51 | n, err := runtime.WriteUTF8String(w, 10, "Hello") 52 | require.Equal(t, errors.New("disk failure"), err) 53 | require.Equal(t, int64(0), n) 54 | }) 55 | 56 | t.Run("InvalidCharacters", func(t *testing.T) { 57 | b := bytes.NewBuffer(nil) 58 | n, err := runtime.WriteUTF8String(b, 30, string([]byte{0xff, 0xfe, 0xfd})) 59 | require.Equal(t, errors.New("string is not valid UTF-8"), err) 60 | require.Equal(t, int64(0), n) 61 | }) 62 | 63 | t.Run("Success", func(t *testing.T) { 64 | b := bytes.NewBuffer(nil) 65 | n, err := runtime.WriteUTF8String(b, 10, "Hello") 66 | require.NoError(t, err) 67 | require.Equal(t, int64(12), n) 68 | require.Equal(t, []byte{0, 0, 0, 5, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0, 0, 0}, b.Bytes()) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /tools/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "tools", 5 | srcs = ["deps.go"], 6 | importpath = "github.com/buildbarn/go-xdr/tools", 7 | tags = ["manual"], 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "@cc_mvdan_gofumpt//:gofumpt", 11 | "@com_github_golang_mock//gomock", 12 | "@com_github_gordonklaus_ineffassign//:ineffassign", 13 | "@com_github_stretchr_testify//require", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /tools/deps.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | // Used by tests. 5 | "github.com/golang/mock/gomock" 6 | // Used by CI. 7 | _ "github.com/gordonklaus/ineffassign" 8 | // Used by tests. 9 | _ "github.com/stretchr/testify/require" 10 | // Used by CI. 11 | _ "mvdan.cc/gofumpt" 12 | ) 13 | -------------------------------------------------------------------------------- /xdr.bzl: -------------------------------------------------------------------------------- 1 | XDRLibrary = provider() 2 | 3 | def _go_xdr_library_impl(ctx): 4 | import_root = ( 5 | ctx.file.src.root.path + ctx.label.workspace_root + ctx.attr.strip_import_prefix 6 | ) 7 | import_root_prefix = import_root 8 | if import_root_prefix: 9 | import_root_prefix += "/" 10 | if not ctx.file.src.path.startswith(import_root_prefix): 11 | fail( 12 | "Source path %s does not start with import root %s" % 13 | (ctx.file.src.path, import_root_prefix), 14 | ) 15 | short_src_path = ctx.file.src.path[len(import_root_prefix):] 16 | 17 | all_srcs = depset( 18 | direct = [ctx.file.src], 19 | transitive = [dep[XDRLibrary].srcs for dep in ctx.attr.deps], 20 | ) 21 | import_roots = depset( 22 | direct = [import_root], 23 | transitive = [dep[XDRLibrary].import_roots for dep in ctx.attr.deps], 24 | ) 25 | 26 | out = ctx.actions.declare_file(ctx.attr.name + ".go") 27 | args = ctx.actions.args() 28 | args.add_all(import_roots, before_each = "-I") 29 | args.add(short_src_path) 30 | args.add(out.path) 31 | ctx.actions.run( 32 | inputs = all_srcs, 33 | outputs = [out], 34 | executable = ctx.executable._xdr_compiler, 35 | arguments = [args], 36 | ) 37 | 38 | return [ 39 | DefaultInfo(files = depset([out])), 40 | XDRLibrary(srcs = all_srcs, import_roots = import_roots), 41 | ] 42 | 43 | go_xdr_library = rule( 44 | implementation = _go_xdr_library_impl, 45 | attrs = { 46 | "_xdr_compiler": attr.label( 47 | default = "@com_github_buildbarn_go_xdr//cmd/xdr_compiler", 48 | executable = True, 49 | cfg = "host", 50 | ), 51 | "src": attr.label(allow_single_file = True), 52 | "deps": attr.label_list(providers = [XDRLibrary]), 53 | "strip_import_prefix": attr.string(default = ""), 54 | }, 55 | ) 56 | --------------------------------------------------------------------------------