From 0d46d6ae58e243e94a75f210d31da7fd97441e0f Mon Sep 17 00:00:00 2001 From: Joshua Leahy Date: Tue, 10 Mar 2026 15:00:38 +0000 Subject: [PATCH 1/6] Binary message reading / writing A very initial cut of this, it's destined to be used by the NFS server. --- docs/generated-code.md | 174 ++++++ docs/msg-format.md | 562 +++++++++++++++++ docs/msg.vim | 68 +++ go/.gitignore | 1 + go/msgparse/analyze.go | 736 +++++++++++++++++++++++ go/msgparse/ast.go | 118 ++++ go/msgparse/gen.go | 547 +++++++++++++++++ go/msgparse/gen_message.go | 1062 ++++++++++++++++++++++++++++++++ go/msgparse/gen_string.go | 359 +++++++++++ go/msgparse/gen_struct.go | 279 +++++++++ go/msgparse/gen_union.go | 118 ++++ go/msgparse/gen_writer.go | 1166 ++++++++++++++++++++++++++++++++++++ go/msgparse/main.go | 78 +++ go/msgparse/parse.go | 458 ++++++++++++++ go/msgparse/xdr2msg.py | 946 +++++++++++++++++++++++++++++ 15 files changed, 6672 insertions(+) create mode 100644 docs/generated-code.md create mode 100644 docs/msg-format.md create mode 100644 docs/msg.vim create mode 100644 go/msgparse/analyze.go create mode 100644 go/msgparse/ast.go create mode 100644 go/msgparse/gen.go create mode 100644 go/msgparse/gen_message.go create mode 100644 go/msgparse/gen_string.go create mode 100644 go/msgparse/gen_struct.go create mode 100644 go/msgparse/gen_union.go create mode 100644 go/msgparse/gen_writer.go create mode 100644 go/msgparse/main.go create mode 100644 go/msgparse/parse.go create mode 100755 go/msgparse/xdr2msg.py diff --git a/docs/generated-code.md b/docs/generated-code.md new file mode 100644 index 00000000..9b6430f9 --- /dev/null +++ b/docs/generated-code.md @@ -0,0 +1,174 @@ + + +# Using Generated Go Code + +This document explains how to use the Go types generated by `msgc` from `.msg` +files. For the `.msg` language itself, see [msg-format.md](msg-format.md). For +internal codegen details, see [codegen-spec.md](codegen-spec.md). + +## Quick start + +```bash +go run ./cmd/msgc -pkg mypackage -o output.go input.msg +``` + +The generated code depends only on `encoding/binary` from the standard library. +Multiple generated files can coexist in the same package. + +## Key concepts + +Generated types operate directly on byte buffers with no intermediate Go +structs. **Readers** alias the input bytes (zero-copy). **Writers** build output +by appending to a `[]byte`. Both are lightweight views — one pointer for structs, +one slice header for messages — so pass them by value freely. + +Since readers alias the buffer, modifying the underlying bytes changes what the +reader sees. If you need the data to outlive the buffer, copy it. + +## Constants and enums + +Constants become untyped Go constants. Enums become typed `const` declarations: + +```go +const NFS4_FHSIZE = 128 + +const ( + REC_READING uint32 = 1 + REC_CONFIG uint32 = 2 + REC_HEARTBEAT uint32 = 3 +) +``` + +## Structs (fixed-size) + +```go +// Reading: +v, ok := ReadVec3i16(buf) // ok=false if buf too short +fmt.Println(v.X(), v.Y(), v.Z()) // fields accessible in any order + +// Writing: +buf, v := StartVec3i16(buf) +v.SetX(-100) +v.SetY(200) +// bytes are live in buf immediately — no Finish needed +``` + +Struct getters/setters also work for embedded structs, fixed-size arrays, and +padded strings (`npchar`/`spchar`): + +```go +r.Header().Sensor().SetKind(SENS_ACCEL) // nested struct access +ac.Cal(0) // read array element 0 +ac.SetCal(2, 100) // write array element 2 +dl.SetName("hello") // writes "hello" + padding +``` + +## Messages (variable-size) + +### Reading + +```go +msg, ok := ReadSampleList(buf) // validates structure, returns typed view +msg.Count() // scalar header field +msg.Samples(0) // indexed array access (fixed-size elements) +``` + +Variable-size array elements use iterators: + +```go +it := lf.Records() +for it.Next() { + entry := it.Record() // current element +} +``` + +### Writing + +The basic pattern is `Start` → set/append fields → `Finish`: + +```go +w := StartSampleList(nil) // nil allocates; or pass existing []byte +w.AppendSamples(100) +w.AppendSamples(200) +buf := w.Finish() // patches count, returns completed buffer +``` + +When a message contains variable-size children (embedded messages, variable-size +array elements), writing them produces a **child writer** that must be finished +and resumed before the parent can continue: + +```go +w := StartStampedData(nil) +w.Ts().SetNanos(1234567890) +child := w.StartPayload() +child.SetData([]byte{0xAA}) +w.Resume(child.Finish()) // REQUIRED — parent is invalid until Resume +buf := w.Finish() +``` + +The same pattern applies to variable-size array elements: each `Append` returns +a child writer. + +### Unions + +Read the discriminant, then call the `As` method for the appropriate arm: + +```go +switch entry.Rtype() { +case REC_READING: + r := entry.Body().AsSingleReading() + fmt.Println(r.Value()) +case REC_CONFIG: + c := entry.Body().AsConfigRecord() + fmt.Println(c.SampleRate()) +} +``` + +`As` methods check the discriminant at runtime — calling the wrong one panics. + +Union arms are written via `Set{Field}_{Arm}()` methods on the enclosing +writer. Fixed-size arms return a struct to fill in place; variable-size arms +return a child writer that must be finished and resumed: + +```go +// Fixed-size arm: +w := StartRecordEntry(nil) +sr := w.SetBody_Reading() +sr.SetValue(42) +buf := w.Finish() + +// Variable-size arm: +w := StartRecordEntry(nil) +cfgW := w.SetBody_Config() +cfgW.SetSampleRate(48000) +w.Resume(cfgW.Finish()) +buf := w.Finish() +``` + +When a message has an array of union elements, the parent writer provides +combined `Append{Array}_{Arm}()` methods that set the discriminant +automatically. + +## Safety + +**Readers** are safe to use freely. `Read` validates input bounds; getters on a +valid reader cannot panic. Readers never modify the buffer. + +**Writers** have a few rules: + +1. **Call `Finish()`.** It patches deferred fields (counts, extent lengths). +2. **Call `Resume(child.Finish())` for variable-size children.** The parent + panics if used while a child is active. +3. **Fill struct accessors before the next append.** A subsequent append may + reallocate the buffer and invalidate the struct pointer. +4. **Write arrays in declaration order** for multi-array messages. + +| Detected at runtime (panics) | Not detected (silent bugs) | +|------------------------------|---------------------------| +| Parent used while child is active | Forgetting top-level `Finish()` | +| Missing `Resume(child.Finish())` | Holding a struct accessor across a reallocation | +| Appending arrays out of order | | diff --git a/docs/msg-format.md b/docs/msg-format.md new file mode 100644 index 00000000..6520fd9a --- /dev/null +++ b/docs/msg-format.md @@ -0,0 +1,562 @@ + + +# .msg File Format + +The `.msg` format describes binary message layouts for code generation. It is +designed for wire protocols where messages are read and written as flat byte +buffers with no deserialization into native structs. The format supports +enumerations, fixed-size structures, variable-length messages, and +discriminated unions. + +## Comments + +Line comments start with `//` and extend to the end of the line. + +``` +// This is a comment +``` + +## Primitive types + +### Numeric types + +| Type | Size | Description | +|---------|---------|----------------------------------------| +| `u8` | 1 byte | Unsigned 8-bit integer | +| `i8` | 1 byte | Signed 8-bit integer | +| `leu16` | 2 bytes | Little-endian unsigned 16-bit integer | +| `beu16` | 2 bytes | Big-endian unsigned 16-bit integer | +| `lei16` | 2 bytes | Little-endian signed 16-bit integer | +| `bei16` | 2 bytes | Big-endian signed 16-bit integer | +| `leu32` | 4 bytes | Little-endian unsigned 32-bit integer | +| `beu32` | 4 bytes | Big-endian unsigned 32-bit integer | +| `lei32` | 4 bytes | Little-endian signed 32-bit integer | +| `bei32` | 4 bytes | Big-endian signed 32-bit integer | +| `leu64` | 8 bytes | Little-endian unsigned 64-bit integer | +| `beu64` | 8 bytes | Big-endian unsigned 64-bit integer | +| `lei64` | 8 bytes | Little-endian signed 64-bit integer | +| `bei64` | 8 bytes | Big-endian signed 64-bit integer | + +### Floating-point types + +| Type | Size | Description | +|------------|---------|------------------------------------------------| +| `lefloat` | 4 bytes | Little-endian IEEE 754 single-precision float | +| `befloat` | 4 bytes | Big-endian IEEE 754 single-precision float | +| `ledouble` | 8 bytes | Little-endian IEEE 754 double-precision float | +| `bedouble` | 8 bytes | Big-endian IEEE 754 double-precision float | + +In Go, `lefloat` and `befloat` map to `float32`, while `ledouble` and `bedouble` +map to `float64`. The generated code uses `math.Float32frombits` / +`math.Float32bits` and the `Float64` equivalents to convert between the wire +representation and Go float types. + +### Character and string types + +| Type | Size | Description | +|----------|--------|---------------------------------------------------------| +| `char` | 1 byte | Character byte, distinct from `u8` | +| `npchar` | 1 byte | Null-padded character (for fixed-length string fields) | +| `spchar` | 1 byte | Space-padded character (for fixed-length string fields) | + +`char` is semantically a text character rather than a raw byte. In Go it +produces `[]byte` (like `u8`), but in C++ it maps to `char*` rather than +`uint8_t*`. + +`npchar` and `spchar` are used for fixed-length string fields. When reading, +trailing padding is stripped (0x00 for `npchar`, 0x20 for `spchar`). When +writing, values shorter than the field are padded to the full field length; +values longer are truncated. + +``` +struct volume_label { + spchar name[11]; // space-padded, getter strips trailing 0x20 + npchar serial[16]; // null-padded, getter strips trailing 0x00 +} +``` + +## Constants + +Named constants can be defined with `const`: + +``` +const NFS4_FHSIZE = 128; +const NFS4_OPAQUE_LIMIT = 1024; +const NFS4_INT64_MAX = 0x7fffffffffffffff; +``` + +Constants are untyped integer values (decimal or hex). They are emitted as +untyped Go constants (`const NAME = value`). Multiple consecutive `const` +declarations are grouped into a single `const ( ... )` block. + +Constants exist only for documentation and convenience — they do not define +types, cannot be used as array lengths or discriminants, and have no effect on +wire format parsing. + +## Enums + +An enum declares a set of named constants with an underlying type. + +### Numeric enums + +The underlying type can be any numeric primitive: + +``` +enum open_claim_type4 : beu32 { + CLAIM_NULL = 0; + CLAIM_PREVIOUS = 1; + CLAIM_DELEGATE_CUR = 2; + CLAIM_DELEGATE_PREV = 3; +} +``` + +Values can be decimal literals, hex literals (`0x` prefix), or character +literals (single-quoted), which resolve to the Unicode code point: + +``` +enum reply_code : u8 { + ACK = 'A'; // 0x41 + NAK = 'N'; // 0x4E + ERR = 'E'; // 0x45 +} +``` + +### String enums + +The underlying type can also be `char[N]`, `npchar[N]`, or `spchar[N]` where N +is 2, 4, or 8. Values are double-quoted strings. These are lowered to `leu16`, +`leu32`, or `leu64` respectively in the backend, with the string bytes packed +into the integer in memory order (first character at the lowest address). + +``` +enum file_type : char[4] { + RIFF = "RIFF"; + PNG = "\x89PNG"; +} +``` + +When the underlying type is `npchar[N]` or `spchar[N]`, string values shorter +than N are padded accordingly (null or space): + +``` +enum tag : spchar[4] { + HDR = "HDR"; // stored as "HDR " + DAT = "DAT"; // stored as "DAT " + END = "END"; // stored as "END " +} +``` + +### Usage + +Enum names are used as discriminant types in unions and as field types in +structs/messages, where they occupy the same number of bytes as their underlying +type. + +Enum constant names must be unique across all enums in the file. This is +because code generators emit constants into a flat namespace (e.g. Go package- +level `const` declarations), so two constants with the same name would collide. + +## Structs + +A `struct` is a **fixed-size** type. Every field must have a size that is known +at compile time: either a primitive, an enum, another struct, or a fixed-size +array with a literal length. + +``` +struct stateid4 { + beu32 seqid; + u8 other[12]; +} +``` + +The total size of a struct is the sum of its field sizes (including any +`pad` directives). There is no implicit padding between fields. Structs can be +embedded in other types and their size is always statically known. + +### Fields + +Scalar fields specify a type and a name: + +``` +beu32 seqid; +``` + +Fixed-size array fields append a literal integer in brackets: + +``` +u8 other[12]; // 12 bytes +u8 data[8]; // 8 bytes +``` + +The bracket value is the **element count**. For `u8` arrays this equals the byte +length. For wider types, the byte length is `count * element_size`. + +### Padding + +The `pad(N)` directive inserts exactly N zero bytes. It can appear in both +structs and messages: + +``` +struct padded_header { + beu32 magic; + pad(4); // 4 bytes of padding + beu64 timestamp; +} +``` + +Padding bytes are not exposed as a field. + +### Alignment + +The `align(N)` directive inserts 0 to N-1 padding bytes so that the next byte +offset (relative to the start of the type) is a multiple of N: + +``` +struct aligned_record { + u8 flags; + align(4); // 3 bytes of padding + beu32 value; +} +``` + +In messages, `align` typically appears after a variable-length `u8` array to +restore alignment: + +``` +message component4 { + beu32 name_len; + u8 name[name_len]; + align(4); +} +``` + +The padding bytes are always zero and are not exposed as a field. `align` is +only needed after fields that may not be a multiple of the alignment — arrays +of wider types (`beu32`, `beu64`) and fixed-size structs are inherently aligned +by their element size. + +### Embedding structs + +A struct can appear as a field type in another struct or message. It is laid out +inline (not length-prefixed): + +``` +struct CLOSE4args { + beu32 seqid; + stateid4 open_stateid; // 16 bytes inline +} +``` + +### Enum-typed fields + +When an enum type is used as a field, it occupies the same bytes as the enum's +underlying type: + +``` +struct claim_previous4 { + open_delegation_type4 delegate_type; // 4 bytes (beu32) +} +``` + +## Messages + +A `message` is a **variable-size** type. It contains at least one field whose +size depends on runtime data: a variable-length array, an alignment directive, +or an embedded message/union. + +``` +message PUTFH4args { + beu32 fh_len; + u8 fh[fh_len]; + align(4); +} +``` + +Messages use the same field syntax as structs, plus variable-length arrays +and extent. + +### Variable-length arrays + +A variable-length array uses a field name (rather than a literal) as its size: + +``` +beu32 bitmap_count; +beu32 bitmap[bitmap_count]; +``` + +The referenced field must appear earlier in the same type and gives the +**element count**. The byte length on the wire is `count * element_size`. + +For `u8` arrays, the element size is 1, so the count field is also the byte +length. For `beu32` arrays, each element is 4 bytes. + +The array element type can also be a named type (struct, message, or union): + +``` +beu32 argarray_count; +nfs_argop4_entry argarray[argarray_count]; +``` + +When the element type is variable-size, each element must be parsed in sequence +to determine the extent of the array. + +### Extent + +The `extent(field)` directive declares that a field gives the byte length from +the directive to the end of the message. All subsequent fields are parsed from +within that byte region, and any bytes remaining after the last known field are +skipped. + +``` +message foo { + beu32 version; + beu32 body_len; + extent(body_len); + beu32 field_a; + beu32 field_b; +} +``` + +The referenced field can appear anywhere in the same message (before or after +the directive). The reader parses fields after the directive normally, but the +total number of bytes consumed from that point is always exactly the value of +the extent field — any unparsed remainder is skipped. + +This provides forward compatibility: future versions of the format can append +fields after `field_b` without breaking older readers, because older readers +know to skip `body_len` bytes regardless of how many fields they understand. + +Wire format for `version=1, body_len=12, field_a=1, field_b=2` with one +unknown extension field: + +``` +[version:4] [body_len:4] [field_a:4] [field_b:4] [unknown:4] + |<----------- 12 bytes ---------->| +``` + +The reader parses `field_a` and `field_b` (8 bytes), sees it has consumed fewer +than `body_len` (12) bytes, and skips the remaining 4. + +If the known fields of the message consume more bytes than the runtime extent +value, the behavior is undefined. The code generator should emit a validation +function that checks the extent is at least as large as the minimum size of the +known fields. + +`extent` may appear at most once in a message and always covers from the +directive to the end of the message. If the length-delimited region is a +sub-region with fields after it, factor it into a separate message type. + +### Embedding messages + +A message can appear as a field type in another message. It is laid out inline +(not length-prefixed) and must be parsed to determine its extent: + +``` +message open_claim_delegate_cur4 { + stateid4 delegate_stateid; // fixed-size struct, 16 bytes + component4 file; // variable-size message +} +``` + +## Unions + +A `union` is a discriminated (tagged) union. The discriminant's enum type is +given in parentheses, followed by arms that map enum values to payload types. + +``` +union createhow4 (createmode4) { + UNCHECKED4: fattr4; + GUARDED4: fattr4; + EXCLUSIVE4: verifier4; +} +``` + +A union's wire format is the **arm payload only** — the discriminant is never +part of the union encoding. The discriminant is always provided by a separate +field in the enclosing message (see [Usage in messages](#usage-in-messages)). + +Each arm label must be a constant from the discriminant's enum type. The payload +type can be any defined type (struct, message, or another union). + +### void arms + +An arm with type `void` has no payload — when the discriminant matches, the +union contributes zero bytes: + +``` +union openflag4 (opentype4) { + OPEN4_CREATE: openflag4_create; + default: void; +} +``` + +### default arm + +A `default` arm matches any discriminant value not explicitly listed. It is +typically used with `void` for cases where no payload is needed: + +``` +union nfs_argop4 (nfs_opnum4) { + OP_ACCESS: ACCESS4args; + OP_CLOSE: CLOSE4args; + // ... + default: void; +} +``` + +### Usage in messages + +A union field must always be annotated with the name of the field that provides +its discriminant value, in parentheses: + +``` +message OPEN4args { + beu32 seqid; + beu32 share_access; + beu32 share_deny; + open_owner4 owner; + opentype4 openhow_type; + openflag4 openhow(openhow_type); + open_claim_type4 claim_type; + open_claim4 claim(claim_type); +} +``` + +The discriminant field must appear earlier in the same message. The code +generator uses its value to determine which arm to parse. On the wire, the +discriminant field and the union payload are separate — the discriminant appears +at its declared position, and the union payload follows at its declared +position. + +When a union contains another union as an arm payload, the inner union needs +its own discriminant. Wrap it in a message that pairs the discriminant with the +payload: + +``` +// createhow4 needs a createmode4 discriminant +message openflag4_create { + createmode4 mode; + createhow4 how(mode); +} + +union openflag4 (opentype4) { + OPEN4_CREATE: openflag4_create; + default: void; +} +``` + +Similarly, arrays of unions require the array element type to be a message that +includes the discriminant: + +``` +message nfs_argop4_entry { + nfs_opnum4 opnum; + nfs_argop4 arg(opnum); +} + +message COMPOUND4args { + // ... + beu32 argarray_count; + nfs_argop4_entry argarray[argarray_count]; +} +``` + +## Type categories summary + +| Keyword | Size | Can contain | +|-----------|----------|--------------------------------------------------| +| `const` | — | Untyped integer constants | +| `enum` | Fixed | Named constants with a numeric or string underlying type | +| `struct` | Fixed | Primitives, enums, structs, fixed-size arrays, `pad`, `align` | +| `message` | Variable | Everything structs can, plus variable-length arrays, `extent`, embedded messages/unions | +| `union` | Variable | Per-arm payloads (any type or `void`), discriminated by an external enum field | + +## Declaration order + +Types may be declared in any order. Forward references are supported — a type +can reference another type that is defined later in the file. + +## Complete example + +``` +enum createmode4 : beu32 { + UNCHECKED4 = 0; + GUARDED4 = 1; + EXCLUSIVE4 = 2; +} + +struct verifier4 { + u8 data[8]; +} + +message fattr4 { + beu32 bitmap_count; + beu32 bitmap[bitmap_count]; + beu32 attrlist_len; + u8 attrlist[attrlist_len]; + align(4); +} + +union createhow4 (createmode4) { + UNCHECKED4: fattr4; + GUARDED4: fattr4; + EXCLUSIVE4: verifier4; +} + +message openflag4_create { + createmode4 mode; + createhow4 how(mode); +} +``` + +This defines a union `createhow4` discriminated on `createmode4`. When the +discriminant is `UNCHECKED4` or `GUARDED4`, the payload is a variable-length +`fattr4`. When `EXCLUSIVE4`, the payload is a fixed 8-byte `verifier4`. The +`openflag4_create` message pairs the discriminant with the union payload. + +## Example: extent and external discriminant + +``` +enum msg_type : beu32 { + MSG_REQUEST = 1; + MSG_RESPONSE = 2; +} + +message request_data { + beu32 request_id; + beu32 flags; +} + +message response_data { + beu32 request_id; + beu32 status; +} + +union msg_body (msg_type) { + MSG_REQUEST: request_data; + MSG_RESPONSE: response_data; +} + +message envelope { + msg_type type; + beu32 body_len; + extent(body_len); + msg_body body(type); +} +``` + +`envelope` uses both features together. The `type` field determines which arm of +`msg_body` to parse, and `body(type)` names it as the discriminant. The +`extent(body_len)` directive says the remaining bytes are exactly `body_len`, so +future versions can append fields after `body` and older readers will skip them. + +Wire format for a request with `body_len=12` and one unknown extension field: + +``` +[type:4 = 1] [body_len:4 = 12] [request_id:4] [flags:4] [unknown:4] + |<------------ 12 bytes ---------->| +``` diff --git a/docs/msg.vim b/docs/msg.vim new file mode 100644 index 00000000..4bc1846b --- /dev/null +++ b/docs/msg.vim @@ -0,0 +1,68 @@ +" Copyright 2026 XTX Markets Technologies Limited +" +" SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +" Vim syntax file for .msg (msgparse binary message format) +" Language: msg +" Latest Revision: 2026-03-11 + +if exists("b:current_syntax") + finish +endif + +" Keywords: declaration types +syntax keyword msgKeyword enum struct message union const + +" Keywords: special identifiers +syntax keyword msgKeyword void default + +" Directives: pad(N), align(N), extent(field) +syntax keyword msgDirective pad align extent + +" Primitive types: integers +syntax keyword msgType u8 i8 +syntax keyword msgType leu16 beu16 lei16 bei16 +syntax keyword msgType leu32 beu32 lei32 bei32 +syntax keyword msgType leu64 beu64 lei64 bei64 + +" Primitive types: floats +syntax keyword msgType lefloat befloat ledouble bedouble + +" Primitive types: character/string +syntax keyword msgType char spchar npchar + +" Comments (// only, no # or /* */) +syntax keyword msgTodo contained TODO FIXME XXX NOTE HACK +syntax match msgComment "//.*$" contains=msgTodo + +" String literals: "..." with backslash escapes +syntax match msgStringEscape contained "\\." +syntax region msgString start=+"+ skip=+\\.+ end=+"+ contains=msgStringEscape + +" Character literals: '.' with backslash escapes +syntax match msgCharEscape contained "\\." +syntax region msgChar start=+'+ skip=+\\.+ end=+'+ contains=msgCharEscape + +" Number literals: hex (0x...) and decimal +syntax match msgNumber "\<0[xX][0-9a-fA-F]\+\>" +syntax match msgNumber "\<[0-9]\+\>" + +" Braces, brackets, parens, semicolons for balanced highlighting +syntax match msgDelimiter "[{}()\[\];]" +syntax match msgOperator "[=:]" + +" Highlight links +highlight default link msgKeyword Keyword +highlight default link msgDirective PreProc +highlight default link msgType Type +highlight default link msgTodo Todo +highlight default link msgComment Comment +highlight default link msgString String +highlight default link msgChar Character +highlight default link msgStringEscape Special +highlight default link msgCharEscape Special +highlight default link msgNumber Number +highlight default link msgDelimiter Delimiter +highlight default link msgOperator Operator + +let b:current_syntax = "msg" diff --git a/go/.gitignore b/go/.gitignore index 699e45ab..e19c16bc 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -17,3 +17,4 @@ terngc/terngc badblocks/badblocks ternregistryproxy/ternregistryproxy terns3/terns3 +msgparse/msgparse diff --git a/go/msgparse/analyze.go b/go/msgparse/analyze.go new file mode 100644 index 00000000..f2c6c3d2 --- /dev/null +++ b/go/msgparse/analyze.go @@ -0,0 +1,736 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// PrimInfo describes a primitive type's wire properties. +type PrimInfo struct { + Size int // 1, 2, 4, 8 + Signed bool + IsBE bool // big-endian + IsChar bool // char, npchar, spchar + CharKind string // "", "np", "sp" + IsFloat bool // IEEE 754 float/double +} + +var primitives = map[string]PrimInfo{ + "u8": {Size: 1}, + "i8": {Size: 1, Signed: true}, + "leu16": {Size: 2}, + "lei16": {Size: 2, Signed: true}, + "beu16": {Size: 2, IsBE: true}, + "bei16": {Size: 2, Signed: true, IsBE: true}, + "leu32": {Size: 4}, + "lei32": {Size: 4, Signed: true}, + "beu32": {Size: 4, IsBE: true}, + "bei32": {Size: 4, Signed: true, IsBE: true}, + "leu64": {Size: 8}, + "lei64": {Size: 8, Signed: true}, + "beu64": {Size: 8, IsBE: true}, + "bei64": {Size: 8, Signed: true, IsBE: true}, + "lefloat": {Size: 4, IsFloat: true}, + "befloat": {Size: 4, IsBE: true, IsFloat: true}, + "ledouble": {Size: 8, IsFloat: true}, + "bedouble": {Size: 8, IsBE: true, IsFloat: true}, + "char": {Size: 1, IsChar: true}, + "npchar": {Size: 1, IsChar: true, CharKind: "np"}, + "spchar": {Size: 1, IsChar: true, CharKind: "sp"}, +} + +// TypeInfo holds computed information about a declared type. +type TypeInfo struct { + Decl Decl + Size int // total byte size (for fixed-size types; -1 for variable) + + // For structs: computed field offsets. + FieldOffsets []int // parallel to Decl.Struct.Fields or Decl.Message.Fields + + // Flags computed during analysis. + NeedsCursorRead bool // needs lowercase readXxx function + IsFixedSize bool + + // For messages: + TotalFixedSize int // total bytes of fixed-size scalar fields (may span across variable content) + HasExtent bool + ExtentFieldOff int // offset of the extent length field + ExtentFieldName string // name of the extent length field + KnownBodySize int // size of known fields after extent + IsExtentStruct bool // reader type is struct{m *[N]byte} + ExtentTotalSize int // headerSize + knownBodySize (for struct reader) + IsMultiArray bool + MultiArrayKind int // 1 = both counts in header, 2 = interleaved + IsUnionWrapper bool // disc + union, nothing else (deferred alloc) + AlignSize int // align directive size, 0 if none + HasVarSizeArrays bool // has variable-length arrays with var-size elements + VarArrays []VarArrayInfo // variable-length arrays with their count info + VarSectionBoundaries []VarSectionBoundary // stored offsets after variable content + ReadPattern string // "compute", "wrap", "lazy", "extent", "multiarray" + + FixedPrefixSize int // contiguous fixed bytes at start (stops at first variable content) + + // Precomputed field role sets (for messages): + CountFields map[string]bool // field names referenced as array length (e.g., "count" in data[count]) + DiscFields map[string]bool // field names referenced as union discriminants + + // Writer phase tracking (for messages with multiple variable-size sections): + WriterPhases int // total phases (0 or 1 = no checking needed) + FieldPhase map[int]int // field index → phase number +} + +// VarArrayInfo describes a variable-length array field and its count field. +type VarArrayInfo struct { + ArrayIdx int // index of array field in message fields + ArrayField Field + CountIdx int // index of count field in message fields + CountField Field + CountOff int // byte offset of count field +} + +// VarSectionBoundary marks the end of a variable-size section in a message. +// The stored offset points to the byte position immediately after the +// section's data, which is the start of the next field. +type VarSectionBoundary struct { + FieldIdx int // index of the var-size field that ends at this boundary +} + +// IterInfo tracks iterator types for variable-size array elements. +type IterInfo struct { + ElemMsgName string // .msg type name of element + FieldName string // first field name using this type (for accessor naming) + Created bool +} + +// Analysis holds the result of analyzing a .msg file. +type Analysis struct { + File *MsgFile + Types map[string]*TypeInfo // keyed by .msg name + Order []string // topologically sorted type names + Iterators map[string]*IterInfo // keyed by element .msg type name +} + +func Analyze(file *MsgFile) *Analysis { + a := &Analysis{ + File: file, + Types: make(map[string]*TypeInfo), + Iterators: make(map[string]*IterInfo), + } + + // Phase 1: Register all types (skip const groups — they are anonymous). + for _, d := range file.Decls { + if d.Kind == DeclConstGroup { + continue + } + name := d.Name() + a.Types[name] = &TypeInfo{ + Decl: d, + } + } + + // Phase 2: Compute sizes and field offsets for fixed-size types. + for _, d := range file.Decls { + if d.Kind == DeclStruct { + a.computeStructSize(d.Struct) + } + if d.Kind == DeclEnum { + ti := a.Types[d.Name()] + ti.IsFixedSize = true + ti.Size = a.enumSize(d.Enum) + } + } + + // Phase 3: Analyze messages. + for _, d := range file.Decls { + if d.Kind == DeclMessage { + a.analyzeMessage(d.Message) + } + } + + // Phase 4: Determine which types need cursor-advancing reads. + a.computeCursorReads() + + // Phase 5: Compute output order. + a.computeOrder() + + // Phase 6: Determine iterators. + a.computeIterators() + + return a +} + +func (a *Analysis) computeStructSize(s *StructDecl) { + ti := a.Types[s.Name] + if ti.Size > 0 { + return // already computed + } + offset := 0 + offsets := make([]int, len(s.Fields)) + for i, f := range s.Fields { + switch f.Kind { + case FieldPad: + offsets[i] = offset + offset += f.Size + case FieldAlign: + pad := (f.Size - (offset % f.Size)) % f.Size + offsets[i] = offset + offset += pad + case FieldNormal: + offsets[i] = offset + sz := a.fieldSize(f) + offset += sz + } + } + ti.Size = offset + ti.IsFixedSize = true + ti.FieldOffsets = offsets +} + +func (a *Analysis) fieldSize(f Field) int { + baseSize := a.typeSize(f.TypeName) + if f.ArrayLen != "" { + n, err := strconv.Atoi(f.ArrayLen) + if err != nil { + // Variable-length array — not fixed size + return -1 + } + return baseSize * n + } + return baseSize +} + +func (a *Analysis) typeSize(name string) int { + if p, ok := primitives[name]; ok { + return p.Size + } + ti, ok := a.Types[name] + if !ok { + fmt.Fprintf(os.Stderr, "unknown type: %s\n", name) + os.Exit(1) + } + if ti.Decl.Kind == DeclStruct { + if ti.Size == 0 { + a.computeStructSize(ti.Decl.Struct) + } + return ti.Size + } + if ti.Decl.Kind == DeclEnum { + return a.enumSize(ti.Decl.Enum) + } + return -1 // variable-size +} + +func (a *Analysis) enumSize(e *EnumDecl) int { + return enumPrimInfo(e).Size +} + +// enumPrimInfo returns the PrimInfo for an enum's underlying wire type. +// String enums (char[N], npchar[N], spchar[N]) are lowered to LE integers. +func enumPrimInfo(e *EnumDecl) PrimInfo { + u := e.Underlying + if strings.Contains(u, "[") { + n := extractBracketNum(u) + switch n { + case 2: + return primitives["leu16"] + case 4: + return primitives["leu32"] + case 8: + return primitives["leu64"] + } + } + if p, ok := primitives[u]; ok { + return p + } + fmt.Fprintf(os.Stderr, "unknown enum underlying type: %s\n", u) + os.Exit(1) + return PrimInfo{} +} + +func (a *Analysis) analyzeMessage(m *MessageDecl) { + ti := a.Types[m.Name] + ti.IsFixedSize = false + ti.Size = -1 + + // Precompute field role sets. + ti.CountFields = map[string]bool{} + ti.DiscFields = map[string]bool{} + for _, f := range m.Fields { + if f.Kind == FieldNormal { + if isVarLenArray(f) { + ti.CountFields[f.ArrayLen] = true + } + if f.DiscRef != "" { + ti.DiscFields[f.DiscRef] = true + } + } + } + + // Compute header size and field offsets. + offset := 0 + offsets := make([]int, len(m.Fields)) + hasOtherVarContent := false // var content besides fixed-element arrays + var extentIdx = -1 + pastExtent := false + + for i, f := range m.Fields { + offsets[i] = offset + switch f.Kind { + case FieldPad: + if !pastExtent { + offset += f.Size + } + case FieldAlign: + // Alignment after variable data — tracked via AlignSize, not + // treated as "other variable content" since compute-read can handle it. + case FieldExtent: + ti.HasExtent = true + extentIdx = i + pastExtent = true + ti.TotalFixedSize = offset // save header size before extent body + // Find the extent field's offset + ti.ExtentFieldName = f.Ref + for j, f2 := range m.Fields { + if f2.Kind == FieldNormal && f2.Name == f.Ref { + ti.ExtentFieldOff = offsets[j] + break + } + } + case FieldNormal: + if pastExtent { + // Fields after extent are inside the extent body. + // Track their offsets for getter generation, but don't affect header size. + offsets[i] = offset + sz := a.typeSize(f.TypeName) + if sz > 0 { + offset += sz + } + } else if isVarLenArray(f) { + countIdx := a.findFieldIdx(m, f.ArrayLen) + ti.VarArrays = append(ti.VarArrays, VarArrayInfo{ + ArrayIdx: i, + ArrayField: f, + CountIdx: countIdx, + CountField: m.Fields[countIdx], + CountOff: offsets[countIdx], + }) + elemSize := a.typeSize(f.TypeName) + if elemSize < 0 { + ti.HasVarSizeArrays = true + } + } else if f.ArrayLen != "" { + // Fixed-size array + offset += a.fieldSize(f) + } else if f.DiscRef != "" { + // Union field — variable-size + hasOtherVarContent = true + } else { + sz := a.typeSize(f.TypeName) + if sz < 0 { + // Embedded variable-size message + hasOtherVarContent = true + } else { + offset += sz + } + } + } + } + + if !pastExtent { + ti.TotalFixedSize = offset // bytes of fixed header before variable content + } + ti.FieldOffsets = offsets + + // Compute real contiguous header size (stops at first variable content). + rh := 0 + for _, f := range m.Fields { + if f.Kind == FieldPad { + rh += f.Size + continue + } + if f.Kind == FieldAlign { + pad := (f.Size - (rh % f.Size)) % f.Size + rh += pad + continue + } + if f.Kind != FieldNormal { + continue + } + if a.isVarContentField(f) { + break + } + rh += a.fieldSize(f) + } + ti.FixedPrefixSize = rh + + // Detect multi-array messages. + if len(ti.VarArrays) >= 2 { + ti.IsMultiArray = true + // Determine pattern: check if all count fields come before all array fields. + allCountsBefore := true + firstArrayIdx := ti.VarArrays[0].ArrayIdx + for _, va := range ti.VarArrays { + if va.CountIdx > firstArrayIdx { + allCountsBefore = false + break + } + } + if allCountsBefore { + ti.MultiArrayKind = 1 // both counts in header + } else { + ti.MultiArrayKind = 2 // interleaved + } + } + + // Compute boundaries after variable-content fields. A boundary is needed + // after each variable-content field that has subsequent normal fields, + // so getters can locate those subsequent fields via stored offsets. + if !ti.IsMultiArray { + var varFieldIdxs []int + for i, f := range m.Fields { + if a.isVarContentField(f) { + varFieldIdxs = append(varFieldIdxs, i) + } + } + for _, vfi := range varFieldIdxs { + hasSubsequent := false + for k := vfi + 1; k < len(m.Fields); k++ { + if m.Fields[k].Kind == FieldNormal { + hasSubsequent = true + break + } + } + if hasSubsequent { + ti.VarSectionBoundaries = append(ti.VarSectionBoundaries, VarSectionBoundary{ + FieldIdx: vfi, + }) + } + } + } + + // Compute align size (first align directive found). + for _, f := range m.Fields { + if f.Kind == FieldAlign { + ti.AlignSize = f.Size + break + } + } + + // Detect extent-based struct reader type: extent with all known fixed-offset fields. + if ti.HasExtent && !ti.IsMultiArray { + // Compute known body size (fields after extent) + bodySize := 0 + allFixed := true + if extentIdx >= 0 { + for j := extentIdx + 1; j < len(m.Fields); j++ { + f := m.Fields[j] + if f.Kind == FieldNormal { + sz := a.typeSize(f.TypeName) + if sz < 0 || f.ArrayLen != "" || f.DiscRef != "" { + allFixed = false + break + } + bodySize += sz + } + } + } + if allFixed && bodySize > 0 { + ti.IsExtentStruct = true + ti.KnownBodySize = bodySize + ti.ExtentTotalSize = ti.TotalFixedSize + bodySize + } + } + + // Determine read pattern. + if ti.IsMultiArray { + ti.ReadPattern = "multiarray" + } else if len(ti.VarSectionBoundaries) > 0 { + // Multiple var-size sections: must use cursor read to compute offsets. + ti.ReadPattern = "wrap" + } else if ti.IsExtentStruct { + ti.ReadPattern = "extent" + } else if len(ti.VarArrays) == 1 && !ti.HasVarSizeArrays && !hasOtherVarContent { + // Single array of fixed-size elements, no other variable content + // → validate length and return slice. + ti.ReadPattern = "compute" + } else if ti.HasVarSizeArrays || len(ti.VarArrays) >= 1 { + // Has var-size array elements → lazy read for public Read + ti.ReadPattern = "lazy" + } else { + // Has var-size children (embedded message, union) but no var-size arrays + ti.ReadPattern = "wrap" + } + + // Detect union wrapper: exactly a discriminant scalar + a union field. + normalCount := 0 + hasUnion := false + for _, f := range m.Fields { + if f.Kind == FieldNormal { + normalCount++ + if f.DiscRef != "" { + hasUnion = true + } + } + } + ti.IsUnionWrapper = normalCount == 2 && hasUnion + + // Compute writer phases. Walk fields left to right; each variable-size + // section (embedded var message, var-size array, or union) that requires + // Resume increments the phase. Fields in the fixed prefix (phase 0) are + // always safe. Fields after each variable section get the next phase number. + a.computeWriterPhases(ti) +} + +// computeWriterPhases assigns phase numbers to message fields for writer +// ordering validation. Phase 0 covers the fixed prefix. Each variable-size +// section that requires Resume increments the phase. Fields (including +// deferred fixed fields) after a variable section get the new phase number. +// +// A "variable-size section" is one of: +// - An embedded variable-size message +// - A variable-length array with variable-size elements +// - A union field (which may have variable-size arms) +// +// Fixed-size arrays (e.g., leu32 samples[count]) don't require Resume and +// don't create phase boundaries. +func (a *Analysis) computeWriterPhases(ti *TypeInfo) { + m := ti.Decl.Message + ti.FieldPhase = make(map[int]int) + phase := 0 + inPrefix := true + + for i, f := range m.Fields { + if f.Kind != FieldNormal { + ti.FieldPhase[i] = phase + continue + } + + // Fields in the fixed prefix are always phase 0. + if inPrefix && ti.FieldOffsets[i] < ti.FixedPrefixSize { + ti.FieldPhase[i] = phase + continue + } + + // Check if this field is a variable-size section. + isVarSection := false + if isVarLenArray(f) { + if a.typeSize(f.TypeName) < 0 { + // Variable-size elements → needs Resume per element. + isVarSection = true + } + } else if f.DiscRef != "" { + isVarSection = a.unionHasVarSizeArm(f.TypeName) + } else if a.isVarSizeType(f.TypeName) { + // Embedded variable-size message. + isVarSection = true + } + + inPrefix = false + ti.FieldPhase[i] = phase + if isVarSection { + phase++ // next field gets the new phase + } + } + + ti.WriterPhases = phase +} + +func (a *Analysis) findFieldIdx(m *MessageDecl, name string) int { + for i, f := range m.Fields { + if f.Kind == FieldNormal && f.Name == name { + return i + } + } + return -1 +} + +func (a *Analysis) computeCursorReads() { + // Mark types that need cursor-advancing reads: + // 1. All unions + // 2. Structs used as union arm payloads + // 3. Messages containing variable-size content OR used in variable contexts + + // First pass: mark unions and their arm dependencies. + for _, d := range a.File.Decls { + if d.Kind == DeclUnion { + a.Types[d.Name()].NeedsCursorRead = true + for _, arm := range d.Union.Arms { + if arm.Payload != "void" { + if ti, ok := a.Types[arm.Payload]; ok { + ti.NeedsCursorRead = true + } + } + } + } + } + + // Second pass: mark messages with variable-size content, and mark + // variable-size types referenced from messages (they need cursor reads + // to be embedded or used as array elements). + for _, d := range a.File.Decls { + if d.Kind != DeclMessage { + continue + } + ti := a.Types[d.Name()] + for _, f := range d.Message.Fields { + if f.Kind != FieldNormal { + continue + } + if f.DiscRef != "" { + ti.NeedsCursorRead = true + } else if isVarLenArray(f) { + if a.typeSize(f.TypeName) < 0 { + ti.NeedsCursorRead = true + } + } else if a.isVarSizeType(f.TypeName) { + ti.NeedsCursorRead = true + } + // Mark any referenced var-size type as needing cursor read. + if eti, ok := a.Types[f.TypeName]; ok && !eti.IsFixedSize { + eti.NeedsCursorRead = true + } + } + } + + // Multi-array and boundary messages always need cursor read. + for _, ti := range a.Types { + if ti.IsMultiArray || len(ti.VarSectionBoundaries) > 0 { + ti.NeedsCursorRead = true + } + } +} + +func (a *Analysis) computeOrder() { + // Emit in declaration order: enums and const groups first, then everything else. + // Const groups are anonymous — use a synthetic key "#const_N" where N is the decl index. + constIdx := 0 + for _, d := range a.File.Decls { + if d.Kind == DeclEnum { + a.Order = append(a.Order, d.Name()) + } else if d.Kind == DeclConstGroup { + key := fmt.Sprintf("#const_%d", constIdx) + constIdx++ + a.Types[key] = &TypeInfo{ + Decl: d, + } + a.Order = append(a.Order, key) + } + } + for _, d := range a.File.Decls { + if d.Kind != DeclEnum && d.Kind != DeclConstGroup { + a.Order = append(a.Order, d.Name()) + } + } +} + +func (a *Analysis) computeIterators() { + // For each variable-size element type used in a variable-length array, + // create an iterator. The accessor name comes from the first field name + // that uses this element type. + for _, name := range a.Order { + ti := a.Types[name] + if ti.Decl.Kind != DeclMessage { + continue + } + for _, f := range ti.Decl.Message.Fields { + if f.Kind != FieldNormal || !isVarLenArray(f) { + continue + } + elemSize := a.typeSize(f.TypeName) + if elemSize >= 0 { + continue // fixed-size elements — no iterator needed + } + // Variable-size element → needs iterator. + if _, exists := a.Iterators[f.TypeName]; !exists { + a.Iterators[f.TypeName] = &IterInfo{ + ElemMsgName: f.TypeName, + FieldName: f.Name, + } + } + } + } +} + +// isVarSizeType returns true if the type is variable-size. +func (a *Analysis) isVarSizeType(name string) bool { + if _, ok := primitives[name]; ok { + return false + } + ti, ok := a.Types[name] + if !ok { + return false + } + return !ti.IsFixedSize +} + +// isStructReader returns true if a type's reader is a struct (not a []byte alias). +// This includes extent structs, multi-array messages, and var-section messages. +func (a *Analysis) isStructReader(name string) bool { + ti, ok := a.Types[name] + if !ok { + return false + } + return ti.IsExtentStruct || ti.IsMultiArray || len(ti.VarSectionBoundaries) > 0 +} + +// unionHasVarSizeArm returns true if a union type has at least one non-void arm +// with a variable-size payload. +func (a *Analysis) unionHasVarSizeArm(typeName string) bool { + uti, ok := a.Types[typeName] + if !ok || uti.Decl.Kind != DeclUnion { + return false + } + for _, arm := range uti.Decl.Union.Arms { + if arm.Payload != "void" { + if pti, ok := a.Types[arm.Payload]; ok && !pti.IsFixedSize { + return true + } + } + } + return false +} + +// isVarLenArray returns true if a field is a variable-length array (count reference, not literal size). +func isVarLenArray(f Field) bool { + if f.ArrayLen == "" { + return false + } + _, err := strconv.Atoi(f.ArrayLen) + return err != nil +} + +// isVarContentField returns true if a normal field introduces variable-length content. +func (a *Analysis) isVarContentField(f Field) bool { + if f.Kind != FieldNormal { + return false + } + if f.DiscRef != "" { + return true + } + if isVarLenArray(f) { + return true + } + if f.ArrayLen != "" { + return false + } + return a.isVarSizeType(f.TypeName) +} + +func extractBracketNum(s string) int { + idx := strings.Index(s, "[") + if idx < 0 { + return 0 + } + end := strings.Index(s, "]") + if end < 0 { + return 0 + } + n, _ := strconv.Atoi(s[idx+1 : end]) + return n +} diff --git a/go/msgparse/ast.go b/go/msgparse/ast.go new file mode 100644 index 00000000..1dd4ed88 --- /dev/null +++ b/go/msgparse/ast.go @@ -0,0 +1,118 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +// AST types for parsed .msg files. + +type MsgFile struct { + Decls []Decl // all declarations in file order +} + +// Decl is a tagged union of declaration types. +type Decl struct { + Kind DeclKind + Enum *EnumDecl + Struct *StructDecl + Message *MessageDecl + Union *UnionDecl + ConstGroup *ConstGroupDecl +} + +type DeclKind int + +const ( + DeclEnum DeclKind = iota + DeclStruct + DeclMessage + DeclUnion + DeclConstGroup +) + +func (d Decl) Name() string { + switch d.Kind { + case DeclEnum: + return d.Enum.Name + case DeclStruct: + return d.Struct.Name + case DeclMessage: + return d.Message.Name + case DeclUnion: + return d.Union.Name + case DeclConstGroup: + return "" // const groups are anonymous + } + return "" +} + +// EnumDecl represents an enum declaration. +type EnumDecl struct { + Name string // e.g. "file_magic" + Underlying string // e.g. "leu32", "u8", "char[4]", "spchar[4]" + Constants []EnumConst +} + +type EnumConst struct { + Name string // e.g. "MAGIC_SLOG" + Value string // raw value: "0x0001", "1", "'I'", "\"SLOG\"" +} + +// ConstGroupDecl represents a group of untyped constants. +type ConstGroupDecl struct { + Constants []ConstEntry +} + +// ConstEntry is a single named constant. +type ConstEntry struct { + Name string // e.g. "NFS4_FHSIZE" + Value string // raw value: "128", "0x7fffffff" +} + +// StructDecl represents a struct declaration. +type StructDecl struct { + Name string + Fields []Field +} + +// MessageDecl represents a message declaration. +type MessageDecl struct { + Name string + Fields []Field +} + +// UnionDecl represents a union declaration. +type UnionDecl struct { + Name string + DiscType string // enum type name for discriminant + Arms []UnionArm +} + +type UnionArm struct { + Label string // enum constant name, or "default" + Payload string // type name, or "void" +} + +// Field represents a field or directive in a struct/message. +type Field struct { + Kind FieldKind + + // For regular fields: + TypeName string // type name (primitive or user-defined) + Name string // field name + ArrayLen string // "" for scalar, numeric for fixed array, field name for variable array + DiscRef string // for union fields: discriminant field name + + // For pad/align/extent: + Size int // N in pad(N), align(N) + Ref string // field name in extent(field) +} + +type FieldKind int + +const ( + FieldNormal FieldKind = iota + FieldPad + FieldAlign + FieldExtent +) diff --git a/go/msgparse/gen.go b/go/msgparse/gen.go new file mode 100644 index 00000000..890b18da --- /dev/null +++ b/go/msgparse/gen.go @@ -0,0 +1,547 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +// Generator produces Go source code from an analyzed .msg file. +type Generator struct { + a *Analysis + w strings.Builder + pkg string + nameMap map[string]string // .msg name → Go PascalCase name (cache) +} + +// goName returns the Go PascalCase name for a TypeInfo. +func (g *Generator) goName(ti *TypeInfo) string { + name := ti.Decl.Name() + if n, ok := g.nameMap[name]; ok { + return n + } + n := pascalCase(name) + g.nameMap[name] = n + return n +} + +// offName returns the Go struct field name for a VarSectionBoundary at index idx. +func offName(idx int) string { + return fmt.Sprintf("off%d", idx+1) +} + +// goType returns the Go type string for a PrimInfo. +func goType(p PrimInfo) string { + if p.IsFloat { + if p.Size == 4 { + return "float32" + } + return "float64" + } + if p.IsChar { + return "byte" + } + base := "uint" + if p.Signed { + base = "int" + } + if p.Size == 1 { + return base + "8" + } + return fmt.Sprintf("%s%d", base, p.Size*8) +} + +// enumGoType returns the Go type for an enum's underlying wire type. +func enumGoType(e *EnumDecl) string { + return goType(enumPrimInfo(e)) +} + +// Naming helpers. + +func pascalCase(s string) string { + parts := strings.Split(s, "_") + for i, p := range parts { + if len(p) > 0 { + parts[i] = strings.ToUpper(p[:1]) + p[1:] + } + } + return strings.Join(parts, "") +} + +func camelCase(s string) string { + p := pascalCase(s) + if len(p) == 0 { + return p + } + return strings.ToLower(p[:1]) + p[1:] +} + +func singularize(s string) string { + if strings.HasSuffix(s, "ies") { + return s[:len(s)-3] + "y" + } + if strings.HasSuffix(s, "ses") || strings.HasSuffix(s, "xes") || + strings.HasSuffix(s, "zes") || strings.HasSuffix(s, "shes") || + strings.HasSuffix(s, "ches") { + return s[:len(s)-2] + } + if strings.HasSuffix(s, "s") { + return s[:len(s)-1] + } + return s +} + +// stripEnumPrefix removes the common prefix from an enum constant name. +func stripEnumPrefix(constName string, allConstants []EnumConst) string { + if len(allConstants) == 0 { + return constName + } + prefix := allConstants[0].Name + for _, c := range allConstants[1:] { + for len(prefix) > 0 && !strings.HasPrefix(c.Name, prefix) { + prefix = prefix[:len(prefix)-1] + } + } + if idx := strings.LastIndex(prefix, "_"); idx >= 0 { + prefix = prefix[:idx+1] + } else { + prefix = "" + } + stripped := strings.TrimPrefix(constName, prefix) + if stripped == "" { + return constName + } + return stripped +} + +// armSuffix converts an ALL_CAPS stripped name to PascalCase. +func armSuffix(allCaps string) string { + if len(allCaps) == 0 { + return allCaps + } + parts := strings.Split(allCaps, "_") + for i, p := range parts { + if len(p) > 0 { + parts[i] = string(unicode.ToUpper(rune(p[0]))) + strings.ToLower(p[1:]) + } + } + return strings.Join(parts, "") +} + +func Generate(a *Analysis, pkg string) string { + g := &Generator{a: a, pkg: pkg, nameMap: make(map[string]string)} + g.emitHeader() + g.emitEnums() + + g.emitTypes() + g.emitStringMethods() + return g.w.String() +} + +func (g *Generator) p(format string, args ...any) { + fmt.Fprintf(&g.w, format, args...) +} + +const sectionSep = "// -------------------------------------------------------\n" + +func sizeConstName(typeName string) string { + return camelCase(typeName) + "Size" +} + +// isByteSliceType returns true if a primitive type should use []byte/string +// slice access instead of element-at-a-time access for variable-length arrays. +// This applies to u8, char, npchar, and spchar (all single-byte, no endian conversion). +func isByteSliceType(typeName string) bool { + switch typeName { + case "u8", "char", "npchar", "spchar": + return true + } + return false +} + +func (g *Generator) emitHeader() { + needsMath := false + for _, name := range g.a.Order { + ti := g.a.Types[name] + var fields []Field + switch ti.Decl.Kind { + case DeclStruct: + fields = ti.Decl.Struct.Fields + case DeclMessage: + fields = ti.Decl.Message.Fields + } + for _, f := range fields { + if p, ok := primitives[f.TypeName]; ok && p.IsFloat { + needsMath = true + } + } + } + // String() methods always need "fmt" and "strings". + g.p("package %s\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n", g.pkg) + if needsMath { + g.p("\t\"math\"\n") + } + g.p("\t\"strings\"\n)\n") +} + +func (g *Generator) emitEnums() { + first := true + + // Collect consecutive const groups and emit them merged. + i := 0 + for i < len(g.a.Order) { + name := g.a.Order[i] + ti := g.a.Types[name] + if ti.Decl.Kind == DeclConstGroup { + if first { + g.p("\n" + sectionSep + "// Enum constants\n" + sectionSep) + first = false + } + // Collect all consecutive const groups. + var allConsts []ConstEntry + for i < len(g.a.Order) { + n := g.a.Order[i] + t := g.a.Types[n] + if t.Decl.Kind != DeclConstGroup { + break + } + allConsts = append(allConsts, t.Decl.ConstGroup.Constants...) + i++ + } + g.emitConstGroup(&ConstGroupDecl{Constants: allConsts}) + continue + } + i++ + if ti.Decl.Kind != DeclEnum { + continue + } + e := ti.Decl.Enum + if first { + g.p("\n" + sectionSep + "// Enum constants\n" + sectionSep) + first = false + } + g.p("\n// %s constants (%s", e.Name, e.Underlying) + if strings.Contains(e.Underlying, "[") { + // String enum — show lowered type. + n := extractBracketNum(e.Underlying) + switch n { + case 2: + g.p(" -> leu16") + case 4: + g.p(" -> leu32") + case 8: + g.p(" -> leu64") + } + } + g.p(")\nconst (\n") + goType := enumGoType(e) + maxNameLen := 0 + for _, c := range e.Constants { + if len(c.Name) > maxNameLen { + maxNameLen = len(c.Name) + } + } + for _, c := range e.Constants { + pad := strings.Repeat(" ", maxNameLen-len(c.Name)) + val, comment := g.enumConstValue(e, c) + if comment != "" { + g.p("\t%s%s %s = %s // %s\n", c.Name, pad, goType, val, comment) + } else { + g.p("\t%s%s %s = %s\n", c.Name, pad, goType, val) + } + } + g.p(")\n") + } +} + +func (g *Generator) enumConstValue(e *EnumDecl, c EnumConst) (string, string) { + if strings.Contains(e.Underlying, "[") { + // String enum: pack string into integer. + n := extractBracketNum(e.Underlying) + raw := unquoteString(c.Value) + charKind := "" + if strings.HasPrefix(e.Underlying, "npchar") { + charKind = "np" + } else if strings.HasPrefix(e.Underlying, "spchar") { + charKind = "sp" + } + padByte := byte(0x00) + if charKind == "sp" { + padByte = 0x20 + } + // Pad or truncate to N bytes. + bytes := make([]byte, n) + for i := range bytes { + bytes[i] = padByte + } + copy(bytes, raw) + // Pack as little-endian integer. + var val uint64 + for i := 0; i < n; i++ { + val |= uint64(bytes[i]) << (uint(i) * 8) + } + var hex string + switch n { + case 2: + hex = fmt.Sprintf("0x%04X", val) + case 4: + hex = fmt.Sprintf("0x%08X", val) + case 8: + hex = fmt.Sprintf("0x%016X", val) + } + // Format the comment showing the padded string. + comment := formatStringComment(c.Value, charKind, n) + return hex, comment + } + + // Numeric enum. + if c.Value[0] == '\'' { + // Character literal: emit as Go rune literal with hex comment. + ch := unquoteChar(c.Value) + hex := fmt.Sprintf("0x%02X", ch) + return c.Value, hex + } + + // Decimal or hex numeric literal — pass through. + return c.Value, "" +} + +func unquoteString(s string) []byte { + // Remove surrounding quotes. + if len(s) < 2 { + return nil + } + s = s[1 : len(s)-1] + var result []byte + for i := 0; i < len(s); i++ { + if s[i] == '\\' && i+1 < len(s) { + switch s[i+1] { + case 'x': + if i+3 < len(s) { + b, _ := strconv.ParseUint(s[i+2:i+4], 16, 8) + result = append(result, byte(b)) + i += 3 + } + case 'n': + result = append(result, '\n') + i++ + case '\\': + result = append(result, '\\') + i++ + case '"': + result = append(result, '"') + i++ + default: + result = append(result, s[i+1]) + i++ + } + } else { + result = append(result, s[i]) + } + } + return result +} + +func unquoteChar(s string) byte { + // 'X' or '\xNN' + if len(s) < 3 { + return 0 + } + inner := s[1 : len(s)-1] + if inner[0] == '\\' { + if len(inner) >= 4 && inner[1] == 'x' { + b, _ := strconv.ParseUint(inner[2:4], 16, 8) + return byte(b) + } + switch inner[1] { + case 'n': + return '\n' + case '\\': + return '\\' + } + return inner[1] + } + return inner[0] +} + +func formatStringComment(value string, charKind string, n int) string { + // Show the padded string as it appears in the packed integer. + raw := unquoteString(value) + padByte := byte(0x00) + if charKind == "sp" { + padByte = 0x20 + } + bytes := make([]byte, n) + for i := range bytes { + bytes[i] = padByte + } + copy(bytes, raw) + + var buf strings.Builder + buf.WriteByte('"') + for _, b := range bytes { + if b >= 0x20 && b <= 0x7e { + buf.WriteByte(b) + } else { + buf.WriteString(fmt.Sprintf("\\x%02x", b)) + } + } + buf.WriteByte('"') + return buf.String() +} + +func (g *Generator) emitConstGroup(cg *ConstGroupDecl) { + if len(cg.Constants) == 1 { + g.p("\nconst %s = %s\n", cg.Constants[0].Name, cg.Constants[0].Value) + return + } + g.p("\nconst (\n") + maxNameLen := 0 + for _, c := range cg.Constants { + if len(c.Name) > maxNameLen { + maxNameLen = len(c.Name) + } + } + for _, c := range cg.Constants { + pad := strings.Repeat(" ", maxNameLen-len(c.Name)) + g.p("\t%s%s = %s\n", c.Name, pad, c.Value) + } + g.p(")\n") +} + +func (g *Generator) emitTypes() { + for _, name := range g.a.Order { + ti := g.a.Types[name] + switch ti.Decl.Kind { + case DeclEnum, DeclConstGroup: + continue // already emitted + case DeclStruct: + g.emitStruct(ti) + case DeclMessage: + g.emitMessage(ti) + case DeclUnion: + g.emitUnion(ti) + } + } +} + +func (g *Generator) writePrim(p PrimInfo, slice, val string) string { + if p.Size == 1 { + if p.Signed { + return fmt.Sprintf("%s[0] = byte(%s)", slice, val) + } + return fmt.Sprintf("%s[0] = %s", slice, val) + } + order := "LittleEndian" + if p.IsBE { + order = "BigEndian" + } + fn := fmt.Sprintf("PutUint%d", p.Size*8) + castVal := val + if p.IsFloat { + floatToBits := fmt.Sprintf("math.Float%dbits", p.Size*8) + castVal = fmt.Sprintf("%s(%s)", floatToBits, val) + } else if p.Signed { + utype := strings.Replace(goType(p), "int", "uint", 1) + castVal = fmt.Sprintf("%s(%s)", utype, val) + } + return fmt.Sprintf("binary.%s.%s(%s, %s)", order, fn, slice, castVal) +} + +// offExpr returns "base" if off==0, "base+N" otherwise. +func offExpr(base string, off int) string { + if off == 0 { + return base + } + return fmt.Sprintf("%s+%d", base, off) +} + +// sliceStr returns "base[start:end]" or "base[start:]" if end is empty. +func sliceStr(base, start, end string) string { + if end != "" { + return fmt.Sprintf("%s[%s:%s]", base, start, end) + } + return fmt.Sprintf("%s[%s:]", base, start) +} + +func (g *Generator) readPrimFromSlice(p PrimInfo, slice string) string { + if p.Size == 1 { + if p.Signed { + return fmt.Sprintf("int8(%s[0])", slice) + } + return fmt.Sprintf("%s[0]", slice) + } + order := "LittleEndian" + if p.IsBE { + order = "BigEndian" + } + fn := fmt.Sprintf("Uint%d", p.Size*8) + expr := fmt.Sprintf("binary.%s.%s(%s)", order, fn, slice) + if p.IsFloat { + bitsToFloat := fmt.Sprintf("math.Float%dfrombits", p.Size*8) + return fmt.Sprintf("%s(%s)", bitsToFloat, expr) + } + if p.Signed { + return fmt.Sprintf("%s(%s)", goType(p), expr) + } + return expr +} + +// readCountExpr returns a Go expression that reads a count/length field as int. +// For signed types, wraps with max(0, ...) to clamp negative values to zero. +func (g *Generator) readCountExpr(p PrimInfo, slice string) string { + inner := g.readPrimFromSlice(p, slice) + if p.Signed { + return fmt.Sprintf("max(0, int(%s))", inner) + } + return fmt.Sprintf("int(%s)", inner) +} + +func (g *Generator) fieldPrimInfo(f Field) PrimInfo { + if p, ok := primitives[f.TypeName]; ok { + return p + } + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclEnum { + return enumPrimInfo(eti.Decl.Enum) + } + return primitives["leu32"] +} + +// fieldPrimInfoOk returns the PrimInfo and true if the field is a primitive +// or enum type (i.e., a scalar). Returns false for struct/message/union types. +func (g *Generator) fieldPrimInfoOk(f Field) (PrimInfo, bool) { + if p, ok := primitives[f.TypeName]; ok { + return p, true + } + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclEnum { + return enumPrimInfo(eti.Decl.Enum), true + } + return PrimInfo{}, false +} + +func (g *Generator) extentPrimInfo(ti *TypeInfo) PrimInfo { + m := ti.Decl.Message + return g.fieldPrimInfo(m.Fields[g.a.findFieldIdx(m, ti.ExtentFieldName)]) +} + +func (g *Generator) extentMinComment(ti *TypeInfo) string { + m := ti.Decl.Message + var parts []string + inExtent := false + for _, f := range m.Fields { + if f.Kind == FieldExtent { + inExtent = true + continue + } + if inExtent && f.Kind == FieldNormal { + sz := g.a.typeSize(f.TypeName) + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } + } + return strings.Join(parts, " + ") +} diff --git a/go/msgparse/gen_message.go b/go/msgparse/gen_message.go new file mode 100644 index 00000000..e6edcd1c --- /dev/null +++ b/go/msgparse/gen_message.go @@ -0,0 +1,1062 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "strconv" + "strings" +) + +func (g *Generator) emitMessage(ti *TypeInfo) { + m := ti.Decl.Message + name := g.goName(ti) + + g.p("\n" + sectionSep) + g.p("// %s — variable", name) + g.emitMessageSizeComment(m, ti) + g.p("\n" + sectionSep) + + // Type declaration. + if ti.IsExtentStruct { + g.p("\ntype %s struct {\n\tm *[%d]byte\n}\n", name, ti.ExtentTotalSize) + } else if ti.IsMultiArray { + g.p("\ntype %s struct {\n\tdata []byte\n\toffB int // byte offset within data where ", name) + g.emitMultiArrayOffComment(m, ti) + g.p("\n}\n") + } else if len(ti.VarSectionBoundaries) > 0 { + g.p("\ntype %s struct {\n\tdata []byte\n", name) + for bi, b := range ti.VarSectionBoundaries { + // Find the next normal field after the boundary to describe the offset. + nextName := "" + for j := b.FieldIdx + 1; j < len(m.Fields); j++ { + if m.Fields[j].Kind == FieldNormal { + nextName = m.Fields[j].Name + break + } + } + g.p("\t%s int // byte offset within data where %s starts\n", offName(bi), nextName) + } + g.p("}\n") + } else { + g.p("\ntype %s []byte\n", name) + } + + // Read functions. + g.emitMessageReads(ti) + + // Getters. + g.emitMessageGetters(ti) + + // Iterator types (emitted after the first message that uses them). + g.emitIteratorsForMessage(ti) + + // Writer. + g.emitMessageWriter(ti) +} + +func (g *Generator) emitMessageSizeComment(m *MessageDecl, ti *TypeInfo) { + var parts []string + for _, f := range m.Fields { + switch f.Kind { + case FieldPad: + parts = append(parts, fmt.Sprintf("pad(%d)", f.Size)) + case FieldAlign: + parts = append(parts, fmt.Sprintf("align(%d)", f.Size)) + case FieldExtent: + // Don't show extent as a separate field; fields after it are "inside" it. + case FieldNormal: + if isVarLenArray(f) { + parts = append(parts, fmt.Sprintf("%s %s[%s]", f.TypeName, f.Name, f.ArrayLen)) + } else if f.ArrayLen != "" { + sz := g.a.fieldSize(f) + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } else if f.DiscRef != "" { + parts = append(parts, fmt.Sprintf("%s %s", f.TypeName, f.Name)) + } else { + sz := g.a.typeSize(f.TypeName) + if sz > 0 { + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } else { + parts = append(parts, f.Name) + } + } + } + } + if len(parts) > 0 { + joined := strings.Join(parts, " + ") + prefix := fmt.Sprintf("// %s — variable: ", g.goName(ti)) + if len(prefix)+len(joined) <= 80 { + g.p(": %s", joined) + } else { + g.p(":\n// %s", joined) + } + } +} + +func (g *Generator) emitMultiArrayOffComment(m *MessageDecl, ti *TypeInfo) { + // Find the second array field to describe what offB points to. + count := 0 + for _, f := range m.Fields { + if f.Kind == FieldNormal && isVarLenArray(f) { + count++ + if count == 2 { + if ti.MultiArrayKind == 1 { + g.p("%s starts", f.Name) + } else { + g.p("%s starts", f.ArrayLen) // count field for second array + } + return + } + } + } +} + +func (g *Generator) emitMessageReads(ti *TypeInfo) { + name := g.goName(ti) + + if ti.NeedsCursorRead { + g.emitCursorRead(ti) + } + + // Public Read. + switch ti.ReadPattern { + case "compute": + g.emitComputeRead(ti) + case "wrap": + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + g.p("\treturn read%s(&b)\n}\n", name) + case "lazy": + g.emitLazyRead(ti) + case "extent": + g.emitExtentRead(ti) + case "multiarray": + g.emitMultiArrayRead(ti) + } +} + +func (g *Generator) emitCursorRead(ti *TypeInfo) { + m := ti.Decl.Message + name := g.goName(ti) + + // Determine return type: []byte for extent-struct messages, TypeName otherwise. + retType := name + nilVal := "nil" + if ti.IsExtentStruct { + retType = "[]byte" + } else if ti.IsMultiArray || len(ti.VarSectionBoundaries) > 0 { + retType = name + nilVal = name + "{}" + } + + g.p("\nfunc read%s(b *[]byte) (%s, bool) {\n", name, retType) + + if ti.HasExtent && ti.IsExtentStruct { + // Extent-based struct: validate header + extent, return []byte. + g.p("\tif len(*b) < %d {\n\t\treturn nil, false\n\t}\n", ti.TotalFixedSize) + extPrim := g.extentPrimInfo(ti) + g.p("\t%s := %s\n", + camelCase(ti.ExtentFieldName), g.readCountExpr(extPrim, fmt.Sprintf("(*b)[%d:%d]", ti.ExtentFieldOff, ti.ExtentFieldOff+extPrim.Size))) + g.p("\tif %s < %d { // extent must hold at least %s\n\t\treturn nil, false\n\t}\n", + camelCase(ti.ExtentFieldName), ti.KnownBodySize, g.extentMinComment(ti)) + g.p("\ttotal := %d + %s\n", ti.TotalFixedSize, camelCase(ti.ExtentFieldName)) + g.p("\tif len(*b) < total {\n\t\treturn nil, false\n\t}\n") + g.p("\tresult := (*b)[:total]\n") + g.p("\t*b = (*b)[total:]\n") + g.p("\treturn result, true\n}\n") + return + } + + if ti.IsMultiArray { + g.emitMultiArrayCursorRead(ti) + return + } + + // Optimized cursor read for byte-slice messages (count + u8 data + optional align). + // Computes total size directly from the count, doing one bounds check. + if g.isByteSliceMessage(ti) { + va := ti.VarArrays[0] + alignSize := ti.AlignSize + lenPrim := g.fieldPrimInfo(va.CountField) + headerSize := ti.TotalFixedSize + + g.p("\tif len(*b) < %d {\n\t\treturn nil, false\n\t}\n", headerSize) + g.p("\tn := %s\n", g.readCountExpr(lenPrim, fmt.Sprintf("(*b)[%d:%d]", va.CountOff, va.CountOff+lenPrim.Size))) + + if alignSize > 0 { + g.p("\tpadded := (n + %d) &^ %d\n", alignSize-1, alignSize-1) + g.p("\ttotal := %d + padded\n", headerSize) + } else { + g.p("\ttotal := %d + n\n", headerSize) + } + + g.p("\tif len(*b) < total {\n\t\treturn nil, false\n\t}\n") + g.p("\tresult := %s((*b)[:total])\n", name) + g.p("\t*b = (*b)[total:]\n") + g.p("\treturn result, true\n}\n") + return + } + + realHeader := ti.FixedPrefixSize + + // General cursor-advancing read. + if realHeader > 0 { + g.p("\tif len(*b) < %d {\n\t\treturn %s, false\n\t}\n", realHeader, nilVal) + } + + // Check if there's any variable content at all. + hasVarContent := false + for _, f := range m.Fields { + if g.a.isVarContentField(f) { + hasVarContent = true + break + } + } + + if !hasVarContent && !ti.HasExtent { + // Fixed-size message (shouldn't happen, but handle gracefully). + g.p("\tresult := %s((*b)[:%d])\n", name, realHeader) + g.p("\t*b = (*b)[%d:]\n", realHeader) + g.p("\treturn result, true\n}\n") + return + } + + g.p("\tstart := *b\n") + g.p("\tstartLen := len(start)\n") + + // Pre-read count fields that are in the contiguous header. + // Prefix local variable names with c_ to avoid shadowing Go builtins. + for _, f := range m.Fields { + if f.Kind == FieldNormal && ti.CountFields[f.Name] { + idx := g.a.findFieldIdx(m, f.Name) + off := ti.FieldOffsets[idx] + if off < realHeader { + p := g.fieldPrimInfo(f) + g.p("\tc_%s := %s\n", f.Name, + g.readCountExpr(p, fmt.Sprintf("(*b)[%d:%d]", off, off+p.Size))) + } + } + } + + // Advance past the contiguous header. + if realHeader > 0 { + g.p("\t*b = (*b)[%d:]\n", realHeader) + } + + // Build boundary map for var-section offset recording. + boundaryAfter := map[int]string{} // fieldIdx → offset variable name + for bi, b := range ti.VarSectionBoundaries { + boundaryAfter[b.FieldIdx] = offName(bi) + } + + // Process fields past the contiguous header in wire format order. + for fieldIdx, f := range m.Fields { + if f.Kind == FieldAlign { + // Alignment padding after variable data. + g.p("\t{\n\t\toff := startLen - len(*b)\n") + g.p("\t\taligned := (off + %d) &^ %d\n", f.Size-1, f.Size-1) + g.p("\t\tif aligned > startLen {\n\t\t\treturn %s, false\n\t\t}\n", nilVal) + g.p("\t\t*b = start[aligned:]\n\t}\n") + continue + } + if f.Kind != FieldNormal { + continue + } + // Skip fields in the fixed prefix (already advanced past). + if !g.a.isVarContentField(f) && ti.FieldOffsets[fieldIdx] < realHeader { + continue + } + + // Handle fields after the header, in order. + if isVarLenArray(f) { + elemSize := g.a.typeSize(f.TypeName) + if elemSize < 0 { + elemReadName := "read" + pascalCase(f.TypeName) + g.p("\tfor i := 0; i < c_%s; i++ {\n", f.ArrayLen) + g.p("\t\tif _, ok := %s(b); !ok {\n", elemReadName) + g.p("\t\t\treturn %s, false\n\t\t}\n\t}\n", nilVal) + } else { + g.p("\tif len(*b) < c_%s*%d {\n\t\treturn %s, false\n\t}\n", f.ArrayLen, elemSize, nilVal) + g.p("\t*b = (*b)[c_%s*%d:]\n", f.ArrayLen, elemSize) + } + if offName, ok := boundaryAfter[fieldIdx]; ok { + g.p("\t%s := startLen - len(*b)\n", offName) + } + continue + } + if f.ArrayLen != "" { + // Fixed-size array after variable content — advance past it. + sz := g.a.fieldSize(f) + g.p("\tif len(*b) < %d {\n\t\treturn %s, false\n\t}\n", sz, nilVal) + g.p("\t*b = (*b)[%d:]\n", sz) + if offName, ok := boundaryAfter[fieldIdx]; ok { + g.p("\t%s := startLen - len(*b)\n", offName) + } + continue + } + + if f.DiscRef != "" { + // Union field — get discriminant value. + discIdx := g.a.findFieldIdx(m, f.DiscRef) + discOff := ti.FieldOffsets[discIdx] + discPrim := g.fieldPrimInfo(m.Fields[discIdx]) + readFn := "read" + pascalCase(f.TypeName) + var discExpr string + if discOff < realHeader { + // Disc is in the contiguous header — read from start. + discExpr = g.readPrimFromSlice(discPrim, + fmt.Sprintf("start[%d:%d]", discOff, discOff+discPrim.Size)) + } else { + // Disc was read inline as a local variable. + discExpr = "c_" + f.DiscRef + } + g.p("\tif _, ok := %s(b, %s); !ok {\n", readFn, discExpr) + g.p("\t\treturn %s, false\n\t}\n", nilVal) + if offName, ok := boundaryAfter[fieldIdx]; ok { + g.p("\t%s := startLen - len(*b)\n", offName) + } + continue + } + + if g.a.isVarSizeType(f.TypeName) { + // Embedded variable-size message. + readFn := "read" + pascalCase(f.TypeName) + g.p("\tif _, ok := %s(b); !ok {\n", readFn) + g.p("\t\treturn %s, false\n\t}\n", nilVal) + if offName, ok := boundaryAfter[fieldIdx]; ok { + g.p("\t%s := startLen - len(*b)\n", offName) + } + continue + } + + // Fixed-size field after variable content — read inline and advance. + sz := g.a.typeSize(f.TypeName) + if sz <= 0 { + continue + } + g.p("\tif len(*b) < %d {\n\t\treturn %s, false\n\t}\n", sz, nilVal) + if ti.CountFields[f.Name] { + // Count field — store in local variable. + p := g.fieldPrimInfo(f) + g.p("\tc_%s := %s\n", f.Name, + g.readCountExpr(p, fmt.Sprintf("(*b)[:%d]", p.Size))) + } + if ti.DiscFields[f.Name] { + p := g.fieldPrimInfo(f) + g.p("\tc_%s := %s\n", f.Name, + g.readPrimFromSlice(p, fmt.Sprintf("(*b)[:%d]", p.Size))) + } + g.p("\t*b = (*b)[%d:]\n", sz) + } + + g.p("\ttotal := startLen - len(*b)\n") + if len(ti.VarSectionBoundaries) > 0 { + // Construct struct with stored offsets. + g.p("\treturn %s{data: start[:total]", name) + for bi := range ti.VarSectionBoundaries { + on := offName(bi) + g.p(", %s: %s", on, on) + } + g.p("}, true\n}\n") + } else { + g.p("\treturn %s(start[:total]), true\n}\n", name) + } +} + +func (g *Generator) emitComputeRead(ti *TypeInfo) { + name := g.goName(ti) + va := ti.VarArrays[0] + countPrim := g.fieldPrimInfo(va.CountField) + alignSize := ti.AlignSize + + elemSize := g.a.typeSize(va.ArrayField.TypeName) + elemSizeStr := strconv.Itoa(elemSize) + // Use size constant if it's a struct. + if eti, ok := g.a.Types[va.ArrayField.TypeName]; ok && eti.Decl.Kind == DeclStruct { + elemSizeStr = sizeConstName(va.ArrayField.TypeName) + } + + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + g.p("\tif len(b) < %d {\n\t\treturn nil, false\n\t}\n", ti.TotalFixedSize) + g.p("\tcount := %s\n", g.readCountExpr(countPrim, fmt.Sprintf("b[%d:%d]", va.CountOff, va.CountOff+countPrim.Size))) + if alignSize > 0 && elemSize == 1 { + g.p("\tpadded := (count + %d) &^ %d\n", alignSize-1, alignSize-1) + g.p("\ttotal := %d + padded\n", ti.TotalFixedSize) + } else { + g.p("\ttotal := %d + count*%s\n", ti.TotalFixedSize, elemSizeStr) + } + g.p("\tif len(b) < total {\n\t\treturn nil, false\n\t}\n") + g.p("\treturn %s(b[:total]), true\n}\n", name) +} + +func (g *Generator) emitLazyRead(ti *TypeInfo) { + name := g.goName(ti) + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + g.p("\tif len(b) < %d {\n\t\treturn nil, false\n\t}\n", ti.TotalFixedSize) + g.p("\treturn %s(b), true\n}\n", name) +} + +func (g *Generator) emitExtentRead(ti *TypeInfo) { + name := g.goName(ti) + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + g.p("\tif len(b) < %d {\n\t\treturn %s{}, false\n\t}\n", ti.TotalFixedSize, name) + extPrim := g.extentPrimInfo(ti) + g.p("\t%s := %s\n", + camelCase(ti.ExtentFieldName), g.readCountExpr(extPrim, fmt.Sprintf("b[%d:%d]", ti.ExtentFieldOff, ti.ExtentFieldOff+extPrim.Size))) + g.p("\tif %s < %d {\n\t\treturn %s{}, false\n\t}\n", + camelCase(ti.ExtentFieldName), ti.KnownBodySize, name) + g.p("\ttotal := %d + %s\n", ti.TotalFixedSize, camelCase(ti.ExtentFieldName)) + g.p("\tif len(b) < total {\n\t\treturn %s{}, false\n\t}\n", name) + g.p("\treturn %s{m: (*[%d]byte)(b)}, true\n}\n", name, ti.ExtentTotalSize) +} + +func (g *Generator) emitMultiArrayRead(ti *TypeInfo) { + name := g.goName(ti) + arrays := ti.VarArrays + + if len(arrays) < 2 { + return + } + + countAPrim := g.fieldPrimInfo(arrays[0].CountField) + headerSize := ti.TotalFixedSize + + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + + if ti.MultiArrayKind == 1 { + // Both counts in header. + g.p("\tif len(b) < %d {\n\t\treturn %s{}, false\n\t}\n", headerSize, name) + g.p("\t%s := %s\n", arrays[0].CountField.Name, + g.readCountExpr(countAPrim, fmt.Sprintf("b[%d:%d]", arrays[0].CountOff, arrays[0].CountOff+countAPrim.Size))) + g.p("\trest := b[%d:]\n", headerSize) + elemRead := "read" + pascalCase(arrays[0].ArrayField.TypeName) + g.p("\tfor i := 0; i < %s; i++ {\n", arrays[0].CountField.Name) + g.p("\t\tif _, ok := %s(&rest); !ok {\n", elemRead) + g.p("\t\t\treturn %s{}, false\n\t\t}\n\t}\n", name) + g.p("\toffB := len(b) - len(rest)\n") + g.p("\treturn %s{data: b, offB: offB}, true\n}\n", name) + } else { + // Interleaved counts. + g.p("\tif len(b) < %d {\n\t\treturn %s{}, false\n\t}\n", countAPrim.Size, name) + g.p("\t%s := %s\n", arrays[0].CountField.Name, + g.readCountExpr(countAPrim, fmt.Sprintf("b[%d:%d]", arrays[0].CountOff, arrays[0].CountOff+countAPrim.Size))) + dataStart := arrays[0].CountOff + countAPrim.Size + g.p("\trest := b[%d:]\n", dataStart) + elemRead := "read" + pascalCase(arrays[0].ArrayField.TypeName) + g.p("\tfor i := 0; i < %s; i++ {\n", arrays[0].CountField.Name) + g.p("\t\tif _, ok := %s(&rest); !ok {\n", elemRead) + g.p("\t\t\treturn %s{}, false\n\t\t}\n\t}\n", name) + g.p("\toffB := len(b) - len(rest)\n") + countBPrim := g.fieldPrimInfo(arrays[1].CountField) + g.p("\tif len(rest) < %d {\n\t\treturn %s{}, false\n\t}\n", countBPrim.Size, name) + g.p("\treturn %s{data: b, offB: offB}, true\n}\n", name) + } +} + +func (g *Generator) emitMultiArrayCursorRead(ti *TypeInfo) { + name := g.goName(ti) + arrays := ti.VarArrays + headerSize := ti.TotalFixedSize + + if ti.MultiArrayKind == 1 { + // Both counts in header. + g.p("\tif len(*b) < %d {\n\t\treturn %s{}, false\n\t}\n", headerSize, name) + + for _, arr := range arrays { + cp := g.fieldPrimInfo(arr.CountField) + g.p("\t%s := %s\n", arr.CountField.Name, + g.readCountExpr(cp, fmt.Sprintf("(*b)[%d:%d]", arr.CountOff, arr.CountOff+cp.Size))) + } + + g.p("\tstart := *b\n") + g.p("\t*b = (*b)[%d:]\n", headerSize) + + elemRead := "read" + pascalCase(arrays[0].ArrayField.TypeName) + g.p("\tfor i := 0; i < %s; i++ {\n", arrays[0].CountField.Name) + g.p("\t\tif _, ok := %s(b); !ok {\n", elemRead) + g.p("\t\t\treturn %s{}, false\n\t\t}\n\t}\n", name) + + g.p("\toffB := len(start) - len(*b)\n") + + elemRead2 := "read" + pascalCase(arrays[1].ArrayField.TypeName) + g.p("\tfor i := 0; i < %s; i++ {\n", arrays[1].CountField.Name) + g.p("\t\tif _, ok := %s(b); !ok {\n", elemRead2) + g.p("\t\t\treturn %s{}, false\n\t\t}\n\t}\n", name) + + g.p("\ttotal := len(start) - len(*b)\n") + g.p("\treturn %s{data: start[:total], offB: offB}, true\n}\n", name) + } else { + // Interleaved counts. + countAPrim := g.fieldPrimInfo(arrays[0].CountField) + g.p("\tif len(*b) < %d {\n\t\treturn %s{}, false\n\t}\n", countAPrim.Size, name) + g.p("\t%s := %s\n", arrays[0].CountField.Name, + g.readCountExpr(countAPrim, fmt.Sprintf("(*b)[%d:%d]", arrays[0].CountOff, arrays[0].CountOff+countAPrim.Size))) + g.p("\tstart := *b\n") + dataStart := arrays[0].CountOff + countAPrim.Size + g.p("\t*b = (*b)[%d:]\n", dataStart) + + elemRead := "read" + pascalCase(arrays[0].ArrayField.TypeName) + g.p("\tfor i := 0; i < %s; i++ {\n", arrays[0].CountField.Name) + g.p("\t\tif _, ok := %s(b); !ok {\n", elemRead) + g.p("\t\t\treturn %s{}, false\n\t\t}\n\t}\n", name) + + g.p("\toffB := len(start) - len(*b)\n") + + countBPrim := g.fieldPrimInfo(arrays[1].CountField) + g.p("\tif len(*b) < %d {\n\t\treturn %s{}, false\n\t}\n", countBPrim.Size, name) + g.p("\t%s := %s\n", arrays[1].CountField.Name, + g.readCountExpr(countBPrim, fmt.Sprintf("(*b)[0:%d]", countBPrim.Size))) + g.p("\t*b = (*b)[%d:]\n", countBPrim.Size) + + elemRead2 := "read" + pascalCase(arrays[1].ArrayField.TypeName) + g.p("\tfor i := 0; i < %s; i++ {\n", arrays[1].CountField.Name) + g.p("\t\tif _, ok := %s(b); !ok {\n", elemRead2) + g.p("\t\t\treturn %s{}, false\n\t\t}\n\t}\n", name) + + g.p("\ttotal := len(start) - len(*b)\n") + g.p("\treturn %s{data: start[:total], offB: offB}, true\n}\n", name) + } +} + +func (g *Generator) emitMessageGetters(ti *TypeInfo) { + m := ti.Decl.Message + name := g.goName(ti) + + receiver := "m" + dataExpr := receiver + if ti.IsMultiArray || len(ti.VarSectionBoundaries) > 0 { + dataExpr = "m.data" + } else if ti.IsExtentStruct { + dataExpr = "m.m" + } + + // Build set of count fields emitted by boundary array handlers (avoid duplicates). + boundaryCountFields := map[string]bool{} + if len(ti.VarSectionBoundaries) > 0 { + for i, f := range m.Fields { + if f.Kind == FieldNormal && isVarLenArray(f) && ti.FieldOffsets[i] >= ti.FixedPrefixSize { + boundaryCountFields[f.ArrayLen] = true + } + } + } + + for i, f := range m.Fields { + if f.Kind != FieldNormal { + continue + } + // Skip count fields whose getter is emitted by the boundary array handler. + if boundaryCountFields[f.Name] { + continue + } + off := ti.FieldOffsets[i] + fieldGoName := pascalCase(f.Name) + + // For messages with boundaries, fields past the prefix use stored offsets. + if len(ti.VarSectionBoundaries) > 0 && off >= ti.FixedPrefixSize { + g.emitBoundaryGetter(ti, i, f) + continue + } + + if isVarLenArray(f) { + g.emitVarArrayGetter(ti, f, dataExpr) + continue + } + if f.ArrayLen != "" { + continue // fixed-size array in message header + } + + if f.DiscRef != "" { + // Union field — return union type from remaining bytes, with disc for safety. + unionGoType := pascalCase(f.TypeName) + discIdx := g.a.findFieldIdx(m, f.DiscRef) + discOff := ti.FieldOffsets[discIdx] + discPrim := g.fieldPrimInfo(m.Fields[discIdx]) + discExpr := g.readPrimFromSlice(discPrim, fmt.Sprintf("%s[%d:%d]", dataExpr, discOff, discOff+discPrim.Size)) + g.p("\nfunc (%s %s) %s() %s {\n", receiver, name, fieldGoName, unionGoType) + g.p("\treturn %s{b: %s[%d:], disc: %s}\n}\n", unionGoType, dataExpr, off, discExpr) + continue + } + + // Scalar field (primitive or enum). + if p, ok := g.fieldPrimInfoOk(f); ok { + // For interleaved multi-array, the second array's count is at m.offB. + if ti.IsMultiArray && ti.MultiArrayKind == 2 && len(ti.VarArrays) >= 2 && + ti.VarArrays[1].CountField.Name == f.Name { + g.p("\nfunc (%s %s) %s() %s {\n", receiver, name, fieldGoName, goType(p)) + g.p("\treturn %s\n}\n", g.readPrimFromSlice(p, fmt.Sprintf("%s[m.offB:m.offB+%d]", dataExpr, p.Size))) + } else { + g.p("\nfunc (%s %s) %s() %s {\n", receiver, name, fieldGoName, goType(p)) + g.p("\treturn %s\n}\n", g.readPrimFromSlice(p, fmt.Sprintf("%s[%d:%d]", dataExpr, off, off+p.Size))) + } + continue + } + + // Embedded struct. + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + embSize := sizeConstName(f.TypeName) + g.p("\nfunc (%s %s) %s() %s {\n", receiver, name, fieldGoName, g.goName(eti)) + g.p("\treturn %s{m: (*[%s]byte)(%s[%d:%s])}\n}\n", + g.goName(eti), embSize, dataExpr, off, fmt.Sprintf("%d+%s", off, embSize)) + continue + } + + // Embedded variable-size message. + if eti, ok := g.a.Types[f.TypeName]; ok && (eti.Decl.Kind == DeclMessage || eti.Decl.Kind == DeclUnion) { + g.p("\nfunc (%s %s) %s() %s {\n", receiver, name, fieldGoName, g.goName(eti)) + if g.a.isStructReader(f.TypeName) { + g.p("\tv, _ := Read%s(%s[%d:])\n\treturn v\n}\n", g.goName(eti), dataExpr, off) + } else { + g.p("\treturn %s(%s[%d:])\n}\n", g.goName(eti), dataExpr, off) + } + continue + } + } +} + +// boundaryInfo computes the position of a field past the fixed prefix in a +// message with VarSectionBoundaries. Returns: +// - baseExpr: Go expression for the boundary base offset (e.g., "m.off1" or "4") +// - relOff: additional static offset from the base +// - endExpr: Go expression for the end of this section ("" if open-ended) +func (g *Generator) boundaryInfo(ti *TypeInfo, fieldIdx int) (baseExpr string, relOff int, endExpr string) { + m := ti.Decl.Message + bounds := ti.VarSectionBoundaries + + // Find which boundary this field is after. + bIdx := -1 + for i, b := range bounds { + if b.FieldIdx < fieldIdx { + bIdx = i + } + } + + // Compute relative offset from section start (sum of fixed-size bytes). + startFieldIdx := 0 + baseExpr = fmt.Sprintf("%d", ti.FixedPrefixSize) + if bIdx >= 0 { + startFieldIdx = bounds[bIdx].FieldIdx + 1 + baseExpr = fmt.Sprintf("m.%s", offName(bIdx)) + } + for j := startFieldIdx; j < fieldIdx; j++ { + fj := m.Fields[j] + if fj.Kind != FieldNormal || ti.FieldOffsets[j] < ti.FixedPrefixSize { + continue + } + sz := g.a.typeSize(fj.TypeName) + if sz > 0 && fj.ArrayLen == "" && fj.DiscRef == "" { + relOff += sz + } + } + + // Find end expression (next boundary or open-ended). + for bi, b := range bounds { + if b.FieldIdx >= fieldIdx { + endExpr = fmt.Sprintf("m.%s", offName(bi)) + break + } + } + return +} + +// emitBoundaryGetter emits a getter for a field past the fixed prefix in a +// message with VarSectionBoundaries. Uses stored boundary offsets (m.off1, etc.) +// to locate fields after variable content. +func (g *Generator) emitBoundaryGetter(ti *TypeInfo, fieldIdx int, f Field) { + m := ti.Decl.Message + name := g.goName(ti) + fieldGoName := pascalCase(f.Name) + dataExpr := "m.data" + + baseExpr, relOff, endExpr := g.boundaryInfo(ti, fieldIdx) + offExprStr := baseExpr + if relOff > 0 { + offExprStr = fmt.Sprintf("%s+%d", baseExpr, relOff) + } + + // --- Union field --- + if f.DiscRef != "" { + unionGoType := pascalCase(f.TypeName) + discIdx := g.a.findFieldIdx(m, f.DiscRef) + discPrim := g.fieldPrimInfo(m.Fields[discIdx]) + discExpr := g.boundaryFieldSlice(ti, discIdx, discPrim.Size) + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, unionGoType) + g.p("\treturn %s{b: %s, disc: %s}\n}\n", + unionGoType, sliceStr(dataExpr, offExprStr, endExpr), + g.readPrimFromSlice(discPrim, discExpr)) + return + } + + // --- Variable-length array --- + if isVarLenArray(f) { + countIdx := g.a.findFieldIdx(m, f.ArrayLen) + countPrim := g.fieldPrimInfo(m.Fields[countIdx]) + countSlice := g.boundaryFieldSlice(ti, countIdx, countPrim.Size) + elemSize := g.a.typeSize(f.TypeName) + + if elemSize >= 0 { + if isByteSliceType(f.TypeName) { + g.emitBoundaryByteSliceGetter(ti, f, offExprStr, countPrim, countSlice) + } else if p, isPrim := primitives[f.TypeName]; isPrim { + g.p("\nfunc (m %s) %s(i int) %s {\n", name, fieldGoName, goType(p)) + g.p("\toff := %s + i*%d\n", offExprStr, elemSize) + g.p("\treturn %s\n}\n", g.readPrimFromSlice(p, fmt.Sprintf("%s[off:off+%d]", dataExpr, elemSize))) + } else if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + sizeConst := sizeConstName(f.TypeName) + g.p("\nfunc (m %s) %s(i int) %s {\n", name, fieldGoName, g.goName(eti)) + g.p("\toff := %s + i*%s\n", offExprStr, sizeConst) + g.p("\treturn %s{m: (*[%s]byte)(%s[off:off+%s])}\n}\n", + g.goName(eti), sizeConst, dataExpr, sizeConst) + } + } else { + iterInfo := g.a.Iterators[f.TypeName] + if iterInfo != nil { + iterType := pascalCase(iterInfo.ElemMsgName) + "Iter" + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, iterType) + g.p("\t%s := %s\n", f.ArrayLen, + g.readCountExpr(countPrim, countSlice)) + g.p("\treturn %s{b: %s[%s:], count: %s}\n}\n", + iterType, dataExpr, offExprStr, f.ArrayLen) + } + } + + // Count getter. + g.p("\nfunc (m %s) %s() %s {\n", name, pascalCase(f.ArrayLen), goType(countPrim)) + g.p("\treturn %s\n}\n", + g.readPrimFromSlice(countPrim, countSlice)) + return + } + if f.ArrayLen != "" { + return + } + + // --- Scalar (primitive or enum) --- + if p, ok := g.fieldPrimInfoOk(f); ok { + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, goType(p)) + g.p("\to := %s\n", offExprStr) + g.p("\treturn %s\n}\n", g.readPrimFromSlice(p, fmt.Sprintf("%s[o:o+%d]", dataExpr, p.Size))) + return + } + + // --- Embedded fixed struct --- + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + embSize := sizeConstName(f.TypeName) + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, g.goName(eti)) + g.p("\to := %s\n", offExprStr) + g.p("\treturn %s{m: (*[%s]byte)(%s[o:o+%s])}\n}\n", + g.goName(eti), embSize, dataExpr, embSize) + return + } + + // --- Embedded variable-size message --- + if eti, ok := g.a.Types[f.TypeName]; ok { + sl := sliceStr(dataExpr, offExprStr, endExpr) + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, g.goName(eti)) + if g.a.isStructReader(f.TypeName) { + g.p("\tv, _ := Read%s(%s)\n\treturn v\n}\n", g.goName(eti), sl) + } else { + g.p("\treturn %s(%s)\n}\n", g.goName(eti), sl) + } + return + } +} + +// boundaryFieldSlice returns a Go slice expression for reading a field at +// fieldIdx within a message with stored boundary offsets. +func (g *Generator) boundaryFieldSlice(ti *TypeInfo, fieldIdx, size int) string { + dataExpr := "m.data" + + // If in fixed prefix, use static offset. + if ti.FieldOffsets[fieldIdx] < ti.FixedPrefixSize { + off := ti.FieldOffsets[fieldIdx] + return fmt.Sprintf("%s[%d:%d]", dataExpr, off, off+size) + } + + base, relOff, _ := g.boundaryInfo(ti, fieldIdx) + if relOff > 0 { + return fmt.Sprintf("%s[%s+%d:%s+%d]", dataExpr, base, relOff, base, relOff+size) + } + return fmt.Sprintf("%s[%s:%s+%d]", dataExpr, base, base, size) +} + +// emitBoundaryByteSliceGetter emits a byte-slice getter for a field past +// the fixed prefix, using boundary offsets. +func (g *Generator) emitBoundaryByteSliceGetter(ti *TypeInfo, f Field, offExprStr string, countPrim PrimInfo, countSlice string) { + name := g.goName(ti) + fieldGoName := pascalCase(f.Name) + dataExpr := "m.data" + + countExpr := g.readCountExpr(countPrim, countSlice) + + switch f.TypeName { + case "npchar": + g.p("\nfunc (m %s) %s() string {\n", name, fieldGoName) + g.p("\tn := %s\n", countExpr) + g.p("\to := %s\n", offExprStr) + g.p("\treturn strings.TrimRight(string(%s[o:o+n]), \"\\x00\")\n}\n", dataExpr) + case "spchar": + g.p("\nfunc (m %s) %s() string {\n", name, fieldGoName) + g.p("\tn := %s\n", countExpr) + g.p("\to := %s\n", offExprStr) + g.p("\treturn strings.TrimRight(string(%s[o:o+n]), \" \")\n}\n", dataExpr) + default: + g.p("\nfunc (m %s) %s() []byte {\n", name, fieldGoName) + g.p("\tn := %s\n", countExpr) + g.p("\to := %s\n", offExprStr) + g.p("\treturn %s[o:o+n]\n}\n", dataExpr) + } +} + +func (g *Generator) emitVarArrayGetter(ti *TypeInfo, f Field, dataExpr string) { + m := ti.Decl.Message + name := g.goName(ti) + fieldGoName := pascalCase(f.Name) + + elemSize := g.a.typeSize(f.TypeName) + + countIdx := g.a.findFieldIdx(m, f.ArrayLen) + countField := m.Fields[countIdx] + countOff := ti.FieldOffsets[countIdx] + countPrim := g.fieldPrimInfo(countField) + + if elemSize >= 0 { + // Fixed-size elements. + if isByteSliceType(f.TypeName) { + // Byte-slice type: return []byte slice. + g.emitByteSliceGetter(ti, f, countOff, countPrim) + return + } + // Index-based access. + if _, isPrim := primitives[f.TypeName]; isPrim { + // Primitive array. + p := primitives[f.TypeName] + g.p("\nfunc (m %s) %s(i int) %s {\n", name, fieldGoName, goType(p)) + g.p("\toff := %d + i*%d\n", ti.TotalFixedSize, elemSize) + g.p("\treturn %s\n}\n", g.readPrimFromSlice(p, fmt.Sprintf("m[off : off+%d]", elemSize))) + } else if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + // Struct array. + sizeConst := sizeConstName(f.TypeName) + g.p("\nfunc (m %s) %s(i int) %s {\n", name, fieldGoName, g.goName(eti)) + g.p("\toff := %d + i*%s\n", ti.TotalFixedSize, sizeConst) + g.p("\treturn %s{m: (*[%s]byte)(m[off : off+%s])}\n}\n", + g.goName(eti), sizeConst, sizeConst) + } + return + } + + // Variable-size elements: iterator. + iterInfo := g.a.Iterators[f.TypeName] + if iterInfo == nil { + return + } + iterType := pascalCase(iterInfo.ElemMsgName) + "Iter" + + // Determine the data start offset for this array. + var dataStart int + if ti.IsMultiArray { + // For multi-array, iterators use offB boundaries. + g.emitMultiArrayIterGetter(ti, f, countField, countOff, countPrim, iterType, dataExpr) + return + } + + dataStart = ti.TotalFixedSize + + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, iterType) + g.p("\t%s := %s\n", countField.Name, + g.readCountExpr(countPrim, fmt.Sprintf("m[%d:%d]", countOff, countOff+countPrim.Size))) + g.p("\treturn %s{\n\t\tb: []byte(m[%d:]),\n\t\tcount: %s,\n\t}\n}\n", + iterType, dataStart, countField.Name) +} + +// emitByteSliceGetter emits a getter that returns []byte (or string for +// npchar/spchar) for a variable-length array of byte-sized elements. +func (g *Generator) emitByteSliceGetter(ti *TypeInfo, f Field, countOff int, countPrim PrimInfo) { + name := g.goName(ti) + fieldGoName := pascalCase(f.Name) + dataStart := ti.TotalFixedSize + + countExpr := g.readCountExpr(countPrim, fmt.Sprintf("m[%d:%d]", countOff, countOff+countPrim.Size)) + + switch f.TypeName { + case "npchar": + g.p("\nfunc (m %s) %s() string {\n", name, fieldGoName) + g.p("\tn := %s\n", countExpr) + g.p("\treturn strings.TrimRight(string(m[%d:%d+n]), \"\\x00\")\n}\n", dataStart, dataStart) + case "spchar": + g.p("\nfunc (m %s) %s() string {\n", name, fieldGoName) + g.p("\tn := %s\n", countExpr) + g.p("\treturn strings.TrimRight(string(m[%d:%d+n]), \" \")\n}\n", dataStart, dataStart) + default: + // u8, char → []byte + g.p("\nfunc (m %s) %s() []byte {\n", name, fieldGoName) + g.p("\tn := %s\n", countExpr) + g.p("\treturn m[%d : %d+n]\n}\n", dataStart, dataStart) + } +} + +func (g *Generator) emitMultiArrayIterGetter(ti *TypeInfo, f, countField Field, countOff int, countPrim PrimInfo, iterType, dataExpr string) { + name := g.goName(ti) + fieldGoName := pascalCase(f.Name) + + // Determine which array this is (first or second). + arrayIdx := 0 + for i, va := range ti.VarArrays { + if va.ArrayField.Name == f.Name { + arrayIdx = i + break + } + } + + g.p("\nfunc (m %s) %s() %s {\n", name, fieldGoName, iterType) + if arrayIdx > 0 && ti.MultiArrayKind == 2 { + // For interleaved multi-array, the second count is at m.offB (not a fixed offset). + g.p("\t%s := %s\n", countField.Name, + g.readCountExpr(countPrim, fmt.Sprintf("%s[m.offB:m.offB+%d]", dataExpr, countPrim.Size))) + } else { + g.p("\t%s := %s\n", countField.Name, + g.readCountExpr(countPrim, fmt.Sprintf("%s[%d:%d]", dataExpr, countOff, countOff+countPrim.Size))) + } + + if arrayIdx == 0 { + if ti.MultiArrayKind == 1 { + g.p("\treturn %s{b: %s[%d:m.offB], count: %s}\n}\n", + iterType, dataExpr, ti.TotalFixedSize, countField.Name) + } else { + g.p("\treturn %s{b: %s[%d:m.offB], count: %s}\n}\n", + iterType, dataExpr, countOff+countPrim.Size, countField.Name) + } + } else { + if ti.MultiArrayKind == 1 { + g.p("\treturn %s{b: %s[m.offB:], count: %s}\n}\n", + iterType, dataExpr, countField.Name) + } else { + g.p("\treturn %s{b: %s[m.offB+%d:], count: %s}\n}\n", + iterType, dataExpr, countPrim.Size, countField.Name) + } + } +} + +func (g *Generator) emitIteratorsForMessage(ti *TypeInfo) { + m := ti.Decl.Message + for _, f := range m.Fields { + if f.Kind != FieldNormal || !isVarLenArray(f) { + continue + } + elemSize := g.a.typeSize(f.TypeName) + if elemSize >= 0 { + continue + } + // This field needs an iterator. + iterInfo := g.a.Iterators[f.TypeName] + if iterInfo == nil || iterInfo.Created { + continue + } + iterInfo.Created = true + + elemGoName := pascalCase(iterInfo.ElemMsgName) + iterType := elemGoName + "Iter" + accessorName := pascalCase(singularize(iterInfo.FieldName)) + + elemTi := g.a.Types[f.TypeName] + if elemTi != nil && g.isByteSliceMessage(elemTi) { + g.emitBlobIterator(elemTi, iterType, elemGoName, accessorName) + } else { + g.emitGenericIterator(iterType, elemGoName, accessorName) + } + } +} + +// emitBlobIterator generates an optimized iterator for blob-pattern elements. +// The struct stores curLen instead of cur, and the accessor returns the +// iterator's b directly (no sub-slice). Next() inlines the size computation. +func (g *Generator) emitBlobIterator(elemTi *TypeInfo, iterType, elemGoName, accessorName string) { + va := elemTi.VarArrays[0] + lenPrim := g.fieldPrimInfo(va.CountField) + headerSize := elemTi.TotalFixedSize + + alignSize := elemTi.AlignSize + + g.p("\n// %s iterates over variable-size %s entries.\n", iterType, elemGoName) + g.p("type %s struct {\n", iterType) + g.p("\tb []byte\n") + g.p("\tcount int\n") + g.p("\ti int\n") + g.p("\tcurLen int\n") + g.p("}\n") + + g.p("\nfunc (it *%s) Next() bool {\n", iterType) + // Advance past previous element with explicit bounds guard. + g.p("\tb := it.b\n") + g.p("\tif it.curLen > 0 {\n") + g.p("\t\tif len(b) < it.curLen {\n\t\t\treturn false\n\t\t}\n") + g.p("\t\tb = b[it.curLen:]\n") + g.p("\t\tit.curLen = 0\n") + g.p("\t}\n") + g.p("\tif it.i >= it.count {\n\t\tit.b = b\n\t\treturn false\n\t}\n") + // Inline size computation for blob element. + g.p("\tif len(b) < %d {\n\t\treturn false\n\t}\n", headerSize) + g.p("\tn := %s\n", g.readCountExpr(lenPrim, fmt.Sprintf("b[%d:%d]", va.CountOff, va.CountOff+lenPrim.Size))) + if alignSize > 0 { + g.p("\tpadded := (n + %d) &^ %d\n", alignSize-1, alignSize-1) + g.p("\ttotal := %d + padded\n", headerSize) + } else { + g.p("\ttotal := %d + n\n", headerSize) + } + g.p("\tif len(b) < total {\n\t\treturn false\n\t}\n") + g.p("\tit.b = b\n") + g.p("\tit.curLen = total\n") + g.p("\tit.i++\n") + g.p("\treturn true\n}\n") + + // Accessor returns the element type aliasing it.b directly (no sub-slice). + g.p("\nfunc (it *%s) %s() %s {\n", iterType, accessorName, elemGoName) + g.p("\treturn %s(it.b)\n", elemGoName) + g.p("}\n") +} + +// emitGenericIterator generates a standard iterator using readElem + cur. +func (g *Generator) emitGenericIterator(iterType, elemGoName, accessorName string) { + readFn := "read" + elemGoName + + g.p("\n// %s iterates over variable-size %s entries.\n", iterType, elemGoName) + g.p("type %s struct {\n", iterType) + g.p("\tb []byte\n") + g.p("\tcount int\n") + g.p("\ti int\n") + g.p("\tcur %s\n", elemGoName) + g.p("}\n") + + g.p("\nfunc (it *%s) Next() bool {\n", iterType) + g.p("\tif it.i >= it.count {\n\t\treturn false\n\t}\n") + g.p("\tvar ok bool\n") + g.p("\tit.cur, ok = %s(&it.b)\n", readFn) + g.p("\tif !ok {\n\t\treturn false\n\t}\n") + g.p("\tit.i++\n") + g.p("\treturn true\n}\n") + + g.p("\nfunc (it *%s) %s() %s {\n", iterType, accessorName, elemGoName) + g.p("\treturn it.cur\n}\n") +} + +// isByteSliceMessage returns true if a message type consists of a single +// byte-slice variable-length array plus optional alignment and fixed fields. +// Used to select the optimized blob iterator for these elements. +func (g *Generator) isByteSliceMessage(ti *TypeInfo) bool { + if ti.Decl.Kind != DeclMessage { + return false + } + if len(ti.VarArrays) != 1 || ti.HasVarSizeArrays || ti.HasExtent { + return false + } + if !isByteSliceType(ti.VarArrays[0].ArrayField.TypeName) { + return false + } + m := ti.Decl.Message + for j := ti.VarArrays[0].ArrayIdx + 1; j < len(m.Fields); j++ { + if m.Fields[j].Kind != FieldAlign { + return false + } + } + return true +} diff --git a/go/msgparse/gen_string.go b/go/msgparse/gen_string.go new file mode 100644 index 00000000..88083c4e --- /dev/null +++ b/go/msgparse/gen_string.go @@ -0,0 +1,359 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "strconv" +) + +// emitStringMethods generates String() methods for all types and name +// functions for enums. +func (g *Generator) emitStringMethods() { + g.p("\n" + sectionSep + "// Pretty-printing\n" + sectionSep) + + for _, name := range g.a.Order { + ti := g.a.Types[name] + switch ti.Decl.Kind { + case DeclEnum: + g.emitEnumNameFunc(ti) + case DeclStruct: + g.emitStructString(ti) + case DeclMessage: + g.emitMessageString(ti) + case DeclUnion: + g.emitUnionString(ti) + } + } +} + +// emitEnumNameFunc generates a function that returns the string name for an +// enum value. Example: +// +// func SensorTypeName(v uint16) string { +// switch v { +// case SENS_ACCEL: return "SENS_ACCEL" +// ... +// default: return fmt.Sprintf("sensor_type(%d)", v) +// } +// } +func (g *Generator) emitEnumNameFunc(ti *TypeInfo) { + e := ti.Decl.Enum + goName := g.goName(ti) + gt := enumGoType(e) + funcName := goName + "Name" + + g.p("\nfunc %s(v %s) string {\n", funcName, gt) + g.p("\tswitch v {\n") + for _, c := range e.Constants { + g.p("\tcase %s:\n\t\treturn %q\n", c.Name, c.Name) + } + // Default: show the raw value. + g.p("\tdefault:\n\t\treturn fmt.Sprintf(\"%s(%%v)\", v)\n", e.Name) + g.p("\t}\n}\n") +} + +// emitStructString generates a String() method for a fixed-size struct. +func (g *Generator) emitStructString(ti *TypeInfo) { + s := ti.Decl.Struct + name := g.goName(ti) + + g.p("\nfunc (m %s) String() string {\n", name) + g.p("\tvar b strings.Builder\n") + g.p("\tb.WriteString(\"%s{\")\n", name) + + first := true + for i, f := range s.Fields { + if f.Kind != FieldNormal { + continue + } + _ = ti.FieldOffsets[i] // just to confirm we have offsets + fieldGoName := pascalCase(f.Name) + sep := "" + if !first { + sep = ", " + } + first = false + + g.emitFieldPrint(ti, f, fieldGoName, sep, false) + } + + g.p("\tb.WriteString(\"}\")\n") + g.p("\treturn b.String()\n}\n") +} + +// emitMessageString generates a String() method for a variable-size message. +func (g *Generator) emitMessageString(ti *TypeInfo) { + m := ti.Decl.Message + name := g.goName(ti) + + g.p("\nfunc (m %s) String() string {\n", name) + g.p("\tvar b strings.Builder\n") + g.p("\tb.WriteString(\"%s{\")\n", name) + + // Skip count fields (they're implicit from the array they count). + countFields := map[string]bool{} + for _, f := range m.Fields { + if f.Kind == FieldNormal && isVarLenArray(f) { + countFields[f.ArrayLen] = true + } + } + + // Also skip disc fields (they're shown via the union). + discFields := map[string]bool{} + for _, f := range m.Fields { + if f.Kind == FieldNormal && f.DiscRef != "" { + discFields[f.DiscRef] = true + } + } + + first := true + for _, f := range m.Fields { + if f.Kind != FieldNormal { + continue + } + if countFields[f.Name] || discFields[f.Name] { + continue + } + fieldGoName := pascalCase(f.Name) + sep := "" + if !first { + sep = ", " + } + first = false + + g.emitFieldPrint(ti, f, fieldGoName, sep, true) + } + + g.p("\tb.WriteString(\"}\")\n") + g.p("\treturn b.String()\n}\n") +} + +// emitFieldPrint emits code to print a single field into a strings.Builder. +// isMessage indicates whether the parent type is a message (vs struct). +func (g *Generator) emitFieldPrint(ti *TypeInfo, f Field, fieldGoName, sep string, isMessage bool) { + prefix := sep + f.Name + ": " + + if f.DiscRef != "" { + // Union field — print the union's String() method. + g.p("\tfmt.Fprintf(&b, \"%s%%v\", m.%s())\n", prefix, fieldGoName) + return + } + + if isVarLenArray(f) { + g.emitVarArrayPrint(ti, f, fieldGoName, prefix) + return + } + + if f.ArrayLen != "" { + // Fixed-size array. + n, err := strconv.Atoi(f.ArrayLen) + if err != nil { + return + } + p, isPrim := primitives[f.TypeName] + if isPrim && p.IsChar { + // char array — print as string/bytes. + if p.CharKind == "" { + g.p("\tfmt.Fprintf(&b, \"%s%%x\", m.%s())\n", prefix, fieldGoName) + } else { + g.p("\tfmt.Fprintf(&b, \"%s%%q\", m.%s())\n", prefix, fieldGoName) + } + return + } + if isPrim { + // Fixed prim array — print each element. + g.p("\tfmt.Fprintf(&b, \"%s[\")\n", prefix) + g.p("\tfor i := 0; i < %d; i++ {\n", n) + g.p("\t\tif i > 0 {\n\t\t\tb.WriteString(\", \")\n\t\t}\n") + g.emitPrimFormatExpr(p, fmt.Sprintf("m.%s(i)", fieldGoName)) + g.p("\t}\n") + g.p("\tb.WriteString(\"]\")\n") + return + } + return + } + + // Scalar: prim, enum, embedded struct, or embedded message. + if p, ok := primitives[f.TypeName]; ok { + g.p("\tfmt.Fprintf(&b, \"%s", prefix) + g.p("%s\", m.%s())\n", primFmtVerb(p), fieldGoName) + return + } + + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclEnum { + enumFuncName := g.goName(eti) + "Name" + g.p("\tfmt.Fprintf(&b, \"%s%%s\", %s(m.%s()))\n", prefix, enumFuncName, fieldGoName) + return + } + + // Embedded struct or message — use its String() method. + g.p("\tfmt.Fprintf(&b, \"%s%%v\", m.%s())\n", prefix, fieldGoName) +} + +// emitVarArrayPrint emits code to print a variable-length array field. +func (g *Generator) emitVarArrayPrint(ti *TypeInfo, f Field, fieldGoName, prefix string) { + elemSize := g.a.typeSize(f.TypeName) + + if isByteSliceType(f.TypeName) { + // Blob field — show length and hex. + p, _ := primitives[f.TypeName] + if p.CharKind != "" { + // npchar/spchar → show as string. + g.p("\tfmt.Fprintf(&b, \"%s%%q\", m.%s())\n", prefix, fieldGoName) + } else { + // u8/char → show as hex with length. + g.p("\t{\n") + g.p("\t\td := m.%s()\n", fieldGoName) + g.p("\t\tif len(d) <= 32 {\n") + g.p("\t\t\tfmt.Fprintf(&b, \"%s%%x\", d)\n", prefix) + g.p("\t\t} else {\n") + g.p("\t\t\tfmt.Fprintf(&b, \"%s%%x...(%%d bytes)\", d[:32], len(d))\n", prefix) + g.p("\t\t}\n") + g.p("\t}\n") + } + return + } + + if elemSize >= 0 { + // Fixed-size elements with index-based access. + if p, isPrim := primitives[f.TypeName]; isPrim { + // Prim array with count. + countField := f.ArrayLen + g.p("\t{\n") + g.p("\t\tn := int(m.%s())\n", pascalCase(countField)) + g.p("\t\tfmt.Fprintf(&b, \"%s[\")\n", prefix) + g.p("\t\tfor i := 0; i < n && i < 64; i++ {\n") + g.p("\t\t\tif i > 0 {\n\t\t\t\tb.WriteString(\", \")\n\t\t\t}\n") + g.emitPrimFormatExprIndent(p, fmt.Sprintf("m.%s(i)", fieldGoName), "\t\t\t") + g.p("\t\t}\n") + g.p("\t\tif n > 64 {\n\t\t\tfmt.Fprintf(&b, \", ...(%%d total)\", n)\n\t\t}\n") + g.p("\t\tb.WriteString(\"]\")\n") + g.p("\t}\n") + return + } + + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + countField := f.ArrayLen + g.p("\t{\n") + g.p("\t\tn := int(m.%s())\n", pascalCase(countField)) + g.p("\t\tfmt.Fprintf(&b, \"%s[\")\n", prefix) + g.p("\t\tfor i := 0; i < n && i < 64; i++ {\n") + g.p("\t\t\tif i > 0 {\n\t\t\t\tb.WriteString(\", \")\n\t\t\t}\n") + g.p("\t\t\tfmt.Fprintf(&b, \"%%v\", m.%s(i))\n", fieldGoName) + g.p("\t\t}\n") + g.p("\t\tif n > 64 {\n\t\t\tfmt.Fprintf(&b, \", ...(%%d total)\", n)\n\t\t}\n") + g.p("\t\tb.WriteString(\"]\")\n") + g.p("\t}\n") + return + } + return + } + + // Variable-size elements: use iterator. + iterInfo := g.a.Iterators[f.TypeName] + if iterInfo == nil { + g.p("\tfmt.Fprintf(&b, \"%s[...]\")\n", prefix) + return + } + accessorName := pascalCase(singularize(iterInfo.FieldName)) + + g.p("\t{\n") + g.p("\t\titer := m.%s()\n", fieldGoName) + g.p("\t\tfmt.Fprintf(&b, \"%s[\")\n", prefix) + g.p("\t\ti := 0\n") + g.p("\t\tfor iter.Next() {\n") + g.p("\t\t\tif i > 0 {\n\t\t\t\tb.WriteString(\", \")\n\t\t\t}\n") + g.p("\t\t\tif i >= 64 {\n") + g.p("\t\t\t\tb.WriteString(\"...\")\n") + g.p("\t\t\t\tbreak\n") + g.p("\t\t\t}\n") + g.p("\t\t\tfmt.Fprintf(&b, \"%%v\", iter.%s())\n", accessorName) + g.p("\t\t\ti++\n") + g.p("\t\t}\n") + g.p("\t\tb.WriteString(\"]\")\n") + g.p("\t}\n") +} + +// emitUnionString generates a String() method for a union type. +func (g *Generator) emitUnionString(ti *TypeInfo) { + u := ti.Decl.Union + name := g.goName(ti) + + // Detect payload types used by multiple arms. + payloadCount := map[string]int{} + for _, arm := range u.Arms { + if arm.Payload != "void" && arm.Label != "default" { + payloadCount[arm.Payload]++ + } + } + + discEnum := g.a.Types[u.DiscType] + + g.p("\nfunc (m %s) String() string {\n", name) + g.p("\tswitch m.disc {\n") + + for _, arm := range u.Arms { + if arm.Label == "default" { + continue + } + g.p("\tcase %s:\n", arm.Label) + if arm.Payload == "void" { + g.p("\t\treturn %q\n", stripEnumPrefix(arm.Label, discEnum.Decl.Enum.Constants)) + } else { + payloadTi := g.a.Types[arm.Payload] + methodName := g.goName(payloadTi) + if payloadCount[arm.Payload] > 1 { + methodName = armSuffix(stripEnumPrefix(arm.Label, discEnum.Decl.Enum.Constants)) + } + g.p("\t\treturn fmt.Sprintf(\"%s:%%v\", m.As%s())\n", + stripEnumPrefix(arm.Label, discEnum.Decl.Enum.Constants), methodName) + } + } + + // Default arm. + g.p("\tdefault:\n") + hasDefault := false + for _, arm := range u.Arms { + if arm.Label == "default" { + hasDefault = true + if arm.Payload == "void" { + g.p("\t\treturn fmt.Sprintf(\"unknown(%%v)\", m.disc)\n") + } else { + payloadTi := g.a.Types[arm.Payload] + g.p("\t\treturn fmt.Sprintf(\"%%v:%%v\", m.disc, m.As%s())\n", g.goName(payloadTi)) + } + break + } + } + if !hasDefault { + g.p("\t\treturn fmt.Sprintf(\"unknown(%%v)\", m.disc)\n") + } + + g.p("\t}\n}\n") +} + +// primFmtVerb returns the fmt verb for a primitive type. +func primFmtVerb(p PrimInfo) string { + if p.IsFloat { + return "%g" + } + if p.IsChar { + return "%c" + } + if p.Size >= 4 { + return "%d" + } + return "%d" +} + +// emitPrimFormatExpr emits a fmt.Fprintf call to print a primitive value. +func (g *Generator) emitPrimFormatExpr(p PrimInfo, expr string) { + g.emitPrimFormatExprIndent(p, expr, "\t\t") +} + +func (g *Generator) emitPrimFormatExprIndent(p PrimInfo, expr, indent string) { + g.p("%sfmt.Fprintf(&b, \"%s\", %s)\n", indent, primFmtVerb(p), expr) +} diff --git a/go/msgparse/gen_struct.go b/go/msgparse/gen_struct.go new file mode 100644 index 00000000..edc4e4fd --- /dev/null +++ b/go/msgparse/gen_struct.go @@ -0,0 +1,279 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "strconv" + "strings" +) + +func (g *Generator) emitStruct(ti *TypeInfo) { + s := ti.Decl.Struct + name := g.goName(ti) + sizeConst := sizeConstName(s.Name) + + g.p("\n" + sectionSep) + g.p("// %s — fixed %d bytes", name, ti.Size) + g.emitStructSizeComment(s, ti) + g.p("\n" + sectionSep) + + // Type declaration. + g.p("\ntype %s struct {\n\tm *[%s]byte\n}\n", name, sizeConst) + g.p("\nconst %s = %d\n", sizeConst, ti.Size) + + // Read function. + if ti.NeedsCursorRead { + // Cursor-advancing read (lowercase). + g.p("\nfunc read%s(b *[]byte) (%s, bool) {\n", name, name) + g.p("\tif len(*b) < %s {\n\t\treturn %s{}, false\n\t}\n", sizeConst, name) + g.p("\tresult := %s{m: (*[%s]byte)(*b)}\n", name, sizeConst) + g.p("\t*b = (*b)[%s:]\n", sizeConst) + g.p("\treturn result, true\n}\n") + + // Public Read wraps cursor-advancing form. + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + g.p("\treturn read%s(&b)\n}\n", name) + } else { + // Simple public Read. + g.p("\nfunc Read%s(b []byte) (%s, bool) {\n", name, name) + g.p("\tif len(b) < %s {\n\t\treturn %s{}, false\n\t}\n", sizeConst, name) + g.p("\treturn %s{m: (*[%s]byte)(b)}, true\n}\n", name, sizeConst) + } + + // Start function (writer). + g.p("\nfunc Start%s(buf []byte) ([]byte, %s) {\n", name, name) + g.p("\tbuf = append(buf, make([]byte, %s)...)\n", sizeConst) + g.p("\treturn buf, %s{m: (*[%s]byte)(buf[len(buf)-%s:])}\n}\n", name, sizeConst, sizeConst) + + // All getters first. + for i, f := range s.Fields { + if f.Kind != FieldNormal { + continue + } + off := ti.FieldOffsets[i] + g.emitStructFieldGetter(name, f, off, ti) + } + // Then all setters. + for i, f := range s.Fields { + if f.Kind != FieldNormal { + continue + } + off := ti.FieldOffsets[i] + g.emitStructFieldSetter(name, f, off, ti) + } +} + +func (g *Generator) emitStructSizeComment(s *StructDecl, ti *TypeInfo) { + // Build a comment showing field layout. + var parts []string + for i, f := range s.Fields { + switch f.Kind { + case FieldPad: + parts = append(parts, fmt.Sprintf("pad(%d)", f.Size)) + case FieldAlign: + alignPad := ti.FieldOffsets[i] + if i > 0 { + alignPad = ti.FieldOffsets[i] - ti.FieldOffsets[i-1] + if s.Fields[i-1].Kind == FieldNormal { + alignPad = ti.FieldOffsets[i] + ((f.Size - (ti.FieldOffsets[i] % f.Size)) % f.Size) - ti.FieldOffsets[i] + } + } + if alignPad > 0 { + parts = append(parts, fmt.Sprintf("align(%d)=%d", f.Size, alignPad)) + } + case FieldNormal: + sz := g.a.fieldSize(f) + if f.ArrayLen != "" { + // Char array: show as "name(type[N])". + parts = append(parts, fmt.Sprintf("%s(%s[%s])", f.Name, f.TypeName, f.ArrayLen)) + } else if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclEnum { + // Enum field: show as "name(size, underlying)" for non-trivial underlying. + u := eti.Decl.Enum.Underlying + if strings.Contains(u, "[") || u == "u8" || u == "i8" { + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } else { + parts = append(parts, fmt.Sprintf("%s(%d, %s)", f.Name, sz, u)) + } + } else if _, ok := g.a.Types[f.TypeName]; ok { + // Embedded struct: show type name in comment. + parts = append(parts, fmt.Sprintf("%s(%d)", f.TypeName, sz)) + } else if p, ok := primitives[f.TypeName]; ok && p.IsBE { + // Big-endian: annotate with type. + parts = append(parts, fmt.Sprintf("%s(%d, %s)", f.Name, sz, f.TypeName)) + } else { + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } + } + } + if len(parts) > 0 { + joined := strings.Join(parts, " + ") + // Check if it fits on one line with the prefix. + prefix := fmt.Sprintf("// %s — fixed %d bytes: ", g.goName(ti), ti.Size) + if len(prefix)+len(joined) <= 80 { + g.p(": %s", joined) + } else { + g.p(":\n// %s", joined) + } + } +} + +func (g *Generator) emitStructFieldGetter(typeName string, f Field, off int, ti *TypeInfo) { + fieldGoName := pascalCase(f.Name) + + if f.ArrayLen != "" { + n, err := strconv.Atoi(f.ArrayLen) + if err != nil { + return + } + p, isPrim := primitives[f.TypeName] + if isPrim && p.IsChar { + g.emitCharArrayGetter(typeName, f, off, n, p) + return + } + if isPrim { + g.emitPrimArrayGetter(typeName, fieldGoName, off, p) + return + } + return + } + + p, isPrim := primitives[f.TypeName] + if isPrim { + g.emitPrimScalarGetter(typeName, fieldGoName, off, p) + return + } + + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclEnum { + ep := enumPrimInfo(eti.Decl.Enum) + g.emitPrimScalarGetter(typeName, fieldGoName, off, ep) + return + } + + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + embSize := sizeConstName(f.TypeName) + g.p("\nfunc (m %s) %s() %s {\n", typeName, fieldGoName, g.goName(eti)) + g.p("\treturn %s{m: (*[%s]byte)(m.m[%d:%d])}\n}\n", + g.goName(eti), embSize, off, off+eti.Size) + return + } +} + +func (g *Generator) emitStructFieldSetter(typeName string, f Field, off int, ti *TypeInfo) { + fieldGoName := pascalCase(f.Name) + + if f.ArrayLen != "" { + n, err := strconv.Atoi(f.ArrayLen) + if err != nil { + return + } + p, isPrim := primitives[f.TypeName] + if isPrim && p.IsChar { + g.emitCharArraySetter(typeName, f, off, n, p) + return + } + if isPrim { + g.emitPrimArraySetter(typeName, fieldGoName, off, p) + return + } + return + } + + p, isPrim := primitives[f.TypeName] + if isPrim { + g.emitPrimScalarSetter(typeName, fieldGoName, off, p) + return + } + + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclEnum { + ep := enumPrimInfo(eti.Decl.Enum) + g.emitPrimScalarSetter(typeName, fieldGoName, off, ep) + return + } + // Embedded structs have no setter — they return a mutable view. +} + +func (g *Generator) emitCharArrayGetter(typeName string, f Field, off, n int, p PrimInfo) { + fieldGoName := pascalCase(f.Name) + end := off + n + + if p.CharKind == "" { + g.p("\n// %s returns the raw char[%d] field as a byte slice.\n", fieldGoName, n) + g.p("func (m %s) %s() []byte {\n", typeName, fieldGoName) + g.p("\treturn m.m[%d:%d]\n}\n", off, end) + } else { + padByte := "0x00" + if p.CharKind == "sp" { + padByte = "0x20" + } + g.p("\n// %s returns %schar[%d] with trailing %s stripped.\n", fieldGoName, p.CharKind, n, padByte) + g.p("func (m %s) %s() string {\n", typeName, fieldGoName) + g.p("\ts := m.m[%d:%d]\n", off, end) + g.p("\ti := len(s)\n") + g.p("\tfor i > 0 && s[i-1] == %s {\n\t\ti--\n\t}\n", padByte) + g.p("\treturn string(s[:i])\n}\n") + } +} + +func (g *Generator) emitCharArraySetter(typeName string, f Field, off, n int, p PrimInfo) { + fieldGoName := pascalCase(f.Name) + end := off + n + + padByte := "0x00" + if p.CharKind == "sp" { + padByte = "0x20" + } + valType := "string" + if p.CharKind == "" { + valType = "[]byte" + } + g.p("\n// Set%s writes v into %schar[%d], padding with %s or truncating.\n", fieldGoName, p.CharKind, n, padByte) + g.p("func (m %s) Set%s(v %s) {\n", typeName, fieldGoName, valType) + g.p("\tb := m.m[%d:%d]\n", off, end) + g.p("\tn := copy(b, v)\n") + g.p("\tfor i := n; i < len(b); i++ {\n\t\tb[i] = %s\n\t}\n}\n", padByte) +} + +func (g *Generator) emitPrimScalarGetter(typeName, fieldGoName string, off int, p PrimInfo) { + g.p("\nfunc (m %s) %s() %s {\n", typeName, fieldGoName, goType(p)) + if p.Size == 1 { + if p.Signed { + g.p("\treturn int8(m.m[%d])\n", off) + } else { + g.p("\treturn m.m[%d]\n", off) + } + } else { + g.p("\treturn %s\n", g.readPrimFromSlice(p, fmt.Sprintf("m.m[%d:%d]", off, off+p.Size))) + } + g.p("}\n") +} + +func (g *Generator) emitPrimScalarSetter(typeName, fieldGoName string, off int, p PrimInfo) { + g.p("\nfunc (m %s) Set%s(v %s) {\n", typeName, fieldGoName, goType(p)) + if p.Size == 1 { + if p.Signed { + g.p("\tm.m[%d] = byte(v)\n", off) + } else { + g.p("\tm.m[%d] = v\n", off) + } + } else { + g.p("\t%s\n", g.writePrim(p, fmt.Sprintf("m.m[%d:%d]", off, off+p.Size), "v")) + } + g.p("}\n") +} + +func (g *Generator) emitPrimArrayGetter(typeName, fieldGoName string, baseOff int, p PrimInfo) { + g.p("\nfunc (m %s) %s(i int) %s {\n", typeName, fieldGoName, goType(p)) + g.p("\toff := %d + i*%d\n", baseOff, p.Size) + g.p("\treturn %s\n}\n", g.readPrimFromSlice(p, fmt.Sprintf("m.m[off : off+%d]", p.Size))) +} + +func (g *Generator) emitPrimArraySetter(typeName, fieldGoName string, baseOff int, p PrimInfo) { + gt := goType(p) + elemSize := p.Size + g.p("\nfunc (m %s) Set%s(i int, v %s) {\n", typeName, fieldGoName, gt) + g.p("\toff := %d + i*%d\n", baseOff, elemSize) + g.p("\t%s\n}\n", g.writePrim(p, fmt.Sprintf("m.m[off:off+%d]", elemSize), "v")) +} diff --git a/go/msgparse/gen_union.go b/go/msgparse/gen_union.go new file mode 100644 index 00000000..36d08831 --- /dev/null +++ b/go/msgparse/gen_union.go @@ -0,0 +1,118 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +// payloadBytesExpr returns a Go expression that converts a payload read result +// variable to []byte for storage in a union struct. The expression depends on +// whether the payload is a struct (r.m[:]), a struct-reader with data (r.data), +// or a []byte alias ([]byte(r)). +func (g *Generator) payloadBytesExpr(payloadTi *TypeInfo) string { + if payloadTi.Decl.Kind == DeclStruct { + return "r.m[:]" + } + if payloadTi.IsMultiArray || len(payloadTi.VarSectionBoundaries) > 0 { + return "r.data" + } + return "[]byte(r)" +} + +func (g *Generator) emitUnion(ti *TypeInfo) { + u := ti.Decl.Union + name := g.goName(ti) + + g.p("\n" + sectionSep) + g.p("// %s — union on %s (external discriminant)\n", name, u.DiscType) + g.p(sectionSep) + + // Type declaration — struct with byte slice and discriminant for safety checks. + discEnum := g.a.Types[u.DiscType] + discGoType := enumGoType(discEnum.Decl.Enum) + g.p("\ntype %s struct {\n\tb []byte\n\tdisc %s\n}\n", name, discGoType) + + discParam := camelCase(u.DiscType) + + // Cursor-advancing read. + g.p("\nfunc read%s(b *[]byte, %s %s) (%s, bool) {\n", + name, discParam, discGoType, name) + g.p("\tswitch %s {\n", discParam) + + emitArmRead := func(arm UnionArm) { + if arm.Payload == "void" { + g.p("\t\treturn %s{b: (*b)[:0], disc: %s}, true\n", name, discParam) + } else { + payloadTi := g.a.Types[arm.Payload] + g.p("\t\tr, ok := read%s(b)\n", g.goName(payloadTi)) + g.p("\t\tif !ok {\n\t\t\treturn %s{}, false\n\t\t}\n", name) + g.p("\t\treturn %s{b: %s, disc: %s}, true\n", name, g.payloadBytesExpr(payloadTi), discParam) + } + } + + var defaultArm *UnionArm + for _, arm := range u.Arms { + if arm.Label == "default" { + defaultArm = &arm + continue + } + g.p("\tcase %s:\n", arm.Label) + emitArmRead(arm) + } + + g.p("\tdefault:\n") + if defaultArm != nil { + emitArmRead(*defaultArm) + } else { + g.p("\t\treturn %s{}, false\n", name) + } + g.p("\t}\n}\n") + + // Detect payload types used by multiple arms (need per-arm naming). + payloadCount := map[string]int{} + for _, arm := range u.Arms { + if arm.Payload != "void" && arm.Label != "default" { + payloadCount[arm.Payload]++ + } + } + + // As-accessors: one per arm, each checks exactly one disc value. + for _, arm := range u.Arms { + if arm.Payload == "void" || arm.Label == "default" { + continue + } + payloadTi := g.a.Types[arm.Payload] + + // Name: use payload type if unique, arm label if shared. + methodName := g.goName(payloadTi) + if payloadCount[arm.Payload] > 1 { + methodName = armSuffix(stripEnumPrefix(arm.Label, discEnum.Decl.Enum.Constants)) + } + + g.p("\nfunc (m %s) As%s() %s {\n", name, methodName, g.goName(payloadTi)) + g.p("\tif m.disc != %s {\n\t\tpanic(\"wrong union discriminant\")\n\t}\n", arm.Label) + if payloadTi.Decl.Kind == DeclStruct { + sizeConst := sizeConstName(arm.Payload) + g.p("\treturn %s{m: (*[%s]byte)(m.b)}\n}\n", g.goName(payloadTi), sizeConst) + } else if payloadTi.IsExtentStruct { + g.p("\treturn %s{m: (*[%d]byte)(m.b)}\n}\n", g.goName(payloadTi), payloadTi.ExtentTotalSize) + } else if g.a.isStructReader(arm.Payload) { + g.p("\tv, _ := Read%s(m.b)\n\treturn v\n}\n", g.goName(payloadTi)) + } else { + g.p("\treturn %s(m.b)\n}\n", g.goName(payloadTi)) + } + } + // Default arm with non-void payload. + emittedDefault := false + for _, arm := range u.Arms { + if arm.Label == "default" && arm.Payload != "void" && !emittedDefault { + emittedDefault = true + payloadTi := g.a.Types[arm.Payload] + g.p("\nfunc (m %s) As%s() %s {\n", name, g.goName(payloadTi), g.goName(payloadTi)) + if g.a.isStructReader(arm.Payload) { + g.p("\tv, _ := Read%s(m.b)\n\treturn v\n}\n", g.goName(payloadTi)) + } else { + g.p("\treturn %s(m.b)\n}\n", g.goName(payloadTi)) + } + } + } +} diff --git a/go/msgparse/gen_writer.go b/go/msgparse/gen_writer.go new file mode 100644 index 00000000..41d7e63a --- /dev/null +++ b/go/msgparse/gen_writer.go @@ -0,0 +1,1166 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "strings" +) + +func (g *Generator) emitMessageWriter(ti *TypeInfo) { + m := ti.Decl.Message + name := g.goName(ti) + writerName := name + "Writer" + + g.p("\n// %s writes a %s", writerName, m.Name) + g.emitWriterComment(m, ti) + g.p("\n") + + if ti.HasExtent && ti.IsExtentStruct { + g.emitExtentWriter(ti) + return + } + + if ti.IsMultiArray { + g.emitMultiArrayWriter(ti) + return + } + + // Standard Pattern A writer. + g.emitPatternAWriterFull(ti) +} + +func (g *Generator) emitWriterComment(m *MessageDecl, ti *TypeInfo) { + // Simple comment like ": count(4) + leu32 samples[count]." + var parts []string + for _, f := range m.Fields { + switch f.Kind { + case FieldPad: + parts = append(parts, fmt.Sprintf("pad(%d)", f.Size)) + case FieldAlign: + parts = append(parts, fmt.Sprintf("align(%d)", f.Size)) + case FieldExtent: + parts = append(parts, fmt.Sprintf("extent(%s)", f.Ref)) + case FieldNormal: + if f.ArrayLen != "" { + parts = append(parts, fmt.Sprintf("%s %s[%s]", f.TypeName, f.Name, f.ArrayLen)) + } else if f.DiscRef != "" { + parts = append(parts, fmt.Sprintf("%s(%s)", f.Name, f.DiscRef)) + } else { + parts = append(parts, f.Name) + } + } + } + if len(parts) > 0 { + g.p(":\n//\n//\t%s", strings.Join(parts, " + ")) + } +} + +func (g *Generator) emitExtentWriter(ti *TypeInfo) { + m := ti.Decl.Message + name := g.goName(ti) + writerName := name + "Writer" + totalSize := ti.ExtentTotalSize + + g.p("type %s struct {\n\tbuf []byte\n\toff int\n}\n", writerName) + + // Start: pre-allocate all known fields. + g.p("\nfunc Start%s(buf []byte) %s {\n", name, writerName) + g.p("\toff := len(buf)\n") + g.p("\tbuf = append(buf, make([]byte, %d)...)", totalSize) + g.emitExtentStartComment(m, ti) + g.p("\n\treturn %s{buf: buf, off: off}\n}\n", writerName) + + // Set methods for each field (value-chaining). + for i, f := range m.Fields { + if f.Kind != FieldNormal { + continue + } + fieldGoName := pascalCase(f.Name) + off := ti.FieldOffsets[i] + + // Skip the extent length field — it's patched by Finish. + if f.Name == ti.ExtentFieldName { + continue + } + + if p, ok := g.fieldPrimInfoOk(f); ok { + g.p("\nfunc (w %s) Set%s(v %s) %s {\n", writerName, fieldGoName, goType(p), writerName) + g.p("\t%s\n", g.writePrim(p, + fmt.Sprintf("(*[%d]byte)(w.buf[w.off:])[%d:%d]", totalSize, off, off+p.Size), "v")) + g.p("\treturn w\n}\n") + } + } + + // AppendExtra for forward compatibility. + g.p("\nfunc (w %s) AppendExtra(extra []byte) %s {\n", writerName, writerName) + g.p("\tw.buf = append(w.buf, extra...)\n") + g.p("\treturn w\n}\n") + + // Finish: patch extent field. + g.p("\nfunc (w %s) Finish() []byte {\n", writerName) + g.p("\tbodyLen := len(w.buf) - w.off - %d\n", ti.TotalFixedSize) + extPrim := g.extentPrimInfo(ti) + g.p("\t%s\n", g.writePrim(extPrim, + fmt.Sprintf("(*[%d]byte)(w.buf[w.off:])[%d:%d]", totalSize, ti.ExtentFieldOff, ti.ExtentFieldOff+extPrim.Size), + fmt.Sprintf("%s(bodyLen)", goType(extPrim)))) + g.p("\treturn w.buf\n}\n") +} + +func (g *Generator) emitExtentStartComment(m *MessageDecl, ti *TypeInfo) { + // Comment showing what the pre-allocated bytes are for. + var parts []string + for _, f := range m.Fields { + if f.Kind == FieldNormal { + sz := g.a.typeSize(f.TypeName) + if sz > 0 { + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } + } + } + if len(parts) > 0 { + g.p(" // %s", strings.Join(parts, " + ")) + } +} + +type writerCountInfo struct { + goName string // camelCase Go name + goType string // Go type (uint32, uint16, etc.) + deferred bool // true if count field comes after variable content + inline bool // true if SetData handles the count inline (no struct field needed) +} + +// emitPatternAWriterFull generates a Pattern A (pointer-receiver) writer +// for a standard message (not blob, not extent, not multi-array). +func (g *Generator) emitPatternAWriterFull(ti *TypeInfo) { + name := g.goName(ti) + writerName := name + "Writer" + + isUnionWrapper := ti.IsUnionWrapper + + // Build count field info from pre-computed VarArrays. + skipHeader := g.allHeaderIsByteSliceCounts(ti) && !isUnionWrapper + var counts []writerCountInfo + for _, va := range ti.VarArrays { + p := g.fieldPrimInfo(va.CountField) + isInline := skipHeader && isByteSliceType(va.ArrayField.TypeName) && + ti.FieldOffsets[va.CountIdx] < ti.FixedPrefixSize + counts = append(counts, writerCountInfo{ + goName: camelCase(va.CountField.Name), + goType: goType(p), + deferred: ti.FieldOffsets[va.CountIdx] >= ti.FixedPrefixSize, + inline: isInline, + }) + } + + // Needs Resume if any variable-size section requires child writers. + needsResume := ti.WriterPhases > 0 + + needsPhase := ti.WriterPhases > 1 + useHeaderPtr := g.writerUsesHeaderPtr(ti) + headerSize := ti.FixedPrefixSize + if skipHeader { + headerSize = 0 + } + + // Value receivers are safe when all variable content is byte-slice arrays + // and no Resume is needed. Counts may be stored in struct fields (non-inline), + // but since SetData returns the modified writer by value, callers get the + // updated copy. + useValueRecv := !needsResume && allVarArraysByteSlice(ti) + + // Emit type declaration. + maxNL := maxFieldNameLen(counts) + // Check if any deferred counts need offset tracking fields. + for _, c := range counts { + offName := c.goName + "Off" + if len(offName) > maxNL { + maxNL = len(offName) + } + } + if needsPhase && len("phase") > maxNL { + maxNL = len("phase") + } + if useHeaderPtr && len("header") > maxNL { + maxNL = len("header") + } + g.p("type %s struct {\n", writerName) + g.p("\tbuf%s []byte\n", strings.Repeat(" ", maxNL-3+1)) + if useHeaderPtr { + g.p("\theader%s *[%d]byte\n", strings.Repeat(" ", maxNL-6+1), headerSize) + } else { + g.p("\toff%s int\n", strings.Repeat(" ", maxNL-3+1)) + } + for _, c := range counts { + if c.inline { + continue // count is written inline by SetData + } + g.p("\t%s%s %s\n", c.goName, strings.Repeat(" ", maxNL-len(c.goName)+1), c.goType) + if c.deferred { + offName := c.goName + "Off" + g.p("\t%s%s int\n", offName, strings.Repeat(" ", maxNL-len(offName)+1)) + } + } + if needsPhase { + g.p("\t%s%s uint8\n", "phase", strings.Repeat(" ", maxNL-len("phase")+1)) + } + g.p("}\n") + + // Emit Start function. + g.p("\nfunc Start%s(buf []byte) %s {\n", name, writerName) + if isUnionWrapper { + // Defer discriminant allocation. + g.p("\treturn %s{buf: buf, off: len(buf)}\n", writerName) + } else if useHeaderPtr { + g.p("\tbuf = append(buf, make([]byte, %d)...)", headerSize) + g.emitStartComment(ti) + g.p("\n") + g.p("\treturn %s{buf: buf, header: (*[%d]byte)(buf[len(buf)-%d:])}\n", + writerName, headerSize, headerSize) + } else { + g.p("\toff := len(buf)\n") + if headerSize > 0 { + if g.headerUsesAppendHelper(ti) { + g.emitHeaderAlloc(ti) + } else { + g.p("\tbuf = append(buf, make([]byte, %d)...)", headerSize) + g.emitStartComment(ti) + g.p("\n") + } + } + g.p("\treturn %s{buf: buf, off: off}\n", writerName) + } + g.p("}\n") + + // Emit setter methods for fixed header fields. + g.emitWriterHeaderSetters(ti, writerName, useValueRecv) + + // Emit array append methods. + g.emitWriterArrayMethods(ti, writerName, counts) + + // Emit union set/append methods. + g.emitWriterUnionMethods(ti, writerName) + + // Emit embedded struct/message accessors. + g.emitWriterEmbeddedAccessors(ti, writerName) + + // Resume method. + if needsResume { + g.p("\nfunc (w *%s) Resume(buf []byte) {\n\tw.buf = buf\n}\n", writerName) + } + + // Finish method. + g.emitWriterFinish(ti, writerName, counts, needsResume, useValueRecv) +} + +// writerUsesHeaderPtr returns true if a message writer should use a stored +// *[N]byte header pointer instead of off int. This applies to Pattern A writers +// that have a pre-allocated header but no deferred count patching in Finish. +func (g *Generator) writerUsesHeaderPtr(ti *TypeInfo) bool { + if ti == nil || ti.Decl.Kind != DeclMessage { + return false + } + if ti.IsExtentStruct || ti.IsMultiArray { + return false + } + if ti.IsUnionWrapper || ti.FixedPrefixSize == 0 { + return false + } + return len(ti.VarArrays) == 0 +} + +// emitChildWriterReturn emits a return statement that constructs a child writer, +// using header pointer or off as appropriate for the child type. offExpr is a Go +// expression giving the child's start offset within buf. +func (g *Generator) emitChildWriterReturn(childTi *TypeInfo, childWriter, bufExpr, offExpr string) { + if g.writerUsesHeaderPtr(childTi) { + hs := childTi.FixedPrefixSize + g.p("\treturn %s{buf: %s, header: (*[%d]byte)(%s[%s:])}\n", + childWriter, bufExpr, hs, bufExpr, offExpr) + } else { + g.p("\treturn %s{buf: %s, off: %s}\n", + childWriter, bufExpr, offExpr) + } +} + +func maxFieldNameLen(counts []writerCountInfo) int { + maxLen := 3 // "buf" and "off" are 3 and 2 + for _, c := range counts { + if len(c.goName) > maxLen { + maxLen = len(c.goName) + } + } + return maxLen +} + +// allVarArraysByteSlice returns true if the message's only variable content +// is byte-slice arrays (u8/char/npchar/spchar). Such messages don't need +// Resume (no child writers), so all setters can use value receivers. +func allVarArraysByteSlice(ti *TypeInfo) bool { + if len(ti.VarArrays) == 0 || ti.HasVarSizeArrays || ti.HasExtent { + return false + } + for _, va := range ti.VarArrays { + if !isByteSliceType(va.ArrayField.TypeName) { + return false + } + } + return true +} + +// allHeaderIsByteSliceCounts returns true if every field in the fixed prefix +// is a count field for a byte-slice array whose data immediately follows the +// count. When true, the header allocation can be skipped — each SetData will +// do a combined count+data alloc. +// +// This currently only matches single-array messages where count is at offset 0 +// and data follows immediately (e.g., data_blob). Multi-array messages have +// their counts grouped in a header separate from data, so combined allocation +// isn't possible. +func (g *Generator) allHeaderIsByteSliceCounts(ti *TypeInfo) bool { + if len(ti.VarArrays) != 1 || ti.HasVarSizeArrays || ti.HasExtent { + return false + } + va := ti.VarArrays[0] + if !isByteSliceType(va.ArrayField.TypeName) { + return false + } + // The count must be the only thing in the fixed prefix. + countSize := g.a.typeSize(va.CountField.TypeName) + return va.CountOff == 0 && ti.FixedPrefixSize == countSize +} + +func (g *Generator) headerUsesAppendHelper(ti *TypeInfo) bool { + // Use appendLeUXX when the header is just a single count placeholder (4 bytes). + if ti.FixedPrefixSize != 4 || len(ti.VarArrays) != 1 { + return false + } + va := ti.VarArrays[0] + return va.CountOff == 0 && g.fieldPrimInfo(va.CountField).Size == 4 +} + +func (g *Generator) emitHeaderAlloc(ti *TypeInfo) { + f := ti.VarArrays[0].CountField + g.p("\tbuf = %s(buf, 0) // %s placeholder\n", g.appendHelper(g.fieldPrimInfo(f)), f.Name) +} + +func (g *Generator) emitStartComment(ti *TypeInfo) { + m := ti.Decl.Message + var parts []string + off := 0 + for _, f := range m.Fields { + if f.Kind == FieldPad { + parts = append(parts, fmt.Sprintf("pad(%d)", f.Size)) + off += f.Size + } else if f.Kind == FieldAlign { + pad := (f.Size - (off % f.Size)) % f.Size + off += pad + } else if f.Kind == FieldNormal { + if isVarLenArray(f) { + break + } + if f.DiscRef != "" { + break + } + sz := g.a.typeSize(f.TypeName) + if sz < 0 { + break + } + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + off += sz + } + } + if len(parts) > 0 { + g.p(" // %s", strings.Join(parts, " + ")) + } +} + +func (g *Generator) emitWriterHeaderSetters(ti *TypeInfo, writerName string, useValueRecv bool) { + m := ti.Decl.Message + headerSize := ti.FixedPrefixSize + needsPhase := ti.WriterPhases > 1 + useHeaderPtr := g.writerUsesHeaderPtr(ti) + + for i, f := range m.Fields { + if f.Kind != FieldNormal { + continue + } + // Skip count fields, array fields, union fields, and embedded var-size. + if f.ArrayLen != "" || f.DiscRef != "" { + continue + } + // Skip if it's a count field (will be set by Finish). + if ti.CountFields[f.Name] { + continue + } + + // Skip if embedded variable-size message. + if g.a.isVarSizeType(f.TypeName) { + continue + } + + off := ti.FieldOffsets[i] + fieldGoName := pascalCase(f.Name) + + if ti.IsUnionWrapper { + // In union wrappers, the discriminant is deferred. + continue + } + + // Fields beyond the pre-allocated header: check if they're + // discriminants handled by union methods, otherwise emit append-style setters. + if off >= headerSize { + if ti.DiscFields[f.Name] { + continue // handled by union Set/Append methods + } + fieldPhase := ti.FieldPhase[i] + // Emit append-style setter. + if p, ok := g.fieldPrimInfoOk(f); ok { + g.p("\nfunc (w *%s) Set%s(v %s) {\n", writerName, fieldGoName, goType(p)) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tw.buf = %s(w.buf, v)\n", g.appendHelper(p)) + g.p("}\n") + } + continue + } + + if p, ok := g.fieldPrimInfoOk(f); ok { + if useValueRecv { + g.p("\nfunc (w %s) Set%s(v %s) %s {\n", writerName, fieldGoName, goType(p), writerName) + g.p("\t%s\n", g.writerSetPrimVal(p, headerSize, off, "v", useHeaderPtr)) + g.p("\treturn w\n}\n") + } else { + g.p("\nfunc (w *%s) Set%s(v %s) {\n", writerName, fieldGoName, goType(p)) + g.p("\t%s\n", g.writerSetPrimVal(p, headerSize, off, "v", useHeaderPtr)) + g.p("}\n") + } + continue + } + + // Embedded fixed struct in header — return mutable view. + if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + sizeConst := sizeConstName(f.TypeName) + g.p("\nfunc (w *%s) %s() %s {\n", writerName, fieldGoName, g.goName(eti)) + if useHeaderPtr { + g.p("\treturn %s{m: (*[%s]byte)(w.header[%d:])}\n}\n", + g.goName(eti), sizeConst, off) + } else { + g.p("\treturn %s{m: (*[%s]byte)(w.buf[%s:])}\n}\n", + g.goName(eti), sizeConst, offExpr("w.off", off)) + } + } + } +} + +func (g *Generator) emitWriterArrayMethods(ti *TypeInfo, writerName string, counts []writerCountInfo) { + needsPhase := ti.WriterPhases > 1 + + // counts is parallel to ti.VarArrays. + for ci, va := range ti.VarArrays { + f := va.ArrayField + c := counts[ci] + fieldGoName := pascalCase(f.Name) + elemSize := g.a.typeSize(f.TypeName) + fieldPhase := ti.FieldPhase[va.ArrayIdx] + + if elemSize >= 0 { + // Fixed-size elements. + if isByteSliceType(f.TypeName) { + // Byte-slice type: SetXxx(data []byte) + useValueRecv := allVarArraysByteSlice(ti) && ti.WriterPhases == 0 + g.emitByteSliceSetter(ti, va.ArrayIdx, f, writerName, c, needsPhase, fieldPhase, useValueRecv) + } else if p, isPrim := primitives[f.TypeName]; isPrim { + // Primitive array: AppendXxx(v Type) + helper := g.appendHelper(p) + g.p("\nfunc (w *%s) Append%s(v %s) {\n", writerName, fieldGoName, goType(p)) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(c.deferred, c.goName, va.CountField) + g.p("\tw.buf = %s(w.buf, v)\n", helper) + g.p("\tw.%s++\n", c.goName) + g.p("}\n") + } else if eti, ok := g.a.Types[f.TypeName]; ok && eti.Decl.Kind == DeclStruct { + // Struct array: AppendXxx() StructType + sizeConst := sizeConstName(f.TypeName) + g.p("\nfunc (w *%s) Append%s() %s {\n", writerName, fieldGoName, g.goName(eti)) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(c.deferred, c.goName, va.CountField) + g.p("\tw.buf = append(w.buf, make([]byte, %s)...)\n", sizeConst) + g.p("\tw.%s++\n", c.goName) + g.p("\treturn %s{m: (*[%s]byte)(w.buf[len(w.buf)-%s:])}\n}\n", + g.goName(eti), sizeConst, sizeConst) + } + } else { + // Variable-size elements. + if eti, ok := g.a.Types[f.TypeName]; ok { + // Check if element is a union wrapper — generate convenience methods. + if eti.IsUnionWrapper { + g.emitConvenienceAppendMethods(ti, f, eti, writerName, c.goName, c.deferred, needsPhase, fieldPhase, va.CountField) + } else { + // Regular variable-size message: AppendXxx() → child writer. + childWriter := g.goName(eti) + "Writer" + g.p("\nfunc (w *%s) Append%s() %s {\n", writerName, fieldGoName, childWriter) + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(c.deferred, c.goName, va.CountField) + g.p("\tw.%s++\n", c.goName) + g.p("\tchild := Start%s(w.buf)\n", g.goName(eti)) + g.p("\tw.buf = nil\n") + g.p("\treturn child\n}\n") + } + } + } + } +} + +// emitDeferredCountAlloc emits lazy allocation of a count field placeholder +// for counts that come after variable content in the message. +func (g *Generator) emitDeferredCountAlloc(deferred bool, countGoName string, countField Field) { + if !deferred { + return + } + p := g.fieldPrimInfo(countField) + offName := countGoName + "Off" + g.p("\tif w.%s == 0 {\n", offName) + g.p("\t\tw.%s = len(w.buf)\n", offName) + g.p("\t\tw.buf = %s(w.buf, 0)\n", g.appendHelper(p)) + g.p("\t}\n") +} + +// emitByteSliceSetter generates a SetXxx(data []byte) method for byte-slice +// array fields (u8, char, npchar, spchar). Handles alignment padding if a +// FieldAlign directive follows the array field. +// +// When c.inline is true, the count field is NOT pre-allocated in the header. +// SetData does a single combined allocation for count + padded data. +func (g *Generator) emitByteSliceSetter(ti *TypeInfo, fieldIdx int, f Field, writerName string, c writerCountInfo, needsPhase bool, fieldPhase int, useValueRecv bool) { + m := ti.Decl.Message + fieldGoName := pascalCase(f.Name) + + // Find the VarArrayInfo for this field. + var va VarArrayInfo + for _, v := range ti.VarArrays { + if v.ArrayIdx == fieldIdx { + va = v + break + } + } + + // Find alignment directive after this field, if any. + alignSize := 0 + for j := fieldIdx + 1; j < len(m.Fields); j++ { + if m.Fields[j].Kind == FieldAlign { + alignSize = m.Fields[j].Size + break + } + if m.Fields[j].Kind == FieldNormal { + break + } + } + + countPrim := g.fieldPrimInfo(va.CountField) + + if c.inline { + // Combined allocation: count + padded data in one append. + g.p("\nfunc (w %s) Set%s(data []byte) %s {\n", writerName, fieldGoName, writerName) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tn := len(data)\n") + if alignSize > 0 { + g.p("\tpadded := (n + %d) &^ %d\n", alignSize-1, alignSize-1) + g.p("\ttotal := %d + padded\n", countPrim.Size) + } else { + g.p("\ttotal := %d + n\n", countPrim.Size) + } + g.p("\tw.buf = append(w.buf, make([]byte, total)...)\n") + g.p("\t%s\n", g.writePrim(countPrim, + fmt.Sprintf("w.buf[w.off:w.off+%d]", countPrim.Size), + fmt.Sprintf("%s(n)", goType(countPrim)))) + g.p("\tcopy(w.buf[w.off+%d:], data)\n", countPrim.Size) + g.p("\treturn w\n}\n") + return + } + + if useValueRecv { + g.p("\nfunc (w %s) Set%s(data []byte) %s {\n", writerName, fieldGoName, writerName) + } else { + g.p("\nfunc (w *%s) Set%s(data []byte) {\n", writerName, fieldGoName) + } + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(c.deferred, c.goName, va.CountField) + g.p("\tn := len(data)\n") + + if alignSize > 0 { + g.p("\tpadded := (n + %d) &^ %d\n", alignSize-1, alignSize-1) + g.p("\tw.buf = append(w.buf, make([]byte, padded)...)\n") + } else { + g.p("\tw.buf = append(w.buf, make([]byte, n)...)\n") + } + + g.p("\tcopy(w.buf[len(w.buf)-") + if alignSize > 0 { + g.p("padded:") + } else { + g.p("n:") + } + g.p("], data)\n") + + // Set the count field value; Finish will write it to the buffer. + g.p("\tw.%s = %s(n)\n", c.goName, goType(countPrim)) + if useValueRecv { + g.p("\treturn w\n") + } + g.p("}\n") +} + +func (g *Generator) emitConvenienceAppendMethods(parentTi *TypeInfo, arrayField Field, elemTi *TypeInfo, writerName, countGoName string, countDeferred bool, needsPhase bool, fieldPhase int, countField Field) { + elemMsg := elemTi.Decl.Message + fieldGoName := pascalCase(arrayField.Name) + + // Find the discriminant field and union field in the element message. + var unionField Field + for _, f := range elemMsg.Fields { + if f.Kind == FieldNormal && f.DiscRef != "" { + unionField = f + } + } + + // Get the union declaration. + unionTi := g.a.Types[unionField.TypeName] + if unionTi == nil || unionTi.Decl.Kind != DeclUnion { + return + } + unionDecl := unionTi.Decl.Union + discEnum := g.a.Types[unionDecl.DiscType] + discPrim := enumPrimInfo(discEnum.Decl.Enum) + discSize := discPrim.Size + + for _, arm := range unionDecl.Arms { + suffix := armSuffix(stripEnumPrefix(arm.Label, discEnum.Decl.Enum.Constants)) + if arm.Label == "default" { + suffix = "Default" + } + + methodName := fmt.Sprintf("Append%s_%s", fieldGoName, suffix) + + if arm.Payload == "void" { + discVal := arm.Label + if arm.Label == "default" { + discVal = camelCase(g.goName(discEnum)) + g.p("\nfunc (w *%s) %s(%s %s) {\n", writerName, methodName, discVal, goType(discPrim)) + } else { + g.p("\nfunc (w *%s) %s() {\n", writerName, methodName) + } + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(countDeferred, countGoName, countField) + g.p("\tw.buf = %s(w.buf, %s)\n", g.appendHelper(discPrim), discVal) + g.p("\tw.%s++\n", countGoName) + g.p("}\n") + continue + } + + payloadTi := g.a.Types[arm.Payload] + if payloadTi == nil { + continue + } + + if payloadTi.IsFixedSize { + // Fixed-size struct payload: coalesce disc + body into single append. + sizeConst := sizeConstName(arm.Payload) + + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, g.goName(payloadTi)) + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(countDeferred, countGoName, countField) + g.p("\tw.buf = append(w.buf, make([]byte, %d+%s)...)\n", discSize, sizeConst) + g.p("\tp := (*[%d + %s]byte)(w.buf[len(w.buf)-%d-%s:])\n", + discSize, sizeConst, discSize, sizeConst) + g.p("\t%s\n", g.writePrim(discPrim, fmt.Sprintf("p[:%d]", discSize), arm.Label)) + g.p("\tw.%s++\n", countGoName) + g.p("\treturn %s{m: (*[%s]byte)(p[%d:])}\n}\n", + g.goName(payloadTi), sizeConst, discSize) + } else { + // Variable-size payload: coalesce disc + known header. + childWriter := g.goName(payloadTi) + "Writer" + knownSize := g.childWriterKnownSize(payloadTi) + if knownSize > 0 { + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, childWriter) + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(countDeferred, countGoName, countField) + g.p("\tw.buf = append(w.buf, make([]byte, %d+%d)...)", discSize, knownSize) + g.emitCoalesceComment(discSize, knownSize, payloadTi) + g.p("\n") + g.p("\toff := len(w.buf) - %d - %d\n", discSize, knownSize) + g.p("\t%s\n", g.writePrim(discPrim, + fmt.Sprintf("(*[%d + %d]byte)(w.buf[off:])[:%d]", discSize, knownSize, discSize), arm.Label)) + g.p("\tw.%s++\n", countGoName) + g.p("\tbuf := w.buf\n") + g.p("\tw.buf = nil\n") + g.emitChildWriterReturn(payloadTi, childWriter, "buf", fmt.Sprintf("off + %d", discSize)) + g.p("}\n") + } else { + // Can't coalesce — just append disc, then start child. + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, childWriter) + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, fieldPhase) + g.emitDeferredCountAlloc(countDeferred, countGoName, countField) + helper := g.appendHelper(discPrim) + g.p("\tw.buf = %s(w.buf, %s)\n", helper, arm.Label) + g.p("\tw.%s++\n", countGoName) + g.p("\tchild := Start%s(w.buf)\n", g.goName(payloadTi)) + g.p("\tw.buf = nil\n") + g.p("\treturn child\n}\n") + } + } + } +} + +func (g *Generator) emitCoalesceComment(discSize, knownSize int, payloadTi *TypeInfo) { + // Add comment explaining what the coalesced bytes are. + if payloadTi.HasExtent && payloadTi.IsExtentStruct { + m := payloadTi.Decl.Message + var parts []string + parts = append(parts, fmt.Sprintf("discriminant(%d)", discSize)) + for _, f := range m.Fields { + if f.Kind == FieldNormal { + sz := g.a.typeSize(f.TypeName) + if sz > 0 { + parts = append(parts, fmt.Sprintf("%s(%d)", f.Name, sz)) + } + } + } + g.p(" // %s", strings.Join(parts, " + ")) + } +} + +func (g *Generator) emitWriterUnionMethods(ti *TypeInfo, writerName string) { + m := ti.Decl.Message + headerSize := ti.FixedPrefixSize + needsPhase := ti.WriterPhases > 1 + useHeaderPtr := g.writerUsesHeaderPtr(ti) + + for fi, f := range m.Fields { + if f.Kind != FieldNormal || f.DiscRef == "" { + continue + } + + unionTi := g.a.Types[f.TypeName] + if unionTi == nil || unionTi.Decl.Kind != DeclUnion { + continue + } + unionDecl := unionTi.Decl.Union + discEnum := g.a.Types[unionDecl.DiscType] + discPrim := enumPrimInfo(discEnum.Decl.Enum) + discSize := discPrim.Size + fieldPhase := ti.FieldPhase[fi] + + // Check if the discriminant field is in the pre-allocated header. + // Union wrappers defer header allocation, so the disc is never pre-allocated. + discInHeader := false + discOff := 0 + if !ti.IsUnionWrapper { + for i2, f2 := range m.Fields { + if f2.Kind == FieldNormal && f2.Name == f.DiscRef { + discOff = ti.FieldOffsets[i2] + discInHeader = discOff+discSize <= headerSize + break + } + } + } + + fieldGoName := pascalCase(f.Name) + + for _, arm := range unionDecl.Arms { + suffix := armSuffix(stripEnumPrefix(arm.Label, discEnum.Decl.Enum.Constants)) + if arm.Label == "default" { + suffix = "Default" + } + + methodName := fmt.Sprintf("Set%s_%s", fieldGoName, suffix) + + if arm.Payload == "void" { + discVal := arm.Label + if arm.Label == "default" { + discVal = camelCase(unionDecl.DiscType) + g.p("\nfunc (w *%s) %s(%s %s) {\n", + writerName, methodName, discVal, goType(discPrim)) + } else { + g.p("\nfunc (w *%s) %s() {\n", writerName, methodName) + } + if discInHeader { + g.p("\t%s\n", g.writerSetPrimVal(discPrim, headerSize, discOff, discVal, useHeaderPtr)) + } else { + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tw.buf = %s(w.buf, %s)\n", g.appendHelper(discPrim), discVal) + } + g.p("}\n") + continue + } + + payloadTi := g.a.Types[arm.Payload] + if payloadTi == nil { + continue + } + + if discInHeader { + // Discriminant is in the pre-allocated header — write it there, + // only append payload bytes. + g.emitUnionArmDiscInHeader(writerName, methodName, payloadTi, + discPrim, headerSize, discOff, arm, needsPhase, fieldPhase, useHeaderPtr) + } else if payloadTi.IsFixedSize { + // Fixed-size struct: coalesce disc + body. + sizeConst := sizeConstName(arm.Payload) + + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, g.goName(payloadTi)) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tw.buf = append(w.buf, make([]byte, %d+%s)...)\n", discSize, sizeConst) + g.p("\tp := (*[%d + %s]byte)(w.buf[len(w.buf)-%d-%s:])\n", + discSize, sizeConst, discSize, sizeConst) + g.p("\t%s\n", g.writePrim(discPrim, + fmt.Sprintf("p[:%d]", discSize), arm.Label)) + g.p("\treturn %s{m: (*[%s]byte)(p[%d:])}\n}\n", + g.goName(payloadTi), sizeConst, discSize) + } else { + // Variable-size payload. + childWriter := g.goName(payloadTi) + "Writer" + knownSize := g.childWriterKnownSize(payloadTi) + if knownSize > 0 { + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, childWriter) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tw.buf = append(w.buf, make([]byte, %d+%d)...)\n", discSize, knownSize) + g.p("\toff := len(w.buf) - %d - %d\n", discSize, knownSize) + g.p("\t%s\n", g.writePrim(discPrim, + fmt.Sprintf("(*[%d + %d]byte)(w.buf[off:])[:%d]", discSize, knownSize, discSize), arm.Label)) + g.p("\tbuf := w.buf\n") + g.p("\tw.buf = nil\n") + g.emitChildWriterReturn(payloadTi, childWriter, "buf", fmt.Sprintf("off + %d", discSize)) + g.p("}\n") + } else { + // Default: append disc, start child. + helper := g.appendHelper(discPrim) + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, childWriter) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tw.buf = %s(w.buf, %s)\n", helper, arm.Label) + g.p("\tchild := Start%s(w.buf)\n", g.goName(payloadTi)) + g.p("\tw.buf = nil\n") + g.p("\treturn child\n}\n") + } + } + } + } +} + +// emitUnionArmDiscInHeader generates a Set method for a union arm whose +// discriminant is in the pre-allocated message header. +func (g *Generator) emitUnionArmDiscInHeader(writerName, methodName string, payloadTi *TypeInfo, discPrim PrimInfo, headerSize, discOff int, arm UnionArm, needsPhase bool, fieldPhase int, useHeaderPtr bool) { + if payloadTi.IsFixedSize { + // Fixed-size struct payload: write disc into header, append only payload. + sizeConst := sizeConstName(arm.Payload) + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, g.goName(payloadTi)) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\t%s\n", g.writerSetPrimVal(discPrim, headerSize, discOff, arm.Label, useHeaderPtr)) + g.p("\tw.buf = append(w.buf, make([]byte, %s)...)\n", sizeConst) + g.p("\treturn %s{m: (*[%s]byte)(w.buf[len(w.buf)-%s:])}\n}\n", + g.goName(payloadTi), sizeConst, sizeConst) + } else { + // Variable-size payload: write disc into header, append only payload header. + childWriter := g.goName(payloadTi) + "Writer" + knownSize := g.childWriterKnownSize(payloadTi) + g.p("\nfunc (w *%s) %s() %s {\n", writerName, methodName, childWriter) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\t%s\n", g.writerSetPrimVal(discPrim, headerSize, discOff, arm.Label, useHeaderPtr)) + if knownSize > 0 { + g.p("\tw.buf = append(w.buf, make([]byte, %d)...)\n", knownSize) + g.p("\tbuf := w.buf\n") + g.p("\tw.buf = nil\n") + g.emitChildWriterReturn(payloadTi, childWriter, "buf", fmt.Sprintf("len(buf) - %d", knownSize)) + g.p("}\n") + } else { + g.p("\tchild := Start%s(w.buf)\n", g.goName(payloadTi)) + g.p("\tw.buf = nil\n") + if useHeaderPtr { + g.p("\tw.header = nil\n") + } + g.p("\treturn child\n}\n") + } + } +} + +func (g *Generator) emitWriterEmbeddedAccessors(ti *TypeInfo, writerName string) { + m := ti.Decl.Message + needsPhase := ti.WriterPhases > 1 + useHeaderPtr := g.writerUsesHeaderPtr(ti) + + for fi, f := range m.Fields { + if f.Kind != FieldNormal { + continue + } + if f.ArrayLen != "" || f.DiscRef != "" { + continue + } + // Only emit for embedded structs and messages. + eti, ok := g.a.Types[f.TypeName] + if !ok { + continue + } + + fieldGoName := pascalCase(f.Name) + + if eti.Decl.Kind == DeclStruct { + // Already handled in header setters as embedded struct getter. + continue + } + + if eti.Decl.Kind == DeclMessage && !eti.IsFixedSize { + // Embedded variable-size message: StartFieldName() → child writer. + childWriter := g.goName(eti) + "Writer" + fieldPhase := ti.FieldPhase[fi] + g.p("\nfunc (w *%s) Start%s() %s {\n", writerName, fieldGoName, childWriter) + g.emitPhaseCheck(needsPhase, fieldPhase) + g.p("\tchild := Start%s(w.buf)\n", g.goName(eti)) + g.p("\tw.buf = nil\n") + if useHeaderPtr { + g.p("\tw.header = nil\n") + } + g.p("\treturn child\n}\n") + } + } +} + +func (g *Generator) emitWriterFinish(ti *TypeInfo, writerName string, counts []writerCountInfo, needsResume bool, useValueRecv bool) { + headerSize := ti.FixedPrefixSize + + if useValueRecv { + g.p("\nfunc (w %s) Finish() []byte {\n", writerName) + } else { + g.p("\nfunc (w *%s) Finish() []byte {\n", writerName) + } + if needsResume && len(counts) == 0 { + g.p("\t_ = w.buf[:1]\n") + } + + // Patch deferred count fields. counts is parallel to ti.VarArrays. + for ci, c := range counts { + if c.inline { + continue // count was written inline by SetData + } + va := ti.VarArrays[ci] + p := g.fieldPrimInfo(va.CountField) + if c.deferred { + offName := c.goName + "Off" + g.p("\t%s\n", g.writePrim(p, + fmt.Sprintf("w.buf[w.%s:w.%s+%d]", offName, offName, p.Size), + fmt.Sprintf("w.%s", c.goName))) + } else { + off := va.CountOff + if headerSize >= 8 { + g.p("\t%s\n", g.writePrim(p, + fmt.Sprintf("(*[%d]byte)(w.buf[w.off:])[%d:%d]", headerSize, off, off+p.Size), + fmt.Sprintf("w.%s", c.goName))) + } else { + start := offExpr("w.off", off) + end := offExpr("w.off", off+p.Size) + g.p("\t%s\n", g.writePrim(p, + fmt.Sprintf("w.buf[%s:%s]", start, end), + fmt.Sprintf("w.%s", c.goName))) + } + } + } + + g.p("\treturn w.buf\n}\n") +} + +func (g *Generator) emitMultiArrayWriter(ti *TypeInfo) { + name := g.goName(ti) + writerName := name + "Writer" + + type arrWriter struct { + VarArrayInfo + goName string + goType string + } + var arrays []arrWriter + for _, va := range ti.VarArrays { + p := g.fieldPrimInfo(va.CountField) + arrays = append(arrays, arrWriter{va, camelCase(va.CountField.Name), goType(p)}) + } + + needsPhase := ti.WriterPhases > 1 + + if ti.MultiArrayKind == 1 { + // Pattern 1: both counts in header. + headerSize := ti.TotalFixedSize + g.p("type %s struct {\n", writerName) + g.p("\tbuf []byte\n") + g.p("\toff int\n") + for _, arr := range arrays { + g.p("\t%s %s\n", arr.goName, arr.goType) + } + g.p("\titemsBOff int // absolute buffer offset where %s starts\n", arrays[1].ArrayField.Name) + if needsPhase { + g.p("\tphase uint8\n") + } + g.p("}\n") + + g.p("\nfunc Start%s(buf []byte) %s {\n", name, writerName) + g.p("\toff := len(buf)\n") + g.p("\tbuf = append(buf, make([]byte, %d)...)", headerSize) + g.emitStartComment(ti) + g.p("\n\treturn %s{buf: buf, off: off}\n}\n", writerName) + + // Append methods for each array. + for idx, arr := range arrays { + elemTi := g.a.Types[arr.ArrayField.TypeName] + childWriter := g.goName(elemTi) + "Writer" + fieldGoName := pascalCase(arr.ArrayField.Name) + arrPhase := ti.FieldPhase[arr.ArrayIdx] + + g.p("\nfunc (w *%s) Append%s() %s {\n", writerName, fieldGoName, childWriter) + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, arrPhase) + if idx == 1 { + // Second array: record offB on first call. + g.p("\tif w.itemsBOff == 0 {\n") + g.p("\t\tw.itemsBOff = len(w.buf)\n") + g.p("\t}\n") + } + g.p("\tw.%s++\n", arr.goName) + g.p("\tchild := Start%s(w.buf)\n", g.goName(elemTi)) + g.p("\tw.buf = nil\n") + g.p("\treturn child\n}\n") + } + + g.p("\nfunc (w *%s) Resume(buf []byte) {\n\tw.buf = buf\n}\n", writerName) + + // Finish. + g.p("\nfunc (w *%s) Finish() []byte {\n", writerName) + for _, arr := range arrays { + p := g.fieldPrimInfo(arr.CountField) + g.p("\t%s\n", g.writePrim(p, + fmt.Sprintf("(*[%d]byte)(w.buf[w.off:])[%d:%d]", headerSize, arr.CountOff, arr.CountOff+p.Size), + fmt.Sprintf("w.%s", arr.goName))) + } + g.p("\treturn w.buf\n}\n") + } else { + // Pattern 2: interleaved counts. + g.p("type %s struct {\n", writerName) + g.p("\tbuf []byte\n") + g.p("\toff int\n") + for _, arr := range arrays { + g.p("\t%s %s\n", arr.goName, arr.goType) + } + g.p("\t%sOff int // absolute buffer offset of %s field\n", + arrays[1].goName, arrays[1].CountField.Name) + if needsPhase { + g.p("\tphase uint8\n") + } + g.p("}\n") + + g.p("\nfunc Start%s(buf []byte) %s {\n", name, writerName) + g.p("\toff := len(buf)\n") + countAPrim := g.fieldPrimInfo(arrays[0].CountField) + helper := g.appendHelper(countAPrim) + g.p("\tbuf = %s(buf, 0) // %s placeholder\n", helper, arrays[0].CountField.Name) + g.p("\treturn %s{buf: buf, off: off}\n}\n", writerName) + + // Append methods. + for idx, arr := range arrays { + elemTi := g.a.Types[arr.ArrayField.TypeName] + childWriter := g.goName(elemTi) + "Writer" + fieldGoName := pascalCase(arr.ArrayField.Name) + arrPhase := ti.FieldPhase[arr.ArrayIdx] + + g.p("\nfunc (w *%s) Append%s() %s {\n", writerName, fieldGoName, childWriter) + g.p("\t_ = w.buf[:1]\n") + g.emitPhaseCheck(needsPhase, arrPhase) + if idx == 1 { + // Second array: emit count_b placeholder on first call. + g.p("\tif w.%sOff == 0 {\n", arrays[1].goName) + g.p("\t\tw.%sOff = len(w.buf)\n", arrays[1].goName) + countBPrim := g.fieldPrimInfo(arrays[1].CountField) + helper2 := g.appendHelper(countBPrim) + g.p("\t\tw.buf = %s(w.buf, 0) // %s placeholder\n", helper2, arrays[1].CountField.Name) + g.p("\t}\n") + } + g.p("\tw.%s++\n", arr.goName) + g.p("\tchild := Start%s(w.buf)\n", g.goName(elemTi)) + g.p("\tw.buf = nil\n") + g.p("\treturn child\n}\n") + } + + g.p("\nfunc (w *%s) Resume(buf []byte) {\n\tw.buf = buf\n}\n", writerName) + + // Finish. + g.p("\nfunc (w *%s) Finish() []byte {\n", writerName) + countAPrim = g.fieldPrimInfo(arrays[0].CountField) + g.p("\t%s\n", g.writePrim(countAPrim, + fmt.Sprintf("w.buf[w.off:w.off+%d]", countAPrim.Size), + fmt.Sprintf("w.%s", arrays[0].goName))) + g.p("\tif w.%sOff == 0 {\n", arrays[1].goName) + g.p("\t\tw.%sOff = len(w.buf)\n", arrays[1].goName) + countBPrim := g.fieldPrimInfo(arrays[1].CountField) + helper2 := g.appendHelper(countBPrim) + g.p("\t\tw.buf = %s(w.buf, 0) // %s = 0\n", helper2, arrays[1].CountField.Name) + g.p("\t} else {\n") + g.p("\t\t%s\n", g.writePrim(countBPrim, + fmt.Sprintf("w.buf[w.%sOff:w.%sOff+%d]", arrays[1].goName, arrays[1].goName, countBPrim.Size), + fmt.Sprintf("w.%s", arrays[1].goName))) + g.p("\t}\n") + g.p("\treturn w.buf\n}\n") + } +} + +// writerSetPrimVal generates a write statement for a given value into the header. +func (g *Generator) writerSetPrimVal(p PrimInfo, headerSize, off int, val string, useHeaderPtr bool) string { + if useHeaderPtr { + return g.writePrim(p, fmt.Sprintf("w.header[%d:%d]", off, off+p.Size), val) + } + if headerSize >= 8 { + return g.writePrim(p, + fmt.Sprintf("(*[%d]byte)(w.buf[w.off:])[%d:%d]", headerSize, off, off+p.Size), val) + } + start := offExpr("w.off", off) + end := offExpr("w.off", off+p.Size) + return g.writePrim(p, fmt.Sprintf("w.buf[%s:%s]", start, end), val) +} + +// emitPhaseCheck generates a runtime check that the writer is in the correct +// phase. Advancing forward (phase < required) is allowed and sets the phase. +// Going backward (phase > required) panics. +func (g *Generator) emitPhaseCheck(needsPhase bool, requiredPhase int) { + if !needsPhase { + return + } + g.p("\tif w.phase > %d {\n\t\tpanic(\"writer fields called out of order\")\n\t}\n", requiredPhase) + if requiredPhase > 0 { + g.p("\tw.phase = %d\n", requiredPhase) + } +} + +// childWriterKnownSize returns the number of bytes to pre-allocate for a +// child message writer's header. Returns 0 if the child handles its own +// allocation (blob writers, union wrappers). +func (g *Generator) childWriterKnownSize(ti *TypeInfo) int { + if ti.IsUnionWrapper { + return 0 + } + if ti.HasExtent && ti.IsExtentStruct { + return ti.ExtentTotalSize + } + // When the header is skipped (all counts handled inline by SetData), + // Start allocates 0 bytes. + if g.allHeaderIsByteSliceCounts(ti) { + return 0 + } + return ti.FixedPrefixSize +} + +func (g *Generator) appendHelper(p PrimInfo) string { + order := "LittleEndian" + if p.IsBE { + order = "BigEndian" + } + return fmt.Sprintf("binary.%s.AppendUint%d", order, p.Size*8) +} diff --git a/go/msgparse/main.go b/go/msgparse/main.go new file mode 100644 index 00000000..97f755db --- /dev/null +++ b/go/msgparse/main.go @@ -0,0 +1,78 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// msgc compiles .msg message definition files into Go source code +// that performs zero-copy serialization/deserialization over byte buffers. +// +// Usage: +// +// msgc [-pkg name] [-o output] input.msg +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + pkg := flag.String("pkg", "", "Go package name (default: derived from directory)") + output := flag.String("o", "", "output file (default: stdout)") + flag.Parse() + + if flag.NArg() < 1 { + fmt.Fprintln(os.Stderr, "usage: msgc [-pkg name] [-o output] input.msg") + os.Exit(1) + } + + inputFile := flag.Arg(0) + src, err := os.ReadFile(inputFile) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading %s: %v\n", inputFile, err) + os.Exit(1) + } + + // Determine package name. + pkgName := *pkg + if pkgName == "" { + dir, _ := filepath.Abs(filepath.Dir(inputFile)) + pkgName = filepath.Base(dir) + // Sanitize. + pkgName = strings.ReplaceAll(pkgName, "-", "") + } + + // Parse. + parser := NewParser(src) + file := parser.Parse() + + // Analyze. + analysis := Analyze(file) + + // Generate. + code := Generate(analysis, pkgName) + + if *output != "" { + if err := os.WriteFile(*output, []byte(code), 0644); err != nil { + fmt.Fprintf(os.Stderr, "error writing %s: %v\n", *output, err) + os.Exit(1) + } + // Run gofmt on the output. + cmd := exec.Command("gofmt", "-w", *output) + cmd.Stderr = os.Stderr + cmd.Run() + } else { + // Write to stdout, pipe through gofmt if available. + cmd := exec.Command("gofmt") + cmd.Stdin = strings.NewReader(code) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + // Fallback: write raw. + fmt.Print(code) + } + } +} diff --git a/go/msgparse/parse.go b/go/msgparse/parse.go new file mode 100644 index 00000000..66561494 --- /dev/null +++ b/go/msgparse/parse.go @@ -0,0 +1,458 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +package main + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// isReservedName returns true if name is a Go keyword or predeclared +// identifier that would cause problems if used as a generated variable name. +func isReservedName(name string) bool { + switch name { + // Go keywords + case "break", "case", "chan", "const", "continue", + "default", "defer", "else", "fallthrough", "for", + "func", "go", "goto", "if", "import", + "interface", "map", "package", "range", "return", + "select", "struct", "switch", "type", "var": + return true + // Predeclared identifiers (builtins) + case "append", "cap", "close", "complex", "copy", + "delete", "imag", "len", "make", "max", "min", + "new", "panic", "print", "println", "real", "recover": + return true + // Predeclared types + case "bool", "byte", "comparable", "error", + "float32", "float64", "complex64", "complex128", + "int", "int8", "int16", "int32", "int64", + "rune", "string", "uint", "uint8", "uint16", "uint32", "uint64", + "uintptr", "any": + return true + // Predeclared constants + case "true", "false", "iota", "nil": + return true + } + return false +} + +// Token types for the lexer. +type TokenKind int + +const ( + tokEOF TokenKind = iota + tokIdent + tokNumber // decimal or hex + tokString // "..." + tokChar // '.' + tokLBrace // { + tokRBrace // } + tokLParen // ( + tokRParen // ) + tokLBrack // [ + tokRBrack // ] + tokSemi // ; + tokColon // : + tokEquals // = +) + +type Token struct { + Kind TokenKind + Text string + Line int +} + +// Lexer tokenizes .msg input. +type Lexer struct { + src []byte + pos int + line int +} + +func NewLexer(src []byte) *Lexer { + return &Lexer{src: src, line: 1} +} + +func (l *Lexer) Next() Token { + for l.pos < len(l.src) { + ch := l.src[l.pos] + + // Skip whitespace + if ch == ' ' || ch == '\t' || ch == '\r' { + l.pos++ + continue + } + if ch == '\n' { + l.pos++ + l.line++ + continue + } + + // Skip line comments + if ch == '/' && l.pos+1 < len(l.src) && l.src[l.pos+1] == '/' { + for l.pos < len(l.src) && l.src[l.pos] != '\n' { + l.pos++ + } + continue + } + + line := l.line + + // Single-character tokens + switch ch { + case '{': + l.pos++ + return Token{tokLBrace, "{", line} + case '}': + l.pos++ + return Token{tokRBrace, "}", line} + case '(': + l.pos++ + return Token{tokLParen, "(", line} + case ')': + l.pos++ + return Token{tokRParen, ")", line} + case '[': + l.pos++ + return Token{tokLBrack, "[", line} + case ']': + l.pos++ + return Token{tokRBrack, "]", line} + case ';': + l.pos++ + return Token{tokSemi, ";", line} + case ':': + l.pos++ + return Token{tokColon, ":", line} + case '=': + l.pos++ + return Token{tokEquals, "=", line} + } + + // String literal + if ch == '"' { + return l.lexString(line) + } + + // Char literal + if ch == '\'' { + return l.lexCharLit(line) + } + + // Number (decimal or hex) + if ch >= '0' && ch <= '9' { + return l.lexNumber(line) + } + + // Identifier + if ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') { + return l.lexIdent(line) + } + + fmt.Fprintf(os.Stderr, "line %d: unexpected character %q\n", line, ch) + l.pos++ + } + return Token{tokEOF, "", l.line} +} + +func (l *Lexer) lexString(line int) Token { + l.pos++ // skip opening " + var buf strings.Builder + buf.WriteByte('"') + for l.pos < len(l.src) && l.src[l.pos] != '"' { + if l.src[l.pos] == '\\' && l.pos+1 < len(l.src) { + buf.WriteByte('\\') + l.pos++ + buf.WriteByte(l.src[l.pos]) + l.pos++ + } else { + buf.WriteByte(l.src[l.pos]) + l.pos++ + } + } + if l.pos < len(l.src) { + l.pos++ // skip closing " + } + buf.WriteByte('"') + return Token{tokString, buf.String(), line} +} + +func (l *Lexer) lexCharLit(line int) Token { + start := l.pos + l.pos++ // skip ' + for l.pos < len(l.src) && l.src[l.pos] != '\'' { + if l.src[l.pos] == '\\' { + l.pos++ + } + l.pos++ + } + if l.pos < len(l.src) { + l.pos++ // skip closing ' + } + return Token{tokChar, string(l.src[start:l.pos]), line} +} + +func (l *Lexer) lexNumber(line int) Token { + start := l.pos + if l.src[l.pos] == '0' && l.pos+1 < len(l.src) && (l.src[l.pos+1] == 'x' || l.src[l.pos+1] == 'X') { + l.pos += 2 + for l.pos < len(l.src) && isHexDigit(l.src[l.pos]) { + l.pos++ + } + } else { + for l.pos < len(l.src) && l.src[l.pos] >= '0' && l.src[l.pos] <= '9' { + l.pos++ + } + } + return Token{tokNumber, string(l.src[start:l.pos]), line} +} + +func (l *Lexer) lexIdent(line int) Token { + start := l.pos + for l.pos < len(l.src) && isIdentChar(l.src[l.pos]) { + l.pos++ + } + return Token{tokIdent, string(l.src[start:l.pos]), line} +} + +func isHexDigit(ch byte) bool { + return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') +} + +func isIdentChar(ch byte) bool { + return ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') +} + +// Parser builds an AST from tokens. +type Parser struct { + lex *Lexer + cur Token +} + +func NewParser(src []byte) *Parser { + p := &Parser{lex: NewLexer(src)} + p.advance() + return p +} + +func (p *Parser) advance() Token { + old := p.cur + p.cur = p.lex.Next() + return old +} + +func (p *Parser) expect(kind TokenKind) Token { + if p.cur.Kind != kind { + fmt.Fprintf(os.Stderr, "line %d: expected token kind %d, got %d (%q)\n", + p.cur.Line, kind, p.cur.Kind, p.cur.Text) + os.Exit(1) + } + return p.advance() +} + +func (p *Parser) Parse() *MsgFile { + file := &MsgFile{} + for p.cur.Kind != tokEOF { + switch p.cur.Text { + case "enum": + file.Decls = append(file.Decls, Decl{Kind: DeclEnum, Enum: p.parseEnum()}) + case "struct": + file.Decls = append(file.Decls, Decl{Kind: DeclStruct, Struct: p.parseStruct()}) + case "message": + file.Decls = append(file.Decls, Decl{Kind: DeclMessage, Message: p.parseMessage()}) + case "union": + file.Decls = append(file.Decls, Decl{Kind: DeclUnion, Union: p.parseUnion()}) + case "const": + file.Decls = append(file.Decls, Decl{Kind: DeclConstGroup, ConstGroup: p.parseConstGroup()}) + default: + fmt.Fprintf(os.Stderr, "line %d: unexpected %q\n", p.cur.Line, p.cur.Text) + os.Exit(1) + } + } + return file +} + +func (p *Parser) parseEnum() *EnumDecl { + p.advance() // skip "enum" + name := p.expect(tokIdent).Text + p.expect(tokColon) + + // Parse underlying type: "leu32", "u8", "char[4]", "spchar[4]", etc. + underlying := p.expect(tokIdent).Text + if p.cur.Kind == tokLBrack { + p.advance() + n := p.expect(tokNumber).Text + p.expect(tokRBrack) + underlying += "[" + n + "]" + } + + p.expect(tokLBrace) + var consts []EnumConst + for p.cur.Kind != tokRBrace { + cname := p.expect(tokIdent).Text + p.expect(tokEquals) + var cval string + switch p.cur.Kind { + case tokNumber: + cval = p.advance().Text + case tokString: + cval = p.advance().Text + case tokChar: + cval = p.advance().Text + default: + fmt.Fprintf(os.Stderr, "line %d: unexpected enum value %q\n", p.cur.Line, p.cur.Text) + os.Exit(1) + } + p.expect(tokSemi) + consts = append(consts, EnumConst{Name: cname, Value: cval}) + } + p.expect(tokRBrace) + return &EnumDecl{Name: name, Underlying: underlying, Constants: consts} +} + +func (p *Parser) parseStruct() *StructDecl { + p.advance() // skip "struct" + name := p.expect(tokIdent).Text + p.expect(tokLBrace) + fields := p.parseFields() + p.expect(tokRBrace) + return &StructDecl{Name: name, Fields: fields} +} + +func (p *Parser) parseMessage() *MessageDecl { + p.advance() // skip "message" + name := p.expect(tokIdent).Text + p.expect(tokLBrace) + fields := p.parseFields() + p.expect(tokRBrace) + return &MessageDecl{Name: name, Fields: fields} +} + +func (p *Parser) parseFields() []Field { + var fields []Field + for p.cur.Kind != tokRBrace { + if p.cur.Kind == tokIdent && p.cur.Text == "pad" { + p.advance() + p.expect(tokLParen) + n, _ := strconv.Atoi(p.expect(tokNumber).Text) + p.expect(tokRParen) + p.expect(tokSemi) + fields = append(fields, Field{Kind: FieldPad, Size: n}) + } else if p.cur.Kind == tokIdent && p.cur.Text == "align" { + p.advance() + p.expect(tokLParen) + n, _ := strconv.Atoi(p.expect(tokNumber).Text) + p.expect(tokRParen) + p.expect(tokSemi) + fields = append(fields, Field{Kind: FieldAlign, Size: n}) + } else if p.cur.Kind == tokIdent && p.cur.Text == "extent" { + p.advance() + p.expect(tokLParen) + ref := p.expect(tokIdent).Text + p.expect(tokRParen) + p.expect(tokSemi) + fields = append(fields, Field{Kind: FieldExtent, Ref: ref}) + } else { + // Regular field: type name [arrayLen] ; or type name(disc) ; + typeName := p.expect(tokIdent).Text + + nameTok := p.expect(tokIdent) + name := nameTok.Text + if isReservedName(name) { + fmt.Fprintf(os.Stderr, "line %d: %q is a Go reserved word and cannot be used as a field name\n", nameTok.Line, name) + os.Exit(1) + } + + var arrayLen string + var discRef string + + if p.cur.Kind == tokLBrack { + p.advance() + // Array length: number or identifier + arrayLen = p.advance().Text + p.expect(tokRBrack) + } + + if p.cur.Kind == tokLParen { + p.advance() + discRef = p.expect(tokIdent).Text + p.expect(tokRParen) + } + + p.expect(tokSemi) + fields = append(fields, Field{ + Kind: FieldNormal, + TypeName: typeName, + Name: name, + ArrayLen: arrayLen, + DiscRef: discRef, + }) + } + } + return fields +} + +func (p *Parser) parseConstGroup() *ConstGroupDecl { + p.advance() // skip "const" + cg := &ConstGroupDecl{} + // Parse one or more constants: const NAME = VALUE; [NAME = VALUE; ...] + // First constant is required. + for { + name := p.expect(tokIdent).Text + p.expect(tokEquals) + var val string + switch p.cur.Kind { + case tokNumber: + val = p.advance().Text + case tokChar: + val = p.advance().Text + default: + fmt.Fprintf(os.Stderr, "line %d: unexpected const value %q\n", p.cur.Line, p.cur.Text) + os.Exit(1) + } + p.expect(tokSemi) + cg.Constants = append(cg.Constants, ConstEntry{Name: name, Value: val}) + // Continue if next token is an identifier followed by '=' (another const in same block). + // Stop if next token is a keyword or EOF. + if p.cur.Kind != tokIdent { + break + } + // Check if this looks like another const (NAME = ...) or a new declaration keyword. + if p.cur.Text == "const" || p.cur.Text == "enum" || p.cur.Text == "struct" || + p.cur.Text == "message" || p.cur.Text == "union" { + break + } + } + return cg +} + +func (p *Parser) parseUnion() *UnionDecl { + p.advance() // skip "union" + name := p.expect(tokIdent).Text + p.expect(tokLParen) + discType := p.expect(tokIdent).Text + p.expect(tokRParen) + p.expect(tokLBrace) + + var arms []UnionArm + for p.cur.Kind != tokRBrace { + var label string + if p.cur.Kind == tokIdent && p.cur.Text == "default" { + label = "default" + p.advance() + } else { + label = p.expect(tokIdent).Text + } + p.expect(tokColon) + payload := p.expect(tokIdent).Text + p.expect(tokSemi) + arms = append(arms, UnionArm{Label: label, Payload: payload}) + } + p.expect(tokRBrace) + return &UnionDecl{Name: name, DiscType: discType, Arms: arms} +} diff --git a/go/msgparse/xdr2msg.py b/go/msgparse/xdr2msg.py new file mode 100755 index 00000000..66865cc5 --- /dev/null +++ b/go/msgparse/xdr2msg.py @@ -0,0 +1,946 @@ +#!/usr/bin/env python3 +# Copyright 2026 XTX Markets Technologies Limited +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +""" +Translate an XDR (.x) file to .msg format. + +Usage: python3 xdr2msg.py nfs.x > nfs_full.msg + +Key mappings: + - XDR enum → .msg enum : beu32 + - XDR struct (fixed) → .msg struct + - XDR struct (var) → .msg message + - XDR union switch → .msg union + wrapper message + - XDR typedef → inline, or promoted to struct/message + - XDR const → .msg const (untyped) + - XDR program → skipped + - XDR bool → beu32 (4 bytes) + - XDR int32/uint32 → bei32/beu32 + - XDR int64/uint64 → bei64/beu64 + - XDR opaque[N] → u8[N] + - XDR opaque<> → beu32 data_len; u8 data[data_len]; align(4); + - XDR T<> → beu32 count; T arr[count]; +""" + +import copy +import re +import sys +from dataclasses import dataclass +from typing import Optional + + +# --------------------------------------------------------------------------- +# Lexer +# --------------------------------------------------------------------------- + +TOKEN_RE = re.compile(r""" + (/\*.*?\*/) | # block comment + (//[^\n]*) | # line comment + (0[xX][\da-fA-F]+) | # hex literal + (\d+) | # decimal literal + ([a-zA-Z_]\w*) | # identifier + ("[^"]*") | # string literal + ([{}\[\]<>()=;:,*]) | # punctuation + (\s+) # whitespace +""", re.VERBOSE | re.DOTALL) + + +def tokenize(src): + for m in TOKEN_RE.finditer(src): + if m.group(1) or m.group(2) or m.group(8): + continue + if m.group(3): + yield ("NUM", m.group(3)) + elif m.group(4): + yield ("NUM", m.group(4)) + elif m.group(5): + yield ("ID", m.group(5)) + elif m.group(6): + yield ("STR", m.group(6)) + elif m.group(7): + yield ("P", m.group(7)) + + +# --------------------------------------------------------------------------- +# AST +# --------------------------------------------------------------------------- + +@dataclass +class Const: + name: str; value: str + +@dataclass +class EnumVal: + name: str; value: str + +@dataclass +class Enum: + name: str; values: list + +@dataclass +class Field: + type_name: str; name: str + fixed_len: Optional[str] = None + var_len: Optional[str] = None # "" = unbounded + is_optional: bool = False + +@dataclass +class Struct: + name: str; fields: list + +@dataclass +class UnionArm: + labels: list; type_name: Optional[str]; field_name: Optional[str] + +@dataclass +class Union: + name: str; disc_type: str; disc_name: str + arms: list; default_arm: Optional[UnionArm] = None + +@dataclass +class Typedef: + name: str; base_type: str + fixed_len: Optional[str] = None + var_len: Optional[str] = None + + +# --------------------------------------------------------------------------- +# Parser +# --------------------------------------------------------------------------- + +class Parser: + def __init__(self, src): + self.toks = list(tokenize(src)) + self.pos = 0 + + def peek(self, off=0): + i = self.pos + off + return self.toks[i] if i < len(self.toks) else ("EOF", "") + + def adv(self): + t = self.toks[self.pos]; self.pos += 1; return t + + def exp(self, kind, text=None): + t = self.adv() + if t[0] != kind or (text is not None and t[1] != text): + raise SyntaxError(f"Expected ({kind},{text!r}), got {t} @{self.pos-1}") + return t + + def match(self, kind, text=None): + t = self.peek() + if t[0] == kind and (text is None or t[1] == text): + return self.adv() + return None + + def parse(self): + decls = [] + while self.peek()[0] != "EOF": + d = self._top() + if d: + decls.append(d) + return decls + + def _top(self): + k, t = self.peek() + if k != "ID": + self.adv(); return None + if t == "const": return self._const() + if t == "enum": return self._enum() + if t == "struct": return self._struct() + if t == "union": return self._union() + if t == "typedef": return self._typedef() + if t == "program": self._skip_prog(); return None + self.adv(); return None + + def _const(self): + self.exp("ID", "const"); n = self.exp("ID")[1] + self.exp("P", "="); v = self.adv()[1]; self.exp("P", ";") + return Const(n, v) + + def _enum(self): + self.exp("ID", "enum"); n = self.exp("ID")[1]; self.exp("P", "{") + vals = [] + while not self.match("P", "}"): + vn = self.exp("ID")[1] + vv = self.adv()[1] if self.match("P", "=") else str(len(vals)) + vals.append(EnumVal(vn, vv)); self.match("P", ",") + self.exp("P", ";") + return Enum(n, vals) + + def _type_spec(self): + if self.match("ID", "unsigned"): + if self.match("ID", "hyper"): return "uint64_t" + self.match("ID", "int"); return "uint32_t" + if self.match("ID", "hyper"): return "int64_t" + if self.peek()[1] == "int": self.adv(); return "int32_t" + return self.exp("ID")[1] + + def _field(self): + tn = self._type_spec() + opt = bool(self.match("P", "*")) + nm = self.exp("ID")[1] + fl = vl = None + if self.match("P", "["): + fl = self.adv()[1]; self.exp("P", "]") + elif self.match("P", "<"): + vl = self.adv()[1] if self.peek()[1] != ">" else "" + self.exp("P", ">") + self.exp("P", ";") + return Field(tn, nm, fl, vl, opt) + + def _struct(self): + self.exp("ID", "struct"); n = self.exp("ID")[1]; self.exp("P", "{") + fs = [] + while not self.match("P", "}"): + fs.append(self._field()) + self.exp("P", ";") + return Struct(n, fs) + + def _union(self): + self.exp("ID", "union"); n = self.exp("ID")[1] + self.exp("ID", "switch"); self.exp("P", "(") + dt = self._type_spec(); dn = self.exp("ID")[1] + self.exp("P", ")"); self.exp("P", "{") + arms = []; default = None; pending = [] + while not self.match("P", "}"): + if self.match("ID", "case"): + lbl = self.adv()[1]; self.exp("P", ":") + if self.peek()[1] in ("case", "default"): + pending.append(lbl); continue + pending.append(lbl) + arms.append(self._arm_payload(pending)); pending = [] + elif self.match("ID", "default"): + self.exp("P", ":") + default = self._arm_payload(pending + ["default"]); pending = [] + else: + self.adv() + self.exp("P", ";") + return Union(n, dt, dn, arms, default) + + def _arm_payload(self, labels): + if self.match("ID", "void"): + self.exp("P", ";"); return UnionArm(labels, None, None) + tn = self._type_spec(); fn = self.exp("ID")[1]; self.exp("P", ";") + return UnionArm(labels, tn, fn) + + def _typedef(self): + self.exp("ID", "typedef"); tn = self._type_spec(); nm = self.exp("ID")[1] + fl = vl = None + if self.match("P", "["): + fl = self.adv()[1]; self.exp("P", "]") + elif self.match("P", "<"): + vl = self.adv()[1] if self.peek()[1] != ">" else "" + self.exp("P", ">") + self.exp("P", ";") + return Typedef(nm, tn, fl, vl) + + def _skip_prog(self): + self.exp("ID", "program"); self.exp("ID") + d = 0 + while True: + t = self.adv() + if t[1] == "{": d += 1 + elif t[1] == "}": + d -= 1 + if d == 0: + self.exp("P", "="); self.adv(); self.exp("P", ";"); return + + +# --------------------------------------------------------------------------- +# .msg Generator +# --------------------------------------------------------------------------- + +XDR_PRIM = { + "uint32_t": "beu32", "int32_t": "bei32", + "uint64_t": "beu64", "int64_t": "bei64", + "bool": "beu32", "opaque": "u8", "string": "u8", +} + + +class Gen: + def __init__(self, decls): + self.decls = decls + self.consts = {} + self.enums = {} + self.enum_vals = {} + self.structs = {} + self.unions = {} + self.tdmap = {} + self.emitted = set() + self.wrappers = {} + self.out = [] + + # External constants not defined in the .x file + self.consts["RPCSEC_GSS"] = "6" # RFC 2203 + + for d in decls: + if isinstance(d, Const): self.consts[d.name] = d.value + elif isinstance(d, Enum): + self.enums[d.name] = d + for v in d.values: self.enum_vals[v.name] = d.name + elif isinstance(d, Struct): self.structs[d.name] = d + elif isinstance(d, Union): self.unions[d.name] = d + elif isinstance(d, Typedef): self.tdmap[d.name] = d + + def rc(self, val): + """Resolve constant.""" + return self.consts.get(val, val) if val else val + + def resolve_td(self, name, depth=0): + """Resolve typedef chain → (base, fixed_len, var_len). + Stops at named types (structs, enums, unions) even if they're typedef'd.""" + if depth > 20 or name not in self.tdmap: + return name, None, None + td = self.tdmap[name] + # If this typedef adds array dimensions and the base is a known named type, + # stop here — don't resolve further + base_is_named = (td.base_type in self.structs or + td.base_type in self.enums or + td.base_type in self.unions or + self._is_promoted(td.base_type)) + if base_is_named: + fl = self.rc(td.fixed_len) + vl = td.var_len + if vl and vl != "": + vl = self.rc(vl) + return td.base_type, fl, vl + + base, bfl, bvl = self.resolve_td(td.base_type, depth + 1) + fl = self.rc(td.fixed_len) if td.fixed_len is not None else bfl + vl = td.var_len if td.var_len is not None else bvl + if vl and vl != "": + vl = self.rc(vl) + return base, fl, vl + + def _is_promoted(self, name): + """Check if a typedef name should become its own .msg type.""" + if name not in self.tdmap: + return False + base, fl, vl = self._raw_resolve(name) + if vl is not None: + return True + if fl is not None and base in ("opaque", "u8", "string"): + return True + return False + + def _raw_resolve(self, name, depth=0): + """Raw resolve without stopping at named types.""" + if depth > 20 or name not in self.tdmap: + return name, None, None + td = self.tdmap[name] + base, bfl, bvl = self._raw_resolve(td.base_type, depth + 1) + fl = self.rc(td.fixed_len) if td.fixed_len is not None else bfl + vl = td.var_len if td.var_len is not None else bvl + if vl and vl != "": + vl = self.rc(vl) + return base, fl, vl + + def msg_type(self, xdr_type): + if xdr_type in XDR_PRIM: + return XDR_PRIM[xdr_type] + base, fl, vl = self.resolve_td(xdr_type) + if base in XDR_PRIM: + return XDR_PRIM[base] + return xdr_type + + def is_enum(self, name): + if name in self.enums: return True + base, _, _ = self.resolve_td(name) + return base in self.enums + + def enum_name(self, name): + if name in self.enums: return name + base, _, _ = self.resolve_td(name) + return base if base in self.enums else name + + def is_fixed(self, xdr_type, visited=None): + if visited is None: visited = set() + if xdr_type in visited: return True # break cycles + visited.add(xdr_type) + if xdr_type in XDR_PRIM: return True + if xdr_type in self.enums: return True + # Promoted typedefs that become messages are variable-size + if self._is_promoted(xdr_type): + _, fl, vl = self._raw_resolve(xdr_type) + if vl is not None: return False + # Fixed promoted (struct) is fixed + return True + base, fl, vl = self.resolve_td(xdr_type) + if vl is not None: return False + if base in XDR_PRIM: return fl is not None or base not in ("opaque", "string") + if base in self.enums: return True + if base in self.structs: + return all(self._field_fixed(f, visited) for f in self.structs[base].fields) + if base in self.unions: return False + # Check if base is promoted + if self._is_promoted(base): return self.is_fixed(base, visited) + return True + + def _field_fixed(self, f, visited=None): + if f.var_len is not None or f.is_optional: return False + base, fl, vl = self.resolve_td(f.type_name) + if vl is not None: return False + return self.is_fixed(f.type_name, visited) + + # -- Emit -- + + def _find_used_types(self): + """Find all promoted typedef names that are actually referenced + as field types in structs, messages, or union arms.""" + used = set() + for d in self.decls: + if isinstance(d, Struct): + for f in d.fields: + self._mark_used(f.type_name, used) + elif isinstance(d, Union): + for arm in d.arms: + if arm.type_name: + self._mark_used(arm.type_name, used) + if d.default_arm and d.default_arm.type_name: + self._mark_used(d.default_arm.type_name, used) + return used + + def _mark_used(self, type_name, used): + """Mark a type as used if it's a promoted typedef.""" + if type_name in used: + return + if self._is_promoted(type_name): + used.add(type_name) + return + # Check if it resolves to a promoted typedef + base, _, _ = self.resolve_td(type_name) + if base != type_name and self._is_promoted(base): + # The field uses the alias name, which is itself promoted. + # Don't mark the base — it's inlined into the alias. + used.add(type_name) + + def emit(self): + self.out = [] + + # Emit constants first. + for d in self.decls: + if isinstance(d, Const): + self.out.append(f"const {d.name} = {d.value};") + + # Add blank line separator if we emitted any constants. + if self.out: + self.out.append("") + + used_promoted = self._find_used_types() + + for d in self.decls: + if isinstance(d, Enum): + self._emit_enum(d) + elif isinstance(d, Typedef) and self._is_promoted(d.name): + if d.name in used_promoted: + self._emit_promoted_td(d) + elif isinstance(d, Struct): + self._emit_struct(d) + elif isinstance(d, Union): + self._emit_union(d) + + return "\n".join(self.out) + "\n" + + def _start_block(self): + """Mark the start of a block whose body lines will be aligned.""" + return len(self.out) + + def _end_block(self, start): + """Align body lines emitted since _start_block.""" + self.out[start:] = self._align_block(self.out[start:]) + + @staticmethod + def _align_block(lines): + """Align the second column within a block of indented lines. + + For field lines like ' type name;', aligns name to a consistent + column. For enum lines like ' NAME = value;', aligns '='. For union + arm lines like ' LABEL: type;', aligns the type after ':'. + Non-matching lines (align directives, braces) are left unchanged. + """ + # Detect the pattern from the first indented two-column line. + field_re = re.compile(r'^( \S+)\s+(\S.*)$') + max_col1 = 0 + for line in lines: + m = field_re.match(line) + if m: + max_col1 = max(max_col1, len(m.group(1))) + if max_col1 == 0: + return lines + result = [] + for line in lines: + m = field_re.match(line) + if m: + result.append(f"{m.group(1):<{max_col1}} {m.group(2)}") + else: + result.append(line) + return result + + def _emit_enum(self, e): + if e.name in self.emitted: return + self.emitted.add(e.name) + self.out.append(f"enum {e.name} : beu32 {{") + start = self._start_block() + for v in e.values: + self.out.append(f" {v.name} = {v.value};") + self._end_block(start) + self.out.append("}") + self.out.append("") + + def _emit_promoted_td(self, td): + if td.name in self.emitted: return + self.emitted.add(td.name) + # Use raw resolution (all the way to primitives) to determine structure + base, fl, vl = self._raw_resolve(td.name) + msg_base = XDR_PRIM.get(base, base) + + # But also check resolve_td for the immediate base — if it's a named + # type (struct/message/union), we reference it directly + imm_base, imm_fl, imm_vl = self.resolve_td(td.name) + if imm_base in self.unions: + # Typedef of a union — if array, use wrapper entry type + if imm_vl is not None: + wrapper = self._ensure_union_wrapper_emitted(imm_base) + self.out.append(f"message {td.name} {{") + start = self._start_block() + self.out.append(f" beu32 count;") + self.out.append(f" {wrapper} data[count];") + self._end_block(start) + self.out.append("}") + self.out.append("") + return + # Simple alias of union — skip, shouldn't happen + return + if imm_base in self.structs or self._is_promoted(imm_base): + # This typedef aliases a named type. Check if it adds array dims. + if imm_fl is not None: + self.out.append(f"struct {td.name} {{") + self.out.append(f" {imm_base} data[{imm_fl}];") + self.out.append("}") + self.out.append("") + return + if imm_vl is not None: + self.out.append(f"message {td.name} {{") + start = self._start_block() + self.out.append(f" beu32 count;") + self.out.append(f" {imm_base} data[count];") + self._end_block(start) + self.out.append("}") + self.out.append("") + return + # Simple alias — emit same structure as the base + # Fall through to raw resolution + + if fl is not None and vl is None: + # Fixed array → struct + self.out.append(f"struct {td.name} {{") + self.out.append(f" {msg_base} data[{fl}];") + self.out.append("}") + elif vl is not None: + # Variable-length → message + self.out.append(f"message {td.name} {{") + start = self._start_block() + if msg_base == "u8": + self.out.append(f" beu32 data_len;") + self.out.append(f" u8 data[data_len];") + self.out.append(f" align(4);") + else: + self.out.append(f" beu32 count;") + self.out.append(f" {msg_base} data[count];") + self._end_block(start) + self.out.append("}") + self.out.append("") + + def _emit_struct(self, s): + if s.name in self.emitted: return + self.emitted.add(s.name) + + has_union = any(self._field_is_union(f) for f in s.fields) + is_fixed = (not has_union) and all(self._field_fixed(f) for f in s.fields) + + # Pre-emit any wrappers needed by fields + for f in s.fields: + self._pre_emit_wrappers(f) + + # Count how many variable-length u8 arrays would be inlined. + # If >=2, we need to use message references instead (multi-array + # with u8 elements isn't supported by the compiler). + inline_var_u8_count = 0 + for f in s.fields: + if self._would_inline_var_u8(f): + inline_var_u8_count += 1 + use_opaque_ref = inline_var_u8_count >= 2 + if use_opaque_ref: + self._ensure_xdr_opaque() + + kw = "struct" if is_fixed else "message" + self.out.append(f"{kw} {s.name} {{") + start = self._start_block() + for f in s.fields: + self._emit_field(f, use_opaque_ref=use_opaque_ref) + self._end_block(start) + self.out.append("}") + self.out.append("") + + def _would_inline_var_u8(self, f): + """Check if a field would be inlined as beu32 len; u8 data[len]; align(4);""" + if f.is_optional or f.fixed_len is not None: + return False + base, fl, vl = self.resolve_td(f.type_name) + if base in self.unions or base in self.structs: + return False + if self._is_promoted(f.type_name) or (base in self.tdmap and self._is_promoted(base)): + return False + if f.type_name in self.structs or f.type_name in self.enums: + return False + msg_t = self.msg_type(f.type_name) + eff_vl = f.var_len + if eff_vl is None: + _, _, eff_vl = self.resolve_td(f.type_name) + return eff_vl is not None and msg_t == "u8" + + def _pre_emit_wrappers(self, f): + """Pre-emit any wrapper types needed by this field.""" + if f.is_optional: + self._ensure_optional_union(f.type_name) + return + base, fl, vl = self.resolve_td(f.type_name) + if base in self.unions: + if f.var_len is not None or (vl is not None and f.fixed_len is None and f.var_len is None): + self._ensure_union_wrapper_emitted(base) + + def _resolves_to(self, tn, target): + base, _, _ = self.resolve_td(tn) + return base == target or tn == target + + def _field_is_union(self, f): + base, _, _ = self.resolve_td(f.type_name) + return base in self.unions + + def _ensure_xdr_opaque(self): + """Ensure the xdr_opaque message type exists.""" + if "xdr_opaque" not in self.emitted: + self.emitted.add("xdr_opaque") + self.out.append("message xdr_opaque {") + start = self._start_block() + self.out.append(" beu32 data_len;") + self.out.append(" u8 data[data_len];") + self.out.append(" align(4);") + self._end_block(start) + self.out.append("}") + self.out.append("") + + def _emit_field(self, f, use_opaque_ref=False): + # XDR "type" clashes with Go keyword. + if f.name == "type": + f = copy.copy(f) + f.name = "type_val" + + if f.is_optional: + opt_union = self._ensure_optional_union(f.type_name) + disc_field = f"{f.name}_present" + self._ensure_xdr_bool() + self.out.append(f" xdr_bool {disc_field};") + self.out.append(f" {opt_union} {f.name}({disc_field});") + return + + base, fl, vl = self.resolve_td(f.type_name) + + # Union-typed field + if base in self.unions: + u = self.unions[base] + if f.var_len is not None or (vl is not None and f.fixed_len is None and f.var_len is None): + # Array of unions → each element needs disc+union wrapper + # (wrapper already emitted by _pre_emit_wrappers) + key = f"uwrap_{base}" + wrapper = self.wrappers.get(key, f"{base}_entry") + cn = f"{f.name}_count" + self.out.append(f" beu32 {cn};") + self.out.append(f" {wrapper} {f.name}[{cn}];") + return + if f.fixed_len is None and f.var_len is None and vl is None: + # Scalar union field → split into disc + union(disc) + disc_enum = self._ensure_disc_enum(u) + disc_field = f"{f.name}_type" + self.out.append(f" {disc_enum} {disc_field};") + self.out.append(f" {u.name} {f.name}({disc_field});") + return + + # Promoted typedef → use its name directly as a type reference + if self._is_promoted(f.type_name): + if f.fixed_len is not None: + self.out.append(f" {f.type_name} {f.name}[{self.rc(f.fixed_len)}];") + elif f.var_len is not None: + cn = f"{f.name}_count" + self.out.append(f" beu32 {cn};") + self.out.append(f" {f.type_name} {f.name}[{cn}];") + else: + self.out.append(f" {f.type_name} {f.name};") + return + + # Check if the type resolves to a promoted typedef (e.g., field type + # is "component4" which is a promoted typedef) + if base in self.tdmap and self._is_promoted(base): + # The base itself is promoted — field type resolves to it + if f.fixed_len is not None: + self.out.append(f" {base} {f.name}[{self.rc(f.fixed_len)}];") + elif f.var_len is not None: + cn = f"{f.name}_count" + self.out.append(f" beu32 {cn};") + self.out.append(f" {base} {f.name}[{cn}];") + else: + self.out.append(f" {base} {f.name};") + return + + # Named struct/enum that isn't a typedef + if f.type_name in self.structs or f.type_name in self.enums: + if f.fixed_len is not None: + self.out.append(f" {f.type_name} {f.name}[{self.rc(f.fixed_len)}];") + elif f.var_len is not None: + cn = f"{f.name}_count" + self.out.append(f" beu32 {cn};") + self.out.append(f" {f.type_name} {f.name}[{cn}];") + else: + self.out.append(f" {f.type_name} {f.name};") + return + + # Resolve the full type + msg_t = self.msg_type(f.type_name) + + # Field-level array specs + eff_fl = self.rc(f.fixed_len) + eff_vl = f.var_len + if eff_vl and eff_vl != "": + eff_vl = self.rc(eff_vl) + + # If no field-level spec, use typedef's + if eff_fl is None and eff_vl is None: + eff_fl = fl + eff_vl = vl + + if eff_vl is not None: + if msg_t == "u8" and use_opaque_ref: + # Multi-array: use xdr_opaque message type instead of inline + self._ensure_xdr_opaque() + self.out.append(f" xdr_opaque {f.name};") + elif msg_t == "u8": + self.out.append(f" beu32 {f.name}_len;") + self.out.append(f" u8 {f.name}[{f.name}_len];") + self.out.append(f" align(4);") + else: + cn = f"{f.name}_count" + self.out.append(f" beu32 {cn};") + self.out.append(f" {msg_t} {f.name}[{cn}];") + elif eff_fl is not None: + self.out.append(f" {msg_t} {f.name}[{eff_fl}];") + elif f.type_name == "bool": + self.out.append(f" beu32 {f.name};") + else: + self.out.append(f" {msg_t} {f.name};") + + def _ensure_xdr_bool(self): + """Ensure the xdr_bool enum exists.""" + if "xdr_bool" not in self.emitted: + self.emitted.add("xdr_bool") + self.out.append("enum xdr_bool : beu32 {") + self.out.append(" FALSE = 0;") + self.out.append(" TRUE = 1;") + self.out.append("}") + self.out.append("") + + def _ensure_optional_union(self, type_name): + """Ensure an optional union for a type exists. Returns the union name.""" + base = self.msg_type(type_name) + uname = f"{base}_opt" + if uname not in self.emitted: + self.emitted.add(uname) + self._ensure_xdr_bool() + arm_lines = [f" TRUE: {base};", f" default: void;"] + self.out.append(f"union {uname} (xdr_bool) {{") + self.out.extend(self._align_block(arm_lines)) + self.out.append("}") + self.out.append("") + return uname + + def _ensure_disc_enum(self, u): + dt = u.disc_type + if dt in self.enums: return dt + base, _, _ = self.resolve_td(dt) + if base in self.enums: return self.enum_name(dt) + if dt == "bool": return self._make_bool_enum(u.name) + return self._make_synth_enum(u) + + def _make_bool_enum(self, uname): + self._ensure_xdr_bool() + return "xdr_bool" + + def _resolve_label_value(self, lbl): + """Resolve a union case label to a numeric value.""" + # Check consts first + if lbl in self.consts: + return self.consts[lbl] + # Check enum values + if lbl in self.enum_vals: + enum_name = self.enum_vals[lbl] + for v in self.enums[enum_name].values: + if v.name == lbl: + return v.value + return lbl + + def _make_synth_enum(self, u): + # First check: if all labels come from a single existing enum, just use it + label_enums = set() + for arm in u.arms: + for lbl in arm.labels: + if lbl in self.enum_vals: + label_enums.add(self.enum_vals[lbl]) + if u.default_arm: + for lbl in u.default_arm.labels: + if lbl != "default" and lbl in self.enum_vals: + label_enums.add(self.enum_vals[lbl]) + if len(label_enums) == 1: + return label_enums.pop() + + key = f"disc_{u.name}" + if key in self.emitted: return key + self.emitted.add(key) + self.out.append(f"enum {key} : beu32 {{") + start = self._start_block() + for arm in u.arms: + for lbl in arm.labels: + val = self._resolve_label_value(lbl) + self.out.append(f" {lbl} = {val};") + if u.default_arm: + for lbl in u.default_arm.labels: + if lbl != "default": + val = self._resolve_label_value(lbl) + self.out.append(f" {lbl} = {val};") + self._end_block(start) + self.out.append("}") + self.out.append("") + return key + + def _emit_union(self, u): + if u.name in self.emitted: return + self.emitted.add(u.name) + + disc_enum = self._ensure_disc_enum(u) + + # Collect arm lines + any wrapper types needed + arm_lines = [] + wrapper_lines = [] + + for arm in u.arms: + for label in arm.labels: + if arm.type_name is None: + arm_lines.append(f" {label}: void;") + else: + payload, wlines = self._resolve_arm(arm.type_name, label, u.name) + wrapper_lines.extend(wlines) + arm_lines.append(f" {label}: {payload};") + + if u.default_arm: + if u.default_arm.type_name is None: + arm_lines.append(f" default: void;") + else: + payload, wlines = self._resolve_arm(u.default_arm.type_name, "default", u.name) + wrapper_lines.extend(wlines) + arm_lines.append(f" default: {payload};") + + # Emit wrappers first, then the union + self.out.extend(wrapper_lines) + self.out.append(f"union {u.name} ({disc_enum}) {{") + self.out.extend(self._align_block(arm_lines)) + self.out.append("}") + self.out.append("") + + def _resolve_arm(self, type_name, label, union_name): + """Resolve a union arm's payload type. Returns (type_name, wrapper_lines).""" + base, fl, vl = self.resolve_td(type_name) + wlines = [] + + # Named struct → use directly + if base in self.structs: + return type_name, wlines + # Promoted typedef → use directly + if self._is_promoted(type_name): + return type_name, wlines + if self._is_promoted(base): + return base, wlines + # Named union → needs disc+union wrapper + if base in self.unions: + name, wl = self._make_union_wrapper(base) + return name, wl + # Bare primitive or enum → scalar wrapper struct + if base in XDR_PRIM or base in self.enums: + name, wl = self._make_scalar_wrapper(type_name, label, union_name) + return name, wl + # Unknown — use as-is + return type_name, wlines + + def _make_scalar_wrapper(self, type_name, label, union_name): + key = f"scalar_{union_name}_{label}" + if key in self.wrappers: + return self.wrappers[key], [] + wname = f"{union_name}_{label}" + self.wrappers[key] = wname + msg_t = self.msg_type(type_name) + lines = [ + f"struct {wname} {{", + f" {msg_t} value;", + "}", + "", + ] + return wname, lines + + def _ensure_union_wrapper_emitted(self, union_name): + """Create and emit a wrapper message inline for a union type.""" + key = f"uwrap_{union_name}" + if key in self.wrappers: + return self.wrappers[key] + u = self.unions[union_name] + disc_enum = self._ensure_disc_enum(u) + wname = f"{union_name}_entry" + self.wrappers[key] = wname + self.out.append(f"message {wname} {{") + start = self._start_block() + self.out.append(f" {disc_enum} disc;") + self.out.append(f" {union_name} value(disc);") + self._end_block(start) + self.out.append("}") + self.out.append("") + return wname + + def _make_union_wrapper(self, union_name): + key = f"uwrap_{union_name}" + if key in self.wrappers: + return self.wrappers[key], [] + u = self.unions[union_name] + disc_enum = self._ensure_disc_enum(u) + wname = f"{union_name}_entry" + self.wrappers[key] = wname + body = self._align_block([ + f" {disc_enum} disc;", + f" {union_name} value(disc);", + ]) + lines = [f"message {wname} {{"] + body + ["}", ""] + return wname, lines + + +def main(): + if len(sys.argv) < 2: + print("Usage: python3 xdr2msg.py ", file=sys.stderr) + sys.exit(1) + + with open(sys.argv[1]) as f: + src = f.read() + + decls = Parser(src).parse() + output = Gen(decls).emit() + print(output, end="") + + +if __name__ == "__main__": + main() From 640cf3c1cfbd509d0dcf0e390165d838eb33c904 Mon Sep 17 00:00:00 2001 From: Joshua Leahy Date: Mon, 16 Mar 2026 17:17:14 +0000 Subject: [PATCH 2/6] Initial cut of NFS server --- go/nfsd/attrs.go | 390 + go/nfsd/clients.go | 177 + go/nfsd/generate.go | 11 + go/nfsd/libnfs_cgo.go | 163 + go/nfsd/libnfs_test.go | 295 + go/nfsd/main.go | 62 + go/nfsd/nfs.go | 15631 +++++++++++++++++++++++++++++++++++++++ go/nfsd/nfs.msg | 1321 ++++ go/nfsd/nfs.x | 1607 ++++ go/nfsd/nfsd_test.go | 3452 +++++++++ go/nfsd/ops.go | 1215 +++ go/nfsd/rpc.go | 167 + go/nfsd/server.go | 277 + go/nfsd/staging.go | 329 + go/nfsd/state.go | 22 + go/nfsd/vfs.go | 581 ++ 16 files changed, 25700 insertions(+) create mode 100644 go/nfsd/attrs.go create mode 100644 go/nfsd/clients.go create mode 100644 go/nfsd/generate.go create mode 100644 go/nfsd/libnfs_cgo.go create mode 100644 go/nfsd/libnfs_test.go create mode 100644 go/nfsd/main.go create mode 100644 go/nfsd/nfs.go create mode 100644 go/nfsd/nfs.msg create mode 100644 go/nfsd/nfs.x create mode 100644 go/nfsd/nfsd_test.go create mode 100644 go/nfsd/ops.go create mode 100644 go/nfsd/rpc.go create mode 100644 go/nfsd/server.go create mode 100644 go/nfsd/staging.go create mode 100644 go/nfsd/state.go create mode 100644 go/nfsd/vfs.go diff --git a/go/nfsd/attrs.go b/go/nfsd/attrs.go new file mode 100644 index 00000000..fbc9bf05 --- /dev/null +++ b/go/nfsd/attrs.go @@ -0,0 +1,390 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "encoding/binary" +) + +// Supported attribute bitmasks. +const ( + supportedAttrs0 = (1 << FATTR4_SUPPORTED_ATTRS) | + (1 << FATTR4_TYPE) | + (1 << FATTR4_FH_EXPIRE_TYPE) | + (1 << FATTR4_CHANGE) | + (1 << FATTR4_SIZE) | + (1 << FATTR4_LINK_SUPPORT) | + (1 << FATTR4_SYMLINK_SUPPORT) | + (1 << FATTR4_NAMED_ATTR) | + (1 << FATTR4_FSID) | + (1 << FATTR4_UNIQUE_HANDLES) | + (1 << FATTR4_LEASE_TIME) | + (1 << FATTR4_RDATTR_ERROR) | + (1 << FATTR4_CANSETTIME) | + (1 << FATTR4_CASE_INSENSITIVE) | + (1 << FATTR4_CASE_PRESERVING) | + (1 << FATTR4_CHOWN_RESTRICTED) | + (1 << FATTR4_FILEHANDLE) | + (1 << FATTR4_FILEID) | + (1 << FATTR4_FILES_AVAIL) | + (1 << FATTR4_FILES_FREE) | + (1 << FATTR4_FILES_TOTAL) | + (1 << FATTR4_HOMOGENEOUS) | + (1 << FATTR4_MAXFILESIZE) | + (1 << FATTR4_MAXLINK) | + (1 << FATTR4_MAXNAME) | + (1 << FATTR4_MAXREAD) | + (1 << FATTR4_MAXWRITE) + + supportedAttrs1 = (1 << (FATTR4_MODE - 32)) | + (1 << (FATTR4_NO_TRUNC - 32)) | + (1 << (FATTR4_NUMLINKS - 32)) | + (1 << (FATTR4_OWNER - 32)) | + (1 << (FATTR4_OWNER_GROUP - 32)) | + (1 << (FATTR4_RAWDEV - 32)) | + (1 << (FATTR4_SPACE_AVAIL - 32)) | + (1 << (FATTR4_SPACE_FREE - 32)) | + (1 << (FATTR4_SPACE_TOTAL - 32)) | + (1 << (FATTR4_SPACE_USED - 32)) | + (1 << (FATTR4_TIME_ACCESS - 32)) | + (1 << (FATTR4_TIME_DELTA - 32)) | + (1 << (FATTR4_TIME_METADATA - 32)) | + (1 << (FATTR4_TIME_MODIFY - 32)) | + (1 << (FATTR4_MOUNTED_ON_FILEID - 32)) + + // Fixed FSID for the entire export. + fsidMajor uint64 = 0x7E4F + fsidMinor uint64 = 0 +) + +// parseBitmap extracts up to two 32-bit words from a Bitmap4 into a fixed array. +func parseBitmap(bm Bitmap4) [2]uint32 { + var mask [2]uint32 + if bm.Count() > 0 { + mask[0] = bm.Data(0) + } + if bm.Count() > 1 { + mask[1] = bm.Data(1) + } + return mask +} + +// inodeNFSType converts an InodeID type to NFSv4 type constant. +func inodeNFSType(id InodeID) uint32 { + switch id.Type() { + case InodeTypeDir: + return 2 // NF4DIR + case InodeTypeSymlink: + return 5 // NF4LNK + default: + return 1 // NF4REG + } +} + +// inodeNFSMode returns the NFS permission mode for an inode. +func inodeNFSMode(id InodeID) uint32 { + switch id.Type() { + case InodeTypeDir: + return 0755 + case InodeTypeSymlink: + return 0777 + default: + return 0644 + } +} + +// encodeAttrs encodes the requested attributes into an XDR byte buffer. +// Attributes MUST be encoded in order of their bit position. +func encodeAttrs(mask [2]uint32, id InodeID, ni NodeInfo) []byte { + buf := make([]byte, 0, 256) + + // Word 0 attributes (bits 0-31). + if mask[0]&(1<: length + data + buf = binary.BigEndian.AppendUint32(buf, 8) + buf = append(buf, inodeIDToFH(id)...) + } + if mask[0]&(1< 0 { + buf = append(buf, make([]byte, pad)...) + } + return buf +} + +// stagedSizes maps InodeID to the staged size for files being written. +// Passed through the dir entry encoding chain so READDIR reflects staged sizes. +type stagedSizes map[InodeID]uint64 + +// readdirEntry holds a directory entry with pre-computed attribute data +// so that exact XDR sizes can be calculated before encoding. +type readdirEntry struct { + DirEntry + respMask [2]uint32 + attrData []byte // nil means Stat failed; encode empty attrs +} + +// prepareReaddirEntries pre-computes attribute data for each directory entry. +func prepareReaddirEntries(entries []DirEntry, reqMask [2]uint32, vfs TernVFS, ss stagedSizes) []readdirEntry { + var respMask [2]uint32 + respMask[0] = reqMask[0] & supportedAttrs0 + respMask[1] = reqMask[1] & supportedAttrs1 + + result := make([]readdirEntry, len(entries)) + for i, e := range entries { + result[i].DirEntry = e + result[i].respMask = respMask + ni, err := vfs.Stat(e.ID) + if err != nil { + continue // attrData stays nil → empty attrs + } + if sz, ok := ss[e.ID]; ok { + ni.Size = sz + } + result[i].attrData = encodeAttrs(respMask, e.ID, ni) + } + return result +} + +// readdirEntryXDRSize returns the exact XDR byte count for one entry, +// including the leading present=TRUE(4) that precedes it in the list. +// +// present(4) + cookie(8) + name_len(4) + name_padded + bitmap_count(4) +// + 2*bitmap_word(8) + attrvals_len(4) + attrdata + nextentry_present(4) +// +// The nextentry_present at the end is this entry's "next" pointer (TRUE or +// FALSE depending on whether another entry follows); its 4 bytes are included +// here so that each entry accounts for all the bytes it contributes. +func readdirEntryXDRSize(re *readdirEntry) int { + namePadded := (len(re.Name) + 3) &^ 3 + var attrsSize int + if re.attrData != nil { + // bitmap: count(4) + 2 words(8) = 12; attrvals: len(4) + data + attrsSize = 12 + 4 + len(re.attrData) + } else { + // Stat failed: empty bitmap count(4)=0 + empty attrvals len(4)=0 + attrsSize = 4 + 4 + } + // present(4) + cookie(8) + name_len(4) + name_padded + attrs + nextentry(4) + return 4 + 8 + 4 + namePadded + attrsSize + 4 +} + +// readdirEntryDirSize returns the directory-information bytes for dircount: +// cookie(8) + name XDR (len(4) + padded data). +func readdirEntryDirSize(re *readdirEntry) int { + namePadded := (len(re.Name) + 3) &^ 3 + return 8 + 4 + namePadded +} + +// Overhead within READDIR4resok that is always present: +// cookieverf(8) + entries_present_or_terminal_FALSE(4) + eof(4). +const readdirResokOverhead = 16 + +// encodeDirEntries writes directory entries into a Dirlist4Writer. +func encodeDirEntries(dirW *Dirlist4Writer, entries []readdirEntry, eof bool) { + if len(entries) == 0 { + dirW.SetEntries_Default(FALSE) + } else { + entW := dirW.SetEntries_True() + writeEntryChain(&entW, dirW, entries, 0) + } + if eof { + dirW.SetEof(TRUE) + } else { + dirW.SetEof(FALSE) + } +} + +func writeEntryChain(entW *Entry4Writer, dirW *Dirlist4Writer, entries []readdirEntry, idx int) { + re := &entries[idx] + entW.SetCookie(re.NameHash) + + nameW := entW.StartName() + buf := nameW.SetData([]byte(re.Name)).Finish() + entW.Resume(buf) + + writeEntryAttrs(entW, re) + + if idx+1 < len(entries) { + nextW := entW.SetNextentry_True() + writeEntryChain(&nextW, dirW, entries, idx+1) + } else { + entW.SetNextentry_Default(FALSE) + buf = entW.Finish() + dirW.Resume(buf) + } +} + +func writeEntryAttrs(entW *Entry4Writer, re *readdirEntry) { + faw := entW.StartAttrs() + if re.attrData == nil { + writeEmptyAttrs(&faw, entW) + return + } + bmW := faw.StartAttrmask() + bmW.AppendData(re.respMask[0]) + bmW.AppendData(re.respMask[1]) + buf := bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(re.attrData).Finish() + faw.Resume(buf) + buf = faw.Finish() + entW.Resume(buf) +} + +func writeEmptyAttrs(faw *Fattr4Writer, entW *Entry4Writer) { + bmW := faw.StartAttrmask() + buf := bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + entW.Resume(buf) +} diff --git a/go/nfsd/clients.go b/go/nfsd/clients.go new file mode 100644 index 00000000..53889c16 --- /dev/null +++ b/go/nfsd/clients.go @@ -0,0 +1,177 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "os" + "strconv" + "strings" + "sync" +) + +// nfsDirName is the hidden directory under the VFS root used for NFS server +// state. Currently contains "clients/" for persistent client ID assignment. +const nfsDirName = ".nfs" + +// ClientStore manages persistent NFS client ID assignment backed by TernVFS. +// +// Client identities are stored as files in /.nfs/clients/. The filename is +// the escaped client id string. The file contents are the 8-byte verifier. +// The file's InodeID serves as the client's unique 64-bit clientid. +// +// Unconfirmed clients have a pending file named .. +// On confirmation, the pending file is renamed over the confirmed file, +// atomically replacing the old verifier and producing a new InodeID (clientid). +type ClientStore struct { + mu sync.Mutex + fs TernVFS + dirID InodeID + pending map[uint64]pendingClient // clientID → pending info + confirmed map[uint64]bool // set of known confirmed clientIDs +} + +type pendingClient struct { + name string // escaped client id + verfHex string // hex-encoded verifier +} + +// NewClientStore creates a ClientStore, ensuring the /.nfs/clients/ directory +// hierarchy exists in the VFS. +func NewClientStore(fs TernVFS) (*ClientStore, error) { + rootID := fs.RootID() + + // Ensure /.nfs/ directory exists. + nfsID, err := fs.Lookup(rootID, nfsDirName) + if errors.Is(err, os.ErrNotExist) { + nfsID, err = fs.Mkdir(rootID, nfsDirName) + } + if err != nil { + return nil, fmt.Errorf("client store: create %s dir: %w", nfsDirName, err) + } + + // Ensure /.nfs/clients/ directory exists. + clientsID, err := fs.Lookup(nfsID, "clients") + if errors.Is(err, os.ErrNotExist) { + clientsID, err = fs.Mkdir(nfsID, "clients") + } + if err != nil { + return nil, fmt.Errorf("client store: create clients dir: %w", err) + } + + return &ClientStore{ + fs: fs, + dirID: clientsID, + pending: make(map[uint64]pendingClient), + confirmed: make(map[uint64]bool), + }, nil +} + +// SetClientID assigns a persistent client ID for the given verifier and +// identity string. If a confirmed file with a matching verifier already +// exists, its InodeID is returned unchanged. Otherwise a pending file is +// created; the caller must follow up with ConfirmClientID. +func (cs *ClientStore) SetClientID(verifier [8]byte, id []byte) (uint64, error) { + cs.mu.Lock() + defer cs.mu.Unlock() + + name := escapeClientID(id) + verfHex := hex.EncodeToString(verifier[:]) + + // Check if a confirmed file exists for this client id. + confirmedID, err := cs.fs.Lookup(cs.dirID, name) + if err == nil { + // File exists — read stored verifier. + var stored [8]byte + n, _, readErr := cs.fs.Read(confirmedID, 0, stored[:]) + if readErr == nil && n == 8 && stored == verifier { + // Same verifier — return existing clientid. + cs.confirmed[uint64(confirmedID)] = true + return uint64(confirmedID), nil + } + // Different verifier — client reboot. Fall through to create pending. + } + + // Check if a pending file already exists (retransmission). + pendingName := name + "." + verfHex + pendingID, err := cs.fs.Lookup(cs.dirID, pendingName) + if err == nil { + cs.pending[uint64(pendingID)] = pendingClient{name: name, verfHex: verfHex} + return uint64(pendingID), nil + } + + // Create pending file with verifier as content. + newID, err := cs.fs.CreateFile(cs.dirID, pendingName, bytes.NewReader(verifier[:])) + if err != nil { + if errors.Is(err, os.ErrExist) { + // Race with another server — lookup the existing file. + newID, err = cs.fs.Lookup(cs.dirID, pendingName) + if err != nil { + return 0, err + } + } else { + return 0, err + } + } + + cs.pending[uint64(newID)] = pendingClient{name: name, verfHex: verfHex} + return uint64(newID), nil +} + +// ConfirmClientID confirms a pending client by renaming its pending file +// over the confirmed file. Returns the old clientid that was replaced +// (0 if none). The caller should purge any state associated with the old +// clientid. +func (cs *ClientStore) ConfirmClientID(clientID uint64) (uint64, error) { + cs.mu.Lock() + defer cs.mu.Unlock() + + p, ok := cs.pending[clientID] + if !ok { + // Not pending — might be already confirmed (no-op confirm). + if cs.confirmed[clientID] { + return 0, nil + } + return 0, nfsError(NFS4ERR_STALE_CLIENTID) + } + delete(cs.pending, clientID) + + // Check if there's an existing confirmed file (will be replaced). + var oldClientID uint64 + oldID, err := cs.fs.Lookup(cs.dirID, p.name) + if err == nil { + oldClientID = uint64(oldID) + delete(cs.confirmed, oldClientID) + } + + // Rename pending file → confirmed file. + pendingName := p.name + "." + p.verfHex + if err := cs.fs.Rename(cs.dirID, pendingName, cs.dirID, p.name); err != nil { + return 0, err + } + + cs.confirmed[clientID] = true + return oldClientID, nil +} + +// escapeClientID produces a filesystem-safe filename from an NFS client id +// string. Uses Go-style escaping: normal printable ASCII passes through, +// control characters and non-printable bytes become \xNN or \t, \n, etc. +// Forward slashes are additionally escaped since they are path separators. +func escapeClientID(id []byte) string { + q := strconv.Quote(string(id)) + // Strip surrounding double quotes added by Quote. + s := q[1 : len(q)-1] + // Escape / which is printable but invalid in filenames. + s = strings.ReplaceAll(s, "/", `\x2f`) + // Guard against pathological "." and ".." names. + if s == "." || s == ".." { + s = `\x2e` + s[1:] + } + return s +} diff --git a/go/nfsd/generate.go b/go/nfsd/generate.go new file mode 100644 index 00000000..68956353 --- /dev/null +++ b/go/nfsd/generate.go @@ -0,0 +1,11 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +// Generate NFS protocol types from XDR specification. +// Pipeline: nfs.x → xdr2msg.py → nfs.msg → msgparse → nfs.go + +//go:generate sh -c "python3 ../msgparse/xdr2msg.py nfs.x > nfs.msg" +//go:generate go run ../msgparse -pkg main -o nfs.go nfs.msg diff --git a/go/nfsd/libnfs_cgo.go b/go/nfsd/libnfs_cgo.go new file mode 100644 index 00000000..78735768 --- /dev/null +++ b/go/nfsd/libnfs_cgo.go @@ -0,0 +1,163 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +//go:build libnfs + +package main + +/* +#cgo CFLAGS: -I/tmp/libnfs-install/include +#cgo LDFLAGS: -L/tmp/libnfs-install/lib -lnfs -Wl,-rpath,/tmp/libnfs-install/lib +#include +#include +#include +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +type libnfsClient struct { + nfs *C.struct_nfs_context +} + +func libnfsConnect(host string, port int) (*libnfsClient, error) { + nfs := C.nfs_init_context() + if nfs == nil { + return nil, fmt.Errorf("nfs_init_context failed") + } + C.nfs_set_version(nfs, 4) + C.nfs_set_nfsport(nfs, C.int(port)) + C.nfs_set_timeout(nfs, 5000) + C.nfs_set_debug(nfs, 2) + + chost := C.CString(host) + defer C.free(unsafe.Pointer(chost)) + cexport := C.CString("/") + defer C.free(unsafe.Pointer(cexport)) + + ret := C.nfs_mount(nfs, chost, cexport) + if ret != 0 { + msg := C.GoString(C.nfs_get_error(nfs)) + C.nfs_destroy_context(nfs) + return nil, fmt.Errorf("nfs_mount: %s (ret=%d)", msg, ret) + } + return &libnfsClient{nfs: nfs}, nil +} + +func (c *libnfsClient) Close() { + if c.nfs != nil { + C.nfs_destroy_context(c.nfs) + c.nfs = nil + } +} + +type libnfsStat struct { + Mode uint64 + Size uint64 + Nlink uint64 +} + +func (c *libnfsClient) Stat(path string) (libnfsStat, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + var st C.struct_nfs_stat_64 + ret := C.nfs_stat64(c.nfs, cpath, &st) + if ret != 0 { + msg := C.GoString(C.nfs_get_error(c.nfs)) + return libnfsStat{}, fmt.Errorf("nfs_stat64(%q): %s", path, msg) + } + return libnfsStat{ + Mode: uint64(st.nfs_mode), + Size: uint64(st.nfs_size), + Nlink: uint64(st.nfs_nlink), + }, nil +} + +func (c *libnfsClient) ReadFile(path string) ([]byte, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + var fh *C.struct_nfsfh + ret := C.nfs_open(c.nfs, cpath, C.O_RDONLY, &fh) + if ret != 0 { + msg := C.GoString(C.nfs_get_error(c.nfs)) + return nil, fmt.Errorf("nfs_open(%q): %s", path, msg) + } + defer C.nfs_close(c.nfs, fh) + + var result []byte + buf := make([]byte, 64*1024) + for { + n := C.nfs_read(c.nfs, fh, unsafe.Pointer(&buf[0]), C.size_t(len(buf))) + if n < 0 { + msg := C.GoString(C.nfs_get_error(c.nfs)) + return nil, fmt.Errorf("nfs_read(%q): %s", path, msg) + } + if n == 0 { + break + } + result = append(result, buf[:n]...) + } + return result, nil +} + +func (c *libnfsClient) ReadDir(path string) ([]string, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + var dir *C.struct_nfsdir + ret := C.nfs_opendir(c.nfs, cpath, &dir) + if ret != 0 { + msg := C.GoString(C.nfs_get_error(c.nfs)) + return nil, fmt.Errorf("nfs_opendir(%q): %s", path, msg) + } + defer C.nfs_closedir(c.nfs, dir) + + var names []string + for { + ent := C.nfs_readdir(c.nfs, dir) + if ent == nil { + break + } + name := C.GoString(ent.name) + if name != "." && name != ".." { + names = append(names, name) + } + } + return names, nil +} + +func (c *libnfsClient) Readlink(path string) (string, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + buf := make([]byte, 4096) + ret := C.nfs_readlink(c.nfs, cpath, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf))) + if ret != 0 { + msg := C.GoString(C.nfs_get_error(c.nfs)) + return "", fmt.Errorf("nfs_readlink(%q): %s", path, msg) + } + for i, b := range buf { + if b == 0 { + return string(buf[:i]), nil + } + } + return string(buf), nil +} + +func (c *libnfsClient) Lstat(path string) (libnfsStat, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + var st C.struct_nfs_stat_64 + ret := C.nfs_lstat64(c.nfs, cpath, &st) + if ret != 0 { + msg := C.GoString(C.nfs_get_error(c.nfs)) + return libnfsStat{}, fmt.Errorf("nfs_lstat64(%q): %s", path, msg) + } + return libnfsStat{ + Mode: uint64(st.nfs_mode), + Size: uint64(st.nfs_size), + Nlink: uint64(st.nfs_nlink), + }, nil +} diff --git a/go/nfsd/libnfs_test.go b/go/nfsd/libnfs_test.go new file mode 100644 index 00000000..3300c78f --- /dev/null +++ b/go/nfsd/libnfs_test.go @@ -0,0 +1,295 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +//go:build libnfs + +package main + +import ( + "fmt" + "net" + "os" + "path/filepath" + "sort" + "testing" +) + +func connectLibnfs(t *testing.T, addr string) *libnfsClient { + t.Helper() + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + t.Fatal(err) + } + var port int + fmt.Sscanf(portStr, "%d", &port) + c, err := libnfsConnect(host, port) + if err != nil { + t.Fatal(err) + } + return c +} + +func TestLibnfs_StatRoot(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + st, err := c.Stat("/") + if err != nil { + t.Fatal(err) + } + if st.Mode&0040000 == 0 { + t.Fatalf("root is not a directory: mode=%#o", st.Mode) + } +} + +func TestLibnfs_ReadFile(t *testing.T) { + dir := t.TempDir() + content := []byte("Hello from libnfs test!") + os.WriteFile(filepath.Join(dir, "test.txt"), content, 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + data, err := c.ReadFile("/test.txt") + if err != nil { + t.Fatal(err) + } + if string(data) != string(content) { + t.Fatalf("read = %q, want %q", data, content) + } +} + +func TestLibnfs_ReadLargeFile(t *testing.T) { + dir := t.TempDir() + content := make([]byte, 128*1024) + for i := range content { + content[i] = byte(i % 251) + } + os.WriteFile(filepath.Join(dir, "large.bin"), content, 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + data, err := c.ReadFile("/large.bin") + if err != nil { + t.Fatal(err) + } + if len(data) != len(content) { + t.Fatalf("read %d bytes, want %d", len(data), len(content)) + } + for i := range data { + if data[i] != content[i] { + t.Fatalf("mismatch at byte %d: got %d, want %d", i, data[i], content[i]) + } + } +} + +func TestLibnfs_ReadDir(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "aaa.txt"), []byte("A"), 0644) + os.WriteFile(filepath.Join(dir, "bbb.txt"), []byte("BB"), 0644) + os.Mkdir(filepath.Join(dir, "subdir"), 0755) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + names, err := c.ReadDir("/") + if err != nil { + t.Fatal(err) + } + sort.Strings(names) + expected := []string{"aaa.txt", "bbb.txt", "subdir"} + if len(names) != len(expected) { + t.Fatalf("readdir = %v, want %v", names, expected) + } + for i := range expected { + if names[i] != expected[i] { + t.Fatalf("readdir[%d] = %q, want %q", i, names[i], expected[i]) + } + } +} + +func TestLibnfs_SubdirNavigation(t *testing.T) { + dir := t.TempDir() + os.MkdirAll(filepath.Join(dir, "a", "b"), 0755) + os.WriteFile(filepath.Join(dir, "a", "b", "deep.txt"), []byte("deep"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + data, err := c.ReadFile("/a/b/deep.txt") + if err != nil { + t.Fatal(err) + } + if string(data) != "deep" { + t.Fatalf("read = %q, want %q", data, "deep") + } + + names, err := c.ReadDir("/a") + if err != nil { + t.Fatal(err) + } + if len(names) != 1 || names[0] != "b" { + t.Fatalf("readdir(/a) = %v, want [b]", names) + } +} + +func TestLibnfs_StatFile(t *testing.T) { + dir := t.TempDir() + content := []byte("stat me") + os.WriteFile(filepath.Join(dir, "info.txt"), content, 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + st, err := c.Stat("/info.txt") + if err != nil { + t.Fatal(err) + } + if st.Size != uint64(len(content)) { + t.Fatalf("size = %d, want %d", st.Size, len(content)) + } + if st.Mode&0100000 == 0 { + t.Fatalf("not a regular file: mode=%#o", st.Mode) + } +} + +func TestLibnfs_Readlink(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "target.txt"), []byte("target"), 0644) + os.Symlink("target.txt", filepath.Join(dir, "link.txt")) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + target, err := c.Readlink("/link.txt") + if err != nil { + t.Fatal(err) + } + if target != "target.txt" { + t.Fatalf("readlink = %q, want %q", target, "target.txt") + } + + // Reading through the symlink should also work. + data, err := c.ReadFile("/link.txt") + if err != nil { + t.Fatal(err) + } + if string(data) != "target" { + t.Fatalf("read through symlink = %q, want %q", data, "target") + } +} + +func TestLibnfs_EmptyFile(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "empty.txt"), nil, 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + data, err := c.ReadFile("/empty.txt") + if err != nil { + t.Fatal(err) + } + if len(data) != 0 { + t.Fatalf("expected empty, got %d bytes", len(data)) + } + + st, err := c.Stat("/empty.txt") + if err != nil { + t.Fatal(err) + } + if st.Size != 0 { + t.Fatalf("size = %d, want 0", st.Size) + } +} + +func TestLibnfs_EmptyDir(t *testing.T) { + dir := t.TempDir() + os.Mkdir(filepath.Join(dir, "empty"), 0755) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + names, err := c.ReadDir("/empty") + if err != nil { + t.Fatal(err) + } + if len(names) != 0 { + t.Fatalf("expected empty dir, got %v", names) + } +} + +func TestLibnfs_NonExistent(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + _, err := c.Stat("/no-such-file.txt") + if err == nil { + t.Fatal("expected error for non-existent file") + } +} + +func TestLibnfs_ManyFiles(t *testing.T) { + dir := t.TempDir() + var expected []string + for i := 0; i < 100; i++ { + name := fmt.Sprintf("file_%03d.txt", i) + os.WriteFile(filepath.Join(dir, name), []byte(name), 0644) + expected = append(expected, name) + } + sort.Strings(expected) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + + c := connectLibnfs(t, addr) + defer c.Close() + + names, err := c.ReadDir("/") + if err != nil { + t.Fatal(err) + } + sort.Strings(names) + if len(names) != len(expected) { + t.Fatalf("readdir returned %d entries, want %d", len(names), len(expected)) + } + for i := range expected { + if names[i] != expected[i] { + t.Fatalf("readdir[%d] = %q, want %q", i, names[i], expected[i]) + } + } +} diff --git a/go/nfsd/main.go b/go/nfsd/main.go new file mode 100644 index 00000000..35990b2f --- /dev/null +++ b/go/nfsd/main.go @@ -0,0 +1,62 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "flag" + "log/slog" + "os" + "path/filepath" +) + +func main() { + addr := flag.String("addr", ":2049", "listen address") + root := flag.String("root", ".", "root directory to export") + staging := flag.String("staging", "", "staging directory for writes (omit for read-only)") + verbose := flag.Bool("v", false, "verbose logging of NFS requests/responses") + flag.Parse() + + level := slog.LevelInfo + if *verbose { + level = slog.LevelDebug + } + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})) + + absRoot, err := filepath.Abs(*root) + if err != nil { + logger.Error("resolving root path", "err", err) + os.Exit(1) + } + info, err := os.Stat(absRoot) + if err != nil || !info.IsDir() { + logger.Error("root must be a directory", "path", absRoot) + os.Exit(1) + } + + var ss StagingStore + if *staging != "" { + ss, err = NewLocalStagingStore(*staging, logger) + if err != nil { + logger.Error("creating staging store", "err", err) + os.Exit(1) + } + logger.Info("staging directory configured", "path", *staging) + } else { + ss = readOnlyStagingStore{} + logger.Info("no staging directory — read-only mode") + } + + fs := NewLocalTernVFS(absRoot) + srv, err := NewServer(fs, ss, logger) + if err != nil { + logger.Error("creating server", "err", err) + os.Exit(1) + } + logger.Info("NFS server listening", "addr", *addr, "root", absRoot) + if err := srv.ListenAndServe(*addr); err != nil { + logger.Error("server error", "err", err) + os.Exit(1) + } +} diff --git a/go/nfsd/nfs.go b/go/nfsd/nfs.go new file mode 100644 index 00000000..1cccd0a6 --- /dev/null +++ b/go/nfsd/nfs.go @@ -0,0 +1,15631 @@ +package main + +import ( + "encoding/binary" + "fmt" + "strings" +) + +// ------------------------------------------------------- +// Enum constants +// ------------------------------------------------------- + +const ( + NFS4_FHSIZE = 128 + NFS4_VERIFIER_SIZE = 8 + NFS4_OTHER_SIZE = 12 + NFS4_OPAQUE_LIMIT = 1024 + NFS4_INT64_MAX = 0x7fffffffffffffff + NFS4_UINT64_MAX = 0xffffffffffffffff + NFS4_INT32_MAX = 0x7fffffff + NFS4_UINT32_MAX = 0xffffffff + ACL4_SUPPORT_ALLOW_ACL = 0x00000001 + ACL4_SUPPORT_DENY_ACL = 0x00000002 + ACL4_SUPPORT_AUDIT_ACL = 0x00000004 + ACL4_SUPPORT_ALARM_ACL = 0x00000008 + ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000 + ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001 + ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002 + ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003 + ACE4_FILE_INHERIT_ACE = 0x00000001 + ACE4_DIRECTORY_INHERIT_ACE = 0x00000002 + ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004 + ACE4_INHERIT_ONLY_ACE = 0x00000008 + ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010 + ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020 + ACE4_IDENTIFIER_GROUP = 0x00000040 + ACE4_READ_DATA = 0x00000001 + ACE4_LIST_DIRECTORY = 0x00000001 + ACE4_WRITE_DATA = 0x00000002 + ACE4_ADD_FILE = 0x00000002 + ACE4_APPEND_DATA = 0x00000004 + ACE4_ADD_SUBDIRECTORY = 0x00000004 + ACE4_READ_NAMED_ATTRS = 0x00000008 + ACE4_WRITE_NAMED_ATTRS = 0x00000010 + ACE4_EXECUTE = 0x00000020 + ACE4_DELETE_CHILD = 0x00000040 + ACE4_READ_ATTRIBUTES = 0x00000080 + ACE4_WRITE_ATTRIBUTES = 0x00000100 + ACE4_DELETE = 0x00010000 + ACE4_READ_ACL = 0x00020000 + ACE4_WRITE_ACL = 0x00040000 + ACE4_WRITE_OWNER = 0x00080000 + ACE4_SYNCHRONIZE = 0x00100000 + ACE4_GENERIC_READ = 0x00120081 + ACE4_GENERIC_WRITE = 0x00160106 + ACE4_GENERIC_EXECUTE = 0x001200A0 + MODE4_SUID = 0x800 + MODE4_SGID = 0x400 + MODE4_SVTX = 0x200 + MODE4_RUSR = 0x100 + MODE4_WUSR = 0x080 + MODE4_XUSR = 0x040 + MODE4_RGRP = 0x020 + MODE4_WGRP = 0x010 + MODE4_XGRP = 0x008 + MODE4_ROTH = 0x004 + MODE4_WOTH = 0x002 + MODE4_XOTH = 0x001 + FH4_PERSISTENT = 0x00000000 + FH4_NOEXPIRE_WITH_OPEN = 0x00000001 + FH4_VOLATILE_ANY = 0x00000002 + FH4_VOL_MIGRATION = 0x00000004 + FH4_VOL_RENAME = 0x00000008 + FATTR4_SUPPORTED_ATTRS = 0 + FATTR4_TYPE = 1 + FATTR4_FH_EXPIRE_TYPE = 2 + FATTR4_CHANGE = 3 + FATTR4_SIZE = 4 + FATTR4_LINK_SUPPORT = 5 + FATTR4_SYMLINK_SUPPORT = 6 + FATTR4_NAMED_ATTR = 7 + FATTR4_FSID = 8 + FATTR4_UNIQUE_HANDLES = 9 + FATTR4_LEASE_TIME = 10 + FATTR4_RDATTR_ERROR = 11 + FATTR4_FILEHANDLE = 19 + FATTR4_ACL = 12 + FATTR4_ACLSUPPORT = 13 + FATTR4_ARCHIVE = 14 + FATTR4_CANSETTIME = 15 + FATTR4_CASE_INSENSITIVE = 16 + FATTR4_CASE_PRESERVING = 17 + FATTR4_CHOWN_RESTRICTED = 18 + FATTR4_FILEID = 20 + FATTR4_FILES_AVAIL = 21 + FATTR4_FILES_FREE = 22 + FATTR4_FILES_TOTAL = 23 + FATTR4_FS_LOCATIONS = 24 + FATTR4_HIDDEN = 25 + FATTR4_HOMOGENEOUS = 26 + FATTR4_MAXFILESIZE = 27 + FATTR4_MAXLINK = 28 + FATTR4_MAXNAME = 29 + FATTR4_MAXREAD = 30 + FATTR4_MAXWRITE = 31 + FATTR4_MIMETYPE = 32 + FATTR4_MODE = 33 + FATTR4_NO_TRUNC = 34 + FATTR4_NUMLINKS = 35 + FATTR4_OWNER = 36 + FATTR4_OWNER_GROUP = 37 + FATTR4_QUOTA_AVAIL_HARD = 38 + FATTR4_QUOTA_AVAIL_SOFT = 39 + FATTR4_QUOTA_USED = 40 + FATTR4_RAWDEV = 41 + FATTR4_SPACE_AVAIL = 42 + FATTR4_SPACE_FREE = 43 + FATTR4_SPACE_TOTAL = 44 + FATTR4_SPACE_USED = 45 + FATTR4_SYSTEM = 46 + FATTR4_TIME_ACCESS = 47 + FATTR4_TIME_ACCESS_SET = 48 + FATTR4_TIME_BACKUP = 49 + FATTR4_TIME_CREATE = 50 + FATTR4_TIME_DELTA = 51 + FATTR4_TIME_METADATA = 52 + FATTR4_TIME_MODIFY = 53 + FATTR4_TIME_MODIFY_SET = 54 + FATTR4_MOUNTED_ON_FILEID = 55 + ACCESS4_READ = 0x00000001 + ACCESS4_LOOKUP = 0x00000002 + ACCESS4_MODIFY = 0x00000004 + ACCESS4_EXTEND = 0x00000008 + ACCESS4_DELETE = 0x00000010 + ACCESS4_EXECUTE = 0x00000020 + OPEN4_SHARE_ACCESS_READ = 0x00000001 + OPEN4_SHARE_ACCESS_WRITE = 0x00000002 + OPEN4_SHARE_ACCESS_BOTH = 0x00000003 + OPEN4_SHARE_DENY_NONE = 0x00000000 + OPEN4_SHARE_DENY_READ = 0x00000001 + OPEN4_SHARE_DENY_WRITE = 0x00000002 + OPEN4_SHARE_DENY_BOTH = 0x00000003 + OPEN4_RESULT_CONFIRM = 0x00000002 + OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004 +) + +// nfs_ftype4 constants (beu32) +const ( + NF4REG uint32 = 1 + NF4DIR uint32 = 2 + NF4BLK uint32 = 3 + NF4CHR uint32 = 4 + NF4LNK uint32 = 5 + NF4SOCK uint32 = 6 + NF4FIFO uint32 = 7 + NF4ATTRDIR uint32 = 8 + NF4NAMEDATTR uint32 = 9 +) + +// nfsstat4 constants (beu32) +const ( + NFS4_OK uint32 = 0 + NFS4ERR_PERM uint32 = 1 + NFS4ERR_NOENT uint32 = 2 + NFS4ERR_IO uint32 = 5 + NFS4ERR_NXIO uint32 = 6 + NFS4ERR_ACCESS uint32 = 13 + NFS4ERR_EXIST uint32 = 17 + NFS4ERR_XDEV uint32 = 18 + NFS4ERR_NOTDIR uint32 = 20 + NFS4ERR_ISDIR uint32 = 21 + NFS4ERR_INVAL uint32 = 22 + NFS4ERR_FBIG uint32 = 27 + NFS4ERR_NOSPC uint32 = 28 + NFS4ERR_ROFS uint32 = 30 + NFS4ERR_MLINK uint32 = 31 + NFS4ERR_NAMETOOLONG uint32 = 63 + NFS4ERR_NOTEMPTY uint32 = 66 + NFS4ERR_DQUOT uint32 = 69 + NFS4ERR_STALE uint32 = 70 + NFS4ERR_BADHANDLE uint32 = 10001 + NFS4ERR_BAD_COOKIE uint32 = 10003 + NFS4ERR_NOTSUPP uint32 = 10004 + NFS4ERR_TOOSMALL uint32 = 10005 + NFS4ERR_SERVERFAULT uint32 = 10006 + NFS4ERR_BADTYPE uint32 = 10007 + NFS4ERR_DELAY uint32 = 10008 + NFS4ERR_SAME uint32 = 10009 + NFS4ERR_DENIED uint32 = 10010 + NFS4ERR_EXPIRED uint32 = 10011 + NFS4ERR_LOCKED uint32 = 10012 + NFS4ERR_GRACE uint32 = 10013 + NFS4ERR_FHEXPIRED uint32 = 10014 + NFS4ERR_SHARE_DENIED uint32 = 10015 + NFS4ERR_WRONGSEC uint32 = 10016 + NFS4ERR_CLID_INUSE uint32 = 10017 + NFS4ERR_RESOURCE uint32 = 10018 + NFS4ERR_MOVED uint32 = 10019 + NFS4ERR_NOFILEHANDLE uint32 = 10020 + NFS4ERR_MINOR_VERS_MISMATCH uint32 = 10021 + NFS4ERR_STALE_CLIENTID uint32 = 10022 + NFS4ERR_STALE_STATEID uint32 = 10023 + NFS4ERR_OLD_STATEID uint32 = 10024 + NFS4ERR_BAD_STATEID uint32 = 10025 + NFS4ERR_BAD_SEQID uint32 = 10026 + NFS4ERR_NOT_SAME uint32 = 10027 + NFS4ERR_LOCK_RANGE uint32 = 10028 + NFS4ERR_SYMLINK uint32 = 10029 + NFS4ERR_RESTOREFH uint32 = 10030 + NFS4ERR_LEASE_MOVED uint32 = 10031 + NFS4ERR_ATTRNOTSUPP uint32 = 10032 + NFS4ERR_NO_GRACE uint32 = 10033 + NFS4ERR_RECLAIM_BAD uint32 = 10034 + NFS4ERR_RECLAIM_CONFLICT uint32 = 10035 + NFS4ERR_BADXDR uint32 = 10036 + NFS4ERR_LOCKS_HELD uint32 = 10037 + NFS4ERR_OPENMODE uint32 = 10038 + NFS4ERR_BADOWNER uint32 = 10039 + NFS4ERR_BADCHAR uint32 = 10040 + NFS4ERR_BADNAME uint32 = 10041 + NFS4ERR_BAD_RANGE uint32 = 10042 + NFS4ERR_LOCK_NOTSUPP uint32 = 10043 + NFS4ERR_OP_ILLEGAL uint32 = 10044 + NFS4ERR_DEADLOCK uint32 = 10045 + NFS4ERR_FILE_OPEN uint32 = 10046 + NFS4ERR_ADMIN_REVOKED uint32 = 10047 + NFS4ERR_CB_PATH_DOWN uint32 = 10048 +) + +// time_how4 constants (beu32) +const ( + SET_TO_SERVER_TIME4 uint32 = 0 + SET_TO_CLIENT_TIME4 uint32 = 1 +) + +// nfs_lock_type4 constants (beu32) +const ( + READ_LT uint32 = 1 + WRITE_LT uint32 = 2 + READW_LT uint32 = 3 + WRITEW_LT uint32 = 4 +) + +// xdr_bool constants (beu32) +const ( + FALSE uint32 = 0 + TRUE uint32 = 1 +) + +// createmode4 constants (beu32) +const ( + UNCHECKED4 uint32 = 0 + GUARDED4 uint32 = 1 + EXCLUSIVE4 uint32 = 2 +) + +// opentype4 constants (beu32) +const ( + OPEN4_NOCREATE uint32 = 0 + OPEN4_CREATE uint32 = 1 +) + +// limit_by4 constants (beu32) +const ( + NFS_LIMIT_SIZE uint32 = 1 + NFS_LIMIT_BLOCKS uint32 = 2 +) + +// open_delegation_type4 constants (beu32) +const ( + OPEN_DELEGATE_NONE uint32 = 0 + OPEN_DELEGATE_READ uint32 = 1 + OPEN_DELEGATE_WRITE uint32 = 2 +) + +// open_claim_type4 constants (beu32) +const ( + CLAIM_NULL uint32 = 0 + CLAIM_PREVIOUS uint32 = 1 + CLAIM_DELEGATE_CUR uint32 = 2 + CLAIM_DELEGATE_PREV uint32 = 3 +) + +// rpc_gss_svc_t constants (beu32) +const ( + RPC_GSS_SVC_NONE uint32 = 1 + RPC_GSS_SVC_INTEGRITY uint32 = 2 + RPC_GSS_SVC_PRIVACY uint32 = 3 +) + +// disc_secinfo4 constants (beu32) +const ( + RPCSEC_GSS uint32 = 6 +) + +// stable_how4 constants (beu32) +const ( + UNSTABLE4 uint32 = 0 + DATA_SYNC4 uint32 = 1 + FILE_SYNC4 uint32 = 2 +) + +// nfs_opnum4 constants (beu32) +const ( + OP_ACCESS uint32 = 3 + OP_CLOSE uint32 = 4 + OP_COMMIT uint32 = 5 + OP_CREATE uint32 = 6 + OP_DELEGPURGE uint32 = 7 + OP_DELEGRETURN uint32 = 8 + OP_GETATTR uint32 = 9 + OP_GETFH uint32 = 10 + OP_LINK uint32 = 11 + OP_LOCK uint32 = 12 + OP_LOCKT uint32 = 13 + OP_LOCKU uint32 = 14 + OP_LOOKUP uint32 = 15 + OP_LOOKUPP uint32 = 16 + OP_NVERIFY uint32 = 17 + OP_OPEN uint32 = 18 + OP_OPENATTR uint32 = 19 + OP_OPEN_CONFIRM uint32 = 20 + OP_OPEN_DOWNGRADE uint32 = 21 + OP_PUTFH uint32 = 22 + OP_PUTPUBFH uint32 = 23 + OP_PUTROOTFH uint32 = 24 + OP_READ uint32 = 25 + OP_READDIR uint32 = 26 + OP_READLINK uint32 = 27 + OP_REMOVE uint32 = 28 + OP_RENAME uint32 = 29 + OP_RENEW uint32 = 30 + OP_RESTOREFH uint32 = 31 + OP_SAVEFH uint32 = 32 + OP_SECINFO uint32 = 33 + OP_SETATTR uint32 = 34 + OP_SETCLIENTID uint32 = 35 + OP_SETCLIENTID_CONFIRM uint32 = 36 + OP_VERIFY uint32 = 37 + OP_WRITE uint32 = 38 + OP_RELEASE_LOCKOWNER uint32 = 39 + OP_ILLEGAL uint32 = 10044 +) + +// nfs_cb_opnum4 constants (beu32) +const ( + OP_CB_GETATTR uint32 = 3 + OP_CB_RECALL uint32 = 4 + OP_CB_ILLEGAL uint32 = 10044 +) + +// ------------------------------------------------------- +// Attrlist4 — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type Attrlist4 []byte + +func readAttrlist4(b *[]byte) (Attrlist4, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := Attrlist4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadAttrlist4(b []byte) (Attrlist4, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return Attrlist4(b[:total]), true +} + +func (m Attrlist4) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Attrlist4) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// Attrlist4Writer writes a attrlist4: +// +// data_len + u8 data[data_len] + align(4) +type Attrlist4Writer struct { + buf []byte + off int +} + +func StartAttrlist4(buf []byte) Attrlist4Writer { + off := len(buf) + return Attrlist4Writer{buf: buf, off: off} +} + +func (w Attrlist4Writer) SetData(data []byte) Attrlist4Writer { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w Attrlist4Writer) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Bitmap4 — variable: count(4) + beu32 data[count] +// ------------------------------------------------------- + +type Bitmap4 []byte + +func readBitmap4(b *[]byte) (Bitmap4, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + c_count := int(binary.BigEndian.Uint32((*b)[0:4])) + *b = (*b)[4:] + if len(*b) < c_count*4 { + return nil, false + } + *b = (*b)[c_count*4:] + total := startLen - len(*b) + return Bitmap4(start[:total]), true +} + +func ReadBitmap4(b []byte) (Bitmap4, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + total := 4 + count*4 + if len(b) < total { + return nil, false + } + return Bitmap4(b[:total]), true +} + +func (m Bitmap4) Count() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Bitmap4) Data(i int) uint32 { + off := 4 + i*4 + return binary.BigEndian.Uint32(m[off : off+4]) +} + +// Bitmap4Writer writes a bitmap4: +// +// count + beu32 data[count] +type Bitmap4Writer struct { + buf []byte + off int + count uint32 +} + +func StartBitmap4(buf []byte) Bitmap4Writer { + off := len(buf) + buf = binary.BigEndian.AppendUint32(buf, 0) // count placeholder + return Bitmap4Writer{buf: buf, off: off} +} + +func (w *Bitmap4Writer) AppendData(v uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, v) + w.count++ +} + +func (w *Bitmap4Writer) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], w.count) + return w.buf +} + +// ------------------------------------------------------- +// NfsFh4 — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type NfsFh4 []byte + +func readNfsFh4(b *[]byte) (NfsFh4, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := NfsFh4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadNfsFh4(b []byte) (NfsFh4, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return NfsFh4(b[:total]), true +} + +func (m NfsFh4) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m NfsFh4) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// NfsFh4Writer writes a nfs_fh4: +// +// data_len + u8 data[data_len] + align(4) +type NfsFh4Writer struct { + buf []byte + off int +} + +func StartNfsFh4(buf []byte) NfsFh4Writer { + off := len(buf) + return NfsFh4Writer{buf: buf, off: off} +} + +func (w NfsFh4Writer) SetData(data []byte) NfsFh4Writer { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w NfsFh4Writer) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// SecOid4 — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type SecOid4 []byte + +func readSecOid4(b *[]byte) (SecOid4, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := SecOid4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadSecOid4(b []byte) (SecOid4, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return SecOid4(b[:total]), true +} + +func (m SecOid4) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m SecOid4) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// SecOid4Writer writes a sec_oid4: +// +// data_len + u8 data[data_len] + align(4) +type SecOid4Writer struct { + buf []byte + off int +} + +func StartSecOid4(buf []byte) SecOid4Writer { + off := len(buf) + return SecOid4Writer{buf: buf, off: off} +} + +func (w SecOid4Writer) SetData(data []byte) SecOid4Writer { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w SecOid4Writer) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Utf8strCis — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type Utf8strCis []byte + +func readUtf8strCis(b *[]byte) (Utf8strCis, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := Utf8strCis((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadUtf8strCis(b []byte) (Utf8strCis, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return Utf8strCis(b[:total]), true +} + +func (m Utf8strCis) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Utf8strCis) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// Utf8strCisWriter writes a utf8str_cis: +// +// data_len + u8 data[data_len] + align(4) +type Utf8strCisWriter struct { + buf []byte + off int +} + +func StartUtf8strCis(buf []byte) Utf8strCisWriter { + off := len(buf) + return Utf8strCisWriter{buf: buf, off: off} +} + +func (w Utf8strCisWriter) SetData(data []byte) Utf8strCisWriter { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w Utf8strCisWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Utf8strCs — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type Utf8strCs []byte + +func readUtf8strCs(b *[]byte) (Utf8strCs, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := Utf8strCs((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadUtf8strCs(b []byte) (Utf8strCs, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return Utf8strCs(b[:total]), true +} + +func (m Utf8strCs) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Utf8strCs) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// Utf8strCsWriter writes a utf8str_cs: +// +// data_len + u8 data[data_len] + align(4) +type Utf8strCsWriter struct { + buf []byte + off int +} + +func StartUtf8strCs(buf []byte) Utf8strCsWriter { + off := len(buf) + return Utf8strCsWriter{buf: buf, off: off} +} + +func (w Utf8strCsWriter) SetData(data []byte) Utf8strCsWriter { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w Utf8strCsWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Utf8strMixed — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type Utf8strMixed []byte + +func readUtf8strMixed(b *[]byte) (Utf8strMixed, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := Utf8strMixed((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadUtf8strMixed(b []byte) (Utf8strMixed, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return Utf8strMixed(b[:total]), true +} + +func (m Utf8strMixed) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Utf8strMixed) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// Utf8strMixedWriter writes a utf8str_mixed: +// +// data_len + u8 data[data_len] + align(4) +type Utf8strMixedWriter struct { + buf []byte + off int +} + +func StartUtf8strMixed(buf []byte) Utf8strMixedWriter { + off := len(buf) + return Utf8strMixedWriter{buf: buf, off: off} +} + +func (w Utf8strMixedWriter) SetData(data []byte) Utf8strMixedWriter { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w Utf8strMixedWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Component4 — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type Component4 []byte + +func readComponent4(b *[]byte) (Component4, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := Component4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadComponent4(b []byte) (Component4, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return Component4(b[:total]), true +} + +func (m Component4) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Component4) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// Component4Writer writes a component4: +// +// data_len + u8 data[data_len] + align(4) +type Component4Writer struct { + buf []byte + off int +} + +func StartComponent4(buf []byte) Component4Writer { + off := len(buf) + return Component4Writer{buf: buf, off: off} +} + +func (w Component4Writer) SetData(data []byte) Component4Writer { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w Component4Writer) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Linktext4 — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type Linktext4 []byte + +func readLinktext4(b *[]byte) (Linktext4, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := Linktext4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadLinktext4(b []byte) (Linktext4, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return Linktext4(b[:total]), true +} + +func (m Linktext4) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Linktext4) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// Linktext4Writer writes a linktext4: +// +// data_len + u8 data[data_len] + align(4) +type Linktext4Writer struct { + buf []byte + off int +} + +func StartLinktext4(buf []byte) Linktext4Writer { + off := len(buf) + return Linktext4Writer{buf: buf, off: off} +} + +func (w Linktext4Writer) SetData(data []byte) Linktext4Writer { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w Linktext4Writer) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Pathname4 — variable: count(4) + component4 data[count] +// ------------------------------------------------------- + +type Pathname4 []byte + +func readPathname4(b *[]byte) (Pathname4, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + c_count := int(binary.BigEndian.Uint32((*b)[0:4])) + *b = (*b)[4:] + for i := 0; i < c_count; i++ { + if _, ok := readComponent4(b); !ok { + return nil, false + } + } + total := startLen - len(*b) + return Pathname4(start[:total]), true +} + +func ReadPathname4(b []byte) (Pathname4, bool) { + if len(b) < 4 { + return nil, false + } + return Pathname4(b), true +} + +func (m Pathname4) Count() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Pathname4) Data() Component4Iter { + count := int(binary.BigEndian.Uint32(m[0:4])) + return Component4Iter{ + b: []byte(m[4:]), + count: count, + } +} + +// Component4Iter iterates over variable-size Component4 entries. +type Component4Iter struct { + b []byte + count int + i int + curLen int +} + +func (it *Component4Iter) Next() bool { + b := it.b + if it.curLen > 0 { + if len(b) < it.curLen { + return false + } + b = b[it.curLen:] + it.curLen = 0 + } + if it.i >= it.count { + it.b = b + return false + } + if len(b) < 4 { + return false + } + n := int(binary.BigEndian.Uint32(b[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return false + } + it.b = b + it.curLen = total + it.i++ + return true +} + +func (it *Component4Iter) Data() Component4 { + return Component4(it.b) +} + +// Pathname4Writer writes a pathname4: +// +// count + component4 data[count] +type Pathname4Writer struct { + buf []byte + off int + count uint32 +} + +func StartPathname4(buf []byte) Pathname4Writer { + off := len(buf) + buf = binary.BigEndian.AppendUint32(buf, 0) // count placeholder + return Pathname4Writer{buf: buf, off: off} +} + +func (w *Pathname4Writer) AppendData() Component4Writer { + _ = w.buf[:1] + w.count++ + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *Pathname4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *Pathname4Writer) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], w.count) + return w.buf +} + +// ------------------------------------------------------- +// Verifier4 — fixed 8 bytes: data(u8[8]) +// ------------------------------------------------------- + +type Verifier4 struct { + m *[verifier4Size]byte +} + +const verifier4Size = 8 + +func readVerifier4(b *[]byte) (Verifier4, bool) { + if len(*b) < verifier4Size { + return Verifier4{}, false + } + result := Verifier4{m: (*[verifier4Size]byte)(*b)} + *b = (*b)[verifier4Size:] + return result, true +} + +func ReadVerifier4(b []byte) (Verifier4, bool) { + return readVerifier4(&b) +} + +func StartVerifier4(buf []byte) ([]byte, Verifier4) { + buf = append(buf, make([]byte, verifier4Size)...) + return buf, Verifier4{m: (*[verifier4Size]byte)(buf[len(buf)-verifier4Size:])} +} + +func (m Verifier4) Data(i int) uint8 { + off := 0 + i*1 + return m.m[off : off+1][0] +} + +func (m Verifier4) SetData(i int, v uint8) { + off := 0 + i*1 + m.m[off : off+1][0] = v +} + +// ------------------------------------------------------- +// Nfstime4 — fixed 12 bytes: seconds(8, bei64) + nseconds(4, beu32) +// ------------------------------------------------------- + +type Nfstime4 struct { + m *[nfstime4Size]byte +} + +const nfstime4Size = 12 + +func readNfstime4(b *[]byte) (Nfstime4, bool) { + if len(*b) < nfstime4Size { + return Nfstime4{}, false + } + result := Nfstime4{m: (*[nfstime4Size]byte)(*b)} + *b = (*b)[nfstime4Size:] + return result, true +} + +func ReadNfstime4(b []byte) (Nfstime4, bool) { + return readNfstime4(&b) +} + +func StartNfstime4(buf []byte) ([]byte, Nfstime4) { + buf = append(buf, make([]byte, nfstime4Size)...) + return buf, Nfstime4{m: (*[nfstime4Size]byte)(buf[len(buf)-nfstime4Size:])} +} + +func (m Nfstime4) Seconds() int64 { + return int64(binary.BigEndian.Uint64(m.m[0:8])) +} + +func (m Nfstime4) Nseconds() uint32 { + return binary.BigEndian.Uint32(m.m[8:12]) +} + +func (m Nfstime4) SetSeconds(v int64) { + binary.BigEndian.PutUint64(m.m[0:8], uint64(v)) +} + +func (m Nfstime4) SetNseconds(v uint32) { + binary.BigEndian.PutUint32(m.m[8:12], v) +} + +// ------------------------------------------------------- +// Settime4 — union on time_how4 (external discriminant) +// ------------------------------------------------------- + +type Settime4 struct { + b []byte + disc uint32 +} + +func readSettime4(b *[]byte, timeHow4 uint32) (Settime4, bool) { + switch timeHow4 { + case SET_TO_CLIENT_TIME4: + r, ok := readNfstime4(b) + if !ok { + return Settime4{}, false + } + return Settime4{b: r.m[:], disc: timeHow4}, true + default: + return Settime4{b: (*b)[:0], disc: timeHow4}, true + } +} + +func (m Settime4) AsNfstime4() Nfstime4 { + if m.disc != SET_TO_CLIENT_TIME4 { + panic("wrong union discriminant") + } + return Nfstime4{m: (*[nfstime4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// Fsid4 — fixed 16 bytes: major(8, beu64) + minor(8, beu64) +// ------------------------------------------------------- + +type Fsid4 struct { + m *[fsid4Size]byte +} + +const fsid4Size = 16 + +func ReadFsid4(b []byte) (Fsid4, bool) { + if len(b) < fsid4Size { + return Fsid4{}, false + } + return Fsid4{m: (*[fsid4Size]byte)(b)}, true +} + +func StartFsid4(buf []byte) ([]byte, Fsid4) { + buf = append(buf, make([]byte, fsid4Size)...) + return buf, Fsid4{m: (*[fsid4Size]byte)(buf[len(buf)-fsid4Size:])} +} + +func (m Fsid4) Major() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m Fsid4) Minor() uint64 { + return binary.BigEndian.Uint64(m.m[8:16]) +} + +func (m Fsid4) SetMajor(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +func (m Fsid4) SetMinor(v uint64) { + binary.BigEndian.PutUint64(m.m[8:16], v) +} + +// ------------------------------------------------------- +// FsLocation4 — variable: +// server_count(4) + utf8str_cis server[server_count] + rootpath +// ------------------------------------------------------- + +type FsLocation4 struct { + data []byte + off1 int // byte offset within data where rootpath starts +} + +func readFsLocation4(b *[]byte) (FsLocation4, bool) { + if len(*b) < 4 { + return FsLocation4{}, false + } + start := *b + startLen := len(start) + c_server_count := int(binary.BigEndian.Uint32((*b)[0:4])) + *b = (*b)[4:] + for i := 0; i < c_server_count; i++ { + if _, ok := readUtf8strCis(b); !ok { + return FsLocation4{}, false + } + } + off1 := startLen - len(*b) + if _, ok := readPathname4(b); !ok { + return FsLocation4{}, false + } + total := startLen - len(*b) + return FsLocation4{data: start[:total], off1: off1}, true +} + +func ReadFsLocation4(b []byte) (FsLocation4, bool) { + return readFsLocation4(&b) +} + +func (m FsLocation4) Server() Utf8strCisIter { + server_count := int(binary.BigEndian.Uint32(m.data[0:4])) + return Utf8strCisIter{b: m.data[4:], count: server_count} +} + +func (m FsLocation4) ServerCount() uint32 { + return binary.BigEndian.Uint32(m.data[0:4]) +} + +func (m FsLocation4) Rootpath() Pathname4 { + return Pathname4(m.data[m.off1:]) +} + +// Utf8strCisIter iterates over variable-size Utf8strCis entries. +type Utf8strCisIter struct { + b []byte + count int + i int + curLen int +} + +func (it *Utf8strCisIter) Next() bool { + b := it.b + if it.curLen > 0 { + if len(b) < it.curLen { + return false + } + b = b[it.curLen:] + it.curLen = 0 + } + if it.i >= it.count { + it.b = b + return false + } + if len(b) < 4 { + return false + } + n := int(binary.BigEndian.Uint32(b[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return false + } + it.b = b + it.curLen = total + it.i++ + return true +} + +func (it *Utf8strCisIter) Server() Utf8strCis { + return Utf8strCis(it.b) +} + +// FsLocation4Writer writes a fs_location4: +// +// server_count + utf8str_cis server[server_count] + rootpath +type FsLocation4Writer struct { + buf []byte + off int + serverCount uint32 + phase uint8 +} + +func StartFsLocation4(buf []byte) FsLocation4Writer { + off := len(buf) + buf = binary.BigEndian.AppendUint32(buf, 0) // server_count placeholder + return FsLocation4Writer{buf: buf, off: off} +} + +func (w *FsLocation4Writer) AppendServer() Utf8strCisWriter { + _ = w.buf[:1] + if w.phase > 0 { + panic("writer fields called out of order") + } + w.serverCount++ + child := StartUtf8strCis(w.buf) + w.buf = nil + return child +} + +func (w *FsLocation4Writer) StartRootpath() Pathname4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartPathname4(w.buf) + w.buf = nil + return child +} + +func (w *FsLocation4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *FsLocation4Writer) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], w.serverCount) + return w.buf +} + +// ------------------------------------------------------- +// FsLocations4 — variable: +// fs_root + locations_count(4) + fs_location4 locations[locations_count] +// ------------------------------------------------------- + +type FsLocations4 struct { + data []byte + off1 int // byte offset within data where locations_count starts +} + +func readFsLocations4(b *[]byte) (FsLocations4, bool) { + start := *b + startLen := len(start) + if _, ok := readPathname4(b); !ok { + return FsLocations4{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return FsLocations4{}, false + } + c_locations_count := int(binary.BigEndian.Uint32((*b)[:4])) + *b = (*b)[4:] + for i := 0; i < c_locations_count; i++ { + if _, ok := readFsLocation4(b); !ok { + return FsLocations4{}, false + } + } + total := startLen - len(*b) + return FsLocations4{data: start[:total], off1: off1}, true +} + +func ReadFsLocations4(b []byte) (FsLocations4, bool) { + return readFsLocations4(&b) +} + +func (m FsLocations4) FsRoot() Pathname4 { + return Pathname4(m.data[0:m.off1]) +} + +func (m FsLocations4) Locations() FsLocation4Iter { + locations_count := int(binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4])) + return FsLocation4Iter{b: m.data[m.off1+4:], count: locations_count} +} + +func (m FsLocations4) LocationsCount() uint32 { + return binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4]) +} + +// FsLocation4Iter iterates over variable-size FsLocation4 entries. +type FsLocation4Iter struct { + b []byte + count int + i int + cur FsLocation4 +} + +func (it *FsLocation4Iter) Next() bool { + if it.i >= it.count { + return false + } + var ok bool + it.cur, ok = readFsLocation4(&it.b) + if !ok { + return false + } + it.i++ + return true +} + +func (it *FsLocation4Iter) Location() FsLocation4 { + return it.cur +} + +// FsLocations4Writer writes a fs_locations4: +// +// fs_root + locations_count + fs_location4 locations[locations_count] +type FsLocations4Writer struct { + buf []byte + off int + locationsCount uint32 + locationsCountOff int + phase uint8 +} + +func StartFsLocations4(buf []byte) FsLocations4Writer { + off := len(buf) + return FsLocations4Writer{buf: buf, off: off} +} + +func (w *FsLocations4Writer) AppendLocations() FsLocation4Writer { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.locationsCountOff == 0 { + w.locationsCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.locationsCount++ + child := StartFsLocation4(w.buf) + w.buf = nil + return child +} + +func (w *FsLocations4Writer) StartFsRoot() Pathname4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartPathname4(w.buf) + w.buf = nil + return child +} + +func (w *FsLocations4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *FsLocations4Writer) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.locationsCountOff:w.locationsCountOff+4], w.locationsCount) + return w.buf +} + +// ------------------------------------------------------- +// Nfsace4 — variable: type_val(4) + flag(4) + access_mask(4) + who +// ------------------------------------------------------- + +type Nfsace4 []byte + +func readNfsace4(b *[]byte) (Nfsace4, bool) { + if len(*b) < 12 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[12:] + if _, ok := readUtf8strMixed(b); !ok { + return nil, false + } + total := startLen - len(*b) + return Nfsace4(start[:total]), true +} + +func ReadNfsace4(b []byte) (Nfsace4, bool) { + return readNfsace4(&b) +} + +func (m Nfsace4) TypeVal() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Nfsace4) Flag() uint32 { + return binary.BigEndian.Uint32(m[4:8]) +} + +func (m Nfsace4) AccessMask() uint32 { + return binary.BigEndian.Uint32(m[8:12]) +} + +func (m Nfsace4) Who() Utf8strMixed { + return Utf8strMixed(m[12:]) +} + +// Nfsace4Writer writes a nfsace4: +// +// type_val + flag + access_mask + who +type Nfsace4Writer struct { + buf []byte + header *[12]byte +} + +func StartNfsace4(buf []byte) Nfsace4Writer { + buf = append(buf, make([]byte, 12)...) // type_val(4) + flag(4) + access_mask(4) + return Nfsace4Writer{buf: buf, header: (*[12]byte)(buf[len(buf)-12:])} +} + +func (w *Nfsace4Writer) SetTypeVal(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *Nfsace4Writer) SetFlag(v uint32) { + binary.BigEndian.PutUint32(w.header[4:8], v) +} + +func (w *Nfsace4Writer) SetAccessMask(v uint32) { + binary.BigEndian.PutUint32(w.header[8:12], v) +} + +func (w *Nfsace4Writer) StartWho() Utf8strMixedWriter { + child := StartUtf8strMixed(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *Nfsace4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *Nfsace4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// Specdata4 — fixed 8 bytes: specdata1(4, beu32) + specdata2(4, beu32) +// ------------------------------------------------------- + +type Specdata4 struct { + m *[specdata4Size]byte +} + +const specdata4Size = 8 + +func readSpecdata4(b *[]byte) (Specdata4, bool) { + if len(*b) < specdata4Size { + return Specdata4{}, false + } + result := Specdata4{m: (*[specdata4Size]byte)(*b)} + *b = (*b)[specdata4Size:] + return result, true +} + +func ReadSpecdata4(b []byte) (Specdata4, bool) { + return readSpecdata4(&b) +} + +func StartSpecdata4(buf []byte) ([]byte, Specdata4) { + buf = append(buf, make([]byte, specdata4Size)...) + return buf, Specdata4{m: (*[specdata4Size]byte)(buf[len(buf)-specdata4Size:])} +} + +func (m Specdata4) Specdata1() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m Specdata4) Specdata2() uint32 { + return binary.BigEndian.Uint32(m.m[4:8]) +} + +func (m Specdata4) SetSpecdata1(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m Specdata4) SetSpecdata2(v uint32) { + binary.BigEndian.PutUint32(m.m[4:8], v) +} + +// ------------------------------------------------------- +// Fattr4 — variable: attrmask + attr_vals +// ------------------------------------------------------- + +type Fattr4 struct { + data []byte + off1 int // byte offset within data where attr_vals starts +} + +func readFattr4(b *[]byte) (Fattr4, bool) { + start := *b + startLen := len(start) + if _, ok := readBitmap4(b); !ok { + return Fattr4{}, false + } + off1 := startLen - len(*b) + if _, ok := readAttrlist4(b); !ok { + return Fattr4{}, false + } + total := startLen - len(*b) + return Fattr4{data: start[:total], off1: off1}, true +} + +func ReadFattr4(b []byte) (Fattr4, bool) { + return readFattr4(&b) +} + +func (m Fattr4) Attrmask() Bitmap4 { + return Bitmap4(m.data[0:m.off1]) +} + +func (m Fattr4) AttrVals() Attrlist4 { + return Attrlist4(m.data[m.off1:]) +} + +// Fattr4Writer writes a fattr4: +// +// attrmask + attr_vals +type Fattr4Writer struct { + buf []byte + off int + phase uint8 +} + +func StartFattr4(buf []byte) Fattr4Writer { + off := len(buf) + return Fattr4Writer{buf: buf, off: off} +} + +func (w *Fattr4Writer) StartAttrmask() Bitmap4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartBitmap4(w.buf) + w.buf = nil + return child +} + +func (w *Fattr4Writer) StartAttrVals() Attrlist4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartAttrlist4(w.buf) + w.buf = nil + return child +} + +func (w *Fattr4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *Fattr4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// ChangeInfo4 — fixed 20 bytes: +// atomic(4, beu32) + before(8, beu64) + after(8, beu64) +// ------------------------------------------------------- + +type ChangeInfo4 struct { + m *[changeInfo4Size]byte +} + +const changeInfo4Size = 20 + +func ReadChangeInfo4(b []byte) (ChangeInfo4, bool) { + if len(b) < changeInfo4Size { + return ChangeInfo4{}, false + } + return ChangeInfo4{m: (*[changeInfo4Size]byte)(b)}, true +} + +func StartChangeInfo4(buf []byte) ([]byte, ChangeInfo4) { + buf = append(buf, make([]byte, changeInfo4Size)...) + return buf, ChangeInfo4{m: (*[changeInfo4Size]byte)(buf[len(buf)-changeInfo4Size:])} +} + +func (m ChangeInfo4) Atomic() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m ChangeInfo4) Before() uint64 { + return binary.BigEndian.Uint64(m.m[4:12]) +} + +func (m ChangeInfo4) After() uint64 { + return binary.BigEndian.Uint64(m.m[12:20]) +} + +func (m ChangeInfo4) SetAtomic(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m ChangeInfo4) SetBefore(v uint64) { + binary.BigEndian.PutUint64(m.m[4:12], v) +} + +func (m ChangeInfo4) SetAfter(v uint64) { + binary.BigEndian.PutUint64(m.m[12:20], v) +} + +// ------------------------------------------------------- +// XdrOpaque — variable: data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type XdrOpaque []byte + +func readXdrOpaque(b *[]byte) (XdrOpaque, bool) { + if len(*b) < 4 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[0:4])) + padded := (n + 3) &^ 3 + total := 4 + padded + if len(*b) < total { + return nil, false + } + result := XdrOpaque((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadXdrOpaque(b []byte) (XdrOpaque, bool) { + if len(b) < 4 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[0:4])) + padded := (count + 3) &^ 3 + total := 4 + padded + if len(b) < total { + return nil, false + } + return XdrOpaque(b[:total]), true +} + +func (m XdrOpaque) DataLen() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m XdrOpaque) Data() []byte { + n := int(binary.BigEndian.Uint32(m[0:4])) + return m[4 : 4+n] +} + +// XdrOpaqueWriter writes a xdr_opaque: +// +// data_len + u8 data[data_len] + align(4) +type XdrOpaqueWriter struct { + buf []byte + off int +} + +func StartXdrOpaque(buf []byte) XdrOpaqueWriter { + off := len(buf) + return XdrOpaqueWriter{buf: buf, off: off} +} + +func (w XdrOpaqueWriter) SetData(data []byte) XdrOpaqueWriter { + n := len(data) + padded := (n + 3) &^ 3 + total := 4 + padded + w.buf = append(w.buf, make([]byte, total)...) + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], uint32(n)) + copy(w.buf[w.off+4:], data) + return w +} + +func (w XdrOpaqueWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// Clientaddr4 — variable: r_netid + r_addr +// ------------------------------------------------------- + +type Clientaddr4 struct { + data []byte + off1 int // byte offset within data where r_addr starts +} + +func readClientaddr4(b *[]byte) (Clientaddr4, bool) { + start := *b + startLen := len(start) + if _, ok := readXdrOpaque(b); !ok { + return Clientaddr4{}, false + } + off1 := startLen - len(*b) + if _, ok := readXdrOpaque(b); !ok { + return Clientaddr4{}, false + } + total := startLen - len(*b) + return Clientaddr4{data: start[:total], off1: off1}, true +} + +func ReadClientaddr4(b []byte) (Clientaddr4, bool) { + return readClientaddr4(&b) +} + +func (m Clientaddr4) RNetid() XdrOpaque { + return XdrOpaque(m.data[0:m.off1]) +} + +func (m Clientaddr4) RAddr() XdrOpaque { + return XdrOpaque(m.data[m.off1:]) +} + +// Clientaddr4Writer writes a clientaddr4: +// +// r_netid + r_addr +type Clientaddr4Writer struct { + buf []byte + off int + phase uint8 +} + +func StartClientaddr4(buf []byte) Clientaddr4Writer { + off := len(buf) + return Clientaddr4Writer{buf: buf, off: off} +} + +func (w *Clientaddr4Writer) StartRNetid() XdrOpaqueWriter { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartXdrOpaque(w.buf) + w.buf = nil + return child +} + +func (w *Clientaddr4Writer) StartRAddr() XdrOpaqueWriter { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartXdrOpaque(w.buf) + w.buf = nil + return child +} + +func (w *Clientaddr4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *Clientaddr4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CbClient4 — variable: cb_program(4) + cb_location +// ------------------------------------------------------- + +type CbClient4 []byte + +func readCbClient4(b *[]byte) (CbClient4, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readClientaddr4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return CbClient4(start[:total]), true +} + +func ReadCbClient4(b []byte) (CbClient4, bool) { + return readCbClient4(&b) +} + +func (m CbClient4) CbProgram() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m CbClient4) CbLocation() Clientaddr4 { + v, _ := ReadClientaddr4(m[4:]) + return v +} + +// CbClient4Writer writes a cb_client4: +// +// cb_program + cb_location +type CbClient4Writer struct { + buf []byte + header *[4]byte +} + +func StartCbClient4(buf []byte) CbClient4Writer { + buf = append(buf, make([]byte, 4)...) // cb_program(4) + return CbClient4Writer{buf: buf, header: (*[4]byte)(buf[len(buf)-4:])} +} + +func (w *CbClient4Writer) SetCbProgram(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *CbClient4Writer) StartCbLocation() Clientaddr4Writer { + child := StartClientaddr4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *CbClient4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *CbClient4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// Stateid4 — fixed 16 bytes: seqid(4, beu32) + other(u8[12]) +// ------------------------------------------------------- + +type Stateid4 struct { + m *[stateid4Size]byte +} + +const stateid4Size = 16 + +func readStateid4(b *[]byte) (Stateid4, bool) { + if len(*b) < stateid4Size { + return Stateid4{}, false + } + result := Stateid4{m: (*[stateid4Size]byte)(*b)} + *b = (*b)[stateid4Size:] + return result, true +} + +func ReadStateid4(b []byte) (Stateid4, bool) { + return readStateid4(&b) +} + +func StartStateid4(buf []byte) ([]byte, Stateid4) { + buf = append(buf, make([]byte, stateid4Size)...) + return buf, Stateid4{m: (*[stateid4Size]byte)(buf[len(buf)-stateid4Size:])} +} + +func (m Stateid4) Seqid() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m Stateid4) Other(i int) uint8 { + off := 4 + i*1 + return m.m[off : off+1][0] +} + +func (m Stateid4) SetSeqid(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m Stateid4) SetOther(i int, v uint8) { + off := 4 + i*1 + m.m[off : off+1][0] = v +} + +// ------------------------------------------------------- +// NfsClientId4 — variable: verifier(8) + id_len(4) + u8 id[id_len] + align(4) +// ------------------------------------------------------- + +type NfsClientId4 []byte + +func readNfsClientId4(b *[]byte) (NfsClientId4, bool) { + if len(*b) < 12 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[8:12])) + padded := (n + 3) &^ 3 + total := 12 + padded + if len(*b) < total { + return nil, false + } + result := NfsClientId4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadNfsClientId4(b []byte) (NfsClientId4, bool) { + if len(b) < 12 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[8:12])) + padded := (count + 3) &^ 3 + total := 12 + padded + if len(b) < total { + return nil, false + } + return NfsClientId4(b[:total]), true +} + +func (m NfsClientId4) Verifier() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m[0 : 0+verifier4Size])} +} + +func (m NfsClientId4) IdLen() uint32 { + return binary.BigEndian.Uint32(m[8:12]) +} + +func (m NfsClientId4) Id() []byte { + n := int(binary.BigEndian.Uint32(m[8:12])) + return m[12 : 12+n] +} + +// NfsClientId4Writer writes a nfs_client_id4: +// +// verifier + id_len + u8 id[id_len] + align(4) +type NfsClientId4Writer struct { + buf []byte + off int + idLen uint32 +} + +func StartNfsClientId4(buf []byte) NfsClientId4Writer { + off := len(buf) + buf = append(buf, make([]byte, 12)...) // verifier(8) + id_len(4) + return NfsClientId4Writer{buf: buf, off: off} +} + +func (w *NfsClientId4Writer) Verifier() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(w.buf[w.off:])} +} + +func (w NfsClientId4Writer) SetId(data []byte) NfsClientId4Writer { + n := len(data) + padded := (n + 3) &^ 3 + w.buf = append(w.buf, make([]byte, padded)...) + copy(w.buf[len(w.buf)-padded:], data) + w.idLen = uint32(n) + return w +} + +func (w NfsClientId4Writer) Finish() []byte { + binary.BigEndian.PutUint32((*[12]byte)(w.buf[w.off:])[8:12], w.idLen) + return w.buf +} + +// ------------------------------------------------------- +// OpenOwner4 — variable: +// clientid(8) + owner_len(4) + u8 owner[owner_len] + align(4) +// ------------------------------------------------------- + +type OpenOwner4 []byte + +func readOpenOwner4(b *[]byte) (OpenOwner4, bool) { + if len(*b) < 12 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[8:12])) + padded := (n + 3) &^ 3 + total := 12 + padded + if len(*b) < total { + return nil, false + } + result := OpenOwner4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadOpenOwner4(b []byte) (OpenOwner4, bool) { + if len(b) < 12 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[8:12])) + padded := (count + 3) &^ 3 + total := 12 + padded + if len(b) < total { + return nil, false + } + return OpenOwner4(b[:total]), true +} + +func (m OpenOwner4) Clientid() uint64 { + return binary.BigEndian.Uint64(m[0:8]) +} + +func (m OpenOwner4) OwnerLen() uint32 { + return binary.BigEndian.Uint32(m[8:12]) +} + +func (m OpenOwner4) Owner() []byte { + n := int(binary.BigEndian.Uint32(m[8:12])) + return m[12 : 12+n] +} + +// OpenOwner4Writer writes a open_owner4: +// +// clientid + owner_len + u8 owner[owner_len] + align(4) +type OpenOwner4Writer struct { + buf []byte + off int + ownerLen uint32 +} + +func StartOpenOwner4(buf []byte) OpenOwner4Writer { + off := len(buf) + buf = append(buf, make([]byte, 12)...) // clientid(8) + owner_len(4) + return OpenOwner4Writer{buf: buf, off: off} +} + +func (w OpenOwner4Writer) SetClientid(v uint64) OpenOwner4Writer { + binary.BigEndian.PutUint64((*[12]byte)(w.buf[w.off:])[0:8], v) + return w +} + +func (w OpenOwner4Writer) SetOwner(data []byte) OpenOwner4Writer { + n := len(data) + padded := (n + 3) &^ 3 + w.buf = append(w.buf, make([]byte, padded)...) + copy(w.buf[len(w.buf)-padded:], data) + w.ownerLen = uint32(n) + return w +} + +func (w OpenOwner4Writer) Finish() []byte { + binary.BigEndian.PutUint32((*[12]byte)(w.buf[w.off:])[8:12], w.ownerLen) + return w.buf +} + +// ------------------------------------------------------- +// LockOwner4 — variable: +// clientid(8) + owner_len(4) + u8 owner[owner_len] + align(4) +// ------------------------------------------------------- + +type LockOwner4 []byte + +func readLockOwner4(b *[]byte) (LockOwner4, bool) { + if len(*b) < 12 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[8:12])) + padded := (n + 3) &^ 3 + total := 12 + padded + if len(*b) < total { + return nil, false + } + result := LockOwner4((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadLockOwner4(b []byte) (LockOwner4, bool) { + if len(b) < 12 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[8:12])) + padded := (count + 3) &^ 3 + total := 12 + padded + if len(b) < total { + return nil, false + } + return LockOwner4(b[:total]), true +} + +func (m LockOwner4) Clientid() uint64 { + return binary.BigEndian.Uint64(m[0:8]) +} + +func (m LockOwner4) OwnerLen() uint32 { + return binary.BigEndian.Uint32(m[8:12]) +} + +func (m LockOwner4) Owner() []byte { + n := int(binary.BigEndian.Uint32(m[8:12])) + return m[12 : 12+n] +} + +// LockOwner4Writer writes a lock_owner4: +// +// clientid + owner_len + u8 owner[owner_len] + align(4) +type LockOwner4Writer struct { + buf []byte + off int + ownerLen uint32 +} + +func StartLockOwner4(buf []byte) LockOwner4Writer { + off := len(buf) + buf = append(buf, make([]byte, 12)...) // clientid(8) + owner_len(4) + return LockOwner4Writer{buf: buf, off: off} +} + +func (w LockOwner4Writer) SetClientid(v uint64) LockOwner4Writer { + binary.BigEndian.PutUint64((*[12]byte)(w.buf[w.off:])[0:8], v) + return w +} + +func (w LockOwner4Writer) SetOwner(data []byte) LockOwner4Writer { + n := len(data) + padded := (n + 3) &^ 3 + w.buf = append(w.buf, make([]byte, padded)...) + copy(w.buf[len(w.buf)-padded:], data) + w.ownerLen = uint32(n) + return w +} + +func (w LockOwner4Writer) Finish() []byte { + binary.BigEndian.PutUint32((*[12]byte)(w.buf[w.off:])[8:12], w.ownerLen) + return w.buf +} + +// ------------------------------------------------------- +// ACCESS4args — fixed 4 bytes: access(4, beu32) +// ------------------------------------------------------- + +type ACCESS4args struct { + m *[aCCESS4argsSize]byte +} + +const aCCESS4argsSize = 4 + +func readACCESS4args(b *[]byte) (ACCESS4args, bool) { + if len(*b) < aCCESS4argsSize { + return ACCESS4args{}, false + } + result := ACCESS4args{m: (*[aCCESS4argsSize]byte)(*b)} + *b = (*b)[aCCESS4argsSize:] + return result, true +} + +func ReadACCESS4args(b []byte) (ACCESS4args, bool) { + return readACCESS4args(&b) +} + +func StartACCESS4args(buf []byte) ([]byte, ACCESS4args) { + buf = append(buf, make([]byte, aCCESS4argsSize)...) + return buf, ACCESS4args{m: (*[aCCESS4argsSize]byte)(buf[len(buf)-aCCESS4argsSize:])} +} + +func (m ACCESS4args) Access() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m ACCESS4args) SetAccess(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// ACCESS4resok — fixed 8 bytes: supported(4, beu32) + access(4, beu32) +// ------------------------------------------------------- + +type ACCESS4resok struct { + m *[aCCESS4resokSize]byte +} + +const aCCESS4resokSize = 8 + +func readACCESS4resok(b *[]byte) (ACCESS4resok, bool) { + if len(*b) < aCCESS4resokSize { + return ACCESS4resok{}, false + } + result := ACCESS4resok{m: (*[aCCESS4resokSize]byte)(*b)} + *b = (*b)[aCCESS4resokSize:] + return result, true +} + +func ReadACCESS4resok(b []byte) (ACCESS4resok, bool) { + return readACCESS4resok(&b) +} + +func StartACCESS4resok(buf []byte) ([]byte, ACCESS4resok) { + buf = append(buf, make([]byte, aCCESS4resokSize)...) + return buf, ACCESS4resok{m: (*[aCCESS4resokSize]byte)(buf[len(buf)-aCCESS4resokSize:])} +} + +func (m ACCESS4resok) Supported() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m ACCESS4resok) Access() uint32 { + return binary.BigEndian.Uint32(m.m[4:8]) +} + +func (m ACCESS4resok) SetSupported(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m ACCESS4resok) SetAccess(v uint32) { + binary.BigEndian.PutUint32(m.m[4:8], v) +} + +// ------------------------------------------------------- +// ACCESS4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type ACCESS4res struct { + b []byte + disc uint32 +} + +func readACCESS4res(b *[]byte, nfsstat4 uint32) (ACCESS4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readACCESS4resok(b) + if !ok { + return ACCESS4res{}, false + } + return ACCESS4res{b: r.m[:], disc: nfsstat4}, true + default: + return ACCESS4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m ACCESS4res) AsACCESS4resok() ACCESS4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return ACCESS4resok{m: (*[aCCESS4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// CLOSE4args — fixed 20 bytes: seqid(4, beu32) + stateid4(16) +// ------------------------------------------------------- + +type CLOSE4args struct { + m *[cLOSE4argsSize]byte +} + +const cLOSE4argsSize = 20 + +func readCLOSE4args(b *[]byte) (CLOSE4args, bool) { + if len(*b) < cLOSE4argsSize { + return CLOSE4args{}, false + } + result := CLOSE4args{m: (*[cLOSE4argsSize]byte)(*b)} + *b = (*b)[cLOSE4argsSize:] + return result, true +} + +func ReadCLOSE4args(b []byte) (CLOSE4args, bool) { + return readCLOSE4args(&b) +} + +func StartCLOSE4args(buf []byte) ([]byte, CLOSE4args) { + buf = append(buf, make([]byte, cLOSE4argsSize)...) + return buf, CLOSE4args{m: (*[cLOSE4argsSize]byte)(buf[len(buf)-cLOSE4argsSize:])} +} + +func (m CLOSE4args) Seqid() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m CLOSE4args) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[4:20])} +} + +func (m CLOSE4args) SetSeqid(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// CLOSE4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type CLOSE4res struct { + b []byte + disc uint32 +} + +func readCLOSE4res(b *[]byte, nfsstat4 uint32) (CLOSE4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readStateid4(b) + if !ok { + return CLOSE4res{}, false + } + return CLOSE4res{b: r.m[:], disc: nfsstat4}, true + default: + return CLOSE4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m CLOSE4res) AsStateid4() Stateid4 { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return Stateid4{m: (*[stateid4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// COMMIT4args — fixed 12 bytes: offset(8, beu64) + count(4, beu32) +// ------------------------------------------------------- + +type COMMIT4args struct { + m *[cOMMIT4argsSize]byte +} + +const cOMMIT4argsSize = 12 + +func readCOMMIT4args(b *[]byte) (COMMIT4args, bool) { + if len(*b) < cOMMIT4argsSize { + return COMMIT4args{}, false + } + result := COMMIT4args{m: (*[cOMMIT4argsSize]byte)(*b)} + *b = (*b)[cOMMIT4argsSize:] + return result, true +} + +func ReadCOMMIT4args(b []byte) (COMMIT4args, bool) { + return readCOMMIT4args(&b) +} + +func StartCOMMIT4args(buf []byte) ([]byte, COMMIT4args) { + buf = append(buf, make([]byte, cOMMIT4argsSize)...) + return buf, COMMIT4args{m: (*[cOMMIT4argsSize]byte)(buf[len(buf)-cOMMIT4argsSize:])} +} + +func (m COMMIT4args) Offset() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m COMMIT4args) Count() uint32 { + return binary.BigEndian.Uint32(m.m[8:12]) +} + +func (m COMMIT4args) SetOffset(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +func (m COMMIT4args) SetCount(v uint32) { + binary.BigEndian.PutUint32(m.m[8:12], v) +} + +// ------------------------------------------------------- +// COMMIT4resok — fixed 8 bytes: verifier4(8) +// ------------------------------------------------------- + +type COMMIT4resok struct { + m *[cOMMIT4resokSize]byte +} + +const cOMMIT4resokSize = 8 + +func readCOMMIT4resok(b *[]byte) (COMMIT4resok, bool) { + if len(*b) < cOMMIT4resokSize { + return COMMIT4resok{}, false + } + result := COMMIT4resok{m: (*[cOMMIT4resokSize]byte)(*b)} + *b = (*b)[cOMMIT4resokSize:] + return result, true +} + +func ReadCOMMIT4resok(b []byte) (COMMIT4resok, bool) { + return readCOMMIT4resok(&b) +} + +func StartCOMMIT4resok(buf []byte) ([]byte, COMMIT4resok) { + buf = append(buf, make([]byte, cOMMIT4resokSize)...) + return buf, COMMIT4resok{m: (*[cOMMIT4resokSize]byte)(buf[len(buf)-cOMMIT4resokSize:])} +} + +func (m COMMIT4resok) Writeverf() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m.m[0:8])} +} + +// ------------------------------------------------------- +// COMMIT4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type COMMIT4res struct { + b []byte + disc uint32 +} + +func readCOMMIT4res(b *[]byte, nfsstat4 uint32) (COMMIT4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readCOMMIT4resok(b) + if !ok { + return COMMIT4res{}, false + } + return COMMIT4res{b: r.m[:], disc: nfsstat4}, true + default: + return COMMIT4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m COMMIT4res) AsCOMMIT4resok() COMMIT4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return COMMIT4resok{m: (*[cOMMIT4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// Createtype4 — union on nfs_ftype4 (external discriminant) +// ------------------------------------------------------- + +type Createtype4 struct { + b []byte + disc uint32 +} + +func readCreatetype4(b *[]byte, nfsFtype4 uint32) (Createtype4, bool) { + switch nfsFtype4 { + case NF4LNK: + r, ok := readLinktext4(b) + if !ok { + return Createtype4{}, false + } + return Createtype4{b: []byte(r), disc: nfsFtype4}, true + case NF4BLK: + r, ok := readSpecdata4(b) + if !ok { + return Createtype4{}, false + } + return Createtype4{b: r.m[:], disc: nfsFtype4}, true + case NF4CHR: + r, ok := readSpecdata4(b) + if !ok { + return Createtype4{}, false + } + return Createtype4{b: r.m[:], disc: nfsFtype4}, true + case NF4SOCK: + return Createtype4{b: (*b)[:0], disc: nfsFtype4}, true + case NF4FIFO: + return Createtype4{b: (*b)[:0], disc: nfsFtype4}, true + case NF4DIR: + return Createtype4{b: (*b)[:0], disc: nfsFtype4}, true + default: + return Createtype4{b: (*b)[:0], disc: nfsFtype4}, true + } +} + +func (m Createtype4) AsLinktext4() Linktext4 { + if m.disc != NF4LNK { + panic("wrong union discriminant") + } + return Linktext4(m.b) +} + +func (m Createtype4) AsNf4blk() Specdata4 { + if m.disc != NF4BLK { + panic("wrong union discriminant") + } + return Specdata4{m: (*[specdata4Size]byte)(m.b)} +} + +func (m Createtype4) AsNf4chr() Specdata4 { + if m.disc != NF4CHR { + panic("wrong union discriminant") + } + return Specdata4{m: (*[specdata4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// CREATE4args — variable: +// objtype_type(4) + createtype4 objtype + objname + createattrs +// ------------------------------------------------------- + +type CREATE4args struct { + data []byte + off1 int // byte offset within data where objname starts + off2 int // byte offset within data where createattrs starts +} + +func readCREATE4args(b *[]byte) (CREATE4args, bool) { + if len(*b) < 4 { + return CREATE4args{}, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readCreatetype4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return CREATE4args{}, false + } + off1 := startLen - len(*b) + if _, ok := readComponent4(b); !ok { + return CREATE4args{}, false + } + off2 := startLen - len(*b) + if _, ok := readFattr4(b); !ok { + return CREATE4args{}, false + } + total := startLen - len(*b) + return CREATE4args{data: start[:total], off1: off1, off2: off2}, true +} + +func ReadCREATE4args(b []byte) (CREATE4args, bool) { + return readCREATE4args(&b) +} + +func (m CREATE4args) ObjtypeType() uint32 { + return binary.BigEndian.Uint32(m.data[0:4]) +} + +func (m CREATE4args) Objtype() Createtype4 { + return Createtype4{b: m.data[4:m.off1], disc: binary.BigEndian.Uint32(m.data[0:4])} +} + +func (m CREATE4args) Objname() Component4 { + return Component4(m.data[m.off1:m.off2]) +} + +func (m CREATE4args) Createattrs() Fattr4 { + v, _ := ReadFattr4(m.data[m.off2:]) + return v +} + +// CREATE4argsWriter writes a CREATE4args: +// +// objtype_type + objtype(objtype_type) + objname + createattrs +type CREATE4argsWriter struct { + buf []byte + header *[4]byte + phase uint8 +} + +func StartCREATE4args(buf []byte) CREATE4argsWriter { + buf = append(buf, make([]byte, 4)...) // objtype_type(4) + return CREATE4argsWriter{buf: buf, header: (*[4]byte)(buf[len(buf)-4:])} +} + +func (w *CREATE4argsWriter) SetObjtypeType(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *CREATE4argsWriter) SetObjtype_Nf4lnk() Linktext4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + binary.BigEndian.PutUint32(w.header[0:4], NF4LNK) + child := StartLinktext4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *CREATE4argsWriter) SetObjtype_Nf4blk() Specdata4 { + if w.phase > 0 { + panic("writer fields called out of order") + } + binary.BigEndian.PutUint32(w.header[0:4], NF4BLK) + w.buf = append(w.buf, make([]byte, specdata4Size)...) + return Specdata4{m: (*[specdata4Size]byte)(w.buf[len(w.buf)-specdata4Size:])} +} + +func (w *CREATE4argsWriter) SetObjtype_Nf4chr() Specdata4 { + if w.phase > 0 { + panic("writer fields called out of order") + } + binary.BigEndian.PutUint32(w.header[0:4], NF4CHR) + w.buf = append(w.buf, make([]byte, specdata4Size)...) + return Specdata4{m: (*[specdata4Size]byte)(w.buf[len(w.buf)-specdata4Size:])} +} + +func (w *CREATE4argsWriter) SetObjtype_Nf4sock() { + binary.BigEndian.PutUint32(w.header[0:4], NF4SOCK) +} + +func (w *CREATE4argsWriter) SetObjtype_Nf4fifo() { + binary.BigEndian.PutUint32(w.header[0:4], NF4FIFO) +} + +func (w *CREATE4argsWriter) SetObjtype_Nf4dir() { + binary.BigEndian.PutUint32(w.header[0:4], NF4DIR) +} + +func (w *CREATE4argsWriter) SetObjtype_Default(nfsFtype4 uint32) { + binary.BigEndian.PutUint32(w.header[0:4], nfsFtype4) +} + +func (w *CREATE4argsWriter) StartObjname() Component4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartComponent4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *CREATE4argsWriter) StartCreateattrs() Fattr4Writer { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + child := StartFattr4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *CREATE4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CREATE4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CREATE4resok — variable: cinfo(20) + attrset +// ------------------------------------------------------- + +type CREATE4resok []byte + +func readCREATE4resok(b *[]byte) (CREATE4resok, bool) { + if len(*b) < 20 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[20:] + if _, ok := readBitmap4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return CREATE4resok(start[:total]), true +} + +func ReadCREATE4resok(b []byte) (CREATE4resok, bool) { + return readCREATE4resok(&b) +} + +func (m CREATE4resok) Cinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(m[0 : 0+changeInfo4Size])} +} + +func (m CREATE4resok) Attrset() Bitmap4 { + return Bitmap4(m[20:]) +} + +// CREATE4resokWriter writes a CREATE4resok: +// +// cinfo + attrset +type CREATE4resokWriter struct { + buf []byte + header *[20]byte +} + +func StartCREATE4resok(buf []byte) CREATE4resokWriter { + buf = append(buf, make([]byte, 20)...) // cinfo(20) + return CREATE4resokWriter{buf: buf, header: (*[20]byte)(buf[len(buf)-20:])} +} + +func (w *CREATE4resokWriter) Cinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(w.header[0:])} +} + +func (w *CREATE4resokWriter) StartAttrset() Bitmap4Writer { + child := StartBitmap4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *CREATE4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CREATE4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CREATE4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type CREATE4res struct { + b []byte + disc uint32 +} + +func readCREATE4res(b *[]byte, nfsstat4 uint32) (CREATE4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readCREATE4resok(b) + if !ok { + return CREATE4res{}, false + } + return CREATE4res{b: []byte(r), disc: nfsstat4}, true + default: + return CREATE4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m CREATE4res) AsCREATE4resok() CREATE4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return CREATE4resok(m.b) +} + +// ------------------------------------------------------- +// DELEGPURGE4args — fixed 8 bytes: clientid(8, beu64) +// ------------------------------------------------------- + +type DELEGPURGE4args struct { + m *[dELEGPURGE4argsSize]byte +} + +const dELEGPURGE4argsSize = 8 + +func readDELEGPURGE4args(b *[]byte) (DELEGPURGE4args, bool) { + if len(*b) < dELEGPURGE4argsSize { + return DELEGPURGE4args{}, false + } + result := DELEGPURGE4args{m: (*[dELEGPURGE4argsSize]byte)(*b)} + *b = (*b)[dELEGPURGE4argsSize:] + return result, true +} + +func ReadDELEGPURGE4args(b []byte) (DELEGPURGE4args, bool) { + return readDELEGPURGE4args(&b) +} + +func StartDELEGPURGE4args(buf []byte) ([]byte, DELEGPURGE4args) { + buf = append(buf, make([]byte, dELEGPURGE4argsSize)...) + return buf, DELEGPURGE4args{m: (*[dELEGPURGE4argsSize]byte)(buf[len(buf)-dELEGPURGE4argsSize:])} +} + +func (m DELEGPURGE4args) Clientid() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m DELEGPURGE4args) SetClientid(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +// ------------------------------------------------------- +// DELEGPURGE4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type DELEGPURGE4res struct { + m *[dELEGPURGE4resSize]byte +} + +const dELEGPURGE4resSize = 4 + +func readDELEGPURGE4res(b *[]byte) (DELEGPURGE4res, bool) { + if len(*b) < dELEGPURGE4resSize { + return DELEGPURGE4res{}, false + } + result := DELEGPURGE4res{m: (*[dELEGPURGE4resSize]byte)(*b)} + *b = (*b)[dELEGPURGE4resSize:] + return result, true +} + +func ReadDELEGPURGE4res(b []byte) (DELEGPURGE4res, bool) { + return readDELEGPURGE4res(&b) +} + +func StartDELEGPURGE4res(buf []byte) ([]byte, DELEGPURGE4res) { + buf = append(buf, make([]byte, dELEGPURGE4resSize)...) + return buf, DELEGPURGE4res{m: (*[dELEGPURGE4resSize]byte)(buf[len(buf)-dELEGPURGE4resSize:])} +} + +func (m DELEGPURGE4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m DELEGPURGE4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// DELEGRETURN4args — fixed 16 bytes: stateid4(16) +// ------------------------------------------------------- + +type DELEGRETURN4args struct { + m *[dELEGRETURN4argsSize]byte +} + +const dELEGRETURN4argsSize = 16 + +func readDELEGRETURN4args(b *[]byte) (DELEGRETURN4args, bool) { + if len(*b) < dELEGRETURN4argsSize { + return DELEGRETURN4args{}, false + } + result := DELEGRETURN4args{m: (*[dELEGRETURN4argsSize]byte)(*b)} + *b = (*b)[dELEGRETURN4argsSize:] + return result, true +} + +func ReadDELEGRETURN4args(b []byte) (DELEGRETURN4args, bool) { + return readDELEGRETURN4args(&b) +} + +func StartDELEGRETURN4args(buf []byte) ([]byte, DELEGRETURN4args) { + buf = append(buf, make([]byte, dELEGRETURN4argsSize)...) + return buf, DELEGRETURN4args{m: (*[dELEGRETURN4argsSize]byte)(buf[len(buf)-dELEGRETURN4argsSize:])} +} + +func (m DELEGRETURN4args) DelegStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +// ------------------------------------------------------- +// DELEGRETURN4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type DELEGRETURN4res struct { + m *[dELEGRETURN4resSize]byte +} + +const dELEGRETURN4resSize = 4 + +func readDELEGRETURN4res(b *[]byte) (DELEGRETURN4res, bool) { + if len(*b) < dELEGRETURN4resSize { + return DELEGRETURN4res{}, false + } + result := DELEGRETURN4res{m: (*[dELEGRETURN4resSize]byte)(*b)} + *b = (*b)[dELEGRETURN4resSize:] + return result, true +} + +func ReadDELEGRETURN4res(b []byte) (DELEGRETURN4res, bool) { + return readDELEGRETURN4res(&b) +} + +func StartDELEGRETURN4res(buf []byte) ([]byte, DELEGRETURN4res) { + buf = append(buf, make([]byte, dELEGRETURN4resSize)...) + return buf, DELEGRETURN4res{m: (*[dELEGRETURN4resSize]byte)(buf[len(buf)-dELEGRETURN4resSize:])} +} + +func (m DELEGRETURN4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m DELEGRETURN4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// GETATTR4args — variable: attr_request +// ------------------------------------------------------- + +type GETATTR4args []byte + +func readGETATTR4args(b *[]byte) (GETATTR4args, bool) { + start := *b + startLen := len(start) + if _, ok := readBitmap4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return GETATTR4args(start[:total]), true +} + +func ReadGETATTR4args(b []byte) (GETATTR4args, bool) { + return readGETATTR4args(&b) +} + +func (m GETATTR4args) AttrRequest() Bitmap4 { + return Bitmap4(m[0:]) +} + +// GETATTR4argsWriter writes a GETATTR4args: +// +// attr_request +type GETATTR4argsWriter struct { + buf []byte + off int +} + +func StartGETATTR4args(buf []byte) GETATTR4argsWriter { + off := len(buf) + return GETATTR4argsWriter{buf: buf, off: off} +} + +func (w *GETATTR4argsWriter) StartAttrRequest() Bitmap4Writer { + child := StartBitmap4(w.buf) + w.buf = nil + return child +} + +func (w *GETATTR4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *GETATTR4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// GETATTR4resok — variable: obj_attributes +// ------------------------------------------------------- + +type GETATTR4resok []byte + +func readGETATTR4resok(b *[]byte) (GETATTR4resok, bool) { + start := *b + startLen := len(start) + if _, ok := readFattr4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return GETATTR4resok(start[:total]), true +} + +func ReadGETATTR4resok(b []byte) (GETATTR4resok, bool) { + return readGETATTR4resok(&b) +} + +func (m GETATTR4resok) ObjAttributes() Fattr4 { + v, _ := ReadFattr4(m[0:]) + return v +} + +// GETATTR4resokWriter writes a GETATTR4resok: +// +// obj_attributes +type GETATTR4resokWriter struct { + buf []byte + off int +} + +func StartGETATTR4resok(buf []byte) GETATTR4resokWriter { + off := len(buf) + return GETATTR4resokWriter{buf: buf, off: off} +} + +func (w *GETATTR4resokWriter) StartObjAttributes() Fattr4Writer { + child := StartFattr4(w.buf) + w.buf = nil + return child +} + +func (w *GETATTR4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *GETATTR4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// GETATTR4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type GETATTR4res struct { + b []byte + disc uint32 +} + +func readGETATTR4res(b *[]byte, nfsstat4 uint32) (GETATTR4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readGETATTR4resok(b) + if !ok { + return GETATTR4res{}, false + } + return GETATTR4res{b: []byte(r), disc: nfsstat4}, true + default: + return GETATTR4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m GETATTR4res) AsGETATTR4resok() GETATTR4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return GETATTR4resok(m.b) +} + +// ------------------------------------------------------- +// GETFH4resok — variable: object +// ------------------------------------------------------- + +type GETFH4resok []byte + +func readGETFH4resok(b *[]byte) (GETFH4resok, bool) { + start := *b + startLen := len(start) + if _, ok := readNfsFh4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return GETFH4resok(start[:total]), true +} + +func ReadGETFH4resok(b []byte) (GETFH4resok, bool) { + return readGETFH4resok(&b) +} + +func (m GETFH4resok) Object() NfsFh4 { + return NfsFh4(m[0:]) +} + +// GETFH4resokWriter writes a GETFH4resok: +// +// object +type GETFH4resokWriter struct { + buf []byte + off int +} + +func StartGETFH4resok(buf []byte) GETFH4resokWriter { + off := len(buf) + return GETFH4resokWriter{buf: buf, off: off} +} + +func (w *GETFH4resokWriter) StartObject() NfsFh4Writer { + child := StartNfsFh4(w.buf) + w.buf = nil + return child +} + +func (w *GETFH4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *GETFH4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// GETFH4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type GETFH4res struct { + b []byte + disc uint32 +} + +func readGETFH4res(b *[]byte, nfsstat4 uint32) (GETFH4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readGETFH4resok(b) + if !ok { + return GETFH4res{}, false + } + return GETFH4res{b: []byte(r), disc: nfsstat4}, true + default: + return GETFH4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m GETFH4res) AsGETFH4resok() GETFH4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return GETFH4resok(m.b) +} + +// ------------------------------------------------------- +// LINK4args — variable: newname +// ------------------------------------------------------- + +type LINK4args []byte + +func readLINK4args(b *[]byte) (LINK4args, bool) { + start := *b + startLen := len(start) + if _, ok := readComponent4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return LINK4args(start[:total]), true +} + +func ReadLINK4args(b []byte) (LINK4args, bool) { + return readLINK4args(&b) +} + +func (m LINK4args) Newname() Component4 { + return Component4(m[0:]) +} + +// LINK4argsWriter writes a LINK4args: +// +// newname +type LINK4argsWriter struct { + buf []byte + off int +} + +func StartLINK4args(buf []byte) LINK4argsWriter { + off := len(buf) + return LINK4argsWriter{buf: buf, off: off} +} + +func (w *LINK4argsWriter) StartNewname() Component4Writer { + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *LINK4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LINK4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LINK4resok — fixed 20 bytes: change_info4(20) +// ------------------------------------------------------- + +type LINK4resok struct { + m *[lINK4resokSize]byte +} + +const lINK4resokSize = 20 + +func readLINK4resok(b *[]byte) (LINK4resok, bool) { + if len(*b) < lINK4resokSize { + return LINK4resok{}, false + } + result := LINK4resok{m: (*[lINK4resokSize]byte)(*b)} + *b = (*b)[lINK4resokSize:] + return result, true +} + +func ReadLINK4resok(b []byte) (LINK4resok, bool) { + return readLINK4resok(&b) +} + +func StartLINK4resok(buf []byte) ([]byte, LINK4resok) { + buf = append(buf, make([]byte, lINK4resokSize)...) + return buf, LINK4resok{m: (*[lINK4resokSize]byte)(buf[len(buf)-lINK4resokSize:])} +} + +func (m LINK4resok) Cinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(m.m[0:20])} +} + +// ------------------------------------------------------- +// LINK4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type LINK4res struct { + b []byte + disc uint32 +} + +func readLINK4res(b *[]byte, nfsstat4 uint32) (LINK4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readLINK4resok(b) + if !ok { + return LINK4res{}, false + } + return LINK4res{b: r.m[:], disc: nfsstat4}, true + default: + return LINK4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m LINK4res) AsLINK4resok() LINK4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return LINK4resok{m: (*[lINK4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// OpenToLockOwner4 — variable: +// open_seqid(4) + open_stateid(16) + lock_seqid(4) + lock_owner +// ------------------------------------------------------- + +type OpenToLockOwner4 []byte + +func readOpenToLockOwner4(b *[]byte) (OpenToLockOwner4, bool) { + if len(*b) < 24 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[24:] + if _, ok := readLockOwner4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return OpenToLockOwner4(start[:total]), true +} + +func ReadOpenToLockOwner4(b []byte) (OpenToLockOwner4, bool) { + return readOpenToLockOwner4(&b) +} + +func (m OpenToLockOwner4) OpenSeqid() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m OpenToLockOwner4) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m[4 : 4+stateid4Size])} +} + +func (m OpenToLockOwner4) LockSeqid() uint32 { + return binary.BigEndian.Uint32(m[20:24]) +} + +func (m OpenToLockOwner4) LockOwner() LockOwner4 { + return LockOwner4(m[24:]) +} + +// OpenToLockOwner4Writer writes a open_to_lock_owner4: +// +// open_seqid + open_stateid + lock_seqid + lock_owner +type OpenToLockOwner4Writer struct { + buf []byte + header *[24]byte +} + +func StartOpenToLockOwner4(buf []byte) OpenToLockOwner4Writer { + buf = append(buf, make([]byte, 24)...) // open_seqid(4) + open_stateid(16) + lock_seqid(4) + return OpenToLockOwner4Writer{buf: buf, header: (*[24]byte)(buf[len(buf)-24:])} +} + +func (w *OpenToLockOwner4Writer) SetOpenSeqid(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *OpenToLockOwner4Writer) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[4:])} +} + +func (w *OpenToLockOwner4Writer) SetLockSeqid(v uint32) { + binary.BigEndian.PutUint32(w.header[20:24], v) +} + +func (w *OpenToLockOwner4Writer) StartLockOwner() LockOwner4Writer { + child := StartLockOwner4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *OpenToLockOwner4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *OpenToLockOwner4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// ExistLockOwner4 — fixed 20 bytes: stateid4(16) + lock_seqid(4, beu32) +// ------------------------------------------------------- + +type ExistLockOwner4 struct { + m *[existLockOwner4Size]byte +} + +const existLockOwner4Size = 20 + +func readExistLockOwner4(b *[]byte) (ExistLockOwner4, bool) { + if len(*b) < existLockOwner4Size { + return ExistLockOwner4{}, false + } + result := ExistLockOwner4{m: (*[existLockOwner4Size]byte)(*b)} + *b = (*b)[existLockOwner4Size:] + return result, true +} + +func ReadExistLockOwner4(b []byte) (ExistLockOwner4, bool) { + return readExistLockOwner4(&b) +} + +func StartExistLockOwner4(buf []byte) ([]byte, ExistLockOwner4) { + buf = append(buf, make([]byte, existLockOwner4Size)...) + return buf, ExistLockOwner4{m: (*[existLockOwner4Size]byte)(buf[len(buf)-existLockOwner4Size:])} +} + +func (m ExistLockOwner4) LockStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +func (m ExistLockOwner4) LockSeqid() uint32 { + return binary.BigEndian.Uint32(m.m[16:20]) +} + +func (m ExistLockOwner4) SetLockSeqid(v uint32) { + binary.BigEndian.PutUint32(m.m[16:20], v) +} + +// ------------------------------------------------------- +// Locker4 — union on xdr_bool (external discriminant) +// ------------------------------------------------------- + +type Locker4 struct { + b []byte + disc uint32 +} + +func readLocker4(b *[]byte, xdrBool uint32) (Locker4, bool) { + switch xdrBool { + case TRUE: + r, ok := readOpenToLockOwner4(b) + if !ok { + return Locker4{}, false + } + return Locker4{b: []byte(r), disc: xdrBool}, true + case FALSE: + r, ok := readExistLockOwner4(b) + if !ok { + return Locker4{}, false + } + return Locker4{b: r.m[:], disc: xdrBool}, true + default: + return Locker4{}, false + } +} + +func (m Locker4) AsOpenToLockOwner4() OpenToLockOwner4 { + if m.disc != TRUE { + panic("wrong union discriminant") + } + return OpenToLockOwner4(m.b) +} + +func (m Locker4) AsExistLockOwner4() ExistLockOwner4 { + if m.disc != FALSE { + panic("wrong union discriminant") + } + return ExistLockOwner4{m: (*[existLockOwner4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// LOCK4args — variable: +// locktype(4) + reclaim(4) + offset(8) + length(8) + locker_type(4) + locker4 locker +// ------------------------------------------------------- + +type LOCK4args []byte + +func readLOCK4args(b *[]byte) (LOCK4args, bool) { + if len(*b) < 28 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[28:] + if _, ok := readLocker4(b, binary.BigEndian.Uint32(start[24:28])); !ok { + return nil, false + } + total := startLen - len(*b) + return LOCK4args(start[:total]), true +} + +func ReadLOCK4args(b []byte) (LOCK4args, bool) { + return readLOCK4args(&b) +} + +func (m LOCK4args) Locktype() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m LOCK4args) Reclaim() uint32 { + return binary.BigEndian.Uint32(m[4:8]) +} + +func (m LOCK4args) Offset() uint64 { + return binary.BigEndian.Uint64(m[8:16]) +} + +func (m LOCK4args) Length() uint64 { + return binary.BigEndian.Uint64(m[16:24]) +} + +func (m LOCK4args) LockerType() uint32 { + return binary.BigEndian.Uint32(m[24:28]) +} + +func (m LOCK4args) Locker() Locker4 { + return Locker4{b: m[28:], disc: binary.BigEndian.Uint32(m[24:28])} +} + +// LOCK4argsWriter writes a LOCK4args: +// +// locktype + reclaim + offset + length + locker_type + locker(locker_type) +type LOCK4argsWriter struct { + buf []byte + header *[28]byte +} + +func StartLOCK4args(buf []byte) LOCK4argsWriter { + buf = append(buf, make([]byte, 28)...) // locktype(4) + reclaim(4) + offset(8) + length(8) + locker_type(4) + return LOCK4argsWriter{buf: buf, header: (*[28]byte)(buf[len(buf)-28:])} +} + +func (w *LOCK4argsWriter) SetLocktype(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *LOCK4argsWriter) SetReclaim(v uint32) { + binary.BigEndian.PutUint32(w.header[4:8], v) +} + +func (w *LOCK4argsWriter) SetOffset(v uint64) { + binary.BigEndian.PutUint64(w.header[8:16], v) +} + +func (w *LOCK4argsWriter) SetLength(v uint64) { + binary.BigEndian.PutUint64(w.header[16:24], v) +} + +func (w *LOCK4argsWriter) SetLockerType(v uint32) { + binary.BigEndian.PutUint32(w.header[24:28], v) +} + +func (w *LOCK4argsWriter) SetLocker_True() OpenToLockOwner4Writer { + binary.BigEndian.PutUint32(w.header[24:28], TRUE) + w.buf = append(w.buf, make([]byte, 24)...) + buf := w.buf + w.buf = nil + return OpenToLockOwner4Writer{buf: buf, header: (*[24]byte)(buf[len(buf)-24:])} +} + +func (w *LOCK4argsWriter) SetLocker_False() ExistLockOwner4 { + binary.BigEndian.PutUint32(w.header[24:28], FALSE) + w.buf = append(w.buf, make([]byte, existLockOwner4Size)...) + return ExistLockOwner4{m: (*[existLockOwner4Size]byte)(w.buf[len(w.buf)-existLockOwner4Size:])} +} + +func (w *LOCK4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LOCK4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LOCK4denied — variable: offset(8) + length(8) + locktype(4) + owner +// ------------------------------------------------------- + +type LOCK4denied []byte + +func readLOCK4denied(b *[]byte) (LOCK4denied, bool) { + if len(*b) < 20 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[20:] + if _, ok := readLockOwner4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return LOCK4denied(start[:total]), true +} + +func ReadLOCK4denied(b []byte) (LOCK4denied, bool) { + return readLOCK4denied(&b) +} + +func (m LOCK4denied) Offset() uint64 { + return binary.BigEndian.Uint64(m[0:8]) +} + +func (m LOCK4denied) Length() uint64 { + return binary.BigEndian.Uint64(m[8:16]) +} + +func (m LOCK4denied) Locktype() uint32 { + return binary.BigEndian.Uint32(m[16:20]) +} + +func (m LOCK4denied) Owner() LockOwner4 { + return LockOwner4(m[20:]) +} + +// LOCK4deniedWriter writes a LOCK4denied: +// +// offset + length + locktype + owner +type LOCK4deniedWriter struct { + buf []byte + header *[20]byte +} + +func StartLOCK4denied(buf []byte) LOCK4deniedWriter { + buf = append(buf, make([]byte, 20)...) // offset(8) + length(8) + locktype(4) + return LOCK4deniedWriter{buf: buf, header: (*[20]byte)(buf[len(buf)-20:])} +} + +func (w *LOCK4deniedWriter) SetOffset(v uint64) { + binary.BigEndian.PutUint64(w.header[0:8], v) +} + +func (w *LOCK4deniedWriter) SetLength(v uint64) { + binary.BigEndian.PutUint64(w.header[8:16], v) +} + +func (w *LOCK4deniedWriter) SetLocktype(v uint32) { + binary.BigEndian.PutUint32(w.header[16:20], v) +} + +func (w *LOCK4deniedWriter) StartOwner() LockOwner4Writer { + child := StartLockOwner4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *LOCK4deniedWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LOCK4deniedWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LOCK4resok — fixed 16 bytes: stateid4(16) +// ------------------------------------------------------- + +type LOCK4resok struct { + m *[lOCK4resokSize]byte +} + +const lOCK4resokSize = 16 + +func readLOCK4resok(b *[]byte) (LOCK4resok, bool) { + if len(*b) < lOCK4resokSize { + return LOCK4resok{}, false + } + result := LOCK4resok{m: (*[lOCK4resokSize]byte)(*b)} + *b = (*b)[lOCK4resokSize:] + return result, true +} + +func ReadLOCK4resok(b []byte) (LOCK4resok, bool) { + return readLOCK4resok(&b) +} + +func StartLOCK4resok(buf []byte) ([]byte, LOCK4resok) { + buf = append(buf, make([]byte, lOCK4resokSize)...) + return buf, LOCK4resok{m: (*[lOCK4resokSize]byte)(buf[len(buf)-lOCK4resokSize:])} +} + +func (m LOCK4resok) LockStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +// ------------------------------------------------------- +// LOCK4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type LOCK4res struct { + b []byte + disc uint32 +} + +func readLOCK4res(b *[]byte, nfsstat4 uint32) (LOCK4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readLOCK4resok(b) + if !ok { + return LOCK4res{}, false + } + return LOCK4res{b: r.m[:], disc: nfsstat4}, true + case NFS4ERR_DENIED: + r, ok := readLOCK4denied(b) + if !ok { + return LOCK4res{}, false + } + return LOCK4res{b: []byte(r), disc: nfsstat4}, true + default: + return LOCK4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m LOCK4res) AsLOCK4resok() LOCK4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return LOCK4resok{m: (*[lOCK4resokSize]byte)(m.b)} +} + +func (m LOCK4res) AsLOCK4denied() LOCK4denied { + if m.disc != NFS4ERR_DENIED { + panic("wrong union discriminant") + } + return LOCK4denied(m.b) +} + +// ------------------------------------------------------- +// LOCKT4args — variable: locktype(4) + offset(8) + length(8) + owner +// ------------------------------------------------------- + +type LOCKT4args []byte + +func readLOCKT4args(b *[]byte) (LOCKT4args, bool) { + if len(*b) < 20 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[20:] + if _, ok := readLockOwner4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return LOCKT4args(start[:total]), true +} + +func ReadLOCKT4args(b []byte) (LOCKT4args, bool) { + return readLOCKT4args(&b) +} + +func (m LOCKT4args) Locktype() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m LOCKT4args) Offset() uint64 { + return binary.BigEndian.Uint64(m[4:12]) +} + +func (m LOCKT4args) Length() uint64 { + return binary.BigEndian.Uint64(m[12:20]) +} + +func (m LOCKT4args) Owner() LockOwner4 { + return LockOwner4(m[20:]) +} + +// LOCKT4argsWriter writes a LOCKT4args: +// +// locktype + offset + length + owner +type LOCKT4argsWriter struct { + buf []byte + header *[20]byte +} + +func StartLOCKT4args(buf []byte) LOCKT4argsWriter { + buf = append(buf, make([]byte, 20)...) // locktype(4) + offset(8) + length(8) + return LOCKT4argsWriter{buf: buf, header: (*[20]byte)(buf[len(buf)-20:])} +} + +func (w *LOCKT4argsWriter) SetLocktype(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *LOCKT4argsWriter) SetOffset(v uint64) { + binary.BigEndian.PutUint64(w.header[4:12], v) +} + +func (w *LOCKT4argsWriter) SetLength(v uint64) { + binary.BigEndian.PutUint64(w.header[12:20], v) +} + +func (w *LOCKT4argsWriter) StartOwner() LockOwner4Writer { + child := StartLockOwner4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *LOCKT4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LOCKT4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LOCKT4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type LOCKT4res struct { + b []byte + disc uint32 +} + +func readLOCKT4res(b *[]byte, nfsstat4 uint32) (LOCKT4res, bool) { + switch nfsstat4 { + case NFS4ERR_DENIED: + r, ok := readLOCK4denied(b) + if !ok { + return LOCKT4res{}, false + } + return LOCKT4res{b: []byte(r), disc: nfsstat4}, true + case NFS4_OK: + return LOCKT4res{b: (*b)[:0], disc: nfsstat4}, true + default: + return LOCKT4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m LOCKT4res) AsLOCK4denied() LOCK4denied { + if m.disc != NFS4ERR_DENIED { + panic("wrong union discriminant") + } + return LOCK4denied(m.b) +} + +// ------------------------------------------------------- +// LOCKU4args — fixed 40 bytes: +// locktype(4, beu32) + seqid(4, beu32) + stateid4(16) + offset(8, beu64) + length(8, beu64) +// ------------------------------------------------------- + +type LOCKU4args struct { + m *[lOCKU4argsSize]byte +} + +const lOCKU4argsSize = 40 + +func readLOCKU4args(b *[]byte) (LOCKU4args, bool) { + if len(*b) < lOCKU4argsSize { + return LOCKU4args{}, false + } + result := LOCKU4args{m: (*[lOCKU4argsSize]byte)(*b)} + *b = (*b)[lOCKU4argsSize:] + return result, true +} + +func ReadLOCKU4args(b []byte) (LOCKU4args, bool) { + return readLOCKU4args(&b) +} + +func StartLOCKU4args(buf []byte) ([]byte, LOCKU4args) { + buf = append(buf, make([]byte, lOCKU4argsSize)...) + return buf, LOCKU4args{m: (*[lOCKU4argsSize]byte)(buf[len(buf)-lOCKU4argsSize:])} +} + +func (m LOCKU4args) Locktype() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m LOCKU4args) Seqid() uint32 { + return binary.BigEndian.Uint32(m.m[4:8]) +} + +func (m LOCKU4args) LockStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[8:24])} +} + +func (m LOCKU4args) Offset() uint64 { + return binary.BigEndian.Uint64(m.m[24:32]) +} + +func (m LOCKU4args) Length() uint64 { + return binary.BigEndian.Uint64(m.m[32:40]) +} + +func (m LOCKU4args) SetLocktype(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m LOCKU4args) SetSeqid(v uint32) { + binary.BigEndian.PutUint32(m.m[4:8], v) +} + +func (m LOCKU4args) SetOffset(v uint64) { + binary.BigEndian.PutUint64(m.m[24:32], v) +} + +func (m LOCKU4args) SetLength(v uint64) { + binary.BigEndian.PutUint64(m.m[32:40], v) +} + +// ------------------------------------------------------- +// LOCKU4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type LOCKU4res struct { + b []byte + disc uint32 +} + +func readLOCKU4res(b *[]byte, nfsstat4 uint32) (LOCKU4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readStateid4(b) + if !ok { + return LOCKU4res{}, false + } + return LOCKU4res{b: r.m[:], disc: nfsstat4}, true + default: + return LOCKU4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m LOCKU4res) AsStateid4() Stateid4 { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return Stateid4{m: (*[stateid4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// LOOKUP4args — variable: objname +// ------------------------------------------------------- + +type LOOKUP4args []byte + +func readLOOKUP4args(b *[]byte) (LOOKUP4args, bool) { + start := *b + startLen := len(start) + if _, ok := readComponent4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return LOOKUP4args(start[:total]), true +} + +func ReadLOOKUP4args(b []byte) (LOOKUP4args, bool) { + return readLOOKUP4args(&b) +} + +func (m LOOKUP4args) Objname() Component4 { + return Component4(m[0:]) +} + +// LOOKUP4argsWriter writes a LOOKUP4args: +// +// objname +type LOOKUP4argsWriter struct { + buf []byte + off int +} + +func StartLOOKUP4args(buf []byte) LOOKUP4argsWriter { + off := len(buf) + return LOOKUP4argsWriter{buf: buf, off: off} +} + +func (w *LOOKUP4argsWriter) StartObjname() Component4Writer { + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *LOOKUP4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LOOKUP4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LOOKUP4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type LOOKUP4res struct { + m *[lOOKUP4resSize]byte +} + +const lOOKUP4resSize = 4 + +func readLOOKUP4res(b *[]byte) (LOOKUP4res, bool) { + if len(*b) < lOOKUP4resSize { + return LOOKUP4res{}, false + } + result := LOOKUP4res{m: (*[lOOKUP4resSize]byte)(*b)} + *b = (*b)[lOOKUP4resSize:] + return result, true +} + +func ReadLOOKUP4res(b []byte) (LOOKUP4res, bool) { + return readLOOKUP4res(&b) +} + +func StartLOOKUP4res(buf []byte) ([]byte, LOOKUP4res) { + buf = append(buf, make([]byte, lOOKUP4resSize)...) + return buf, LOOKUP4res{m: (*[lOOKUP4resSize]byte)(buf[len(buf)-lOOKUP4resSize:])} +} + +func (m LOOKUP4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m LOOKUP4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// LOOKUPP4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type LOOKUPP4res struct { + m *[lOOKUPP4resSize]byte +} + +const lOOKUPP4resSize = 4 + +func readLOOKUPP4res(b *[]byte) (LOOKUPP4res, bool) { + if len(*b) < lOOKUPP4resSize { + return LOOKUPP4res{}, false + } + result := LOOKUPP4res{m: (*[lOOKUPP4resSize]byte)(*b)} + *b = (*b)[lOOKUPP4resSize:] + return result, true +} + +func ReadLOOKUPP4res(b []byte) (LOOKUPP4res, bool) { + return readLOOKUPP4res(&b) +} + +func StartLOOKUPP4res(buf []byte) ([]byte, LOOKUPP4res) { + buf = append(buf, make([]byte, lOOKUPP4resSize)...) + return buf, LOOKUPP4res{m: (*[lOOKUPP4resSize]byte)(buf[len(buf)-lOOKUPP4resSize:])} +} + +func (m LOOKUPP4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m LOOKUPP4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// NVERIFY4args — variable: obj_attributes +// ------------------------------------------------------- + +type NVERIFY4args []byte + +func readNVERIFY4args(b *[]byte) (NVERIFY4args, bool) { + start := *b + startLen := len(start) + if _, ok := readFattr4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return NVERIFY4args(start[:total]), true +} + +func ReadNVERIFY4args(b []byte) (NVERIFY4args, bool) { + return readNVERIFY4args(&b) +} + +func (m NVERIFY4args) ObjAttributes() Fattr4 { + v, _ := ReadFattr4(m[0:]) + return v +} + +// NVERIFY4argsWriter writes a NVERIFY4args: +// +// obj_attributes +type NVERIFY4argsWriter struct { + buf []byte + off int +} + +func StartNVERIFY4args(buf []byte) NVERIFY4argsWriter { + off := len(buf) + return NVERIFY4argsWriter{buf: buf, off: off} +} + +func (w *NVERIFY4argsWriter) StartObjAttributes() Fattr4Writer { + child := StartFattr4(w.buf) + w.buf = nil + return child +} + +func (w *NVERIFY4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *NVERIFY4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// NVERIFY4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type NVERIFY4res struct { + m *[nVERIFY4resSize]byte +} + +const nVERIFY4resSize = 4 + +func readNVERIFY4res(b *[]byte) (NVERIFY4res, bool) { + if len(*b) < nVERIFY4resSize { + return NVERIFY4res{}, false + } + result := NVERIFY4res{m: (*[nVERIFY4resSize]byte)(*b)} + *b = (*b)[nVERIFY4resSize:] + return result, true +} + +func ReadNVERIFY4res(b []byte) (NVERIFY4res, bool) { + return readNVERIFY4res(&b) +} + +func StartNVERIFY4res(buf []byte) ([]byte, NVERIFY4res) { + buf = append(buf, make([]byte, nVERIFY4resSize)...) + return buf, NVERIFY4res{m: (*[nVERIFY4resSize]byte)(buf[len(buf)-nVERIFY4resSize:])} +} + +func (m NVERIFY4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m NVERIFY4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// Createhow4 — union on createmode4 (external discriminant) +// ------------------------------------------------------- + +type Createhow4 struct { + b []byte + disc uint32 +} + +func readCreatehow4(b *[]byte, createmode4 uint32) (Createhow4, bool) { + switch createmode4 { + case UNCHECKED4: + r, ok := readFattr4(b) + if !ok { + return Createhow4{}, false + } + return Createhow4{b: r.data, disc: createmode4}, true + case GUARDED4: + r, ok := readFattr4(b) + if !ok { + return Createhow4{}, false + } + return Createhow4{b: r.data, disc: createmode4}, true + case EXCLUSIVE4: + r, ok := readVerifier4(b) + if !ok { + return Createhow4{}, false + } + return Createhow4{b: r.m[:], disc: createmode4}, true + default: + return Createhow4{}, false + } +} + +func (m Createhow4) AsUnchecked4() Fattr4 { + if m.disc != UNCHECKED4 { + panic("wrong union discriminant") + } + v, _ := ReadFattr4(m.b) + return v +} + +func (m Createhow4) AsGuarded4() Fattr4 { + if m.disc != GUARDED4 { + panic("wrong union discriminant") + } + v, _ := ReadFattr4(m.b) + return v +} + +func (m Createhow4) AsVerifier4() Verifier4 { + if m.disc != EXCLUSIVE4 { + panic("wrong union discriminant") + } + return Verifier4{m: (*[verifier4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// Createhow4Entry — variable: disc(4) + createhow4 value +// ------------------------------------------------------- + +type Createhow4Entry []byte + +func readCreatehow4Entry(b *[]byte) (Createhow4Entry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readCreatehow4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return Createhow4Entry(start[:total]), true +} + +func ReadCreatehow4Entry(b []byte) (Createhow4Entry, bool) { + return readCreatehow4Entry(&b) +} + +func (m Createhow4Entry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Createhow4Entry) Value() Createhow4 { + return Createhow4{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// Createhow4EntryWriter writes a createhow4_entry: +// +// disc + value(disc) +type Createhow4EntryWriter struct { + buf []byte + off int +} + +func StartCreatehow4Entry(buf []byte) Createhow4EntryWriter { + return Createhow4EntryWriter{buf: buf, off: len(buf)} +} + +func (w *Createhow4EntryWriter) SetValue_Unchecked4() Fattr4Writer { + w.buf = binary.BigEndian.AppendUint32(w.buf, UNCHECKED4) + child := StartFattr4(w.buf) + w.buf = nil + return child +} + +func (w *Createhow4EntryWriter) SetValue_Guarded4() Fattr4Writer { + w.buf = binary.BigEndian.AppendUint32(w.buf, GUARDED4) + child := StartFattr4(w.buf) + w.buf = nil + return child +} + +func (w *Createhow4EntryWriter) SetValue_Exclusive4() Verifier4 { + w.buf = append(w.buf, make([]byte, 4+verifier4Size)...) + p := (*[4 + verifier4Size]byte)(w.buf[len(w.buf)-4-verifier4Size:]) + binary.BigEndian.PutUint32(p[:4], EXCLUSIVE4) + return Verifier4{m: (*[verifier4Size]byte)(p[4:])} +} + +func (w *Createhow4EntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *Createhow4EntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// Openflag4 — union on opentype4 (external discriminant) +// ------------------------------------------------------- + +type Openflag4 struct { + b []byte + disc uint32 +} + +func readOpenflag4(b *[]byte, opentype4 uint32) (Openflag4, bool) { + switch opentype4 { + case OPEN4_CREATE: + r, ok := readCreatehow4Entry(b) + if !ok { + return Openflag4{}, false + } + return Openflag4{b: []byte(r), disc: opentype4}, true + default: + return Openflag4{b: (*b)[:0], disc: opentype4}, true + } +} + +func (m Openflag4) AsCreatehow4Entry() Createhow4Entry { + if m.disc != OPEN4_CREATE { + panic("wrong union discriminant") + } + return Createhow4Entry(m.b) +} + +// ------------------------------------------------------- +// NfsModifiedLimit4 — fixed 8 bytes: +// num_blocks(4, beu32) + bytes_per_block(4, beu32) +// ------------------------------------------------------- + +type NfsModifiedLimit4 struct { + m *[nfsModifiedLimit4Size]byte +} + +const nfsModifiedLimit4Size = 8 + +func readNfsModifiedLimit4(b *[]byte) (NfsModifiedLimit4, bool) { + if len(*b) < nfsModifiedLimit4Size { + return NfsModifiedLimit4{}, false + } + result := NfsModifiedLimit4{m: (*[nfsModifiedLimit4Size]byte)(*b)} + *b = (*b)[nfsModifiedLimit4Size:] + return result, true +} + +func ReadNfsModifiedLimit4(b []byte) (NfsModifiedLimit4, bool) { + return readNfsModifiedLimit4(&b) +} + +func StartNfsModifiedLimit4(buf []byte) ([]byte, NfsModifiedLimit4) { + buf = append(buf, make([]byte, nfsModifiedLimit4Size)...) + return buf, NfsModifiedLimit4{m: (*[nfsModifiedLimit4Size]byte)(buf[len(buf)-nfsModifiedLimit4Size:])} +} + +func (m NfsModifiedLimit4) NumBlocks() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m NfsModifiedLimit4) BytesPerBlock() uint32 { + return binary.BigEndian.Uint32(m.m[4:8]) +} + +func (m NfsModifiedLimit4) SetNumBlocks(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m NfsModifiedLimit4) SetBytesPerBlock(v uint32) { + binary.BigEndian.PutUint32(m.m[4:8], v) +} + +// ------------------------------------------------------- +// NfsSpaceLimit4NFSLIMITSIZE — fixed 8 bytes: value(8, beu64) +// ------------------------------------------------------- + +type NfsSpaceLimit4NFSLIMITSIZE struct { + m *[nfsSpaceLimit4NFSLIMITSIZESize]byte +} + +const nfsSpaceLimit4NFSLIMITSIZESize = 8 + +func readNfsSpaceLimit4NFSLIMITSIZE(b *[]byte) (NfsSpaceLimit4NFSLIMITSIZE, bool) { + if len(*b) < nfsSpaceLimit4NFSLIMITSIZESize { + return NfsSpaceLimit4NFSLIMITSIZE{}, false + } + result := NfsSpaceLimit4NFSLIMITSIZE{m: (*[nfsSpaceLimit4NFSLIMITSIZESize]byte)(*b)} + *b = (*b)[nfsSpaceLimit4NFSLIMITSIZESize:] + return result, true +} + +func ReadNfsSpaceLimit4NFSLIMITSIZE(b []byte) (NfsSpaceLimit4NFSLIMITSIZE, bool) { + return readNfsSpaceLimit4NFSLIMITSIZE(&b) +} + +func StartNfsSpaceLimit4NFSLIMITSIZE(buf []byte) ([]byte, NfsSpaceLimit4NFSLIMITSIZE) { + buf = append(buf, make([]byte, nfsSpaceLimit4NFSLIMITSIZESize)...) + return buf, NfsSpaceLimit4NFSLIMITSIZE{m: (*[nfsSpaceLimit4NFSLIMITSIZESize]byte)(buf[len(buf)-nfsSpaceLimit4NFSLIMITSIZESize:])} +} + +func (m NfsSpaceLimit4NFSLIMITSIZE) Value() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m NfsSpaceLimit4NFSLIMITSIZE) SetValue(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +// ------------------------------------------------------- +// NfsSpaceLimit4 — union on limit_by4 (external discriminant) +// ------------------------------------------------------- + +type NfsSpaceLimit4 struct { + b []byte + disc uint32 +} + +func readNfsSpaceLimit4(b *[]byte, limitBy4 uint32) (NfsSpaceLimit4, bool) { + switch limitBy4 { + case NFS_LIMIT_SIZE: + r, ok := readNfsSpaceLimit4NFSLIMITSIZE(b) + if !ok { + return NfsSpaceLimit4{}, false + } + return NfsSpaceLimit4{b: r.m[:], disc: limitBy4}, true + case NFS_LIMIT_BLOCKS: + r, ok := readNfsModifiedLimit4(b) + if !ok { + return NfsSpaceLimit4{}, false + } + return NfsSpaceLimit4{b: r.m[:], disc: limitBy4}, true + default: + return NfsSpaceLimit4{}, false + } +} + +func (m NfsSpaceLimit4) AsNfsSpaceLimit4NFSLIMITSIZE() NfsSpaceLimit4NFSLIMITSIZE { + if m.disc != NFS_LIMIT_SIZE { + panic("wrong union discriminant") + } + return NfsSpaceLimit4NFSLIMITSIZE{m: (*[nfsSpaceLimit4NFSLIMITSIZESize]byte)(m.b)} +} + +func (m NfsSpaceLimit4) AsNfsModifiedLimit4() NfsModifiedLimit4 { + if m.disc != NFS_LIMIT_BLOCKS { + panic("wrong union discriminant") + } + return NfsModifiedLimit4{m: (*[nfsModifiedLimit4Size]byte)(m.b)} +} + +// ------------------------------------------------------- +// OpenClaimDelegateCur4 — variable: delegate_stateid(16) + file +// ------------------------------------------------------- + +type OpenClaimDelegateCur4 []byte + +func readOpenClaimDelegateCur4(b *[]byte) (OpenClaimDelegateCur4, bool) { + if len(*b) < 16 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[16:] + if _, ok := readComponent4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return OpenClaimDelegateCur4(start[:total]), true +} + +func ReadOpenClaimDelegateCur4(b []byte) (OpenClaimDelegateCur4, bool) { + return readOpenClaimDelegateCur4(&b) +} + +func (m OpenClaimDelegateCur4) DelegateStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m[0 : 0+stateid4Size])} +} + +func (m OpenClaimDelegateCur4) File() Component4 { + return Component4(m[16:]) +} + +// OpenClaimDelegateCur4Writer writes a open_claim_delegate_cur4: +// +// delegate_stateid + file +type OpenClaimDelegateCur4Writer struct { + buf []byte + header *[16]byte +} + +func StartOpenClaimDelegateCur4(buf []byte) OpenClaimDelegateCur4Writer { + buf = append(buf, make([]byte, 16)...) // delegate_stateid(16) + return OpenClaimDelegateCur4Writer{buf: buf, header: (*[16]byte)(buf[len(buf)-16:])} +} + +func (w *OpenClaimDelegateCur4Writer) DelegateStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[0:])} +} + +func (w *OpenClaimDelegateCur4Writer) StartFile() Component4Writer { + child := StartComponent4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *OpenClaimDelegateCur4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *OpenClaimDelegateCur4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// OpenClaim4CLAIMPREVIOUS — fixed 4 bytes: value(4, beu32) +// ------------------------------------------------------- + +type OpenClaim4CLAIMPREVIOUS struct { + m *[openClaim4CLAIMPREVIOUSSize]byte +} + +const openClaim4CLAIMPREVIOUSSize = 4 + +func readOpenClaim4CLAIMPREVIOUS(b *[]byte) (OpenClaim4CLAIMPREVIOUS, bool) { + if len(*b) < openClaim4CLAIMPREVIOUSSize { + return OpenClaim4CLAIMPREVIOUS{}, false + } + result := OpenClaim4CLAIMPREVIOUS{m: (*[openClaim4CLAIMPREVIOUSSize]byte)(*b)} + *b = (*b)[openClaim4CLAIMPREVIOUSSize:] + return result, true +} + +func ReadOpenClaim4CLAIMPREVIOUS(b []byte) (OpenClaim4CLAIMPREVIOUS, bool) { + return readOpenClaim4CLAIMPREVIOUS(&b) +} + +func StartOpenClaim4CLAIMPREVIOUS(buf []byte) ([]byte, OpenClaim4CLAIMPREVIOUS) { + buf = append(buf, make([]byte, openClaim4CLAIMPREVIOUSSize)...) + return buf, OpenClaim4CLAIMPREVIOUS{m: (*[openClaim4CLAIMPREVIOUSSize]byte)(buf[len(buf)-openClaim4CLAIMPREVIOUSSize:])} +} + +func (m OpenClaim4CLAIMPREVIOUS) Value() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m OpenClaim4CLAIMPREVIOUS) SetValue(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// OpenClaim4 — union on open_claim_type4 (external discriminant) +// ------------------------------------------------------- + +type OpenClaim4 struct { + b []byte + disc uint32 +} + +func readOpenClaim4(b *[]byte, openClaimType4 uint32) (OpenClaim4, bool) { + switch openClaimType4 { + case CLAIM_NULL: + r, ok := readComponent4(b) + if !ok { + return OpenClaim4{}, false + } + return OpenClaim4{b: []byte(r), disc: openClaimType4}, true + case CLAIM_PREVIOUS: + r, ok := readOpenClaim4CLAIMPREVIOUS(b) + if !ok { + return OpenClaim4{}, false + } + return OpenClaim4{b: r.m[:], disc: openClaimType4}, true + case CLAIM_DELEGATE_CUR: + r, ok := readOpenClaimDelegateCur4(b) + if !ok { + return OpenClaim4{}, false + } + return OpenClaim4{b: []byte(r), disc: openClaimType4}, true + case CLAIM_DELEGATE_PREV: + r, ok := readComponent4(b) + if !ok { + return OpenClaim4{}, false + } + return OpenClaim4{b: []byte(r), disc: openClaimType4}, true + default: + return OpenClaim4{}, false + } +} + +func (m OpenClaim4) AsNull() Component4 { + if m.disc != CLAIM_NULL { + panic("wrong union discriminant") + } + return Component4(m.b) +} + +func (m OpenClaim4) AsOpenClaim4CLAIMPREVIOUS() OpenClaim4CLAIMPREVIOUS { + if m.disc != CLAIM_PREVIOUS { + panic("wrong union discriminant") + } + return OpenClaim4CLAIMPREVIOUS{m: (*[openClaim4CLAIMPREVIOUSSize]byte)(m.b)} +} + +func (m OpenClaim4) AsOpenClaimDelegateCur4() OpenClaimDelegateCur4 { + if m.disc != CLAIM_DELEGATE_CUR { + panic("wrong union discriminant") + } + return OpenClaimDelegateCur4(m.b) +} + +func (m OpenClaim4) AsDelegatePrev() Component4 { + if m.disc != CLAIM_DELEGATE_PREV { + panic("wrong union discriminant") + } + return Component4(m.b) +} + +// ------------------------------------------------------- +// OPEN4args — variable: +// seqid(4) + share_access(4) + share_deny(4) + owner + openhow_type(4) + openflag4 openhow + claim_type(4) + open_claim4 claim +// ------------------------------------------------------- + +type OPEN4args struct { + data []byte + off1 int // byte offset within data where openhow_type starts + off2 int // byte offset within data where claim_type starts +} + +func readOPEN4args(b *[]byte) (OPEN4args, bool) { + if len(*b) < 12 { + return OPEN4args{}, false + } + start := *b + startLen := len(start) + *b = (*b)[12:] + if _, ok := readOpenOwner4(b); !ok { + return OPEN4args{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return OPEN4args{}, false + } + c_openhow_type := binary.BigEndian.Uint32((*b)[:4]) + *b = (*b)[4:] + if _, ok := readOpenflag4(b, c_openhow_type); !ok { + return OPEN4args{}, false + } + off2 := startLen - len(*b) + if len(*b) < 4 { + return OPEN4args{}, false + } + c_claim_type := binary.BigEndian.Uint32((*b)[:4]) + *b = (*b)[4:] + if _, ok := readOpenClaim4(b, c_claim_type); !ok { + return OPEN4args{}, false + } + total := startLen - len(*b) + return OPEN4args{data: start[:total], off1: off1, off2: off2}, true +} + +func ReadOPEN4args(b []byte) (OPEN4args, bool) { + return readOPEN4args(&b) +} + +func (m OPEN4args) Seqid() uint32 { + return binary.BigEndian.Uint32(m.data[0:4]) +} + +func (m OPEN4args) ShareAccess() uint32 { + return binary.BigEndian.Uint32(m.data[4:8]) +} + +func (m OPEN4args) ShareDeny() uint32 { + return binary.BigEndian.Uint32(m.data[8:12]) +} + +func (m OPEN4args) Owner() OpenOwner4 { + return OpenOwner4(m.data[12:m.off1]) +} + +func (m OPEN4args) OpenhowType() uint32 { + o := m.off1 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m OPEN4args) Openhow() Openflag4 { + return Openflag4{b: m.data[m.off1+4 : m.off2], disc: binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4])} +} + +func (m OPEN4args) ClaimType() uint32 { + o := m.off2 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m OPEN4args) Claim() OpenClaim4 { + return OpenClaim4{b: m.data[m.off2+4:], disc: binary.BigEndian.Uint32(m.data[m.off2 : m.off2+4])} +} + +// OPEN4argsWriter writes a OPEN4args: +// +// seqid + share_access + share_deny + owner + openhow_type + openhow(openhow_type) + claim_type + claim(claim_type) +type OPEN4argsWriter struct { + buf []byte + header *[12]byte + phase uint8 +} + +func StartOPEN4args(buf []byte) OPEN4argsWriter { + buf = append(buf, make([]byte, 12)...) // seqid(4) + share_access(4) + share_deny(4) + return OPEN4argsWriter{buf: buf, header: (*[12]byte)(buf[len(buf)-12:])} +} + +func (w *OPEN4argsWriter) SetSeqid(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *OPEN4argsWriter) SetShareAccess(v uint32) { + binary.BigEndian.PutUint32(w.header[4:8], v) +} + +func (w *OPEN4argsWriter) SetShareDeny(v uint32) { + binary.BigEndian.PutUint32(w.header[8:12], v) +} + +func (w *OPEN4argsWriter) SetOpenhow_Create() Createhow4EntryWriter { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = binary.BigEndian.AppendUint32(w.buf, OPEN4_CREATE) + child := StartCreatehow4Entry(w.buf) + w.buf = nil + return child +} + +func (w *OPEN4argsWriter) SetOpenhow_Default(opentype4 uint32) { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = binary.BigEndian.AppendUint32(w.buf, opentype4) +} + +func (w *OPEN4argsWriter) SetClaim_Null() Component4Writer { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = binary.BigEndian.AppendUint32(w.buf, CLAIM_NULL) + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *OPEN4argsWriter) SetClaim_Previous() OpenClaim4CLAIMPREVIOUS { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = append(w.buf, make([]byte, 4+openClaim4CLAIMPREVIOUSSize)...) + p := (*[4 + openClaim4CLAIMPREVIOUSSize]byte)(w.buf[len(w.buf)-4-openClaim4CLAIMPREVIOUSSize:]) + binary.BigEndian.PutUint32(p[:4], CLAIM_PREVIOUS) + return OpenClaim4CLAIMPREVIOUS{m: (*[openClaim4CLAIMPREVIOUSSize]byte)(p[4:])} +} + +func (w *OPEN4argsWriter) SetClaim_DelegateCur() OpenClaimDelegateCur4Writer { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = append(w.buf, make([]byte, 4+16)...) + off := len(w.buf) - 4 - 16 + binary.BigEndian.PutUint32((*[4 + 16]byte)(w.buf[off:])[:4], CLAIM_DELEGATE_CUR) + buf := w.buf + w.buf = nil + return OpenClaimDelegateCur4Writer{buf: buf, header: (*[16]byte)(buf[off+4:])} +} + +func (w *OPEN4argsWriter) SetClaim_DelegatePrev() Component4Writer { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = binary.BigEndian.AppendUint32(w.buf, CLAIM_DELEGATE_PREV) + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *OPEN4argsWriter) StartOwner() OpenOwner4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartOpenOwner4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *OPEN4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *OPEN4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// OpenReadDelegation4 — variable: stateid(16) + recall(4) + permissions +// ------------------------------------------------------- + +type OpenReadDelegation4 []byte + +func readOpenReadDelegation4(b *[]byte) (OpenReadDelegation4, bool) { + if len(*b) < 20 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[20:] + if _, ok := readNfsace4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return OpenReadDelegation4(start[:total]), true +} + +func ReadOpenReadDelegation4(b []byte) (OpenReadDelegation4, bool) { + return readOpenReadDelegation4(&b) +} + +func (m OpenReadDelegation4) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m[0 : 0+stateid4Size])} +} + +func (m OpenReadDelegation4) Recall() uint32 { + return binary.BigEndian.Uint32(m[16:20]) +} + +func (m OpenReadDelegation4) Permissions() Nfsace4 { + return Nfsace4(m[20:]) +} + +// OpenReadDelegation4Writer writes a open_read_delegation4: +// +// stateid + recall + permissions +type OpenReadDelegation4Writer struct { + buf []byte + header *[20]byte +} + +func StartOpenReadDelegation4(buf []byte) OpenReadDelegation4Writer { + buf = append(buf, make([]byte, 20)...) // stateid(16) + recall(4) + return OpenReadDelegation4Writer{buf: buf, header: (*[20]byte)(buf[len(buf)-20:])} +} + +func (w *OpenReadDelegation4Writer) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[0:])} +} + +func (w *OpenReadDelegation4Writer) SetRecall(v uint32) { + binary.BigEndian.PutUint32(w.header[16:20], v) +} + +func (w *OpenReadDelegation4Writer) StartPermissions() Nfsace4Writer { + child := StartNfsace4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *OpenReadDelegation4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *OpenReadDelegation4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// OpenWriteDelegation4 — variable: +// stateid(16) + recall(4) + space_limit_type(4) + nfs_space_limit4 space_limit + permissions +// ------------------------------------------------------- + +type OpenWriteDelegation4 struct { + data []byte + off1 int // byte offset within data where permissions starts +} + +func readOpenWriteDelegation4(b *[]byte) (OpenWriteDelegation4, bool) { + if len(*b) < 24 { + return OpenWriteDelegation4{}, false + } + start := *b + startLen := len(start) + *b = (*b)[24:] + if _, ok := readNfsSpaceLimit4(b, binary.BigEndian.Uint32(start[20:24])); !ok { + return OpenWriteDelegation4{}, false + } + off1 := startLen - len(*b) + if _, ok := readNfsace4(b); !ok { + return OpenWriteDelegation4{}, false + } + total := startLen - len(*b) + return OpenWriteDelegation4{data: start[:total], off1: off1}, true +} + +func ReadOpenWriteDelegation4(b []byte) (OpenWriteDelegation4, bool) { + return readOpenWriteDelegation4(&b) +} + +func (m OpenWriteDelegation4) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.data[0 : 0+stateid4Size])} +} + +func (m OpenWriteDelegation4) Recall() uint32 { + return binary.BigEndian.Uint32(m.data[16:20]) +} + +func (m OpenWriteDelegation4) SpaceLimitType() uint32 { + return binary.BigEndian.Uint32(m.data[20:24]) +} + +func (m OpenWriteDelegation4) SpaceLimit() NfsSpaceLimit4 { + return NfsSpaceLimit4{b: m.data[24:m.off1], disc: binary.BigEndian.Uint32(m.data[20:24])} +} + +func (m OpenWriteDelegation4) Permissions() Nfsace4 { + return Nfsace4(m.data[m.off1:]) +} + +// OpenWriteDelegation4Writer writes a open_write_delegation4: +// +// stateid + recall + space_limit_type + space_limit(space_limit_type) + permissions +type OpenWriteDelegation4Writer struct { + buf []byte + header *[24]byte +} + +func StartOpenWriteDelegation4(buf []byte) OpenWriteDelegation4Writer { + buf = append(buf, make([]byte, 24)...) // stateid(16) + recall(4) + space_limit_type(4) + return OpenWriteDelegation4Writer{buf: buf, header: (*[24]byte)(buf[len(buf)-24:])} +} + +func (w *OpenWriteDelegation4Writer) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[0:])} +} + +func (w *OpenWriteDelegation4Writer) SetRecall(v uint32) { + binary.BigEndian.PutUint32(w.header[16:20], v) +} + +func (w *OpenWriteDelegation4Writer) SetSpaceLimitType(v uint32) { + binary.BigEndian.PutUint32(w.header[20:24], v) +} + +func (w *OpenWriteDelegation4Writer) SetSpaceLimit_Size() NfsSpaceLimit4NFSLIMITSIZE { + binary.BigEndian.PutUint32(w.header[20:24], NFS_LIMIT_SIZE) + w.buf = append(w.buf, make([]byte, nfsSpaceLimit4NFSLIMITSIZESize)...) + return NfsSpaceLimit4NFSLIMITSIZE{m: (*[nfsSpaceLimit4NFSLIMITSIZESize]byte)(w.buf[len(w.buf)-nfsSpaceLimit4NFSLIMITSIZESize:])} +} + +func (w *OpenWriteDelegation4Writer) SetSpaceLimit_Blocks() NfsModifiedLimit4 { + binary.BigEndian.PutUint32(w.header[20:24], NFS_LIMIT_BLOCKS) + w.buf = append(w.buf, make([]byte, nfsModifiedLimit4Size)...) + return NfsModifiedLimit4{m: (*[nfsModifiedLimit4Size]byte)(w.buf[len(w.buf)-nfsModifiedLimit4Size:])} +} + +func (w *OpenWriteDelegation4Writer) StartPermissions() Nfsace4Writer { + child := StartNfsace4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *OpenWriteDelegation4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *OpenWriteDelegation4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// OpenDelegation4 — union on open_delegation_type4 (external discriminant) +// ------------------------------------------------------- + +type OpenDelegation4 struct { + b []byte + disc uint32 +} + +func readOpenDelegation4(b *[]byte, openDelegationType4 uint32) (OpenDelegation4, bool) { + switch openDelegationType4 { + case OPEN_DELEGATE_NONE: + return OpenDelegation4{b: (*b)[:0], disc: openDelegationType4}, true + case OPEN_DELEGATE_READ: + r, ok := readOpenReadDelegation4(b) + if !ok { + return OpenDelegation4{}, false + } + return OpenDelegation4{b: []byte(r), disc: openDelegationType4}, true + case OPEN_DELEGATE_WRITE: + r, ok := readOpenWriteDelegation4(b) + if !ok { + return OpenDelegation4{}, false + } + return OpenDelegation4{b: r.data, disc: openDelegationType4}, true + default: + return OpenDelegation4{}, false + } +} + +func (m OpenDelegation4) AsOpenReadDelegation4() OpenReadDelegation4 { + if m.disc != OPEN_DELEGATE_READ { + panic("wrong union discriminant") + } + return OpenReadDelegation4(m.b) +} + +func (m OpenDelegation4) AsOpenWriteDelegation4() OpenWriteDelegation4 { + if m.disc != OPEN_DELEGATE_WRITE { + panic("wrong union discriminant") + } + v, _ := ReadOpenWriteDelegation4(m.b) + return v +} + +// ------------------------------------------------------- +// OPEN4resok — variable: +// stateid(16) + cinfo(20) + rflags(4) + attrset + delegation_type(4) + open_delegation4 delegation +// ------------------------------------------------------- + +type OPEN4resok struct { + data []byte + off1 int // byte offset within data where delegation_type starts +} + +func readOPEN4resok(b *[]byte) (OPEN4resok, bool) { + if len(*b) < 40 { + return OPEN4resok{}, false + } + start := *b + startLen := len(start) + *b = (*b)[40:] + if _, ok := readBitmap4(b); !ok { + return OPEN4resok{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return OPEN4resok{}, false + } + c_delegation_type := binary.BigEndian.Uint32((*b)[:4]) + *b = (*b)[4:] + if _, ok := readOpenDelegation4(b, c_delegation_type); !ok { + return OPEN4resok{}, false + } + total := startLen - len(*b) + return OPEN4resok{data: start[:total], off1: off1}, true +} + +func ReadOPEN4resok(b []byte) (OPEN4resok, bool) { + return readOPEN4resok(&b) +} + +func (m OPEN4resok) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.data[0 : 0+stateid4Size])} +} + +func (m OPEN4resok) Cinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(m.data[16 : 16+changeInfo4Size])} +} + +func (m OPEN4resok) Rflags() uint32 { + return binary.BigEndian.Uint32(m.data[36:40]) +} + +func (m OPEN4resok) Attrset() Bitmap4 { + return Bitmap4(m.data[40:m.off1]) +} + +func (m OPEN4resok) DelegationType() uint32 { + o := m.off1 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m OPEN4resok) Delegation() OpenDelegation4 { + return OpenDelegation4{b: m.data[m.off1+4:], disc: binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4])} +} + +// OPEN4resokWriter writes a OPEN4resok: +// +// stateid + cinfo + rflags + attrset + delegation_type + delegation(delegation_type) +type OPEN4resokWriter struct { + buf []byte + header *[40]byte + phase uint8 +} + +func StartOPEN4resok(buf []byte) OPEN4resokWriter { + buf = append(buf, make([]byte, 40)...) // stateid(16) + cinfo(20) + rflags(4) + return OPEN4resokWriter{buf: buf, header: (*[40]byte)(buf[len(buf)-40:])} +} + +func (w *OPEN4resokWriter) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[0:])} +} + +func (w *OPEN4resokWriter) Cinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(w.header[16:])} +} + +func (w *OPEN4resokWriter) SetRflags(v uint32) { + binary.BigEndian.PutUint32(w.header[36:40], v) +} + +func (w *OPEN4resokWriter) SetDelegation_None() { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = binary.BigEndian.AppendUint32(w.buf, OPEN_DELEGATE_NONE) +} + +func (w *OPEN4resokWriter) SetDelegation_Read() OpenReadDelegation4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], OPEN_DELEGATE_READ) + buf := w.buf + w.buf = nil + return OpenReadDelegation4Writer{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *OPEN4resokWriter) SetDelegation_Write() OpenWriteDelegation4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = append(w.buf, make([]byte, 4+24)...) + off := len(w.buf) - 4 - 24 + binary.BigEndian.PutUint32((*[4 + 24]byte)(w.buf[off:])[:4], OPEN_DELEGATE_WRITE) + buf := w.buf + w.buf = nil + return OpenWriteDelegation4Writer{buf: buf, header: (*[24]byte)(buf[off+4:])} +} + +func (w *OPEN4resokWriter) StartAttrset() Bitmap4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartBitmap4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *OPEN4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *OPEN4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// OPEN4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type OPEN4res struct { + b []byte + disc uint32 +} + +func readOPEN4res(b *[]byte, nfsstat4 uint32) (OPEN4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readOPEN4resok(b) + if !ok { + return OPEN4res{}, false + } + return OPEN4res{b: r.data, disc: nfsstat4}, true + default: + return OPEN4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m OPEN4res) AsOPEN4resok() OPEN4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + v, _ := ReadOPEN4resok(m.b) + return v +} + +// ------------------------------------------------------- +// OPENATTR4args — fixed 4 bytes: createdir(4, beu32) +// ------------------------------------------------------- + +type OPENATTR4args struct { + m *[oPENATTR4argsSize]byte +} + +const oPENATTR4argsSize = 4 + +func readOPENATTR4args(b *[]byte) (OPENATTR4args, bool) { + if len(*b) < oPENATTR4argsSize { + return OPENATTR4args{}, false + } + result := OPENATTR4args{m: (*[oPENATTR4argsSize]byte)(*b)} + *b = (*b)[oPENATTR4argsSize:] + return result, true +} + +func ReadOPENATTR4args(b []byte) (OPENATTR4args, bool) { + return readOPENATTR4args(&b) +} + +func StartOPENATTR4args(buf []byte) ([]byte, OPENATTR4args) { + buf = append(buf, make([]byte, oPENATTR4argsSize)...) + return buf, OPENATTR4args{m: (*[oPENATTR4argsSize]byte)(buf[len(buf)-oPENATTR4argsSize:])} +} + +func (m OPENATTR4args) Createdir() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m OPENATTR4args) SetCreatedir(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// OPENATTR4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type OPENATTR4res struct { + m *[oPENATTR4resSize]byte +} + +const oPENATTR4resSize = 4 + +func readOPENATTR4res(b *[]byte) (OPENATTR4res, bool) { + if len(*b) < oPENATTR4resSize { + return OPENATTR4res{}, false + } + result := OPENATTR4res{m: (*[oPENATTR4resSize]byte)(*b)} + *b = (*b)[oPENATTR4resSize:] + return result, true +} + +func ReadOPENATTR4res(b []byte) (OPENATTR4res, bool) { + return readOPENATTR4res(&b) +} + +func StartOPENATTR4res(buf []byte) ([]byte, OPENATTR4res) { + buf = append(buf, make([]byte, oPENATTR4resSize)...) + return buf, OPENATTR4res{m: (*[oPENATTR4resSize]byte)(buf[len(buf)-oPENATTR4resSize:])} +} + +func (m OPENATTR4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m OPENATTR4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// OPENCONFIRM4args — fixed 20 bytes: stateid4(16) + seqid(4, beu32) +// ------------------------------------------------------- + +type OPENCONFIRM4args struct { + m *[oPENCONFIRM4argsSize]byte +} + +const oPENCONFIRM4argsSize = 20 + +func readOPENCONFIRM4args(b *[]byte) (OPENCONFIRM4args, bool) { + if len(*b) < oPENCONFIRM4argsSize { + return OPENCONFIRM4args{}, false + } + result := OPENCONFIRM4args{m: (*[oPENCONFIRM4argsSize]byte)(*b)} + *b = (*b)[oPENCONFIRM4argsSize:] + return result, true +} + +func ReadOPENCONFIRM4args(b []byte) (OPENCONFIRM4args, bool) { + return readOPENCONFIRM4args(&b) +} + +func StartOPENCONFIRM4args(buf []byte) ([]byte, OPENCONFIRM4args) { + buf = append(buf, make([]byte, oPENCONFIRM4argsSize)...) + return buf, OPENCONFIRM4args{m: (*[oPENCONFIRM4argsSize]byte)(buf[len(buf)-oPENCONFIRM4argsSize:])} +} + +func (m OPENCONFIRM4args) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +func (m OPENCONFIRM4args) Seqid() uint32 { + return binary.BigEndian.Uint32(m.m[16:20]) +} + +func (m OPENCONFIRM4args) SetSeqid(v uint32) { + binary.BigEndian.PutUint32(m.m[16:20], v) +} + +// ------------------------------------------------------- +// OPENCONFIRM4resok — fixed 16 bytes: stateid4(16) +// ------------------------------------------------------- + +type OPENCONFIRM4resok struct { + m *[oPENCONFIRM4resokSize]byte +} + +const oPENCONFIRM4resokSize = 16 + +func readOPENCONFIRM4resok(b *[]byte) (OPENCONFIRM4resok, bool) { + if len(*b) < oPENCONFIRM4resokSize { + return OPENCONFIRM4resok{}, false + } + result := OPENCONFIRM4resok{m: (*[oPENCONFIRM4resokSize]byte)(*b)} + *b = (*b)[oPENCONFIRM4resokSize:] + return result, true +} + +func ReadOPENCONFIRM4resok(b []byte) (OPENCONFIRM4resok, bool) { + return readOPENCONFIRM4resok(&b) +} + +func StartOPENCONFIRM4resok(buf []byte) ([]byte, OPENCONFIRM4resok) { + buf = append(buf, make([]byte, oPENCONFIRM4resokSize)...) + return buf, OPENCONFIRM4resok{m: (*[oPENCONFIRM4resokSize]byte)(buf[len(buf)-oPENCONFIRM4resokSize:])} +} + +func (m OPENCONFIRM4resok) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +// ------------------------------------------------------- +// OPENCONFIRM4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type OPENCONFIRM4res struct { + b []byte + disc uint32 +} + +func readOPENCONFIRM4res(b *[]byte, nfsstat4 uint32) (OPENCONFIRM4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readOPENCONFIRM4resok(b) + if !ok { + return OPENCONFIRM4res{}, false + } + return OPENCONFIRM4res{b: r.m[:], disc: nfsstat4}, true + default: + return OPENCONFIRM4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m OPENCONFIRM4res) AsOPENCONFIRM4resok() OPENCONFIRM4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return OPENCONFIRM4resok{m: (*[oPENCONFIRM4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// OPENDOWNGRADE4args — fixed 28 bytes: +// stateid4(16) + seqid(4, beu32) + share_access(4, beu32) + share_deny(4, beu32) +// ------------------------------------------------------- + +type OPENDOWNGRADE4args struct { + m *[oPENDOWNGRADE4argsSize]byte +} + +const oPENDOWNGRADE4argsSize = 28 + +func readOPENDOWNGRADE4args(b *[]byte) (OPENDOWNGRADE4args, bool) { + if len(*b) < oPENDOWNGRADE4argsSize { + return OPENDOWNGRADE4args{}, false + } + result := OPENDOWNGRADE4args{m: (*[oPENDOWNGRADE4argsSize]byte)(*b)} + *b = (*b)[oPENDOWNGRADE4argsSize:] + return result, true +} + +func ReadOPENDOWNGRADE4args(b []byte) (OPENDOWNGRADE4args, bool) { + return readOPENDOWNGRADE4args(&b) +} + +func StartOPENDOWNGRADE4args(buf []byte) ([]byte, OPENDOWNGRADE4args) { + buf = append(buf, make([]byte, oPENDOWNGRADE4argsSize)...) + return buf, OPENDOWNGRADE4args{m: (*[oPENDOWNGRADE4argsSize]byte)(buf[len(buf)-oPENDOWNGRADE4argsSize:])} +} + +func (m OPENDOWNGRADE4args) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +func (m OPENDOWNGRADE4args) Seqid() uint32 { + return binary.BigEndian.Uint32(m.m[16:20]) +} + +func (m OPENDOWNGRADE4args) ShareAccess() uint32 { + return binary.BigEndian.Uint32(m.m[20:24]) +} + +func (m OPENDOWNGRADE4args) ShareDeny() uint32 { + return binary.BigEndian.Uint32(m.m[24:28]) +} + +func (m OPENDOWNGRADE4args) SetSeqid(v uint32) { + binary.BigEndian.PutUint32(m.m[16:20], v) +} + +func (m OPENDOWNGRADE4args) SetShareAccess(v uint32) { + binary.BigEndian.PutUint32(m.m[20:24], v) +} + +func (m OPENDOWNGRADE4args) SetShareDeny(v uint32) { + binary.BigEndian.PutUint32(m.m[24:28], v) +} + +// ------------------------------------------------------- +// OPENDOWNGRADE4resok — fixed 16 bytes: stateid4(16) +// ------------------------------------------------------- + +type OPENDOWNGRADE4resok struct { + m *[oPENDOWNGRADE4resokSize]byte +} + +const oPENDOWNGRADE4resokSize = 16 + +func readOPENDOWNGRADE4resok(b *[]byte) (OPENDOWNGRADE4resok, bool) { + if len(*b) < oPENDOWNGRADE4resokSize { + return OPENDOWNGRADE4resok{}, false + } + result := OPENDOWNGRADE4resok{m: (*[oPENDOWNGRADE4resokSize]byte)(*b)} + *b = (*b)[oPENDOWNGRADE4resokSize:] + return result, true +} + +func ReadOPENDOWNGRADE4resok(b []byte) (OPENDOWNGRADE4resok, bool) { + return readOPENDOWNGRADE4resok(&b) +} + +func StartOPENDOWNGRADE4resok(buf []byte) ([]byte, OPENDOWNGRADE4resok) { + buf = append(buf, make([]byte, oPENDOWNGRADE4resokSize)...) + return buf, OPENDOWNGRADE4resok{m: (*[oPENDOWNGRADE4resokSize]byte)(buf[len(buf)-oPENDOWNGRADE4resokSize:])} +} + +func (m OPENDOWNGRADE4resok) OpenStateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +// ------------------------------------------------------- +// OPENDOWNGRADE4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type OPENDOWNGRADE4res struct { + b []byte + disc uint32 +} + +func readOPENDOWNGRADE4res(b *[]byte, nfsstat4 uint32) (OPENDOWNGRADE4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readOPENDOWNGRADE4resok(b) + if !ok { + return OPENDOWNGRADE4res{}, false + } + return OPENDOWNGRADE4res{b: r.m[:], disc: nfsstat4}, true + default: + return OPENDOWNGRADE4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m OPENDOWNGRADE4res) AsOPENDOWNGRADE4resok() OPENDOWNGRADE4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return OPENDOWNGRADE4resok{m: (*[oPENDOWNGRADE4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// PUTFH4args — variable: object +// ------------------------------------------------------- + +type PUTFH4args []byte + +func readPUTFH4args(b *[]byte) (PUTFH4args, bool) { + start := *b + startLen := len(start) + if _, ok := readNfsFh4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return PUTFH4args(start[:total]), true +} + +func ReadPUTFH4args(b []byte) (PUTFH4args, bool) { + return readPUTFH4args(&b) +} + +func (m PUTFH4args) Object() NfsFh4 { + return NfsFh4(m[0:]) +} + +// PUTFH4argsWriter writes a PUTFH4args: +// +// object +type PUTFH4argsWriter struct { + buf []byte + off int +} + +func StartPUTFH4args(buf []byte) PUTFH4argsWriter { + off := len(buf) + return PUTFH4argsWriter{buf: buf, off: off} +} + +func (w *PUTFH4argsWriter) StartObject() NfsFh4Writer { + child := StartNfsFh4(w.buf) + w.buf = nil + return child +} + +func (w *PUTFH4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *PUTFH4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// PUTFH4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type PUTFH4res struct { + m *[pUTFH4resSize]byte +} + +const pUTFH4resSize = 4 + +func readPUTFH4res(b *[]byte) (PUTFH4res, bool) { + if len(*b) < pUTFH4resSize { + return PUTFH4res{}, false + } + result := PUTFH4res{m: (*[pUTFH4resSize]byte)(*b)} + *b = (*b)[pUTFH4resSize:] + return result, true +} + +func ReadPUTFH4res(b []byte) (PUTFH4res, bool) { + return readPUTFH4res(&b) +} + +func StartPUTFH4res(buf []byte) ([]byte, PUTFH4res) { + buf = append(buf, make([]byte, pUTFH4resSize)...) + return buf, PUTFH4res{m: (*[pUTFH4resSize]byte)(buf[len(buf)-pUTFH4resSize:])} +} + +func (m PUTFH4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m PUTFH4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// PUTPUBFH4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type PUTPUBFH4res struct { + m *[pUTPUBFH4resSize]byte +} + +const pUTPUBFH4resSize = 4 + +func readPUTPUBFH4res(b *[]byte) (PUTPUBFH4res, bool) { + if len(*b) < pUTPUBFH4resSize { + return PUTPUBFH4res{}, false + } + result := PUTPUBFH4res{m: (*[pUTPUBFH4resSize]byte)(*b)} + *b = (*b)[pUTPUBFH4resSize:] + return result, true +} + +func ReadPUTPUBFH4res(b []byte) (PUTPUBFH4res, bool) { + return readPUTPUBFH4res(&b) +} + +func StartPUTPUBFH4res(buf []byte) ([]byte, PUTPUBFH4res) { + buf = append(buf, make([]byte, pUTPUBFH4resSize)...) + return buf, PUTPUBFH4res{m: (*[pUTPUBFH4resSize]byte)(buf[len(buf)-pUTPUBFH4resSize:])} +} + +func (m PUTPUBFH4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m PUTPUBFH4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// PUTROOTFH4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type PUTROOTFH4res struct { + m *[pUTROOTFH4resSize]byte +} + +const pUTROOTFH4resSize = 4 + +func readPUTROOTFH4res(b *[]byte) (PUTROOTFH4res, bool) { + if len(*b) < pUTROOTFH4resSize { + return PUTROOTFH4res{}, false + } + result := PUTROOTFH4res{m: (*[pUTROOTFH4resSize]byte)(*b)} + *b = (*b)[pUTROOTFH4resSize:] + return result, true +} + +func ReadPUTROOTFH4res(b []byte) (PUTROOTFH4res, bool) { + return readPUTROOTFH4res(&b) +} + +func StartPUTROOTFH4res(buf []byte) ([]byte, PUTROOTFH4res) { + buf = append(buf, make([]byte, pUTROOTFH4resSize)...) + return buf, PUTROOTFH4res{m: (*[pUTROOTFH4resSize]byte)(buf[len(buf)-pUTROOTFH4resSize:])} +} + +func (m PUTROOTFH4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m PUTROOTFH4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// READ4args — fixed 28 bytes: +// stateid4(16) + offset(8, beu64) + count(4, beu32) +// ------------------------------------------------------- + +type READ4args struct { + m *[rEAD4argsSize]byte +} + +const rEAD4argsSize = 28 + +func readREAD4args(b *[]byte) (READ4args, bool) { + if len(*b) < rEAD4argsSize { + return READ4args{}, false + } + result := READ4args{m: (*[rEAD4argsSize]byte)(*b)} + *b = (*b)[rEAD4argsSize:] + return result, true +} + +func ReadREAD4args(b []byte) (READ4args, bool) { + return readREAD4args(&b) +} + +func StartREAD4args(buf []byte) ([]byte, READ4args) { + buf = append(buf, make([]byte, rEAD4argsSize)...) + return buf, READ4args{m: (*[rEAD4argsSize]byte)(buf[len(buf)-rEAD4argsSize:])} +} + +func (m READ4args) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m.m[0:16])} +} + +func (m READ4args) Offset() uint64 { + return binary.BigEndian.Uint64(m.m[16:24]) +} + +func (m READ4args) Count() uint32 { + return binary.BigEndian.Uint32(m.m[24:28]) +} + +func (m READ4args) SetOffset(v uint64) { + binary.BigEndian.PutUint64(m.m[16:24], v) +} + +func (m READ4args) SetCount(v uint32) { + binary.BigEndian.PutUint32(m.m[24:28], v) +} + +// ------------------------------------------------------- +// READ4resok — variable: eof(4) + data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type READ4resok []byte + +func readREAD4resok(b *[]byte) (READ4resok, bool) { + if len(*b) < 8 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[4:8])) + padded := (n + 3) &^ 3 + total := 8 + padded + if len(*b) < total { + return nil, false + } + result := READ4resok((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadREAD4resok(b []byte) (READ4resok, bool) { + if len(b) < 8 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[4:8])) + padded := (count + 3) &^ 3 + total := 8 + padded + if len(b) < total { + return nil, false + } + return READ4resok(b[:total]), true +} + +func (m READ4resok) Eof() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m READ4resok) DataLen() uint32 { + return binary.BigEndian.Uint32(m[4:8]) +} + +func (m READ4resok) Data() []byte { + n := int(binary.BigEndian.Uint32(m[4:8])) + return m[8 : 8+n] +} + +// READ4resokWriter writes a READ4resok: +// +// eof + data_len + u8 data[data_len] + align(4) +type READ4resokWriter struct { + buf []byte + off int + dataLen uint32 +} + +func StartREAD4resok(buf []byte) READ4resokWriter { + off := len(buf) + buf = append(buf, make([]byte, 8)...) // eof(4) + data_len(4) + return READ4resokWriter{buf: buf, off: off} +} + +func (w READ4resokWriter) SetEof(v uint32) READ4resokWriter { + binary.BigEndian.PutUint32((*[8]byte)(w.buf[w.off:])[0:4], v) + return w +} + +func (w READ4resokWriter) SetData(data []byte) READ4resokWriter { + n := len(data) + padded := (n + 3) &^ 3 + w.buf = append(w.buf, make([]byte, padded)...) + copy(w.buf[len(w.buf)-padded:], data) + w.dataLen = uint32(n) + return w +} + +func (w READ4resokWriter) Finish() []byte { + binary.BigEndian.PutUint32((*[8]byte)(w.buf[w.off:])[4:8], w.dataLen) + return w.buf +} + +// ------------------------------------------------------- +// READ4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type READ4res struct { + b []byte + disc uint32 +} + +func readREAD4res(b *[]byte, nfsstat4 uint32) (READ4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readREAD4resok(b) + if !ok { + return READ4res{}, false + } + return READ4res{b: []byte(r), disc: nfsstat4}, true + default: + return READ4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m READ4res) AsREAD4resok() READ4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return READ4resok(m.b) +} + +// ------------------------------------------------------- +// READDIR4args — variable: +// cookie(8) + cookieverf(8) + dircount(4) + maxcount(4) + attr_request +// ------------------------------------------------------- + +type READDIR4args []byte + +func readREADDIR4args(b *[]byte) (READDIR4args, bool) { + if len(*b) < 24 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[24:] + if _, ok := readBitmap4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return READDIR4args(start[:total]), true +} + +func ReadREADDIR4args(b []byte) (READDIR4args, bool) { + return readREADDIR4args(&b) +} + +func (m READDIR4args) Cookie() uint64 { + return binary.BigEndian.Uint64(m[0:8]) +} + +func (m READDIR4args) Cookieverf() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m[8 : 8+verifier4Size])} +} + +func (m READDIR4args) Dircount() uint32 { + return binary.BigEndian.Uint32(m[16:20]) +} + +func (m READDIR4args) Maxcount() uint32 { + return binary.BigEndian.Uint32(m[20:24]) +} + +func (m READDIR4args) AttrRequest() Bitmap4 { + return Bitmap4(m[24:]) +} + +// READDIR4argsWriter writes a READDIR4args: +// +// cookie + cookieverf + dircount + maxcount + attr_request +type READDIR4argsWriter struct { + buf []byte + header *[24]byte +} + +func StartREADDIR4args(buf []byte) READDIR4argsWriter { + buf = append(buf, make([]byte, 24)...) // cookie(8) + cookieverf(8) + dircount(4) + maxcount(4) + return READDIR4argsWriter{buf: buf, header: (*[24]byte)(buf[len(buf)-24:])} +} + +func (w *READDIR4argsWriter) SetCookie(v uint64) { + binary.BigEndian.PutUint64(w.header[0:8], v) +} + +func (w *READDIR4argsWriter) Cookieverf() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(w.header[8:])} +} + +func (w *READDIR4argsWriter) SetDircount(v uint32) { + binary.BigEndian.PutUint32(w.header[16:20], v) +} + +func (w *READDIR4argsWriter) SetMaxcount(v uint32) { + binary.BigEndian.PutUint32(w.header[20:24], v) +} + +func (w *READDIR4argsWriter) StartAttrRequest() Bitmap4Writer { + child := StartBitmap4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *READDIR4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *READDIR4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// Entry4Opt — union on xdr_bool (external discriminant) +// ------------------------------------------------------- + +type Entry4Opt struct { + b []byte + disc uint32 +} + +func readEntry4Opt(b *[]byte, xdrBool uint32) (Entry4Opt, bool) { + switch xdrBool { + case TRUE: + r, ok := readEntry4(b) + if !ok { + return Entry4Opt{}, false + } + return Entry4Opt{b: r.data, disc: xdrBool}, true + default: + return Entry4Opt{b: (*b)[:0], disc: xdrBool}, true + } +} + +func (m Entry4Opt) AsEntry4() Entry4 { + if m.disc != TRUE { + panic("wrong union discriminant") + } + v, _ := ReadEntry4(m.b) + return v +} + +// ------------------------------------------------------- +// Entry4 — variable: +// cookie(8) + name + attrs + nextentry_present(4) + entry4_opt nextentry +// ------------------------------------------------------- + +type Entry4 struct { + data []byte + off1 int // byte offset within data where attrs starts + off2 int // byte offset within data where nextentry_present starts +} + +func readEntry4(b *[]byte) (Entry4, bool) { + if len(*b) < 8 { + return Entry4{}, false + } + start := *b + startLen := len(start) + *b = (*b)[8:] + if _, ok := readComponent4(b); !ok { + return Entry4{}, false + } + off1 := startLen - len(*b) + if _, ok := readFattr4(b); !ok { + return Entry4{}, false + } + off2 := startLen - len(*b) + if len(*b) < 4 { + return Entry4{}, false + } + c_nextentry_present := binary.BigEndian.Uint32((*b)[:4]) + *b = (*b)[4:] + if _, ok := readEntry4Opt(b, c_nextentry_present); !ok { + return Entry4{}, false + } + total := startLen - len(*b) + return Entry4{data: start[:total], off1: off1, off2: off2}, true +} + +func ReadEntry4(b []byte) (Entry4, bool) { + return readEntry4(&b) +} + +func (m Entry4) Cookie() uint64 { + return binary.BigEndian.Uint64(m.data[0:8]) +} + +func (m Entry4) Name() Component4 { + return Component4(m.data[8:m.off1]) +} + +func (m Entry4) Attrs() Fattr4 { + v, _ := ReadFattr4(m.data[m.off1:m.off2]) + return v +} + +func (m Entry4) NextentryPresent() uint32 { + o := m.off2 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m Entry4) Nextentry() Entry4Opt { + return Entry4Opt{b: m.data[m.off2+4:], disc: binary.BigEndian.Uint32(m.data[m.off2 : m.off2+4])} +} + +// Entry4Writer writes a entry4: +// +// cookie + name + attrs + nextentry_present + nextentry(nextentry_present) +type Entry4Writer struct { + buf []byte + header *[8]byte + phase uint8 +} + +func StartEntry4(buf []byte) Entry4Writer { + buf = append(buf, make([]byte, 8)...) // cookie(8) + return Entry4Writer{buf: buf, header: (*[8]byte)(buf[len(buf)-8:])} +} + +func (w *Entry4Writer) SetCookie(v uint64) { + binary.BigEndian.PutUint64(w.header[0:8], v) +} + +func (w *Entry4Writer) SetNextentry_True() Entry4Writer { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = append(w.buf, make([]byte, 4+8)...) + off := len(w.buf) - 4 - 8 + binary.BigEndian.PutUint32((*[4 + 8]byte)(w.buf[off:])[:4], TRUE) + buf := w.buf + w.buf = nil + return Entry4Writer{buf: buf, header: (*[8]byte)(buf[off+4:])} +} + +func (w *Entry4Writer) SetNextentry_Default(xdrBool uint32) { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = binary.BigEndian.AppendUint32(w.buf, xdrBool) +} + +func (w *Entry4Writer) StartName() Component4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartComponent4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *Entry4Writer) StartAttrs() Fattr4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartFattr4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *Entry4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *Entry4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// Dirlist4 — variable: entries_present(4) + entry4_opt entries + eof(4) +// ------------------------------------------------------- + +type Dirlist4 struct { + data []byte + off1 int // byte offset within data where eof starts +} + +func readDirlist4(b *[]byte) (Dirlist4, bool) { + if len(*b) < 4 { + return Dirlist4{}, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readEntry4Opt(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return Dirlist4{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return Dirlist4{}, false + } + *b = (*b)[4:] + total := startLen - len(*b) + return Dirlist4{data: start[:total], off1: off1}, true +} + +func ReadDirlist4(b []byte) (Dirlist4, bool) { + return readDirlist4(&b) +} + +func (m Dirlist4) EntriesPresent() uint32 { + return binary.BigEndian.Uint32(m.data[0:4]) +} + +func (m Dirlist4) Entries() Entry4Opt { + return Entry4Opt{b: m.data[4:m.off1], disc: binary.BigEndian.Uint32(m.data[0:4])} +} + +func (m Dirlist4) Eof() uint32 { + o := m.off1 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +// Dirlist4Writer writes a dirlist4: +// +// entries_present + entries(entries_present) + eof +type Dirlist4Writer struct { + buf []byte + header *[4]byte +} + +func StartDirlist4(buf []byte) Dirlist4Writer { + buf = append(buf, make([]byte, 4)...) // entries_present(4) + return Dirlist4Writer{buf: buf, header: (*[4]byte)(buf[len(buf)-4:])} +} + +func (w *Dirlist4Writer) SetEntriesPresent(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *Dirlist4Writer) SetEof(v uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *Dirlist4Writer) SetEntries_True() Entry4Writer { + binary.BigEndian.PutUint32(w.header[0:4], TRUE) + w.buf = append(w.buf, make([]byte, 8)...) + buf := w.buf + w.buf = nil + return Entry4Writer{buf: buf, header: (*[8]byte)(buf[len(buf)-8:])} +} + +func (w *Dirlist4Writer) SetEntries_Default(xdrBool uint32) { + binary.BigEndian.PutUint32(w.header[0:4], xdrBool) +} + +func (w *Dirlist4Writer) Resume(buf []byte) { + w.buf = buf +} + +func (w *Dirlist4Writer) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// READDIR4resok — variable: cookieverf(8) + reply +// ------------------------------------------------------- + +type READDIR4resok []byte + +func readREADDIR4resok(b *[]byte) (READDIR4resok, bool) { + if len(*b) < 8 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[8:] + if _, ok := readDirlist4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return READDIR4resok(start[:total]), true +} + +func ReadREADDIR4resok(b []byte) (READDIR4resok, bool) { + return readREADDIR4resok(&b) +} + +func (m READDIR4resok) Cookieverf() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m[0 : 0+verifier4Size])} +} + +func (m READDIR4resok) Reply() Dirlist4 { + v, _ := ReadDirlist4(m[8:]) + return v +} + +// READDIR4resokWriter writes a READDIR4resok: +// +// cookieverf + reply +type READDIR4resokWriter struct { + buf []byte + header *[8]byte +} + +func StartREADDIR4resok(buf []byte) READDIR4resokWriter { + buf = append(buf, make([]byte, 8)...) // cookieverf(8) + return READDIR4resokWriter{buf: buf, header: (*[8]byte)(buf[len(buf)-8:])} +} + +func (w *READDIR4resokWriter) Cookieverf() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(w.header[0:])} +} + +func (w *READDIR4resokWriter) StartReply() Dirlist4Writer { + child := StartDirlist4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *READDIR4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *READDIR4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// READDIR4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type READDIR4res struct { + b []byte + disc uint32 +} + +func readREADDIR4res(b *[]byte, nfsstat4 uint32) (READDIR4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readREADDIR4resok(b) + if !ok { + return READDIR4res{}, false + } + return READDIR4res{b: []byte(r), disc: nfsstat4}, true + default: + return READDIR4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m READDIR4res) AsREADDIR4resok() READDIR4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return READDIR4resok(m.b) +} + +// ------------------------------------------------------- +// READLINK4resok — variable: link +// ------------------------------------------------------- + +type READLINK4resok []byte + +func readREADLINK4resok(b *[]byte) (READLINK4resok, bool) { + start := *b + startLen := len(start) + if _, ok := readLinktext4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return READLINK4resok(start[:total]), true +} + +func ReadREADLINK4resok(b []byte) (READLINK4resok, bool) { + return readREADLINK4resok(&b) +} + +func (m READLINK4resok) Link() Linktext4 { + return Linktext4(m[0:]) +} + +// READLINK4resokWriter writes a READLINK4resok: +// +// link +type READLINK4resokWriter struct { + buf []byte + off int +} + +func StartREADLINK4resok(buf []byte) READLINK4resokWriter { + off := len(buf) + return READLINK4resokWriter{buf: buf, off: off} +} + +func (w *READLINK4resokWriter) StartLink() Linktext4Writer { + child := StartLinktext4(w.buf) + w.buf = nil + return child +} + +func (w *READLINK4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *READLINK4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// READLINK4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type READLINK4res struct { + b []byte + disc uint32 +} + +func readREADLINK4res(b *[]byte, nfsstat4 uint32) (READLINK4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readREADLINK4resok(b) + if !ok { + return READLINK4res{}, false + } + return READLINK4res{b: []byte(r), disc: nfsstat4}, true + default: + return READLINK4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m READLINK4res) AsREADLINK4resok() READLINK4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return READLINK4resok(m.b) +} + +// ------------------------------------------------------- +// REMOVE4args — variable: target +// ------------------------------------------------------- + +type REMOVE4args []byte + +func readREMOVE4args(b *[]byte) (REMOVE4args, bool) { + start := *b + startLen := len(start) + if _, ok := readComponent4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return REMOVE4args(start[:total]), true +} + +func ReadREMOVE4args(b []byte) (REMOVE4args, bool) { + return readREMOVE4args(&b) +} + +func (m REMOVE4args) Target() Component4 { + return Component4(m[0:]) +} + +// REMOVE4argsWriter writes a REMOVE4args: +// +// target +type REMOVE4argsWriter struct { + buf []byte + off int +} + +func StartREMOVE4args(buf []byte) REMOVE4argsWriter { + off := len(buf) + return REMOVE4argsWriter{buf: buf, off: off} +} + +func (w *REMOVE4argsWriter) StartTarget() Component4Writer { + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *REMOVE4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *REMOVE4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// REMOVE4resok — fixed 20 bytes: change_info4(20) +// ------------------------------------------------------- + +type REMOVE4resok struct { + m *[rEMOVE4resokSize]byte +} + +const rEMOVE4resokSize = 20 + +func readREMOVE4resok(b *[]byte) (REMOVE4resok, bool) { + if len(*b) < rEMOVE4resokSize { + return REMOVE4resok{}, false + } + result := REMOVE4resok{m: (*[rEMOVE4resokSize]byte)(*b)} + *b = (*b)[rEMOVE4resokSize:] + return result, true +} + +func ReadREMOVE4resok(b []byte) (REMOVE4resok, bool) { + return readREMOVE4resok(&b) +} + +func StartREMOVE4resok(buf []byte) ([]byte, REMOVE4resok) { + buf = append(buf, make([]byte, rEMOVE4resokSize)...) + return buf, REMOVE4resok{m: (*[rEMOVE4resokSize]byte)(buf[len(buf)-rEMOVE4resokSize:])} +} + +func (m REMOVE4resok) Cinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(m.m[0:20])} +} + +// ------------------------------------------------------- +// REMOVE4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type REMOVE4res struct { + b []byte + disc uint32 +} + +func readREMOVE4res(b *[]byte, nfsstat4 uint32) (REMOVE4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readREMOVE4resok(b) + if !ok { + return REMOVE4res{}, false + } + return REMOVE4res{b: r.m[:], disc: nfsstat4}, true + default: + return REMOVE4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m REMOVE4res) AsREMOVE4resok() REMOVE4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return REMOVE4resok{m: (*[rEMOVE4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// RENAME4args — variable: oldname + newname +// ------------------------------------------------------- + +type RENAME4args struct { + data []byte + off1 int // byte offset within data where newname starts +} + +func readRENAME4args(b *[]byte) (RENAME4args, bool) { + start := *b + startLen := len(start) + if _, ok := readComponent4(b); !ok { + return RENAME4args{}, false + } + off1 := startLen - len(*b) + if _, ok := readComponent4(b); !ok { + return RENAME4args{}, false + } + total := startLen - len(*b) + return RENAME4args{data: start[:total], off1: off1}, true +} + +func ReadRENAME4args(b []byte) (RENAME4args, bool) { + return readRENAME4args(&b) +} + +func (m RENAME4args) Oldname() Component4 { + return Component4(m.data[0:m.off1]) +} + +func (m RENAME4args) Newname() Component4 { + return Component4(m.data[m.off1:]) +} + +// RENAME4argsWriter writes a RENAME4args: +// +// oldname + newname +type RENAME4argsWriter struct { + buf []byte + off int + phase uint8 +} + +func StartRENAME4args(buf []byte) RENAME4argsWriter { + off := len(buf) + return RENAME4argsWriter{buf: buf, off: off} +} + +func (w *RENAME4argsWriter) StartOldname() Component4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *RENAME4argsWriter) StartNewname() Component4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *RENAME4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *RENAME4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// RENAME4resok — fixed 40 bytes: change_info4(20) + change_info4(20) +// ------------------------------------------------------- + +type RENAME4resok struct { + m *[rENAME4resokSize]byte +} + +const rENAME4resokSize = 40 + +func readRENAME4resok(b *[]byte) (RENAME4resok, bool) { + if len(*b) < rENAME4resokSize { + return RENAME4resok{}, false + } + result := RENAME4resok{m: (*[rENAME4resokSize]byte)(*b)} + *b = (*b)[rENAME4resokSize:] + return result, true +} + +func ReadRENAME4resok(b []byte) (RENAME4resok, bool) { + return readRENAME4resok(&b) +} + +func StartRENAME4resok(buf []byte) ([]byte, RENAME4resok) { + buf = append(buf, make([]byte, rENAME4resokSize)...) + return buf, RENAME4resok{m: (*[rENAME4resokSize]byte)(buf[len(buf)-rENAME4resokSize:])} +} + +func (m RENAME4resok) SourceCinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(m.m[0:20])} +} + +func (m RENAME4resok) TargetCinfo() ChangeInfo4 { + return ChangeInfo4{m: (*[changeInfo4Size]byte)(m.m[20:40])} +} + +// ------------------------------------------------------- +// RENAME4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type RENAME4res struct { + b []byte + disc uint32 +} + +func readRENAME4res(b *[]byte, nfsstat4 uint32) (RENAME4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readRENAME4resok(b) + if !ok { + return RENAME4res{}, false + } + return RENAME4res{b: r.m[:], disc: nfsstat4}, true + default: + return RENAME4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m RENAME4res) AsRENAME4resok() RENAME4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return RENAME4resok{m: (*[rENAME4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// RENEW4args — fixed 8 bytes: clientid(8, beu64) +// ------------------------------------------------------- + +type RENEW4args struct { + m *[rENEW4argsSize]byte +} + +const rENEW4argsSize = 8 + +func readRENEW4args(b *[]byte) (RENEW4args, bool) { + if len(*b) < rENEW4argsSize { + return RENEW4args{}, false + } + result := RENEW4args{m: (*[rENEW4argsSize]byte)(*b)} + *b = (*b)[rENEW4argsSize:] + return result, true +} + +func ReadRENEW4args(b []byte) (RENEW4args, bool) { + return readRENEW4args(&b) +} + +func StartRENEW4args(buf []byte) ([]byte, RENEW4args) { + buf = append(buf, make([]byte, rENEW4argsSize)...) + return buf, RENEW4args{m: (*[rENEW4argsSize]byte)(buf[len(buf)-rENEW4argsSize:])} +} + +func (m RENEW4args) Clientid() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m RENEW4args) SetClientid(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +// ------------------------------------------------------- +// RENEW4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type RENEW4res struct { + m *[rENEW4resSize]byte +} + +const rENEW4resSize = 4 + +func readRENEW4res(b *[]byte) (RENEW4res, bool) { + if len(*b) < rENEW4resSize { + return RENEW4res{}, false + } + result := RENEW4res{m: (*[rENEW4resSize]byte)(*b)} + *b = (*b)[rENEW4resSize:] + return result, true +} + +func ReadRENEW4res(b []byte) (RENEW4res, bool) { + return readRENEW4res(&b) +} + +func StartRENEW4res(buf []byte) ([]byte, RENEW4res) { + buf = append(buf, make([]byte, rENEW4resSize)...) + return buf, RENEW4res{m: (*[rENEW4resSize]byte)(buf[len(buf)-rENEW4resSize:])} +} + +func (m RENEW4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m RENEW4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// RESTOREFH4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type RESTOREFH4res struct { + m *[rESTOREFH4resSize]byte +} + +const rESTOREFH4resSize = 4 + +func readRESTOREFH4res(b *[]byte) (RESTOREFH4res, bool) { + if len(*b) < rESTOREFH4resSize { + return RESTOREFH4res{}, false + } + result := RESTOREFH4res{m: (*[rESTOREFH4resSize]byte)(*b)} + *b = (*b)[rESTOREFH4resSize:] + return result, true +} + +func ReadRESTOREFH4res(b []byte) (RESTOREFH4res, bool) { + return readRESTOREFH4res(&b) +} + +func StartRESTOREFH4res(buf []byte) ([]byte, RESTOREFH4res) { + buf = append(buf, make([]byte, rESTOREFH4resSize)...) + return buf, RESTOREFH4res{m: (*[rESTOREFH4resSize]byte)(buf[len(buf)-rESTOREFH4resSize:])} +} + +func (m RESTOREFH4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m RESTOREFH4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// SAVEFH4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type SAVEFH4res struct { + m *[sAVEFH4resSize]byte +} + +const sAVEFH4resSize = 4 + +func readSAVEFH4res(b *[]byte) (SAVEFH4res, bool) { + if len(*b) < sAVEFH4resSize { + return SAVEFH4res{}, false + } + result := SAVEFH4res{m: (*[sAVEFH4resSize]byte)(*b)} + *b = (*b)[sAVEFH4resSize:] + return result, true +} + +func ReadSAVEFH4res(b []byte) (SAVEFH4res, bool) { + return readSAVEFH4res(&b) +} + +func StartSAVEFH4res(buf []byte) ([]byte, SAVEFH4res) { + buf = append(buf, make([]byte, sAVEFH4resSize)...) + return buf, SAVEFH4res{m: (*[sAVEFH4resSize]byte)(buf[len(buf)-sAVEFH4resSize:])} +} + +func (m SAVEFH4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m SAVEFH4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// SECINFO4args — variable: name +// ------------------------------------------------------- + +type SECINFO4args []byte + +func readSECINFO4args(b *[]byte) (SECINFO4args, bool) { + start := *b + startLen := len(start) + if _, ok := readComponent4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return SECINFO4args(start[:total]), true +} + +func ReadSECINFO4args(b []byte) (SECINFO4args, bool) { + return readSECINFO4args(&b) +} + +func (m SECINFO4args) Name() Component4 { + return Component4(m[0:]) +} + +// SECINFO4argsWriter writes a SECINFO4args: +// +// name +type SECINFO4argsWriter struct { + buf []byte + off int +} + +func StartSECINFO4args(buf []byte) SECINFO4argsWriter { + off := len(buf) + return SECINFO4argsWriter{buf: buf, off: off} +} + +func (w *SECINFO4argsWriter) StartName() Component4Writer { + child := StartComponent4(w.buf) + w.buf = nil + return child +} + +func (w *SECINFO4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SECINFO4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// RpcsecGssInfo — variable: oid + qop(4) + service(4) +// ------------------------------------------------------- + +type RpcsecGssInfo struct { + data []byte + off1 int // byte offset within data where qop starts +} + +func readRpcsecGssInfo(b *[]byte) (RpcsecGssInfo, bool) { + start := *b + startLen := len(start) + if _, ok := readSecOid4(b); !ok { + return RpcsecGssInfo{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return RpcsecGssInfo{}, false + } + *b = (*b)[4:] + if len(*b) < 4 { + return RpcsecGssInfo{}, false + } + *b = (*b)[4:] + total := startLen - len(*b) + return RpcsecGssInfo{data: start[:total], off1: off1}, true +} + +func ReadRpcsecGssInfo(b []byte) (RpcsecGssInfo, bool) { + return readRpcsecGssInfo(&b) +} + +func (m RpcsecGssInfo) Oid() SecOid4 { + return SecOid4(m.data[0:m.off1]) +} + +func (m RpcsecGssInfo) Qop() uint32 { + o := m.off1 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m RpcsecGssInfo) Service() uint32 { + o := m.off1 + 4 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +// RpcsecGssInfoWriter writes a rpcsec_gss_info: +// +// oid + qop + service +type RpcsecGssInfoWriter struct { + buf []byte + off int +} + +func StartRpcsecGssInfo(buf []byte) RpcsecGssInfoWriter { + off := len(buf) + return RpcsecGssInfoWriter{buf: buf, off: off} +} + +func (w *RpcsecGssInfoWriter) SetQop(v uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *RpcsecGssInfoWriter) SetService(v uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *RpcsecGssInfoWriter) StartOid() SecOid4Writer { + child := StartSecOid4(w.buf) + w.buf = nil + return child +} + +func (w *RpcsecGssInfoWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *RpcsecGssInfoWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// Secinfo4 — union on disc_secinfo4 (external discriminant) +// ------------------------------------------------------- + +type Secinfo4 struct { + b []byte + disc uint32 +} + +func readSecinfo4(b *[]byte, discSecinfo4 uint32) (Secinfo4, bool) { + switch discSecinfo4 { + case RPCSEC_GSS: + r, ok := readRpcsecGssInfo(b) + if !ok { + return Secinfo4{}, false + } + return Secinfo4{b: r.data, disc: discSecinfo4}, true + default: + return Secinfo4{b: (*b)[:0], disc: discSecinfo4}, true + } +} + +func (m Secinfo4) AsRpcsecGssInfo() RpcsecGssInfo { + if m.disc != RPCSEC_GSS { + panic("wrong union discriminant") + } + v, _ := ReadRpcsecGssInfo(m.b) + return v +} + +// ------------------------------------------------------- +// Secinfo4Entry — variable: disc(4) + secinfo4 value +// ------------------------------------------------------- + +type Secinfo4Entry []byte + +func readSecinfo4Entry(b *[]byte) (Secinfo4Entry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readSecinfo4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return Secinfo4Entry(start[:total]), true +} + +func ReadSecinfo4Entry(b []byte) (Secinfo4Entry, bool) { + return readSecinfo4Entry(&b) +} + +func (m Secinfo4Entry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m Secinfo4Entry) Value() Secinfo4 { + return Secinfo4{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// Secinfo4EntryWriter writes a secinfo4_entry: +// +// disc + value(disc) +type Secinfo4EntryWriter struct { + buf []byte + off int +} + +func StartSecinfo4Entry(buf []byte) Secinfo4EntryWriter { + return Secinfo4EntryWriter{buf: buf, off: len(buf)} +} + +func (w *Secinfo4EntryWriter) SetValue_Gss() RpcsecGssInfoWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, RPCSEC_GSS) + child := StartRpcsecGssInfo(w.buf) + w.buf = nil + return child +} + +func (w *Secinfo4EntryWriter) SetValue_Default(discSecinfo4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, discSecinfo4) +} + +func (w *Secinfo4EntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *Secinfo4EntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// SECINFO4resok — variable: count(4) + secinfo4_entry data[count] +// ------------------------------------------------------- + +type SECINFO4resok []byte + +func readSECINFO4resok(b *[]byte) (SECINFO4resok, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + c_count := int(binary.BigEndian.Uint32((*b)[0:4])) + *b = (*b)[4:] + for i := 0; i < c_count; i++ { + if _, ok := readSecinfo4Entry(b); !ok { + return nil, false + } + } + total := startLen - len(*b) + return SECINFO4resok(start[:total]), true +} + +func ReadSECINFO4resok(b []byte) (SECINFO4resok, bool) { + if len(b) < 4 { + return nil, false + } + return SECINFO4resok(b), true +} + +func (m SECINFO4resok) Count() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m SECINFO4resok) Data() Secinfo4EntryIter { + count := int(binary.BigEndian.Uint32(m[0:4])) + return Secinfo4EntryIter{ + b: []byte(m[4:]), + count: count, + } +} + +// Secinfo4EntryIter iterates over variable-size Secinfo4Entry entries. +type Secinfo4EntryIter struct { + b []byte + count int + i int + cur Secinfo4Entry +} + +func (it *Secinfo4EntryIter) Next() bool { + if it.i >= it.count { + return false + } + var ok bool + it.cur, ok = readSecinfo4Entry(&it.b) + if !ok { + return false + } + it.i++ + return true +} + +func (it *Secinfo4EntryIter) Data() Secinfo4Entry { + return it.cur +} + +// SECINFO4resokWriter writes a SECINFO4resok: +// +// count + secinfo4_entry data[count] +type SECINFO4resokWriter struct { + buf []byte + off int + count uint32 +} + +func StartSECINFO4resok(buf []byte) SECINFO4resokWriter { + off := len(buf) + buf = binary.BigEndian.AppendUint32(buf, 0) // count placeholder + return SECINFO4resokWriter{buf: buf, off: off} +} + +func (w *SECINFO4resokWriter) AppendData_Gss() RpcsecGssInfoWriter { + _ = w.buf[:1] + w.buf = binary.BigEndian.AppendUint32(w.buf, RPCSEC_GSS) + w.count++ + child := StartRpcsecGssInfo(w.buf) + w.buf = nil + return child +} + +func (w *SECINFO4resokWriter) AppendData_Default(discSecinfo4 uint32) { + _ = w.buf[:1] + w.buf = binary.BigEndian.AppendUint32(w.buf, discSecinfo4) + w.count++ +} + +func (w *SECINFO4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SECINFO4resokWriter) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], w.count) + return w.buf +} + +// ------------------------------------------------------- +// SECINFO4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type SECINFO4res struct { + b []byte + disc uint32 +} + +func readSECINFO4res(b *[]byte, nfsstat4 uint32) (SECINFO4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readSECINFO4resok(b) + if !ok { + return SECINFO4res{}, false + } + return SECINFO4res{b: []byte(r), disc: nfsstat4}, true + default: + return SECINFO4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m SECINFO4res) AsSECINFO4resok() SECINFO4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return SECINFO4resok(m.b) +} + +// ------------------------------------------------------- +// SETATTR4args — variable: stateid(16) + obj_attributes +// ------------------------------------------------------- + +type SETATTR4args []byte + +func readSETATTR4args(b *[]byte) (SETATTR4args, bool) { + if len(*b) < 16 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[16:] + if _, ok := readFattr4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return SETATTR4args(start[:total]), true +} + +func ReadSETATTR4args(b []byte) (SETATTR4args, bool) { + return readSETATTR4args(&b) +} + +func (m SETATTR4args) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m[0 : 0+stateid4Size])} +} + +func (m SETATTR4args) ObjAttributes() Fattr4 { + v, _ := ReadFattr4(m[16:]) + return v +} + +// SETATTR4argsWriter writes a SETATTR4args: +// +// stateid + obj_attributes +type SETATTR4argsWriter struct { + buf []byte + header *[16]byte +} + +func StartSETATTR4args(buf []byte) SETATTR4argsWriter { + buf = append(buf, make([]byte, 16)...) // stateid(16) + return SETATTR4argsWriter{buf: buf, header: (*[16]byte)(buf[len(buf)-16:])} +} + +func (w *SETATTR4argsWriter) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[0:])} +} + +func (w *SETATTR4argsWriter) StartObjAttributes() Fattr4Writer { + child := StartFattr4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *SETATTR4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SETATTR4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// SETATTR4res — variable: status(4) + attrsset +// ------------------------------------------------------- + +type SETATTR4res []byte + +func readSETATTR4res(b *[]byte) (SETATTR4res, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readBitmap4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return SETATTR4res(start[:total]), true +} + +func ReadSETATTR4res(b []byte) (SETATTR4res, bool) { + return readSETATTR4res(&b) +} + +func (m SETATTR4res) Status() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m SETATTR4res) Attrsset() Bitmap4 { + return Bitmap4(m[4:]) +} + +// SETATTR4resWriter writes a SETATTR4res: +// +// status + attrsset +type SETATTR4resWriter struct { + buf []byte + header *[4]byte +} + +func StartSETATTR4res(buf []byte) SETATTR4resWriter { + buf = append(buf, make([]byte, 4)...) // status(4) + return SETATTR4resWriter{buf: buf, header: (*[4]byte)(buf[len(buf)-4:])} +} + +func (w *SETATTR4resWriter) SetStatus(v uint32) { + binary.BigEndian.PutUint32(w.header[0:4], v) +} + +func (w *SETATTR4resWriter) StartAttrsset() Bitmap4Writer { + child := StartBitmap4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *SETATTR4resWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SETATTR4resWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// SETCLIENTID4args — variable: client + callback + callback_ident(4) +// ------------------------------------------------------- + +type SETCLIENTID4args struct { + data []byte + off1 int // byte offset within data where callback starts + off2 int // byte offset within data where callback_ident starts +} + +func readSETCLIENTID4args(b *[]byte) (SETCLIENTID4args, bool) { + start := *b + startLen := len(start) + if _, ok := readNfsClientId4(b); !ok { + return SETCLIENTID4args{}, false + } + off1 := startLen - len(*b) + if _, ok := readCbClient4(b); !ok { + return SETCLIENTID4args{}, false + } + off2 := startLen - len(*b) + if len(*b) < 4 { + return SETCLIENTID4args{}, false + } + *b = (*b)[4:] + total := startLen - len(*b) + return SETCLIENTID4args{data: start[:total], off1: off1, off2: off2}, true +} + +func ReadSETCLIENTID4args(b []byte) (SETCLIENTID4args, bool) { + return readSETCLIENTID4args(&b) +} + +func (m SETCLIENTID4args) Client() NfsClientId4 { + return NfsClientId4(m.data[0:m.off1]) +} + +func (m SETCLIENTID4args) Callback() CbClient4 { + return CbClient4(m.data[m.off1:m.off2]) +} + +func (m SETCLIENTID4args) CallbackIdent() uint32 { + o := m.off2 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +// SETCLIENTID4argsWriter writes a SETCLIENTID4args: +// +// client + callback + callback_ident +type SETCLIENTID4argsWriter struct { + buf []byte + off int + phase uint8 +} + +func StartSETCLIENTID4args(buf []byte) SETCLIENTID4argsWriter { + off := len(buf) + return SETCLIENTID4argsWriter{buf: buf, off: off} +} + +func (w *SETCLIENTID4argsWriter) SetCallbackIdent(v uint32) { + if w.phase > 2 { + panic("writer fields called out of order") + } + w.phase = 2 + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *SETCLIENTID4argsWriter) StartClient() NfsClientId4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartNfsClientId4(w.buf) + w.buf = nil + return child +} + +func (w *SETCLIENTID4argsWriter) StartCallback() CbClient4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartCbClient4(w.buf) + w.buf = nil + return child +} + +func (w *SETCLIENTID4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SETCLIENTID4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// SETCLIENTID4resok — fixed 16 bytes: clientid(8, beu64) + verifier4(8) +// ------------------------------------------------------- + +type SETCLIENTID4resok struct { + m *[sETCLIENTID4resokSize]byte +} + +const sETCLIENTID4resokSize = 16 + +func readSETCLIENTID4resok(b *[]byte) (SETCLIENTID4resok, bool) { + if len(*b) < sETCLIENTID4resokSize { + return SETCLIENTID4resok{}, false + } + result := SETCLIENTID4resok{m: (*[sETCLIENTID4resokSize]byte)(*b)} + *b = (*b)[sETCLIENTID4resokSize:] + return result, true +} + +func ReadSETCLIENTID4resok(b []byte) (SETCLIENTID4resok, bool) { + return readSETCLIENTID4resok(&b) +} + +func StartSETCLIENTID4resok(buf []byte) ([]byte, SETCLIENTID4resok) { + buf = append(buf, make([]byte, sETCLIENTID4resokSize)...) + return buf, SETCLIENTID4resok{m: (*[sETCLIENTID4resokSize]byte)(buf[len(buf)-sETCLIENTID4resokSize:])} +} + +func (m SETCLIENTID4resok) Clientid() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m SETCLIENTID4resok) SetclientidConfirm() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m.m[8:16])} +} + +func (m SETCLIENTID4resok) SetClientid(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +// ------------------------------------------------------- +// SETCLIENTID4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type SETCLIENTID4res struct { + b []byte + disc uint32 +} + +func readSETCLIENTID4res(b *[]byte, nfsstat4 uint32) (SETCLIENTID4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readSETCLIENTID4resok(b) + if !ok { + return SETCLIENTID4res{}, false + } + return SETCLIENTID4res{b: r.m[:], disc: nfsstat4}, true + case NFS4ERR_CLID_INUSE: + r, ok := readClientaddr4(b) + if !ok { + return SETCLIENTID4res{}, false + } + return SETCLIENTID4res{b: r.data, disc: nfsstat4}, true + default: + return SETCLIENTID4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m SETCLIENTID4res) AsSETCLIENTID4resok() SETCLIENTID4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return SETCLIENTID4resok{m: (*[sETCLIENTID4resokSize]byte)(m.b)} +} + +func (m SETCLIENTID4res) AsClientaddr4() Clientaddr4 { + if m.disc != NFS4ERR_CLID_INUSE { + panic("wrong union discriminant") + } + v, _ := ReadClientaddr4(m.b) + return v +} + +// ------------------------------------------------------- +// SETCLIENTIDCONFIRM4args — fixed 16 bytes: clientid(8, beu64) + verifier4(8) +// ------------------------------------------------------- + +type SETCLIENTIDCONFIRM4args struct { + m *[sETCLIENTIDCONFIRM4argsSize]byte +} + +const sETCLIENTIDCONFIRM4argsSize = 16 + +func readSETCLIENTIDCONFIRM4args(b *[]byte) (SETCLIENTIDCONFIRM4args, bool) { + if len(*b) < sETCLIENTIDCONFIRM4argsSize { + return SETCLIENTIDCONFIRM4args{}, false + } + result := SETCLIENTIDCONFIRM4args{m: (*[sETCLIENTIDCONFIRM4argsSize]byte)(*b)} + *b = (*b)[sETCLIENTIDCONFIRM4argsSize:] + return result, true +} + +func ReadSETCLIENTIDCONFIRM4args(b []byte) (SETCLIENTIDCONFIRM4args, bool) { + return readSETCLIENTIDCONFIRM4args(&b) +} + +func StartSETCLIENTIDCONFIRM4args(buf []byte) ([]byte, SETCLIENTIDCONFIRM4args) { + buf = append(buf, make([]byte, sETCLIENTIDCONFIRM4argsSize)...) + return buf, SETCLIENTIDCONFIRM4args{m: (*[sETCLIENTIDCONFIRM4argsSize]byte)(buf[len(buf)-sETCLIENTIDCONFIRM4argsSize:])} +} + +func (m SETCLIENTIDCONFIRM4args) Clientid() uint64 { + return binary.BigEndian.Uint64(m.m[0:8]) +} + +func (m SETCLIENTIDCONFIRM4args) SetclientidConfirm() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m.m[8:16])} +} + +func (m SETCLIENTIDCONFIRM4args) SetClientid(v uint64) { + binary.BigEndian.PutUint64(m.m[0:8], v) +} + +// ------------------------------------------------------- +// SETCLIENTIDCONFIRM4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type SETCLIENTIDCONFIRM4res struct { + m *[sETCLIENTIDCONFIRM4resSize]byte +} + +const sETCLIENTIDCONFIRM4resSize = 4 + +func readSETCLIENTIDCONFIRM4res(b *[]byte) (SETCLIENTIDCONFIRM4res, bool) { + if len(*b) < sETCLIENTIDCONFIRM4resSize { + return SETCLIENTIDCONFIRM4res{}, false + } + result := SETCLIENTIDCONFIRM4res{m: (*[sETCLIENTIDCONFIRM4resSize]byte)(*b)} + *b = (*b)[sETCLIENTIDCONFIRM4resSize:] + return result, true +} + +func ReadSETCLIENTIDCONFIRM4res(b []byte) (SETCLIENTIDCONFIRM4res, bool) { + return readSETCLIENTIDCONFIRM4res(&b) +} + +func StartSETCLIENTIDCONFIRM4res(buf []byte) ([]byte, SETCLIENTIDCONFIRM4res) { + buf = append(buf, make([]byte, sETCLIENTIDCONFIRM4resSize)...) + return buf, SETCLIENTIDCONFIRM4res{m: (*[sETCLIENTIDCONFIRM4resSize]byte)(buf[len(buf)-sETCLIENTIDCONFIRM4resSize:])} +} + +func (m SETCLIENTIDCONFIRM4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m SETCLIENTIDCONFIRM4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// VERIFY4args — variable: obj_attributes +// ------------------------------------------------------- + +type VERIFY4args []byte + +func readVERIFY4args(b *[]byte) (VERIFY4args, bool) { + start := *b + startLen := len(start) + if _, ok := readFattr4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return VERIFY4args(start[:total]), true +} + +func ReadVERIFY4args(b []byte) (VERIFY4args, bool) { + return readVERIFY4args(&b) +} + +func (m VERIFY4args) ObjAttributes() Fattr4 { + v, _ := ReadFattr4(m[0:]) + return v +} + +// VERIFY4argsWriter writes a VERIFY4args: +// +// obj_attributes +type VERIFY4argsWriter struct { + buf []byte + off int +} + +func StartVERIFY4args(buf []byte) VERIFY4argsWriter { + off := len(buf) + return VERIFY4argsWriter{buf: buf, off: off} +} + +func (w *VERIFY4argsWriter) StartObjAttributes() Fattr4Writer { + child := StartFattr4(w.buf) + w.buf = nil + return child +} + +func (w *VERIFY4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *VERIFY4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// VERIFY4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type VERIFY4res struct { + m *[vERIFY4resSize]byte +} + +const vERIFY4resSize = 4 + +func readVERIFY4res(b *[]byte) (VERIFY4res, bool) { + if len(*b) < vERIFY4resSize { + return VERIFY4res{}, false + } + result := VERIFY4res{m: (*[vERIFY4resSize]byte)(*b)} + *b = (*b)[vERIFY4resSize:] + return result, true +} + +func ReadVERIFY4res(b []byte) (VERIFY4res, bool) { + return readVERIFY4res(&b) +} + +func StartVERIFY4res(buf []byte) ([]byte, VERIFY4res) { + buf = append(buf, make([]byte, vERIFY4resSize)...) + return buf, VERIFY4res{m: (*[vERIFY4resSize]byte)(buf[len(buf)-vERIFY4resSize:])} +} + +func (m VERIFY4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m VERIFY4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// WRITE4args — variable: +// stateid(16) + offset(8) + stable(4) + data_len(4) + u8 data[data_len] + align(4) +// ------------------------------------------------------- + +type WRITE4args []byte + +func readWRITE4args(b *[]byte) (WRITE4args, bool) { + if len(*b) < 32 { + return nil, false + } + n := int(binary.BigEndian.Uint32((*b)[28:32])) + padded := (n + 3) &^ 3 + total := 32 + padded + if len(*b) < total { + return nil, false + } + result := WRITE4args((*b)[:total]) + *b = (*b)[total:] + return result, true +} + +func ReadWRITE4args(b []byte) (WRITE4args, bool) { + if len(b) < 32 { + return nil, false + } + count := int(binary.BigEndian.Uint32(b[28:32])) + padded := (count + 3) &^ 3 + total := 32 + padded + if len(b) < total { + return nil, false + } + return WRITE4args(b[:total]), true +} + +func (m WRITE4args) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m[0 : 0+stateid4Size])} +} + +func (m WRITE4args) Offset() uint64 { + return binary.BigEndian.Uint64(m[16:24]) +} + +func (m WRITE4args) Stable() uint32 { + return binary.BigEndian.Uint32(m[24:28]) +} + +func (m WRITE4args) DataLen() uint32 { + return binary.BigEndian.Uint32(m[28:32]) +} + +func (m WRITE4args) Data() []byte { + n := int(binary.BigEndian.Uint32(m[28:32])) + return m[32 : 32+n] +} + +// WRITE4argsWriter writes a WRITE4args: +// +// stateid + offset + stable + data_len + u8 data[data_len] + align(4) +type WRITE4argsWriter struct { + buf []byte + off int + dataLen uint32 +} + +func StartWRITE4args(buf []byte) WRITE4argsWriter { + off := len(buf) + buf = append(buf, make([]byte, 32)...) // stateid(16) + offset(8) + stable(4) + data_len(4) + return WRITE4argsWriter{buf: buf, off: off} +} + +func (w *WRITE4argsWriter) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.buf[w.off:])} +} + +func (w WRITE4argsWriter) SetOffset(v uint64) WRITE4argsWriter { + binary.BigEndian.PutUint64((*[32]byte)(w.buf[w.off:])[16:24], v) + return w +} + +func (w WRITE4argsWriter) SetStable(v uint32) WRITE4argsWriter { + binary.BigEndian.PutUint32((*[32]byte)(w.buf[w.off:])[24:28], v) + return w +} + +func (w WRITE4argsWriter) SetData(data []byte) WRITE4argsWriter { + n := len(data) + padded := (n + 3) &^ 3 + w.buf = append(w.buf, make([]byte, padded)...) + copy(w.buf[len(w.buf)-padded:], data) + w.dataLen = uint32(n) + return w +} + +func (w WRITE4argsWriter) Finish() []byte { + binary.BigEndian.PutUint32((*[32]byte)(w.buf[w.off:])[28:32], w.dataLen) + return w.buf +} + +// ------------------------------------------------------- +// WRITE4resok — fixed 16 bytes: +// count(4, beu32) + committed(4, beu32) + verifier4(8) +// ------------------------------------------------------- + +type WRITE4resok struct { + m *[wRITE4resokSize]byte +} + +const wRITE4resokSize = 16 + +func readWRITE4resok(b *[]byte) (WRITE4resok, bool) { + if len(*b) < wRITE4resokSize { + return WRITE4resok{}, false + } + result := WRITE4resok{m: (*[wRITE4resokSize]byte)(*b)} + *b = (*b)[wRITE4resokSize:] + return result, true +} + +func ReadWRITE4resok(b []byte) (WRITE4resok, bool) { + return readWRITE4resok(&b) +} + +func StartWRITE4resok(buf []byte) ([]byte, WRITE4resok) { + buf = append(buf, make([]byte, wRITE4resokSize)...) + return buf, WRITE4resok{m: (*[wRITE4resokSize]byte)(buf[len(buf)-wRITE4resokSize:])} +} + +func (m WRITE4resok) Count() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m WRITE4resok) Committed() uint32 { + return binary.BigEndian.Uint32(m.m[4:8]) +} + +func (m WRITE4resok) Writeverf() Verifier4 { + return Verifier4{m: (*[verifier4Size]byte)(m.m[8:16])} +} + +func (m WRITE4resok) SetCount(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +func (m WRITE4resok) SetCommitted(v uint32) { + binary.BigEndian.PutUint32(m.m[4:8], v) +} + +// ------------------------------------------------------- +// WRITE4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type WRITE4res struct { + b []byte + disc uint32 +} + +func readWRITE4res(b *[]byte, nfsstat4 uint32) (WRITE4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readWRITE4resok(b) + if !ok { + return WRITE4res{}, false + } + return WRITE4res{b: r.m[:], disc: nfsstat4}, true + default: + return WRITE4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m WRITE4res) AsWRITE4resok() WRITE4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return WRITE4resok{m: (*[wRITE4resokSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// RELEASELOCKOWNER4args — variable: lock_owner +// ------------------------------------------------------- + +type RELEASELOCKOWNER4args []byte + +func readRELEASELOCKOWNER4args(b *[]byte) (RELEASELOCKOWNER4args, bool) { + start := *b + startLen := len(start) + if _, ok := readLockOwner4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return RELEASELOCKOWNER4args(start[:total]), true +} + +func ReadRELEASELOCKOWNER4args(b []byte) (RELEASELOCKOWNER4args, bool) { + return readRELEASELOCKOWNER4args(&b) +} + +func (m RELEASELOCKOWNER4args) LockOwner() LockOwner4 { + return LockOwner4(m[0:]) +} + +// RELEASELOCKOWNER4argsWriter writes a RELEASE_LOCKOWNER4args: +// +// lock_owner +type RELEASELOCKOWNER4argsWriter struct { + buf []byte + off int +} + +func StartRELEASELOCKOWNER4args(buf []byte) RELEASELOCKOWNER4argsWriter { + off := len(buf) + return RELEASELOCKOWNER4argsWriter{buf: buf, off: off} +} + +func (w *RELEASELOCKOWNER4argsWriter) StartLockOwner() LockOwner4Writer { + child := StartLockOwner4(w.buf) + w.buf = nil + return child +} + +func (w *RELEASELOCKOWNER4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *RELEASELOCKOWNER4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// RELEASELOCKOWNER4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type RELEASELOCKOWNER4res struct { + m *[rELEASELOCKOWNER4resSize]byte +} + +const rELEASELOCKOWNER4resSize = 4 + +func readRELEASELOCKOWNER4res(b *[]byte) (RELEASELOCKOWNER4res, bool) { + if len(*b) < rELEASELOCKOWNER4resSize { + return RELEASELOCKOWNER4res{}, false + } + result := RELEASELOCKOWNER4res{m: (*[rELEASELOCKOWNER4resSize]byte)(*b)} + *b = (*b)[rELEASELOCKOWNER4resSize:] + return result, true +} + +func ReadRELEASELOCKOWNER4res(b []byte) (RELEASELOCKOWNER4res, bool) { + return readRELEASELOCKOWNER4res(&b) +} + +func StartRELEASELOCKOWNER4res(buf []byte) ([]byte, RELEASELOCKOWNER4res) { + buf = append(buf, make([]byte, rELEASELOCKOWNER4resSize)...) + return buf, RELEASELOCKOWNER4res{m: (*[rELEASELOCKOWNER4resSize]byte)(buf[len(buf)-rELEASELOCKOWNER4resSize:])} +} + +func (m RELEASELOCKOWNER4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m RELEASELOCKOWNER4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// ILLEGAL4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type ILLEGAL4res struct { + m *[iLLEGAL4resSize]byte +} + +const iLLEGAL4resSize = 4 + +func readILLEGAL4res(b *[]byte) (ILLEGAL4res, bool) { + if len(*b) < iLLEGAL4resSize { + return ILLEGAL4res{}, false + } + result := ILLEGAL4res{m: (*[iLLEGAL4resSize]byte)(*b)} + *b = (*b)[iLLEGAL4resSize:] + return result, true +} + +func ReadILLEGAL4res(b []byte) (ILLEGAL4res, bool) { + return readILLEGAL4res(&b) +} + +func StartILLEGAL4res(buf []byte) ([]byte, ILLEGAL4res) { + buf = append(buf, make([]byte, iLLEGAL4resSize)...) + return buf, ILLEGAL4res{m: (*[iLLEGAL4resSize]byte)(buf[len(buf)-iLLEGAL4resSize:])} +} + +func (m ILLEGAL4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m ILLEGAL4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// NfsArgop4 — union on nfs_opnum4 (external discriminant) +// ------------------------------------------------------- + +type NfsArgop4 struct { + b []byte + disc uint32 +} + +func readNfsArgop4(b *[]byte, nfsOpnum4 uint32) (NfsArgop4, bool) { + switch nfsOpnum4 { + case OP_ACCESS: + r, ok := readACCESS4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_CLOSE: + r, ok := readCLOSE4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_COMMIT: + r, ok := readCOMMIT4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_CREATE: + r, ok := readCREATE4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.data, disc: nfsOpnum4}, true + case OP_DELEGPURGE: + r, ok := readDELEGPURGE4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_DELEGRETURN: + r, ok := readDELEGRETURN4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_GETATTR: + r, ok := readGETATTR4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_GETFH: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_LINK: + r, ok := readLINK4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOCK: + r, ok := readLOCK4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOCKT: + r, ok := readLOCKT4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOCKU: + r, ok := readLOCKU4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_LOOKUP: + r, ok := readLOOKUP4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOOKUPP: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_NVERIFY: + r, ok := readNVERIFY4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_OPEN: + r, ok := readOPEN4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.data, disc: nfsOpnum4}, true + case OP_OPENATTR: + r, ok := readOPENATTR4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_OPEN_CONFIRM: + r, ok := readOPENCONFIRM4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_OPEN_DOWNGRADE: + r, ok := readOPENDOWNGRADE4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_PUTFH: + r, ok := readPUTFH4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_PUTPUBFH: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_PUTROOTFH: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_READ: + r, ok := readREAD4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_READDIR: + r, ok := readREADDIR4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_READLINK: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_REMOVE: + r, ok := readREMOVE4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_RENAME: + r, ok := readRENAME4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.data, disc: nfsOpnum4}, true + case OP_RENEW: + r, ok := readRENEW4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_RESTOREFH: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_SAVEFH: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + case OP_SECINFO: + r, ok := readSECINFO4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_SETATTR: + r, ok := readSETATTR4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_SETCLIENTID: + r, ok := readSETCLIENTID4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.data, disc: nfsOpnum4}, true + case OP_SETCLIENTID_CONFIRM: + r, ok := readSETCLIENTIDCONFIRM4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_VERIFY: + r, ok := readVERIFY4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_WRITE: + r, ok := readWRITE4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_RELEASE_LOCKOWNER: + r, ok := readRELEASELOCKOWNER4args(b) + if !ok { + return NfsArgop4{}, false + } + return NfsArgop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_ILLEGAL: + return NfsArgop4{b: (*b)[:0], disc: nfsOpnum4}, true + default: + return NfsArgop4{}, false + } +} + +func (m NfsArgop4) AsACCESS4args() ACCESS4args { + if m.disc != OP_ACCESS { + panic("wrong union discriminant") + } + return ACCESS4args{m: (*[aCCESS4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsCLOSE4args() CLOSE4args { + if m.disc != OP_CLOSE { + panic("wrong union discriminant") + } + return CLOSE4args{m: (*[cLOSE4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsCOMMIT4args() COMMIT4args { + if m.disc != OP_COMMIT { + panic("wrong union discriminant") + } + return COMMIT4args{m: (*[cOMMIT4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsCREATE4args() CREATE4args { + if m.disc != OP_CREATE { + panic("wrong union discriminant") + } + v, _ := ReadCREATE4args(m.b) + return v +} + +func (m NfsArgop4) AsDELEGPURGE4args() DELEGPURGE4args { + if m.disc != OP_DELEGPURGE { + panic("wrong union discriminant") + } + return DELEGPURGE4args{m: (*[dELEGPURGE4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsDELEGRETURN4args() DELEGRETURN4args { + if m.disc != OP_DELEGRETURN { + panic("wrong union discriminant") + } + return DELEGRETURN4args{m: (*[dELEGRETURN4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsGETATTR4args() GETATTR4args { + if m.disc != OP_GETATTR { + panic("wrong union discriminant") + } + return GETATTR4args(m.b) +} + +func (m NfsArgop4) AsLINK4args() LINK4args { + if m.disc != OP_LINK { + panic("wrong union discriminant") + } + return LINK4args(m.b) +} + +func (m NfsArgop4) AsLOCK4args() LOCK4args { + if m.disc != OP_LOCK { + panic("wrong union discriminant") + } + return LOCK4args(m.b) +} + +func (m NfsArgop4) AsLOCKT4args() LOCKT4args { + if m.disc != OP_LOCKT { + panic("wrong union discriminant") + } + return LOCKT4args(m.b) +} + +func (m NfsArgop4) AsLOCKU4args() LOCKU4args { + if m.disc != OP_LOCKU { + panic("wrong union discriminant") + } + return LOCKU4args{m: (*[lOCKU4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsLOOKUP4args() LOOKUP4args { + if m.disc != OP_LOOKUP { + panic("wrong union discriminant") + } + return LOOKUP4args(m.b) +} + +func (m NfsArgop4) AsNVERIFY4args() NVERIFY4args { + if m.disc != OP_NVERIFY { + panic("wrong union discriminant") + } + return NVERIFY4args(m.b) +} + +func (m NfsArgop4) AsOPEN4args() OPEN4args { + if m.disc != OP_OPEN { + panic("wrong union discriminant") + } + v, _ := ReadOPEN4args(m.b) + return v +} + +func (m NfsArgop4) AsOPENATTR4args() OPENATTR4args { + if m.disc != OP_OPENATTR { + panic("wrong union discriminant") + } + return OPENATTR4args{m: (*[oPENATTR4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsOPENCONFIRM4args() OPENCONFIRM4args { + if m.disc != OP_OPEN_CONFIRM { + panic("wrong union discriminant") + } + return OPENCONFIRM4args{m: (*[oPENCONFIRM4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsOPENDOWNGRADE4args() OPENDOWNGRADE4args { + if m.disc != OP_OPEN_DOWNGRADE { + panic("wrong union discriminant") + } + return OPENDOWNGRADE4args{m: (*[oPENDOWNGRADE4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsPUTFH4args() PUTFH4args { + if m.disc != OP_PUTFH { + panic("wrong union discriminant") + } + return PUTFH4args(m.b) +} + +func (m NfsArgop4) AsREAD4args() READ4args { + if m.disc != OP_READ { + panic("wrong union discriminant") + } + return READ4args{m: (*[rEAD4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsREADDIR4args() READDIR4args { + if m.disc != OP_READDIR { + panic("wrong union discriminant") + } + return READDIR4args(m.b) +} + +func (m NfsArgop4) AsREMOVE4args() REMOVE4args { + if m.disc != OP_REMOVE { + panic("wrong union discriminant") + } + return REMOVE4args(m.b) +} + +func (m NfsArgop4) AsRENAME4args() RENAME4args { + if m.disc != OP_RENAME { + panic("wrong union discriminant") + } + v, _ := ReadRENAME4args(m.b) + return v +} + +func (m NfsArgop4) AsRENEW4args() RENEW4args { + if m.disc != OP_RENEW { + panic("wrong union discriminant") + } + return RENEW4args{m: (*[rENEW4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsSECINFO4args() SECINFO4args { + if m.disc != OP_SECINFO { + panic("wrong union discriminant") + } + return SECINFO4args(m.b) +} + +func (m NfsArgop4) AsSETATTR4args() SETATTR4args { + if m.disc != OP_SETATTR { + panic("wrong union discriminant") + } + return SETATTR4args(m.b) +} + +func (m NfsArgop4) AsSETCLIENTID4args() SETCLIENTID4args { + if m.disc != OP_SETCLIENTID { + panic("wrong union discriminant") + } + v, _ := ReadSETCLIENTID4args(m.b) + return v +} + +func (m NfsArgop4) AsSETCLIENTIDCONFIRM4args() SETCLIENTIDCONFIRM4args { + if m.disc != OP_SETCLIENTID_CONFIRM { + panic("wrong union discriminant") + } + return SETCLIENTIDCONFIRM4args{m: (*[sETCLIENTIDCONFIRM4argsSize]byte)(m.b)} +} + +func (m NfsArgop4) AsVERIFY4args() VERIFY4args { + if m.disc != OP_VERIFY { + panic("wrong union discriminant") + } + return VERIFY4args(m.b) +} + +func (m NfsArgop4) AsWRITE4args() WRITE4args { + if m.disc != OP_WRITE { + panic("wrong union discriminant") + } + return WRITE4args(m.b) +} + +func (m NfsArgop4) AsRELEASELOCKOWNER4args() RELEASELOCKOWNER4args { + if m.disc != OP_RELEASE_LOCKOWNER { + panic("wrong union discriminant") + } + return RELEASELOCKOWNER4args(m.b) +} + +// ------------------------------------------------------- +// ACCESS4resEntry — variable: disc(4) + ACCESS4res value +// ------------------------------------------------------- + +type ACCESS4resEntry []byte + +func readACCESS4resEntry(b *[]byte) (ACCESS4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readACCESS4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return ACCESS4resEntry(start[:total]), true +} + +func ReadACCESS4resEntry(b []byte) (ACCESS4resEntry, bool) { + return readACCESS4resEntry(&b) +} + +func (m ACCESS4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m ACCESS4resEntry) Value() ACCESS4res { + return ACCESS4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// ACCESS4resEntryWriter writes a ACCESS4res_entry: +// +// disc + value(disc) +type ACCESS4resEntryWriter struct { + buf []byte + off int +} + +func StartACCESS4resEntry(buf []byte) ACCESS4resEntryWriter { + return ACCESS4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *ACCESS4resEntryWriter) SetValue_Nfs4Ok() ACCESS4resok { + w.buf = append(w.buf, make([]byte, 4+aCCESS4resokSize)...) + p := (*[4 + aCCESS4resokSize]byte)(w.buf[len(w.buf)-4-aCCESS4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return ACCESS4resok{m: (*[aCCESS4resokSize]byte)(p[4:])} +} + +func (w *ACCESS4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *ACCESS4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// CLOSE4resEntry — variable: disc(4) + CLOSE4res value +// ------------------------------------------------------- + +type CLOSE4resEntry []byte + +func readCLOSE4resEntry(b *[]byte) (CLOSE4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readCLOSE4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return CLOSE4resEntry(start[:total]), true +} + +func ReadCLOSE4resEntry(b []byte) (CLOSE4resEntry, bool) { + return readCLOSE4resEntry(&b) +} + +func (m CLOSE4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m CLOSE4resEntry) Value() CLOSE4res { + return CLOSE4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// CLOSE4resEntryWriter writes a CLOSE4res_entry: +// +// disc + value(disc) +type CLOSE4resEntryWriter struct { + buf []byte + off int +} + +func StartCLOSE4resEntry(buf []byte) CLOSE4resEntryWriter { + return CLOSE4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *CLOSE4resEntryWriter) SetValue_Nfs4Ok() Stateid4 { + w.buf = append(w.buf, make([]byte, 4+stateid4Size)...) + p := (*[4 + stateid4Size]byte)(w.buf[len(w.buf)-4-stateid4Size:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return Stateid4{m: (*[stateid4Size]byte)(p[4:])} +} + +func (w *CLOSE4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *CLOSE4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// COMMIT4resEntry — variable: disc(4) + COMMIT4res value +// ------------------------------------------------------- + +type COMMIT4resEntry []byte + +func readCOMMIT4resEntry(b *[]byte) (COMMIT4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readCOMMIT4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return COMMIT4resEntry(start[:total]), true +} + +func ReadCOMMIT4resEntry(b []byte) (COMMIT4resEntry, bool) { + return readCOMMIT4resEntry(&b) +} + +func (m COMMIT4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m COMMIT4resEntry) Value() COMMIT4res { + return COMMIT4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// COMMIT4resEntryWriter writes a COMMIT4res_entry: +// +// disc + value(disc) +type COMMIT4resEntryWriter struct { + buf []byte + off int +} + +func StartCOMMIT4resEntry(buf []byte) COMMIT4resEntryWriter { + return COMMIT4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *COMMIT4resEntryWriter) SetValue_Nfs4Ok() COMMIT4resok { + w.buf = append(w.buf, make([]byte, 4+cOMMIT4resokSize)...) + p := (*[4 + cOMMIT4resokSize]byte)(w.buf[len(w.buf)-4-cOMMIT4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return COMMIT4resok{m: (*[cOMMIT4resokSize]byte)(p[4:])} +} + +func (w *COMMIT4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *COMMIT4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// CREATE4resEntry — variable: disc(4) + CREATE4res value +// ------------------------------------------------------- + +type CREATE4resEntry []byte + +func readCREATE4resEntry(b *[]byte) (CREATE4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readCREATE4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return CREATE4resEntry(start[:total]), true +} + +func ReadCREATE4resEntry(b []byte) (CREATE4resEntry, bool) { + return readCREATE4resEntry(&b) +} + +func (m CREATE4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m CREATE4resEntry) Value() CREATE4res { + return CREATE4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// CREATE4resEntryWriter writes a CREATE4res_entry: +// +// disc + value(disc) +type CREATE4resEntryWriter struct { + buf []byte + off int +} + +func StartCREATE4resEntry(buf []byte) CREATE4resEntryWriter { + return CREATE4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *CREATE4resEntryWriter) SetValue_Nfs4Ok() CREATE4resokWriter { + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], NFS4_OK) + buf := w.buf + w.buf = nil + return CREATE4resokWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *CREATE4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *CREATE4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CREATE4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// GETATTR4resEntry — variable: disc(4) + GETATTR4res value +// ------------------------------------------------------- + +type GETATTR4resEntry []byte + +func readGETATTR4resEntry(b *[]byte) (GETATTR4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readGETATTR4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return GETATTR4resEntry(start[:total]), true +} + +func ReadGETATTR4resEntry(b []byte) (GETATTR4resEntry, bool) { + return readGETATTR4resEntry(&b) +} + +func (m GETATTR4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m GETATTR4resEntry) Value() GETATTR4res { + return GETATTR4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// GETATTR4resEntryWriter writes a GETATTR4res_entry: +// +// disc + value(disc) +type GETATTR4resEntryWriter struct { + buf []byte + off int +} + +func StartGETATTR4resEntry(buf []byte) GETATTR4resEntryWriter { + return GETATTR4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *GETATTR4resEntryWriter) SetValue_Nfs4Ok() GETATTR4resokWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, NFS4_OK) + child := StartGETATTR4resok(w.buf) + w.buf = nil + return child +} + +func (w *GETATTR4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *GETATTR4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *GETATTR4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// GETFH4resEntry — variable: disc(4) + GETFH4res value +// ------------------------------------------------------- + +type GETFH4resEntry []byte + +func readGETFH4resEntry(b *[]byte) (GETFH4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readGETFH4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return GETFH4resEntry(start[:total]), true +} + +func ReadGETFH4resEntry(b []byte) (GETFH4resEntry, bool) { + return readGETFH4resEntry(&b) +} + +func (m GETFH4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m GETFH4resEntry) Value() GETFH4res { + return GETFH4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// GETFH4resEntryWriter writes a GETFH4res_entry: +// +// disc + value(disc) +type GETFH4resEntryWriter struct { + buf []byte + off int +} + +func StartGETFH4resEntry(buf []byte) GETFH4resEntryWriter { + return GETFH4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *GETFH4resEntryWriter) SetValue_Nfs4Ok() GETFH4resokWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, NFS4_OK) + child := StartGETFH4resok(w.buf) + w.buf = nil + return child +} + +func (w *GETFH4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *GETFH4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *GETFH4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LINK4resEntry — variable: disc(4) + LINK4res value +// ------------------------------------------------------- + +type LINK4resEntry []byte + +func readLINK4resEntry(b *[]byte) (LINK4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readLINK4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return LINK4resEntry(start[:total]), true +} + +func ReadLINK4resEntry(b []byte) (LINK4resEntry, bool) { + return readLINK4resEntry(&b) +} + +func (m LINK4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m LINK4resEntry) Value() LINK4res { + return LINK4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// LINK4resEntryWriter writes a LINK4res_entry: +// +// disc + value(disc) +type LINK4resEntryWriter struct { + buf []byte + off int +} + +func StartLINK4resEntry(buf []byte) LINK4resEntryWriter { + return LINK4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *LINK4resEntryWriter) SetValue_Nfs4Ok() LINK4resok { + w.buf = append(w.buf, make([]byte, 4+lINK4resokSize)...) + p := (*[4 + lINK4resokSize]byte)(w.buf[len(w.buf)-4-lINK4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return LINK4resok{m: (*[lINK4resokSize]byte)(p[4:])} +} + +func (w *LINK4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *LINK4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// LOCK4resEntry — variable: disc(4) + LOCK4res value +// ------------------------------------------------------- + +type LOCK4resEntry []byte + +func readLOCK4resEntry(b *[]byte) (LOCK4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readLOCK4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return LOCK4resEntry(start[:total]), true +} + +func ReadLOCK4resEntry(b []byte) (LOCK4resEntry, bool) { + return readLOCK4resEntry(&b) +} + +func (m LOCK4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m LOCK4resEntry) Value() LOCK4res { + return LOCK4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// LOCK4resEntryWriter writes a LOCK4res_entry: +// +// disc + value(disc) +type LOCK4resEntryWriter struct { + buf []byte + off int +} + +func StartLOCK4resEntry(buf []byte) LOCK4resEntryWriter { + return LOCK4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *LOCK4resEntryWriter) SetValue_Nfs4Ok() LOCK4resok { + w.buf = append(w.buf, make([]byte, 4+lOCK4resokSize)...) + p := (*[4 + lOCK4resokSize]byte)(w.buf[len(w.buf)-4-lOCK4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return LOCK4resok{m: (*[lOCK4resokSize]byte)(p[4:])} +} + +func (w *LOCK4resEntryWriter) SetValue_Nfs4errDenied() LOCK4deniedWriter { + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], NFS4ERR_DENIED) + buf := w.buf + w.buf = nil + return LOCK4deniedWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *LOCK4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *LOCK4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LOCK4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LOCKT4resEntry — variable: disc(4) + LOCKT4res value +// ------------------------------------------------------- + +type LOCKT4resEntry []byte + +func readLOCKT4resEntry(b *[]byte) (LOCKT4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readLOCKT4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return LOCKT4resEntry(start[:total]), true +} + +func ReadLOCKT4resEntry(b []byte) (LOCKT4resEntry, bool) { + return readLOCKT4resEntry(&b) +} + +func (m LOCKT4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m LOCKT4resEntry) Value() LOCKT4res { + return LOCKT4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// LOCKT4resEntryWriter writes a LOCKT4res_entry: +// +// disc + value(disc) +type LOCKT4resEntryWriter struct { + buf []byte + off int +} + +func StartLOCKT4resEntry(buf []byte) LOCKT4resEntryWriter { + return LOCKT4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *LOCKT4resEntryWriter) SetValue_Nfs4errDenied() LOCK4deniedWriter { + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], NFS4ERR_DENIED) + buf := w.buf + w.buf = nil + return LOCK4deniedWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *LOCKT4resEntryWriter) SetValue_Nfs4Ok() { + w.buf = binary.BigEndian.AppendUint32(w.buf, NFS4_OK) +} + +func (w *LOCKT4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *LOCKT4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *LOCKT4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// LOCKU4resEntry — variable: disc(4) + LOCKU4res value +// ------------------------------------------------------- + +type LOCKU4resEntry []byte + +func readLOCKU4resEntry(b *[]byte) (LOCKU4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readLOCKU4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return LOCKU4resEntry(start[:total]), true +} + +func ReadLOCKU4resEntry(b []byte) (LOCKU4resEntry, bool) { + return readLOCKU4resEntry(&b) +} + +func (m LOCKU4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m LOCKU4resEntry) Value() LOCKU4res { + return LOCKU4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// LOCKU4resEntryWriter writes a LOCKU4res_entry: +// +// disc + value(disc) +type LOCKU4resEntryWriter struct { + buf []byte + off int +} + +func StartLOCKU4resEntry(buf []byte) LOCKU4resEntryWriter { + return LOCKU4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *LOCKU4resEntryWriter) SetValue_Nfs4Ok() Stateid4 { + w.buf = append(w.buf, make([]byte, 4+stateid4Size)...) + p := (*[4 + stateid4Size]byte)(w.buf[len(w.buf)-4-stateid4Size:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return Stateid4{m: (*[stateid4Size]byte)(p[4:])} +} + +func (w *LOCKU4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *LOCKU4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// OPEN4resEntry — variable: disc(4) + OPEN4res value +// ------------------------------------------------------- + +type OPEN4resEntry []byte + +func readOPEN4resEntry(b *[]byte) (OPEN4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readOPEN4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return OPEN4resEntry(start[:total]), true +} + +func ReadOPEN4resEntry(b []byte) (OPEN4resEntry, bool) { + return readOPEN4resEntry(&b) +} + +func (m OPEN4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m OPEN4resEntry) Value() OPEN4res { + return OPEN4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// OPEN4resEntryWriter writes a OPEN4res_entry: +// +// disc + value(disc) +type OPEN4resEntryWriter struct { + buf []byte + off int +} + +func StartOPEN4resEntry(buf []byte) OPEN4resEntryWriter { + return OPEN4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *OPEN4resEntryWriter) SetValue_Nfs4Ok() OPEN4resokWriter { + w.buf = append(w.buf, make([]byte, 4+40)...) + off := len(w.buf) - 4 - 40 + binary.BigEndian.PutUint32((*[4 + 40]byte)(w.buf[off:])[:4], NFS4_OK) + buf := w.buf + w.buf = nil + return OPEN4resokWriter{buf: buf, header: (*[40]byte)(buf[off+4:])} +} + +func (w *OPEN4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *OPEN4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *OPEN4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// OPENCONFIRM4resEntry — variable: disc(4) + OPEN_CONFIRM4res value +// ------------------------------------------------------- + +type OPENCONFIRM4resEntry []byte + +func readOPENCONFIRM4resEntry(b *[]byte) (OPENCONFIRM4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readOPENCONFIRM4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return OPENCONFIRM4resEntry(start[:total]), true +} + +func ReadOPENCONFIRM4resEntry(b []byte) (OPENCONFIRM4resEntry, bool) { + return readOPENCONFIRM4resEntry(&b) +} + +func (m OPENCONFIRM4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m OPENCONFIRM4resEntry) Value() OPENCONFIRM4res { + return OPENCONFIRM4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// OPENCONFIRM4resEntryWriter writes a OPEN_CONFIRM4res_entry: +// +// disc + value(disc) +type OPENCONFIRM4resEntryWriter struct { + buf []byte + off int +} + +func StartOPENCONFIRM4resEntry(buf []byte) OPENCONFIRM4resEntryWriter { + return OPENCONFIRM4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *OPENCONFIRM4resEntryWriter) SetValue_Nfs4Ok() OPENCONFIRM4resok { + w.buf = append(w.buf, make([]byte, 4+oPENCONFIRM4resokSize)...) + p := (*[4 + oPENCONFIRM4resokSize]byte)(w.buf[len(w.buf)-4-oPENCONFIRM4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return OPENCONFIRM4resok{m: (*[oPENCONFIRM4resokSize]byte)(p[4:])} +} + +func (w *OPENCONFIRM4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *OPENCONFIRM4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// OPENDOWNGRADE4resEntry — variable: disc(4) + OPEN_DOWNGRADE4res value +// ------------------------------------------------------- + +type OPENDOWNGRADE4resEntry []byte + +func readOPENDOWNGRADE4resEntry(b *[]byte) (OPENDOWNGRADE4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readOPENDOWNGRADE4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return OPENDOWNGRADE4resEntry(start[:total]), true +} + +func ReadOPENDOWNGRADE4resEntry(b []byte) (OPENDOWNGRADE4resEntry, bool) { + return readOPENDOWNGRADE4resEntry(&b) +} + +func (m OPENDOWNGRADE4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m OPENDOWNGRADE4resEntry) Value() OPENDOWNGRADE4res { + return OPENDOWNGRADE4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// OPENDOWNGRADE4resEntryWriter writes a OPEN_DOWNGRADE4res_entry: +// +// disc + value(disc) +type OPENDOWNGRADE4resEntryWriter struct { + buf []byte + off int +} + +func StartOPENDOWNGRADE4resEntry(buf []byte) OPENDOWNGRADE4resEntryWriter { + return OPENDOWNGRADE4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *OPENDOWNGRADE4resEntryWriter) SetValue_Nfs4Ok() OPENDOWNGRADE4resok { + w.buf = append(w.buf, make([]byte, 4+oPENDOWNGRADE4resokSize)...) + p := (*[4 + oPENDOWNGRADE4resokSize]byte)(w.buf[len(w.buf)-4-oPENDOWNGRADE4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return OPENDOWNGRADE4resok{m: (*[oPENDOWNGRADE4resokSize]byte)(p[4:])} +} + +func (w *OPENDOWNGRADE4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *OPENDOWNGRADE4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// READ4resEntry — variable: disc(4) + READ4res value +// ------------------------------------------------------- + +type READ4resEntry []byte + +func readREAD4resEntry(b *[]byte) (READ4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readREAD4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return READ4resEntry(start[:total]), true +} + +func ReadREAD4resEntry(b []byte) (READ4resEntry, bool) { + return readREAD4resEntry(&b) +} + +func (m READ4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m READ4resEntry) Value() READ4res { + return READ4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// READ4resEntryWriter writes a READ4res_entry: +// +// disc + value(disc) +type READ4resEntryWriter struct { + buf []byte + off int +} + +func StartREAD4resEntry(buf []byte) READ4resEntryWriter { + return READ4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *READ4resEntryWriter) SetValue_Nfs4Ok() READ4resokWriter { + w.buf = append(w.buf, make([]byte, 4+8)...) + off := len(w.buf) - 4 - 8 + binary.BigEndian.PutUint32((*[4 + 8]byte)(w.buf[off:])[:4], NFS4_OK) + buf := w.buf + w.buf = nil + return READ4resokWriter{buf: buf, off: off + 4} +} + +func (w *READ4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *READ4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *READ4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// READDIR4resEntry — variable: disc(4) + READDIR4res value +// ------------------------------------------------------- + +type READDIR4resEntry []byte + +func readREADDIR4resEntry(b *[]byte) (READDIR4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readREADDIR4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return READDIR4resEntry(start[:total]), true +} + +func ReadREADDIR4resEntry(b []byte) (READDIR4resEntry, bool) { + return readREADDIR4resEntry(&b) +} + +func (m READDIR4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m READDIR4resEntry) Value() READDIR4res { + return READDIR4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// READDIR4resEntryWriter writes a READDIR4res_entry: +// +// disc + value(disc) +type READDIR4resEntryWriter struct { + buf []byte + off int +} + +func StartREADDIR4resEntry(buf []byte) READDIR4resEntryWriter { + return READDIR4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *READDIR4resEntryWriter) SetValue_Nfs4Ok() READDIR4resokWriter { + w.buf = append(w.buf, make([]byte, 4+8)...) + off := len(w.buf) - 4 - 8 + binary.BigEndian.PutUint32((*[4 + 8]byte)(w.buf[off:])[:4], NFS4_OK) + buf := w.buf + w.buf = nil + return READDIR4resokWriter{buf: buf, header: (*[8]byte)(buf[off+4:])} +} + +func (w *READDIR4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *READDIR4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *READDIR4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// READLINK4resEntry — variable: disc(4) + READLINK4res value +// ------------------------------------------------------- + +type READLINK4resEntry []byte + +func readREADLINK4resEntry(b *[]byte) (READLINK4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readREADLINK4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return READLINK4resEntry(start[:total]), true +} + +func ReadREADLINK4resEntry(b []byte) (READLINK4resEntry, bool) { + return readREADLINK4resEntry(&b) +} + +func (m READLINK4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m READLINK4resEntry) Value() READLINK4res { + return READLINK4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// READLINK4resEntryWriter writes a READLINK4res_entry: +// +// disc + value(disc) +type READLINK4resEntryWriter struct { + buf []byte + off int +} + +func StartREADLINK4resEntry(buf []byte) READLINK4resEntryWriter { + return READLINK4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *READLINK4resEntryWriter) SetValue_Nfs4Ok() READLINK4resokWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, NFS4_OK) + child := StartREADLINK4resok(w.buf) + w.buf = nil + return child +} + +func (w *READLINK4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *READLINK4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *READLINK4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// REMOVE4resEntry — variable: disc(4) + REMOVE4res value +// ------------------------------------------------------- + +type REMOVE4resEntry []byte + +func readREMOVE4resEntry(b *[]byte) (REMOVE4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readREMOVE4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return REMOVE4resEntry(start[:total]), true +} + +func ReadREMOVE4resEntry(b []byte) (REMOVE4resEntry, bool) { + return readREMOVE4resEntry(&b) +} + +func (m REMOVE4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m REMOVE4resEntry) Value() REMOVE4res { + return REMOVE4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// REMOVE4resEntryWriter writes a REMOVE4res_entry: +// +// disc + value(disc) +type REMOVE4resEntryWriter struct { + buf []byte + off int +} + +func StartREMOVE4resEntry(buf []byte) REMOVE4resEntryWriter { + return REMOVE4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *REMOVE4resEntryWriter) SetValue_Nfs4Ok() REMOVE4resok { + w.buf = append(w.buf, make([]byte, 4+rEMOVE4resokSize)...) + p := (*[4 + rEMOVE4resokSize]byte)(w.buf[len(w.buf)-4-rEMOVE4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return REMOVE4resok{m: (*[rEMOVE4resokSize]byte)(p[4:])} +} + +func (w *REMOVE4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *REMOVE4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// RENAME4resEntry — variable: disc(4) + RENAME4res value +// ------------------------------------------------------- + +type RENAME4resEntry []byte + +func readRENAME4resEntry(b *[]byte) (RENAME4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readRENAME4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return RENAME4resEntry(start[:total]), true +} + +func ReadRENAME4resEntry(b []byte) (RENAME4resEntry, bool) { + return readRENAME4resEntry(&b) +} + +func (m RENAME4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m RENAME4resEntry) Value() RENAME4res { + return RENAME4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// RENAME4resEntryWriter writes a RENAME4res_entry: +// +// disc + value(disc) +type RENAME4resEntryWriter struct { + buf []byte + off int +} + +func StartRENAME4resEntry(buf []byte) RENAME4resEntryWriter { + return RENAME4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *RENAME4resEntryWriter) SetValue_Nfs4Ok() RENAME4resok { + w.buf = append(w.buf, make([]byte, 4+rENAME4resokSize)...) + p := (*[4 + rENAME4resokSize]byte)(w.buf[len(w.buf)-4-rENAME4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return RENAME4resok{m: (*[rENAME4resokSize]byte)(p[4:])} +} + +func (w *RENAME4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *RENAME4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// SECINFO4resEntry — variable: disc(4) + SECINFO4res value +// ------------------------------------------------------- + +type SECINFO4resEntry []byte + +func readSECINFO4resEntry(b *[]byte) (SECINFO4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readSECINFO4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return SECINFO4resEntry(start[:total]), true +} + +func ReadSECINFO4resEntry(b []byte) (SECINFO4resEntry, bool) { + return readSECINFO4resEntry(&b) +} + +func (m SECINFO4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m SECINFO4resEntry) Value() SECINFO4res { + return SECINFO4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// SECINFO4resEntryWriter writes a SECINFO4res_entry: +// +// disc + value(disc) +type SECINFO4resEntryWriter struct { + buf []byte + off int +} + +func StartSECINFO4resEntry(buf []byte) SECINFO4resEntryWriter { + return SECINFO4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *SECINFO4resEntryWriter) SetValue_Nfs4Ok() SECINFO4resokWriter { + w.buf = append(w.buf, make([]byte, 4+4)...) + off := len(w.buf) - 4 - 4 + binary.BigEndian.PutUint32((*[4 + 4]byte)(w.buf[off:])[:4], NFS4_OK) + buf := w.buf + w.buf = nil + return SECINFO4resokWriter{buf: buf, off: off + 4} +} + +func (w *SECINFO4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *SECINFO4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SECINFO4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// SETCLIENTID4resEntry — variable: disc(4) + SETCLIENTID4res value +// ------------------------------------------------------- + +type SETCLIENTID4resEntry []byte + +func readSETCLIENTID4resEntry(b *[]byte) (SETCLIENTID4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readSETCLIENTID4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return SETCLIENTID4resEntry(start[:total]), true +} + +func ReadSETCLIENTID4resEntry(b []byte) (SETCLIENTID4resEntry, bool) { + return readSETCLIENTID4resEntry(&b) +} + +func (m SETCLIENTID4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m SETCLIENTID4resEntry) Value() SETCLIENTID4res { + return SETCLIENTID4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// SETCLIENTID4resEntryWriter writes a SETCLIENTID4res_entry: +// +// disc + value(disc) +type SETCLIENTID4resEntryWriter struct { + buf []byte + off int +} + +func StartSETCLIENTID4resEntry(buf []byte) SETCLIENTID4resEntryWriter { + return SETCLIENTID4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *SETCLIENTID4resEntryWriter) SetValue_Nfs4Ok() SETCLIENTID4resok { + w.buf = append(w.buf, make([]byte, 4+sETCLIENTID4resokSize)...) + p := (*[4 + sETCLIENTID4resokSize]byte)(w.buf[len(w.buf)-4-sETCLIENTID4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return SETCLIENTID4resok{m: (*[sETCLIENTID4resokSize]byte)(p[4:])} +} + +func (w *SETCLIENTID4resEntryWriter) SetValue_Nfs4errClidInuse() Clientaddr4Writer { + w.buf = binary.BigEndian.AppendUint32(w.buf, NFS4ERR_CLID_INUSE) + child := StartClientaddr4(w.buf) + w.buf = nil + return child +} + +func (w *SETCLIENTID4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *SETCLIENTID4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *SETCLIENTID4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// WRITE4resEntry — variable: disc(4) + WRITE4res value +// ------------------------------------------------------- + +type WRITE4resEntry []byte + +func readWRITE4resEntry(b *[]byte) (WRITE4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readWRITE4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return WRITE4resEntry(start[:total]), true +} + +func ReadWRITE4resEntry(b []byte) (WRITE4resEntry, bool) { + return readWRITE4resEntry(&b) +} + +func (m WRITE4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m WRITE4resEntry) Value() WRITE4res { + return WRITE4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// WRITE4resEntryWriter writes a WRITE4res_entry: +// +// disc + value(disc) +type WRITE4resEntryWriter struct { + buf []byte + off int +} + +func StartWRITE4resEntry(buf []byte) WRITE4resEntryWriter { + return WRITE4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *WRITE4resEntryWriter) SetValue_Nfs4Ok() WRITE4resok { + w.buf = append(w.buf, make([]byte, 4+wRITE4resokSize)...) + p := (*[4 + wRITE4resokSize]byte)(w.buf[len(w.buf)-4-wRITE4resokSize:]) + binary.BigEndian.PutUint32(p[:4], NFS4_OK) + return WRITE4resok{m: (*[wRITE4resokSize]byte)(p[4:])} +} + +func (w *WRITE4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *WRITE4resEntryWriter) Finish() []byte { + return w.buf +} + +// ------------------------------------------------------- +// NfsResop4 — union on nfs_opnum4 (external discriminant) +// ------------------------------------------------------- + +type NfsResop4 struct { + b []byte + disc uint32 +} + +func readNfsResop4(b *[]byte, nfsOpnum4 uint32) (NfsResop4, bool) { + switch nfsOpnum4 { + case OP_ACCESS: + r, ok := readACCESS4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_CLOSE: + r, ok := readCLOSE4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_COMMIT: + r, ok := readCOMMIT4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_CREATE: + r, ok := readCREATE4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_DELEGPURGE: + r, ok := readDELEGPURGE4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_DELEGRETURN: + r, ok := readDELEGRETURN4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_GETATTR: + r, ok := readGETATTR4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_GETFH: + r, ok := readGETFH4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LINK: + r, ok := readLINK4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOCK: + r, ok := readLOCK4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOCKT: + r, ok := readLOCKT4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOCKU: + r, ok := readLOCKU4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_LOOKUP: + r, ok := readLOOKUP4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_LOOKUPP: + r, ok := readLOOKUPP4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_NVERIFY: + r, ok := readNVERIFY4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_OPEN: + r, ok := readOPEN4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_OPENATTR: + r, ok := readOPENATTR4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_OPEN_CONFIRM: + r, ok := readOPENCONFIRM4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_OPEN_DOWNGRADE: + r, ok := readOPENDOWNGRADE4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_PUTFH: + r, ok := readPUTFH4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_PUTPUBFH: + r, ok := readPUTPUBFH4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_PUTROOTFH: + r, ok := readPUTROOTFH4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_READ: + r, ok := readREAD4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_READDIR: + r, ok := readREADDIR4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_READLINK: + r, ok := readREADLINK4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_REMOVE: + r, ok := readREMOVE4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_RENAME: + r, ok := readRENAME4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_RENEW: + r, ok := readRENEW4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_RESTOREFH: + r, ok := readRESTOREFH4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_SAVEFH: + r, ok := readSAVEFH4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_SECINFO: + r, ok := readSECINFO4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_SETATTR: + r, ok := readSETATTR4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_SETCLIENTID: + r, ok := readSETCLIENTID4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_SETCLIENTID_CONFIRM: + r, ok := readSETCLIENTIDCONFIRM4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_VERIFY: + r, ok := readVERIFY4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_WRITE: + r, ok := readWRITE4resEntry(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: []byte(r), disc: nfsOpnum4}, true + case OP_RELEASE_LOCKOWNER: + r, ok := readRELEASELOCKOWNER4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + case OP_ILLEGAL: + r, ok := readILLEGAL4res(b) + if !ok { + return NfsResop4{}, false + } + return NfsResop4{b: r.m[:], disc: nfsOpnum4}, true + default: + return NfsResop4{}, false + } +} + +func (m NfsResop4) AsACCESS4resEntry() ACCESS4resEntry { + if m.disc != OP_ACCESS { + panic("wrong union discriminant") + } + return ACCESS4resEntry(m.b) +} + +func (m NfsResop4) AsCLOSE4resEntry() CLOSE4resEntry { + if m.disc != OP_CLOSE { + panic("wrong union discriminant") + } + return CLOSE4resEntry(m.b) +} + +func (m NfsResop4) AsCOMMIT4resEntry() COMMIT4resEntry { + if m.disc != OP_COMMIT { + panic("wrong union discriminant") + } + return COMMIT4resEntry(m.b) +} + +func (m NfsResop4) AsCREATE4resEntry() CREATE4resEntry { + if m.disc != OP_CREATE { + panic("wrong union discriminant") + } + return CREATE4resEntry(m.b) +} + +func (m NfsResop4) AsDELEGPURGE4res() DELEGPURGE4res { + if m.disc != OP_DELEGPURGE { + panic("wrong union discriminant") + } + return DELEGPURGE4res{m: (*[dELEGPURGE4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsDELEGRETURN4res() DELEGRETURN4res { + if m.disc != OP_DELEGRETURN { + panic("wrong union discriminant") + } + return DELEGRETURN4res{m: (*[dELEGRETURN4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsGETATTR4resEntry() GETATTR4resEntry { + if m.disc != OP_GETATTR { + panic("wrong union discriminant") + } + return GETATTR4resEntry(m.b) +} + +func (m NfsResop4) AsGETFH4resEntry() GETFH4resEntry { + if m.disc != OP_GETFH { + panic("wrong union discriminant") + } + return GETFH4resEntry(m.b) +} + +func (m NfsResop4) AsLINK4resEntry() LINK4resEntry { + if m.disc != OP_LINK { + panic("wrong union discriminant") + } + return LINK4resEntry(m.b) +} + +func (m NfsResop4) AsLOCK4resEntry() LOCK4resEntry { + if m.disc != OP_LOCK { + panic("wrong union discriminant") + } + return LOCK4resEntry(m.b) +} + +func (m NfsResop4) AsLOCKT4resEntry() LOCKT4resEntry { + if m.disc != OP_LOCKT { + panic("wrong union discriminant") + } + return LOCKT4resEntry(m.b) +} + +func (m NfsResop4) AsLOCKU4resEntry() LOCKU4resEntry { + if m.disc != OP_LOCKU { + panic("wrong union discriminant") + } + return LOCKU4resEntry(m.b) +} + +func (m NfsResop4) AsLOOKUP4res() LOOKUP4res { + if m.disc != OP_LOOKUP { + panic("wrong union discriminant") + } + return LOOKUP4res{m: (*[lOOKUP4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsLOOKUPP4res() LOOKUPP4res { + if m.disc != OP_LOOKUPP { + panic("wrong union discriminant") + } + return LOOKUPP4res{m: (*[lOOKUPP4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsNVERIFY4res() NVERIFY4res { + if m.disc != OP_NVERIFY { + panic("wrong union discriminant") + } + return NVERIFY4res{m: (*[nVERIFY4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsOPEN4resEntry() OPEN4resEntry { + if m.disc != OP_OPEN { + panic("wrong union discriminant") + } + return OPEN4resEntry(m.b) +} + +func (m NfsResop4) AsOPENATTR4res() OPENATTR4res { + if m.disc != OP_OPENATTR { + panic("wrong union discriminant") + } + return OPENATTR4res{m: (*[oPENATTR4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsOPENCONFIRM4resEntry() OPENCONFIRM4resEntry { + if m.disc != OP_OPEN_CONFIRM { + panic("wrong union discriminant") + } + return OPENCONFIRM4resEntry(m.b) +} + +func (m NfsResop4) AsOPENDOWNGRADE4resEntry() OPENDOWNGRADE4resEntry { + if m.disc != OP_OPEN_DOWNGRADE { + panic("wrong union discriminant") + } + return OPENDOWNGRADE4resEntry(m.b) +} + +func (m NfsResop4) AsPUTFH4res() PUTFH4res { + if m.disc != OP_PUTFH { + panic("wrong union discriminant") + } + return PUTFH4res{m: (*[pUTFH4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsPUTPUBFH4res() PUTPUBFH4res { + if m.disc != OP_PUTPUBFH { + panic("wrong union discriminant") + } + return PUTPUBFH4res{m: (*[pUTPUBFH4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsPUTROOTFH4res() PUTROOTFH4res { + if m.disc != OP_PUTROOTFH { + panic("wrong union discriminant") + } + return PUTROOTFH4res{m: (*[pUTROOTFH4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsREAD4resEntry() READ4resEntry { + if m.disc != OP_READ { + panic("wrong union discriminant") + } + return READ4resEntry(m.b) +} + +func (m NfsResop4) AsREADDIR4resEntry() READDIR4resEntry { + if m.disc != OP_READDIR { + panic("wrong union discriminant") + } + return READDIR4resEntry(m.b) +} + +func (m NfsResop4) AsREADLINK4resEntry() READLINK4resEntry { + if m.disc != OP_READLINK { + panic("wrong union discriminant") + } + return READLINK4resEntry(m.b) +} + +func (m NfsResop4) AsREMOVE4resEntry() REMOVE4resEntry { + if m.disc != OP_REMOVE { + panic("wrong union discriminant") + } + return REMOVE4resEntry(m.b) +} + +func (m NfsResop4) AsRENAME4resEntry() RENAME4resEntry { + if m.disc != OP_RENAME { + panic("wrong union discriminant") + } + return RENAME4resEntry(m.b) +} + +func (m NfsResop4) AsRENEW4res() RENEW4res { + if m.disc != OP_RENEW { + panic("wrong union discriminant") + } + return RENEW4res{m: (*[rENEW4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsRESTOREFH4res() RESTOREFH4res { + if m.disc != OP_RESTOREFH { + panic("wrong union discriminant") + } + return RESTOREFH4res{m: (*[rESTOREFH4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsSAVEFH4res() SAVEFH4res { + if m.disc != OP_SAVEFH { + panic("wrong union discriminant") + } + return SAVEFH4res{m: (*[sAVEFH4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsSECINFO4resEntry() SECINFO4resEntry { + if m.disc != OP_SECINFO { + panic("wrong union discriminant") + } + return SECINFO4resEntry(m.b) +} + +func (m NfsResop4) AsSETATTR4res() SETATTR4res { + if m.disc != OP_SETATTR { + panic("wrong union discriminant") + } + return SETATTR4res(m.b) +} + +func (m NfsResop4) AsSETCLIENTID4resEntry() SETCLIENTID4resEntry { + if m.disc != OP_SETCLIENTID { + panic("wrong union discriminant") + } + return SETCLIENTID4resEntry(m.b) +} + +func (m NfsResop4) AsSETCLIENTIDCONFIRM4res() SETCLIENTIDCONFIRM4res { + if m.disc != OP_SETCLIENTID_CONFIRM { + panic("wrong union discriminant") + } + return SETCLIENTIDCONFIRM4res{m: (*[sETCLIENTIDCONFIRM4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsVERIFY4res() VERIFY4res { + if m.disc != OP_VERIFY { + panic("wrong union discriminant") + } + return VERIFY4res{m: (*[vERIFY4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsWRITE4resEntry() WRITE4resEntry { + if m.disc != OP_WRITE { + panic("wrong union discriminant") + } + return WRITE4resEntry(m.b) +} + +func (m NfsResop4) AsRELEASELOCKOWNER4res() RELEASELOCKOWNER4res { + if m.disc != OP_RELEASE_LOCKOWNER { + panic("wrong union discriminant") + } + return RELEASELOCKOWNER4res{m: (*[rELEASELOCKOWNER4resSize]byte)(m.b)} +} + +func (m NfsResop4) AsILLEGAL4res() ILLEGAL4res { + if m.disc != OP_ILLEGAL { + panic("wrong union discriminant") + } + return ILLEGAL4res{m: (*[iLLEGAL4resSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// NfsArgop4Entry — variable: disc(4) + nfs_argop4 value +// ------------------------------------------------------- + +type NfsArgop4Entry []byte + +func readNfsArgop4Entry(b *[]byte) (NfsArgop4Entry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readNfsArgop4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return NfsArgop4Entry(start[:total]), true +} + +func ReadNfsArgop4Entry(b []byte) (NfsArgop4Entry, bool) { + return readNfsArgop4Entry(&b) +} + +func (m NfsArgop4Entry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m NfsArgop4Entry) Value() NfsArgop4 { + return NfsArgop4{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// NfsArgop4EntryWriter writes a nfs_argop4_entry: +// +// disc + value(disc) +type NfsArgop4EntryWriter struct { + buf []byte + off int +} + +func StartNfsArgop4Entry(buf []byte) NfsArgop4EntryWriter { + return NfsArgop4EntryWriter{buf: buf, off: len(buf)} +} + +func (w *NfsArgop4EntryWriter) SetValue_Access() ACCESS4args { + w.buf = append(w.buf, make([]byte, 4+aCCESS4argsSize)...) + p := (*[4 + aCCESS4argsSize]byte)(w.buf[len(w.buf)-4-aCCESS4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_ACCESS) + return ACCESS4args{m: (*[aCCESS4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Close() CLOSE4args { + w.buf = append(w.buf, make([]byte, 4+cLOSE4argsSize)...) + p := (*[4 + cLOSE4argsSize]byte)(w.buf[len(w.buf)-4-cLOSE4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_CLOSE) + return CLOSE4args{m: (*[cLOSE4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Commit() COMMIT4args { + w.buf = append(w.buf, make([]byte, 4+cOMMIT4argsSize)...) + p := (*[4 + cOMMIT4argsSize]byte)(w.buf[len(w.buf)-4-cOMMIT4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_COMMIT) + return COMMIT4args{m: (*[cOMMIT4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Create() CREATE4argsWriter { + w.buf = append(w.buf, make([]byte, 4+4)...) + off := len(w.buf) - 4 - 4 + binary.BigEndian.PutUint32((*[4 + 4]byte)(w.buf[off:])[:4], OP_CREATE) + buf := w.buf + w.buf = nil + return CREATE4argsWriter{buf: buf, header: (*[4]byte)(buf[off+4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Delegpurge() DELEGPURGE4args { + w.buf = append(w.buf, make([]byte, 4+dELEGPURGE4argsSize)...) + p := (*[4 + dELEGPURGE4argsSize]byte)(w.buf[len(w.buf)-4-dELEGPURGE4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGPURGE) + return DELEGPURGE4args{m: (*[dELEGPURGE4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Delegreturn() DELEGRETURN4args { + w.buf = append(w.buf, make([]byte, 4+dELEGRETURN4argsSize)...) + p := (*[4 + dELEGRETURN4argsSize]byte)(w.buf[len(w.buf)-4-dELEGRETURN4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGRETURN) + return DELEGRETURN4args{m: (*[dELEGRETURN4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Getattr() GETATTR4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETATTR) + child := StartGETATTR4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Getfh() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETFH) +} + +func (w *NfsArgop4EntryWriter) SetValue_Link() LINK4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LINK) + child := StartLINK4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Lock() LOCK4argsWriter { + w.buf = append(w.buf, make([]byte, 4+28)...) + off := len(w.buf) - 4 - 28 + binary.BigEndian.PutUint32((*[4 + 28]byte)(w.buf[off:])[:4], OP_LOCK) + buf := w.buf + w.buf = nil + return LOCK4argsWriter{buf: buf, header: (*[28]byte)(buf[off+4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Lockt() LOCKT4argsWriter { + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], OP_LOCKT) + buf := w.buf + w.buf = nil + return LOCKT4argsWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Locku() LOCKU4args { + w.buf = append(w.buf, make([]byte, 4+lOCKU4argsSize)...) + p := (*[4 + lOCKU4argsSize]byte)(w.buf[len(w.buf)-4-lOCKU4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_LOCKU) + return LOCKU4args{m: (*[lOCKU4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Lookup() LOOKUP4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOOKUP) + child := StartLOOKUP4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Lookupp() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOOKUPP) +} + +func (w *NfsArgop4EntryWriter) SetValue_Nverify() NVERIFY4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_NVERIFY) + child := StartNVERIFY4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Open() OPEN4argsWriter { + w.buf = append(w.buf, make([]byte, 4+12)...) + off := len(w.buf) - 4 - 12 + binary.BigEndian.PutUint32((*[4 + 12]byte)(w.buf[off:])[:4], OP_OPEN) + buf := w.buf + w.buf = nil + return OPEN4argsWriter{buf: buf, header: (*[12]byte)(buf[off+4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Openattr() OPENATTR4args { + w.buf = append(w.buf, make([]byte, 4+oPENATTR4argsSize)...) + p := (*[4 + oPENATTR4argsSize]byte)(w.buf[len(w.buf)-4-oPENATTR4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPENATTR) + return OPENATTR4args{m: (*[oPENATTR4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_OpenConfirm() OPENCONFIRM4args { + w.buf = append(w.buf, make([]byte, 4+oPENCONFIRM4argsSize)...) + p := (*[4 + oPENCONFIRM4argsSize]byte)(w.buf[len(w.buf)-4-oPENCONFIRM4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPEN_CONFIRM) + return OPENCONFIRM4args{m: (*[oPENCONFIRM4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_OpenDowngrade() OPENDOWNGRADE4args { + w.buf = append(w.buf, make([]byte, 4+oPENDOWNGRADE4argsSize)...) + p := (*[4 + oPENDOWNGRADE4argsSize]byte)(w.buf[len(w.buf)-4-oPENDOWNGRADE4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPEN_DOWNGRADE) + return OPENDOWNGRADE4args{m: (*[oPENDOWNGRADE4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Putfh() PUTFH4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_PUTFH) + child := StartPUTFH4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Putpubfh() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_PUTPUBFH) +} + +func (w *NfsArgop4EntryWriter) SetValue_Putrootfh() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_PUTROOTFH) +} + +func (w *NfsArgop4EntryWriter) SetValue_Read() READ4args { + w.buf = append(w.buf, make([]byte, 4+rEAD4argsSize)...) + p := (*[4 + rEAD4argsSize]byte)(w.buf[len(w.buf)-4-rEAD4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_READ) + return READ4args{m: (*[rEAD4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Readdir() READDIR4argsWriter { + w.buf = append(w.buf, make([]byte, 4+24)...) + off := len(w.buf) - 4 - 24 + binary.BigEndian.PutUint32((*[4 + 24]byte)(w.buf[off:])[:4], OP_READDIR) + buf := w.buf + w.buf = nil + return READDIR4argsWriter{buf: buf, header: (*[24]byte)(buf[off+4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Readlink() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READLINK) +} + +func (w *NfsArgop4EntryWriter) SetValue_Remove() REMOVE4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_REMOVE) + child := StartREMOVE4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Rename() RENAME4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RENAME) + child := StartRENAME4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Renew() RENEW4args { + w.buf = append(w.buf, make([]byte, 4+rENEW4argsSize)...) + p := (*[4 + rENEW4argsSize]byte)(w.buf[len(w.buf)-4-rENEW4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RENEW) + return RENEW4args{m: (*[rENEW4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Restorefh() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RESTOREFH) +} + +func (w *NfsArgop4EntryWriter) SetValue_Savefh() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SAVEFH) +} + +func (w *NfsArgop4EntryWriter) SetValue_Secinfo() SECINFO4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SECINFO) + child := StartSECINFO4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Setattr() SETATTR4argsWriter { + w.buf = append(w.buf, make([]byte, 4+16)...) + off := len(w.buf) - 4 - 16 + binary.BigEndian.PutUint32((*[4 + 16]byte)(w.buf[off:])[:4], OP_SETATTR) + buf := w.buf + w.buf = nil + return SETATTR4argsWriter{buf: buf, header: (*[16]byte)(buf[off+4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Setclientid() SETCLIENTID4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SETCLIENTID) + child := StartSETCLIENTID4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_SetclientidConfirm() SETCLIENTIDCONFIRM4args { + w.buf = append(w.buf, make([]byte, 4+sETCLIENTIDCONFIRM4argsSize)...) + p := (*[4 + sETCLIENTIDCONFIRM4argsSize]byte)(w.buf[len(w.buf)-4-sETCLIENTIDCONFIRM4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_SETCLIENTID_CONFIRM) + return SETCLIENTIDCONFIRM4args{m: (*[sETCLIENTIDCONFIRM4argsSize]byte)(p[4:])} +} + +func (w *NfsArgop4EntryWriter) SetValue_Verify() VERIFY4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_VERIFY) + child := StartVERIFY4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Write() WRITE4argsWriter { + w.buf = append(w.buf, make([]byte, 4+32)...) + off := len(w.buf) - 4 - 32 + binary.BigEndian.PutUint32((*[4 + 32]byte)(w.buf[off:])[:4], OP_WRITE) + buf := w.buf + w.buf = nil + return WRITE4argsWriter{buf: buf, off: off + 4} +} + +func (w *NfsArgop4EntryWriter) SetValue_ReleaseLockowner() RELEASELOCKOWNER4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RELEASE_LOCKOWNER) + child := StartRELEASELOCKOWNER4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsArgop4EntryWriter) SetValue_Illegal() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_ILLEGAL) +} + +func (w *NfsArgop4EntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *NfsArgop4EntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// COMPOUND4args — variable: +// tag + minorversion(4) + argarray_count(4) + nfs_argop4_entry argarray[argarray_count] +// ------------------------------------------------------- + +type COMPOUND4args struct { + data []byte + off1 int // byte offset within data where minorversion starts +} + +func readCOMPOUND4args(b *[]byte) (COMPOUND4args, bool) { + start := *b + startLen := len(start) + if _, ok := readUtf8strCs(b); !ok { + return COMPOUND4args{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return COMPOUND4args{}, false + } + *b = (*b)[4:] + if len(*b) < 4 { + return COMPOUND4args{}, false + } + c_argarray_count := int(binary.BigEndian.Uint32((*b)[:4])) + *b = (*b)[4:] + for i := 0; i < c_argarray_count; i++ { + if _, ok := readNfsArgop4Entry(b); !ok { + return COMPOUND4args{}, false + } + } + total := startLen - len(*b) + return COMPOUND4args{data: start[:total], off1: off1}, true +} + +func ReadCOMPOUND4args(b []byte) (COMPOUND4args, bool) { + return readCOMPOUND4args(&b) +} + +func (m COMPOUND4args) Tag() Utf8strCs { + return Utf8strCs(m.data[0:m.off1]) +} + +func (m COMPOUND4args) Minorversion() uint32 { + o := m.off1 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m COMPOUND4args) Argarray() NfsArgop4EntryIter { + argarray_count := int(binary.BigEndian.Uint32(m.data[m.off1+4 : m.off1+8])) + return NfsArgop4EntryIter{b: m.data[m.off1+8:], count: argarray_count} +} + +func (m COMPOUND4args) ArgarrayCount() uint32 { + return binary.BigEndian.Uint32(m.data[m.off1+4 : m.off1+8]) +} + +// NfsArgop4EntryIter iterates over variable-size NfsArgop4Entry entries. +type NfsArgop4EntryIter struct { + b []byte + count int + i int + cur NfsArgop4Entry +} + +func (it *NfsArgop4EntryIter) Next() bool { + if it.i >= it.count { + return false + } + var ok bool + it.cur, ok = readNfsArgop4Entry(&it.b) + if !ok { + return false + } + it.i++ + return true +} + +func (it *NfsArgop4EntryIter) Argarray() NfsArgop4Entry { + return it.cur +} + +// COMPOUND4argsWriter writes a COMPOUND4args: +// +// tag + minorversion + argarray_count + nfs_argop4_entry argarray[argarray_count] +type COMPOUND4argsWriter struct { + buf []byte + off int + argarrayCount uint32 + argarrayCountOff int + phase uint8 +} + +func StartCOMPOUND4args(buf []byte) COMPOUND4argsWriter { + off := len(buf) + return COMPOUND4argsWriter{buf: buf, off: off} +} + +func (w *COMPOUND4argsWriter) SetMinorversion(v uint32) { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Access() ACCESS4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+aCCESS4argsSize)...) + p := (*[4 + aCCESS4argsSize]byte)(w.buf[len(w.buf)-4-aCCESS4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_ACCESS) + w.argarrayCount++ + return ACCESS4args{m: (*[aCCESS4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Close() CLOSE4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+cLOSE4argsSize)...) + p := (*[4 + cLOSE4argsSize]byte)(w.buf[len(w.buf)-4-cLOSE4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_CLOSE) + w.argarrayCount++ + return CLOSE4args{m: (*[cLOSE4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Commit() COMMIT4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+cOMMIT4argsSize)...) + p := (*[4 + cOMMIT4argsSize]byte)(w.buf[len(w.buf)-4-cOMMIT4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_COMMIT) + w.argarrayCount++ + return COMMIT4args{m: (*[cOMMIT4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Create() CREATE4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+4)...) + off := len(w.buf) - 4 - 4 + binary.BigEndian.PutUint32((*[4 + 4]byte)(w.buf[off:])[:4], OP_CREATE) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return CREATE4argsWriter{buf: buf, header: (*[4]byte)(buf[off+4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Delegpurge() DELEGPURGE4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+dELEGPURGE4argsSize)...) + p := (*[4 + dELEGPURGE4argsSize]byte)(w.buf[len(w.buf)-4-dELEGPURGE4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGPURGE) + w.argarrayCount++ + return DELEGPURGE4args{m: (*[dELEGPURGE4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Delegreturn() DELEGRETURN4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+dELEGRETURN4argsSize)...) + p := (*[4 + dELEGRETURN4argsSize]byte)(w.buf[len(w.buf)-4-dELEGRETURN4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGRETURN) + w.argarrayCount++ + return DELEGRETURN4args{m: (*[dELEGRETURN4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Getattr() GETATTR4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETATTR) + w.argarrayCount++ + child := StartGETATTR4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Getfh() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETFH) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Link() LINK4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LINK) + w.argarrayCount++ + child := StartLINK4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Lock() LOCK4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+28)...) + off := len(w.buf) - 4 - 28 + binary.BigEndian.PutUint32((*[4 + 28]byte)(w.buf[off:])[:4], OP_LOCK) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return LOCK4argsWriter{buf: buf, header: (*[28]byte)(buf[off+4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Lockt() LOCKT4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], OP_LOCKT) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return LOCKT4argsWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Locku() LOCKU4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+lOCKU4argsSize)...) + p := (*[4 + lOCKU4argsSize]byte)(w.buf[len(w.buf)-4-lOCKU4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_LOCKU) + w.argarrayCount++ + return LOCKU4args{m: (*[lOCKU4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Lookup() LOOKUP4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOOKUP) + w.argarrayCount++ + child := StartLOOKUP4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Lookupp() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOOKUPP) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Nverify() NVERIFY4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_NVERIFY) + w.argarrayCount++ + child := StartNVERIFY4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Open() OPEN4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+12)...) + off := len(w.buf) - 4 - 12 + binary.BigEndian.PutUint32((*[4 + 12]byte)(w.buf[off:])[:4], OP_OPEN) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return OPEN4argsWriter{buf: buf, header: (*[12]byte)(buf[off+4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Openattr() OPENATTR4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+oPENATTR4argsSize)...) + p := (*[4 + oPENATTR4argsSize]byte)(w.buf[len(w.buf)-4-oPENATTR4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPENATTR) + w.argarrayCount++ + return OPENATTR4args{m: (*[oPENATTR4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_OpenConfirm() OPENCONFIRM4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+oPENCONFIRM4argsSize)...) + p := (*[4 + oPENCONFIRM4argsSize]byte)(w.buf[len(w.buf)-4-oPENCONFIRM4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPEN_CONFIRM) + w.argarrayCount++ + return OPENCONFIRM4args{m: (*[oPENCONFIRM4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_OpenDowngrade() OPENDOWNGRADE4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+oPENDOWNGRADE4argsSize)...) + p := (*[4 + oPENDOWNGRADE4argsSize]byte)(w.buf[len(w.buf)-4-oPENDOWNGRADE4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPEN_DOWNGRADE) + w.argarrayCount++ + return OPENDOWNGRADE4args{m: (*[oPENDOWNGRADE4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Putfh() PUTFH4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_PUTFH) + w.argarrayCount++ + child := StartPUTFH4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Putpubfh() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_PUTPUBFH) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Putrootfh() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_PUTROOTFH) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Read() READ4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+rEAD4argsSize)...) + p := (*[4 + rEAD4argsSize]byte)(w.buf[len(w.buf)-4-rEAD4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_READ) + w.argarrayCount++ + return READ4args{m: (*[rEAD4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Readdir() READDIR4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+24)...) + off := len(w.buf) - 4 - 24 + binary.BigEndian.PutUint32((*[4 + 24]byte)(w.buf[off:])[:4], OP_READDIR) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return READDIR4argsWriter{buf: buf, header: (*[24]byte)(buf[off+4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Readlink() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READLINK) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Remove() REMOVE4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_REMOVE) + w.argarrayCount++ + child := StartREMOVE4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Rename() RENAME4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RENAME) + w.argarrayCount++ + child := StartRENAME4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Renew() RENEW4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+rENEW4argsSize)...) + p := (*[4 + rENEW4argsSize]byte)(w.buf[len(w.buf)-4-rENEW4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RENEW) + w.argarrayCount++ + return RENEW4args{m: (*[rENEW4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Restorefh() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RESTOREFH) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Savefh() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SAVEFH) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Secinfo() SECINFO4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SECINFO) + w.argarrayCount++ + child := StartSECINFO4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Setattr() SETATTR4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+16)...) + off := len(w.buf) - 4 - 16 + binary.BigEndian.PutUint32((*[4 + 16]byte)(w.buf[off:])[:4], OP_SETATTR) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return SETATTR4argsWriter{buf: buf, header: (*[16]byte)(buf[off+4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Setclientid() SETCLIENTID4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SETCLIENTID) + w.argarrayCount++ + child := StartSETCLIENTID4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_SetclientidConfirm() SETCLIENTIDCONFIRM4args { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+sETCLIENTIDCONFIRM4argsSize)...) + p := (*[4 + sETCLIENTIDCONFIRM4argsSize]byte)(w.buf[len(w.buf)-4-sETCLIENTIDCONFIRM4argsSize:]) + binary.BigEndian.PutUint32(p[:4], OP_SETCLIENTID_CONFIRM) + w.argarrayCount++ + return SETCLIENTIDCONFIRM4args{m: (*[sETCLIENTIDCONFIRM4argsSize]byte)(p[4:])} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Verify() VERIFY4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_VERIFY) + w.argarrayCount++ + child := StartVERIFY4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Write() WRITE4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+32)...) + off := len(w.buf) - 4 - 32 + binary.BigEndian.PutUint32((*[4 + 32]byte)(w.buf[off:])[:4], OP_WRITE) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return WRITE4argsWriter{buf: buf, off: off + 4} +} + +func (w *COMPOUND4argsWriter) AppendArgarray_ReleaseLockowner() RELEASELOCKOWNER4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RELEASE_LOCKOWNER) + w.argarrayCount++ + child := StartRELEASELOCKOWNER4args(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) AppendArgarray_Illegal() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_ILLEGAL) + w.argarrayCount++ +} + +func (w *COMPOUND4argsWriter) StartTag() Utf8strCsWriter { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartUtf8strCs(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *COMPOUND4argsWriter) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.argarrayCountOff:w.argarrayCountOff+4], w.argarrayCount) + return w.buf +} + +// ------------------------------------------------------- +// NfsResop4Entry — variable: disc(4) + nfs_resop4 value +// ------------------------------------------------------- + +type NfsResop4Entry []byte + +func readNfsResop4Entry(b *[]byte) (NfsResop4Entry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readNfsResop4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return NfsResop4Entry(start[:total]), true +} + +func ReadNfsResop4Entry(b []byte) (NfsResop4Entry, bool) { + return readNfsResop4Entry(&b) +} + +func (m NfsResop4Entry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m NfsResop4Entry) Value() NfsResop4 { + return NfsResop4{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// NfsResop4EntryWriter writes a nfs_resop4_entry: +// +// disc + value(disc) +type NfsResop4EntryWriter struct { + buf []byte + off int +} + +func StartNfsResop4Entry(buf []byte) NfsResop4EntryWriter { + return NfsResop4EntryWriter{buf: buf, off: len(buf)} +} + +func (w *NfsResop4EntryWriter) SetValue_Access() ACCESS4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_ACCESS) + child := StartACCESS4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Close() CLOSE4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CLOSE) + child := StartCLOSE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Commit() COMMIT4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_COMMIT) + child := StartCOMMIT4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Create() CREATE4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CREATE) + child := StartCREATE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Delegpurge() DELEGPURGE4res { + w.buf = append(w.buf, make([]byte, 4+dELEGPURGE4resSize)...) + p := (*[4 + dELEGPURGE4resSize]byte)(w.buf[len(w.buf)-4-dELEGPURGE4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGPURGE) + return DELEGPURGE4res{m: (*[dELEGPURGE4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Delegreturn() DELEGRETURN4res { + w.buf = append(w.buf, make([]byte, 4+dELEGRETURN4resSize)...) + p := (*[4 + dELEGRETURN4resSize]byte)(w.buf[len(w.buf)-4-dELEGRETURN4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGRETURN) + return DELEGRETURN4res{m: (*[dELEGRETURN4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Getattr() GETATTR4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETATTR) + child := StartGETATTR4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Getfh() GETFH4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETFH) + child := StartGETFH4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Link() LINK4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LINK) + child := StartLINK4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Lock() LOCK4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOCK) + child := StartLOCK4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Lockt() LOCKT4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOCKT) + child := StartLOCKT4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Locku() LOCKU4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOCKU) + child := StartLOCKU4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Lookup() LOOKUP4res { + w.buf = append(w.buf, make([]byte, 4+lOOKUP4resSize)...) + p := (*[4 + lOOKUP4resSize]byte)(w.buf[len(w.buf)-4-lOOKUP4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_LOOKUP) + return LOOKUP4res{m: (*[lOOKUP4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Lookupp() LOOKUPP4res { + w.buf = append(w.buf, make([]byte, 4+lOOKUPP4resSize)...) + p := (*[4 + lOOKUPP4resSize]byte)(w.buf[len(w.buf)-4-lOOKUPP4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_LOOKUPP) + return LOOKUPP4res{m: (*[lOOKUPP4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Nverify() NVERIFY4res { + w.buf = append(w.buf, make([]byte, 4+nVERIFY4resSize)...) + p := (*[4 + nVERIFY4resSize]byte)(w.buf[len(w.buf)-4-nVERIFY4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_NVERIFY) + return NVERIFY4res{m: (*[nVERIFY4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Open() OPEN4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_OPEN) + child := StartOPEN4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Openattr() OPENATTR4res { + w.buf = append(w.buf, make([]byte, 4+oPENATTR4resSize)...) + p := (*[4 + oPENATTR4resSize]byte)(w.buf[len(w.buf)-4-oPENATTR4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPENATTR) + return OPENATTR4res{m: (*[oPENATTR4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_OpenConfirm() OPENCONFIRM4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_OPEN_CONFIRM) + child := StartOPENCONFIRM4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_OpenDowngrade() OPENDOWNGRADE4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_OPEN_DOWNGRADE) + child := StartOPENDOWNGRADE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Putfh() PUTFH4res { + w.buf = append(w.buf, make([]byte, 4+pUTFH4resSize)...) + p := (*[4 + pUTFH4resSize]byte)(w.buf[len(w.buf)-4-pUTFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_PUTFH) + return PUTFH4res{m: (*[pUTFH4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Putpubfh() PUTPUBFH4res { + w.buf = append(w.buf, make([]byte, 4+pUTPUBFH4resSize)...) + p := (*[4 + pUTPUBFH4resSize]byte)(w.buf[len(w.buf)-4-pUTPUBFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_PUTPUBFH) + return PUTPUBFH4res{m: (*[pUTPUBFH4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Putrootfh() PUTROOTFH4res { + w.buf = append(w.buf, make([]byte, 4+pUTROOTFH4resSize)...) + p := (*[4 + pUTROOTFH4resSize]byte)(w.buf[len(w.buf)-4-pUTROOTFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_PUTROOTFH) + return PUTROOTFH4res{m: (*[pUTROOTFH4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Read() READ4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READ) + child := StartREAD4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Readdir() READDIR4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READDIR) + child := StartREADDIR4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Readlink() READLINK4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READLINK) + child := StartREADLINK4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Remove() REMOVE4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_REMOVE) + child := StartREMOVE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Rename() RENAME4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RENAME) + child := StartRENAME4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Renew() RENEW4res { + w.buf = append(w.buf, make([]byte, 4+rENEW4resSize)...) + p := (*[4 + rENEW4resSize]byte)(w.buf[len(w.buf)-4-rENEW4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RENEW) + return RENEW4res{m: (*[rENEW4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Restorefh() RESTOREFH4res { + w.buf = append(w.buf, make([]byte, 4+rESTOREFH4resSize)...) + p := (*[4 + rESTOREFH4resSize]byte)(w.buf[len(w.buf)-4-rESTOREFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RESTOREFH) + return RESTOREFH4res{m: (*[rESTOREFH4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Savefh() SAVEFH4res { + w.buf = append(w.buf, make([]byte, 4+sAVEFH4resSize)...) + p := (*[4 + sAVEFH4resSize]byte)(w.buf[len(w.buf)-4-sAVEFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_SAVEFH) + return SAVEFH4res{m: (*[sAVEFH4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Secinfo() SECINFO4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SECINFO) + child := StartSECINFO4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_Setattr() SETATTR4resWriter { + w.buf = append(w.buf, make([]byte, 4+4)...) + off := len(w.buf) - 4 - 4 + binary.BigEndian.PutUint32((*[4 + 4]byte)(w.buf[off:])[:4], OP_SETATTR) + buf := w.buf + w.buf = nil + return SETATTR4resWriter{buf: buf, header: (*[4]byte)(buf[off+4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Setclientid() SETCLIENTID4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SETCLIENTID) + child := StartSETCLIENTID4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_SetclientidConfirm() SETCLIENTIDCONFIRM4res { + w.buf = append(w.buf, make([]byte, 4+sETCLIENTIDCONFIRM4resSize)...) + p := (*[4 + sETCLIENTIDCONFIRM4resSize]byte)(w.buf[len(w.buf)-4-sETCLIENTIDCONFIRM4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_SETCLIENTID_CONFIRM) + return SETCLIENTIDCONFIRM4res{m: (*[sETCLIENTIDCONFIRM4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Verify() VERIFY4res { + w.buf = append(w.buf, make([]byte, 4+vERIFY4resSize)...) + p := (*[4 + vERIFY4resSize]byte)(w.buf[len(w.buf)-4-vERIFY4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_VERIFY) + return VERIFY4res{m: (*[vERIFY4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Write() WRITE4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_WRITE) + child := StartWRITE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsResop4EntryWriter) SetValue_ReleaseLockowner() RELEASELOCKOWNER4res { + w.buf = append(w.buf, make([]byte, 4+rELEASELOCKOWNER4resSize)...) + p := (*[4 + rELEASELOCKOWNER4resSize]byte)(w.buf[len(w.buf)-4-rELEASELOCKOWNER4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RELEASE_LOCKOWNER) + return RELEASELOCKOWNER4res{m: (*[rELEASELOCKOWNER4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) SetValue_Illegal() ILLEGAL4res { + w.buf = append(w.buf, make([]byte, 4+iLLEGAL4resSize)...) + p := (*[4 + iLLEGAL4resSize]byte)(w.buf[len(w.buf)-4-iLLEGAL4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_ILLEGAL) + return ILLEGAL4res{m: (*[iLLEGAL4resSize]byte)(p[4:])} +} + +func (w *NfsResop4EntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *NfsResop4EntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// COMPOUND4res — variable: +// status(4) + tag + resarray_count(4) + nfs_resop4_entry resarray[resarray_count] +// ------------------------------------------------------- + +type COMPOUND4res struct { + data []byte + off1 int // byte offset within data where resarray_count starts +} + +func readCOMPOUND4res(b *[]byte) (COMPOUND4res, bool) { + if len(*b) < 4 { + return COMPOUND4res{}, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readUtf8strCs(b); !ok { + return COMPOUND4res{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return COMPOUND4res{}, false + } + c_resarray_count := int(binary.BigEndian.Uint32((*b)[:4])) + *b = (*b)[4:] + for i := 0; i < c_resarray_count; i++ { + if _, ok := readNfsResop4Entry(b); !ok { + return COMPOUND4res{}, false + } + } + total := startLen - len(*b) + return COMPOUND4res{data: start[:total], off1: off1}, true +} + +func ReadCOMPOUND4res(b []byte) (COMPOUND4res, bool) { + return readCOMPOUND4res(&b) +} + +func (m COMPOUND4res) Status() uint32 { + return binary.BigEndian.Uint32(m.data[0:4]) +} + +func (m COMPOUND4res) Tag() Utf8strCs { + return Utf8strCs(m.data[4:m.off1]) +} + +func (m COMPOUND4res) Resarray() NfsResop4EntryIter { + resarray_count := int(binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4])) + return NfsResop4EntryIter{b: m.data[m.off1+4:], count: resarray_count} +} + +func (m COMPOUND4res) ResarrayCount() uint32 { + return binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4]) +} + +// NfsResop4EntryIter iterates over variable-size NfsResop4Entry entries. +type NfsResop4EntryIter struct { + b []byte + count int + i int + cur NfsResop4Entry +} + +func (it *NfsResop4EntryIter) Next() bool { + if it.i >= it.count { + return false + } + var ok bool + it.cur, ok = readNfsResop4Entry(&it.b) + if !ok { + return false + } + it.i++ + return true +} + +func (it *NfsResop4EntryIter) Resarray() NfsResop4Entry { + return it.cur +} + +// COMPOUND4resWriter writes a COMPOUND4res: +// +// status + tag + resarray_count + nfs_resop4_entry resarray[resarray_count] +type COMPOUND4resWriter struct { + buf []byte + off int + resarrayCount uint32 + resarrayCountOff int + phase uint8 +} + +func StartCOMPOUND4res(buf []byte) COMPOUND4resWriter { + off := len(buf) + buf = append(buf, make([]byte, 4)...) // status(4) + return COMPOUND4resWriter{buf: buf, off: off} +} + +func (w *COMPOUND4resWriter) SetStatus(v uint32) { + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], v) +} + +func (w *COMPOUND4resWriter) AppendResarray_Access() ACCESS4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_ACCESS) + w.resarrayCount++ + child := StartACCESS4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Close() CLOSE4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CLOSE) + w.resarrayCount++ + child := StartCLOSE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Commit() COMMIT4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_COMMIT) + w.resarrayCount++ + child := StartCOMMIT4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Create() CREATE4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CREATE) + w.resarrayCount++ + child := StartCREATE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Delegpurge() DELEGPURGE4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+dELEGPURGE4resSize)...) + p := (*[4 + dELEGPURGE4resSize]byte)(w.buf[len(w.buf)-4-dELEGPURGE4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGPURGE) + w.resarrayCount++ + return DELEGPURGE4res{m: (*[dELEGPURGE4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Delegreturn() DELEGRETURN4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+dELEGRETURN4resSize)...) + p := (*[4 + dELEGRETURN4resSize]byte)(w.buf[len(w.buf)-4-dELEGRETURN4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_DELEGRETURN) + w.resarrayCount++ + return DELEGRETURN4res{m: (*[dELEGRETURN4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Getattr() GETATTR4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETATTR) + w.resarrayCount++ + child := StartGETATTR4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Getfh() GETFH4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_GETFH) + w.resarrayCount++ + child := StartGETFH4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Link() LINK4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LINK) + w.resarrayCount++ + child := StartLINK4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Lock() LOCK4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOCK) + w.resarrayCount++ + child := StartLOCK4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Lockt() LOCKT4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOCKT) + w.resarrayCount++ + child := StartLOCKT4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Locku() LOCKU4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_LOCKU) + w.resarrayCount++ + child := StartLOCKU4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Lookup() LOOKUP4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+lOOKUP4resSize)...) + p := (*[4 + lOOKUP4resSize]byte)(w.buf[len(w.buf)-4-lOOKUP4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_LOOKUP) + w.resarrayCount++ + return LOOKUP4res{m: (*[lOOKUP4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Lookupp() LOOKUPP4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+lOOKUPP4resSize)...) + p := (*[4 + lOOKUPP4resSize]byte)(w.buf[len(w.buf)-4-lOOKUPP4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_LOOKUPP) + w.resarrayCount++ + return LOOKUPP4res{m: (*[lOOKUPP4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Nverify() NVERIFY4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+nVERIFY4resSize)...) + p := (*[4 + nVERIFY4resSize]byte)(w.buf[len(w.buf)-4-nVERIFY4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_NVERIFY) + w.resarrayCount++ + return NVERIFY4res{m: (*[nVERIFY4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Open() OPEN4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_OPEN) + w.resarrayCount++ + child := StartOPEN4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Openattr() OPENATTR4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+oPENATTR4resSize)...) + p := (*[4 + oPENATTR4resSize]byte)(w.buf[len(w.buf)-4-oPENATTR4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_OPENATTR) + w.resarrayCount++ + return OPENATTR4res{m: (*[oPENATTR4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_OpenConfirm() OPENCONFIRM4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_OPEN_CONFIRM) + w.resarrayCount++ + child := StartOPENCONFIRM4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_OpenDowngrade() OPENDOWNGRADE4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_OPEN_DOWNGRADE) + w.resarrayCount++ + child := StartOPENDOWNGRADE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Putfh() PUTFH4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+pUTFH4resSize)...) + p := (*[4 + pUTFH4resSize]byte)(w.buf[len(w.buf)-4-pUTFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_PUTFH) + w.resarrayCount++ + return PUTFH4res{m: (*[pUTFH4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Putpubfh() PUTPUBFH4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+pUTPUBFH4resSize)...) + p := (*[4 + pUTPUBFH4resSize]byte)(w.buf[len(w.buf)-4-pUTPUBFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_PUTPUBFH) + w.resarrayCount++ + return PUTPUBFH4res{m: (*[pUTPUBFH4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Putrootfh() PUTROOTFH4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+pUTROOTFH4resSize)...) + p := (*[4 + pUTROOTFH4resSize]byte)(w.buf[len(w.buf)-4-pUTROOTFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_PUTROOTFH) + w.resarrayCount++ + return PUTROOTFH4res{m: (*[pUTROOTFH4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Read() READ4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READ) + w.resarrayCount++ + child := StartREAD4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Readdir() READDIR4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READDIR) + w.resarrayCount++ + child := StartREADDIR4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Readlink() READLINK4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_READLINK) + w.resarrayCount++ + child := StartREADLINK4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Remove() REMOVE4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_REMOVE) + w.resarrayCount++ + child := StartREMOVE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Rename() RENAME4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_RENAME) + w.resarrayCount++ + child := StartRENAME4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Renew() RENEW4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+rENEW4resSize)...) + p := (*[4 + rENEW4resSize]byte)(w.buf[len(w.buf)-4-rENEW4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RENEW) + w.resarrayCount++ + return RENEW4res{m: (*[rENEW4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Restorefh() RESTOREFH4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+rESTOREFH4resSize)...) + p := (*[4 + rESTOREFH4resSize]byte)(w.buf[len(w.buf)-4-rESTOREFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RESTOREFH) + w.resarrayCount++ + return RESTOREFH4res{m: (*[rESTOREFH4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Savefh() SAVEFH4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+sAVEFH4resSize)...) + p := (*[4 + sAVEFH4resSize]byte)(w.buf[len(w.buf)-4-sAVEFH4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_SAVEFH) + w.resarrayCount++ + return SAVEFH4res{m: (*[sAVEFH4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Secinfo() SECINFO4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SECINFO) + w.resarrayCount++ + child := StartSECINFO4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_Setattr() SETATTR4resWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+4)...) + off := len(w.buf) - 4 - 4 + binary.BigEndian.PutUint32((*[4 + 4]byte)(w.buf[off:])[:4], OP_SETATTR) + w.resarrayCount++ + buf := w.buf + w.buf = nil + return SETATTR4resWriter{buf: buf, header: (*[4]byte)(buf[off+4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Setclientid() SETCLIENTID4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_SETCLIENTID) + w.resarrayCount++ + child := StartSETCLIENTID4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_SetclientidConfirm() SETCLIENTIDCONFIRM4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+sETCLIENTIDCONFIRM4resSize)...) + p := (*[4 + sETCLIENTIDCONFIRM4resSize]byte)(w.buf[len(w.buf)-4-sETCLIENTIDCONFIRM4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_SETCLIENTID_CONFIRM) + w.resarrayCount++ + return SETCLIENTIDCONFIRM4res{m: (*[sETCLIENTIDCONFIRM4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Verify() VERIFY4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+vERIFY4resSize)...) + p := (*[4 + vERIFY4resSize]byte)(w.buf[len(w.buf)-4-vERIFY4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_VERIFY) + w.resarrayCount++ + return VERIFY4res{m: (*[vERIFY4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Write() WRITE4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_WRITE) + w.resarrayCount++ + child := StartWRITE4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) AppendResarray_ReleaseLockowner() RELEASELOCKOWNER4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+rELEASELOCKOWNER4resSize)...) + p := (*[4 + rELEASELOCKOWNER4resSize]byte)(w.buf[len(w.buf)-4-rELEASELOCKOWNER4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_RELEASE_LOCKOWNER) + w.resarrayCount++ + return RELEASELOCKOWNER4res{m: (*[rELEASELOCKOWNER4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) AppendResarray_Illegal() ILLEGAL4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+iLLEGAL4resSize)...) + p := (*[4 + iLLEGAL4resSize]byte)(w.buf[len(w.buf)-4-iLLEGAL4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_ILLEGAL) + w.resarrayCount++ + return ILLEGAL4res{m: (*[iLLEGAL4resSize]byte)(p[4:])} +} + +func (w *COMPOUND4resWriter) StartTag() Utf8strCsWriter { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartUtf8strCs(w.buf) + w.buf = nil + return child +} + +func (w *COMPOUND4resWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *COMPOUND4resWriter) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.resarrayCountOff:w.resarrayCountOff+4], w.resarrayCount) + return w.buf +} + +// ------------------------------------------------------- +// CBGETATTR4args — variable: fh + attr_request +// ------------------------------------------------------- + +type CBGETATTR4args struct { + data []byte + off1 int // byte offset within data where attr_request starts +} + +func readCBGETATTR4args(b *[]byte) (CBGETATTR4args, bool) { + start := *b + startLen := len(start) + if _, ok := readNfsFh4(b); !ok { + return CBGETATTR4args{}, false + } + off1 := startLen - len(*b) + if _, ok := readBitmap4(b); !ok { + return CBGETATTR4args{}, false + } + total := startLen - len(*b) + return CBGETATTR4args{data: start[:total], off1: off1}, true +} + +func ReadCBGETATTR4args(b []byte) (CBGETATTR4args, bool) { + return readCBGETATTR4args(&b) +} + +func (m CBGETATTR4args) Fh() NfsFh4 { + return NfsFh4(m.data[0:m.off1]) +} + +func (m CBGETATTR4args) AttrRequest() Bitmap4 { + return Bitmap4(m.data[m.off1:]) +} + +// CBGETATTR4argsWriter writes a CB_GETATTR4args: +// +// fh + attr_request +type CBGETATTR4argsWriter struct { + buf []byte + off int + phase uint8 +} + +func StartCBGETATTR4args(buf []byte) CBGETATTR4argsWriter { + off := len(buf) + return CBGETATTR4argsWriter{buf: buf, off: off} +} + +func (w *CBGETATTR4argsWriter) StartFh() NfsFh4Writer { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartNfsFh4(w.buf) + w.buf = nil + return child +} + +func (w *CBGETATTR4argsWriter) StartAttrRequest() Bitmap4Writer { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + child := StartBitmap4(w.buf) + w.buf = nil + return child +} + +func (w *CBGETATTR4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CBGETATTR4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CBGETATTR4resok — variable: obj_attributes +// ------------------------------------------------------- + +type CBGETATTR4resok []byte + +func readCBGETATTR4resok(b *[]byte) (CBGETATTR4resok, bool) { + start := *b + startLen := len(start) + if _, ok := readFattr4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return CBGETATTR4resok(start[:total]), true +} + +func ReadCBGETATTR4resok(b []byte) (CBGETATTR4resok, bool) { + return readCBGETATTR4resok(&b) +} + +func (m CBGETATTR4resok) ObjAttributes() Fattr4 { + v, _ := ReadFattr4(m[0:]) + return v +} + +// CBGETATTR4resokWriter writes a CB_GETATTR4resok: +// +// obj_attributes +type CBGETATTR4resokWriter struct { + buf []byte + off int +} + +func StartCBGETATTR4resok(buf []byte) CBGETATTR4resokWriter { + off := len(buf) + return CBGETATTR4resokWriter{buf: buf, off: off} +} + +func (w *CBGETATTR4resokWriter) StartObjAttributes() Fattr4Writer { + child := StartFattr4(w.buf) + w.buf = nil + return child +} + +func (w *CBGETATTR4resokWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CBGETATTR4resokWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CBGETATTR4res — union on nfsstat4 (external discriminant) +// ------------------------------------------------------- + +type CBGETATTR4res struct { + b []byte + disc uint32 +} + +func readCBGETATTR4res(b *[]byte, nfsstat4 uint32) (CBGETATTR4res, bool) { + switch nfsstat4 { + case NFS4_OK: + r, ok := readCBGETATTR4resok(b) + if !ok { + return CBGETATTR4res{}, false + } + return CBGETATTR4res{b: []byte(r), disc: nfsstat4}, true + default: + return CBGETATTR4res{b: (*b)[:0], disc: nfsstat4}, true + } +} + +func (m CBGETATTR4res) AsCBGETATTR4resok() CBGETATTR4resok { + if m.disc != NFS4_OK { + panic("wrong union discriminant") + } + return CBGETATTR4resok(m.b) +} + +// ------------------------------------------------------- +// CBRECALL4args — variable: stateid(16) + truncate(4) + fh +// ------------------------------------------------------- + +type CBRECALL4args []byte + +func readCBRECALL4args(b *[]byte) (CBRECALL4args, bool) { + if len(*b) < 20 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[20:] + if _, ok := readNfsFh4(b); !ok { + return nil, false + } + total := startLen - len(*b) + return CBRECALL4args(start[:total]), true +} + +func ReadCBRECALL4args(b []byte) (CBRECALL4args, bool) { + return readCBRECALL4args(&b) +} + +func (m CBRECALL4args) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(m[0 : 0+stateid4Size])} +} + +func (m CBRECALL4args) Truncate() uint32 { + return binary.BigEndian.Uint32(m[16:20]) +} + +func (m CBRECALL4args) Fh() NfsFh4 { + return NfsFh4(m[20:]) +} + +// CBRECALL4argsWriter writes a CB_RECALL4args: +// +// stateid + truncate + fh +type CBRECALL4argsWriter struct { + buf []byte + header *[20]byte +} + +func StartCBRECALL4args(buf []byte) CBRECALL4argsWriter { + buf = append(buf, make([]byte, 20)...) // stateid(16) + truncate(4) + return CBRECALL4argsWriter{buf: buf, header: (*[20]byte)(buf[len(buf)-20:])} +} + +func (w *CBRECALL4argsWriter) Stateid() Stateid4 { + return Stateid4{m: (*[stateid4Size]byte)(w.header[0:])} +} + +func (w *CBRECALL4argsWriter) SetTruncate(v uint32) { + binary.BigEndian.PutUint32(w.header[16:20], v) +} + +func (w *CBRECALL4argsWriter) StartFh() NfsFh4Writer { + child := StartNfsFh4(w.buf) + w.buf = nil + w.header = nil + return child +} + +func (w *CBRECALL4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CBRECALL4argsWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CBRECALL4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type CBRECALL4res struct { + m *[cBRECALL4resSize]byte +} + +const cBRECALL4resSize = 4 + +func readCBRECALL4res(b *[]byte) (CBRECALL4res, bool) { + if len(*b) < cBRECALL4resSize { + return CBRECALL4res{}, false + } + result := CBRECALL4res{m: (*[cBRECALL4resSize]byte)(*b)} + *b = (*b)[cBRECALL4resSize:] + return result, true +} + +func ReadCBRECALL4res(b []byte) (CBRECALL4res, bool) { + return readCBRECALL4res(&b) +} + +func StartCBRECALL4res(buf []byte) ([]byte, CBRECALL4res) { + buf = append(buf, make([]byte, cBRECALL4resSize)...) + return buf, CBRECALL4res{m: (*[cBRECALL4resSize]byte)(buf[len(buf)-cBRECALL4resSize:])} +} + +func (m CBRECALL4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m CBRECALL4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// CBILLEGAL4res — fixed 4 bytes: status(4, beu32) +// ------------------------------------------------------- + +type CBILLEGAL4res struct { + m *[cBILLEGAL4resSize]byte +} + +const cBILLEGAL4resSize = 4 + +func readCBILLEGAL4res(b *[]byte) (CBILLEGAL4res, bool) { + if len(*b) < cBILLEGAL4resSize { + return CBILLEGAL4res{}, false + } + result := CBILLEGAL4res{m: (*[cBILLEGAL4resSize]byte)(*b)} + *b = (*b)[cBILLEGAL4resSize:] + return result, true +} + +func ReadCBILLEGAL4res(b []byte) (CBILLEGAL4res, bool) { + return readCBILLEGAL4res(&b) +} + +func StartCBILLEGAL4res(buf []byte) ([]byte, CBILLEGAL4res) { + buf = append(buf, make([]byte, cBILLEGAL4resSize)...) + return buf, CBILLEGAL4res{m: (*[cBILLEGAL4resSize]byte)(buf[len(buf)-cBILLEGAL4resSize:])} +} + +func (m CBILLEGAL4res) Status() uint32 { + return binary.BigEndian.Uint32(m.m[0:4]) +} + +func (m CBILLEGAL4res) SetStatus(v uint32) { + binary.BigEndian.PutUint32(m.m[0:4], v) +} + +// ------------------------------------------------------- +// NfsCbArgop4 — union on nfs_cb_opnum4 (external discriminant) +// ------------------------------------------------------- + +type NfsCbArgop4 struct { + b []byte + disc uint32 +} + +func readNfsCbArgop4(b *[]byte, nfsCbOpnum4 uint32) (NfsCbArgop4, bool) { + switch nfsCbOpnum4 { + case OP_CB_GETATTR: + r, ok := readCBGETATTR4args(b) + if !ok { + return NfsCbArgop4{}, false + } + return NfsCbArgop4{b: r.data, disc: nfsCbOpnum4}, true + case OP_CB_RECALL: + r, ok := readCBRECALL4args(b) + if !ok { + return NfsCbArgop4{}, false + } + return NfsCbArgop4{b: []byte(r), disc: nfsCbOpnum4}, true + case OP_CB_ILLEGAL: + return NfsCbArgop4{b: (*b)[:0], disc: nfsCbOpnum4}, true + default: + return NfsCbArgop4{}, false + } +} + +func (m NfsCbArgop4) AsCBGETATTR4args() CBGETATTR4args { + if m.disc != OP_CB_GETATTR { + panic("wrong union discriminant") + } + v, _ := ReadCBGETATTR4args(m.b) + return v +} + +func (m NfsCbArgop4) AsCBRECALL4args() CBRECALL4args { + if m.disc != OP_CB_RECALL { + panic("wrong union discriminant") + } + return CBRECALL4args(m.b) +} + +// ------------------------------------------------------- +// CBGETATTR4resEntry — variable: disc(4) + CB_GETATTR4res value +// ------------------------------------------------------- + +type CBGETATTR4resEntry []byte + +func readCBGETATTR4resEntry(b *[]byte) (CBGETATTR4resEntry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readCBGETATTR4res(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return CBGETATTR4resEntry(start[:total]), true +} + +func ReadCBGETATTR4resEntry(b []byte) (CBGETATTR4resEntry, bool) { + return readCBGETATTR4resEntry(&b) +} + +func (m CBGETATTR4resEntry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m CBGETATTR4resEntry) Value() CBGETATTR4res { + return CBGETATTR4res{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// CBGETATTR4resEntryWriter writes a CB_GETATTR4res_entry: +// +// disc + value(disc) +type CBGETATTR4resEntryWriter struct { + buf []byte + off int +} + +func StartCBGETATTR4resEntry(buf []byte) CBGETATTR4resEntryWriter { + return CBGETATTR4resEntryWriter{buf: buf, off: len(buf)} +} + +func (w *CBGETATTR4resEntryWriter) SetValue_Nfs4Ok() CBGETATTR4resokWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, NFS4_OK) + child := StartCBGETATTR4resok(w.buf) + w.buf = nil + return child +} + +func (w *CBGETATTR4resEntryWriter) SetValue_Default(nfsstat4 uint32) { + w.buf = binary.BigEndian.AppendUint32(w.buf, nfsstat4) +} + +func (w *CBGETATTR4resEntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CBGETATTR4resEntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// NfsCbResop4 — union on nfs_cb_opnum4 (external discriminant) +// ------------------------------------------------------- + +type NfsCbResop4 struct { + b []byte + disc uint32 +} + +func readNfsCbResop4(b *[]byte, nfsCbOpnum4 uint32) (NfsCbResop4, bool) { + switch nfsCbOpnum4 { + case OP_CB_GETATTR: + r, ok := readCBGETATTR4resEntry(b) + if !ok { + return NfsCbResop4{}, false + } + return NfsCbResop4{b: []byte(r), disc: nfsCbOpnum4}, true + case OP_CB_RECALL: + r, ok := readCBRECALL4res(b) + if !ok { + return NfsCbResop4{}, false + } + return NfsCbResop4{b: r.m[:], disc: nfsCbOpnum4}, true + case OP_CB_ILLEGAL: + r, ok := readCBILLEGAL4res(b) + if !ok { + return NfsCbResop4{}, false + } + return NfsCbResop4{b: r.m[:], disc: nfsCbOpnum4}, true + default: + return NfsCbResop4{}, false + } +} + +func (m NfsCbResop4) AsCBGETATTR4resEntry() CBGETATTR4resEntry { + if m.disc != OP_CB_GETATTR { + panic("wrong union discriminant") + } + return CBGETATTR4resEntry(m.b) +} + +func (m NfsCbResop4) AsCBRECALL4res() CBRECALL4res { + if m.disc != OP_CB_RECALL { + panic("wrong union discriminant") + } + return CBRECALL4res{m: (*[cBRECALL4resSize]byte)(m.b)} +} + +func (m NfsCbResop4) AsCBILLEGAL4res() CBILLEGAL4res { + if m.disc != OP_CB_ILLEGAL { + panic("wrong union discriminant") + } + return CBILLEGAL4res{m: (*[cBILLEGAL4resSize]byte)(m.b)} +} + +// ------------------------------------------------------- +// NfsCbArgop4Entry — variable: disc(4) + nfs_cb_argop4 value +// ------------------------------------------------------- + +type NfsCbArgop4Entry []byte + +func readNfsCbArgop4Entry(b *[]byte) (NfsCbArgop4Entry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readNfsCbArgop4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return NfsCbArgop4Entry(start[:total]), true +} + +func ReadNfsCbArgop4Entry(b []byte) (NfsCbArgop4Entry, bool) { + return readNfsCbArgop4Entry(&b) +} + +func (m NfsCbArgop4Entry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m NfsCbArgop4Entry) Value() NfsCbArgop4 { + return NfsCbArgop4{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// NfsCbArgop4EntryWriter writes a nfs_cb_argop4_entry: +// +// disc + value(disc) +type NfsCbArgop4EntryWriter struct { + buf []byte + off int +} + +func StartNfsCbArgop4Entry(buf []byte) NfsCbArgop4EntryWriter { + return NfsCbArgop4EntryWriter{buf: buf, off: len(buf)} +} + +func (w *NfsCbArgop4EntryWriter) SetValue_Getattr() CBGETATTR4argsWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CB_GETATTR) + child := StartCBGETATTR4args(w.buf) + w.buf = nil + return child +} + +func (w *NfsCbArgop4EntryWriter) SetValue_Recall() CBRECALL4argsWriter { + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], OP_CB_RECALL) + buf := w.buf + w.buf = nil + return CBRECALL4argsWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *NfsCbArgop4EntryWriter) SetValue_Illegal() { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CB_ILLEGAL) +} + +func (w *NfsCbArgop4EntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *NfsCbArgop4EntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CBCOMPOUND4args — variable: +// tag + minorversion(4) + callback_ident(4) + argarray_count(4) + nfs_cb_argop4_entry argarray[argarray_count] +// ------------------------------------------------------- + +type CBCOMPOUND4args struct { + data []byte + off1 int // byte offset within data where minorversion starts +} + +func readCBCOMPOUND4args(b *[]byte) (CBCOMPOUND4args, bool) { + start := *b + startLen := len(start) + if _, ok := readUtf8strCs(b); !ok { + return CBCOMPOUND4args{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return CBCOMPOUND4args{}, false + } + *b = (*b)[4:] + if len(*b) < 4 { + return CBCOMPOUND4args{}, false + } + *b = (*b)[4:] + if len(*b) < 4 { + return CBCOMPOUND4args{}, false + } + c_argarray_count := int(binary.BigEndian.Uint32((*b)[:4])) + *b = (*b)[4:] + for i := 0; i < c_argarray_count; i++ { + if _, ok := readNfsCbArgop4Entry(b); !ok { + return CBCOMPOUND4args{}, false + } + } + total := startLen - len(*b) + return CBCOMPOUND4args{data: start[:total], off1: off1}, true +} + +func ReadCBCOMPOUND4args(b []byte) (CBCOMPOUND4args, bool) { + return readCBCOMPOUND4args(&b) +} + +func (m CBCOMPOUND4args) Tag() Utf8strCs { + return Utf8strCs(m.data[0:m.off1]) +} + +func (m CBCOMPOUND4args) Minorversion() uint32 { + o := m.off1 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m CBCOMPOUND4args) CallbackIdent() uint32 { + o := m.off1 + 4 + return binary.BigEndian.Uint32(m.data[o : o+4]) +} + +func (m CBCOMPOUND4args) Argarray() NfsCbArgop4EntryIter { + argarray_count := int(binary.BigEndian.Uint32(m.data[m.off1+8 : m.off1+12])) + return NfsCbArgop4EntryIter{b: m.data[m.off1+12:], count: argarray_count} +} + +func (m CBCOMPOUND4args) ArgarrayCount() uint32 { + return binary.BigEndian.Uint32(m.data[m.off1+8 : m.off1+12]) +} + +// NfsCbArgop4EntryIter iterates over variable-size NfsCbArgop4Entry entries. +type NfsCbArgop4EntryIter struct { + b []byte + count int + i int + cur NfsCbArgop4Entry +} + +func (it *NfsCbArgop4EntryIter) Next() bool { + if it.i >= it.count { + return false + } + var ok bool + it.cur, ok = readNfsCbArgop4Entry(&it.b) + if !ok { + return false + } + it.i++ + return true +} + +func (it *NfsCbArgop4EntryIter) Argarray() NfsCbArgop4Entry { + return it.cur +} + +// CBCOMPOUND4argsWriter writes a CB_COMPOUND4args: +// +// tag + minorversion + callback_ident + argarray_count + nfs_cb_argop4_entry argarray[argarray_count] +type CBCOMPOUND4argsWriter struct { + buf []byte + off int + argarrayCount uint32 + argarrayCountOff int + phase uint8 +} + +func StartCBCOMPOUND4args(buf []byte) CBCOMPOUND4argsWriter { + off := len(buf) + return CBCOMPOUND4argsWriter{buf: buf, off: off} +} + +func (w *CBCOMPOUND4argsWriter) SetMinorversion(v uint32) { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *CBCOMPOUND4argsWriter) SetCallbackIdent(v uint32) { + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + w.buf = binary.BigEndian.AppendUint32(w.buf, v) +} + +func (w *CBCOMPOUND4argsWriter) AppendArgarray_Getattr() CBGETATTR4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CB_GETATTR) + w.argarrayCount++ + child := StartCBGETATTR4args(w.buf) + w.buf = nil + return child +} + +func (w *CBCOMPOUND4argsWriter) AppendArgarray_Recall() CBRECALL4argsWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+20)...) + off := len(w.buf) - 4 - 20 + binary.BigEndian.PutUint32((*[4 + 20]byte)(w.buf[off:])[:4], OP_CB_RECALL) + w.argarrayCount++ + buf := w.buf + w.buf = nil + return CBRECALL4argsWriter{buf: buf, header: (*[20]byte)(buf[off+4:])} +} + +func (w *CBCOMPOUND4argsWriter) AppendArgarray_Illegal() { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.argarrayCountOff == 0 { + w.argarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CB_ILLEGAL) + w.argarrayCount++ +} + +func (w *CBCOMPOUND4argsWriter) StartTag() Utf8strCsWriter { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartUtf8strCs(w.buf) + w.buf = nil + return child +} + +func (w *CBCOMPOUND4argsWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CBCOMPOUND4argsWriter) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.argarrayCountOff:w.argarrayCountOff+4], w.argarrayCount) + return w.buf +} + +// ------------------------------------------------------- +// NfsCbResop4Entry — variable: disc(4) + nfs_cb_resop4 value +// ------------------------------------------------------- + +type NfsCbResop4Entry []byte + +func readNfsCbResop4Entry(b *[]byte) (NfsCbResop4Entry, bool) { + if len(*b) < 4 { + return nil, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readNfsCbResop4(b, binary.BigEndian.Uint32(start[0:4])); !ok { + return nil, false + } + total := startLen - len(*b) + return NfsCbResop4Entry(start[:total]), true +} + +func ReadNfsCbResop4Entry(b []byte) (NfsCbResop4Entry, bool) { + return readNfsCbResop4Entry(&b) +} + +func (m NfsCbResop4Entry) Disc() uint32 { + return binary.BigEndian.Uint32(m[0:4]) +} + +func (m NfsCbResop4Entry) Value() NfsCbResop4 { + return NfsCbResop4{b: m[4:], disc: binary.BigEndian.Uint32(m[0:4])} +} + +// NfsCbResop4EntryWriter writes a nfs_cb_resop4_entry: +// +// disc + value(disc) +type NfsCbResop4EntryWriter struct { + buf []byte + off int +} + +func StartNfsCbResop4Entry(buf []byte) NfsCbResop4EntryWriter { + return NfsCbResop4EntryWriter{buf: buf, off: len(buf)} +} + +func (w *NfsCbResop4EntryWriter) SetValue_Getattr() CBGETATTR4resEntryWriter { + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CB_GETATTR) + child := StartCBGETATTR4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *NfsCbResop4EntryWriter) SetValue_Recall() CBRECALL4res { + w.buf = append(w.buf, make([]byte, 4+cBRECALL4resSize)...) + p := (*[4 + cBRECALL4resSize]byte)(w.buf[len(w.buf)-4-cBRECALL4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_CB_RECALL) + return CBRECALL4res{m: (*[cBRECALL4resSize]byte)(p[4:])} +} + +func (w *NfsCbResop4EntryWriter) SetValue_Illegal() CBILLEGAL4res { + w.buf = append(w.buf, make([]byte, 4+cBILLEGAL4resSize)...) + p := (*[4 + cBILLEGAL4resSize]byte)(w.buf[len(w.buf)-4-cBILLEGAL4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_CB_ILLEGAL) + return CBILLEGAL4res{m: (*[cBILLEGAL4resSize]byte)(p[4:])} +} + +func (w *NfsCbResop4EntryWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *NfsCbResop4EntryWriter) Finish() []byte { + _ = w.buf[:1] + return w.buf +} + +// ------------------------------------------------------- +// CBCOMPOUND4res — variable: +// status(4) + tag + resarray_count(4) + nfs_cb_resop4_entry resarray[resarray_count] +// ------------------------------------------------------- + +type CBCOMPOUND4res struct { + data []byte + off1 int // byte offset within data where resarray_count starts +} + +func readCBCOMPOUND4res(b *[]byte) (CBCOMPOUND4res, bool) { + if len(*b) < 4 { + return CBCOMPOUND4res{}, false + } + start := *b + startLen := len(start) + *b = (*b)[4:] + if _, ok := readUtf8strCs(b); !ok { + return CBCOMPOUND4res{}, false + } + off1 := startLen - len(*b) + if len(*b) < 4 { + return CBCOMPOUND4res{}, false + } + c_resarray_count := int(binary.BigEndian.Uint32((*b)[:4])) + *b = (*b)[4:] + for i := 0; i < c_resarray_count; i++ { + if _, ok := readNfsCbResop4Entry(b); !ok { + return CBCOMPOUND4res{}, false + } + } + total := startLen - len(*b) + return CBCOMPOUND4res{data: start[:total], off1: off1}, true +} + +func ReadCBCOMPOUND4res(b []byte) (CBCOMPOUND4res, bool) { + return readCBCOMPOUND4res(&b) +} + +func (m CBCOMPOUND4res) Status() uint32 { + return binary.BigEndian.Uint32(m.data[0:4]) +} + +func (m CBCOMPOUND4res) Tag() Utf8strCs { + return Utf8strCs(m.data[4:m.off1]) +} + +func (m CBCOMPOUND4res) Resarray() NfsCbResop4EntryIter { + resarray_count := int(binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4])) + return NfsCbResop4EntryIter{b: m.data[m.off1+4:], count: resarray_count} +} + +func (m CBCOMPOUND4res) ResarrayCount() uint32 { + return binary.BigEndian.Uint32(m.data[m.off1 : m.off1+4]) +} + +// NfsCbResop4EntryIter iterates over variable-size NfsCbResop4Entry entries. +type NfsCbResop4EntryIter struct { + b []byte + count int + i int + cur NfsCbResop4Entry +} + +func (it *NfsCbResop4EntryIter) Next() bool { + if it.i >= it.count { + return false + } + var ok bool + it.cur, ok = readNfsCbResop4Entry(&it.b) + if !ok { + return false + } + it.i++ + return true +} + +func (it *NfsCbResop4EntryIter) Resarray() NfsCbResop4Entry { + return it.cur +} + +// CBCOMPOUND4resWriter writes a CB_COMPOUND4res: +// +// status + tag + resarray_count + nfs_cb_resop4_entry resarray[resarray_count] +type CBCOMPOUND4resWriter struct { + buf []byte + off int + resarrayCount uint32 + resarrayCountOff int + phase uint8 +} + +func StartCBCOMPOUND4res(buf []byte) CBCOMPOUND4resWriter { + off := len(buf) + buf = append(buf, make([]byte, 4)...) // status(4) + return CBCOMPOUND4resWriter{buf: buf, off: off} +} + +func (w *CBCOMPOUND4resWriter) SetStatus(v uint32) { + binary.BigEndian.PutUint32(w.buf[w.off:w.off+4], v) +} + +func (w *CBCOMPOUND4resWriter) AppendResarray_Getattr() CBGETATTR4resEntryWriter { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = binary.BigEndian.AppendUint32(w.buf, OP_CB_GETATTR) + w.resarrayCount++ + child := StartCBGETATTR4resEntry(w.buf) + w.buf = nil + return child +} + +func (w *CBCOMPOUND4resWriter) AppendResarray_Recall() CBRECALL4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+cBRECALL4resSize)...) + p := (*[4 + cBRECALL4resSize]byte)(w.buf[len(w.buf)-4-cBRECALL4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_CB_RECALL) + w.resarrayCount++ + return CBRECALL4res{m: (*[cBRECALL4resSize]byte)(p[4:])} +} + +func (w *CBCOMPOUND4resWriter) AppendResarray_Illegal() CBILLEGAL4res { + _ = w.buf[:1] + if w.phase > 1 { + panic("writer fields called out of order") + } + w.phase = 1 + if w.resarrayCountOff == 0 { + w.resarrayCountOff = len(w.buf) + w.buf = binary.BigEndian.AppendUint32(w.buf, 0) + } + w.buf = append(w.buf, make([]byte, 4+cBILLEGAL4resSize)...) + p := (*[4 + cBILLEGAL4resSize]byte)(w.buf[len(w.buf)-4-cBILLEGAL4resSize:]) + binary.BigEndian.PutUint32(p[:4], OP_CB_ILLEGAL) + w.resarrayCount++ + return CBILLEGAL4res{m: (*[cBILLEGAL4resSize]byte)(p[4:])} +} + +func (w *CBCOMPOUND4resWriter) StartTag() Utf8strCsWriter { + if w.phase > 0 { + panic("writer fields called out of order") + } + child := StartUtf8strCs(w.buf) + w.buf = nil + return child +} + +func (w *CBCOMPOUND4resWriter) Resume(buf []byte) { + w.buf = buf +} + +func (w *CBCOMPOUND4resWriter) Finish() []byte { + binary.BigEndian.PutUint32(w.buf[w.resarrayCountOff:w.resarrayCountOff+4], w.resarrayCount) + return w.buf +} + +// ------------------------------------------------------- +// Pretty-printing +// ------------------------------------------------------- + +func NfsFtype4Name(v uint32) string { + switch v { + case NF4REG: + return "NF4REG" + case NF4DIR: + return "NF4DIR" + case NF4BLK: + return "NF4BLK" + case NF4CHR: + return "NF4CHR" + case NF4LNK: + return "NF4LNK" + case NF4SOCK: + return "NF4SOCK" + case NF4FIFO: + return "NF4FIFO" + case NF4ATTRDIR: + return "NF4ATTRDIR" + case NF4NAMEDATTR: + return "NF4NAMEDATTR" + default: + return fmt.Sprintf("nfs_ftype4(%v)", v) + } +} + +func Nfsstat4Name(v uint32) string { + switch v { + case NFS4_OK: + return "NFS4_OK" + case NFS4ERR_PERM: + return "NFS4ERR_PERM" + case NFS4ERR_NOENT: + return "NFS4ERR_NOENT" + case NFS4ERR_IO: + return "NFS4ERR_IO" + case NFS4ERR_NXIO: + return "NFS4ERR_NXIO" + case NFS4ERR_ACCESS: + return "NFS4ERR_ACCESS" + case NFS4ERR_EXIST: + return "NFS4ERR_EXIST" + case NFS4ERR_XDEV: + return "NFS4ERR_XDEV" + case NFS4ERR_NOTDIR: + return "NFS4ERR_NOTDIR" + case NFS4ERR_ISDIR: + return "NFS4ERR_ISDIR" + case NFS4ERR_INVAL: + return "NFS4ERR_INVAL" + case NFS4ERR_FBIG: + return "NFS4ERR_FBIG" + case NFS4ERR_NOSPC: + return "NFS4ERR_NOSPC" + case NFS4ERR_ROFS: + return "NFS4ERR_ROFS" + case NFS4ERR_MLINK: + return "NFS4ERR_MLINK" + case NFS4ERR_NAMETOOLONG: + return "NFS4ERR_NAMETOOLONG" + case NFS4ERR_NOTEMPTY: + return "NFS4ERR_NOTEMPTY" + case NFS4ERR_DQUOT: + return "NFS4ERR_DQUOT" + case NFS4ERR_STALE: + return "NFS4ERR_STALE" + case NFS4ERR_BADHANDLE: + return "NFS4ERR_BADHANDLE" + case NFS4ERR_BAD_COOKIE: + return "NFS4ERR_BAD_COOKIE" + case NFS4ERR_NOTSUPP: + return "NFS4ERR_NOTSUPP" + case NFS4ERR_TOOSMALL: + return "NFS4ERR_TOOSMALL" + case NFS4ERR_SERVERFAULT: + return "NFS4ERR_SERVERFAULT" + case NFS4ERR_BADTYPE: + return "NFS4ERR_BADTYPE" + case NFS4ERR_DELAY: + return "NFS4ERR_DELAY" + case NFS4ERR_SAME: + return "NFS4ERR_SAME" + case NFS4ERR_DENIED: + return "NFS4ERR_DENIED" + case NFS4ERR_EXPIRED: + return "NFS4ERR_EXPIRED" + case NFS4ERR_LOCKED: + return "NFS4ERR_LOCKED" + case NFS4ERR_GRACE: + return "NFS4ERR_GRACE" + case NFS4ERR_FHEXPIRED: + return "NFS4ERR_FHEXPIRED" + case NFS4ERR_SHARE_DENIED: + return "NFS4ERR_SHARE_DENIED" + case NFS4ERR_WRONGSEC: + return "NFS4ERR_WRONGSEC" + case NFS4ERR_CLID_INUSE: + return "NFS4ERR_CLID_INUSE" + case NFS4ERR_RESOURCE: + return "NFS4ERR_RESOURCE" + case NFS4ERR_MOVED: + return "NFS4ERR_MOVED" + case NFS4ERR_NOFILEHANDLE: + return "NFS4ERR_NOFILEHANDLE" + case NFS4ERR_MINOR_VERS_MISMATCH: + return "NFS4ERR_MINOR_VERS_MISMATCH" + case NFS4ERR_STALE_CLIENTID: + return "NFS4ERR_STALE_CLIENTID" + case NFS4ERR_STALE_STATEID: + return "NFS4ERR_STALE_STATEID" + case NFS4ERR_OLD_STATEID: + return "NFS4ERR_OLD_STATEID" + case NFS4ERR_BAD_STATEID: + return "NFS4ERR_BAD_STATEID" + case NFS4ERR_BAD_SEQID: + return "NFS4ERR_BAD_SEQID" + case NFS4ERR_NOT_SAME: + return "NFS4ERR_NOT_SAME" + case NFS4ERR_LOCK_RANGE: + return "NFS4ERR_LOCK_RANGE" + case NFS4ERR_SYMLINK: + return "NFS4ERR_SYMLINK" + case NFS4ERR_RESTOREFH: + return "NFS4ERR_RESTOREFH" + case NFS4ERR_LEASE_MOVED: + return "NFS4ERR_LEASE_MOVED" + case NFS4ERR_ATTRNOTSUPP: + return "NFS4ERR_ATTRNOTSUPP" + case NFS4ERR_NO_GRACE: + return "NFS4ERR_NO_GRACE" + case NFS4ERR_RECLAIM_BAD: + return "NFS4ERR_RECLAIM_BAD" + case NFS4ERR_RECLAIM_CONFLICT: + return "NFS4ERR_RECLAIM_CONFLICT" + case NFS4ERR_BADXDR: + return "NFS4ERR_BADXDR" + case NFS4ERR_LOCKS_HELD: + return "NFS4ERR_LOCKS_HELD" + case NFS4ERR_OPENMODE: + return "NFS4ERR_OPENMODE" + case NFS4ERR_BADOWNER: + return "NFS4ERR_BADOWNER" + case NFS4ERR_BADCHAR: + return "NFS4ERR_BADCHAR" + case NFS4ERR_BADNAME: + return "NFS4ERR_BADNAME" + case NFS4ERR_BAD_RANGE: + return "NFS4ERR_BAD_RANGE" + case NFS4ERR_LOCK_NOTSUPP: + return "NFS4ERR_LOCK_NOTSUPP" + case NFS4ERR_OP_ILLEGAL: + return "NFS4ERR_OP_ILLEGAL" + case NFS4ERR_DEADLOCK: + return "NFS4ERR_DEADLOCK" + case NFS4ERR_FILE_OPEN: + return "NFS4ERR_FILE_OPEN" + case NFS4ERR_ADMIN_REVOKED: + return "NFS4ERR_ADMIN_REVOKED" + case NFS4ERR_CB_PATH_DOWN: + return "NFS4ERR_CB_PATH_DOWN" + default: + return fmt.Sprintf("nfsstat4(%v)", v) + } +} + +func TimeHow4Name(v uint32) string { + switch v { + case SET_TO_SERVER_TIME4: + return "SET_TO_SERVER_TIME4" + case SET_TO_CLIENT_TIME4: + return "SET_TO_CLIENT_TIME4" + default: + return fmt.Sprintf("time_how4(%v)", v) + } +} + +func NfsLockType4Name(v uint32) string { + switch v { + case READ_LT: + return "READ_LT" + case WRITE_LT: + return "WRITE_LT" + case READW_LT: + return "READW_LT" + case WRITEW_LT: + return "WRITEW_LT" + default: + return fmt.Sprintf("nfs_lock_type4(%v)", v) + } +} + +func XdrBoolName(v uint32) string { + switch v { + case FALSE: + return "FALSE" + case TRUE: + return "TRUE" + default: + return fmt.Sprintf("xdr_bool(%v)", v) + } +} + +func Createmode4Name(v uint32) string { + switch v { + case UNCHECKED4: + return "UNCHECKED4" + case GUARDED4: + return "GUARDED4" + case EXCLUSIVE4: + return "EXCLUSIVE4" + default: + return fmt.Sprintf("createmode4(%v)", v) + } +} + +func Opentype4Name(v uint32) string { + switch v { + case OPEN4_NOCREATE: + return "OPEN4_NOCREATE" + case OPEN4_CREATE: + return "OPEN4_CREATE" + default: + return fmt.Sprintf("opentype4(%v)", v) + } +} + +func LimitBy4Name(v uint32) string { + switch v { + case NFS_LIMIT_SIZE: + return "NFS_LIMIT_SIZE" + case NFS_LIMIT_BLOCKS: + return "NFS_LIMIT_BLOCKS" + default: + return fmt.Sprintf("limit_by4(%v)", v) + } +} + +func OpenDelegationType4Name(v uint32) string { + switch v { + case OPEN_DELEGATE_NONE: + return "OPEN_DELEGATE_NONE" + case OPEN_DELEGATE_READ: + return "OPEN_DELEGATE_READ" + case OPEN_DELEGATE_WRITE: + return "OPEN_DELEGATE_WRITE" + default: + return fmt.Sprintf("open_delegation_type4(%v)", v) + } +} + +func OpenClaimType4Name(v uint32) string { + switch v { + case CLAIM_NULL: + return "CLAIM_NULL" + case CLAIM_PREVIOUS: + return "CLAIM_PREVIOUS" + case CLAIM_DELEGATE_CUR: + return "CLAIM_DELEGATE_CUR" + case CLAIM_DELEGATE_PREV: + return "CLAIM_DELEGATE_PREV" + default: + return fmt.Sprintf("open_claim_type4(%v)", v) + } +} + +func RpcGssSvcTName(v uint32) string { + switch v { + case RPC_GSS_SVC_NONE: + return "RPC_GSS_SVC_NONE" + case RPC_GSS_SVC_INTEGRITY: + return "RPC_GSS_SVC_INTEGRITY" + case RPC_GSS_SVC_PRIVACY: + return "RPC_GSS_SVC_PRIVACY" + default: + return fmt.Sprintf("rpc_gss_svc_t(%v)", v) + } +} + +func DiscSecinfo4Name(v uint32) string { + switch v { + case RPCSEC_GSS: + return "RPCSEC_GSS" + default: + return fmt.Sprintf("disc_secinfo4(%v)", v) + } +} + +func StableHow4Name(v uint32) string { + switch v { + case UNSTABLE4: + return "UNSTABLE4" + case DATA_SYNC4: + return "DATA_SYNC4" + case FILE_SYNC4: + return "FILE_SYNC4" + default: + return fmt.Sprintf("stable_how4(%v)", v) + } +} + +func NfsOpnum4Name(v uint32) string { + switch v { + case OP_ACCESS: + return "OP_ACCESS" + case OP_CLOSE: + return "OP_CLOSE" + case OP_COMMIT: + return "OP_COMMIT" + case OP_CREATE: + return "OP_CREATE" + case OP_DELEGPURGE: + return "OP_DELEGPURGE" + case OP_DELEGRETURN: + return "OP_DELEGRETURN" + case OP_GETATTR: + return "OP_GETATTR" + case OP_GETFH: + return "OP_GETFH" + case OP_LINK: + return "OP_LINK" + case OP_LOCK: + return "OP_LOCK" + case OP_LOCKT: + return "OP_LOCKT" + case OP_LOCKU: + return "OP_LOCKU" + case OP_LOOKUP: + return "OP_LOOKUP" + case OP_LOOKUPP: + return "OP_LOOKUPP" + case OP_NVERIFY: + return "OP_NVERIFY" + case OP_OPEN: + return "OP_OPEN" + case OP_OPENATTR: + return "OP_OPENATTR" + case OP_OPEN_CONFIRM: + return "OP_OPEN_CONFIRM" + case OP_OPEN_DOWNGRADE: + return "OP_OPEN_DOWNGRADE" + case OP_PUTFH: + return "OP_PUTFH" + case OP_PUTPUBFH: + return "OP_PUTPUBFH" + case OP_PUTROOTFH: + return "OP_PUTROOTFH" + case OP_READ: + return "OP_READ" + case OP_READDIR: + return "OP_READDIR" + case OP_READLINK: + return "OP_READLINK" + case OP_REMOVE: + return "OP_REMOVE" + case OP_RENAME: + return "OP_RENAME" + case OP_RENEW: + return "OP_RENEW" + case OP_RESTOREFH: + return "OP_RESTOREFH" + case OP_SAVEFH: + return "OP_SAVEFH" + case OP_SECINFO: + return "OP_SECINFO" + case OP_SETATTR: + return "OP_SETATTR" + case OP_SETCLIENTID: + return "OP_SETCLIENTID" + case OP_SETCLIENTID_CONFIRM: + return "OP_SETCLIENTID_CONFIRM" + case OP_VERIFY: + return "OP_VERIFY" + case OP_WRITE: + return "OP_WRITE" + case OP_RELEASE_LOCKOWNER: + return "OP_RELEASE_LOCKOWNER" + case OP_ILLEGAL: + return "OP_ILLEGAL" + default: + return fmt.Sprintf("nfs_opnum4(%v)", v) + } +} + +func NfsCbOpnum4Name(v uint32) string { + switch v { + case OP_CB_GETATTR: + return "OP_CB_GETATTR" + case OP_CB_RECALL: + return "OP_CB_RECALL" + case OP_CB_ILLEGAL: + return "OP_CB_ILLEGAL" + default: + return fmt.Sprintf("nfs_cb_opnum4(%v)", v) + } +} + +func (m Attrlist4) String() string { + var b strings.Builder + b.WriteString("Attrlist4{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Bitmap4) String() string { + var b strings.Builder + b.WriteString("Bitmap4{") + { + n := int(m.Count()) + fmt.Fprintf(&b, "data: [") + for i := 0; i < n && i < 64; i++ { + if i > 0 { + b.WriteString(", ") + } + fmt.Fprintf(&b, "%d", m.Data(i)) + } + if n > 64 { + fmt.Fprintf(&b, ", ...(%d total)", n) + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m NfsFh4) String() string { + var b strings.Builder + b.WriteString("NfsFh4{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m SecOid4) String() string { + var b strings.Builder + b.WriteString("SecOid4{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Utf8strCis) String() string { + var b strings.Builder + b.WriteString("Utf8strCis{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Utf8strCs) String() string { + var b strings.Builder + b.WriteString("Utf8strCs{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Utf8strMixed) String() string { + var b strings.Builder + b.WriteString("Utf8strMixed{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Component4) String() string { + var b strings.Builder + b.WriteString("Component4{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Linktext4) String() string { + var b strings.Builder + b.WriteString("Linktext4{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Pathname4) String() string { + var b strings.Builder + b.WriteString("Pathname4{") + { + iter := m.Data() + fmt.Fprintf(&b, "data: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Data()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m Verifier4) String() string { + var b strings.Builder + b.WriteString("Verifier4{") + fmt.Fprintf(&b, "data: [") + for i := 0; i < 8; i++ { + if i > 0 { + b.WriteString(", ") + } + fmt.Fprintf(&b, "%d", m.Data(i)) + } + b.WriteString("]") + b.WriteString("}") + return b.String() +} + +func (m Nfstime4) String() string { + var b strings.Builder + b.WriteString("Nfstime4{") + fmt.Fprintf(&b, "seconds: %d", m.Seconds()) + fmt.Fprintf(&b, ", nseconds: %d", m.Nseconds()) + b.WriteString("}") + return b.String() +} + +func (m Settime4) String() string { + switch m.disc { + case SET_TO_CLIENT_TIME4: + return fmt.Sprintf("CLIENT_TIME4:%v", m.AsNfstime4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m Fsid4) String() string { + var b strings.Builder + b.WriteString("Fsid4{") + fmt.Fprintf(&b, "major: %d", m.Major()) + fmt.Fprintf(&b, ", minor: %d", m.Minor()) + b.WriteString("}") + return b.String() +} + +func (m FsLocation4) String() string { + var b strings.Builder + b.WriteString("FsLocation4{") + { + iter := m.Server() + fmt.Fprintf(&b, "server: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Server()) + i++ + } + b.WriteString("]") + } + fmt.Fprintf(&b, ", rootpath: %v", m.Rootpath()) + b.WriteString("}") + return b.String() +} + +func (m FsLocations4) String() string { + var b strings.Builder + b.WriteString("FsLocations4{") + fmt.Fprintf(&b, "fs_root: %v", m.FsRoot()) + { + iter := m.Locations() + fmt.Fprintf(&b, ", locations: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Location()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m Nfsace4) String() string { + var b strings.Builder + b.WriteString("Nfsace4{") + fmt.Fprintf(&b, "type_val: %d", m.TypeVal()) + fmt.Fprintf(&b, ", flag: %d", m.Flag()) + fmt.Fprintf(&b, ", access_mask: %d", m.AccessMask()) + fmt.Fprintf(&b, ", who: %v", m.Who()) + b.WriteString("}") + return b.String() +} + +func (m Specdata4) String() string { + var b strings.Builder + b.WriteString("Specdata4{") + fmt.Fprintf(&b, "specdata1: %d", m.Specdata1()) + fmt.Fprintf(&b, ", specdata2: %d", m.Specdata2()) + b.WriteString("}") + return b.String() +} + +func (m Fattr4) String() string { + var b strings.Builder + b.WriteString("Fattr4{") + fmt.Fprintf(&b, "attrmask: %v", m.Attrmask()) + fmt.Fprintf(&b, ", attr_vals: %v", m.AttrVals()) + b.WriteString("}") + return b.String() +} + +func (m ChangeInfo4) String() string { + var b strings.Builder + b.WriteString("ChangeInfo4{") + fmt.Fprintf(&b, "atomic: %d", m.Atomic()) + fmt.Fprintf(&b, ", before: %d", m.Before()) + fmt.Fprintf(&b, ", after: %d", m.After()) + b.WriteString("}") + return b.String() +} + +func (m XdrOpaque) String() string { + var b strings.Builder + b.WriteString("XdrOpaque{") + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, "data: %x", d) + } else { + fmt.Fprintf(&b, "data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m Clientaddr4) String() string { + var b strings.Builder + b.WriteString("Clientaddr4{") + fmt.Fprintf(&b, "r_netid: %v", m.RNetid()) + fmt.Fprintf(&b, ", r_addr: %v", m.RAddr()) + b.WriteString("}") + return b.String() +} + +func (m CbClient4) String() string { + var b strings.Builder + b.WriteString("CbClient4{") + fmt.Fprintf(&b, "cb_program: %d", m.CbProgram()) + fmt.Fprintf(&b, ", cb_location: %v", m.CbLocation()) + b.WriteString("}") + return b.String() +} + +func (m Stateid4) String() string { + var b strings.Builder + b.WriteString("Stateid4{") + fmt.Fprintf(&b, "seqid: %d", m.Seqid()) + fmt.Fprintf(&b, ", other: [") + for i := 0; i < 12; i++ { + if i > 0 { + b.WriteString(", ") + } + fmt.Fprintf(&b, "%d", m.Other(i)) + } + b.WriteString("]") + b.WriteString("}") + return b.String() +} + +func (m NfsClientId4) String() string { + var b strings.Builder + b.WriteString("NfsClientId4{") + fmt.Fprintf(&b, "verifier: %v", m.Verifier()) + { + d := m.Id() + if len(d) <= 32 { + fmt.Fprintf(&b, ", id: %x", d) + } else { + fmt.Fprintf(&b, ", id: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m OpenOwner4) String() string { + var b strings.Builder + b.WriteString("OpenOwner4{") + fmt.Fprintf(&b, "clientid: %d", m.Clientid()) + { + d := m.Owner() + if len(d) <= 32 { + fmt.Fprintf(&b, ", owner: %x", d) + } else { + fmt.Fprintf(&b, ", owner: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m LockOwner4) String() string { + var b strings.Builder + b.WriteString("LockOwner4{") + fmt.Fprintf(&b, "clientid: %d", m.Clientid()) + { + d := m.Owner() + if len(d) <= 32 { + fmt.Fprintf(&b, ", owner: %x", d) + } else { + fmt.Fprintf(&b, ", owner: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m ACCESS4args) String() string { + var b strings.Builder + b.WriteString("ACCESS4args{") + fmt.Fprintf(&b, "access: %d", m.Access()) + b.WriteString("}") + return b.String() +} + +func (m ACCESS4resok) String() string { + var b strings.Builder + b.WriteString("ACCESS4resok{") + fmt.Fprintf(&b, "supported: %d", m.Supported()) + fmt.Fprintf(&b, ", access: %d", m.Access()) + b.WriteString("}") + return b.String() +} + +func (m ACCESS4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsACCESS4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m CLOSE4args) String() string { + var b strings.Builder + b.WriteString("CLOSE4args{") + fmt.Fprintf(&b, "seqid: %d", m.Seqid()) + fmt.Fprintf(&b, ", open_stateid: %v", m.OpenStateid()) + b.WriteString("}") + return b.String() +} + +func (m CLOSE4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsStateid4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m COMMIT4args) String() string { + var b strings.Builder + b.WriteString("COMMIT4args{") + fmt.Fprintf(&b, "offset: %d", m.Offset()) + fmt.Fprintf(&b, ", count: %d", m.Count()) + b.WriteString("}") + return b.String() +} + +func (m COMMIT4resok) String() string { + var b strings.Builder + b.WriteString("COMMIT4resok{") + fmt.Fprintf(&b, "writeverf: %v", m.Writeverf()) + b.WriteString("}") + return b.String() +} + +func (m COMMIT4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsCOMMIT4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m Createtype4) String() string { + switch m.disc { + case NF4LNK: + return fmt.Sprintf("NF4LNK:%v", m.AsLinktext4()) + case NF4BLK: + return fmt.Sprintf("NF4BLK:%v", m.AsNf4blk()) + case NF4CHR: + return fmt.Sprintf("NF4CHR:%v", m.AsNf4chr()) + case NF4SOCK: + return "NF4SOCK" + case NF4FIFO: + return "NF4FIFO" + case NF4DIR: + return "NF4DIR" + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m CREATE4args) String() string { + var b strings.Builder + b.WriteString("CREATE4args{") + fmt.Fprintf(&b, "objtype: %v", m.Objtype()) + fmt.Fprintf(&b, ", objname: %v", m.Objname()) + fmt.Fprintf(&b, ", createattrs: %v", m.Createattrs()) + b.WriteString("}") + return b.String() +} + +func (m CREATE4resok) String() string { + var b strings.Builder + b.WriteString("CREATE4resok{") + fmt.Fprintf(&b, "cinfo: %v", m.Cinfo()) + fmt.Fprintf(&b, ", attrset: %v", m.Attrset()) + b.WriteString("}") + return b.String() +} + +func (m CREATE4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsCREATE4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m DELEGPURGE4args) String() string { + var b strings.Builder + b.WriteString("DELEGPURGE4args{") + fmt.Fprintf(&b, "clientid: %d", m.Clientid()) + b.WriteString("}") + return b.String() +} + +func (m DELEGPURGE4res) String() string { + var b strings.Builder + b.WriteString("DELEGPURGE4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m DELEGRETURN4args) String() string { + var b strings.Builder + b.WriteString("DELEGRETURN4args{") + fmt.Fprintf(&b, "deleg_stateid: %v", m.DelegStateid()) + b.WriteString("}") + return b.String() +} + +func (m DELEGRETURN4res) String() string { + var b strings.Builder + b.WriteString("DELEGRETURN4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m GETATTR4args) String() string { + var b strings.Builder + b.WriteString("GETATTR4args{") + fmt.Fprintf(&b, "attr_request: %v", m.AttrRequest()) + b.WriteString("}") + return b.String() +} + +func (m GETATTR4resok) String() string { + var b strings.Builder + b.WriteString("GETATTR4resok{") + fmt.Fprintf(&b, "obj_attributes: %v", m.ObjAttributes()) + b.WriteString("}") + return b.String() +} + +func (m GETATTR4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsGETATTR4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m GETFH4resok) String() string { + var b strings.Builder + b.WriteString("GETFH4resok{") + fmt.Fprintf(&b, "object: %v", m.Object()) + b.WriteString("}") + return b.String() +} + +func (m GETFH4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsGETFH4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m LINK4args) String() string { + var b strings.Builder + b.WriteString("LINK4args{") + fmt.Fprintf(&b, "newname: %v", m.Newname()) + b.WriteString("}") + return b.String() +} + +func (m LINK4resok) String() string { + var b strings.Builder + b.WriteString("LINK4resok{") + fmt.Fprintf(&b, "cinfo: %v", m.Cinfo()) + b.WriteString("}") + return b.String() +} + +func (m LINK4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsLINK4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m OpenToLockOwner4) String() string { + var b strings.Builder + b.WriteString("OpenToLockOwner4{") + fmt.Fprintf(&b, "open_seqid: %d", m.OpenSeqid()) + fmt.Fprintf(&b, ", open_stateid: %v", m.OpenStateid()) + fmt.Fprintf(&b, ", lock_seqid: %d", m.LockSeqid()) + fmt.Fprintf(&b, ", lock_owner: %v", m.LockOwner()) + b.WriteString("}") + return b.String() +} + +func (m ExistLockOwner4) String() string { + var b strings.Builder + b.WriteString("ExistLockOwner4{") + fmt.Fprintf(&b, "lock_stateid: %v", m.LockStateid()) + fmt.Fprintf(&b, ", lock_seqid: %d", m.LockSeqid()) + b.WriteString("}") + return b.String() +} + +func (m Locker4) String() string { + switch m.disc { + case TRUE: + return fmt.Sprintf("TRUE:%v", m.AsOpenToLockOwner4()) + case FALSE: + return fmt.Sprintf("FALSE:%v", m.AsExistLockOwner4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m LOCK4args) String() string { + var b strings.Builder + b.WriteString("LOCK4args{") + fmt.Fprintf(&b, "locktype: %s", NfsLockType4Name(m.Locktype())) + fmt.Fprintf(&b, ", reclaim: %d", m.Reclaim()) + fmt.Fprintf(&b, ", offset: %d", m.Offset()) + fmt.Fprintf(&b, ", length: %d", m.Length()) + fmt.Fprintf(&b, ", locker: %v", m.Locker()) + b.WriteString("}") + return b.String() +} + +func (m LOCK4denied) String() string { + var b strings.Builder + b.WriteString("LOCK4denied{") + fmt.Fprintf(&b, "offset: %d", m.Offset()) + fmt.Fprintf(&b, ", length: %d", m.Length()) + fmt.Fprintf(&b, ", locktype: %s", NfsLockType4Name(m.Locktype())) + fmt.Fprintf(&b, ", owner: %v", m.Owner()) + b.WriteString("}") + return b.String() +} + +func (m LOCK4resok) String() string { + var b strings.Builder + b.WriteString("LOCK4resok{") + fmt.Fprintf(&b, "lock_stateid: %v", m.LockStateid()) + b.WriteString("}") + return b.String() +} + +func (m LOCK4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsLOCK4resok()) + case NFS4ERR_DENIED: + return fmt.Sprintf("NFS4ERR_DENIED:%v", m.AsLOCK4denied()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m LOCKT4args) String() string { + var b strings.Builder + b.WriteString("LOCKT4args{") + fmt.Fprintf(&b, "locktype: %s", NfsLockType4Name(m.Locktype())) + fmt.Fprintf(&b, ", offset: %d", m.Offset()) + fmt.Fprintf(&b, ", length: %d", m.Length()) + fmt.Fprintf(&b, ", owner: %v", m.Owner()) + b.WriteString("}") + return b.String() +} + +func (m LOCKT4res) String() string { + switch m.disc { + case NFS4ERR_DENIED: + return fmt.Sprintf("NFS4ERR_DENIED:%v", m.AsLOCK4denied()) + case NFS4_OK: + return "NFS4_OK" + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m LOCKU4args) String() string { + var b strings.Builder + b.WriteString("LOCKU4args{") + fmt.Fprintf(&b, "locktype: %s", NfsLockType4Name(m.Locktype())) + fmt.Fprintf(&b, ", seqid: %d", m.Seqid()) + fmt.Fprintf(&b, ", lock_stateid: %v", m.LockStateid()) + fmt.Fprintf(&b, ", offset: %d", m.Offset()) + fmt.Fprintf(&b, ", length: %d", m.Length()) + b.WriteString("}") + return b.String() +} + +func (m LOCKU4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsStateid4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m LOOKUP4args) String() string { + var b strings.Builder + b.WriteString("LOOKUP4args{") + fmt.Fprintf(&b, "objname: %v", m.Objname()) + b.WriteString("}") + return b.String() +} + +func (m LOOKUP4res) String() string { + var b strings.Builder + b.WriteString("LOOKUP4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m LOOKUPP4res) String() string { + var b strings.Builder + b.WriteString("LOOKUPP4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m NVERIFY4args) String() string { + var b strings.Builder + b.WriteString("NVERIFY4args{") + fmt.Fprintf(&b, "obj_attributes: %v", m.ObjAttributes()) + b.WriteString("}") + return b.String() +} + +func (m NVERIFY4res) String() string { + var b strings.Builder + b.WriteString("NVERIFY4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m Createhow4) String() string { + switch m.disc { + case UNCHECKED4: + return fmt.Sprintf("UNCHECKED4:%v", m.AsUnchecked4()) + case GUARDED4: + return fmt.Sprintf("GUARDED4:%v", m.AsGuarded4()) + case EXCLUSIVE4: + return fmt.Sprintf("EXCLUSIVE4:%v", m.AsVerifier4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m Createhow4Entry) String() string { + var b strings.Builder + b.WriteString("Createhow4Entry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m Openflag4) String() string { + switch m.disc { + case OPEN4_CREATE: + return fmt.Sprintf("CREATE:%v", m.AsCreatehow4Entry()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m NfsModifiedLimit4) String() string { + var b strings.Builder + b.WriteString("NfsModifiedLimit4{") + fmt.Fprintf(&b, "num_blocks: %d", m.NumBlocks()) + fmt.Fprintf(&b, ", bytes_per_block: %d", m.BytesPerBlock()) + b.WriteString("}") + return b.String() +} + +func (m NfsSpaceLimit4NFSLIMITSIZE) String() string { + var b strings.Builder + b.WriteString("NfsSpaceLimit4NFSLIMITSIZE{") + fmt.Fprintf(&b, "value: %d", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m NfsSpaceLimit4) String() string { + switch m.disc { + case NFS_LIMIT_SIZE: + return fmt.Sprintf("SIZE:%v", m.AsNfsSpaceLimit4NFSLIMITSIZE()) + case NFS_LIMIT_BLOCKS: + return fmt.Sprintf("BLOCKS:%v", m.AsNfsModifiedLimit4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m OpenClaimDelegateCur4) String() string { + var b strings.Builder + b.WriteString("OpenClaimDelegateCur4{") + fmt.Fprintf(&b, "delegate_stateid: %v", m.DelegateStateid()) + fmt.Fprintf(&b, ", file: %v", m.File()) + b.WriteString("}") + return b.String() +} + +func (m OpenClaim4CLAIMPREVIOUS) String() string { + var b strings.Builder + b.WriteString("OpenClaim4CLAIMPREVIOUS{") + fmt.Fprintf(&b, "value: %s", OpenDelegationType4Name(m.Value())) + b.WriteString("}") + return b.String() +} + +func (m OpenClaim4) String() string { + switch m.disc { + case CLAIM_NULL: + return fmt.Sprintf("NULL:%v", m.AsNull()) + case CLAIM_PREVIOUS: + return fmt.Sprintf("PREVIOUS:%v", m.AsOpenClaim4CLAIMPREVIOUS()) + case CLAIM_DELEGATE_CUR: + return fmt.Sprintf("DELEGATE_CUR:%v", m.AsOpenClaimDelegateCur4()) + case CLAIM_DELEGATE_PREV: + return fmt.Sprintf("DELEGATE_PREV:%v", m.AsDelegatePrev()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m OPEN4args) String() string { + var b strings.Builder + b.WriteString("OPEN4args{") + fmt.Fprintf(&b, "seqid: %d", m.Seqid()) + fmt.Fprintf(&b, ", share_access: %d", m.ShareAccess()) + fmt.Fprintf(&b, ", share_deny: %d", m.ShareDeny()) + fmt.Fprintf(&b, ", owner: %v", m.Owner()) + fmt.Fprintf(&b, ", openhow: %v", m.Openhow()) + fmt.Fprintf(&b, ", claim: %v", m.Claim()) + b.WriteString("}") + return b.String() +} + +func (m OpenReadDelegation4) String() string { + var b strings.Builder + b.WriteString("OpenReadDelegation4{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", recall: %d", m.Recall()) + fmt.Fprintf(&b, ", permissions: %v", m.Permissions()) + b.WriteString("}") + return b.String() +} + +func (m OpenWriteDelegation4) String() string { + var b strings.Builder + b.WriteString("OpenWriteDelegation4{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", recall: %d", m.Recall()) + fmt.Fprintf(&b, ", space_limit: %v", m.SpaceLimit()) + fmt.Fprintf(&b, ", permissions: %v", m.Permissions()) + b.WriteString("}") + return b.String() +} + +func (m OpenDelegation4) String() string { + switch m.disc { + case OPEN_DELEGATE_NONE: + return "NONE" + case OPEN_DELEGATE_READ: + return fmt.Sprintf("READ:%v", m.AsOpenReadDelegation4()) + case OPEN_DELEGATE_WRITE: + return fmt.Sprintf("WRITE:%v", m.AsOpenWriteDelegation4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m OPEN4resok) String() string { + var b strings.Builder + b.WriteString("OPEN4resok{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", cinfo: %v", m.Cinfo()) + fmt.Fprintf(&b, ", rflags: %d", m.Rflags()) + fmt.Fprintf(&b, ", attrset: %v", m.Attrset()) + fmt.Fprintf(&b, ", delegation: %v", m.Delegation()) + b.WriteString("}") + return b.String() +} + +func (m OPEN4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsOPEN4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m OPENATTR4args) String() string { + var b strings.Builder + b.WriteString("OPENATTR4args{") + fmt.Fprintf(&b, "createdir: %d", m.Createdir()) + b.WriteString("}") + return b.String() +} + +func (m OPENATTR4res) String() string { + var b strings.Builder + b.WriteString("OPENATTR4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m OPENCONFIRM4args) String() string { + var b strings.Builder + b.WriteString("OPENCONFIRM4args{") + fmt.Fprintf(&b, "open_stateid: %v", m.OpenStateid()) + fmt.Fprintf(&b, ", seqid: %d", m.Seqid()) + b.WriteString("}") + return b.String() +} + +func (m OPENCONFIRM4resok) String() string { + var b strings.Builder + b.WriteString("OPENCONFIRM4resok{") + fmt.Fprintf(&b, "open_stateid: %v", m.OpenStateid()) + b.WriteString("}") + return b.String() +} + +func (m OPENCONFIRM4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsOPENCONFIRM4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m OPENDOWNGRADE4args) String() string { + var b strings.Builder + b.WriteString("OPENDOWNGRADE4args{") + fmt.Fprintf(&b, "open_stateid: %v", m.OpenStateid()) + fmt.Fprintf(&b, ", seqid: %d", m.Seqid()) + fmt.Fprintf(&b, ", share_access: %d", m.ShareAccess()) + fmt.Fprintf(&b, ", share_deny: %d", m.ShareDeny()) + b.WriteString("}") + return b.String() +} + +func (m OPENDOWNGRADE4resok) String() string { + var b strings.Builder + b.WriteString("OPENDOWNGRADE4resok{") + fmt.Fprintf(&b, "open_stateid: %v", m.OpenStateid()) + b.WriteString("}") + return b.String() +} + +func (m OPENDOWNGRADE4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsOPENDOWNGRADE4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m PUTFH4args) String() string { + var b strings.Builder + b.WriteString("PUTFH4args{") + fmt.Fprintf(&b, "object: %v", m.Object()) + b.WriteString("}") + return b.String() +} + +func (m PUTFH4res) String() string { + var b strings.Builder + b.WriteString("PUTFH4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m PUTPUBFH4res) String() string { + var b strings.Builder + b.WriteString("PUTPUBFH4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m PUTROOTFH4res) String() string { + var b strings.Builder + b.WriteString("PUTROOTFH4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m READ4args) String() string { + var b strings.Builder + b.WriteString("READ4args{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", offset: %d", m.Offset()) + fmt.Fprintf(&b, ", count: %d", m.Count()) + b.WriteString("}") + return b.String() +} + +func (m READ4resok) String() string { + var b strings.Builder + b.WriteString("READ4resok{") + fmt.Fprintf(&b, "eof: %d", m.Eof()) + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, ", data: %x", d) + } else { + fmt.Fprintf(&b, ", data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m READ4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsREAD4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m READDIR4args) String() string { + var b strings.Builder + b.WriteString("READDIR4args{") + fmt.Fprintf(&b, "cookie: %d", m.Cookie()) + fmt.Fprintf(&b, ", cookieverf: %v", m.Cookieverf()) + fmt.Fprintf(&b, ", dircount: %d", m.Dircount()) + fmt.Fprintf(&b, ", maxcount: %d", m.Maxcount()) + fmt.Fprintf(&b, ", attr_request: %v", m.AttrRequest()) + b.WriteString("}") + return b.String() +} + +func (m Entry4Opt) String() string { + switch m.disc { + case TRUE: + return fmt.Sprintf("TRUE:%v", m.AsEntry4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m Entry4) String() string { + var b strings.Builder + b.WriteString("Entry4{") + fmt.Fprintf(&b, "cookie: %d", m.Cookie()) + fmt.Fprintf(&b, ", name: %v", m.Name()) + fmt.Fprintf(&b, ", attrs: %v", m.Attrs()) + fmt.Fprintf(&b, ", nextentry: %v", m.Nextentry()) + b.WriteString("}") + return b.String() +} + +func (m Dirlist4) String() string { + var b strings.Builder + b.WriteString("Dirlist4{") + fmt.Fprintf(&b, "entries: %v", m.Entries()) + fmt.Fprintf(&b, ", eof: %d", m.Eof()) + b.WriteString("}") + return b.String() +} + +func (m READDIR4resok) String() string { + var b strings.Builder + b.WriteString("READDIR4resok{") + fmt.Fprintf(&b, "cookieverf: %v", m.Cookieverf()) + fmt.Fprintf(&b, ", reply: %v", m.Reply()) + b.WriteString("}") + return b.String() +} + +func (m READDIR4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsREADDIR4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m READLINK4resok) String() string { + var b strings.Builder + b.WriteString("READLINK4resok{") + fmt.Fprintf(&b, "link: %v", m.Link()) + b.WriteString("}") + return b.String() +} + +func (m READLINK4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsREADLINK4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m REMOVE4args) String() string { + var b strings.Builder + b.WriteString("REMOVE4args{") + fmt.Fprintf(&b, "target: %v", m.Target()) + b.WriteString("}") + return b.String() +} + +func (m REMOVE4resok) String() string { + var b strings.Builder + b.WriteString("REMOVE4resok{") + fmt.Fprintf(&b, "cinfo: %v", m.Cinfo()) + b.WriteString("}") + return b.String() +} + +func (m REMOVE4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsREMOVE4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m RENAME4args) String() string { + var b strings.Builder + b.WriteString("RENAME4args{") + fmt.Fprintf(&b, "oldname: %v", m.Oldname()) + fmt.Fprintf(&b, ", newname: %v", m.Newname()) + b.WriteString("}") + return b.String() +} + +func (m RENAME4resok) String() string { + var b strings.Builder + b.WriteString("RENAME4resok{") + fmt.Fprintf(&b, "source_cinfo: %v", m.SourceCinfo()) + fmt.Fprintf(&b, ", target_cinfo: %v", m.TargetCinfo()) + b.WriteString("}") + return b.String() +} + +func (m RENAME4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsRENAME4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m RENEW4args) String() string { + var b strings.Builder + b.WriteString("RENEW4args{") + fmt.Fprintf(&b, "clientid: %d", m.Clientid()) + b.WriteString("}") + return b.String() +} + +func (m RENEW4res) String() string { + var b strings.Builder + b.WriteString("RENEW4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m RESTOREFH4res) String() string { + var b strings.Builder + b.WriteString("RESTOREFH4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m SAVEFH4res) String() string { + var b strings.Builder + b.WriteString("SAVEFH4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m SECINFO4args) String() string { + var b strings.Builder + b.WriteString("SECINFO4args{") + fmt.Fprintf(&b, "name: %v", m.Name()) + b.WriteString("}") + return b.String() +} + +func (m RpcsecGssInfo) String() string { + var b strings.Builder + b.WriteString("RpcsecGssInfo{") + fmt.Fprintf(&b, "oid: %v", m.Oid()) + fmt.Fprintf(&b, ", qop: %d", m.Qop()) + fmt.Fprintf(&b, ", service: %s", RpcGssSvcTName(m.Service())) + b.WriteString("}") + return b.String() +} + +func (m Secinfo4) String() string { + switch m.disc { + case RPCSEC_GSS: + return fmt.Sprintf("GSS:%v", m.AsRpcsecGssInfo()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m Secinfo4Entry) String() string { + var b strings.Builder + b.WriteString("Secinfo4Entry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m SECINFO4resok) String() string { + var b strings.Builder + b.WriteString("SECINFO4resok{") + { + iter := m.Data() + fmt.Fprintf(&b, "data: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Data()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m SECINFO4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsSECINFO4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m SETATTR4args) String() string { + var b strings.Builder + b.WriteString("SETATTR4args{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", obj_attributes: %v", m.ObjAttributes()) + b.WriteString("}") + return b.String() +} + +func (m SETATTR4res) String() string { + var b strings.Builder + b.WriteString("SETATTR4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + fmt.Fprintf(&b, ", attrsset: %v", m.Attrsset()) + b.WriteString("}") + return b.String() +} + +func (m SETCLIENTID4args) String() string { + var b strings.Builder + b.WriteString("SETCLIENTID4args{") + fmt.Fprintf(&b, "client: %v", m.Client()) + fmt.Fprintf(&b, ", callback: %v", m.Callback()) + fmt.Fprintf(&b, ", callback_ident: %d", m.CallbackIdent()) + b.WriteString("}") + return b.String() +} + +func (m SETCLIENTID4resok) String() string { + var b strings.Builder + b.WriteString("SETCLIENTID4resok{") + fmt.Fprintf(&b, "clientid: %d", m.Clientid()) + fmt.Fprintf(&b, ", setclientid_confirm: %v", m.SetclientidConfirm()) + b.WriteString("}") + return b.String() +} + +func (m SETCLIENTID4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsSETCLIENTID4resok()) + case NFS4ERR_CLID_INUSE: + return fmt.Sprintf("NFS4ERR_CLID_INUSE:%v", m.AsClientaddr4()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m SETCLIENTIDCONFIRM4args) String() string { + var b strings.Builder + b.WriteString("SETCLIENTIDCONFIRM4args{") + fmt.Fprintf(&b, "clientid: %d", m.Clientid()) + fmt.Fprintf(&b, ", setclientid_confirm: %v", m.SetclientidConfirm()) + b.WriteString("}") + return b.String() +} + +func (m SETCLIENTIDCONFIRM4res) String() string { + var b strings.Builder + b.WriteString("SETCLIENTIDCONFIRM4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m VERIFY4args) String() string { + var b strings.Builder + b.WriteString("VERIFY4args{") + fmt.Fprintf(&b, "obj_attributes: %v", m.ObjAttributes()) + b.WriteString("}") + return b.String() +} + +func (m VERIFY4res) String() string { + var b strings.Builder + b.WriteString("VERIFY4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m WRITE4args) String() string { + var b strings.Builder + b.WriteString("WRITE4args{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", offset: %d", m.Offset()) + fmt.Fprintf(&b, ", stable: %s", StableHow4Name(m.Stable())) + { + d := m.Data() + if len(d) <= 32 { + fmt.Fprintf(&b, ", data: %x", d) + } else { + fmt.Fprintf(&b, ", data: %x...(%d bytes)", d[:32], len(d)) + } + } + b.WriteString("}") + return b.String() +} + +func (m WRITE4resok) String() string { + var b strings.Builder + b.WriteString("WRITE4resok{") + fmt.Fprintf(&b, "count: %d", m.Count()) + fmt.Fprintf(&b, ", committed: %s", StableHow4Name(m.Committed())) + fmt.Fprintf(&b, ", writeverf: %v", m.Writeverf()) + b.WriteString("}") + return b.String() +} + +func (m WRITE4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsWRITE4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m RELEASELOCKOWNER4args) String() string { + var b strings.Builder + b.WriteString("RELEASELOCKOWNER4args{") + fmt.Fprintf(&b, "lock_owner: %v", m.LockOwner()) + b.WriteString("}") + return b.String() +} + +func (m RELEASELOCKOWNER4res) String() string { + var b strings.Builder + b.WriteString("RELEASELOCKOWNER4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m ILLEGAL4res) String() string { + var b strings.Builder + b.WriteString("ILLEGAL4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m NfsArgop4) String() string { + switch m.disc { + case OP_ACCESS: + return fmt.Sprintf("ACCESS:%v", m.AsACCESS4args()) + case OP_CLOSE: + return fmt.Sprintf("CLOSE:%v", m.AsCLOSE4args()) + case OP_COMMIT: + return fmt.Sprintf("COMMIT:%v", m.AsCOMMIT4args()) + case OP_CREATE: + return fmt.Sprintf("CREATE:%v", m.AsCREATE4args()) + case OP_DELEGPURGE: + return fmt.Sprintf("DELEGPURGE:%v", m.AsDELEGPURGE4args()) + case OP_DELEGRETURN: + return fmt.Sprintf("DELEGRETURN:%v", m.AsDELEGRETURN4args()) + case OP_GETATTR: + return fmt.Sprintf("GETATTR:%v", m.AsGETATTR4args()) + case OP_GETFH: + return "GETFH" + case OP_LINK: + return fmt.Sprintf("LINK:%v", m.AsLINK4args()) + case OP_LOCK: + return fmt.Sprintf("LOCK:%v", m.AsLOCK4args()) + case OP_LOCKT: + return fmt.Sprintf("LOCKT:%v", m.AsLOCKT4args()) + case OP_LOCKU: + return fmt.Sprintf("LOCKU:%v", m.AsLOCKU4args()) + case OP_LOOKUP: + return fmt.Sprintf("LOOKUP:%v", m.AsLOOKUP4args()) + case OP_LOOKUPP: + return "LOOKUPP" + case OP_NVERIFY: + return fmt.Sprintf("NVERIFY:%v", m.AsNVERIFY4args()) + case OP_OPEN: + return fmt.Sprintf("OPEN:%v", m.AsOPEN4args()) + case OP_OPENATTR: + return fmt.Sprintf("OPENATTR:%v", m.AsOPENATTR4args()) + case OP_OPEN_CONFIRM: + return fmt.Sprintf("OPEN_CONFIRM:%v", m.AsOPENCONFIRM4args()) + case OP_OPEN_DOWNGRADE: + return fmt.Sprintf("OPEN_DOWNGRADE:%v", m.AsOPENDOWNGRADE4args()) + case OP_PUTFH: + return fmt.Sprintf("PUTFH:%v", m.AsPUTFH4args()) + case OP_PUTPUBFH: + return "PUTPUBFH" + case OP_PUTROOTFH: + return "PUTROOTFH" + case OP_READ: + return fmt.Sprintf("READ:%v", m.AsREAD4args()) + case OP_READDIR: + return fmt.Sprintf("READDIR:%v", m.AsREADDIR4args()) + case OP_READLINK: + return "READLINK" + case OP_REMOVE: + return fmt.Sprintf("REMOVE:%v", m.AsREMOVE4args()) + case OP_RENAME: + return fmt.Sprintf("RENAME:%v", m.AsRENAME4args()) + case OP_RENEW: + return fmt.Sprintf("RENEW:%v", m.AsRENEW4args()) + case OP_RESTOREFH: + return "RESTOREFH" + case OP_SAVEFH: + return "SAVEFH" + case OP_SECINFO: + return fmt.Sprintf("SECINFO:%v", m.AsSECINFO4args()) + case OP_SETATTR: + return fmt.Sprintf("SETATTR:%v", m.AsSETATTR4args()) + case OP_SETCLIENTID: + return fmt.Sprintf("SETCLIENTID:%v", m.AsSETCLIENTID4args()) + case OP_SETCLIENTID_CONFIRM: + return fmt.Sprintf("SETCLIENTID_CONFIRM:%v", m.AsSETCLIENTIDCONFIRM4args()) + case OP_VERIFY: + return fmt.Sprintf("VERIFY:%v", m.AsVERIFY4args()) + case OP_WRITE: + return fmt.Sprintf("WRITE:%v", m.AsWRITE4args()) + case OP_RELEASE_LOCKOWNER: + return fmt.Sprintf("RELEASE_LOCKOWNER:%v", m.AsRELEASELOCKOWNER4args()) + case OP_ILLEGAL: + return "ILLEGAL" + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m ACCESS4resEntry) String() string { + var b strings.Builder + b.WriteString("ACCESS4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m CLOSE4resEntry) String() string { + var b strings.Builder + b.WriteString("CLOSE4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m COMMIT4resEntry) String() string { + var b strings.Builder + b.WriteString("COMMIT4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m CREATE4resEntry) String() string { + var b strings.Builder + b.WriteString("CREATE4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m GETATTR4resEntry) String() string { + var b strings.Builder + b.WriteString("GETATTR4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m GETFH4resEntry) String() string { + var b strings.Builder + b.WriteString("GETFH4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m LINK4resEntry) String() string { + var b strings.Builder + b.WriteString("LINK4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m LOCK4resEntry) String() string { + var b strings.Builder + b.WriteString("LOCK4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m LOCKT4resEntry) String() string { + var b strings.Builder + b.WriteString("LOCKT4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m LOCKU4resEntry) String() string { + var b strings.Builder + b.WriteString("LOCKU4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m OPEN4resEntry) String() string { + var b strings.Builder + b.WriteString("OPEN4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m OPENCONFIRM4resEntry) String() string { + var b strings.Builder + b.WriteString("OPENCONFIRM4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m OPENDOWNGRADE4resEntry) String() string { + var b strings.Builder + b.WriteString("OPENDOWNGRADE4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m READ4resEntry) String() string { + var b strings.Builder + b.WriteString("READ4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m READDIR4resEntry) String() string { + var b strings.Builder + b.WriteString("READDIR4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m READLINK4resEntry) String() string { + var b strings.Builder + b.WriteString("READLINK4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m REMOVE4resEntry) String() string { + var b strings.Builder + b.WriteString("REMOVE4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m RENAME4resEntry) String() string { + var b strings.Builder + b.WriteString("RENAME4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m SECINFO4resEntry) String() string { + var b strings.Builder + b.WriteString("SECINFO4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m SETCLIENTID4resEntry) String() string { + var b strings.Builder + b.WriteString("SETCLIENTID4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m WRITE4resEntry) String() string { + var b strings.Builder + b.WriteString("WRITE4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m NfsResop4) String() string { + switch m.disc { + case OP_ACCESS: + return fmt.Sprintf("ACCESS:%v", m.AsACCESS4resEntry()) + case OP_CLOSE: + return fmt.Sprintf("CLOSE:%v", m.AsCLOSE4resEntry()) + case OP_COMMIT: + return fmt.Sprintf("COMMIT:%v", m.AsCOMMIT4resEntry()) + case OP_CREATE: + return fmt.Sprintf("CREATE:%v", m.AsCREATE4resEntry()) + case OP_DELEGPURGE: + return fmt.Sprintf("DELEGPURGE:%v", m.AsDELEGPURGE4res()) + case OP_DELEGRETURN: + return fmt.Sprintf("DELEGRETURN:%v", m.AsDELEGRETURN4res()) + case OP_GETATTR: + return fmt.Sprintf("GETATTR:%v", m.AsGETATTR4resEntry()) + case OP_GETFH: + return fmt.Sprintf("GETFH:%v", m.AsGETFH4resEntry()) + case OP_LINK: + return fmt.Sprintf("LINK:%v", m.AsLINK4resEntry()) + case OP_LOCK: + return fmt.Sprintf("LOCK:%v", m.AsLOCK4resEntry()) + case OP_LOCKT: + return fmt.Sprintf("LOCKT:%v", m.AsLOCKT4resEntry()) + case OP_LOCKU: + return fmt.Sprintf("LOCKU:%v", m.AsLOCKU4resEntry()) + case OP_LOOKUP: + return fmt.Sprintf("LOOKUP:%v", m.AsLOOKUP4res()) + case OP_LOOKUPP: + return fmt.Sprintf("LOOKUPP:%v", m.AsLOOKUPP4res()) + case OP_NVERIFY: + return fmt.Sprintf("NVERIFY:%v", m.AsNVERIFY4res()) + case OP_OPEN: + return fmt.Sprintf("OPEN:%v", m.AsOPEN4resEntry()) + case OP_OPENATTR: + return fmt.Sprintf("OPENATTR:%v", m.AsOPENATTR4res()) + case OP_OPEN_CONFIRM: + return fmt.Sprintf("OPEN_CONFIRM:%v", m.AsOPENCONFIRM4resEntry()) + case OP_OPEN_DOWNGRADE: + return fmt.Sprintf("OPEN_DOWNGRADE:%v", m.AsOPENDOWNGRADE4resEntry()) + case OP_PUTFH: + return fmt.Sprintf("PUTFH:%v", m.AsPUTFH4res()) + case OP_PUTPUBFH: + return fmt.Sprintf("PUTPUBFH:%v", m.AsPUTPUBFH4res()) + case OP_PUTROOTFH: + return fmt.Sprintf("PUTROOTFH:%v", m.AsPUTROOTFH4res()) + case OP_READ: + return fmt.Sprintf("READ:%v", m.AsREAD4resEntry()) + case OP_READDIR: + return fmt.Sprintf("READDIR:%v", m.AsREADDIR4resEntry()) + case OP_READLINK: + return fmt.Sprintf("READLINK:%v", m.AsREADLINK4resEntry()) + case OP_REMOVE: + return fmt.Sprintf("REMOVE:%v", m.AsREMOVE4resEntry()) + case OP_RENAME: + return fmt.Sprintf("RENAME:%v", m.AsRENAME4resEntry()) + case OP_RENEW: + return fmt.Sprintf("RENEW:%v", m.AsRENEW4res()) + case OP_RESTOREFH: + return fmt.Sprintf("RESTOREFH:%v", m.AsRESTOREFH4res()) + case OP_SAVEFH: + return fmt.Sprintf("SAVEFH:%v", m.AsSAVEFH4res()) + case OP_SECINFO: + return fmt.Sprintf("SECINFO:%v", m.AsSECINFO4resEntry()) + case OP_SETATTR: + return fmt.Sprintf("SETATTR:%v", m.AsSETATTR4res()) + case OP_SETCLIENTID: + return fmt.Sprintf("SETCLIENTID:%v", m.AsSETCLIENTID4resEntry()) + case OP_SETCLIENTID_CONFIRM: + return fmt.Sprintf("SETCLIENTID_CONFIRM:%v", m.AsSETCLIENTIDCONFIRM4res()) + case OP_VERIFY: + return fmt.Sprintf("VERIFY:%v", m.AsVERIFY4res()) + case OP_WRITE: + return fmt.Sprintf("WRITE:%v", m.AsWRITE4resEntry()) + case OP_RELEASE_LOCKOWNER: + return fmt.Sprintf("RELEASE_LOCKOWNER:%v", m.AsRELEASELOCKOWNER4res()) + case OP_ILLEGAL: + return fmt.Sprintf("ILLEGAL:%v", m.AsILLEGAL4res()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m NfsArgop4Entry) String() string { + var b strings.Builder + b.WriteString("NfsArgop4Entry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m COMPOUND4args) String() string { + var b strings.Builder + b.WriteString("COMPOUND4args{") + fmt.Fprintf(&b, "tag: %v", m.Tag()) + fmt.Fprintf(&b, ", minorversion: %d", m.Minorversion()) + { + iter := m.Argarray() + fmt.Fprintf(&b, ", argarray: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Argarray()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m NfsResop4Entry) String() string { + var b strings.Builder + b.WriteString("NfsResop4Entry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m COMPOUND4res) String() string { + var b strings.Builder + b.WriteString("COMPOUND4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + fmt.Fprintf(&b, ", tag: %v", m.Tag()) + { + iter := m.Resarray() + fmt.Fprintf(&b, ", resarray: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Resarray()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m CBGETATTR4args) String() string { + var b strings.Builder + b.WriteString("CBGETATTR4args{") + fmt.Fprintf(&b, "fh: %v", m.Fh()) + fmt.Fprintf(&b, ", attr_request: %v", m.AttrRequest()) + b.WriteString("}") + return b.String() +} + +func (m CBGETATTR4resok) String() string { + var b strings.Builder + b.WriteString("CBGETATTR4resok{") + fmt.Fprintf(&b, "obj_attributes: %v", m.ObjAttributes()) + b.WriteString("}") + return b.String() +} + +func (m CBGETATTR4res) String() string { + switch m.disc { + case NFS4_OK: + return fmt.Sprintf("NFS4_OK:%v", m.AsCBGETATTR4resok()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m CBRECALL4args) String() string { + var b strings.Builder + b.WriteString("CBRECALL4args{") + fmt.Fprintf(&b, "stateid: %v", m.Stateid()) + fmt.Fprintf(&b, ", truncate: %d", m.Truncate()) + fmt.Fprintf(&b, ", fh: %v", m.Fh()) + b.WriteString("}") + return b.String() +} + +func (m CBRECALL4res) String() string { + var b strings.Builder + b.WriteString("CBRECALL4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m CBILLEGAL4res) String() string { + var b strings.Builder + b.WriteString("CBILLEGAL4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + b.WriteString("}") + return b.String() +} + +func (m NfsCbArgop4) String() string { + switch m.disc { + case OP_CB_GETATTR: + return fmt.Sprintf("GETATTR:%v", m.AsCBGETATTR4args()) + case OP_CB_RECALL: + return fmt.Sprintf("RECALL:%v", m.AsCBRECALL4args()) + case OP_CB_ILLEGAL: + return "ILLEGAL" + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m CBGETATTR4resEntry) String() string { + var b strings.Builder + b.WriteString("CBGETATTR4resEntry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m NfsCbResop4) String() string { + switch m.disc { + case OP_CB_GETATTR: + return fmt.Sprintf("GETATTR:%v", m.AsCBGETATTR4resEntry()) + case OP_CB_RECALL: + return fmt.Sprintf("RECALL:%v", m.AsCBRECALL4res()) + case OP_CB_ILLEGAL: + return fmt.Sprintf("ILLEGAL:%v", m.AsCBILLEGAL4res()) + default: + return fmt.Sprintf("unknown(%v)", m.disc) + } +} + +func (m NfsCbArgop4Entry) String() string { + var b strings.Builder + b.WriteString("NfsCbArgop4Entry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m CBCOMPOUND4args) String() string { + var b strings.Builder + b.WriteString("CBCOMPOUND4args{") + fmt.Fprintf(&b, "tag: %v", m.Tag()) + fmt.Fprintf(&b, ", minorversion: %d", m.Minorversion()) + fmt.Fprintf(&b, ", callback_ident: %d", m.CallbackIdent()) + { + iter := m.Argarray() + fmt.Fprintf(&b, ", argarray: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Argarray()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} + +func (m NfsCbResop4Entry) String() string { + var b strings.Builder + b.WriteString("NfsCbResop4Entry{") + fmt.Fprintf(&b, "value: %v", m.Value()) + b.WriteString("}") + return b.String() +} + +func (m CBCOMPOUND4res) String() string { + var b strings.Builder + b.WriteString("CBCOMPOUND4res{") + fmt.Fprintf(&b, "status: %s", Nfsstat4Name(m.Status())) + fmt.Fprintf(&b, ", tag: %v", m.Tag()) + { + iter := m.Resarray() + fmt.Fprintf(&b, ", resarray: [") + i := 0 + for iter.Next() { + if i > 0 { + b.WriteString(", ") + } + if i >= 64 { + b.WriteString("...") + break + } + fmt.Fprintf(&b, "%v", iter.Resarray()) + i++ + } + b.WriteString("]") + } + b.WriteString("}") + return b.String() +} diff --git a/go/nfsd/nfs.msg b/go/nfsd/nfs.msg new file mode 100644 index 00000000..6d1c8d92 --- /dev/null +++ b/go/nfsd/nfs.msg @@ -0,0 +1,1321 @@ +const NFS4_FHSIZE = 128; +const NFS4_VERIFIER_SIZE = 8; +const NFS4_OTHER_SIZE = 12; +const NFS4_OPAQUE_LIMIT = 1024; +const NFS4_INT64_MAX = 0x7fffffffffffffff; +const NFS4_UINT64_MAX = 0xffffffffffffffff; +const NFS4_INT32_MAX = 0x7fffffff; +const NFS4_UINT32_MAX = 0xffffffff; +const ACL4_SUPPORT_ALLOW_ACL = 0x00000001; +const ACL4_SUPPORT_DENY_ACL = 0x00000002; +const ACL4_SUPPORT_AUDIT_ACL = 0x00000004; +const ACL4_SUPPORT_ALARM_ACL = 0x00000008; +const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; +const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; +const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; +const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; +const ACE4_FILE_INHERIT_ACE = 0x00000001; +const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; +const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; +const ACE4_INHERIT_ONLY_ACE = 0x00000008; +const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; +const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; +const ACE4_IDENTIFIER_GROUP = 0x00000040; +const ACE4_READ_DATA = 0x00000001; +const ACE4_LIST_DIRECTORY = 0x00000001; +const ACE4_WRITE_DATA = 0x00000002; +const ACE4_ADD_FILE = 0x00000002; +const ACE4_APPEND_DATA = 0x00000004; +const ACE4_ADD_SUBDIRECTORY = 0x00000004; +const ACE4_READ_NAMED_ATTRS = 0x00000008; +const ACE4_WRITE_NAMED_ATTRS = 0x00000010; +const ACE4_EXECUTE = 0x00000020; +const ACE4_DELETE_CHILD = 0x00000040; +const ACE4_READ_ATTRIBUTES = 0x00000080; +const ACE4_WRITE_ATTRIBUTES = 0x00000100; +const ACE4_DELETE = 0x00010000; +const ACE4_READ_ACL = 0x00020000; +const ACE4_WRITE_ACL = 0x00040000; +const ACE4_WRITE_OWNER = 0x00080000; +const ACE4_SYNCHRONIZE = 0x00100000; +const ACE4_GENERIC_READ = 0x00120081; +const ACE4_GENERIC_WRITE = 0x00160106; +const ACE4_GENERIC_EXECUTE = 0x001200A0; +const MODE4_SUID = 0x800; +const MODE4_SGID = 0x400; +const MODE4_SVTX = 0x200; +const MODE4_RUSR = 0x100; +const MODE4_WUSR = 0x080; +const MODE4_XUSR = 0x040; +const MODE4_RGRP = 0x020; +const MODE4_WGRP = 0x010; +const MODE4_XGRP = 0x008; +const MODE4_ROTH = 0x004; +const MODE4_WOTH = 0x002; +const MODE4_XOTH = 0x001; +const FH4_PERSISTENT = 0x00000000; +const FH4_NOEXPIRE_WITH_OPEN = 0x00000001; +const FH4_VOLATILE_ANY = 0x00000002; +const FH4_VOL_MIGRATION = 0x00000004; +const FH4_VOL_RENAME = 0x00000008; +const FATTR4_SUPPORTED_ATTRS = 0; +const FATTR4_TYPE = 1; +const FATTR4_FH_EXPIRE_TYPE = 2; +const FATTR4_CHANGE = 3; +const FATTR4_SIZE = 4; +const FATTR4_LINK_SUPPORT = 5; +const FATTR4_SYMLINK_SUPPORT = 6; +const FATTR4_NAMED_ATTR = 7; +const FATTR4_FSID = 8; +const FATTR4_UNIQUE_HANDLES = 9; +const FATTR4_LEASE_TIME = 10; +const FATTR4_RDATTR_ERROR = 11; +const FATTR4_FILEHANDLE = 19; +const FATTR4_ACL = 12; +const FATTR4_ACLSUPPORT = 13; +const FATTR4_ARCHIVE = 14; +const FATTR4_CANSETTIME = 15; +const FATTR4_CASE_INSENSITIVE = 16; +const FATTR4_CASE_PRESERVING = 17; +const FATTR4_CHOWN_RESTRICTED = 18; +const FATTR4_FILEID = 20; +const FATTR4_FILES_AVAIL = 21; +const FATTR4_FILES_FREE = 22; +const FATTR4_FILES_TOTAL = 23; +const FATTR4_FS_LOCATIONS = 24; +const FATTR4_HIDDEN = 25; +const FATTR4_HOMOGENEOUS = 26; +const FATTR4_MAXFILESIZE = 27; +const FATTR4_MAXLINK = 28; +const FATTR4_MAXNAME = 29; +const FATTR4_MAXREAD = 30; +const FATTR4_MAXWRITE = 31; +const FATTR4_MIMETYPE = 32; +const FATTR4_MODE = 33; +const FATTR4_NO_TRUNC = 34; +const FATTR4_NUMLINKS = 35; +const FATTR4_OWNER = 36; +const FATTR4_OWNER_GROUP = 37; +const FATTR4_QUOTA_AVAIL_HARD = 38; +const FATTR4_QUOTA_AVAIL_SOFT = 39; +const FATTR4_QUOTA_USED = 40; +const FATTR4_RAWDEV = 41; +const FATTR4_SPACE_AVAIL = 42; +const FATTR4_SPACE_FREE = 43; +const FATTR4_SPACE_TOTAL = 44; +const FATTR4_SPACE_USED = 45; +const FATTR4_SYSTEM = 46; +const FATTR4_TIME_ACCESS = 47; +const FATTR4_TIME_ACCESS_SET = 48; +const FATTR4_TIME_BACKUP = 49; +const FATTR4_TIME_CREATE = 50; +const FATTR4_TIME_DELTA = 51; +const FATTR4_TIME_METADATA = 52; +const FATTR4_TIME_MODIFY = 53; +const FATTR4_TIME_MODIFY_SET = 54; +const FATTR4_MOUNTED_ON_FILEID = 55; +const ACCESS4_READ = 0x00000001; +const ACCESS4_LOOKUP = 0x00000002; +const ACCESS4_MODIFY = 0x00000004; +const ACCESS4_EXTEND = 0x00000008; +const ACCESS4_DELETE = 0x00000010; +const ACCESS4_EXECUTE = 0x00000020; +const OPEN4_SHARE_ACCESS_READ = 0x00000001; +const OPEN4_SHARE_ACCESS_WRITE = 0x00000002; +const OPEN4_SHARE_ACCESS_BOTH = 0x00000003; +const OPEN4_SHARE_DENY_NONE = 0x00000000; +const OPEN4_SHARE_DENY_READ = 0x00000001; +const OPEN4_SHARE_DENY_WRITE = 0x00000002; +const OPEN4_SHARE_DENY_BOTH = 0x00000003; +const OPEN4_RESULT_CONFIRM = 0x00000002; +const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004; + +enum nfs_ftype4 : beu32 { + NF4REG = 1; + NF4DIR = 2; + NF4BLK = 3; + NF4CHR = 4; + NF4LNK = 5; + NF4SOCK = 6; + NF4FIFO = 7; + NF4ATTRDIR = 8; + NF4NAMEDATTR = 9; +} + +enum nfsstat4 : beu32 { + NFS4_OK = 0; + NFS4ERR_PERM = 1; + NFS4ERR_NOENT = 2; + NFS4ERR_IO = 5; + NFS4ERR_NXIO = 6; + NFS4ERR_ACCESS = 13; + NFS4ERR_EXIST = 17; + NFS4ERR_XDEV = 18; + NFS4ERR_NOTDIR = 20; + NFS4ERR_ISDIR = 21; + NFS4ERR_INVAL = 22; + NFS4ERR_FBIG = 27; + NFS4ERR_NOSPC = 28; + NFS4ERR_ROFS = 30; + NFS4ERR_MLINK = 31; + NFS4ERR_NAMETOOLONG = 63; + NFS4ERR_NOTEMPTY = 66; + NFS4ERR_DQUOT = 69; + NFS4ERR_STALE = 70; + NFS4ERR_BADHANDLE = 10001; + NFS4ERR_BAD_COOKIE = 10003; + NFS4ERR_NOTSUPP = 10004; + NFS4ERR_TOOSMALL = 10005; + NFS4ERR_SERVERFAULT = 10006; + NFS4ERR_BADTYPE = 10007; + NFS4ERR_DELAY = 10008; + NFS4ERR_SAME = 10009; + NFS4ERR_DENIED = 10010; + NFS4ERR_EXPIRED = 10011; + NFS4ERR_LOCKED = 10012; + NFS4ERR_GRACE = 10013; + NFS4ERR_FHEXPIRED = 10014; + NFS4ERR_SHARE_DENIED = 10015; + NFS4ERR_WRONGSEC = 10016; + NFS4ERR_CLID_INUSE = 10017; + NFS4ERR_RESOURCE = 10018; + NFS4ERR_MOVED = 10019; + NFS4ERR_NOFILEHANDLE = 10020; + NFS4ERR_MINOR_VERS_MISMATCH = 10021; + NFS4ERR_STALE_CLIENTID = 10022; + NFS4ERR_STALE_STATEID = 10023; + NFS4ERR_OLD_STATEID = 10024; + NFS4ERR_BAD_STATEID = 10025; + NFS4ERR_BAD_SEQID = 10026; + NFS4ERR_NOT_SAME = 10027; + NFS4ERR_LOCK_RANGE = 10028; + NFS4ERR_SYMLINK = 10029; + NFS4ERR_RESTOREFH = 10030; + NFS4ERR_LEASE_MOVED = 10031; + NFS4ERR_ATTRNOTSUPP = 10032; + NFS4ERR_NO_GRACE = 10033; + NFS4ERR_RECLAIM_BAD = 10034; + NFS4ERR_RECLAIM_CONFLICT = 10035; + NFS4ERR_BADXDR = 10036; + NFS4ERR_LOCKS_HELD = 10037; + NFS4ERR_OPENMODE = 10038; + NFS4ERR_BADOWNER = 10039; + NFS4ERR_BADCHAR = 10040; + NFS4ERR_BADNAME = 10041; + NFS4ERR_BAD_RANGE = 10042; + NFS4ERR_LOCK_NOTSUPP = 10043; + NFS4ERR_OP_ILLEGAL = 10044; + NFS4ERR_DEADLOCK = 10045; + NFS4ERR_FILE_OPEN = 10046; + NFS4ERR_ADMIN_REVOKED = 10047; + NFS4ERR_CB_PATH_DOWN = 10048; +} + +message attrlist4 { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message bitmap4 { + beu32 count; + beu32 data[count]; +} + +message nfs_fh4 { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message sec_oid4 { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message utf8str_cis { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message utf8str_cs { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message utf8str_mixed { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message component4 { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message linktext4 { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message pathname4 { + beu32 count; + component4 data[count]; +} + +struct verifier4 { + u8 data[8]; +} + +struct nfstime4 { + bei64 seconds; + beu32 nseconds; +} + +enum time_how4 : beu32 { + SET_TO_SERVER_TIME4 = 0; + SET_TO_CLIENT_TIME4 = 1; +} + +union settime4 (time_how4) { + SET_TO_CLIENT_TIME4: nfstime4; + default: void; +} + +struct fsid4 { + beu64 major; + beu64 minor; +} + +message fs_location4 { + beu32 server_count; + utf8str_cis server[server_count]; + pathname4 rootpath; +} + +message fs_locations4 { + pathname4 fs_root; + beu32 locations_count; + fs_location4 locations[locations_count]; +} + +message nfsace4 { + beu32 type_val; + beu32 flag; + beu32 access_mask; + utf8str_mixed who; +} + +struct specdata4 { + beu32 specdata1; + beu32 specdata2; +} + +message fattr4 { + bitmap4 attrmask; + attrlist4 attr_vals; +} + +struct change_info4 { + beu32 atomic; + beu64 before; + beu64 after; +} + +message xdr_opaque { + beu32 data_len; + u8 data[data_len]; + align(4); +} + +message clientaddr4 { + xdr_opaque r_netid; + xdr_opaque r_addr; +} + +message cb_client4 { + beu32 cb_program; + clientaddr4 cb_location; +} + +struct stateid4 { + beu32 seqid; + u8 other[12]; +} + +message nfs_client_id4 { + verifier4 verifier; + beu32 id_len; + u8 id[id_len]; + align(4); +} + +message open_owner4 { + beu64 clientid; + beu32 owner_len; + u8 owner[owner_len]; + align(4); +} + +message lock_owner4 { + beu64 clientid; + beu32 owner_len; + u8 owner[owner_len]; + align(4); +} + +enum nfs_lock_type4 : beu32 { + READ_LT = 1; + WRITE_LT = 2; + READW_LT = 3; + WRITEW_LT = 4; +} + +struct ACCESS4args { + beu32 access; +} + +struct ACCESS4resok { + beu32 supported; + beu32 access; +} + +union ACCESS4res (nfsstat4) { + NFS4_OK: ACCESS4resok; + default: void; +} + +struct CLOSE4args { + beu32 seqid; + stateid4 open_stateid; +} + +union CLOSE4res (nfsstat4) { + NFS4_OK: stateid4; + default: void; +} + +struct COMMIT4args { + beu64 offset; + beu32 count; +} + +struct COMMIT4resok { + verifier4 writeverf; +} + +union COMMIT4res (nfsstat4) { + NFS4_OK: COMMIT4resok; + default: void; +} + +union createtype4 (nfs_ftype4) { + NF4LNK: linktext4; + NF4BLK: specdata4; + NF4CHR: specdata4; + NF4SOCK: void; + NF4FIFO: void; + NF4DIR: void; + default: void; +} + +message CREATE4args { + nfs_ftype4 objtype_type; + createtype4 objtype(objtype_type); + component4 objname; + fattr4 createattrs; +} + +message CREATE4resok { + change_info4 cinfo; + bitmap4 attrset; +} + +union CREATE4res (nfsstat4) { + NFS4_OK: CREATE4resok; + default: void; +} + +struct DELEGPURGE4args { + beu64 clientid; +} + +struct DELEGPURGE4res { + nfsstat4 status; +} + +struct DELEGRETURN4args { + stateid4 deleg_stateid; +} + +struct DELEGRETURN4res { + nfsstat4 status; +} + +message GETATTR4args { + bitmap4 attr_request; +} + +message GETATTR4resok { + fattr4 obj_attributes; +} + +union GETATTR4res (nfsstat4) { + NFS4_OK: GETATTR4resok; + default: void; +} + +message GETFH4resok { + nfs_fh4 object; +} + +union GETFH4res (nfsstat4) { + NFS4_OK: GETFH4resok; + default: void; +} + +message LINK4args { + component4 newname; +} + +struct LINK4resok { + change_info4 cinfo; +} + +union LINK4res (nfsstat4) { + NFS4_OK: LINK4resok; + default: void; +} + +message open_to_lock_owner4 { + beu32 open_seqid; + stateid4 open_stateid; + beu32 lock_seqid; + lock_owner4 lock_owner; +} + +struct exist_lock_owner4 { + stateid4 lock_stateid; + beu32 lock_seqid; +} + +enum xdr_bool : beu32 { + FALSE = 0; + TRUE = 1; +} + +union locker4 (xdr_bool) { + TRUE: open_to_lock_owner4; + FALSE: exist_lock_owner4; +} + +message LOCK4args { + nfs_lock_type4 locktype; + beu32 reclaim; + beu64 offset; + beu64 length; + xdr_bool locker_type; + locker4 locker(locker_type); +} + +message LOCK4denied { + beu64 offset; + beu64 length; + nfs_lock_type4 locktype; + lock_owner4 owner; +} + +struct LOCK4resok { + stateid4 lock_stateid; +} + +union LOCK4res (nfsstat4) { + NFS4_OK: LOCK4resok; + NFS4ERR_DENIED: LOCK4denied; + default: void; +} + +message LOCKT4args { + nfs_lock_type4 locktype; + beu64 offset; + beu64 length; + lock_owner4 owner; +} + +union LOCKT4res (nfsstat4) { + NFS4ERR_DENIED: LOCK4denied; + NFS4_OK: void; + default: void; +} + +struct LOCKU4args { + nfs_lock_type4 locktype; + beu32 seqid; + stateid4 lock_stateid; + beu64 offset; + beu64 length; +} + +union LOCKU4res (nfsstat4) { + NFS4_OK: stateid4; + default: void; +} + +message LOOKUP4args { + component4 objname; +} + +struct LOOKUP4res { + nfsstat4 status; +} + +struct LOOKUPP4res { + nfsstat4 status; +} + +message NVERIFY4args { + fattr4 obj_attributes; +} + +struct NVERIFY4res { + nfsstat4 status; +} + +enum createmode4 : beu32 { + UNCHECKED4 = 0; + GUARDED4 = 1; + EXCLUSIVE4 = 2; +} + +union createhow4 (createmode4) { + UNCHECKED4: fattr4; + GUARDED4: fattr4; + EXCLUSIVE4: verifier4; +} + +enum opentype4 : beu32 { + OPEN4_NOCREATE = 0; + OPEN4_CREATE = 1; +} + +message createhow4_entry { + createmode4 disc; + createhow4 value(disc); +} + +union openflag4 (opentype4) { + OPEN4_CREATE: createhow4_entry; + default: void; +} + +enum limit_by4 : beu32 { + NFS_LIMIT_SIZE = 1; + NFS_LIMIT_BLOCKS = 2; +} + +struct nfs_modified_limit4 { + beu32 num_blocks; + beu32 bytes_per_block; +} + +struct nfs_space_limit4_NFS_LIMIT_SIZE { + beu64 value; +} + +union nfs_space_limit4 (limit_by4) { + NFS_LIMIT_SIZE: nfs_space_limit4_NFS_LIMIT_SIZE; + NFS_LIMIT_BLOCKS: nfs_modified_limit4; +} + +enum open_delegation_type4 : beu32 { + OPEN_DELEGATE_NONE = 0; + OPEN_DELEGATE_READ = 1; + OPEN_DELEGATE_WRITE = 2; +} + +enum open_claim_type4 : beu32 { + CLAIM_NULL = 0; + CLAIM_PREVIOUS = 1; + CLAIM_DELEGATE_CUR = 2; + CLAIM_DELEGATE_PREV = 3; +} + +message open_claim_delegate_cur4 { + stateid4 delegate_stateid; + component4 file; +} + +struct open_claim4_CLAIM_PREVIOUS { + open_delegation_type4 value; +} + +union open_claim4 (open_claim_type4) { + CLAIM_NULL: component4; + CLAIM_PREVIOUS: open_claim4_CLAIM_PREVIOUS; + CLAIM_DELEGATE_CUR: open_claim_delegate_cur4; + CLAIM_DELEGATE_PREV: component4; +} + +message OPEN4args { + beu32 seqid; + beu32 share_access; + beu32 share_deny; + open_owner4 owner; + opentype4 openhow_type; + openflag4 openhow(openhow_type); + open_claim_type4 claim_type; + open_claim4 claim(claim_type); +} + +message open_read_delegation4 { + stateid4 stateid; + beu32 recall; + nfsace4 permissions; +} + +message open_write_delegation4 { + stateid4 stateid; + beu32 recall; + limit_by4 space_limit_type; + nfs_space_limit4 space_limit(space_limit_type); + nfsace4 permissions; +} + +union open_delegation4 (open_delegation_type4) { + OPEN_DELEGATE_NONE: void; + OPEN_DELEGATE_READ: open_read_delegation4; + OPEN_DELEGATE_WRITE: open_write_delegation4; +} + +message OPEN4resok { + stateid4 stateid; + change_info4 cinfo; + beu32 rflags; + bitmap4 attrset; + open_delegation_type4 delegation_type; + open_delegation4 delegation(delegation_type); +} + +union OPEN4res (nfsstat4) { + NFS4_OK: OPEN4resok; + default: void; +} + +struct OPENATTR4args { + beu32 createdir; +} + +struct OPENATTR4res { + nfsstat4 status; +} + +struct OPEN_CONFIRM4args { + stateid4 open_stateid; + beu32 seqid; +} + +struct OPEN_CONFIRM4resok { + stateid4 open_stateid; +} + +union OPEN_CONFIRM4res (nfsstat4) { + NFS4_OK: OPEN_CONFIRM4resok; + default: void; +} + +struct OPEN_DOWNGRADE4args { + stateid4 open_stateid; + beu32 seqid; + beu32 share_access; + beu32 share_deny; +} + +struct OPEN_DOWNGRADE4resok { + stateid4 open_stateid; +} + +union OPEN_DOWNGRADE4res (nfsstat4) { + NFS4_OK: OPEN_DOWNGRADE4resok; + default: void; +} + +message PUTFH4args { + nfs_fh4 object; +} + +struct PUTFH4res { + nfsstat4 status; +} + +struct PUTPUBFH4res { + nfsstat4 status; +} + +struct PUTROOTFH4res { + nfsstat4 status; +} + +struct READ4args { + stateid4 stateid; + beu64 offset; + beu32 count; +} + +message READ4resok { + beu32 eof; + beu32 data_len; + u8 data[data_len]; + align(4); +} + +union READ4res (nfsstat4) { + NFS4_OK: READ4resok; + default: void; +} + +message READDIR4args { + beu64 cookie; + verifier4 cookieverf; + beu32 dircount; + beu32 maxcount; + bitmap4 attr_request; +} + +union entry4_opt (xdr_bool) { + TRUE: entry4; + default: void; +} + +message entry4 { + beu64 cookie; + component4 name; + fattr4 attrs; + xdr_bool nextentry_present; + entry4_opt nextentry(nextentry_present); +} + +message dirlist4 { + xdr_bool entries_present; + entry4_opt entries(entries_present); + beu32 eof; +} + +message READDIR4resok { + verifier4 cookieverf; + dirlist4 reply; +} + +union READDIR4res (nfsstat4) { + NFS4_OK: READDIR4resok; + default: void; +} + +message READLINK4resok { + linktext4 link; +} + +union READLINK4res (nfsstat4) { + NFS4_OK: READLINK4resok; + default: void; +} + +message REMOVE4args { + component4 target; +} + +struct REMOVE4resok { + change_info4 cinfo; +} + +union REMOVE4res (nfsstat4) { + NFS4_OK: REMOVE4resok; + default: void; +} + +message RENAME4args { + component4 oldname; + component4 newname; +} + +struct RENAME4resok { + change_info4 source_cinfo; + change_info4 target_cinfo; +} + +union RENAME4res (nfsstat4) { + NFS4_OK: RENAME4resok; + default: void; +} + +struct RENEW4args { + beu64 clientid; +} + +struct RENEW4res { + nfsstat4 status; +} + +struct RESTOREFH4res { + nfsstat4 status; +} + +struct SAVEFH4res { + nfsstat4 status; +} + +message SECINFO4args { + component4 name; +} + +enum rpc_gss_svc_t : beu32 { + RPC_GSS_SVC_NONE = 1; + RPC_GSS_SVC_INTEGRITY = 2; + RPC_GSS_SVC_PRIVACY = 3; +} + +message rpcsec_gss_info { + sec_oid4 oid; + beu32 qop; + rpc_gss_svc_t service; +} + +enum disc_secinfo4 : beu32 { + RPCSEC_GSS = 6; +} + +union secinfo4 (disc_secinfo4) { + RPCSEC_GSS: rpcsec_gss_info; + default: void; +} + +message secinfo4_entry { + disc_secinfo4 disc; + secinfo4 value(disc); +} + +message SECINFO4resok { + beu32 count; + secinfo4_entry data[count]; +} + +union SECINFO4res (nfsstat4) { + NFS4_OK: SECINFO4resok; + default: void; +} + +message SETATTR4args { + stateid4 stateid; + fattr4 obj_attributes; +} + +message SETATTR4res { + nfsstat4 status; + bitmap4 attrsset; +} + +message SETCLIENTID4args { + nfs_client_id4 client; + cb_client4 callback; + beu32 callback_ident; +} + +struct SETCLIENTID4resok { + beu64 clientid; + verifier4 setclientid_confirm; +} + +union SETCLIENTID4res (nfsstat4) { + NFS4_OK: SETCLIENTID4resok; + NFS4ERR_CLID_INUSE: clientaddr4; + default: void; +} + +struct SETCLIENTID_CONFIRM4args { + beu64 clientid; + verifier4 setclientid_confirm; +} + +struct SETCLIENTID_CONFIRM4res { + nfsstat4 status; +} + +message VERIFY4args { + fattr4 obj_attributes; +} + +struct VERIFY4res { + nfsstat4 status; +} + +enum stable_how4 : beu32 { + UNSTABLE4 = 0; + DATA_SYNC4 = 1; + FILE_SYNC4 = 2; +} + +message WRITE4args { + stateid4 stateid; + beu64 offset; + stable_how4 stable; + beu32 data_len; + u8 data[data_len]; + align(4); +} + +struct WRITE4resok { + beu32 count; + stable_how4 committed; + verifier4 writeverf; +} + +union WRITE4res (nfsstat4) { + NFS4_OK: WRITE4resok; + default: void; +} + +message RELEASE_LOCKOWNER4args { + lock_owner4 lock_owner; +} + +struct RELEASE_LOCKOWNER4res { + nfsstat4 status; +} + +struct ILLEGAL4res { + nfsstat4 status; +} + +enum nfs_opnum4 : beu32 { + OP_ACCESS = 3; + OP_CLOSE = 4; + OP_COMMIT = 5; + OP_CREATE = 6; + OP_DELEGPURGE = 7; + OP_DELEGRETURN = 8; + OP_GETATTR = 9; + OP_GETFH = 10; + OP_LINK = 11; + OP_LOCK = 12; + OP_LOCKT = 13; + OP_LOCKU = 14; + OP_LOOKUP = 15; + OP_LOOKUPP = 16; + OP_NVERIFY = 17; + OP_OPEN = 18; + OP_OPENATTR = 19; + OP_OPEN_CONFIRM = 20; + OP_OPEN_DOWNGRADE = 21; + OP_PUTFH = 22; + OP_PUTPUBFH = 23; + OP_PUTROOTFH = 24; + OP_READ = 25; + OP_READDIR = 26; + OP_READLINK = 27; + OP_REMOVE = 28; + OP_RENAME = 29; + OP_RENEW = 30; + OP_RESTOREFH = 31; + OP_SAVEFH = 32; + OP_SECINFO = 33; + OP_SETATTR = 34; + OP_SETCLIENTID = 35; + OP_SETCLIENTID_CONFIRM = 36; + OP_VERIFY = 37; + OP_WRITE = 38; + OP_RELEASE_LOCKOWNER = 39; + OP_ILLEGAL = 10044; +} + +union nfs_argop4 (nfs_opnum4) { + OP_ACCESS: ACCESS4args; + OP_CLOSE: CLOSE4args; + OP_COMMIT: COMMIT4args; + OP_CREATE: CREATE4args; + OP_DELEGPURGE: DELEGPURGE4args; + OP_DELEGRETURN: DELEGRETURN4args; + OP_GETATTR: GETATTR4args; + OP_GETFH: void; + OP_LINK: LINK4args; + OP_LOCK: LOCK4args; + OP_LOCKT: LOCKT4args; + OP_LOCKU: LOCKU4args; + OP_LOOKUP: LOOKUP4args; + OP_LOOKUPP: void; + OP_NVERIFY: NVERIFY4args; + OP_OPEN: OPEN4args; + OP_OPENATTR: OPENATTR4args; + OP_OPEN_CONFIRM: OPEN_CONFIRM4args; + OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4args; + OP_PUTFH: PUTFH4args; + OP_PUTPUBFH: void; + OP_PUTROOTFH: void; + OP_READ: READ4args; + OP_READDIR: READDIR4args; + OP_READLINK: void; + OP_REMOVE: REMOVE4args; + OP_RENAME: RENAME4args; + OP_RENEW: RENEW4args; + OP_RESTOREFH: void; + OP_SAVEFH: void; + OP_SECINFO: SECINFO4args; + OP_SETATTR: SETATTR4args; + OP_SETCLIENTID: SETCLIENTID4args; + OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4args; + OP_VERIFY: VERIFY4args; + OP_WRITE: WRITE4args; + OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4args; + OP_ILLEGAL: void; +} + +message ACCESS4res_entry { + nfsstat4 disc; + ACCESS4res value(disc); +} + +message CLOSE4res_entry { + nfsstat4 disc; + CLOSE4res value(disc); +} + +message COMMIT4res_entry { + nfsstat4 disc; + COMMIT4res value(disc); +} + +message CREATE4res_entry { + nfsstat4 disc; + CREATE4res value(disc); +} + +message GETATTR4res_entry { + nfsstat4 disc; + GETATTR4res value(disc); +} + +message GETFH4res_entry { + nfsstat4 disc; + GETFH4res value(disc); +} + +message LINK4res_entry { + nfsstat4 disc; + LINK4res value(disc); +} + +message LOCK4res_entry { + nfsstat4 disc; + LOCK4res value(disc); +} + +message LOCKT4res_entry { + nfsstat4 disc; + LOCKT4res value(disc); +} + +message LOCKU4res_entry { + nfsstat4 disc; + LOCKU4res value(disc); +} + +message OPEN4res_entry { + nfsstat4 disc; + OPEN4res value(disc); +} + +message OPEN_CONFIRM4res_entry { + nfsstat4 disc; + OPEN_CONFIRM4res value(disc); +} + +message OPEN_DOWNGRADE4res_entry { + nfsstat4 disc; + OPEN_DOWNGRADE4res value(disc); +} + +message READ4res_entry { + nfsstat4 disc; + READ4res value(disc); +} + +message READDIR4res_entry { + nfsstat4 disc; + READDIR4res value(disc); +} + +message READLINK4res_entry { + nfsstat4 disc; + READLINK4res value(disc); +} + +message REMOVE4res_entry { + nfsstat4 disc; + REMOVE4res value(disc); +} + +message RENAME4res_entry { + nfsstat4 disc; + RENAME4res value(disc); +} + +message SECINFO4res_entry { + nfsstat4 disc; + SECINFO4res value(disc); +} + +message SETCLIENTID4res_entry { + nfsstat4 disc; + SETCLIENTID4res value(disc); +} + +message WRITE4res_entry { + nfsstat4 disc; + WRITE4res value(disc); +} + +union nfs_resop4 (nfs_opnum4) { + OP_ACCESS: ACCESS4res_entry; + OP_CLOSE: CLOSE4res_entry; + OP_COMMIT: COMMIT4res_entry; + OP_CREATE: CREATE4res_entry; + OP_DELEGPURGE: DELEGPURGE4res; + OP_DELEGRETURN: DELEGRETURN4res; + OP_GETATTR: GETATTR4res_entry; + OP_GETFH: GETFH4res_entry; + OP_LINK: LINK4res_entry; + OP_LOCK: LOCK4res_entry; + OP_LOCKT: LOCKT4res_entry; + OP_LOCKU: LOCKU4res_entry; + OP_LOOKUP: LOOKUP4res; + OP_LOOKUPP: LOOKUPP4res; + OP_NVERIFY: NVERIFY4res; + OP_OPEN: OPEN4res_entry; + OP_OPENATTR: OPENATTR4res; + OP_OPEN_CONFIRM: OPEN_CONFIRM4res_entry; + OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4res_entry; + OP_PUTFH: PUTFH4res; + OP_PUTPUBFH: PUTPUBFH4res; + OP_PUTROOTFH: PUTROOTFH4res; + OP_READ: READ4res_entry; + OP_READDIR: READDIR4res_entry; + OP_READLINK: READLINK4res_entry; + OP_REMOVE: REMOVE4res_entry; + OP_RENAME: RENAME4res_entry; + OP_RENEW: RENEW4res; + OP_RESTOREFH: RESTOREFH4res; + OP_SAVEFH: SAVEFH4res; + OP_SECINFO: SECINFO4res_entry; + OP_SETATTR: SETATTR4res; + OP_SETCLIENTID: SETCLIENTID4res_entry; + OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4res; + OP_VERIFY: VERIFY4res; + OP_WRITE: WRITE4res_entry; + OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4res; + OP_ILLEGAL: ILLEGAL4res; +} + +message nfs_argop4_entry { + nfs_opnum4 disc; + nfs_argop4 value(disc); +} + +message COMPOUND4args { + utf8str_cs tag; + beu32 minorversion; + beu32 argarray_count; + nfs_argop4_entry argarray[argarray_count]; +} + +message nfs_resop4_entry { + nfs_opnum4 disc; + nfs_resop4 value(disc); +} + +message COMPOUND4res { + nfsstat4 status; + utf8str_cs tag; + beu32 resarray_count; + nfs_resop4_entry resarray[resarray_count]; +} + +message CB_GETATTR4args { + nfs_fh4 fh; + bitmap4 attr_request; +} + +message CB_GETATTR4resok { + fattr4 obj_attributes; +} + +union CB_GETATTR4res (nfsstat4) { + NFS4_OK: CB_GETATTR4resok; + default: void; +} + +message CB_RECALL4args { + stateid4 stateid; + beu32 truncate; + nfs_fh4 fh; +} + +struct CB_RECALL4res { + nfsstat4 status; +} + +struct CB_ILLEGAL4res { + nfsstat4 status; +} + +enum nfs_cb_opnum4 : beu32 { + OP_CB_GETATTR = 3; + OP_CB_RECALL = 4; + OP_CB_ILLEGAL = 10044; +} + +union nfs_cb_argop4 (nfs_cb_opnum4) { + OP_CB_GETATTR: CB_GETATTR4args; + OP_CB_RECALL: CB_RECALL4args; + OP_CB_ILLEGAL: void; +} + +message CB_GETATTR4res_entry { + nfsstat4 disc; + CB_GETATTR4res value(disc); +} + +union nfs_cb_resop4 (nfs_cb_opnum4) { + OP_CB_GETATTR: CB_GETATTR4res_entry; + OP_CB_RECALL: CB_RECALL4res; + OP_CB_ILLEGAL: CB_ILLEGAL4res; +} + +message nfs_cb_argop4_entry { + nfs_cb_opnum4 disc; + nfs_cb_argop4 value(disc); +} + +message CB_COMPOUND4args { + utf8str_cs tag; + beu32 minorversion; + beu32 callback_ident; + beu32 argarray_count; + nfs_cb_argop4_entry argarray[argarray_count]; +} + +message nfs_cb_resop4_entry { + nfs_cb_opnum4 disc; + nfs_cb_resop4 value(disc); +} + +message CB_COMPOUND4res { + nfsstat4 status; + utf8str_cs tag; + beu32 resarray_count; + nfs_cb_resop4_entry resarray[resarray_count]; +} + diff --git a/go/nfsd/nfs.x b/go/nfsd/nfs.x new file mode 100644 index 00000000..24460cca --- /dev/null +++ b/go/nfsd/nfs.x @@ -0,0 +1,1607 @@ +/* + * This file was machine generated for [RFC7530]. + * + * Last updated Tue Mar 10 11:51:21 PDT 2015. + */ + +/* + * Copyright (c) 2015 IETF Trust and the persons identified + * as authors of the code. All rights reserved. + * + * Redistribution and use in source and binary forms, with + * or without modification, are permitted provided that the + * following conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * - Neither the name of Internet Society, IETF or IETF + * Trust, nor the names of specific contributors, may be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS + * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code was derived from RFC 7531. + */ + +/* + * nfs4_prot.x + * + */ + +/* + * Basic typedefs for RFC 1832 data type definitions + */ +/* + * typedef int int32_t; + * typedef unsigned int uint32_t; + * typedef hyper int64_t; + * typedef unsigned hyper uint64_t; + */ + +/* + * Sizes + */ +const NFS4_FHSIZE = 128; +const NFS4_VERIFIER_SIZE = 8; +const NFS4_OTHER_SIZE = 12; +const NFS4_OPAQUE_LIMIT = 1024; + +const NFS4_INT64_MAX = 0x7fffffffffffffff; +const NFS4_UINT64_MAX = 0xffffffffffffffff; +const NFS4_INT32_MAX = 0x7fffffff; +const NFS4_UINT32_MAX = 0xffffffff; + + +/* + * File types + */ +enum nfs_ftype4 { + NF4REG = 1, /* Regular File */ + NF4DIR = 2, /* Directory */ + NF4BLK = 3, /* Special File - block device */ + NF4CHR = 4, /* Special File - character device */ + NF4LNK = 5, /* Symbolic Link */ + NF4SOCK = 6, /* Special File - socket */ + NF4FIFO = 7, /* Special File - fifo */ + NF4ATTRDIR + = 8, /* Attribute Directory */ + NF4NAMEDATTR + = 9 /* Named Attribute */ +}; + +/* + * Error status + */ +enum nfsstat4 { + NFS4_OK = 0, /* everything is okay */ + NFS4ERR_PERM = 1, /* caller not privileged */ + NFS4ERR_NOENT = 2, /* no such file/directory */ + NFS4ERR_IO = 5, /* hard I/O error */ + NFS4ERR_NXIO = 6, /* no such device */ + NFS4ERR_ACCESS = 13, /* access denied */ + NFS4ERR_EXIST = 17, /* file already exists */ + NFS4ERR_XDEV = 18, /* different file systems */ + /* Unused/reserved 19 */ + NFS4ERR_NOTDIR = 20, /* should be a directory */ + NFS4ERR_ISDIR = 21, /* should not be directory */ + NFS4ERR_INVAL = 22, /* invalid argument */ + NFS4ERR_FBIG = 27, /* file exceeds server max */ + NFS4ERR_NOSPC = 28, /* no space on file system */ + NFS4ERR_ROFS = 30, /* read-only file system */ + NFS4ERR_MLINK = 31, /* too many hard links */ + NFS4ERR_NAMETOOLONG = 63, /* name exceeds server max */ + NFS4ERR_NOTEMPTY = 66, /* directory not empty */ + NFS4ERR_DQUOT = 69, /* hard quota limit reached */ + NFS4ERR_STALE = 70, /* file no longer exists */ + NFS4ERR_BADHANDLE = 10001,/* Illegal filehandle */ + NFS4ERR_BAD_COOKIE = 10003,/* READDIR cookie is stale */ + NFS4ERR_NOTSUPP = 10004,/* operation not supported */ + NFS4ERR_TOOSMALL = 10005,/* response limit exceeded */ + NFS4ERR_SERVERFAULT = 10006,/* undefined server error */ + NFS4ERR_BADTYPE = 10007,/* type invalid for CREATE */ + NFS4ERR_DELAY = 10008,/* file "busy" - retry */ + NFS4ERR_SAME = 10009,/* nverify says attrs same */ + NFS4ERR_DENIED = 10010,/* lock unavailable */ + NFS4ERR_EXPIRED = 10011,/* lock lease expired */ + NFS4ERR_LOCKED = 10012,/* I/O failed due to lock */ + NFS4ERR_GRACE = 10013,/* in grace period */ + NFS4ERR_FHEXPIRED = 10014,/* filehandle expired */ + NFS4ERR_SHARE_DENIED = 10015,/* share reserve denied */ + NFS4ERR_WRONGSEC = 10016,/* wrong security flavor */ + NFS4ERR_CLID_INUSE = 10017,/* clientid in use */ + NFS4ERR_RESOURCE = 10018,/* resource exhaustion */ + NFS4ERR_MOVED = 10019,/* file system relocated */ + NFS4ERR_NOFILEHANDLE = 10020,/* current FH is not set */ + NFS4ERR_MINOR_VERS_MISMATCH = 10021,/* minor vers not supp */ + NFS4ERR_STALE_CLIENTID = 10022,/* server has rebooted */ + NFS4ERR_STALE_STATEID = 10023,/* server has rebooted */ + NFS4ERR_OLD_STATEID = 10024,/* state is out of sync */ + NFS4ERR_BAD_STATEID = 10025,/* incorrect stateid */ + NFS4ERR_BAD_SEQID = 10026,/* request is out of seq. */ + NFS4ERR_NOT_SAME = 10027,/* verify - attrs not same */ + NFS4ERR_LOCK_RANGE = 10028,/* lock range not supported */ + NFS4ERR_SYMLINK = 10029,/* should be file/directory */ + NFS4ERR_RESTOREFH = 10030,/* no saved filehandle */ + NFS4ERR_LEASE_MOVED = 10031,/* some file system moved */ + NFS4ERR_ATTRNOTSUPP = 10032,/* recommended attr not sup */ + NFS4ERR_NO_GRACE = 10033,/* reclaim outside of grace */ + NFS4ERR_RECLAIM_BAD = 10034,/* reclaim error at server */ + NFS4ERR_RECLAIM_CONFLICT = 10035,/* conflict on reclaim */ + NFS4ERR_BADXDR = 10036,/* XDR decode failed */ + NFS4ERR_LOCKS_HELD = 10037,/* file locks held at CLOSE */ + NFS4ERR_OPENMODE = 10038,/* conflict in OPEN and I/O */ + NFS4ERR_BADOWNER = 10039,/* owner translation bad */ + NFS4ERR_BADCHAR = 10040,/* UTF-8 char not supported */ + NFS4ERR_BADNAME = 10041,/* name not supported */ + NFS4ERR_BAD_RANGE = 10042,/* lock range not supported */ + NFS4ERR_LOCK_NOTSUPP = 10043,/* no atomic up/downgrade */ + NFS4ERR_OP_ILLEGAL = 10044,/* undefined operation */ + NFS4ERR_DEADLOCK = 10045,/* file locking deadlock */ + NFS4ERR_FILE_OPEN = 10046,/* open file blocks op. */ + NFS4ERR_ADMIN_REVOKED = 10047,/* lock-owner state revoked */ + NFS4ERR_CB_PATH_DOWN = 10048 /* callback path down */ +}; + +/* + * Basic data types + */ +typedef opaque attrlist4<>; +typedef uint32_t bitmap4<>; +typedef uint64_t changeid4; +typedef uint64_t clientid4; +typedef uint32_t count4; +typedef uint64_t length4; +typedef uint32_t mode4; +typedef uint64_t nfs_cookie4; +typedef opaque nfs_fh4; +typedef uint32_t nfs_lease4; +typedef uint64_t offset4; +typedef uint32_t qop4; +typedef opaque sec_oid4<>; +typedef uint32_t seqid4; +typedef opaque utf8string<>; +typedef utf8string utf8str_cis; +typedef utf8string utf8str_cs; +typedef utf8string utf8str_mixed; +typedef utf8str_cs component4; +typedef opaque linktext4<>; +typedef utf8string ascii_REQUIRED4; +typedef component4 pathname4<>; +typedef uint64_t nfs_lockid4; +typedef opaque verifier4[NFS4_VERIFIER_SIZE]; + + +/* + * Timeval + */ +struct nfstime4 { + int64_t seconds; + uint32_t nseconds; +}; + +enum time_how4 { + SET_TO_SERVER_TIME4 = 0, + SET_TO_CLIENT_TIME4 = 1 +}; + +union settime4 switch (time_how4 set_it) { + case SET_TO_CLIENT_TIME4: + nfstime4 time; + default: + void; +}; + + +/* + * File attribute definitions + */ + +/* + * FSID structure for major/minor + */ +struct fsid4 { + uint64_t major; + uint64_t minor; +}; + + +/* + * File system locations attribute for relocation/migration + */ +struct fs_location4 { + utf8str_cis server<>; + pathname4 rootpath; +}; + +struct fs_locations4 { + pathname4 fs_root; + fs_location4 locations<>; +}; + + +/* + * Various Access Control Entry definitions + */ + +/* + * Mask that indicates which Access Control Entries + * are supported. Values for the fattr4_aclsupport attribute. + */ +const ACL4_SUPPORT_ALLOW_ACL = 0x00000001; +const ACL4_SUPPORT_DENY_ACL = 0x00000002; +const ACL4_SUPPORT_AUDIT_ACL = 0x00000004; +const ACL4_SUPPORT_ALARM_ACL = 0x00000008; + + +typedef uint32_t acetype4; + + +/* + * acetype4 values; others can be added as needed. + */ +const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; +const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; +const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; +const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; + + + +/* + * ACE flag + */ +typedef uint32_t aceflag4; + + +/* + * ACE flag values + */ +const ACE4_FILE_INHERIT_ACE = 0x00000001; +const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; +const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; +const ACE4_INHERIT_ONLY_ACE = 0x00000008; +const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; +const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; +const ACE4_IDENTIFIER_GROUP = 0x00000040; + + + +/* + * ACE mask + */ +typedef uint32_t acemask4; + + +/* + * ACE mask values + */ +const ACE4_READ_DATA = 0x00000001; +const ACE4_LIST_DIRECTORY = 0x00000001; +const ACE4_WRITE_DATA = 0x00000002; +const ACE4_ADD_FILE = 0x00000002; +const ACE4_APPEND_DATA = 0x00000004; +const ACE4_ADD_SUBDIRECTORY = 0x00000004; +const ACE4_READ_NAMED_ATTRS = 0x00000008; +const ACE4_WRITE_NAMED_ATTRS = 0x00000010; +const ACE4_EXECUTE = 0x00000020; +const ACE4_DELETE_CHILD = 0x00000040; +const ACE4_READ_ATTRIBUTES = 0x00000080; +const ACE4_WRITE_ATTRIBUTES = 0x00000100; + +const ACE4_DELETE = 0x00010000; +const ACE4_READ_ACL = 0x00020000; +const ACE4_WRITE_ACL = 0x00040000; +const ACE4_WRITE_OWNER = 0x00080000; +const ACE4_SYNCHRONIZE = 0x00100000; + + +/* + * ACE4_GENERIC_READ - defined as a combination of + * ACE4_READ_ACL | + * ACE4_READ_DATA | + * ACE4_READ_ATTRIBUTES | + * ACE4_SYNCHRONIZE + */ + +const ACE4_GENERIC_READ = 0x00120081; + +/* + * ACE4_GENERIC_WRITE - defined as a combination of + * ACE4_READ_ACL | + * ACE4_WRITE_DATA | + * ACE4_WRITE_ATTRIBUTES | + * ACE4_WRITE_ACL | + * ACE4_APPEND_DATA | + * ACE4_SYNCHRONIZE + */ +const ACE4_GENERIC_WRITE = 0x00160106; + + +/* + * ACE4_GENERIC_EXECUTE - defined as a combination of + * ACE4_READ_ACL + * ACE4_READ_ATTRIBUTES + * ACE4_EXECUTE + * ACE4_SYNCHRONIZE + */ +const ACE4_GENERIC_EXECUTE = 0x001200A0; + + +/* + * Access Control Entry definition + */ +struct nfsace4 { + acetype4 type; + aceflag4 flag; + acemask4 access_mask; + utf8str_mixed who; +}; + + +/* + * Field definitions for the fattr4_mode attribute + */ +const MODE4_SUID = 0x800; /* set user id on execution */ +const MODE4_SGID = 0x400; /* set group id on execution */ +const MODE4_SVTX = 0x200; /* save text even after use */ +const MODE4_RUSR = 0x100; /* read permission: owner */ +const MODE4_WUSR = 0x080; /* write permission: owner */ +const MODE4_XUSR = 0x040; /* execute permission: owner */ +const MODE4_RGRP = 0x020; /* read permission: group */ +const MODE4_WGRP = 0x010; /* write permission: group */ +const MODE4_XGRP = 0x008; /* execute permission: group */ +const MODE4_ROTH = 0x004; /* read permission: other */ +const MODE4_WOTH = 0x002; /* write permission: other */ +const MODE4_XOTH = 0x001; /* execute permission: other */ + + +/* + * Special data/attribute associated with + * file types NF4BLK and NF4CHR. + */ +struct specdata4 { + uint32_t specdata1; /* major device number */ + uint32_t specdata2; /* minor device number */ +}; + + +/* + * Values for fattr4_fh_expire_type + */ +const FH4_PERSISTENT = 0x00000000; +const FH4_NOEXPIRE_WITH_OPEN = 0x00000001; +const FH4_VOLATILE_ANY = 0x00000002; +const FH4_VOL_MIGRATION = 0x00000004; +const FH4_VOL_RENAME = 0x00000008; + + +typedef bitmap4 fattr4_supported_attrs; +typedef nfs_ftype4 fattr4_type; +typedef uint32_t fattr4_fh_expire_type; +typedef changeid4 fattr4_change; +typedef uint64_t fattr4_size; +typedef bool fattr4_link_support; +typedef bool fattr4_symlink_support; +typedef bool fattr4_named_attr; +typedef fsid4 fattr4_fsid; +typedef bool fattr4_unique_handles; +typedef nfs_lease4 fattr4_lease_time; +typedef nfsstat4 fattr4_rdattr_error; + +typedef nfsace4 fattr4_acl<>; +typedef uint32_t fattr4_aclsupport; +typedef bool fattr4_archive; +typedef bool fattr4_cansettime; +typedef bool fattr4_case_insensitive; +typedef bool fattr4_case_preserving; +typedef bool fattr4_chown_restricted; +typedef uint64_t fattr4_fileid; +typedef uint64_t fattr4_files_avail; +typedef nfs_fh4 fattr4_filehandle; +typedef uint64_t fattr4_files_free; +typedef uint64_t fattr4_files_total; +typedef fs_locations4 fattr4_fs_locations; +typedef bool fattr4_hidden; +typedef bool fattr4_homogeneous; +typedef uint64_t fattr4_maxfilesize; +typedef uint32_t fattr4_maxlink; +typedef uint32_t fattr4_maxname; +typedef uint64_t fattr4_maxread; +typedef uint64_t fattr4_maxwrite; +typedef ascii_REQUIRED4 fattr4_mimetype; +typedef mode4 fattr4_mode; +typedef uint64_t fattr4_mounted_on_fileid; +typedef bool fattr4_no_trunc; +typedef uint32_t fattr4_numlinks; +typedef utf8str_mixed fattr4_owner; +typedef utf8str_mixed fattr4_owner_group; +typedef uint64_t fattr4_quota_avail_hard; +typedef uint64_t fattr4_quota_avail_soft; +typedef uint64_t fattr4_quota_used; +typedef specdata4 fattr4_rawdev; +typedef uint64_t fattr4_space_avail; +typedef uint64_t fattr4_space_free; +typedef uint64_t fattr4_space_total; +typedef uint64_t fattr4_space_used; +typedef bool fattr4_system; +typedef nfstime4 fattr4_time_access; +typedef settime4 fattr4_time_access_set; +typedef nfstime4 fattr4_time_backup; +typedef nfstime4 fattr4_time_create; +typedef nfstime4 fattr4_time_delta; +typedef nfstime4 fattr4_time_metadata; +typedef nfstime4 fattr4_time_modify; +typedef settime4 fattr4_time_modify_set; + + +/* + * Mandatory attributes + */ +const FATTR4_SUPPORTED_ATTRS = 0; +const FATTR4_TYPE = 1; +const FATTR4_FH_EXPIRE_TYPE = 2; +const FATTR4_CHANGE = 3; +const FATTR4_SIZE = 4; +const FATTR4_LINK_SUPPORT = 5; +const FATTR4_SYMLINK_SUPPORT = 6; +const FATTR4_NAMED_ATTR = 7; +const FATTR4_FSID = 8; +const FATTR4_UNIQUE_HANDLES = 9; +const FATTR4_LEASE_TIME = 10; +const FATTR4_RDATTR_ERROR = 11; +const FATTR4_FILEHANDLE = 19; + +/* + * Recommended attributes + */ +const FATTR4_ACL = 12; +const FATTR4_ACLSUPPORT = 13; +const FATTR4_ARCHIVE = 14; +const FATTR4_CANSETTIME = 15; +const FATTR4_CASE_INSENSITIVE = 16; +const FATTR4_CASE_PRESERVING = 17; +const FATTR4_CHOWN_RESTRICTED = 18; +const FATTR4_FILEID = 20; +const FATTR4_FILES_AVAIL = 21; +const FATTR4_FILES_FREE = 22; +const FATTR4_FILES_TOTAL = 23; +const FATTR4_FS_LOCATIONS = 24; +const FATTR4_HIDDEN = 25; +const FATTR4_HOMOGENEOUS = 26; +const FATTR4_MAXFILESIZE = 27; +const FATTR4_MAXLINK = 28; +const FATTR4_MAXNAME = 29; +const FATTR4_MAXREAD = 30; +const FATTR4_MAXWRITE = 31; +const FATTR4_MIMETYPE = 32; +const FATTR4_MODE = 33; +const FATTR4_NO_TRUNC = 34; +const FATTR4_NUMLINKS = 35; +const FATTR4_OWNER = 36; +const FATTR4_OWNER_GROUP = 37; +const FATTR4_QUOTA_AVAIL_HARD = 38; +const FATTR4_QUOTA_AVAIL_SOFT = 39; +const FATTR4_QUOTA_USED = 40; +const FATTR4_RAWDEV = 41; +const FATTR4_SPACE_AVAIL = 42; +const FATTR4_SPACE_FREE = 43; +const FATTR4_SPACE_TOTAL = 44; +const FATTR4_SPACE_USED = 45; +const FATTR4_SYSTEM = 46; +const FATTR4_TIME_ACCESS = 47; +const FATTR4_TIME_ACCESS_SET = 48; +const FATTR4_TIME_BACKUP = 49; +const FATTR4_TIME_CREATE = 50; +const FATTR4_TIME_DELTA = 51; +const FATTR4_TIME_METADATA = 52; +const FATTR4_TIME_MODIFY = 53; +const FATTR4_TIME_MODIFY_SET = 54; +const FATTR4_MOUNTED_ON_FILEID = 55; + +/* + * File attribute container + */ +struct fattr4 { + bitmap4 attrmask; + attrlist4 attr_vals; +}; + + +/* + * Change info for the client + */ +struct change_info4 { + bool atomic; + changeid4 before; + changeid4 after; +}; + + +struct clientaddr4 { + /* see struct rpcb in RFC 1833 */ + string r_netid<>; /* network id */ + string r_addr<>; /* universal address */ +}; + + +/* + * Callback program info as provided by the client + */ +struct cb_client4 { + unsigned int cb_program; + clientaddr4 cb_location; +}; + + +/* + * Stateid + */ +struct stateid4 { + uint32_t seqid; + opaque other[NFS4_OTHER_SIZE]; +}; + +/* + * Client ID + */ +struct nfs_client_id4 { + verifier4 verifier; + opaque id; +}; + + +struct open_owner4 { + clientid4 clientid; + opaque owner; +}; + + +struct lock_owner4 { + clientid4 clientid; + opaque owner; +}; + + +enum nfs_lock_type4 { + READ_LT = 1, + WRITE_LT = 2, + READW_LT = 3, /* blocking read */ + WRITEW_LT = 4 /* blocking write */ +}; + + +const ACCESS4_READ = 0x00000001; +const ACCESS4_LOOKUP = 0x00000002; +const ACCESS4_MODIFY = 0x00000004; +const ACCESS4_EXTEND = 0x00000008; +const ACCESS4_DELETE = 0x00000010; +const ACCESS4_EXECUTE = 0x00000020; + +struct ACCESS4args { + /* CURRENT_FH: object */ + uint32_t access; +}; + +struct ACCESS4resok { + uint32_t supported; + uint32_t access; +}; + +union ACCESS4res switch (nfsstat4 status) { + case NFS4_OK: + ACCESS4resok resok4; + default: + void; +}; + +struct CLOSE4args { + /* CURRENT_FH: object */ + seqid4 seqid; + stateid4 open_stateid; +}; + +union CLOSE4res switch (nfsstat4 status) { + case NFS4_OK: + stateid4 open_stateid; + default: + void; +}; + +struct COMMIT4args { + /* CURRENT_FH: file */ + offset4 offset; + count4 count; +}; + +struct COMMIT4resok { + verifier4 writeverf; +}; + +union COMMIT4res switch (nfsstat4 status) { + case NFS4_OK: + COMMIT4resok resok4; + default: + void; +}; + +union createtype4 switch (nfs_ftype4 type) { + case NF4LNK: + linktext4 linkdata; + case NF4BLK: + case NF4CHR: + specdata4 devdata; + case NF4SOCK: + case NF4FIFO: + case NF4DIR: + void; + default: + void; /* server should return NFS4ERR_BADTYPE */ +}; + +struct CREATE4args { + /* CURRENT_FH: directory for creation */ + createtype4 objtype; + component4 objname; + fattr4 createattrs; +}; + +struct CREATE4resok { + change_info4 cinfo; + bitmap4 attrset; /* attributes set */ +}; + +union CREATE4res switch (nfsstat4 status) { + case NFS4_OK: + CREATE4resok resok4; + default: + void; +}; + +struct DELEGPURGE4args { + clientid4 clientid; +}; + +struct DELEGPURGE4res { + nfsstat4 status; +}; + +struct DELEGRETURN4args { + /* CURRENT_FH: delegated file */ + stateid4 deleg_stateid; +}; + +struct DELEGRETURN4res { + nfsstat4 status; +}; + +struct GETATTR4args { + /* CURRENT_FH: directory or file */ + bitmap4 attr_request; +}; + +struct GETATTR4resok { + fattr4 obj_attributes; +}; + +union GETATTR4res switch (nfsstat4 status) { + case NFS4_OK: + GETATTR4resok resok4; + default: + void; +}; + +struct GETFH4resok { + nfs_fh4 object; +}; + +union GETFH4res switch (nfsstat4 status) { + case NFS4_OK: + GETFH4resok resok4; + default: + void; +}; + +struct LINK4args { + /* SAVED_FH: source object */ + /* CURRENT_FH: target directory */ + component4 newname; +}; + +struct LINK4resok { + change_info4 cinfo; +}; + +union LINK4res switch (nfsstat4 status) { + case NFS4_OK: + LINK4resok resok4; + default: + void; +}; + +/* + * For LOCK, transition from open_owner to new lock_owner + */ +struct open_to_lock_owner4 { + seqid4 open_seqid; + stateid4 open_stateid; + seqid4 lock_seqid; + lock_owner4 lock_owner; +}; + +/* + * For LOCK, existing lock_owner continues to request file locks + */ +struct exist_lock_owner4 { + stateid4 lock_stateid; + seqid4 lock_seqid; +}; + +union locker4 switch (bool new_lock_owner) { + case TRUE: + open_to_lock_owner4 open_owner; + case FALSE: + exist_lock_owner4 lock_owner; +}; + +/* + * LOCK/LOCKT/LOCKU: Record lock management + */ +struct LOCK4args { + /* CURRENT_FH: file */ + nfs_lock_type4 locktype; + bool reclaim; + offset4 offset; + length4 length; + locker4 locker; +}; + +struct LOCK4denied { + offset4 offset; + length4 length; + nfs_lock_type4 locktype; + lock_owner4 owner; +}; + +struct LOCK4resok { + stateid4 lock_stateid; +}; + +union LOCK4res switch (nfsstat4 status) { + case NFS4_OK: + LOCK4resok resok4; + case NFS4ERR_DENIED: + LOCK4denied denied; + default: + void; +}; + +struct LOCKT4args { + /* CURRENT_FH: file */ + nfs_lock_type4 locktype; + offset4 offset; + length4 length; + lock_owner4 owner; +}; + +union LOCKT4res switch (nfsstat4 status) { + case NFS4ERR_DENIED: + LOCK4denied denied; + case NFS4_OK: + void; + default: + void; +}; + +struct LOCKU4args { + /* CURRENT_FH: file */ + nfs_lock_type4 locktype; + seqid4 seqid; + stateid4 lock_stateid; + offset4 offset; + length4 length; +}; + +union LOCKU4res switch (nfsstat4 status) { + case NFS4_OK: + stateid4 lock_stateid; + default: + void; +}; + +struct LOOKUP4args { + /* CURRENT_FH: directory */ + component4 objname; +}; + +struct LOOKUP4res { + /* CURRENT_FH: object */ + nfsstat4 status; +}; + +struct LOOKUPP4res { + /* CURRENT_FH: directory */ + nfsstat4 status; +}; + +struct NVERIFY4args { + /* CURRENT_FH: object */ + fattr4 obj_attributes; +}; + +struct NVERIFY4res { + nfsstat4 status; +}; + +const OPEN4_SHARE_ACCESS_READ = 0x00000001; +const OPEN4_SHARE_ACCESS_WRITE = 0x00000002; +const OPEN4_SHARE_ACCESS_BOTH = 0x00000003; + +const OPEN4_SHARE_DENY_NONE = 0x00000000; +const OPEN4_SHARE_DENY_READ = 0x00000001; +const OPEN4_SHARE_DENY_WRITE = 0x00000002; +const OPEN4_SHARE_DENY_BOTH = 0x00000003; +/* + * Various definitions for OPEN + */ +enum createmode4 { + UNCHECKED4 = 0, + GUARDED4 = 1, + EXCLUSIVE4 = 2 +}; + +union createhow4 switch (createmode4 mode) { + case UNCHECKED4: + case GUARDED4: + fattr4 createattrs; + case EXCLUSIVE4: + verifier4 createverf; +}; + +enum opentype4 { + OPEN4_NOCREATE = 0, + OPEN4_CREATE = 1 +}; + +union openflag4 switch (opentype4 opentype) { + case OPEN4_CREATE: + createhow4 how; + default: + void; +}; + +/* Next definitions used for OPEN delegation */ +enum limit_by4 { + NFS_LIMIT_SIZE = 1, + NFS_LIMIT_BLOCKS = 2 + /* others as needed */ +}; + +struct nfs_modified_limit4 { + uint32_t num_blocks; + uint32_t bytes_per_block; +}; + +union nfs_space_limit4 switch (limit_by4 limitby) { + /* limit specified as file size */ + case NFS_LIMIT_SIZE: + uint64_t filesize; + /* limit specified by number of blocks */ + case NFS_LIMIT_BLOCKS: + nfs_modified_limit4 mod_blocks; +} ; + +enum open_delegation_type4 { + OPEN_DELEGATE_NONE = 0, + OPEN_DELEGATE_READ = 1, + OPEN_DELEGATE_WRITE = 2 +}; + +enum open_claim_type4 { + CLAIM_NULL = 0, + CLAIM_PREVIOUS = 1, + CLAIM_DELEGATE_CUR = 2, + CLAIM_DELEGATE_PREV = 3 +}; + +struct open_claim_delegate_cur4 { + stateid4 delegate_stateid; + component4 file; +}; + +union open_claim4 switch (open_claim_type4 claim) { + /* + * No special rights to file. + * Ordinary OPEN of the specified file. + */ + case CLAIM_NULL: + /* CURRENT_FH: directory */ + component4 file; + /* + * Right to the file established by an + * open previous to server reboot. File + * identified by filehandle obtained at + * that time rather than by name. + */ + case CLAIM_PREVIOUS: + /* CURRENT_FH: file being reclaimed */ + open_delegation_type4 delegate_type; + + /* + * Right to file based on a delegation + * granted by the server. File is + * specified by name. + */ + case CLAIM_DELEGATE_CUR: + /* CURRENT_FH: directory */ + open_claim_delegate_cur4 delegate_cur_info; + + /* + * Right to file based on a delegation + * granted to a previous boot instance + * of the client. File is specified by name. + */ + case CLAIM_DELEGATE_PREV: + /* CURRENT_FH: directory */ + component4 file_delegate_prev; +}; + +/* + * OPEN: Open a file, potentially receiving an open delegation + */ +struct OPEN4args { + seqid4 seqid; + uint32_t share_access; + uint32_t share_deny; + open_owner4 owner; + openflag4 openhow; + open_claim4 claim; +}; + +struct open_read_delegation4 { + stateid4 stateid; /* Stateid for delegation */ + bool recall; /* Pre-recalled flag for + delegations obtained + by reclaim (CLAIM_PREVIOUS). */ + + nfsace4 permissions; /* Defines users who don't + need an ACCESS call to + open for read. */ +}; + +struct open_write_delegation4 { + stateid4 stateid; /* Stateid for delegation */ + bool recall; /* Pre-recalled flag for + delegations obtained + by reclaim + (CLAIM_PREVIOUS). */ + + nfs_space_limit4 + space_limit; /* Defines condition that + the client must check to + determine whether the + file needs to be flushed + to the server on close. */ + + nfsace4 permissions; /* Defines users who don't + need an ACCESS call as + part of a delegated + open. */ +}; + +union open_delegation4 +switch (open_delegation_type4 delegation_type) { + case OPEN_DELEGATE_NONE: + void; + case OPEN_DELEGATE_READ: + open_read_delegation4 read; + case OPEN_DELEGATE_WRITE: + open_write_delegation4 write; +}; + +/* + * Result flags + */ + +/* Client must confirm open */ +const OPEN4_RESULT_CONFIRM = 0x00000002; +/* Type of file locking behavior at the server */ +const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004; + +struct OPEN4resok { + stateid4 stateid; /* Stateid for open */ + change_info4 cinfo; /* Directory change info */ + uint32_t rflags; /* Result flags */ + bitmap4 attrset; /* attribute set for create */ + open_delegation4 delegation; /* Info on any open + delegation */ +}; + +union OPEN4res switch (nfsstat4 status) { + case NFS4_OK: + /* CURRENT_FH: opened file */ + OPEN4resok resok4; + default: + void; +}; + +struct OPENATTR4args { + /* CURRENT_FH: object */ + bool createdir; +}; + +struct OPENATTR4res { + /* CURRENT_FH: named attr directory */ + nfsstat4 status; +}; + +struct OPEN_CONFIRM4args { + /* CURRENT_FH: opened file */ + stateid4 open_stateid; + seqid4 seqid; +}; + +struct OPEN_CONFIRM4resok { + stateid4 open_stateid; +}; + +union OPEN_CONFIRM4res switch (nfsstat4 status) { + case NFS4_OK: + OPEN_CONFIRM4resok resok4; + default: + void; +}; + +struct OPEN_DOWNGRADE4args { + /* CURRENT_FH: opened file */ + stateid4 open_stateid; + seqid4 seqid; + uint32_t share_access; + uint32_t share_deny; +}; + +struct OPEN_DOWNGRADE4resok { + stateid4 open_stateid; +}; + +union OPEN_DOWNGRADE4res switch (nfsstat4 status) { + case NFS4_OK: + OPEN_DOWNGRADE4resok resok4; + default: + void; +}; + +struct PUTFH4args { + nfs_fh4 object; +}; + +struct PUTFH4res { + /* CURRENT_FH: */ + nfsstat4 status; +}; + +struct PUTPUBFH4res { + /* CURRENT_FH: public fh */ + nfsstat4 status; +}; + +struct PUTROOTFH4res { + /* CURRENT_FH: root fh */ + nfsstat4 status; +}; + +struct READ4args { + /* CURRENT_FH: file */ + stateid4 stateid; + offset4 offset; + count4 count; +}; + +struct READ4resok { + bool eof; + opaque data<>; +}; + +union READ4res switch (nfsstat4 status) { + case NFS4_OK: + READ4resok resok4; + default: + void; +}; + +struct READDIR4args { + /* CURRENT_FH: directory */ + nfs_cookie4 cookie; + verifier4 cookieverf; + count4 dircount; + count4 maxcount; + bitmap4 attr_request; +}; + +struct entry4 { + nfs_cookie4 cookie; + component4 name; + fattr4 attrs; + entry4 *nextentry; +}; + +struct dirlist4 { + entry4 *entries; + bool eof; +}; + +struct READDIR4resok { + verifier4 cookieverf; + dirlist4 reply; +}; + + +union READDIR4res switch (nfsstat4 status) { + case NFS4_OK: + READDIR4resok resok4; + default: + void; +}; + + +struct READLINK4resok { + linktext4 link; +}; + +union READLINK4res switch (nfsstat4 status) { + case NFS4_OK: + READLINK4resok resok4; + default: + void; +}; + +struct REMOVE4args { + /* CURRENT_FH: directory */ + component4 target; +}; + +struct REMOVE4resok { + change_info4 cinfo; +}; + +union REMOVE4res switch (nfsstat4 status) { + case NFS4_OK: + REMOVE4resok resok4; + default: + void; +}; + +struct RENAME4args { + /* SAVED_FH: source directory */ + component4 oldname; + /* CURRENT_FH: target directory */ + component4 newname; +}; + +struct RENAME4resok { + change_info4 source_cinfo; + change_info4 target_cinfo; +}; + +union RENAME4res switch (nfsstat4 status) { + case NFS4_OK: + RENAME4resok resok4; + default: + void; +}; + +struct RENEW4args { + clientid4 clientid; +}; + +struct RENEW4res { + nfsstat4 status; +}; + +struct RESTOREFH4res { + /* CURRENT_FH: value of saved fh */ + nfsstat4 status; +}; + +struct SAVEFH4res { + /* SAVED_FH: value of current fh */ + nfsstat4 status; +}; + +struct SECINFO4args { + /* CURRENT_FH: directory */ + component4 name; +}; + +/* + * From RFC 2203 + */ +enum rpc_gss_svc_t { + RPC_GSS_SVC_NONE = 1, + RPC_GSS_SVC_INTEGRITY = 2, + RPC_GSS_SVC_PRIVACY = 3 +}; + +struct rpcsec_gss_info { + sec_oid4 oid; + qop4 qop; + rpc_gss_svc_t service; +}; + +/* RPCSEC_GSS has a value of '6'. See RFC 2203 */ +union secinfo4 switch (uint32_t flavor) { + case RPCSEC_GSS: + rpcsec_gss_info flavor_info; + default: + void; +}; + +typedef secinfo4 SECINFO4resok<>; + +union SECINFO4res switch (nfsstat4 status) { + case NFS4_OK: + SECINFO4resok resok4; + default: + void; +}; + +struct SETATTR4args { + /* CURRENT_FH: target object */ + stateid4 stateid; + fattr4 obj_attributes; +}; + +struct SETATTR4res { + nfsstat4 status; + bitmap4 attrsset; +}; + +struct SETCLIENTID4args { + nfs_client_id4 client; + cb_client4 callback; + uint32_t callback_ident; +}; + +struct SETCLIENTID4resok { + clientid4 clientid; + verifier4 setclientid_confirm; +}; + +union SETCLIENTID4res switch (nfsstat4 status) { + case NFS4_OK: + SETCLIENTID4resok resok4; + case NFS4ERR_CLID_INUSE: + clientaddr4 client_using; + default: + void; +}; + +struct SETCLIENTID_CONFIRM4args { + clientid4 clientid; + verifier4 setclientid_confirm; +}; + +struct SETCLIENTID_CONFIRM4res { + nfsstat4 status; +}; + +struct VERIFY4args { + /* CURRENT_FH: object */ + fattr4 obj_attributes; +}; + +struct VERIFY4res { + nfsstat4 status; +}; + +enum stable_how4 { + UNSTABLE4 = 0, + DATA_SYNC4 = 1, + FILE_SYNC4 = 2 +}; + +struct WRITE4args { + /* CURRENT_FH: file */ + stateid4 stateid; + offset4 offset; + stable_how4 stable; + opaque data<>; +}; + +struct WRITE4resok { + count4 count; + stable_how4 committed; + verifier4 writeverf; +}; + +union WRITE4res switch (nfsstat4 status) { + case NFS4_OK: + WRITE4resok resok4; + default: + void; +}; + +struct RELEASE_LOCKOWNER4args { + lock_owner4 lock_owner; +}; + +struct RELEASE_LOCKOWNER4res { + nfsstat4 status; +}; + +struct ILLEGAL4res { + nfsstat4 status; +}; + +/* + * Operation arrays + */ + +enum nfs_opnum4 { + OP_ACCESS = 3, + OP_CLOSE = 4, + OP_COMMIT = 5, + OP_CREATE = 6, + OP_DELEGPURGE = 7, + OP_DELEGRETURN = 8, + OP_GETATTR = 9, + OP_GETFH = 10, + OP_LINK = 11, + OP_LOCK = 12, + OP_LOCKT = 13, + OP_LOCKU = 14, + OP_LOOKUP = 15, + OP_LOOKUPP = 16, + OP_NVERIFY = 17, + OP_OPEN = 18, + OP_OPENATTR = 19, + OP_OPEN_CONFIRM = 20, + OP_OPEN_DOWNGRADE = 21, + OP_PUTFH = 22, + OP_PUTPUBFH = 23, + OP_PUTROOTFH = 24, + OP_READ = 25, + OP_READDIR = 26, + OP_READLINK = 27, + OP_REMOVE = 28, + OP_RENAME = 29, + OP_RENEW = 30, + OP_RESTOREFH = 31, + OP_SAVEFH = 32, + OP_SECINFO = 33, + OP_SETATTR = 34, + OP_SETCLIENTID = 35, + OP_SETCLIENTID_CONFIRM = 36, + OP_VERIFY = 37, + OP_WRITE = 38, + OP_RELEASE_LOCKOWNER = 39, + OP_ILLEGAL = 10044 +}; + +union nfs_argop4 switch (nfs_opnum4 argop) { + case OP_ACCESS: ACCESS4args opaccess; + case OP_CLOSE: CLOSE4args opclose; + case OP_COMMIT: COMMIT4args opcommit; + case OP_CREATE: CREATE4args opcreate; + case OP_DELEGPURGE: DELEGPURGE4args opdelegpurge; + case OP_DELEGRETURN: DELEGRETURN4args opdelegreturn; + case OP_GETATTR: GETATTR4args opgetattr; + case OP_GETFH: void; + case OP_LINK: LINK4args oplink; + case OP_LOCK: LOCK4args oplock; + case OP_LOCKT: LOCKT4args oplockt; + case OP_LOCKU: LOCKU4args oplocku; + case OP_LOOKUP: LOOKUP4args oplookup; + case OP_LOOKUPP: void; + case OP_NVERIFY: NVERIFY4args opnverify; + case OP_OPEN: OPEN4args opopen; + case OP_OPENATTR: OPENATTR4args opopenattr; + case OP_OPEN_CONFIRM: OPEN_CONFIRM4args opopen_confirm; + case OP_OPEN_DOWNGRADE: + OPEN_DOWNGRADE4args opopen_downgrade; + case OP_PUTFH: PUTFH4args opputfh; + case OP_PUTPUBFH: void; + case OP_PUTROOTFH: void; + case OP_READ: READ4args opread; + case OP_READDIR: READDIR4args opreaddir; + case OP_READLINK: void; + case OP_REMOVE: REMOVE4args opremove; + case OP_RENAME: RENAME4args oprename; + case OP_RENEW: RENEW4args oprenew; + case OP_RESTOREFH: void; + case OP_SAVEFH: void; + case OP_SECINFO: SECINFO4args opsecinfo; + case OP_SETATTR: SETATTR4args opsetattr; + case OP_SETCLIENTID: SETCLIENTID4args opsetclientid; + case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4args + opsetclientid_confirm; + case OP_VERIFY: VERIFY4args opverify; + case OP_WRITE: WRITE4args opwrite; + case OP_RELEASE_LOCKOWNER: + RELEASE_LOCKOWNER4args + oprelease_lockowner; + case OP_ILLEGAL: void; +}; + +union nfs_resop4 switch (nfs_opnum4 resop) { + case OP_ACCESS: ACCESS4res opaccess; + case OP_CLOSE: CLOSE4res opclose; + case OP_COMMIT: COMMIT4res opcommit; + case OP_CREATE: CREATE4res opcreate; + case OP_DELEGPURGE: DELEGPURGE4res opdelegpurge; + case OP_DELEGRETURN: DELEGRETURN4res opdelegreturn; + case OP_GETATTR: GETATTR4res opgetattr; + case OP_GETFH: GETFH4res opgetfh; + case OP_LINK: LINK4res oplink; + case OP_LOCK: LOCK4res oplock; + case OP_LOCKT: LOCKT4res oplockt; + case OP_LOCKU: LOCKU4res oplocku; + case OP_LOOKUP: LOOKUP4res oplookup; + case OP_LOOKUPP: LOOKUPP4res oplookupp; + case OP_NVERIFY: NVERIFY4res opnverify; + case OP_OPEN: OPEN4res opopen; + case OP_OPENATTR: OPENATTR4res opopenattr; + case OP_OPEN_CONFIRM: OPEN_CONFIRM4res opopen_confirm; + case OP_OPEN_DOWNGRADE: + OPEN_DOWNGRADE4res + opopen_downgrade; + case OP_PUTFH: PUTFH4res opputfh; + case OP_PUTPUBFH: PUTPUBFH4res opputpubfh; + case OP_PUTROOTFH: PUTROOTFH4res opputrootfh; + case OP_READ: READ4res opread; + case OP_READDIR: READDIR4res opreaddir; + case OP_READLINK: READLINK4res opreadlink; + case OP_REMOVE: REMOVE4res opremove; + case OP_RENAME: RENAME4res oprename; + case OP_RENEW: RENEW4res oprenew; + case OP_RESTOREFH: RESTOREFH4res oprestorefh; + case OP_SAVEFH: SAVEFH4res opsavefh; + case OP_SECINFO: SECINFO4res opsecinfo; + case OP_SETATTR: SETATTR4res opsetattr; + case OP_SETCLIENTID: SETCLIENTID4res opsetclientid; + case OP_SETCLIENTID_CONFIRM: + SETCLIENTID_CONFIRM4res + opsetclientid_confirm; + case OP_VERIFY: VERIFY4res opverify; + case OP_WRITE: WRITE4res opwrite; + case OP_RELEASE_LOCKOWNER: + RELEASE_LOCKOWNER4res + oprelease_lockowner; + case OP_ILLEGAL: ILLEGAL4res opillegal; +}; + +struct COMPOUND4args { + utf8str_cs tag; + uint32_t minorversion; + nfs_argop4 argarray<>; +}; + +struct COMPOUND4res { + nfsstat4 status; + utf8str_cs tag; + nfs_resop4 resarray<>; +}; + + +/* + * Remote file service routines + */ +program NFS4_PROGRAM { + version NFS_V4 { + void + NFSPROC4_NULL(void) = 0; + + COMPOUND4res + NFSPROC4_COMPOUND(COMPOUND4args) = 1; + + } = 4; +} = 100003; + +/* + * NFS4 callback procedure definitions and program + */ +struct CB_GETATTR4args { + nfs_fh4 fh; + bitmap4 attr_request; +}; + +struct CB_GETATTR4resok { + fattr4 obj_attributes; +}; + +union CB_GETATTR4res switch (nfsstat4 status) { + case NFS4_OK: + CB_GETATTR4resok resok4; + default: + void; +}; + +struct CB_RECALL4args { + stateid4 stateid; + bool truncate; + nfs_fh4 fh; +}; + +struct CB_RECALL4res { + nfsstat4 status; +}; + +/* + * CB_ILLEGAL: Response for illegal operation numbers + */ +struct CB_ILLEGAL4res { + nfsstat4 status; +}; + +/* + * Various definitions for CB_COMPOUND + */ +enum nfs_cb_opnum4 { + OP_CB_GETATTR = 3, + OP_CB_RECALL = 4, + OP_CB_ILLEGAL = 10044 +}; + +union nfs_cb_argop4 switch (unsigned argop) { + case OP_CB_GETATTR: CB_GETATTR4args opcbgetattr; + case OP_CB_RECALL: CB_RECALL4args opcbrecall; + case OP_CB_ILLEGAL: void; +}; + +union nfs_cb_resop4 switch (unsigned resop) { + case OP_CB_GETATTR: CB_GETATTR4res opcbgetattr; + case OP_CB_RECALL: CB_RECALL4res opcbrecall; + case OP_CB_ILLEGAL: CB_ILLEGAL4res opcbillegal; +}; + + +struct CB_COMPOUND4args { + utf8str_cs tag; + uint32_t minorversion; + uint32_t callback_ident; + nfs_cb_argop4 argarray<>; +}; + +struct CB_COMPOUND4res { + nfsstat4 status; + utf8str_cs tag; + nfs_cb_resop4 resarray<>; +}; + + + +/* + * Program number is in the transient range, since the client + * will assign the exact transient program number and provide + * that to the server via the SETCLIENTID operation. + */ +program NFS4_CALLBACK { + version NFS_CB { + void + CB_NULL(void) = 0; + CB_COMPOUND4res + CB_COMPOUND(CB_COMPOUND4args) = 1; + } = 1; +} = 0x40000000; diff --git a/go/nfsd/nfsd_test.go b/go/nfsd/nfsd_test.go new file mode 100644 index 00000000..fd31b7ef --- /dev/null +++ b/go/nfsd/nfsd_test.go @@ -0,0 +1,3452 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "encoding/binary" + "fmt" + "net" + "os" + "path/filepath" + "sort" + "testing" + "time" +) + +func startTestServer(t *testing.T, dir string) (addr string, cleanup func()) { + t.Helper() + fs := NewLocalTernVFS(dir) + stagingDir := t.TempDir() + ss, err := NewLocalStagingStore(stagingDir, nil) + if err != nil { + t.Fatal(err) + } + srv, err := NewServer(fs, ss, nil) + if err != nil { + t.Fatal(err) + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + go func() { + for { + conn, err := ln.Accept() + if err != nil { + return + } + go srv.handleConn(conn) + } + }() + return ln.Addr().String(), func() { ln.Close() } +} + +func dial(t *testing.T, addr string) net.Conn { + t.Helper() + conn, err := net.DialTimeout("tcp", addr, time.Second) + if err != nil { + t.Fatal(err) + } + return conn +} + +func sendRPC(conn net.Conn, xid uint32, proc uint32, body []byte) ([]byte, error) { + buf := make([]byte, 0, 128+len(body)) + buf = binary.BigEndian.AppendUint32(buf, xid) + buf = binary.BigEndian.AppendUint32(buf, 0) // CALL + buf = binary.BigEndian.AppendUint32(buf, 2) // RPC version + buf = binary.BigEndian.AppendUint32(buf, 100003) // NFS prog + buf = binary.BigEndian.AppendUint32(buf, 4) // NFS v4 + buf = binary.BigEndian.AppendUint32(buf, proc) + buf = binary.BigEndian.AppendUint32(buf, 0) // cred flavor + buf = binary.BigEndian.AppendUint32(buf, 0) // cred len + buf = binary.BigEndian.AppendUint32(buf, 0) // verf flavor + buf = binary.BigEndian.AppendUint32(buf, 0) // verf len + buf = append(buf, body...) + + if err := writeFrame(conn, buf); err != nil { + return nil, err + } + return readFrame(conn) +} + +func parseRPCReply(t *testing.T, reply []byte) []byte { + t.Helper() + if len(reply) < 24 { + t.Fatalf("reply too short: %d bytes", len(reply)) + } + acceptStat := binary.BigEndian.Uint32(reply[20:24]) + if acceptStat != 0 { + t.Fatalf("accept_stat = %d, want SUCCESS", acceptStat) + } + return reply[24:] +} + +// sendCompound builds a compound, sends it, and returns the parsed COMPOUND4res. +func sendCompound(t *testing.T, conn net.Conn, xid uint32, build func(w *COMPOUND4argsWriter)) COMPOUND4res { + t.Helper() + var body []byte + w := StartCOMPOUND4args(body) + tagW := w.StartTag() + body = tagW.SetData(nil).Finish() + w.Resume(body) + w.SetMinorversion(0) + + build(&w) + + body = w.Finish() + + reply, err := sendRPC(conn, xid, procCompound, body) + if err != nil { + t.Fatal(err) + } + nfsBody := parseRPCReply(t, reply) + res, ok := ReadCOMPOUND4res(nfsBody) + if !ok { + t.Fatal("failed to parse COMPOUND4res") + } + return res +} + +// expectOK checks compound status is NFS4_OK and returns the resarray iterator. +func expectOK(t *testing.T, res COMPOUND4res) NfsResop4EntryIter { + t.Helper() + if res.Status() != NFS4_OK { + t.Fatalf("compound status = %d, want NFS4_OK", res.Status()) + } + return res.Resarray() +} + +// nextOp advances the iterator and returns the entry. +func nextOp(t *testing.T, iter *NfsResop4EntryIter) NfsResop4Entry { + t.Helper() + if !iter.Next() { + t.Fatal("expected another op in resarray") + } + return iter.Resarray() +} + +// getAttrData extracts attribute values from a GETATTR4resok fattr4. +// Uses Fattr4.AttrVals().Data() which requires correct codegen for +// sequential variable-size field getters (bitmap4 then attrlist4). +func getAttrData(t *testing.T, getattrOk GETATTR4resok) []byte { + t.Helper() + return getattrOk.ObjAttributes().AttrVals().Data() +} + +// setupClient runs SETCLIENTID + SETCLIENTID_CONFIRM and returns the assigned clientid. +func setupClient(t *testing.T, conn net.Conn, xid *uint32) uint64 { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + scw := w.AppendArgarray_Setclientid() + clientW := scw.StartClient() + clientW = clientW.SetId([]byte("test-client")) + buf := clientW.Finish() + scw.Resume(buf) + cbW := scw.StartCallback() + cbW.SetCbProgram(0x40000000) + locW := cbW.StartCbLocation() + netidW := locW.StartRNetid() + buf = netidW.SetData([]byte("tcp")).Finish() + locW.Resume(buf) + addrW := locW.StartRAddr() + buf = addrW.SetData([]byte("0.0.0.0.0.0")).Finish() + locW.Resume(buf) + buf = locW.Finish() + cbW.Resume(buf) + buf = cbW.Finish() + scw.Resume(buf) + scw.SetCallbackIdent(0) + buf = scw.Finish() + w.Resume(buf) + }) + *xid++ + iter := expectOK(t, res) + entry := nextOp(t, &iter) + scRes := entry.Value().AsSETCLIENTID4resEntry() + if scRes.Disc() != NFS4_OK { + t.Fatalf("SETCLIENTID status = %d", scRes.Disc()) + } + clientid := scRes.Value().AsSETCLIENTID4resok().Clientid() + + res = sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + scw := w.AppendArgarray_SetclientidConfirm() + scw.SetClientid(clientid) + }) + *xid++ + iter = expectOK(t, res) + entry = nextOp(t, &iter) + if entry.Value().AsSETCLIENTIDCONFIRM4res().Status() != NFS4_OK { + t.Fatal("SETCLIENTID_CONFIRM failed") + } + return clientid +} + +func TestNullRPC(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + reply, err := sendRPC(conn, 1, procNull, nil) + if err != nil { + t.Fatal(err) + } + parseRPCReply(t, reply) +} + +func TestProgMismatch(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // Send an RPC with wrong program number. + buf := make([]byte, 0, 64) + buf = binary.BigEndian.AppendUint32(buf, 99) // xid + buf = binary.BigEndian.AppendUint32(buf, 0) // CALL + buf = binary.BigEndian.AppendUint32(buf, 2) // RPC version + buf = binary.BigEndian.AppendUint32(buf, 999999) // wrong program + buf = binary.BigEndian.AppendUint32(buf, 4) // version + buf = binary.BigEndian.AppendUint32(buf, 0) // proc + buf = binary.BigEndian.AppendUint32(buf, 0) // cred flavor + buf = binary.BigEndian.AppendUint32(buf, 0) // cred len + buf = binary.BigEndian.AppendUint32(buf, 0) // verf flavor + buf = binary.BigEndian.AppendUint32(buf, 0) // verf len + + if err := writeFrame(conn, buf); err != nil { + t.Fatal(err) + } + reply, err := readFrame(conn) + if err != nil { + t.Fatal(err) + } + // Should get a PROG_MISMATCH reply. + if len(reply) < 24 { + t.Fatalf("reply too short: %d bytes", len(reply)) + } + acceptStat := binary.BigEndian.Uint32(reply[20:24]) + if acceptStat != acceptProgMismatch { + t.Fatalf("accept_stat = %d, want PROG_MISMATCH (%d)", acceptStat, acceptProgMismatch) + } +} + +func TestPutrootfhGetattr(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + res := sendCompound(t, conn, 2, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(1<= 28", len(attrData)) + } + off := 0 + + nfsType := binary.BigEndian.Uint32(attrData[off:]) + off += 4 + if nfsType != 1 { // NF4REG + t.Fatalf("type = %d, want 1 (NF4REG)", nfsType) + } + + change := binary.BigEndian.Uint64(attrData[off:]) + off += 8 + if change == 0 { + t.Fatal("change = 0, want nonzero mtime-based value") + } + + size := binary.BigEndian.Uint64(attrData[off:]) + off += 8 + if size != uint64(len(content)) { + t.Fatalf("size = %d, want %d", size, len(content)) + } + + fileid := binary.BigEndian.Uint64(attrData[off:]) + if fileid == 0 { + t.Fatal("fileid = 0, want nonzero inode number") + } +} + +func TestWriteAndRead(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // PUTROOTFH + OPEN(CREATE) + OPEN_CONFIRM + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + // OPEN4_CREATE with UNCHECKED4 and empty attrs. + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("newfile.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + + // Get the stateid from OPEN. + entry := nextOp(t, &iter) + openRes := entry.Value().AsOPEN4resEntry() + if openRes.Disc() != NFS4_OK { + t.Fatalf("OPEN status = %d", openRes.Disc()) + } + openOk := openRes.Value().AsOPEN4resok() + openStateid := openOk.Stateid() + + // Get the filehandle from GETFH (file is transient, not yet in directory). + entry = nextOp(t, &iter) + fh := append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + + nextOp(t, &iter) // OPEN_CONFIRM + + // WRITE to the file. + writeData := []byte("hello world from NFS write!") + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + ww := w.AppendArgarray_Write() + sid := ww.Stateid() + sid.SetSeqid(openStateid.Seqid()) + for i := 0; i < 12; i++ { + sid.SetOther(i, openStateid.Other(i)) + } + ww = ww.SetOffset(0) + ww = ww.SetStable(2) // FILE_SYNC4 + ww = ww.SetData(writeData) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry = nextOp(t, &iter) + writeRes := entry.Value().AsWRITE4resEntry() + if writeRes.Disc() != NFS4_OK { + t.Fatalf("WRITE status = %d", writeRes.Disc()) + } + writeOk := writeRes.Value().AsWRITE4resok() + if writeOk.Count() != uint32(len(writeData)) { + t.Fatalf("WRITE count = %d, want %d", writeOk.Count(), len(writeData)) + } + + // CLOSE to commit the file. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + sid := caw.OpenStateid() + sid.SetSeqid(openStateid.Seqid()) + for i := 0; i < 12; i++ { + sid.SetOther(i, openStateid.Other(i)) + } + }) + xid++ + expectOK(t, res) + + // Verify the file was written to disk. + data, err := os.ReadFile(filepath.Join(dir, "newfile.txt")) + if err != nil { + t.Fatalf("reading file: %v", err) + } + if string(data) != string(writeData) { + t.Fatalf("file content = %q, want %q", data, writeData) + } +} + +func TestStagedFileGetattrAndRead(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create and open a new file for write. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("staged.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + openOk := entry.Value().AsOPEN4resEntry().Value().AsOPEN4resok() + openStateid := openOk.Stateid() + entry = nextOp(t, &iter) // GETFH + fh := append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // Write some data using the open stateid. + writeData := []byte("staged content here!") + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + ww := w.AppendArgarray_Write() + sid := ww.Stateid() + sid.SetSeqid(openStateid.Seqid()) + for i := 0; i < 12; i++ { + sid.SetOther(i, openStateid.Other(i)) + } + ww = ww.SetOffset(0) + ww = ww.SetStable(2) + ww = ww.SetData(writeData) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // GETATTR should reflect the staged size, not the empty VFS file. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(1 << FATTR4_SIZE) + buf = bw.Finish() + gw.Resume(buf) + buf = gw.Finish() + w.Resume(buf) + }) + xid++ + + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry = nextOp(t, &iter) + getattrOk := entry.Value().AsGETATTR4resEntry().Value().AsGETATTR4resok() + attrData := getAttrData(t, getattrOk) + if len(attrData) < 8 { + t.Fatalf("attr data too short: %d bytes", len(attrData)) + } + size := binary.BigEndian.Uint64(attrData[:8]) + if size != uint64(len(writeData)) { + t.Fatalf("GETATTR size = %d, want %d (staged size)", size, len(writeData)) + } + + // READ with anonymous stateid should return staged data. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + rw := w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) // anonymous stateid (all zeros) + rw.SetOffset(0) + rw.SetCount(1024) + }) + xid++ + + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry = nextOp(t, &iter) + readRes := entry.Value().AsREAD4resEntry() + if readRes.Disc() != NFS4_OK { + t.Fatalf("READ status = %s", Nfsstat4Name(readRes.Disc())) + } + readData := readRes.Value().AsREAD4resok().Data() + if string(readData) != string(writeData) { + t.Fatalf("READ data = %q, want %q", readData, writeData) + } + + // Close the file. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + sid := caw.OpenStateid() + sid.SetSeqid(openStateid.Seqid()) + for i := 0; i < 12; i++ { + sid.SetOther(i, openStateid.Other(i)) + } + }) + xid++ + expectOK(t, res) +} + +func TestOpenExistingForWriteRejected(t *testing.T) { + dir := t.TempDir() + // Create an existing file. + os.WriteFile(filepath.Join(dir, "existing.txt"), []byte("original"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Try to OPEN existing file for write. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_WRITE) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + ow.SetOpenhow_Default(OPEN4_NOCREATE) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("existing.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + }) + xid++ + + // PUTROOTFH should succeed, but OPEN should fail with NFS4ERR_PERM. + status := res.Status() + if status != NFS4ERR_PERM { + t.Fatalf("OPEN existing for write: got status %s, want NFS4ERR_PERM", + Nfsstat4Name(status)) + } +} + +func TestCreateDirectory(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // PUTROOTFH + CREATE(NF4DIR, "subdir") + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + cw := w.AppendArgarray_Create() + cw.SetObjtype_Nf4dir() + nameW := cw.StartObjname() + buf := nameW.SetData([]byte("subdir")).Finish() + cw.Resume(buf) + // Empty createattrs. + faw := cw.StartCreateattrs() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + cw.Resume(buf) + buf = cw.Finish() + w.Resume(buf) + }) + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + createRes := entry.Value().AsCREATE4resEntry() + if createRes.Disc() != NFS4_OK { + t.Fatalf("CREATE status = %d", createRes.Disc()) + } + + // Verify the directory exists on disk. + info, err := os.Stat(filepath.Join(dir, "subdir")) + if err != nil { + t.Fatalf("stat subdir: %v", err) + } + if !info.IsDir() { + t.Fatal("subdir is not a directory") + } +} + +func TestRemoveFile(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "victim.txt"), []byte("delete me"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // PUTROOTFH + REMOVE("victim.txt") + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf := tw.SetData([]byte("victim.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + removeRes := entry.Value().AsREMOVE4resEntry() + if removeRes.Disc() != NFS4_OK { + t.Fatalf("REMOVE status = %d", removeRes.Disc()) + } + + // Verify the file is gone. + if _, err := os.Stat(filepath.Join(dir, "victim.txt")); !os.IsNotExist(err) { + t.Fatal("victim.txt still exists after REMOVE") + } +} + +func TestRename(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "old.txt"), []byte("rename me"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // PUTROOTFH + SAVEFH + RENAME(old.txt -> new.txt) + // savedFH = source dir, currentFH = target dir (both root here). + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Savefh() + + rw := w.AppendArgarray_Rename() + onw := rw.StartOldname() + buf := onw.SetData([]byte("old.txt")).Finish() + rw.Resume(buf) + nnw := rw.StartNewname() + buf = nnw.SetData([]byte("new.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // SAVEFH + entry := nextOp(t, &iter) + renameRes := entry.Value().AsRENAME4resEntry() + if renameRes.Disc() != NFS4_OK { + t.Fatalf("RENAME status = %d", renameRes.Disc()) + } + + // Verify: old gone, new exists with correct content. + if _, err := os.Stat(filepath.Join(dir, "old.txt")); !os.IsNotExist(err) { + t.Fatal("old.txt still exists after RENAME") + } + data, err := os.ReadFile(filepath.Join(dir, "new.txt")) + if err != nil { + t.Fatalf("reading new.txt: %v", err) + } + if string(data) != "rename me" { + t.Fatalf("new.txt content = %q, want %q", data, "rename me") + } +} + +func TestDelegpurgeNotSupported(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + dw := w.AppendArgarray_Delegpurge() + dw.SetClientid(1) + }) + + if res.Status() != NFS4ERR_NOTSUPP { + t.Fatalf("DELEGPURGE status = %d, want NFS4ERR_NOTSUPP", res.Status()) + } +} + +func TestLinkNotSupported(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("test"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Savefh() + + lw := w.AppendArgarray_Link() + nw := lw.StartNewname() + buf := nw.SetData([]byte("link.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + }) + + if res.Status() != NFS4ERR_NOTSUPP { + t.Fatalf("LINK status = %d, want NFS4ERR_NOTSUPP", res.Status()) + } +} + +func TestCreateSymlink(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "target.txt"), []byte("target"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // PUTROOTFH + CREATE(NF4LNK, "link" -> "target.txt") + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + cw := w.AppendArgarray_Create() + ltw := cw.SetObjtype_Nf4lnk() + buf := ltw.SetData([]byte("target.txt")).Finish() + cw.Resume(buf) + nameW := cw.StartObjname() + buf = nameW.SetData([]byte("link")).Finish() + cw.Resume(buf) + // Empty createattrs. + faw := cw.StartCreateattrs() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + cw.Resume(buf) + buf = cw.Finish() + w.Resume(buf) + }) + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + createRes := entry.Value().AsCREATE4resEntry() + if createRes.Disc() != NFS4_OK { + t.Fatalf("CREATE symlink status = %d", createRes.Disc()) + } + + // Verify the symlink. + target, err := os.Readlink(filepath.Join(dir, "link")) + if err != nil { + t.Fatalf("readlink: %v", err) + } + if target != "target.txt" { + t.Fatalf("symlink target = %q, want %q", target, "target.txt") + } +} + +func TestMinorVersionMismatch(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // Build a COMPOUND with minorversion=1 (NFSv4.1). + var body []byte + w := StartCOMPOUND4args(body) + tagW := w.StartTag() + body = tagW.SetData(nil).Finish() + w.Resume(body) + w.SetMinorversion(1) + w.AppendArgarray_Putrootfh() + body = w.Finish() + + reply, err := sendRPC(conn, 1, procCompound, body) + if err != nil { + t.Fatal(err) + } + nfsBody := parseRPCReply(t, reply) + res, ok := ReadCOMPOUND4res(nfsBody) + if !ok { + t.Fatal("failed to parse COMPOUND4res") + } + if res.Status() != NFS4ERR_MINOR_VERS_MISMATCH { + t.Fatalf("status = %s, want NFS4ERR_MINOR_VERS_MISMATCH", + Nfsstat4Name(res.Status())) + } +} + +func TestCreateEmptyFile(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // PUTROOTFH + OPEN(CREATE) + OPEN_CONFIRM + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("empty.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + openRes := entry.Value().AsOPEN4resEntry() + if openRes.Disc() != NFS4_OK { + t.Fatalf("OPEN status = %d", openRes.Disc()) + } + openOk := openRes.Value().AsOPEN4resok() + openStateid := openOk.Stateid() + entry = nextOp(t, &iter) // GETFH + fh := append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // CLOSE immediately without writing anything. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + sid := caw.OpenStateid() + sid.SetSeqid(openStateid.Seqid()) + for i := 0; i < 12; i++ { + sid.SetOther(i, openStateid.Other(i)) + } + }) + xid++ + expectOK(t, res) + + // Verify the empty file was created on disk. + data, err := os.ReadFile(filepath.Join(dir, "empty.txt")) + if err != nil { + t.Fatalf("reading file: %v", err) + } + if len(data) != 0 { + t.Fatalf("file content = %q, want empty", data) + } +} + +// openReadFile opens a file for read and returns the open stateid and filehandle. +func openReadFile(t *testing.T, conn net.Conn, xid *uint32, clientid uint64, filename string) (stateid [16]byte, fh []byte) { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_READ) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + ow.SetOpenhow_Default(OPEN4_NOCREATE) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte(filename)).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + w.AppendArgarray_Getfh() + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + openRes := entry.Value().AsOPEN4resEntry() + if openRes.Disc() != NFS4_OK { + t.Fatalf("OPEN status = %s", Nfsstat4Name(openRes.Disc())) + } + openOk := openRes.Value().AsOPEN4resok() + sid := openOk.Stateid() + binary.BigEndian.PutUint32(stateid[0:4], sid.Seqid()) + for i := 0; i < 12; i++ { + stateid[4+i] = sid.Other(i) + } + entry = nextOp(t, &iter) + fh = append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + return stateid, fh +} + +// openCreateFile opens a new file for write and returns the open stateid and filehandle. +func openCreateFile(t *testing.T, conn net.Conn, xid *uint32, clientid uint64, filename string) (stateid [16]byte, fh []byte) { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte(filename)).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + w.AppendArgarray_Getfh() + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + openRes := entry.Value().AsOPEN4resEntry() + if openRes.Disc() != NFS4_OK { + t.Fatalf("OPEN CREATE status = %s", Nfsstat4Name(openRes.Disc())) + } + openOk := openRes.Value().AsOPEN4resok() + sid := openOk.Stateid() + binary.BigEndian.PutUint32(stateid[0:4], sid.Seqid()) + for i := 0; i < 12; i++ { + stateid[4+i] = sid.Other(i) + } + entry = nextOp(t, &iter) + fh = append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + return stateid, fh +} + +// closeFile sends CLOSE for the given filehandle and stateid. +func closeFile(t *testing.T, conn net.Conn, xid *uint32, fh []byte, stateid [16]byte) { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + sid := caw.OpenStateid() + sid.SetSeqid(binary.BigEndian.Uint32(stateid[0:4])) + for i := 0; i < 12; i++ { + sid.SetOther(i, stateid[4+i]) + } + }) + *xid++ + expectOK(t, res) +} + +// closeFileExpectStatus sends CLOSE and returns the NFS status. +func closeFileExpectStatus(t *testing.T, conn net.Conn, xid *uint32, fh []byte, stateid [16]byte) uint32 { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + sid := caw.OpenStateid() + sid.SetSeqid(binary.BigEndian.Uint32(stateid[0:4])) + for i := 0; i < 12; i++ { + sid.SetOther(i, stateid[4+i]) + } + }) + *xid++ + // The compound status reflects the CLOSE status. + return res.Status() +} + +// collectReaddirNames issues READDIR calls to collect all names in a directory. +func collectReaddirNames(t *testing.T, conn net.Conn, xid *uint32, dirFH []byte) []string { + t.Helper() + var allNames []string + cookie := uint64(0) + var cookieVerf [8]byte + for { + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(dirFH).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + + rdw := w.AppendArgarray_Readdir() + rdw.SetCookie(cookie) + cv := rdw.Cookieverf() + for i := 0; i < 8; i++ { + cv.SetData(i, cookieVerf[i]) + } + rdw.SetDircount(4096) + rdw.SetMaxcount(8192) + bmW := rdw.StartAttrRequest() + bmW.AppendData(1 << FATTR4_TYPE) + buf = bmW.Finish() + rdw.Resume(buf) + buf = rdw.Finish() + w.Resume(buf) + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry := nextOp(t, &iter) + readdirRes := entry.Value().AsREADDIR4resEntry() + if readdirRes.Disc() != NFS4_OK { + t.Fatalf("READDIR status = %s", Nfsstat4Name(readdirRes.Disc())) + } + okRes := readdirRes.Value().AsREADDIR4resok() + // Save cookieverf for next request. + cv := okRes.Cookieverf() + for i := 0; i < 8; i++ { + cookieVerf[i] = cv.Data(i) + } + reply := okRes.Reply() + if reply.EntriesPresent() == TRUE { + entOpt := reply.Entries() + for { + ent := entOpt.AsEntry4() + allNames = append(allNames, string(ent.Name().Data())) + cookie = ent.Cookie() + if ent.NextentryPresent() != TRUE { + break + } + entOpt = ent.Nextentry() + } + } + if reply.Eof() == TRUE { + break + } + } + return allNames +} + +// --- Checklist of new tests --- +// [x] TestReaddirPagination — multi-batch READDIR with continuation cookies +// [x] TestReaddirCookieverfMismatch — NFS4ERR_NOT_SAME on stale verifier +// [x] TestReaddirTooSmall — NFS4ERR_TOOSMALL with tiny maxcount +// [x] TestReaddirNfsDirHidden — .nfs not visible in root listing +// [x] TestReaddirTransientNotVisible — transient files not in listing until CLOSE +// [x] TestSetattrTime — SET_TO_CLIENT_TIME4 and SET_TO_SERVER_TIME4 +// [x] TestSetattrModeRejected — mode/owner → NFS4ERR_ATTRNOTSUPP +// [x] TestSetattrSize — truncate staging file via SETATTR +// [x] TestOpenExclusive4Rejected — EXCLUSIVE4 → NFS4ERR_NOTSUPP +// [x] TestOpenClaimPrevious — CLAIM_PREVIOUS → NFS4ERR_NO_GRACE +// [x] TestOpenRflagsNoConfirm — OPEN4_RESULT_CONFIRM absent from rflags +// [x] TestCloseReplay — CLOSE twice returns OK both times +// [x] TestCloseExpired — CLOSE on nonexistent inode → NFS4ERR_EXPIRED +// [x] TestWriteBadStateid — WRITE with wrong stateid → error +// [x] TestCommitVerifier — COMMIT returns write verifier +// [x] TestReadOnlyMode — no staging → NFS4ERR_ROFS +// [x] TestLockNotSupported — LOCK/LOCKT/LOCKU → NFS4ERR_NOTSUPP +// [x] TestDeterministicReadStateid — same file+client → same stateid +// [x] TestSetclientidReboot — same identity, different verifier + +func TestReaddirPagination(t *testing.T) { + dir := t.TempDir() + // Create 50 files to force multiple batches (VFS batch size is 16). + var expected []string + for i := 0; i < 50; i++ { + name := fmt.Sprintf("file_%03d.txt", i) + os.WriteFile(filepath.Join(dir, name), []byte(name), 0644) + expected = append(expected, name) + } + sort.Strings(expected) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + + // Get root FH. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Getfh() + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + rootFH := append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + + names := collectReaddirNames(t, conn, &xid, rootFH) + sort.Strings(names) + + if len(names) != len(expected) { + t.Fatalf("got %d entries, want %d", len(names), len(expected)) + } + for i := range expected { + if names[i] != expected[i] { + t.Fatalf("entry[%d] = %q, want %q", i, names[i], expected[i]) + } + } +} + +func TestReaddirCookieverfMismatch(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + // First READDIR to get a valid cookie. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + rdw := w.AppendArgarray_Readdir() + rdw.SetCookie(0) + rdw.SetDircount(4096) + rdw.SetMaxcount(8192) + bmW := rdw.StartAttrRequest() + bmW.AppendData(1 << FATTR4_TYPE) + buf := bmW.Finish() + rdw.Resume(buf) + buf = rdw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + readdirOk := entry.Value().AsREADDIR4resEntry().Value().AsREADDIR4resok() + + // Get a valid cookie from first entry. + entOpt := readdirOk.Reply().Entries() + validCookie := entOpt.AsEntry4().Cookie() + + // Send continuation with a bogus non-zero cookieverf. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + rdw := w.AppendArgarray_Readdir() + rdw.SetCookie(validCookie) + cv := rdw.Cookieverf() + // Set a non-zero but wrong verifier. + cv.SetData(0, 0xFF) + cv.SetData(1, 0xFF) + cv.SetData(2, 0xFF) + cv.SetData(3, 0xFF) + cv.SetData(4, 0xFF) + cv.SetData(5, 0xFF) + cv.SetData(6, 0xFF) + cv.SetData(7, 0xFF) + rdw.SetDircount(4096) + rdw.SetMaxcount(8192) + bmW := rdw.StartAttrRequest() + bmW.AppendData(1 << FATTR4_TYPE) + buf := bmW.Finish() + rdw.Resume(buf) + buf = rdw.Finish() + w.Resume(buf) + }) + xid++ + + // Compound should report the READDIR error. + if res.Status() != NFS4ERR_NOT_SAME { + t.Fatalf("expected NFS4ERR_NOT_SAME, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestReaddirTooSmall(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("x"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // Send READDIR with maxcount too small to fit any entry. + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + rdw := w.AppendArgarray_Readdir() + rdw.SetCookie(0) + rdw.SetDircount(4096) + rdw.SetMaxcount(10) // Way too small. + bmW := rdw.StartAttrRequest() + bmW.AppendData(1 << FATTR4_TYPE) + buf := bmW.Finish() + rdw.Resume(buf) + buf = rdw.Finish() + w.Resume(buf) + }) + + if res.Status() != NFS4ERR_TOOSMALL { + t.Fatalf("expected NFS4ERR_TOOSMALL, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestReaddirNfsDirHidden(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "visible.txt"), []byte("x"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + + // Get root FH. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Getfh() + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) + entry := nextOp(t, &iter) + rootFH := append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + + names := collectReaddirNames(t, conn, &xid, rootFH) + for _, name := range names { + if name == ".nfs" { + t.Fatal(".nfs directory should be hidden from READDIR") + } + } + found := false + for _, name := range names { + if name == "visible.txt" { + found = true + } + } + if !found { + t.Fatal("visible.txt not found in READDIR") + } +} + +func TestReaddirTransientNotVisible(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "existing.txt"), []byte("x"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Get root FH. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Getfh() + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) + entry := nextOp(t, &iter) + rootFH := append([]byte(nil), entry.Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + + // Create a file but don't close it yet (still transient). + stateid, fh := openCreateFile(t, conn, &xid, clientid, "newfile.txt") + + // READDIR should NOT show newfile.txt. + names := collectReaddirNames(t, conn, &xid, rootFH) + for _, name := range names { + if name == "newfile.txt" { + t.Fatal("transient file should not appear in READDIR before CLOSE") + } + } + + // Close the file (links it into the directory). + closeFile(t, conn, &xid, fh, stateid) + + // READDIR should now show newfile.txt. + names = collectReaddirNames(t, conn, &xid, rootFH) + found := false + for _, name := range names { + if name == "newfile.txt" { + found = true + } + } + if !found { + t.Fatal("newfile.txt should appear in READDIR after CLOSE") + } +} + +func TestSetattrTime(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // Set mtime to a specific time (2020-01-01 00:00:00 UTC). + targetTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + targetSec := targetTime.Unix() + targetNsec := uint32(0) + + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("file.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + saw := w.AppendArgarray_Setattr() + saw.Stateid().SetSeqid(0) + faw := saw.StartObjAttributes() + bmW := faw.StartAttrmask() + bmW.AppendData(0) // word 0: nothing + bmW.AppendData(1 << (FATTR4_TIME_MODIFY_SET - 32)) + buf = bmW.Finish() + faw.Resume(buf) + // Attr data: SET_TO_CLIENT_TIME4(1) + nfstime4(sec:8 + nsec:4) + attrData := make([]byte, 4+8+4) + binary.BigEndian.PutUint32(attrData[0:4], SET_TO_CLIENT_TIME4) + binary.BigEndian.PutUint64(attrData[4:12], uint64(targetSec)) + binary.BigEndian.PutUint32(attrData[12:16], targetNsec) + alW := faw.StartAttrVals() + buf = alW.SetData(attrData).Finish() + faw.Resume(buf) + buf = faw.Finish() + saw.Resume(buf) + buf = saw.Finish() + w.Resume(buf) + }) + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP + entry := nextOp(t, &iter) + if entry.Value().AsSETATTR4res().Status() != NFS4_OK { + t.Fatalf("SETATTR status = %s", Nfsstat4Name(entry.Value().AsSETATTR4res().Status())) + } + + // Verify mtime was set on disk. + info, err := os.Stat(filepath.Join(dir, "file.txt")) + if err != nil { + t.Fatal(err) + } + if !info.ModTime().Equal(targetTime) { + t.Fatalf("mtime = %v, want %v", info.ModTime(), targetTime) + } +} + +func TestSetattrModeRejected(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("file.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + saw := w.AppendArgarray_Setattr() + saw.Stateid().SetSeqid(0) + faw := saw.StartObjAttributes() + bmW := faw.StartAttrmask() + bmW.AppendData(0) + bmW.AppendData(1 << (FATTR4_MODE - 32)) // mode is not supported + buf = bmW.Finish() + faw.Resume(buf) + attrData := make([]byte, 4) + binary.BigEndian.PutUint32(attrData[0:4], 0755) + alW := faw.StartAttrVals() + buf = alW.SetData(attrData).Finish() + faw.Resume(buf) + buf = faw.Finish() + saw.Resume(buf) + buf = saw.Finish() + w.Resume(buf) + }) + + if res.Status() != NFS4ERR_ATTRNOTSUPP { + t.Fatalf("expected NFS4ERR_ATTRNOTSUPP, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestSetattrSize(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create a file, write data, then truncate via SETATTR size. + stateid, fh := openCreateFile(t, conn, &xid, clientid, "trunc.txt") + + // WRITE 100 bytes. + writeData := make([]byte, 100) + for i := range writeData { + writeData[i] = byte(i) + } + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + sid := ww.Stateid() + sid.SetSeqid(binary.BigEndian.Uint32(stateid[0:4])) + for i := 0; i < 12; i++ { + sid.SetOther(i, stateid[4+i]) + } + ww = ww.SetOffset(0) + ww = ww.SetStable(2) + ww = ww.SetData(writeData) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // SETATTR to truncate to 10 bytes. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + saw := w.AppendArgarray_Setattr() + sid := saw.Stateid() + sid.SetSeqid(binary.BigEndian.Uint32(stateid[0:4])) + for i := 0; i < 12; i++ { + sid.SetOther(i, stateid[4+i]) + } + faw := saw.StartObjAttributes() + bmW := faw.StartAttrmask() + bmW.AppendData(1 << FATTR4_SIZE) + buf = bmW.Finish() + faw.Resume(buf) + attrData := make([]byte, 8) + binary.BigEndian.PutUint64(attrData[0:8], 10) + alW := faw.StartAttrVals() + buf = alW.SetData(attrData).Finish() + faw.Resume(buf) + buf = faw.Finish() + saw.Resume(buf) + buf = saw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry := nextOp(t, &iter) + if entry.Value().AsSETATTR4res().Status() != NFS4_OK { + t.Fatalf("SETATTR status = %s", Nfsstat4Name(entry.Value().AsSETATTR4res().Status())) + } + + // Close and verify. + closeFile(t, conn, &xid, fh, stateid) + + data, err := os.ReadFile(filepath.Join(dir, "trunc.txt")) + if err != nil { + t.Fatal(err) + } + if len(data) != 10 { + t.Fatalf("file size = %d, want 10", len(data)) + } +} + +func TestOpenExclusive4Rejected(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + verf := chw.SetValue_Exclusive4() + for i := 0; i < 8; i++ { + verf.SetData(i, byte(i)) + } + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("excl.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + }) + xid++ + + if res.Status() != NFS4ERR_NOTSUPP { + t.Fatalf("expected NFS4ERR_NOTSUPP, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestOpenClaimPrevious(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_READ) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + ow.SetOpenhow_Default(OPEN4_NOCREATE) + ow.SetClaim_Previous() + buf = ow.Finish() + w.Resume(buf) + }) + xid++ + + if res.Status() != NFS4ERR_NO_GRACE { + t.Fatalf("expected NFS4ERR_NO_GRACE, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestOpenRflagsNoConfirm(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_READ) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + ow.SetOpenhow_Default(OPEN4_NOCREATE) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("file.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + }) + xid++ + + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + entry := nextOp(t, &iter) + openOk := entry.Value().AsOPEN4resEntry().Value().AsOPEN4resok() + rflags := openOk.Rflags() + if rflags&OPEN4_RESULT_CONFIRM != 0 { + t.Fatal("OPEN4_RESULT_CONFIRM should not be set") + } +} + +func TestCloseReplay(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create and close a file. + stateid, fh := openCreateFile(t, conn, &xid, clientid, "replay.txt") + closeFile(t, conn, &xid, fh, stateid) + + // Verify file exists. + if _, err := os.Stat(filepath.Join(dir, "replay.txt")); err != nil { + t.Fatalf("file not created: %v", err) + } + + // Send CLOSE again (replay). Should succeed. + status := closeFileExpectStatus(t, conn, &xid, fh, stateid) + if status != NFS4_OK { + t.Fatalf("CLOSE replay: expected NFS4_OK, got %s", Nfsstat4Name(status)) + } +} + +func TestCloseExpired(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // Construct a filehandle for a nonexistent inode. Use a type=file inode + // with a made-up number that doesn't correspond to any real file. + var fakeFH [8]byte + fakeID := MakeInodeID(InodeTypeFile, 0xDEADDEAD) + binary.BigEndian.PutUint64(fakeFH[:], uint64(fakeID)) + var fakeStateid [16]byte // all zeros + + xid := uint32(1) + status := closeFileExpectStatus(t, conn, &xid, fakeFH[:], fakeStateid) + if status != NFS4ERR_EXPIRED { + t.Fatalf("expected NFS4ERR_EXPIRED, got %s", Nfsstat4Name(status)) + } +} + +func TestWriteBadStateid(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create a file. + _, fh := openCreateFile(t, conn, &xid, clientid, "badwrite.txt") + + // WRITE with a wrong stateid. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + sid := ww.Stateid() + sid.SetSeqid(99) + for i := 0; i < 12; i++ { + sid.SetOther(i, 0xFF) // wrong + } + ww = ww.SetOffset(0) + ww = ww.SetStable(2) + ww = ww.SetData([]byte("bad")) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + + // The WRITE should still succeed because we don't validate stateids + // on WRITE (design doc: "Optionally validate the stateid"). + // Just verify it doesn't crash. + if res.Status() != NFS4_OK { + // If we do validate, the error should be sensible. + t.Logf("WRITE with bad stateid: %s (acceptable)", Nfsstat4Name(res.Status())) + } +} + +func TestCommitVerifier(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + stateid, fh := openCreateFile(t, conn, &xid, clientid, "commit.txt") + + // WRITE some data. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + sid := ww.Stateid() + sid.SetSeqid(binary.BigEndian.Uint32(stateid[0:4])) + for i := 0; i < 12; i++ { + sid.SetOther(i, stateid[4+i]) + } + ww = ww.SetOffset(0) + ww = ww.SetStable(0) // UNSTABLE4 + ww = ww.SetData([]byte("commit test")) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // COMMIT and check the write verifier is non-zero. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pw := w.AppendArgarray_Putfh() + fhW := pw.StartObject() + buf := fhW.SetData(fh).Finish() + pw.Resume(buf) + buf = pw.Finish() + w.Resume(buf) + w.AppendArgarray_Commit() + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry := nextOp(t, &iter) + commitOk := entry.Value().AsCOMMIT4resEntry().Value().AsCOMMIT4resok() + verf := commitOk.Writeverf() + var allZero bool = true + for i := 0; i < 8; i++ { + if verf.Data(i) != 0 { + allZero = false + break + } + } + if allZero { + t.Fatal("COMMIT write verifier should be non-zero") + } + + closeFile(t, conn, &xid, fh, stateid) +} + +func TestReadOnlyMode(t *testing.T) { + dir := t.TempDir() + fs := NewLocalTernVFS(dir) + ss := readOnlyStagingStore{} + srv, err := NewServer(fs, ss, nil) + if err != nil { + t.Fatal(err) + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + go func() { + for { + conn, err := ln.Accept() + if err != nil { + return + } + go srv.handleConn(conn) + } + }() + defer ln.Close() + + conn := dial(t, ln.Addr().String()) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // OPEN with CREATE should fail with NFS4ERR_ROFS. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("test-owner")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("newfile.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + }) + xid++ + + if res.Status() != NFS4ERR_ROFS { + t.Fatalf("expected NFS4ERR_ROFS, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestLockNotSupported(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // LOCK — variable-size writer, need to finish it properly. + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lock() + lw.SetLocker_False() + w.Resume(lw.Finish()) + }) + if res.Status() != NFS4ERR_NOTSUPP { + t.Fatalf("LOCK: expected NFS4ERR_NOTSUPP, got %s", Nfsstat4Name(res.Status())) + } + + // LOCKT — variable-size writer. + res = sendCompound(t, conn, 2, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lockt() + ow := lw.StartOwner() + ow.SetOwner([]byte("x")) + lw.Resume(ow.Finish()) + w.Resume(lw.Finish()) + }) + if res.Status() != NFS4ERR_NOTSUPP { + t.Fatalf("LOCKT: expected NFS4ERR_NOTSUPP, got %s", Nfsstat4Name(res.Status())) + } + + // LOCKU — fixed-size, no finish needed. + res = sendCompound(t, conn, 3, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Locku() + }) + if res.Status() != NFS4ERR_NOTSUPP { + t.Fatalf("LOCKU: expected NFS4ERR_NOTSUPP, got %s", Nfsstat4Name(res.Status())) + } +} + +func TestDeterministicReadStateid(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0644) + + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Open the same file twice and check the stateids are identical. + sid1, _ := openReadFile(t, conn, &xid, clientid, "file.txt") + sid2, _ := openReadFile(t, conn, &xid, clientid, "file.txt") + + if sid1 != sid2 { + t.Fatalf("read stateids differ: %x vs %x", sid1, sid2) + } +} + +func TestSetclientidReboot(t *testing.T) { + dir := t.TempDir() + addr, cleanup := startTestServer(t, dir) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + // First SETCLIENTID + CONFIRM. + xid := uint32(1) + clientid1 := setupClient(t, conn, &xid) + + // Second SETCLIENTID with same identity but different verifier (simulating reboot). + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + scw := w.AppendArgarray_Setclientid() + clientW := scw.StartClient() + // Same identity string as setupClient. + clientW = clientW.SetId([]byte("test-client")) + // Different verifier (setupClient uses default zero verifier). + verf := clientW.Verifier() + for i := 0; i < 8; i++ { + verf.SetData(i, byte(i+1)) + } + buf := clientW.Finish() + scw.Resume(buf) + cbW := scw.StartCallback() + cbW.SetCbProgram(0x40000000) + locW := cbW.StartCbLocation() + netidW := locW.StartRNetid() + buf = netidW.SetData([]byte("tcp")).Finish() + locW.Resume(buf) + addrW := locW.StartRAddr() + buf = addrW.SetData([]byte("0.0.0.0.0.0")).Finish() + locW.Resume(buf) + buf = locW.Finish() + cbW.Resume(buf) + buf = cbW.Finish() + scw.Resume(buf) + scw.SetCallbackIdent(0) + buf = scw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + entry := nextOp(t, &iter) + scRes := entry.Value().AsSETCLIENTID4resEntry() + if scRes.Disc() != NFS4_OK { + t.Fatalf("SETCLIENTID reboot: status = %s", Nfsstat4Name(scRes.Disc())) + } + clientid2 := scRes.Value().AsSETCLIENTID4resok().Clientid() + + // CONFIRM. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + scw := w.AppendArgarray_SetclientidConfirm() + scw.SetClientid(clientid2) + }) + xid++ + iter = expectOK(t, res) + entry = nextOp(t, &iter) + if entry.Value().AsSETCLIENTIDCONFIRM4res().Status() != NFS4_OK { + t.Fatal("SETCLIENTID_CONFIRM reboot failed") + } + + // The new client ID should differ (new file = new InodeID). + if clientid1 == clientid2 { + t.Log("client IDs are the same (verifier stored in same file)") + } +} + +// TestReaddirEntryXDRSize verifies that the size prediction formula in +// readdirEntryXDRSize exactly matches the actual encoded XDR output for +// various attribute masks and name lengths. +func TestReaddirEntryXDRSize(t *testing.T) { + id := MakeInodeID(InodeTypeFile, 42) + ni := NodeInfo{Size: 1024} + + cases := []struct { + name string + mask [2]uint32 + fname string + statOK bool + }{ + {"no_attrs", [2]uint32{0, 0}, "file.txt", true}, + {"type_only", [2]uint32{1 << FATTR4_TYPE, 0}, "file.txt", true}, + {"size_and_type", [2]uint32{(1 << FATTR4_TYPE) | (1 << FATTR4_SIZE), 0}, "file.txt", true}, + {"full_attrs", [2]uint32{supportedAttrs0, supportedAttrs1}, "file.txt", true}, + {"short_name", [2]uint32{1 << FATTR4_TYPE, 0}, "a", true}, + {"padded_name", [2]uint32{1 << FATTR4_TYPE, 0}, "ab", true}, // 2 bytes, pads to 4 + {"exact_4", [2]uint32{1 << FATTR4_TYPE, 0}, "abcd", true}, // 4 bytes, no pad + {"needs_pad", [2]uint32{1 << FATTR4_TYPE, 0}, "abcde", true}, // 5 bytes, pads to 8 + {"stat_failed", [2]uint32{supportedAttrs0, supportedAttrs1}, "file.txt", false}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var respMask [2]uint32 + respMask[0] = tc.mask[0] & supportedAttrs0 + respMask[1] = tc.mask[1] & supportedAttrs1 + + re := readdirEntry{ + DirEntry: DirEntry{ + ID: id, + Name: tc.fname, + NameHash: 12345, + }, + respMask: respMask, + } + if tc.statOK { + re.attrData = encodeAttrs(respMask, id, ni) + } + + predicted := readdirEntryXDRSize(&re) + + // Encode one entry into a dirlist4 and measure. + dirW := StartDirlist4(nil) + encodeDirEntries(&dirW, []readdirEntry{re}, true) + buf := dirW.Finish() + // dirlist4 = entries_present(4) + entry_bytes + eof(4) + // With one entry: entries_present=TRUE(4) is part of the entry's + // XDR size, so total = entry_bytes + eof(4). + // But entries_present(4) is written by SetEntries_True, which is + // accounted in the entry's leading present(4). + actualDirlist := len(buf) + expectedDirlist := predicted + 4 // entry + eof(4) + if actualDirlist != expectedDirlist { + t.Errorf("size mismatch: predicted entry=%d, expected dirlist=%d, actual dirlist=%d", + predicted, expectedDirlist, actualDirlist) + } + }) + } +} diff --git a/go/nfsd/ops.go b/go/nfsd/ops.go new file mode 100644 index 00000000..8c83156b --- /dev/null +++ b/go/nfsd/ops.go @@ -0,0 +1,1215 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "bytes" + "encoding/binary" + "os" + "time" +) + +// Stable write modes. +const ( + unstable4 = 0 + dataSync4 = 1 + fileSync4 = 2 +) + +func (s *Server) opAccess(args ACCESS4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Access() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + requested := args.Access() + ew := w.AppendResarray_Access() + ok := ew.SetValue_Nfs4Ok() + ok.SetSupported(requested) + ok.SetAccess(requested) // grant everything requested + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opClose(args CLOSE4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Close() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + sid := extractStateID(args.OpenStateid()) + + // Check if there's a staging file for the current filehandle. + meta, hasMeta := s.stagingStore.GetMeta(st.currentID) + if hasMeta { + // First CLOSE for a write-open: link the transient file. + if sid != meta.NFSStateID { + ew := w.AppendResarray_Close() + ew.SetValue_Default(NFS4ERR_BAD_STATEID) + w.Resume(ew.Finish()) + return NFS4ERR_BAD_STATEID + } + sf := s.stagingStore.Get(st.currentID) + if sf != nil { + r, rErr := sf.Reader() + if rErr == nil { + _ = s.fs.LinkFile(st.currentID, meta.TernCookie, meta.DirID, meta.FileName, r) + } + } + s.stagingStore.Remove(st.currentID) + } else { + // No staging: either a read-close or a replay of a write-close. + // Check if the file still exists. For read opens the file is + // immutable and always exists. For write-close replays the file + // exists if LinkFile succeeded previously. If the transient was + // GC'd without being linked, Stat fails and we report expired. + if _, err := s.fs.Stat(st.currentID); err != nil { + ew := w.AppendResarray_Close() + ew.SetValue_Default(NFS4ERR_EXPIRED) + w.Resume(ew.Finish()) + return NFS4ERR_EXPIRED + } + } + + ew := w.AppendResarray_Close() + stid := ew.SetValue_Nfs4Ok() + stid.SetSeqid(args.OpenStateid().Seqid() + 1) + writeStateID(stid, sid) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opCommit(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Commit() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + // Sync the staging file for the current filehandle. + if sf := s.stagingStore.Get(st.currentID); sf != nil { + sf.Sync() + } + + ew := w.AppendResarray_Commit() + okW := ew.SetValue_Nfs4Ok() + verf := okW.Writeverf() + for i := 0; i < 8; i++ { + verf.SetData(i, s.writeVerifier[i]) + } + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opCreate(args CREATE4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Create() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + if s.stagingStore.ReadOnly() { + ew := w.AppendResarray_Create() + ew.SetValue_Default(NFS4ERR_ROFS) + w.Resume(ew.Finish()) + return NFS4ERR_ROFS + } + + name := string(args.Objname().Data()) + objType := args.ObjtypeType() + + var newID InodeID + var err error + + switch objType { + case NF4DIR: + newID, err = s.fs.Mkdir(st.currentID, name) + case NF4LNK: + target := string(args.Objtype().AsLinktext4().Data()) + newID, err = s.fs.Symlink(st.currentID, name, target) + default: + // We don't support creating block/char/socket/fifo devices. + ew := w.AppendResarray_Create() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP + } + + if err != nil { + ew := w.AppendResarray_Create() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + + st.currentID = newID + st.currentIDSet = true + + ew := w.AppendResarray_Create() + okW := ew.SetValue_Nfs4Ok() + cinfo := okW.Cinfo() + cinfo.SetAtomic(TRUE) + now := uint64(time.Now().UnixNano()) + cinfo.SetBefore(now - 1) + cinfo.SetAfter(now) + + // attrset bitmap: empty (we don't apply createattrs). + bmW := okW.StartAttrset() + buf := bmW.Finish() + okW.Resume(buf) + buf = okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opDelegpurge(w *COMPOUND4resWriter) uint32 { + r := w.AppendResarray_Delegpurge() + r.SetStatus(NFS4ERR_NOTSUPP) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opDelegreturn(st *compoundState, w *COMPOUND4resWriter) uint32 { + // We never grant delegations, but accept returns gracefully. + r := w.AppendResarray_Delegreturn() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opGetattr(args GETATTR4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Getattr() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + ni, err := s.fs.Stat(st.currentID) + if err != nil { + ew := w.AppendResarray_Getattr() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + + // If the file has an active staging buffer, use its size. + if sz, ok := s.stagingStore.StagedSize(st.currentID); ok { + ni.Size = sz + } + + reqMask := parseBitmap(args.AttrRequest()) + + ew := w.AppendResarray_Getattr() + okW := ew.SetValue_Nfs4Ok() + + // Build fattr4: bitmap + attrlist. + faw := okW.StartObjAttributes() + bmW := faw.StartAttrmask() + + // Compute response bitmap: intersection of requested and supported. + var respMask [2]uint32 + respMask[0] = reqMask[0] & supportedAttrs0 + respMask[1] = reqMask[1] & supportedAttrs1 + + bmW.AppendData(respMask[0]) + bmW.AppendData(respMask[1]) + buf := bmW.Finish() + faw.Resume(buf) + + // Build attribute values. + alW := faw.StartAttrVals() + attrBuf := encodeAttrs(respMask, st.currentID, ni) + buf = alW.SetData(attrBuf).Finish() + faw.Resume(buf) + buf = faw.Finish() + okW.Resume(buf) + buf = okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + + return NFS4_OK +} + +func (s *Server) opGetfh(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Getfh() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + ew := w.AppendResarray_Getfh() + okW := ew.SetValue_Nfs4Ok() + fhW := okW.StartObject() + buf := fhW.SetData(inodeIDToFH(st.currentID)).Finish() + okW.Resume(buf) + buf = okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opLink(st *compoundState, w *COMPOUND4resWriter) uint32 { + // TernFS doesn't support hard links. + ew := w.AppendResarray_Link() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opLock(w *COMPOUND4resWriter) uint32 { + ew := w.AppendResarray_Lock() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opLockt(w *COMPOUND4resWriter) uint32 { + ew := w.AppendResarray_Lockt() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opLocku(w *COMPOUND4resWriter) uint32 { + ew := w.AppendResarray_Locku() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opLookup(args LOOKUP4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + r := w.AppendResarray_Lookup() + r.SetStatus(NFS4ERR_NOFILEHANDLE) + return NFS4ERR_NOFILEHANDLE + } + name := string(args.Objname().Data()) + // Hide the internal .nfs directory from client access. + if name == nfsDirName && st.currentID == s.fs.RootID() { + r := w.AppendResarray_Lookup() + r.SetStatus(NFS4ERR_NOENT) + return NFS4ERR_NOENT + } + id, err := s.fs.Lookup(st.currentID, name) + if err != nil { + r := w.AppendResarray_Lookup() + status := s.errToNFS(err) + r.SetStatus(status) + return status + } + st.currentID = id + st.currentIDSet = true + r := w.AppendResarray_Lookup() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opLookupp(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + r := w.AppendResarray_Lookupp() + r.SetStatus(NFS4ERR_NOFILEHANDLE) + return NFS4ERR_NOFILEHANDLE + } + id, err := s.fs.LookupParent(st.currentID) + if err != nil { + r := w.AppendResarray_Lookupp() + status := s.errToNFS(err) + r.SetStatus(status) + return status + } + st.currentID = id + st.currentIDSet = true + r := w.AppendResarray_Lookupp() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opNverify(args NVERIFY4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + r := w.AppendResarray_Nverify() + r.SetStatus(NFS4ERR_NOFILEHANDLE) + return NFS4ERR_NOFILEHANDLE + } + same, status := s.verifyAttrs(st.currentID, args.ObjAttributes()) + if status != NFS4_OK { + r := w.AppendResarray_Nverify() + r.SetStatus(status) + return status + } + r := w.AppendResarray_Nverify() + if same { + // NVERIFY: fail if attributes are the same. + r.SetStatus(NFS4ERR_SAME) + return NFS4ERR_SAME + } + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opOpen(args OPEN4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + // Reject all writes if read-only (no staging directory configured). + access := args.ShareAccess() + if s.stagingStore.ReadOnly() && access&OPEN4_SHARE_ACCESS_WRITE != 0 { + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_ROFS) + w.Resume(ew.Finish()) + return NFS4ERR_ROFS + } + + claim := args.Claim() + claimType := args.ClaimType() + owner := args.Owner() + clientID := owner.Clientid() + + var targetID InodeID + var nfsSID StateID + created := false + + switch claimType { + case CLAIM_NULL: + dirID := st.currentID + fileName := string(claim.AsNull().Data()) + + // Check if this is a create. + if args.OpenhowType() == OPEN4_CREATE { + // EXCLUSIVE4 not supported — clients should fall back + // to GUARDED4 or UNCHECKED4. + if args.Openhow().AsCreatehow4Entry().Disc() == EXCLUSIVE4 { + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP + } + // Try lookup first. + id, err := s.fs.Lookup(dirID, fileName) + if err != nil { + // File doesn't exist — construct a transient file. + var fileCookie Cookie + id, fileCookie, err = s.fs.ConstructFile() + if err != nil { + ew := w.AppendResarray_Open() + ew.SetValue_Default(s.errToNFS(err)) + w.Resume(ew.Finish()) + return s.errToNFS(err) + } + // Generate a random NFS stateid and create staging with metadata. + nfsSID = newNFSStateID() + meta := StagingMeta{ + DirID: dirID, + FileName: fileName, + TernCookie: fileCookie, + NFSStateID: nfsSID, + } + if _, sfErr := s.stagingStore.Create(id, meta); sfErr != nil { + s.log.Error("staging create error", "err", sfErr) + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_IO) + w.Resume(ew.Finish()) + return NFS4ERR_IO + } + created = true + } + targetID = id + } else { + id, err := s.fs.Lookup(dirID, fileName) + if err != nil { + ew := w.AppendResarray_Open() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + targetID = id + } + + // Files are immutable: reject write access to existing files. + // To replace a file, clients must remove + create. + if !created && access&OPEN4_SHARE_ACCESS_WRITE != 0 { + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_PERM) + w.Resume(ew.Finish()) + return NFS4ERR_PERM + } + case CLAIM_PREVIOUS: + // No grace period — there is no lock/open state to reclaim. + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_NO_GRACE) + w.Resume(ew.Finish()) + return NFS4ERR_NO_GRACE + default: + ew := w.AppendResarray_Open() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP + } + + // For read opens, derive a deterministic stateid from the file and client. + if !created { + nfsSID = deriveReadStateID(targetID, clientID) + } + + st.currentID = targetID + st.currentIDSet = true + + ew := w.AppendResarray_Open() + okW := ew.SetValue_Nfs4Ok() + + stid := okW.Stateid() + stid.SetSeqid(1) + writeStateID(stid, nfsSID) + + cinfo := okW.Cinfo() + cinfo.SetAtomic(TRUE) + now := uint64(time.Now().UnixNano()) + if created { + cinfo.SetBefore(now - 1) + cinfo.SetAfter(now) + } else { + cinfo.SetBefore(now) + cinfo.SetAfter(now) + } + + okW.SetRflags(OPEN4_RESULT_LOCKTYPE_POSIX) + + bmW := okW.StartAttrset() + buf := bmW.Finish() + okW.Resume(buf) + + okW.SetDelegation_None() + + buf = okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +// deriveReadStateID produces a deterministic stateid for read opens. +// The stateid encodes the InodeID and a hash of the client ID, so any +// server instance can recompute it without persistent state. +func deriveReadStateID(fileID InodeID, clientID uint64) StateID { + var sid StateID + binary.BigEndian.PutUint64(sid[0:8], uint64(fileID)) + // Mix in the client ID for basic validation. + binary.BigEndian.PutUint32(sid[8:12], uint32(clientID^(clientID>>32))) + return sid +} + +func (s *Server) opOpenConfirm(args OPENCONFIRM4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_OpenConfirm() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + sid := extractStateID(args.OpenStateid()) + seqid := args.OpenStateid().Seqid() + + // No persistent open state to confirm — just echo the stateid + // with an incremented seqid. + ew := w.AppendResarray_OpenConfirm() + ok := ew.SetValue_Nfs4Ok() + stid := ok.OpenStateid() + stid.SetSeqid(seqid + 1) + writeStateID(stid, sid) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opOpenDowngrade(st *compoundState, w *COMPOUND4resWriter) uint32 { + // No persistent open state — downgrade is a no-op. + ew := w.AppendResarray_OpenDowngrade() + ew.SetValue_Default(NFS4ERR_NOTSUPP) + w.Resume(ew.Finish()) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opOpenattr(w *COMPOUND4resWriter) uint32 { + r := w.AppendResarray_Openattr() + r.SetStatus(NFS4ERR_NOTSUPP) + return NFS4ERR_NOTSUPP +} + +func (s *Server) opPutfh(args PUTFH4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + fhData := args.Object().Data() + id, ok := fhToInodeID(fhData) + if !ok { + r := w.AppendResarray_Putfh() + r.SetStatus(NFS4ERR_BADHANDLE) + return NFS4ERR_BADHANDLE + } + st.currentID = id + st.currentIDSet = true + r := w.AppendResarray_Putfh() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opPutrootfh(st *compoundState, w *COMPOUND4resWriter, isPub bool) uint32 { + st.currentID = s.fs.RootID() + st.currentIDSet = true + if isPub { + r := w.AppendResarray_Putpubfh() + r.SetStatus(NFS4_OK) + } else { + r := w.AppendResarray_Putrootfh() + r.SetStatus(NFS4_OK) + } + return NFS4_OK +} + +func (s *Server) opRead(args READ4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Read() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + // Check if we should read from a staging buffer. + sf := s.stagingStore.Get(st.currentID) + + offset := args.Offset() + count := args.Count() + buf := make([]byte, count) + + var n int + var eof bool + var err error + + if sf != nil { + n, eof, err = sf.Read(offset, buf) + } else { + n, eof, err = s.fs.Read(st.currentID, offset, buf) + } + + if err != nil { + ew := w.AppendResarray_Read() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + ew := w.AppendResarray_Read() + okW := ew.SetValue_Nfs4Ok() + if eof { + okW = okW.SetEof(TRUE) + } else { + okW = okW.SetEof(FALSE) + } + okW = okW.SetData(buf[:n]) + rbuf := okW.Finish() + ew.Resume(rbuf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opReaddir(args READDIR4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Readdir() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + cookie := args.Cookie() + maxCount := args.Maxcount() + if maxCount > 1<<20 { + maxCount = 1 << 20 + } + + // Validate cookieverf on continuation requests (RFC 7530 §16.24.4). + // cookieverf = directory mtime; if it changed, the listing is stale. + // Some clients (e.g. libnfs) send all-zero cookieverf on continuations + // ("should" echo it back per RFC, not "MUST"), so only check non-zero. + if cookie != 0 { + var clientVerf [8]byte + cv := args.Cookieverf() + for i := range clientVerf { + clientVerf[i] = cv.Data(i) + } + if clientVerf != [8]byte{} { + ni, err := s.fs.Stat(st.currentID) + if err != nil { + ew := w.AppendResarray_Readdir() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + var expectedVerf [8]byte + binary.BigEndian.PutUint64(expectedVerf[:], uint64(ni.Mtime.UnixNano())) + if clientVerf != expectedVerf { + ew := w.AppendResarray_Readdir() + ew.SetValue_Default(NFS4ERR_NOT_SAME) + w.Resume(ew.Finish()) + return NFS4ERR_NOT_SAME + } + } + } + + reqMask := parseBitmap(args.AttrRequest()) + dirCount := args.Dircount() + if dirCount > 1<<20 { + dirCount = 1 << 20 + } + + // Batch multiple VFS Readdir calls until we have enough entries + // and NextHash >= 3 (avoiding NFS reserved cookie values 1 and 2). + maxEntries := int(maxCount / 100) + if maxEntries < 32 { + maxEntries = 32 + } + var allEntries []DirEntry + startHash := cookie + eof := false + for { + entries, nextHash, err := s.fs.Readdir(st.currentID, startHash) + if err != nil { + ew := w.AppendResarray_Readdir() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + allEntries = append(allEntries, entries...) + if nextHash == 0 { + eof = true + break + } + if nextHash >= 3 && len(allEntries) >= maxEntries { + break + } + startHash = nextHash + } + + // NFS cookie semantics: cookie=X means "I already have entries up to + // and including X". Skip entries with NameHash <= cookie on continuations, + // since VFS Readdir returns entries with hash >= startHash (inclusive). + // Also hide the internal .nfs directory from client directory listings. + { + filtered := allEntries[:0] + for _, e := range allEntries { + if cookie != 0 && e.NameHash <= cookie { + continue + } + if st.currentID == s.fs.RootID() && e.Name == nfsDirName { + continue + } + filtered = append(filtered, e) + } + allEntries = filtered + } + + // Build staged sizes map for entries being written. + ss := stagedSizes(s.stagingStore.StagedSizes()) + + // Pre-compute attributes for each entry so we can calculate exact + // XDR sizes before encoding. + prepared := prepareReaddirEntries(allEntries, reqMask, s.fs, ss) + + // Enforce maxcount and dircount with exact XDR sizes. + // maxcount covers the entire READDIR4resok: cookieverf(8) + + // entries_present(4) + entry chain + eof(4). The entry chain + // ends with a terminal FALSE(4) (either entries_present=FALSE + // when empty, or the last entry's nextentry=FALSE). + // dircount limits directory-information bytes: cookie + name per entry. + maxBudget := int(maxCount) - readdirResokOverhead + dirBudget := int(dirCount) + if maxBudget < 0 { + ew := w.AppendResarray_Readdir() + ew.SetValue_Default(NFS4ERR_TOOSMALL) + w.Resume(ew.Finish()) + return NFS4ERR_TOOSMALL + } + n := 0 + predictedEntryBytes := 0 + predictedDirBytes := 0 + for i := range prepared { + eSize := readdirEntryXDRSize(&prepared[i]) + dSize := readdirEntryDirSize(&prepared[i]) + if predictedEntryBytes+eSize > maxBudget || predictedDirBytes+dSize > dirBudget { + if i == 0 { + ew := w.AppendResarray_Readdir() + ew.SetValue_Default(NFS4ERR_TOOSMALL) + w.Resume(ew.Finish()) + return NFS4ERR_TOOSMALL + } + eof = false + break + } + predictedEntryBytes += eSize + predictedDirBytes += dSize + n++ + } + prepared = prepared[:n] + + ew := w.AppendResarray_Readdir() + okW := ew.SetValue_Nfs4Ok() + + // Set cookieverf to directory mtime. + ni, _ := s.fs.Stat(st.currentID) + verf := okW.Cookieverf() + var mtimeBytes [8]byte + binary.BigEndian.PutUint64(mtimeBytes[:], uint64(ni.Mtime.UnixNano())) + for i := 0; i < 8; i++ { + verf.SetData(i, mtimeBytes[i]) + } + + dirW := okW.StartReply() + encodeDirEntries(&dirW, prepared, eof) + buf := dirW.Finish() + okW.Resume(buf) + buf = okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opReadlink(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Readlink() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + target, err := s.fs.Readlink(st.currentID) + if err != nil { + ew := w.AppendResarray_Readlink() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + ew := w.AppendResarray_Readlink() + okW := ew.SetValue_Nfs4Ok() + lw := okW.StartLink() + buf := lw.SetData([]byte(target)).Finish() + okW.Resume(buf) + buf = okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opRemove(args REMOVE4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Remove() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + if s.stagingStore.ReadOnly() { + ew := w.AppendResarray_Remove() + ew.SetValue_Default(NFS4ERR_ROFS) + w.Resume(ew.Finish()) + return NFS4ERR_ROFS + } + + name := string(args.Target().Data()) + err := s.fs.Remove(st.currentID, name) + if err != nil { + ew := w.AppendResarray_Remove() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + + ew := w.AppendResarray_Remove() + okW := ew.SetValue_Nfs4Ok() + cinfo := okW.Cinfo() + cinfo.SetAtomic(TRUE) + now := uint64(time.Now().UnixNano()) + cinfo.SetBefore(now - 1) + cinfo.SetAfter(now) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opRename(args RENAME4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + // RENAME uses savedFH as source directory and currentFH as target directory. + if !st.currentIDSet || !st.savedIDSet { + ew := w.AppendResarray_Rename() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + if s.stagingStore.ReadOnly() { + ew := w.AppendResarray_Rename() + ew.SetValue_Default(NFS4ERR_ROFS) + w.Resume(ew.Finish()) + return NFS4ERR_ROFS + } + + oldName := string(args.Oldname().Data()) + newName := string(args.Newname().Data()) + + err := s.fs.Rename(st.savedID, oldName, st.currentID, newName) + if err != nil { + ew := w.AppendResarray_Rename() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + + ew := w.AppendResarray_Rename() + okW := ew.SetValue_Nfs4Ok() + now := uint64(time.Now().UnixNano()) + srcInfo := okW.SourceCinfo() + srcInfo.SetAtomic(TRUE) + srcInfo.SetBefore(now - 1) + srcInfo.SetAfter(now) + tgtInfo := okW.TargetCinfo() + tgtInfo.SetAtomic(TRUE) + tgtInfo.SetBefore(now - 1) + tgtInfo.SetAfter(now) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opRenew(args RENEW4args, w *COMPOUND4resWriter) uint32 { + // No lease state — just accept the renewal. + _ = args.Clientid() + r := w.AppendResarray_Renew() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opSavefh(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + r := w.AppendResarray_Savefh() + r.SetStatus(NFS4ERR_NOFILEHANDLE) + return NFS4ERR_NOFILEHANDLE + } + st.savedID = st.currentID + st.savedIDSet = true + r := w.AppendResarray_Savefh() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opRestorefh(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.savedIDSet { + r := w.AppendResarray_Restorefh() + r.SetStatus(NFS4ERR_RESTOREFH) + return NFS4ERR_RESTOREFH + } + st.currentID = st.savedID + st.currentIDSet = true + r := w.AppendResarray_Restorefh() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opSecinfo(st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Secinfo() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + // Return AUTH_SYS (flavor 1) and AUTH_NONE (flavor 0). + ew := w.AppendResarray_Secinfo() + okW := ew.SetValue_Nfs4Ok() + okW.AppendData_Default(authSys) + okW.AppendData_Default(authNone) + buf := okW.Finish() + ew.Resume(buf) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opSetattr(args SETATTR4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + // setattrReply writes a SETATTR response with the given status and + // optional result bitmap. Factored out because the response always + // requires an attrsset bitmap, even on error. + setattrReply := func(status uint32, resultMask [2]uint32) uint32 { + saw := w.AppendResarray_Setattr() + saw.SetStatus(status) + bmW := saw.StartAttrsset() + if resultMask[0] != 0 || resultMask[1] != 0 { + bmW.AppendData(resultMask[0]) + bmW.AppendData(resultMask[1]) + } + buf := bmW.Finish() + saw.Resume(buf) + w.Resume(saw.Finish()) + return status + } + + if !st.currentIDSet { + return setattrReply(NFS4ERR_NOFILEHANDLE, [2]uint32{}) + } + + fa := args.ObjAttributes() + mask := parseBitmap(fa.Attrmask()) + + // Supported writable attrs. + const supportedSet0 = 1 << FATTR4_SIZE + const supportedSet1 = (1 << (FATTR4_TIME_ACCESS_SET - 32)) | + (1 << (FATTR4_TIME_MODIFY_SET - 32)) + + if mask[0] & ^uint32(supportedSet0) != 0 || mask[1] & ^uint32(supportedSet1) != 0 { + return setattrReply(NFS4ERR_ATTRNOTSUPP, [2]uint32{}) + } + + var resultMask [2]uint32 + + if mask[0]&(1< len(attrData) { + return nil, NFS4ERR_BADXDR + } + how := binary.BigEndian.Uint32(attrData[attrOff : attrOff+4]) + attrOff += 4 + if how == SET_TO_CLIENT_TIME4 { + if attrOff+12 > len(attrData) { + return nil, NFS4ERR_BADXDR + } + sec := int64(binary.BigEndian.Uint64(attrData[attrOff : attrOff+8])) + nsec := binary.BigEndian.Uint32(attrData[attrOff+8 : attrOff+12]) + attrOff += 12 + t := time.Unix(sec, int64(nsec)) + return &t, NFS4_OK + } + t := time.Now() + return &t, NFS4_OK + } + + var setAtime, setMtime *time.Time + if mask[1]&(1<<(FATTR4_TIME_ACCESS_SET-32)) != 0 { + t, status := parseTimeSet() + if status != NFS4_OK { + return setattrReply(status, [2]uint32{}) + } + setAtime = t + resultMask[1] |= 1 << (FATTR4_TIME_ACCESS_SET - 32) + } + if mask[1]&(1<<(FATTR4_TIME_MODIFY_SET-32)) != 0 { + t, status := parseTimeSet() + if status != NFS4_OK { + return setattrReply(status, [2]uint32{}) + } + setMtime = t + resultMask[1] |= 1 << (FATTR4_TIME_MODIFY_SET - 32) + } + + if setAtime != nil || setMtime != nil { + if err := s.fs.SetTime(st.currentID, setMtime, setAtime); err != nil { + return setattrReply(NFS4ERR_IO, [2]uint32{}) + } + } + + return setattrReply(NFS4_OK, resultMask) +} + +func (s *Server) opSetclientid(args SETCLIENTID4args, w *COMPOUND4resWriter) uint32 { + clientID := args.Client() + verifier := clientID.Verifier() + idData := clientID.Id() + + var verf [8]byte + for i := 0; i < 8; i++ { + verf[i] = verifier.Data(i) + } + + clid, err := s.clients.SetClientID(verf, idData) + if err != nil { + ew := w.AppendResarray_Setclientid() + ew.SetValue_Default(nfsErrCode(err)) + w.Resume(ew.Finish()) + return nfsErrCode(err) + } + + ew := w.AppendResarray_Setclientid() + ok := ew.SetValue_Nfs4Ok() + ok.SetClientid(clid) + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opSetclientidConfirm(args SETCLIENTIDCONFIRM4args, w *COMPOUND4resWriter) uint32 { + clid := args.Clientid() + _, err := s.clients.ConfirmClientID(clid) + if err != nil { + r := w.AppendResarray_SetclientidConfirm() + r.SetStatus(nfsErrCode(err)) + return nfsErrCode(err) + } + r := w.AppendResarray_SetclientidConfirm() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opVerify(args VERIFY4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + r := w.AppendResarray_Verify() + r.SetStatus(NFS4ERR_NOFILEHANDLE) + return NFS4ERR_NOFILEHANDLE + } + same, status := s.verifyAttrs(st.currentID, args.ObjAttributes()) + if status != NFS4_OK { + r := w.AppendResarray_Verify() + r.SetStatus(status) + return status + } + r := w.AppendResarray_Verify() + if !same { + // VERIFY: fail if attributes differ. + r.SetStatus(NFS4ERR_NOT_SAME) + return NFS4ERR_NOT_SAME + } + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +func (s *Server) opWrite(args WRITE4args, st *compoundState, w *COMPOUND4resWriter) uint32 { + if !st.currentIDSet { + ew := w.AppendResarray_Write() + ew.SetValue_Default(NFS4ERR_NOFILEHANDLE) + w.Resume(ew.Finish()) + return NFS4ERR_NOFILEHANDLE + } + + // Find the staging buffer for this file. + sf := s.stagingStore.Get(st.currentID) + if sf == nil { + // No staging buffer — not opened for write. + ew := w.AppendResarray_Write() + ew.SetValue_Default(NFS4ERR_OPENMODE) + w.Resume(ew.Finish()) + return NFS4ERR_OPENMODE + } + + offset := args.Offset() + data := args.Data() + + if err := sf.Write(offset, data); err != nil { + ew := w.AppendResarray_Write() + ew.SetValue_Default(NFS4ERR_IO) + w.Resume(ew.Finish()) + return NFS4ERR_IO + } + + ew := w.AppendResarray_Write() + okW := ew.SetValue_Nfs4Ok() + okW.SetCount(uint32(len(data))) + okW.SetCommitted(unstable4) + verf := okW.Writeverf() + for i := 0; i < 8; i++ { + verf.SetData(i, s.writeVerifier[i]) + } + w.Resume(ew.Finish()) + return NFS4_OK +} + +func (s *Server) opReleaseLockowner(w *COMPOUND4resWriter) uint32 { + // No lock state — always succeeds. + r := w.AppendResarray_ReleaseLockowner() + r.SetStatus(NFS4_OK) + return NFS4_OK +} + +// verifyAttrs compares the supplied fattr4 against the current file's attributes. +// Returns (same bool, status uint32). If status != NFS4_OK, comparison failed. +func (s *Server) verifyAttrs(id InodeID, supplied Fattr4) (bool, uint32) { + ni, err := s.fs.Stat(id) + if err != nil { + return false, s.errToNFS(err) + } + + mask := parseBitmap(supplied.Attrmask()) + + // Only compare attributes we support. + mask[0] &= supportedAttrs0 + mask[1] &= supportedAttrs1 + + // Encode what we would return for these attributes. + expected := encodeAttrs(mask, id, ni) + + // Get the supplied attribute values. + suppliedData := supplied.AttrVals().Data() + + return bytes.Equal(expected, suppliedData), NFS4_OK +} + +// extractStateID reads the 12-byte "other" field from a Stateid4 into a StateID. +func extractStateID(s Stateid4) StateID { + var sid StateID + for i := 0; i < 12; i++ { + sid[i] = s.Other(i) + } + return sid +} + +// writeStateID writes a StateID into a Stateid4's "other" field. +func writeStateID(s Stateid4, sid StateID) { + for i := 0; i < 12; i++ { + s.SetOther(i, sid[i]) + } +} + +// errToNFS converts a Go error to an NFS status code. +func (s *Server) errToNFS(err error) uint32 { + if e, ok := err.(nfsError); ok { + return uint32(e) + } + if os.IsNotExist(err) { + return NFS4ERR_NOENT + } + if os.IsPermission(err) { + return NFS4ERR_ACCESS + } + if os.IsExist(err) { + return NFS4ERR_EXIST + } + s.log.Warn("VFS error", "err", err) + return NFS4ERR_IO +} + +// Time helper. +func timeToNFS(t time.Time) (int64, uint32) { + return t.Unix(), uint32(t.Nanosecond()) +} diff --git a/go/nfsd/rpc.go b/go/nfsd/rpc.go new file mode 100644 index 00000000..74908ca7 --- /dev/null +++ b/go/nfsd/rpc.go @@ -0,0 +1,167 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "encoding/binary" + "fmt" + "io" + "net" +) + +// RPC constants. +const ( + rpcCall = 0 + rpcReply = 1 + + rpcVersion = 2 + + nfsProg = 100003 + nfsVersion = 4 + + // RPC procedures. + procNull = 0 + procCompound = 1 + + // Auth flavors. + authNone = 0 + authSys = 1 + + // Reply status. + msgAccepted = 0 + msgDenied = 1 + + // Accept status. + acceptSuccess = 0 + acceptProgUnavail = 1 + acceptProgMismatch = 2 + acceptProcUnavail = 3 + acceptGarbageArgs = 4 +) + +// readFrame reads one RPC record-marking frame from a TCP connection. +// Reassembles multi-fragment messages (bit 31 = last fragment flag). +func readFrame(r io.Reader) ([]byte, error) { + var buf []byte + for { + var hdr [4]byte + if _, err := io.ReadFull(r, hdr[:]); err != nil { + return nil, err + } + fragLen := binary.BigEndian.Uint32(hdr[:]) + last := fragLen&0x80000000 != 0 + fragLen &= 0x7FFFFFFF + + if fragLen > 1<<20 { + return nil, fmt.Errorf("fragment too large: %d bytes", fragLen) + } + + frag := make([]byte, fragLen) + if _, err := io.ReadFull(r, frag); err != nil { + return nil, err + } + buf = append(buf, frag...) + if last { + return buf, nil + } + } +} + +// writeFrame writes an RPC record-marking frame (single fragment, last=true). +func writeFrame(w net.Conn, data []byte) error { + var hdr [4]byte + binary.BigEndian.PutUint32(hdr[:], uint32(len(data))|0x80000000) + if _, err := w.Write(hdr[:]); err != nil { + return err + } + _, err := w.Write(data) + return err +} + +// rpcRequest holds the parsed ONC RPC call header. +type rpcRequest struct { + xid uint32 + prog uint32 + vers uint32 + proc uint32 + credFlavor uint32 + credBody []byte + verfFlavor uint32 + verfBody []byte + body []byte // remaining bytes after header +} + +// parseRPCCall parses an ONC RPC CALL message. +func parseRPCCall(data []byte) (*rpcRequest, error) { + if len(data) < 40 { + return nil, fmt.Errorf("RPC message too short: %d bytes", len(data)) + } + xid := binary.BigEndian.Uint32(data[0:4]) + msgType := binary.BigEndian.Uint32(data[4:8]) + if msgType != rpcCall { + return nil, fmt.Errorf("expected RPC CALL (0), got %d", msgType) + } + rpcVers := binary.BigEndian.Uint32(data[8:12]) + if rpcVers != rpcVersion { + return nil, fmt.Errorf("expected RPC version 2, got %d", rpcVers) + } + prog := binary.BigEndian.Uint32(data[12:16]) + vers := binary.BigEndian.Uint32(data[16:20]) + proc := binary.BigEndian.Uint32(data[20:24]) + + off := 24 + + // Parse credential. + if off+8 > len(data) { + return nil, fmt.Errorf("truncated credential") + } + credFlavor := binary.BigEndian.Uint32(data[off : off+4]) + credLen := binary.BigEndian.Uint32(data[off+4 : off+8]) + off += 8 + if off+int(credLen) > len(data) { + return nil, fmt.Errorf("truncated credential body") + } + credBody := data[off : off+int(credLen)] + off += int(credLen) + + // Parse verifier. + if off+8 > len(data) { + return nil, fmt.Errorf("truncated verifier") + } + verfFlavor := binary.BigEndian.Uint32(data[off : off+4]) + verfLen := binary.BigEndian.Uint32(data[off+4 : off+8]) + off += 8 + if off+int(verfLen) > len(data) { + return nil, fmt.Errorf("truncated verifier body") + } + verfBody := data[off : off+int(verfLen)] + off += int(verfLen) + + return &rpcRequest{ + xid: xid, + prog: prog, + vers: vers, + proc: proc, + credFlavor: credFlavor, + credBody: credBody, + verfFlavor: verfFlavor, + verfBody: verfBody, + body: data[off:], + }, nil +} + +// buildRPCReply builds an RPC reply header for an accepted, successful call. +// Returns a buffer that the caller appends procedure-specific data to. +func buildRPCReply(xid uint32, acceptStat uint32) []byte { + buf := make([]byte, 0, 128) + buf = binary.BigEndian.AppendUint32(buf, xid) + buf = binary.BigEndian.AppendUint32(buf, rpcReply) + buf = binary.BigEndian.AppendUint32(buf, msgAccepted) + // Verifier: AUTH_NONE, length 0. + buf = binary.BigEndian.AppendUint32(buf, authNone) + buf = binary.BigEndian.AppendUint32(buf, 0) + buf = binary.BigEndian.AppendUint32(buf, acceptStat) + return buf +} diff --git a/go/nfsd/server.go b/go/nfsd/server.go new file mode 100644 index 00000000..36513b00 --- /dev/null +++ b/go/nfsd/server.go @@ -0,0 +1,277 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "crypto/rand" + "encoding/binary" + "log/slog" + "net" + "time" +) + +// peekCompoundHeader extracts the tag and minor version from COMPOUND4args +// without fully parsing the argarray. The wire format is: tag (XDR opaque: +// u32 len + data padded to 4 bytes), then u32 minorversion. +func peekCompoundHeader(body []byte) (tag []byte, minor uint32, ok bool) { + if len(body) < 4 { + return nil, 0, false + } + tagLen := binary.BigEndian.Uint32(body[0:4]) + padded := (tagLen + 3) &^ 3 // XDR pad to 4-byte boundary + off := 4 + padded + if uint64(off)+4 > uint64(len(body)) { + return nil, 0, false + } + return body[4 : 4+tagLen], binary.BigEndian.Uint32(body[off : off+4]), true +} + +// Server is the NFSv4 server. +type Server struct { + fs TernVFS + clients *ClientStore + stagingStore StagingStore + writeVerifier [8]byte // random per server instance, changes on restart + idleTimeout time.Duration // connection idle timeout + log *slog.Logger +} + +func NewServer(fs TernVFS, stagingStore StagingStore, logger *slog.Logger) (*Server, error) { + clients, err := NewClientStore(fs) + if err != nil { + return nil, err + } + var verf [8]byte + rand.Read(verf[:]) + if logger == nil { + logger = slog.Default() + } + s := &Server{ + fs: fs, + clients: clients, + stagingStore: stagingStore, + writeVerifier: verf, + idleTimeout: 5 * time.Minute, + log: logger, + } + return s, nil +} + +func (s *Server) ListenAndServe(addr string) error { + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + defer ln.Close() + for { + conn, err := ln.Accept() + if err != nil { + s.log.Error("accept failed", "err", err) + continue + } + go s.handleConn(conn) + } +} + +func (s *Server) handleConn(conn net.Conn) { + defer conn.Close() + remote := conn.RemoteAddr().String() + s.log.Info("client connected", "remote", remote) + for { + if s.idleTimeout > 0 { + conn.SetDeadline(time.Now().Add(s.idleTimeout)) + } + frame, err := readFrame(conn) + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + s.log.Info("client idle timeout", "remote", remote) + } else { + s.log.Info("client disconnected", "remote", remote, "err", err) + } + return + } + req, err := parseRPCCall(frame) + if err != nil { + s.log.Warn("RPC parse error", "remote", remote, "err", err) + continue + } + s.log.Debug("RPC call", "remote", remote, "xid", req.xid, + "prog", req.prog, "vers", req.vers, "proc", req.proc) + var reply []byte + if req.prog != nfsProg || req.vers != nfsVersion { + s.log.Debug("prog/vers mismatch", "prog", req.prog, "vers", req.vers) + reply = buildRPCReply(req.xid, acceptProgMismatch) + reply = binary.BigEndian.AppendUint32(reply, nfsVersion) + reply = binary.BigEndian.AppendUint32(reply, nfsVersion) + } else { + switch req.proc { + case procNull: + s.log.Debug("NULL") + reply = buildRPCReply(req.xid, acceptSuccess) + case procCompound: + reply = s.handleCompound(req) + default: + s.log.Debug("unknown proc", "proc", req.proc) + reply = buildRPCReply(req.xid, acceptProcUnavail) + } + } + if err := writeFrame(conn, reply); err != nil { + return + } + } +} + +// compoundState tracks the current/saved filehandle during COMPOUND execution. +type compoundState struct { + currentID InodeID + currentIDSet bool + savedID InodeID + savedIDSet bool +} + +func (s *Server) handleCompound(req *rpcRequest) []byte { + // Pre-check minor version before full parse, since ReadCOMPOUND4args + // eagerly walks the argarray and will fail on unknown v4.1+ opcodes. + if tag, minor, ok := peekCompoundHeader(req.body); ok && minor != 0 { + s.log.Debug("minor version not supported", "minor", minor) + reply := buildRPCReply(req.xid, acceptSuccess) + // Build COMPOUND4res: status + echoed tag + 0 ops. + reply = binary.BigEndian.AppendUint32(reply, NFS4ERR_MINOR_VERS_MISMATCH) + // Echo tag as XDR opaque (u32 len + data + padding). + reply = binary.BigEndian.AppendUint32(reply, uint32(len(tag))) + reply = append(reply, tag...) + if pad := (4 - len(tag)%4) % 4; pad > 0 { + reply = append(reply, make([]byte, pad)...) + } + reply = binary.BigEndian.AppendUint32(reply, 0) // resarray count + return reply + } + + args, ok := ReadCOMPOUND4args(req.body) + if !ok { + s.log.Debug("COMPOUND: garbage args") + reply := buildRPCReply(req.xid, acceptGarbageArgs) + return reply + } + + reply := buildRPCReply(req.xid, acceptSuccess) + + w := StartCOMPOUND4res(reply) + w.SetStatus(NFS4_OK) + + // Echo the tag. + tagW := w.StartTag() + tagData := args.Tag().Data() + reply = tagW.SetData(tagData).Finish() + w.Resume(reply) + + st := &compoundState{} + overallStatus := NFS4_OK + opCount := 0 + + iter := args.Argarray() + for iter.Next() { + entry := iter.Argarray() + opnum := entry.Disc() + op := entry.Value() + + s.log.Debug("op", "opnum", NfsOpnum4Name(opnum)) + + var opStatus uint32 + switch opnum { + case OP_ACCESS: + opStatus = s.opAccess(op.AsACCESS4args(), st, &w) + case OP_CLOSE: + opStatus = s.opClose(op.AsCLOSE4args(), st, &w) + case OP_COMMIT: + opStatus = s.opCommit(st, &w) + case OP_CREATE: + opStatus = s.opCreate(op.AsCREATE4args(), st, &w) + case OP_DELEGPURGE: + opStatus = s.opDelegpurge(&w) + case OP_DELEGRETURN: + opStatus = s.opDelegreturn(st, &w) + case OP_GETATTR: + opStatus = s.opGetattr(op.AsGETATTR4args(), st, &w) + case OP_GETFH: + opStatus = s.opGetfh(st, &w) + case OP_LINK: + opStatus = s.opLink(st, &w) + case OP_LOCK: + opStatus = s.opLock(&w) + case OP_LOCKT: + opStatus = s.opLockt(&w) + case OP_LOCKU: + opStatus = s.opLocku(&w) + case OP_LOOKUP: + opStatus = s.opLookup(op.AsLOOKUP4args(), st, &w) + case OP_LOOKUPP: + opStatus = s.opLookupp(st, &w) + case OP_NVERIFY: + opStatus = s.opNverify(op.AsNVERIFY4args(), st, &w) + case OP_OPEN: + opStatus = s.opOpen(op.AsOPEN4args(), st, &w) + case OP_OPEN_CONFIRM: + opStatus = s.opOpenConfirm(op.AsOPENCONFIRM4args(), st, &w) + case OP_OPEN_DOWNGRADE: + opStatus = s.opOpenDowngrade(st, &w) + case OP_OPENATTR: + opStatus = s.opOpenattr(&w) + case OP_PUTFH: + opStatus = s.opPutfh(op.AsPUTFH4args(), st, &w) + case OP_PUTPUBFH: + opStatus = s.opPutrootfh(st, &w, true) + case OP_PUTROOTFH: + opStatus = s.opPutrootfh(st, &w, false) + case OP_READ: + opStatus = s.opRead(op.AsREAD4args(), st, &w) + case OP_READDIR: + opStatus = s.opReaddir(op.AsREADDIR4args(), st, &w) + case OP_READLINK: + opStatus = s.opReadlink(st, &w) + case OP_REMOVE: + opStatus = s.opRemove(op.AsREMOVE4args(), st, &w) + case OP_RENAME: + opStatus = s.opRename(op.AsRENAME4args(), st, &w) + case OP_RENEW: + opStatus = s.opRenew(op.AsRENEW4args(), &w) + case OP_RESTOREFH: + opStatus = s.opRestorefh(st, &w) + case OP_SAVEFH: + opStatus = s.opSavefh(st, &w) + case OP_SECINFO: + opStatus = s.opSecinfo(st, &w) + case OP_SETATTR: + opStatus = s.opSetattr(op.AsSETATTR4args(), st, &w) + case OP_SETCLIENTID: + opStatus = s.opSetclientid(op.AsSETCLIENTID4args(), &w) + case OP_SETCLIENTID_CONFIRM: + opStatus = s.opSetclientidConfirm(op.AsSETCLIENTIDCONFIRM4args(), &w) + case OP_VERIFY: + opStatus = s.opVerify(op.AsVERIFY4args(), st, &w) + case OP_WRITE: + opStatus = s.opWrite(op.AsWRITE4args(), st, &w) + case OP_RELEASE_LOCKOWNER: + opStatus = s.opReleaseLockowner(&w) + default: + ew := w.AppendResarray_Illegal() + ew.SetStatus(NFS4ERR_OP_ILLEGAL) + opStatus = NFS4ERR_OP_ILLEGAL + } + + opCount++ + + if opStatus != NFS4_OK { + s.log.Debug("op failed", "op", NfsOpnum4Name(opnum), "status", Nfsstat4Name(opStatus)) + overallStatus = opStatus + break + } + } + + s.log.Debug("COMPOUND done", "status", Nfsstat4Name(overallStatus), "ops", opCount) + + w.SetStatus(overallStatus) + return w.Finish() +} diff --git a/go/nfsd/staging.go b/go/nfsd/staging.go new file mode 100644 index 00000000..97f5f01c --- /dev/null +++ b/go/nfsd/staging.go @@ -0,0 +1,329 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "io" + "log/slog" + "os" + "path/filepath" + "strconv" + "strings" + "sync" +) + +// StagingMeta holds metadata for an in-progress write, persisted in a +// sidecar file alongside the staging data. This is the only persistent +// record of an NFS write-open; the NFS server derives open stateids from +// the NFSStateID stored here rather than keeping separate open state. +type StagingMeta struct { + DirID InodeID // directory to link the file into on CLOSE + FileName string // name in directory + TernCookie Cookie // cookie from VFS ConstructFile + NFSStateID StateID // random, returned to NFS client as stateid "other" +} + +// StagingFile is the interface for staged file data during NFS writes. +// Files are immutable after creation, so staging only applies to new files. +// The store owns the file lifecycle — there is no Close method. +type StagingFile interface { + SetSize(size uint64) error + Write(offset uint64, data []byte) error + Read(offset uint64, dest []byte) (n int, eof bool, err error) + Sync() error // persist to stable storage + Reader() (io.ReadSeeker, error) // returns a reader positioned at offset 0 +} + +// StagingStore manages staging files keyed by InodeID. +// All staging queries go through this interface — the NFS server +// does not maintain its own staging caches. +type StagingStore interface { + // ReadOnly returns true if no staging directory is configured. + ReadOnly() bool + // Create creates a new staging file with associated metadata. + Create(id InodeID, meta StagingMeta) (StagingFile, error) + // Get returns the staging file for the given inode, or nil. + Get(id InodeID) StagingFile + // GetMeta returns the metadata sidecar for the given inode. + GetMeta(id InodeID) (StagingMeta, bool) + // Remove closes and removes the staging file and sidecar for the given inode. + Remove(id InodeID) + // StagedSize returns the staged size for the given inode. + // Returns (0, false) if the inode has no staging file. + StagedSize(id InodeID) (uint64, bool) + // StagedSizes returns a snapshot of all staged InodeID → size. + StagedSizes() map[InodeID]uint64 +} + +// LocalStagingStore manages staging files as local files in a directory. +// File names encode the InodeID so state can be recovered on restart. +type LocalStagingStore struct { + mu sync.Mutex + dir string + files map[InodeID]*localStagingEntry + log *slog.Logger +} + +type localStagingEntry struct { + file *localStagingFile + meta StagingMeta +} + +func NewLocalStagingStore(dir string, logger *slog.Logger) (*LocalStagingStore, error) { + if err := os.MkdirAll(dir, 0700); err != nil { + return nil, err + } + if logger == nil { + logger = slog.Default() + } + s := &LocalStagingStore{ + dir: dir, + files: make(map[InodeID]*localStagingEntry), + log: logger, + } + // Scan the staging directory and re-register any staging files + // left from a previous run. + entries, err := os.ReadDir(dir) + if err != nil { + return s, nil + } + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasSuffix(name, ".staging") { + continue + } + idStr := strings.TrimSuffix(name, ".staging") + idVal, err := strconv.ParseUint(idStr, 16, 64) + if err != nil { + continue + } + id := InodeID(idVal) + path := filepath.Join(dir, name) + f, err := os.OpenFile(path, os.O_RDWR, 0644) + if err != nil { + s.log.Warn("staging recover: cannot open file", "path", path, "err", err) + continue + } + info, err := f.Stat() + if err != nil { + f.Close() + continue + } + entry := &localStagingEntry{ + file: &localStagingFile{f: f, size: uint64(info.Size())}, + } + // Try to load sidecar metadata. + metaPath := filepath.Join(dir, fmt.Sprintf("%016x.meta", uint64(id))) + if meta, err := loadStagingMeta(metaPath); err == nil { + entry.meta = meta + s.log.Info("staging recover", "file", name, "inode", fmt.Sprintf("%016x", uint64(id)), "size", info.Size()) + } else { + s.log.Info("staging recover (no meta)", "file", name, "inode", fmt.Sprintf("%016x", uint64(id)), "size", info.Size()) + } + s.files[id] = entry + } + return s, nil +} + +func (s *LocalStagingStore) ReadOnly() bool { return false } + +func (s *LocalStagingStore) Create(id InodeID, meta StagingMeta) (StagingFile, error) { + s.mu.Lock() + defer s.mu.Unlock() + if entry, ok := s.files[id]; ok { + return entry.file, nil // already exists (recovered or duplicate create) + } + path := filepath.Join(s.dir, fmt.Sprintf("%016x.staging", uint64(id))) + f, err := os.Create(path) + if err != nil { + return nil, err + } + // Write sidecar metadata. + metaPath := filepath.Join(s.dir, fmt.Sprintf("%016x.meta", uint64(id))) + if err := saveStagingMeta(metaPath, meta); err != nil { + f.Close() + os.Remove(path) + return nil, err + } + sf := &localStagingFile{f: f} + s.files[id] = &localStagingEntry{file: sf, meta: meta} + return sf, nil +} + +func (s *LocalStagingStore) Get(id InodeID) StagingFile { + s.mu.Lock() + defer s.mu.Unlock() + entry := s.files[id] + if entry == nil { + return nil + } + return entry.file +} + +func (s *LocalStagingStore) GetMeta(id InodeID) (StagingMeta, bool) { + s.mu.Lock() + defer s.mu.Unlock() + entry, ok := s.files[id] + if !ok { + return StagingMeta{}, false + } + return entry.meta, true +} + +func (s *LocalStagingStore) Remove(id InodeID) { + s.mu.Lock() + entry := s.files[id] + delete(s.files, id) + s.mu.Unlock() + if entry != nil { + name := entry.file.f.Name() + entry.file.f.Close() + os.Remove(name) + metaPath := strings.TrimSuffix(name, ".staging") + ".meta" + os.Remove(metaPath) + } +} + +func (s *LocalStagingStore) StagedSize(id InodeID) (uint64, bool) { + s.mu.Lock() + defer s.mu.Unlock() + entry, ok := s.files[id] + if !ok { + return 0, false + } + return entry.file.size, true +} + +func (s *LocalStagingStore) StagedSizes() map[InodeID]uint64 { + s.mu.Lock() + defer s.mu.Unlock() + if len(s.files) == 0 { + return nil + } + m := make(map[InodeID]uint64, len(s.files)) + for id, entry := range s.files { + m[id] = entry.file.size + } + return m +} + +// readOnlyStagingStore is used when no staging directory is configured. +type readOnlyStagingStore struct{} + +func (readOnlyStagingStore) ReadOnly() bool { return true } +func (readOnlyStagingStore) Create(InodeID, StagingMeta) (StagingFile, error) { + return nil, os.ErrPermission +} +func (readOnlyStagingStore) Get(InodeID) StagingFile { return nil } +func (readOnlyStagingStore) GetMeta(InodeID) (StagingMeta, bool) { return StagingMeta{}, false } +func (readOnlyStagingStore) Remove(InodeID) {} +func (readOnlyStagingStore) StagedSize(InodeID) (uint64, bool) { return 0, false } +func (readOnlyStagingStore) StagedSizes() map[InodeID]uint64 { return nil } + +// localStagingFile is a disk-backed staging file for new file creation. +type localStagingFile struct { + f *os.File + size uint64 +} + +func (sf *localStagingFile) SetSize(size uint64) error { + if err := sf.f.Truncate(int64(size)); err != nil { + return err + } + sf.size = size + return nil +} + +func (sf *localStagingFile) Write(offset uint64, data []byte) error { + end := offset + uint64(len(data)) + if end > sf.size { + if err := sf.f.Truncate(int64(end)); err != nil { + return err + } + sf.size = end + } + if _, err := sf.f.WriteAt(data, int64(offset)); err != nil { + return err + } + return nil +} + +func (sf *localStagingFile) Read(offset uint64, dest []byte) (int, bool, error) { + if offset >= sf.size { + return 0, true, nil + } + avail := sf.size - offset + if uint64(len(dest)) > avail { + dest = dest[:avail] + } + n, err := sf.f.ReadAt(dest, int64(offset)) + if err != nil && n == 0 { + return 0, false, err + } + eof := offset+uint64(n) >= sf.size + return n, eof, nil +} + +func (sf *localStagingFile) Sync() error { + return sf.f.Sync() +} + +func (sf *localStagingFile) Reader() (io.ReadSeeker, error) { + if _, err := sf.f.Seek(0, io.SeekStart); err != nil { + return nil, err + } + return sf.f, nil +} + +// Sidecar file format (binary, big-endian): +// [8] DirID +// [8] TernCookie +// [12] NFSStateID +// [2] FileNameLen +// [N] FileName (UTF-8) + +func saveStagingMeta(path string, meta StagingMeta) error { + nameBytes := []byte(meta.FileName) + buf := make([]byte, 8+8+12+2+len(nameBytes)) + binary.BigEndian.PutUint64(buf[0:8], uint64(meta.DirID)) + copy(buf[8:16], meta.TernCookie[:]) + copy(buf[16:28], meta.NFSStateID[:]) + binary.BigEndian.PutUint16(buf[28:30], uint16(len(nameBytes))) + copy(buf[30:], nameBytes) + return os.WriteFile(path, buf, 0600) +} + +func loadStagingMeta(path string) (StagingMeta, error) { + data, err := os.ReadFile(path) + if err != nil { + return StagingMeta{}, err + } + if len(data) < 30 { + return StagingMeta{}, fmt.Errorf("meta file too short") + } + var meta StagingMeta + meta.DirID = InodeID(binary.BigEndian.Uint64(data[0:8])) + copy(meta.TernCookie[:], data[8:16]) + copy(meta.NFSStateID[:], data[16:28]) + nameLen := binary.BigEndian.Uint16(data[28:30]) + if len(data) < 30+int(nameLen) { + return StagingMeta{}, fmt.Errorf("meta file truncated") + } + meta.FileName = string(data[30 : 30+nameLen]) + return meta, nil +} + +// newNFSStateID generates a random 12-byte NFS state ID for write opens. +func newNFSStateID() StateID { + var sid StateID + rand.Read(sid[:]) + return sid +} diff --git a/go/nfsd/state.go b/go/nfsd/state.go new file mode 100644 index 00000000..b676e5a8 --- /dev/null +++ b/go/nfsd/state.go @@ -0,0 +1,22 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +// StateID is the 12-byte "other" field of an NFSv4 stateid4. +type StateID [12]byte + +// nfsError is a simple error type that carries an NFS status code. +type nfsError uint32 + +func (e nfsError) Error() string { + return "nfs error" +} + +func nfsErrCode(err error) uint32 { + if e, ok := err.(nfsError); ok { + return uint32(e) + } + return NFS4ERR_SERVERFAULT +} diff --git a/go/nfsd/vfs.go b/go/nfsd/vfs.go new file mode 100644 index 00000000..c7437cd4 --- /dev/null +++ b/go/nfsd/vfs.go @@ -0,0 +1,581 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "sync" + "syscall" + "time" +) + +// InodeID is a 64-bit inode identifier. Bits 62-61 encode the inode type +// (matching TernFS InodeId layout). +type InodeID uint64 + +const ( + InodeTypeDir = 1 + InodeTypeFile = 2 + InodeTypeSymlink = 3 +) + +func (id InodeID) Type() uint64 { return (uint64(id) >> 61) & 0x3 } +func (id InodeID) Fileid() uint64 { return uint64(id) & ((1 << 61) - 1) } + +func MakeInodeID(typ uint64, ino uint64) InodeID { + return InodeID((typ << 61) | (ino & ((1 << 61) - 1))) +} + +// Cookie is an opaque 8-byte token returned by ConstructFile. It must be +// passed to all subsequent operations on a transient file (matching TernFS +// semantics where the cookie prevents other clients from interfering with +// an in-progress write). +type Cookie [8]byte + +// NodeInfo holds metadata returned by Stat. +type NodeInfo struct { + Size uint64 // 0 for directories + Mtime time.Time // modification time + Atime time.Time // access time (same as Mtime for directories in TernFS) +} + +// DirEntry is one entry from a directory listing. +type DirEntry struct { + Name string + ID InodeID + NameHash uint64 +} + +// TernVFS is the filesystem abstraction layer. This matches the operations +// available from TernFS closely enough for a real implementation later. +type TernVFS interface { + RootID() InodeID + + // Stat returns metadata for a file, directory, or symlink. + Stat(id InodeID) (NodeInfo, error) + + // Lookup finds a child by name in a directory. + Lookup(dirID InodeID, name string) (InodeID, error) + + // LookupParent returns the parent directory of the given inode. + LookupParent(id InodeID) (InodeID, error) + + // Readdir lists directory entries starting at the given name hash. + // Returns entries and the NextHash continuation cursor (0 = EOF). + // Matches TernFS ReadDirReq semantics. + Readdir(dirID InodeID, startHash uint64) ([]DirEntry, uint64, error) + + // Read reads file data into dest, returning bytes read and EOF flag. + Read(fileID InodeID, offset uint64, dest []byte) (n int, eof bool, err error) + + // Readlink reads the target of a symlink. + Readlink(fileID InodeID) (string, error) + + // Mkdir creates a directory. Returns the new directory's InodeID. + Mkdir(dirID InodeID, name string) (InodeID, error) + + // Symlink creates a symlink. Returns the new symlink's InodeID. + Symlink(dirID InodeID, name string, target string) (InodeID, error) + + // ConstructFile creates a transient file (not yet visible in any + // directory). Returns the file's InodeID and a Cookie that must be + // provided for subsequent operations on the file. Matches TernFS + // ConstructFileReq semantics. + ConstructFile() (InodeID, Cookie, error) + + // LinkFile links a transient file into a directory, making it visible. + // The correct cookie (from ConstructFile) must be provided. data is the + // file content; in real TernFS the data was already written via AddSpan, + // but LocalTernVFS writes it at link time. + LinkFile(fileID InodeID, cookie Cookie, dirID InodeID, name string, data io.Reader) error + + // CreateFile creates a regular file with the given data in a single step. + // Used for internal bookkeeping files (e.g. client ID files), not for + // NFS file creation (which uses ConstructFile + LinkFile). + CreateFile(dirID InodeID, name string, data io.Reader) (InodeID, error) + + // Remove removes a file, directory, or symlink by name from a directory. + Remove(dirID InodeID, name string) error + + // Rename moves/renames a directory entry. + Rename(srcDirID InodeID, srcName string, dstDirID InodeID, dstName string) error + + // SetTime sets the mtime and/or atime of a file or directory. + // A nil pointer means "don't change this field." + SetTime(id InodeID, mtime *time.Time, atime *time.Time) error +} + +// LocalTernVFS implements TernVFS backed by a local directory for testing. +type LocalTernVFS struct { + root string + rootID InodeID + + mu sync.RWMutex + byID map[InodeID]string // id -> path relative to root + byPath map[string]InodeID // path -> id + parent map[InodeID]InodeID + transient map[InodeID]transientFile // transient files not yet linked +} + +type transientFile struct { + cookie Cookie + path string // temp file path on disk +} + +func NewLocalTernVFS(root string) *LocalTernVFS { + lfs := &LocalTernVFS{ + root: root, + byID: make(map[InodeID]string), + byPath: make(map[string]InodeID), + parent: make(map[InodeID]InodeID), + transient: make(map[InodeID]transientFile), + } + rootID := lfs.statInodeID(root) + lfs.rootID = rootID + lfs.byID[rootID] = "" + lfs.byPath[""] = rootID + lfs.parent[rootID] = rootID // root is its own parent + // Walk the directory tree to populate maps so that file handles from a + // previous server instance remain valid after restart. + filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil || path == root { + return nil + } + rel, _ := filepath.Rel(root, path) + id := lfs.statInodeID(path) + if id == 0 { + return nil + } + parentRel := filepath.Dir(rel) + if parentRel == "." { + parentRel = "" + } + lfs.byID[id] = rel + lfs.byPath[rel] = id + lfs.parent[id] = lfs.byPath[parentRel] + return nil + }) + return lfs +} + +func (lfs *LocalTernVFS) RootID() InodeID { return lfs.rootID } + +func (lfs *LocalTernVFS) statInodeID(path string) InodeID { + var st syscall.Stat_t + if err := syscall.Lstat(path, &st); err != nil { + return 0 + } + var typ uint64 + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFDIR: + typ = InodeTypeDir + case syscall.S_IFLNK: + typ = InodeTypeSymlink + default: + typ = InodeTypeFile + } + return MakeInodeID(typ, st.Ino) +} + +func (lfs *LocalTernVFS) resolve(id InodeID) (string, bool) { + lfs.mu.RLock() + rel, ok := lfs.byID[id] + lfs.mu.RUnlock() + if !ok { + return "", false + } + return filepath.Join(lfs.root, rel), true +} + +func (lfs *LocalTernVFS) register(absPath string, parentID InodeID) InodeID { + id := lfs.statInodeID(absPath) + if id == 0 { + return 0 + } + rel, _ := filepath.Rel(lfs.root, absPath) + if rel == "." { + rel = "" + } + lfs.mu.Lock() + lfs.byID[id] = rel + lfs.byPath[rel] = id + lfs.parent[id] = parentID + lfs.mu.Unlock() + return id +} + +func (lfs *LocalTernVFS) Stat(id InodeID) (NodeInfo, error) { + path, ok := lfs.resolve(id) + if !ok { + return NodeInfo{}, os.ErrNotExist + } + var st syscall.Stat_t + if err := syscall.Lstat(path, &st); err != nil { + return NodeInfo{}, err + } + return NodeInfo{ + Size: uint64(st.Size), + Mtime: time.Unix(st.Mtim.Sec, st.Mtim.Nsec), + Atime: time.Unix(st.Atim.Sec, st.Atim.Nsec), + }, nil +} + +func (lfs *LocalTernVFS) Lookup(dirID InodeID, name string) (InodeID, error) { + dirPath, ok := lfs.resolve(dirID) + if !ok { + return 0, os.ErrNotExist + } + childPath := filepath.Join(dirPath, name) + abs, err := filepath.Abs(childPath) + if err != nil { + return 0, err + } + rel, err := filepath.Rel(lfs.root, abs) + if err != nil || len(rel) >= 2 && rel[:2] == ".." { + return 0, os.ErrPermission + } + if _, err := os.Lstat(childPath); err != nil { + return 0, err + } + return lfs.register(childPath, dirID), nil +} + +func (lfs *LocalTernVFS) LookupParent(id InodeID) (InodeID, error) { + lfs.mu.RLock() + pid, ok := lfs.parent[id] + lfs.mu.RUnlock() + if !ok { + return 0, os.ErrNotExist + } + return pid, nil +} + +func (lfs *LocalTernVFS) Readdir(dirID InodeID, startHash uint64) ([]DirEntry, uint64, error) { + dirPath, ok := lfs.resolve(dirID) + if !ok { + return nil, 0, os.ErrNotExist + } + entries, err := os.ReadDir(dirPath) + if err != nil { + return nil, 0, err + } + + // Simulate TernFS hash-based ordering: use simple hash of name. + // In real TernFS, NameHash is computed by the storage layer. + type hashedEntry struct { + name string + hash uint64 + path string + } + var hashed []hashedEntry + for _, e := range entries { + h := hashName(e.Name()) + if h >= startHash { + hashed = append(hashed, hashedEntry{ + name: e.Name(), + hash: h, + path: filepath.Join(dirPath, e.Name()), + }) + } + } + + // Sort by hash to match TernFS ordering. + sort.Slice(hashed, func(i, j int) bool { + if hashed[i].hash != hashed[j].hash { + return hashed[i].hash < hashed[j].hash + } + return hashed[i].name < hashed[j].name + }) + + // Return a batch (simulate TernFS MTU limit). + const batchSize = 16 + var result []DirEntry + for i, he := range hashed { + childID := lfs.register(he.path, dirID) + if childID == 0 { + continue + } + result = append(result, DirEntry{ + Name: he.name, + ID: childID, + NameHash: he.hash, + }) + if len(result) >= batchSize { + // Return NextHash from the next unprocessed entry. + if i+1 < len(hashed) { + return result, hashed[i+1].hash, nil + } + return result, 0, nil + } + } + return result, 0, nil // EOF +} + +// hashName produces a simple hash for local testing. +// Real TernFS uses its own hash function. +func hashName(name string) uint64 { + // FNV-1a hash, offset to avoid reserved values 0-2. + var h uint64 = 14695981039346656037 + for _, c := range []byte(name) { + h ^= uint64(c) + h *= 1099511628211 + } + // Ensure hash is >= 3 to avoid NFS reserved cookie values. + if h < 3 { + h += 3 + } + return h +} + +func (lfs *LocalTernVFS) Read(fileID InodeID, offset uint64, dest []byte) (int, bool, error) { + path, ok := lfs.resolve(fileID) + if !ok { + return 0, false, os.ErrNotExist + } + f, err := os.Open(path) + if err != nil { + return 0, false, err + } + defer f.Close() + + n, err := f.ReadAt(dest, int64(offset)) + eof := false + if err != nil { + if err.Error() == "EOF" || n < len(dest) { + eof = true + } else { + return 0, false, err + } + } + return n, eof, nil +} + +func (lfs *LocalTernVFS) Readlink(fileID InodeID) (string, error) { + path, ok := lfs.resolve(fileID) + if !ok { + return "", os.ErrNotExist + } + return os.Readlink(path) +} + +func (lfs *LocalTernVFS) Mkdir(dirID InodeID, name string) (InodeID, error) { + dirPath, ok := lfs.resolve(dirID) + if !ok { + return 0, os.ErrNotExist + } + childPath := filepath.Join(dirPath, name) + if err := os.Mkdir(childPath, 0755); err != nil { + return 0, err + } + return lfs.register(childPath, dirID), nil +} + +func (lfs *LocalTernVFS) Symlink(dirID InodeID, name string, target string) (InodeID, error) { + dirPath, ok := lfs.resolve(dirID) + if !ok { + return 0, os.ErrNotExist + } + childPath := filepath.Join(dirPath, name) + if err := os.Symlink(target, childPath); err != nil { + return 0, err + } + return lfs.register(childPath, dirID), nil +} + +func (lfs *LocalTernVFS) ConstructFile() (InodeID, Cookie, error) { + // Create a temp file to simulate a transient TernFS file. + f, err := os.CreateTemp(lfs.root, ".transient-*") + if err != nil { + return 0, Cookie{}, err + } + path := f.Name() + f.Close() + + id := lfs.statInodeID(path) + if id == 0 { + os.Remove(path) + return 0, Cookie{}, fmt.Errorf("failed to stat transient file") + } + + var cookie Cookie + rand.Read(cookie[:]) + + rel, _ := filepath.Rel(lfs.root, path) + lfs.mu.Lock() + lfs.transient[id] = transientFile{cookie: cookie, path: path} + lfs.byID[id] = rel + lfs.mu.Unlock() + + return id, cookie, nil +} + +func (lfs *LocalTernVFS) LinkFile(fileID InodeID, cookie Cookie, dirID InodeID, name string, data io.Reader) error { + lfs.mu.RLock() + tf, ok := lfs.transient[fileID] + lfs.mu.RUnlock() + if !ok { + return os.ErrNotExist + } + if tf.cookie != cookie { + return os.ErrPermission + } + + dirPath, ok := lfs.resolve(dirID) + if !ok { + return os.ErrNotExist + } + childPath := filepath.Join(dirPath, name) + + // Write data to the destination file. + f, err := os.OpenFile(childPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return err + } + if data != nil { + if _, err := io.Copy(f, data); err != nil { + f.Close() + os.Remove(childPath) + return err + } + } + if err := f.Close(); err != nil { + os.Remove(childPath) + return err + } + + // Remove the transient temp file and register the linked file. + os.Remove(tf.path) + rel, _ := filepath.Rel(lfs.root, childPath) + lfs.mu.Lock() + delete(lfs.transient, fileID) + // Re-register the original fileID to point to the linked path so that + // Stat(fileID) continues to work (needed for CLOSE replay detection). + lfs.byID[fileID] = rel + lfs.mu.Unlock() + + return nil +} + +func (lfs *LocalTernVFS) CreateFile(dirID InodeID, name string, data io.Reader) (InodeID, error) { + dirPath, ok := lfs.resolve(dirID) + if !ok { + return 0, os.ErrNotExist + } + childPath := filepath.Join(dirPath, name) + f, err := os.OpenFile(childPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return 0, err + } + if data != nil { + if _, err := io.Copy(f, data); err != nil { + f.Close() + os.Remove(childPath) + return 0, err + } + } + if err := f.Close(); err != nil { + os.Remove(childPath) + return 0, err + } + return lfs.register(childPath, dirID), nil +} + +func (lfs *LocalTernVFS) Remove(dirID InodeID, name string) error { + dirPath, ok := lfs.resolve(dirID) + if !ok { + return os.ErrNotExist + } + childPath := filepath.Join(dirPath, name) + // Check what it is to update our maps. + childID := lfs.statInodeID(childPath) + if childID == 0 { + return os.ErrNotExist + } + if err := os.RemoveAll(childPath); err != nil { + return err + } + rel, _ := filepath.Rel(lfs.root, childPath) + lfs.mu.Lock() + delete(lfs.byID, childID) + delete(lfs.byPath, rel) + delete(lfs.parent, childID) + lfs.mu.Unlock() + return nil +} + +func (lfs *LocalTernVFS) Rename(srcDirID InodeID, srcName string, dstDirID InodeID, dstName string) error { + srcDir, ok := lfs.resolve(srcDirID) + if !ok { + return os.ErrNotExist + } + dstDir, ok := lfs.resolve(dstDirID) + if !ok { + return os.ErrNotExist + } + srcPath := filepath.Join(srcDir, srcName) + dstPath := filepath.Join(dstDir, dstName) + + srcID := lfs.statInodeID(srcPath) + if srcID == 0 { + return os.ErrNotExist + } + + if err := os.Rename(srcPath, dstPath); err != nil { + return err + } + + // Update maps: remove old path, register new path. + srcRel, _ := filepath.Rel(lfs.root, srcPath) + lfs.mu.Lock() + delete(lfs.byID, srcID) + delete(lfs.byPath, srcRel) + delete(lfs.parent, srcID) + lfs.mu.Unlock() + + lfs.register(dstPath, dstDirID) + return nil +} + +func (lfs *LocalTernVFS) SetTime(id InodeID, mtime *time.Time, atime *time.Time) error { + path, ok := lfs.resolve(id) + if !ok { + return os.ErrNotExist + } + // Read current times to preserve unchanged fields. + var st syscall.Stat_t + if err := syscall.Lstat(path, &st); err != nil { + return err + } + at := time.Unix(st.Atim.Sec, st.Atim.Nsec) + mt := time.Unix(st.Mtim.Sec, st.Mtim.Nsec) + if atime != nil { + at = *atime + } + if mtime != nil { + mt = *mtime + } + return os.Chtimes(path, at, mt) +} + +// inodeIDToFH converts an InodeID to an 8-byte NFS file handle. +func inodeIDToFH(id InodeID) []byte { + var fh [8]byte + binary.BigEndian.PutUint64(fh[:], uint64(id)) + return fh[:] +} + +// fhToInodeID converts an 8-byte NFS file handle back to an InodeID. +func fhToInodeID(fh []byte) (InodeID, bool) { + if len(fh) != 8 { + return 0, false + } + return InodeID(binary.BigEndian.Uint64(fh)), true +} From 17efbd4652eea9447d6d813a56feb18858e2ce54 Mon Sep 17 00:00:00 2001 From: Joshua Leahy Date: Thu, 19 Mar 2026 09:28:40 +0000 Subject: [PATCH 3/6] ternvfs first cut --- go/nfsd/main.go | 67 +++++-- go/nfsd/ops.go | 2 +- go/nfsd/ternvfs.go | 452 +++++++++++++++++++++++++++++++++++++++++++++ go/nfsd/vfs.go | 9 +- 4 files changed, 506 insertions(+), 24 deletions(-) create mode 100644 go/nfsd/ternvfs.go diff --git a/go/nfsd/main.go b/go/nfsd/main.go index 35990b2f..24944dd5 100644 --- a/go/nfsd/main.go +++ b/go/nfsd/main.go @@ -9,11 +9,16 @@ import ( "log/slog" "os" "path/filepath" + "xtx/ternfs/client" + "xtx/ternfs/core/bufpool" + "xtx/ternfs/core/log" + "xtx/ternfs/msgs" ) func main() { addr := flag.String("addr", ":2049", "listen address") - root := flag.String("root", ".", "root directory to export") + root := flag.String("root", "", "local root directory to export (for testing)") + registry := flag.String("registry", "", "TernFS registry address (for production)") staging := flag.String("staging", "", "staging directory for writes (omit for read-only)") verbose := flag.Bool("v", false, "verbose logging of NFS requests/responses") flag.Parse() @@ -22,41 +27,65 @@ func main() { if *verbose { level = slog.LevelDebug } - logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})) + slogger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})) - absRoot, err := filepath.Abs(*root) - if err != nil { - logger.Error("resolving root path", "err", err) - os.Exit(1) - } - info, err := os.Stat(absRoot) - if err != nil || !info.IsDir() { - logger.Error("root must be a directory", "path", absRoot) + if (*root == "") == (*registry == "") { + slogger.Error("exactly one of -root (local) or -registry (TernFS) must be specified") os.Exit(1) } var ss StagingStore + var err error if *staging != "" { - ss, err = NewLocalStagingStore(*staging, logger) + ss, err = NewLocalStagingStore(*staging, slogger) if err != nil { - logger.Error("creating staging store", "err", err) + slogger.Error("creating staging store", "err", err) os.Exit(1) } - logger.Info("staging directory configured", "path", *staging) + slogger.Info("staging directory configured", "path", *staging) } else { ss = readOnlyStagingStore{} - logger.Info("no staging directory — read-only mode") + slogger.Info("no staging directory — read-only mode") + } + + var fs TernVFS + if *root != "" { + absRoot, err := filepath.Abs(*root) + if err != nil { + slogger.Error("resolving root path", "err", err) + os.Exit(1) + } + info, err := os.Stat(absRoot) + if err != nil || !info.IsDir() { + slogger.Error("root must be a directory", "path", absRoot) + os.Exit(1) + } + fs = NewLocalTernVFS(absRoot) + slogger.Info("local VFS mode", "root", absRoot) + } else { + logLevel := log.INFO + if *verbose { + logLevel = log.DEBUG + } + ternLogger := log.NewLogger(os.Stderr, &log.LoggerOptions{Level: logLevel}) + c, err := client.NewClient(ternLogger, nil, *registry, msgs.AddrsInfo{}) + if err != nil { + slogger.Error("connecting to TernFS registry", "err", err) + os.Exit(1) + } + bp := bufpool.NewBufPool() + fs = NewRemoteTernVFS(c, ternLogger, bp) + slogger.Info("TernFS mode", "registry", *registry) } - fs := NewLocalTernVFS(absRoot) - srv, err := NewServer(fs, ss, logger) + srv, err := NewServer(fs, ss, slogger) if err != nil { - logger.Error("creating server", "err", err) + slogger.Error("creating server", "err", err) os.Exit(1) } - logger.Info("NFS server listening", "addr", *addr, "root", absRoot) + slogger.Info("NFS server listening", "addr", *addr) if err := srv.ListenAndServe(*addr); err != nil { - logger.Error("server error", "err", err) + slogger.Error("server error", "err", err) os.Exit(1) } } diff --git a/go/nfsd/ops.go b/go/nfsd/ops.go index 8c83156b..2d593219 100644 --- a/go/nfsd/ops.go +++ b/go/nfsd/ops.go @@ -399,7 +399,7 @@ func (s *Server) opOpen(args OPEN4args, st *compoundState, w *COMPOUND4resWriter if err != nil { // File doesn't exist — construct a transient file. var fileCookie Cookie - id, fileCookie, err = s.fs.ConstructFile() + id, fileCookie, err = s.fs.ConstructFile(dirID) if err != nil { ew := w.AppendResarray_Open() ew.SetValue_Default(s.errToNFS(err)) diff --git a/go/nfsd/ternvfs.go b/go/nfsd/ternvfs.go new file mode 100644 index 00000000..e11bc461 --- /dev/null +++ b/go/nfsd/ternvfs.go @@ -0,0 +1,452 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "errors" + "io" + "os" + "sync" + "time" + "xtx/ternfs/client" + "xtx/ternfs/core/bufpool" + "xtx/ternfs/core/crc32c" + "xtx/ternfs/core/log" + "xtx/ternfs/msgs" +) + +// RemoteTernVFS implements TernVFS backed by a real TernFS cluster. +type RemoteTernVFS struct { + client *client.Client + log *log.Logger + bufPool *bufpool.BufPool + dirInfoCache *client.DirInfoCache + + mu sync.Mutex + parents map[InodeID]InodeID // child → parent, populated by Lookup + readers map[msgs.InodeId]*cachedReader // file reader cache +} + +type cachedReader struct { + reader *client.FileReader + fileSize uint64 +} + +func NewRemoteTernVFS(c *client.Client, logger *log.Logger, bufPool *bufpool.BufPool) *RemoteTernVFS { + return &RemoteTernVFS{ + client: c, + log: logger, + bufPool: bufPool, + dirInfoCache: client.NewDirInfoCache(), + parents: make(map[InodeID]InodeID), + readers: make(map[msgs.InodeId]*cachedReader), + } +} + +func (t *RemoteTernVFS) RootID() InodeID { + return InodeID(msgs.ROOT_DIR_INODE_ID) +} + +func (t *RemoteTernVFS) Stat(id InodeID) (NodeInfo, error) { + mid := msgs.InodeId(id) + switch mid.Type() { + case msgs.DIRECTORY: + var resp msgs.StatDirectoryResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.StatDirectoryReq{Id: mid}, &resp); err != nil { + return NodeInfo{}, ternToOSError(err) + } + mt := resp.Mtime.Time() + return NodeInfo{Size: 0, Mtime: mt, Atime: mt}, nil + default: + var resp msgs.StatFileResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.StatFileReq{Id: mid}, &resp); err != nil { + return NodeInfo{}, ternToOSError(err) + } + return NodeInfo{ + Size: resp.Size, + Mtime: resp.Mtime.Time(), + Atime: resp.Atime.Time(), + }, nil + } +} + +func (t *RemoteTernVFS) Lookup(dirID InodeID, name string) (InodeID, error) { + mid := msgs.InodeId(dirID) + var resp msgs.LookupResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.LookupReq{DirId: mid, Name: name}, &resp); err != nil { + return 0, ternToOSError(err) + } + childID := InodeID(resp.TargetId) + t.mu.Lock() + t.parents[childID] = dirID + t.mu.Unlock() + return childID, nil +} + +func (t *RemoteTernVFS) LookupParent(id InodeID) (InodeID, error) { + // Check the parent cache first. + t.mu.Lock() + pid, ok := t.parents[id] + t.mu.Unlock() + if ok { + return pid, nil + } + // For directories, StatDirectory returns the Owner (= parent directory). + mid := msgs.InodeId(id) + if mid.Type() == msgs.DIRECTORY { + var resp msgs.StatDirectoryResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.StatDirectoryReq{Id: mid}, &resp); err != nil { + return 0, ternToOSError(err) + } + if resp.Owner == msgs.NULL_INODE_ID { + // Root directory or snapshot directory — parent is itself. + return id, nil + } + return InodeID(resp.Owner), nil + } + // For files/symlinks without a cached parent, we have no way to find it. + return 0, os.ErrNotExist +} + +func (t *RemoteTernVFS) Readdir(dirID InodeID, startHash uint64) ([]DirEntry, uint64, error) { + mid := msgs.InodeId(dirID) + var resp msgs.ReadDirResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.ReadDirReq{ + DirId: mid, + StartHash: msgs.NameHash(startHash), + }, &resp); err != nil { + return nil, 0, ternToOSError(err) + } + entries := make([]DirEntry, len(resp.Results)) + t.mu.Lock() + for i, e := range resp.Results { + entries[i] = DirEntry{ + Name: e.Name, + ID: InodeID(e.TargetId), + NameHash: uint64(e.NameHash), + } + t.parents[InodeID(e.TargetId)] = dirID + } + t.mu.Unlock() + return entries, uint64(resp.NextHash), nil +} + +func (t *RemoteTernVFS) Read(fileID InodeID, offset uint64, dest []byte) (int, bool, error) { + mid := msgs.InodeId(fileID) + cr, err := t.getOrCreateReader(mid) + if err != nil { + return 0, false, ternToOSError(err) + } + n, err := cr.reader.Read(t.log, t.client, nil, t.bufPool, offset, dest) + if errors.Is(err, io.EOF) { + return 0, true, nil + } + if err != nil { + return 0, false, err + } + eof := offset+uint64(n) >= cr.fileSize + return n, eof, nil +} + +func (t *RemoteTernVFS) getOrCreateReader(mid msgs.InodeId) (*cachedReader, error) { + t.mu.Lock() + cr, ok := t.readers[mid] + t.mu.Unlock() + if ok { + return cr, nil + } + fr, err := t.client.NewFileReader(t.log, mid) + if err != nil { + return nil, err + } + // Get file size from Stat. + var resp msgs.StatFileResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.StatFileReq{Id: mid}, &resp); err != nil { + return nil, err + } + cr = &cachedReader{reader: fr, fileSize: resp.Size} + t.mu.Lock() + t.readers[mid] = cr + t.mu.Unlock() + return cr, nil +} + +func (t *RemoteTernVFS) Readlink(fileID InodeID) (string, error) { + mid := msgs.InodeId(fileID) + buf, err := t.client.FetchFile(t.log, t.bufPool, mid) + if err != nil { + return "", ternToOSError(err) + } + target := string(buf.Bytes()) + t.bufPool.Put(buf) + return target, nil +} + +func (t *RemoteTernVFS) Mkdir(dirID InodeID, name string) (InodeID, error) { + mid := msgs.InodeId(dirID) + var resp msgs.MakeDirectoryResp + if err := t.client.CDCRequest(t.log, &msgs.MakeDirectoryReq{ + OwnerId: mid, + Name: name, + }, &resp); err != nil { + return 0, ternToOSError(err) + } + childID := InodeID(resp.Id) + t.mu.Lock() + t.parents[childID] = dirID + t.mu.Unlock() + return childID, nil +} + +func (t *RemoteTernVFS) Symlink(dirID InodeID, name string, target string) (InodeID, error) { + mid := msgs.InodeId(dirID) + // Create transient symlink inode. + var constructResp msgs.ConstructFileResp + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.ConstructFileReq{ + Type: msgs.SYMLINK, + Note: name, + }, &constructResp); err != nil { + return 0, ternToOSError(err) + } + fileId := constructResp.Id + cookie := constructResp.Cookie + // Write symlink target as inline span. + body := []byte(target) + crc := msgs.Crc(crc32c.Sum(0, body)) + if err := t.client.ShardRequest(t.log, fileId.Shard(), &msgs.AddInlineSpanReq{ + FileId: fileId, + Cookie: cookie, + StorageClass: msgs.INLINE_STORAGE, + ByteOffset: 0, + Size: uint32(len(body)), + Crc: crc, + Body: body, + }, &msgs.AddInlineSpanResp{}); err != nil { + return 0, ternToOSError(err) + } + // Link into directory. + if err := t.client.ShardRequest(t.log, mid.Shard(), &msgs.LinkFileReq{ + FileId: fileId, + Cookie: cookie, + OwnerId: mid, + Name: name, + }, &msgs.LinkFileResp{}); err != nil { + return 0, ternToOSError(err) + } + childID := InodeID(fileId) + t.mu.Lock() + t.parents[childID] = dirID + t.mu.Unlock() + return childID, nil +} + +func (t *RemoteTernVFS) ConstructFile(dirID InodeID) (InodeID, Cookie, error) { + dirMid := msgs.InodeId(dirID) + var resp msgs.ConstructFileResp + if err := t.client.ShardRequest(t.log, dirMid.Shard(), &msgs.ConstructFileReq{ + Type: msgs.FILE, + }, &resp); err != nil { + return 0, Cookie{}, ternToOSError(err) + } + return InodeID(resp.Id), Cookie(resp.Cookie), nil +} + +func (t *RemoteTernVFS) LinkFile(fileID InodeID, cookie Cookie, dirID InodeID, name string, data io.Reader) error { + mid := msgs.InodeId(fileID) + dirMid := msgs.InodeId(dirID) + // Write file data as spans. + if data != nil { + if err := t.client.WriteFile(t.log, t.bufPool, t.dirInfoCache, dirMid, mid, msgs.Cookie(cookie), data); err != nil { + return ternToOSError(err) + } + } + // Link the file into the directory. + if err := t.client.ShardRequest(t.log, dirMid.Shard(), &msgs.LinkFileReq{ + FileId: mid, + Cookie: msgs.Cookie(cookie), + OwnerId: dirMid, + Name: name, + }, &msgs.LinkFileResp{}); err != nil { + return ternToOSError(err) + } + childID := InodeID(mid) + t.mu.Lock() + t.parents[childID] = dirID + t.mu.Unlock() + return nil +} + +func (t *RemoteTernVFS) CreateFile(dirID InodeID, name string, data io.Reader) (InodeID, error) { + dirMid := msgs.InodeId(dirID) + // ConstructFile on the same shard as the directory. + var constructResp msgs.ConstructFileResp + if err := t.client.ShardRequest(t.log, dirMid.Shard(), &msgs.ConstructFileReq{ + Type: msgs.FILE, + Note: name, + }, &constructResp); err != nil { + return 0, ternToOSError(err) + } + fileId := constructResp.Id + cookie := constructResp.Cookie + // Write data. + if data != nil { + if err := t.client.WriteFile(t.log, t.bufPool, t.dirInfoCache, dirMid, fileId, cookie, data); err != nil { + return 0, ternToOSError(err) + } + } + // Link. + if err := t.client.ShardRequest(t.log, dirMid.Shard(), &msgs.LinkFileReq{ + FileId: fileId, + Cookie: cookie, + OwnerId: dirMid, + Name: name, + }, &msgs.LinkFileResp{}); err != nil { + return 0, ternToOSError(err) + } + childID := InodeID(fileId) + t.mu.Lock() + t.parents[childID] = dirID + t.mu.Unlock() + return childID, nil +} + +func (t *RemoteTernVFS) Remove(dirID InodeID, name string) error { + dirMid := msgs.InodeId(dirID) + // Lookup to get target ID and creation time. + var lookupResp msgs.LookupResp + if err := t.client.ShardRequest(t.log, dirMid.Shard(), &msgs.LookupReq{ + DirId: dirMid, + Name: name, + }, &lookupResp); err != nil { + return ternToOSError(err) + } + targetId := lookupResp.TargetId + creationTime := lookupResp.CreationTime + switch targetId.Type() { + case msgs.DIRECTORY: + // Directory removal goes through CDC. + if err := t.client.CDCRequest(t.log, &msgs.SoftUnlinkDirectoryReq{ + OwnerId: dirMid, + TargetId: targetId, + CreationTime: creationTime, + Name: name, + }, &msgs.SoftUnlinkDirectoryResp{}); err != nil { + return ternToOSError(err) + } + default: + // File/symlink removal is a shard-local operation. + if err := t.client.ShardRequest(t.log, dirMid.Shard(), &msgs.SoftUnlinkFileReq{ + OwnerId: dirMid, + FileId: targetId, + Name: name, + CreationTime: creationTime, + }, &msgs.SoftUnlinkFileResp{}); err != nil { + return ternToOSError(err) + } + } + // Evict cached reader for removed files. + t.mu.Lock() + delete(t.readers, targetId) + delete(t.parents, InodeID(targetId)) + t.mu.Unlock() + return nil +} + +func (t *RemoteTernVFS) Rename(srcDirID InodeID, srcName string, dstDirID InodeID, dstName string) error { + srcMid := msgs.InodeId(srcDirID) + dstMid := msgs.InodeId(dstDirID) + // Lookup source to get target ID and creation time. + var lookupResp msgs.LookupResp + if err := t.client.ShardRequest(t.log, srcMid.Shard(), &msgs.LookupReq{ + DirId: srcMid, + Name: srcName, + }, &lookupResp); err != nil { + return ternToOSError(err) + } + targetId := lookupResp.TargetId + creationTime := lookupResp.CreationTime + if srcDirID == dstDirID && targetId.Type() != msgs.DIRECTORY { + // Same-directory file/symlink rename — shard-local. + if err := t.client.ShardRequest(t.log, srcMid.Shard(), &msgs.SameDirectoryRenameReq{ + TargetId: targetId, + DirId: srcMid, + OldName: srcName, + OldCreationTime: creationTime, + NewName: dstName, + }, &msgs.SameDirectoryRenameResp{}); err != nil { + return ternToOSError(err) + } + } else if targetId.Type() == msgs.DIRECTORY { + // Directory rename — always through CDC. + if err := t.client.CDCRequest(t.log, &msgs.RenameDirectoryReq{ + TargetId: targetId, + OldOwnerId: srcMid, + OldName: srcName, + OldCreationTime: creationTime, + NewOwnerId: dstMid, + NewName: dstName, + }, &msgs.RenameDirectoryResp{}); err != nil { + return ternToOSError(err) + } + } else { + // Cross-directory file rename — through CDC. + if err := t.client.CDCRequest(t.log, &msgs.RenameFileReq{ + TargetId: targetId, + OldOwnerId: srcMid, + OldName: srcName, + OldCreationTime: creationTime, + NewOwnerId: dstMid, + NewName: dstName, + }, &msgs.RenameFileResp{}); err != nil { + return ternToOSError(err) + } + } + // Update parent cache. + t.mu.Lock() + t.parents[InodeID(targetId)] = dstDirID + t.mu.Unlock() + return nil +} + +func (t *RemoteTernVFS) SetTime(id InodeID, mtime *time.Time, atime *time.Time) error { + mid := msgs.InodeId(id) + var req msgs.SetTimeReq + req.Id = mid + // MSB of Mtime/Atime indicates "set this field". + if mtime != nil { + req.Mtime = (1 << 63) | uint64(mtime.UnixNano()) + } + if atime != nil { + req.Atime = (1 << 63) | uint64(atime.UnixNano()) + } + if err := t.client.ShardRequest(t.log, mid.Shard(), &req, &msgs.SetTimeResp{}); err != nil { + return ternToOSError(err) + } + return nil +} + +// ternToOSError maps TernFS errors to os-level errors so that the NFS +// server's errToNFS can translate them to NFS status codes. +func ternToOSError(err error) error { + var te msgs.TernError + if !errors.As(err, &te) { + return err + } + switch te { + case msgs.EDGE_NOT_FOUND, msgs.FILE_NOT_FOUND, msgs.DIRECTORY_NOT_FOUND, + msgs.NAME_NOT_FOUND, msgs.OLD_DIRECTORY_NOT_FOUND, msgs.NEW_DIRECTORY_NOT_FOUND: + return os.ErrNotExist + case msgs.NOT_AUTHORISED: + return os.ErrPermission + case msgs.CANNOT_OVERRIDE_NAME: + return os.ErrExist + case msgs.DIRECTORY_NOT_EMPTY: + return nfsError(NFS4ERR_NOTEMPTY) + case msgs.EDGE_IS_LOCKED, msgs.NAME_IS_LOCKED: + return nfsError(NFS4ERR_LOCKED) + default: + return err + } +} diff --git a/go/nfsd/vfs.go b/go/nfsd/vfs.go index c7437cd4..1b8f5d4e 100644 --- a/go/nfsd/vfs.go +++ b/go/nfsd/vfs.go @@ -87,9 +87,10 @@ type TernVFS interface { // ConstructFile creates a transient file (not yet visible in any // directory). Returns the file's InodeID and a Cookie that must be - // provided for subsequent operations on the file. Matches TernFS - // ConstructFileReq semantics. - ConstructFile() (InodeID, Cookie, error) + // provided for subsequent operations on the file. dirID is the + // intended target directory (used for shard selection in TernFS). + // Matches TernFS ConstructFileReq semantics. + ConstructFile(dirID InodeID) (InodeID, Cookie, error) // LinkFile links a transient file into a directory, making it visible. // The correct cookie (from ConstructFile) must be provided. data is the @@ -390,7 +391,7 @@ func (lfs *LocalTernVFS) Symlink(dirID InodeID, name string, target string) (Ino return lfs.register(childPath, dirID), nil } -func (lfs *LocalTernVFS) ConstructFile() (InodeID, Cookie, error) { +func (lfs *LocalTernVFS) ConstructFile(dirID InodeID) (InodeID, Cookie, error) { // Create a temp file to simulate a transient TernFS file. f, err := os.CreateTemp(lfs.root, ".transient-*") if err != nil { From 4df3b76ccd125e08b6b3b24fba47dac7faa5599c Mon Sep 17 00:00:00 2001 From: Joshua Leahy Date: Thu, 19 Mar 2026 14:37:07 +0000 Subject: [PATCH 4/6] Fix stat for transient files --- go/nfsd/ops.go | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/go/nfsd/ops.go b/go/nfsd/ops.go index 2d593219..4dc04203 100644 --- a/go/nfsd/ops.go +++ b/go/nfsd/ops.go @@ -192,16 +192,23 @@ func (s *Server) opGetattr(args GETATTR4args, st *compoundState, w *COMPOUND4res } ni, err := s.fs.Stat(st.currentID) if err != nil { - ew := w.AppendResarray_Getattr() - status := s.errToNFS(err) - ew.SetValue_Default(status) - w.Resume(ew.Finish()) - return status - } - - // If the file has an active staging buffer, use its size. - if sz, ok := s.stagingStore.StagedSize(st.currentID); ok { - ni.Size = sz + // If Stat fails but the file is being staged (transient file not yet + // linked), synthesize metadata from the staging store. + if sz, ok := s.stagingStore.StagedSize(st.currentID); ok { + now := time.Now() + ni = NodeInfo{Size: sz, Mtime: now, Atime: now} + } else { + ew := w.AppendResarray_Getattr() + status := s.errToNFS(err) + ew.SetValue_Default(status) + w.Resume(ew.Finish()) + return status + } + } else { + // If the file has an active staging buffer, use its size. + if sz, ok := s.stagingStore.StagedSize(st.currentID); ok { + ni.Size = sz + } } reqMask := parseBitmap(args.AttrRequest()) From 251b3de9dfd340ced52de6ed45830bdf04705b8a Mon Sep 17 00:00:00 2001 From: Joshua Leahy Date: Thu, 19 Mar 2026 14:37:35 +0000 Subject: [PATCH 5/6] tern cluster integration test --- go/nfsd/cluster_test.go | 992 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 992 insertions(+) create mode 100644 go/nfsd/cluster_test.go diff --git a/go/nfsd/cluster_test.go b/go/nfsd/cluster_test.go new file mode 100644 index 00000000..f23e3d2f --- /dev/null +++ b/go/nfsd/cluster_test.go @@ -0,0 +1,992 @@ +// Copyright 2026 XTX Markets Technologies Limited +// +// SPDX-License-Identifier: GPL-2.0-or-later + +//go:build ternnfs + +package main + +import ( + "encoding/binary" + "flag" + "fmt" + "net" + "os" + "path" + "runtime" + "testing" + "time" + "xtx/ternfs/client" + "xtx/ternfs/core/bufpool" + "xtx/ternfs/core/log" + "xtx/ternfs/core/managedprocess" + "xtx/ternfs/msgs" +) + +var ( + binariesDir = flag.String("binaries-dir", "", "directory containing pre-built TernFS binaries") + repoDir = flag.String("repo-dir", "", "repository root (for building binaries)") + registryPort uint16 + registryAddr string + dataDir string + procs *managedprocess.ManagedProcesses + ternLogger *log.Logger +) + +func TestMain(m *testing.M) { + flag.Parse() + + if *repoDir == "" { + _, filename, _, ok := runtime.Caller(0) + if !ok { + panic("no caller information") + } + // cluster_test.go is in go/nfsd/, repo root is two levels up + *repoDir = path.Dir(path.Dir(path.Dir(filename))) + } + + var err error + dataDir, err = os.MkdirTemp("", "nfstests.") + if err != nil { + panic(err) + } + + logFile, err := os.OpenFile(path.Join(dataDir, "test-log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + panic(err) + } + ternLogger = log.NewLogger(logFile, &log.LoggerOptions{Level: log.INFO}) + + var cppExes *managedprocess.CppExes + var goExes *managedprocess.GoExes + if *binariesDir != "" { + cppExes = &managedprocess.CppExes{ + RegistryExe: path.Join(*binariesDir, "ternregistry"), + ShardExe: path.Join(*binariesDir, "ternshard"), + CDCExe: path.Join(*binariesDir, "terncdc"), + DBToolsExe: path.Join(*binariesDir, "terndbtools"), + } + goExes = &managedprocess.GoExes{ + BlocksExe: path.Join(*binariesDir, "ternblocks"), + } + } else { + fmt.Println("building TernFS binaries...") + cppExes = managedprocess.BuildCppExes(ternLogger, *repoDir, "release") + goExes = managedprocess.BuildGoExes(ternLogger, *repoDir, false) + } + + terminateChan := make(chan any, 1) + procs = managedprocess.New(terminateChan) + + registryPort = 55556 // different from terntests default + registryAddr = fmt.Sprintf("127.0.0.1:%d", registryPort) + + // Start registry (leader only, single replica). + procs.StartRegistry(ternLogger, &managedprocess.RegistryOpts{ + Exe: cppExes.RegistryExe, + LogLevel: log.INFO, + Dir: path.Join(dataDir, "registry"), + RegistryAddress: registryAddr, + Replica: 0, + Addr1: fmt.Sprintf("127.0.0.1:%d", registryPort), + UsingDynamicPorts: true, + LogsDBFlags: []string{"-logsdb-leader", "-logsdb-no-replication"}, + }) + if err := client.WaitForRegistry(ternLogger, registryAddr, 10*time.Second); err != nil { + panic(fmt.Errorf("registry not ready: %w", err)) + } + + // Start block services (14 failure domains, 1 HDD + 1 FLASH each). + // Shards require at least 14 block services per storage class. + failureDomains := 14 + servicesPerDomain := 2 + for i := 0; i < failureDomains; i++ { + storageClasses := []msgs.StorageClass{ + msgs.HDD_STORAGE, + msgs.FLASH_STORAGE, + } + procs.StartBlockService(ternLogger, &managedprocess.BlockServiceOpts{ + Exe: goExes.BlocksExe, + Path: path.Join(dataDir, fmt.Sprintf("bs_%d", i)), + Addr1: "127.0.0.1:0", + StorageClasses: storageClasses, + FailureDomain: fmt.Sprintf("%d", i), + LogLevel: log.INFO, + RegistryAddress: registryAddr, + }) + } + fmt.Println("waiting for block services...") + client.WaitForBlockServices(ternLogger, registryAddr, failureDomains*servicesPerDomain, true, 30*time.Second) + + // Start CDC (single replica). + procs.StartCDC(ternLogger, *repoDir, &managedprocess.CDCOpts{ + ReplicaId: 0, + Exe: cppExes.CDCExe, + Dir: path.Join(dataDir, "cdc"), + LogLevel: log.INFO, + RegistryAddress: registryAddr, + Addr1: "127.0.0.1:0", + LogsDBFlags: []string{"-logsdb-leader", "-logsdb-no-replication", "-logsdb-initial-start"}, + }) + + // Start 256 shards (single replica each). + for i := 0; i < 256; i++ { + shrid := msgs.MakeShardReplicaId(msgs.ShardId(i), 0) + procs.StartShard(ternLogger, *repoDir, &managedprocess.ShardOpts{ + Exe: cppExes.ShardExe, + Dir: path.Join(dataDir, fmt.Sprintf("shard_%03d", i)), + LogLevel: log.INFO, + Shrid: shrid, + RegistryAddress: registryAddr, + Addr1: "127.0.0.1:0", + LogsDBFlags: []string{"-logsdb-leader", "-logsdb-no-replication", "-logsdb-initial-start"}, + }) + } + + fmt.Println("waiting for cluster to be ready...") + client.WaitForClient(ternLogger, registryAddr, 60*time.Second) + fmt.Println("cluster ready") + + // Monitor for unexpected process termination. + go func() { + err := <-terminateChan + if err != nil { + fmt.Fprintf(os.Stderr, "cluster process died: %v\n", err) + os.Exit(1) + } + }() + + code := m.Run() + + procs.Close() + logFile.Close() + if code == 0 { + os.RemoveAll(dataDir) + } else { + fmt.Printf("test data preserved at %s\n", dataDir) + } + os.Exit(code) +} + +// startTernTestServer creates an NFS server backed by the shared TernFS cluster. +// It creates a fresh RemoteTernVFS client and staging directory per test. +func startTernTestServer(t *testing.T) (addr string, cleanup func()) { + t.Helper() + c, err := client.NewClient(ternLogger, nil, registryAddr, msgs.AddrsInfo{}) + if err != nil { + t.Fatal(err) + } + bp := bufpool.NewBufPool() + fs := NewRemoteTernVFS(c, ternLogger, bp) + + stagingDir := t.TempDir() + ss, err := NewLocalStagingStore(stagingDir, nil) + if err != nil { + t.Fatal(err) + } + srv, err := NewServer(fs, ss, nil) + if err != nil { + t.Fatal(err) + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + go func() { + for { + conn, err := ln.Accept() + if err != nil { + return + } + go srv.handleConn(conn) + } + }() + return ln.Addr().String(), func() { + ln.Close() + c.Close() + } +} + +// copyStateid copies a stateid4 from src (read-only) to dst (writer). +func copyStateid(dst Stateid4, src Stateid4) { + dst.SetSeqid(src.Seqid()) + for i := 0; i < 12; i++ { + dst.SetOther(i, src.Other(i)) + } +} + +// createFileViaNFS creates a file through the NFS protocol by doing +// PUTROOTFH + OPEN(CREATE) + GETFH + OPEN_CONFIRM + WRITE + CLOSE. +// Returns the file handle. +func createFileViaNFS(t *testing.T, conn net.Conn, xid *uint32, clientid uint64, name string, data []byte) []byte { + t.Helper() + + // OPEN CREATE + GETFH + OPEN_CONFIRM + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte(name)).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + + openEntry := nextOp(t, &iter) + openRes := openEntry.Value().AsOPEN4resEntry() + if openRes.Disc() != NFS4_OK { + t.Fatalf("OPEN status = %d", openRes.Disc()) + } + openOK := openRes.Value().AsOPEN4resok() + stateid := openOK.Stateid() + + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // WRITE + if len(data) > 0 { + res = sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + ww := w.AppendArgarray_Write() + copyStateid(ww.Stateid(), stateid) + ww = ww.SetOffset(0) + ww = ww.SetStable(2) // FILE_SYNC4 + ww = ww.SetData(data) + buf = ww.Finish() + w.Resume(buf) + }) + *xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + writeEntry := nextOp(t, &iter) + writeRes := writeEntry.Value().AsWRITE4resEntry() + if writeRes.Disc() != NFS4_OK { + t.Fatalf("WRITE status = %d", writeRes.Disc()) + } + } + + // CLOSE + res = sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + *xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + closeEntry := nextOp(t, &iter) + closeRes := closeEntry.Value().AsCLOSE4resEntry() + if closeRes.Disc() != NFS4_OK { + t.Fatalf("CLOSE status = %d", closeRes.Disc()) + } + + return fh +} + +// cleanupViaNFS removes a file by name from the root directory. +func cleanupViaNFS(t *testing.T, conn net.Conn, xid *uint32, name string) { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf := tw.SetData([]byte(name)).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + *xid++ + // Ignore errors — file might not exist. + _ = res +} + +func TestTernPutrootfhGetattr(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + res := sendCompound(t, conn, 1, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(1< "symlink-target" + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + cw := w.AppendArgarray_Create() + ltw := cw.SetObjtype_Nf4lnk() + buf := ltw.SetData([]byte("symlink-target")).Finish() + cw.Resume(buf) + nameW := cw.StartObjname() + buf = nameW.SetData([]byte("test-link")).Finish() + cw.Resume(buf) + faw := cw.StartCreateattrs() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + cw.Resume(buf) + buf = cw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + createRes := nextOp(t, &iter).Value().AsCREATE4resEntry() + if createRes.Disc() != NFS4_OK { + t.Fatalf("CREATE symlink status = %d", createRes.Disc()) + } + + // READLINK to verify target. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("test-link")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + w.AppendArgarray_Readlink() + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP + rlEntry := nextOp(t, &iter) + rlRes := rlEntry.Value().AsREADLINK4resEntry() + if rlRes.Disc() != NFS4_OK { + t.Fatalf("READLINK status = %d", rlRes.Disc()) + } + target := string(rlRes.Value().AsREADLINK4resok().Link().Data()) + if target != "symlink-target" { + t.Fatalf("READLINK = %q, want %q", target, "symlink-target") + } + + cleanupViaNFS(t, conn, &xid, "test-link") +} + +func TestTernRename(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + createFileViaNFS(t, conn, &xid, clientid, "rename-src.txt", []byte("rename data")) + + // RENAME: PUTROOTFH + SAVEFH + PUTROOTFH + RENAME + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Savefh() + w.AppendArgarray_Putrootfh() // same dir rename + + rw := w.AppendArgarray_Rename() + oldW := rw.StartOldname() + buf := oldW.SetData([]byte("rename-src.txt")).Finish() + rw.Resume(buf) + newW := rw.StartNewname() + buf = newW.SetData([]byte("rename-dst.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // SAVEFH + nextOp(t, &iter) // PUTROOTFH + renameEntry := nextOp(t, &iter) + renameRes := renameEntry.Value().AsRENAME4resEntry() + if renameRes.Disc() != NFS4_OK { + t.Fatalf("RENAME status = %d", renameRes.Disc()) + } + + // Verify old name is gone. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("rename-src.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + }) + xid++ + if res.Status() == NFS4_OK { + t.Fatal("expected old name to be gone after rename") + } + + // Verify new name exists and has correct data. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("rename-dst.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + rw := w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) + rw.SetOffset(0) + rw.SetCount(4096) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP + readEntry := nextOp(t, &iter) + readRes := readEntry.Value().AsREAD4resEntry() + if readRes.Disc() != NFS4_OK { + t.Fatalf("READ after rename status = %d", readRes.Disc()) + } + got := string(readRes.Value().AsREAD4resok().Data()) + if got != "rename data" { + t.Fatalf("data after rename = %q, want %q", got, "rename data") + } + + cleanupViaNFS(t, conn, &xid, "rename-dst.txt") +} + +func TestTernSetclientidAndRenew(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // RENEW + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + rw := w.AppendArgarray_Renew() + rw.SetClientid(clientid) + }) + xid++ + iter := expectOK(t, res) + entry := nextOp(t, &iter) + if entry.Value().AsRENEW4res().Status() != NFS4_OK { + t.Fatal("RENEW failed") + } +} + +func TestTernStagedFileGetattrAndRead(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // OPEN CREATE + GETFH + OPEN_CONFIRM + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("staged.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + openOK := nextOp(t, &iter).Value().AsOPEN4resEntry().Value().AsOPEN4resok() + stateid := openOK.Stateid() + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // WRITE some data + testData := []byte("staged file content") + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + ww := w.AppendArgarray_Write() + copyStateid(ww.Stateid(), stateid) + ww = ww.SetOffset(0) + ww = ww.SetStable(2) // FILE_SYNC4 + ww = ww.SetData(testData) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // GETATTR should return staged size (before CLOSE). + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(1 << FATTR4_SIZE) + buf = bw.Finish() + gw.Resume(buf) + buf = gw.Finish() + w.Resume(buf) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + attrData := getAttrData(t, nextOp(t, &iter).Value().AsGETATTR4resEntry().Value().AsGETATTR4resok()) + if len(attrData) < 8 { + t.Fatal("attr data too short") + } + size := binary.BigEndian.Uint64(attrData[:8]) + if size != uint64(len(testData)) { + t.Fatalf("staged GETATTR size = %d, want %d", size, len(testData)) + } + + // READ should return staged data (before CLOSE). + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + rw := w.AppendArgarray_Read() + copyStateid(rw.Stateid(), stateid) + rw.SetOffset(0) + rw.SetCount(4096) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + readOK := nextOp(t, &iter).Value().AsREAD4resEntry().Value().AsREAD4resok() + got := string(readOK.Data()) + if got != string(testData) { + t.Fatalf("staged READ = %q, want %q", got, string(testData)) + } + + // CLOSE to commit. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + xid++ + expectOK(t, res) + + cleanupViaNFS(t, conn, &xid, "staged.txt") +} From 4729b60c27926a61e8e844a0d800294dfdba0774 Mon Sep 17 00:00:00 2001 From: Joshua Leahy Date: Thu, 19 Mar 2026 17:12:41 +0000 Subject: [PATCH 6/6] more integration tests, courtesy of claude --- go/nfsd/cluster_test.go | 1520 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1520 insertions(+) diff --git a/go/nfsd/cluster_test.go b/go/nfsd/cluster_test.go index f23e3d2f..7b98ee6c 100644 --- a/go/nfsd/cluster_test.go +++ b/go/nfsd/cluster_test.go @@ -14,6 +14,8 @@ import ( "os" "path" "runtime" + "sort" + "sync" "testing" "time" "xtx/ternfs/client" @@ -990,3 +992,1521 @@ func TestTernStagedFileGetattrAndRead(t *testing.T) { cleanupViaNFS(t, conn, &xid, "staged.txt") } + +// setupNamedClient is like setupClient but with a custom identity string. +func setupNamedClient(t *testing.T, conn net.Conn, xid *uint32, identity string) uint64 { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + scw := w.AppendArgarray_Setclientid() + clientW := scw.StartClient() + clientW = clientW.SetId([]byte(identity)) + buf := clientW.Finish() + scw.Resume(buf) + cbW := scw.StartCallback() + cbW.SetCbProgram(0x40000000) + locW := cbW.StartCbLocation() + netidW := locW.StartRNetid() + buf = netidW.SetData([]byte("tcp")).Finish() + locW.Resume(buf) + addrW := locW.StartRAddr() + buf = addrW.SetData([]byte("0.0.0.0.0.0")).Finish() + locW.Resume(buf) + buf = locW.Finish() + cbW.Resume(buf) + buf = cbW.Finish() + scw.Resume(buf) + scw.SetCallbackIdent(0) + buf = scw.Finish() + w.Resume(buf) + }) + *xid++ + iter := expectOK(t, res) + entry := nextOp(t, &iter) + scRes := entry.Value().AsSETCLIENTID4resEntry() + if scRes.Disc() != NFS4_OK { + t.Fatalf("SETCLIENTID status = %d", scRes.Disc()) + } + clientid := scRes.Value().AsSETCLIENTID4resok().Clientid() + res = sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + scw := w.AppendArgarray_SetclientidConfirm() + scw.SetClientid(clientid) + }) + *xid++ + iter = expectOK(t, res) + entry = nextOp(t, &iter) + if entry.Value().AsSETCLIENTIDCONFIRM4res().Status() != NFS4_OK { + t.Fatal("SETCLIENTID_CONFIRM failed") + } + return clientid +} + +// lookupFH looks up a name in the root directory and returns the file handle. +func lookupFH(t *testing.T, conn net.Conn, xid *uint32, name string) []byte { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte(name)).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + w.AppendArgarray_Getfh() + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + return fh +} + +// readFileData reads a file by handle and returns the data. +func readFileData(t *testing.T, conn net.Conn, xid *uint32, fh []byte, offset uint64, count uint32) (data []byte, eof bool) { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + rw := w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) + rw.SetOffset(offset) + rw.SetCount(count) + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + readRes := nextOp(t, &iter).Value().AsREAD4resEntry() + if readRes.Disc() != NFS4_OK { + t.Fatalf("READ status = %s", Nfsstat4Name(readRes.Disc())) + } + readOK := readRes.Value().AsREAD4resok() + return readOK.Data(), readOK.Eof() != 0 +} + +// getAttrSize gets the size attribute from a file handle. +func getAttrSize(t *testing.T, conn net.Conn, xid *uint32, fh []byte) uint64 { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(1 << FATTR4_SIZE) + buf = bw.Finish() + gw.Resume(buf) + buf = gw.Finish() + w.Resume(buf) + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + ad := getAttrData(t, nextOp(t, &iter).Value().AsGETATTR4resEntry().Value().AsGETATTR4resok()) + if len(ad) < 8 { + t.Fatal("attr data too short for size") + } + return binary.BigEndian.Uint64(ad[:8]) +} + +// collectTernReaddirNames collects all names from a directory via paginated READDIR. +func collectTernReaddirNames(t *testing.T, conn net.Conn, xid *uint32, dirFH []byte) []string { + t.Helper() + var allNames []string + cookie := uint64(0) + var cookieVerf [8]byte + for { + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(dirFH).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + rdw := w.AppendArgarray_Readdir() + rdw.SetCookie(cookie) + cv := rdw.Cookieverf() + for i := 0; i < 8; i++ { + cv.SetData(i, cookieVerf[i]) + } + rdw.SetDircount(4096) + rdw.SetMaxcount(32768) + bmW := rdw.StartAttrRequest() + bmW.AppendData(1 << FATTR4_TYPE) + buf = bmW.Finish() + rdw.Resume(buf) + buf = rdw.Finish() + w.Resume(buf) + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + readdirRes := nextOp(t, &iter).Value().AsREADDIR4resEntry() + if readdirRes.Disc() != NFS4_OK { + t.Fatalf("READDIR status = %s", Nfsstat4Name(readdirRes.Disc())) + } + okRes := readdirRes.Value().AsREADDIR4resok() + cv := okRes.Cookieverf() + for i := 0; i < 8; i++ { + cookieVerf[i] = cv.Data(i) + } + reply := okRes.Reply() + if reply.EntriesPresent() == TRUE { + entOpt := reply.Entries() + for { + ent := entOpt.AsEntry4() + allNames = append(allNames, string(ent.Name().Data())) + cookie = ent.Cookie() + if ent.NextentryPresent() != TRUE { + break + } + entOpt = ent.Nextentry() + } + } + if reply.Eof() == TRUE { + break + } + } + return allNames +} + +// mkdirViaNFS creates a directory via NFS CREATE. +func mkdirViaNFS(t *testing.T, conn net.Conn, xid *uint32, name string) { + t.Helper() + res := sendCompound(t, conn, *xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + cw := w.AppendArgarray_Create() + cw.SetObjtype_Nf4dir() + nameW := cw.StartObjname() + buf := nameW.SetData([]byte(name)).Finish() + cw.Resume(buf) + faw := cw.StartCreateattrs() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + cw.Resume(buf) + buf = cw.Finish() + w.Resume(buf) + }) + *xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + createRes := nextOp(t, &iter).Value().AsCREATE4resEntry() + if createRes.Disc() != NFS4_OK { + t.Fatalf("CREATE dir %q status = %s", name, Nfsstat4Name(createRes.Disc())) + } +} + +// --- Large file test: multi-MB write and read-back --- + +func TestTernLargeFile(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // 2MB file — large enough to span multiple TernFS blocks/spans. + // Write in 512KB chunks to stay under the 1MB RPC frame limit. + totalSize := 2 * 1024 * 1024 + chunkSize := 512 * 1024 + data := make([]byte, totalSize) + for i := range data { + data[i] = byte((i*7 + i/256) % 251) + } + + // OPEN CREATE + GETFH + OPEN_CONFIRM + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("large-2mb.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + w.AppendArgarray_Getfh() + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + openOK := nextOp(t, &iter).Value().AsOPEN4resEntry().Value().AsOPEN4resok() + stateid := openOK.Stateid() + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // Write in chunks. + for off := 0; off < totalSize; off += chunkSize { + end := off + chunkSize + if end > totalSize { + end = totalSize + } + chunk := data[off:end] + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + copyStateid(ww.Stateid(), stateid) + ww = ww.SetOffset(uint64(off)) + ww = ww.SetStable(2) // FILE_SYNC4 + ww = ww.SetData(chunk) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + } + + // CLOSE. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + xid++ + expectOK(t, res) + + // Read back in chunks and verify. + var got []byte + for off := 0; off < totalSize; { + chunk, eof := readFileData(t, conn, &xid, fh, uint64(off), uint32(chunkSize)) + got = append(got, chunk...) + off += len(chunk) + if eof { + break + } + if len(chunk) == 0 { + t.Fatalf("READ returned 0 bytes at offset %d without EOF", off) + } + } + if len(got) != totalSize { + t.Fatalf("total READ len = %d, want %d", len(got), totalSize) + } + for i := range got { + if got[i] != data[i] { + t.Fatalf("data mismatch at byte %d: got %d, want %d", i, got[i], data[i]) + } + } + + cleanupViaNFS(t, conn, &xid, "large-2mb.txt") +} + +// --- Read at offset / partial reads --- + +func TestTernReadAtOffset(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + data := []byte("0123456789abcdefghijklmnopqrstuvwxyz") + fh := createFileViaNFS(t, conn, &xid, clientid, "offset-test.txt", data) + + // Read 4 bytes from middle — should not be EOF. + got, eof := readFileData(t, conn, &xid, fh, 10, 4) + if string(got) != "abcd" { + t.Fatalf("READ at offset 10 = %q, want %q", got, "abcd") + } + if eof { + t.Fatal("unexpected EOF in middle of file") + } + + // Read past end — should get remaining bytes with EOF. + got, eof = readFileData(t, conn, &xid, fh, 32, 100) + if string(got) != "wxyz" { + t.Fatalf("READ past end = %q, want %q", got, "wxyz") + } + if !eof { + t.Fatal("expected EOF past end of file") + } + + // Read at exact end — should get empty with EOF. + got, eof = readFileData(t, conn, &xid, fh, uint64(len(data)), 100) + if len(got) != 0 { + t.Fatalf("READ at end = %d bytes, want 0", len(got)) + } + if !eof { + t.Fatal("expected EOF at end of file") + } + + cleanupViaNFS(t, conn, &xid, "offset-test.txt") +} + +// --- Server restart with persistent file handles --- + +func TestTernServerRestart(t *testing.T) { + // Create a file with the first server instance. + addr1, cleanup1 := startTernTestServer(t) + conn1 := dial(t, addr1) + + xid := uint32(1) + clientid := setupClient(t, conn1, &xid) + testData := []byte("persistent handle test data") + fh := createFileViaNFS(t, conn1, &xid, clientid, "persist.txt", testData) + + // Shut down first server. + conn1.Close() + cleanup1() + + // Start a new server against the same cluster. + addr2, cleanup2 := startTernTestServer(t) + defer cleanup2() + conn2 := dial(t, addr2) + defer conn2.Close() + + xid = 1 // reset xid for new connection + + // Use the file handle from the first server — should still work. + got, _ := readFileData(t, conn2, &xid, fh, 0, 4096) + if string(got) != string(testData) { + t.Fatalf("READ after restart = %q, want %q", got, testData) + } + + // Verify GETATTR also works with the old handle. + size := getAttrSize(t, conn2, &xid, fh) + if size != uint64(len(testData)) { + t.Fatalf("GETATTR size after restart = %d, want %d", size, len(testData)) + } + + // Also verify LOOKUP gives same handle. + fh2 := lookupFH(t, conn2, &xid, "persist.txt") + if string(fh) != string(fh2) { + t.Fatalf("file handle changed after restart: %x → %x", fh, fh2) + } + + clientid = setupClient(t, conn2, &xid) + _ = clientid + cleanupViaNFS(t, conn2, &xid, "persist.txt") +} + +// --- READDIR pagination: enough entries to force multiple batches --- + +func TestTernReaddirPagination(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create 30 files — forces at least 2 batches (batch size is 16). + var expected []string + for i := 0; i < 30; i++ { + name := fmt.Sprintf("pg_%03d.txt", i) + createFileViaNFS(t, conn, &xid, clientid, name, []byte(fmt.Sprintf("data-%d", i))) + expected = append(expected, name) + } + sort.Strings(expected) + + // Get root FH for paginated readdir. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Getfh() + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) + rootFH := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + + names := collectTernReaddirNames(t, conn, &xid, rootFH) + + // Filter to only our test files (other tests may have left .nfs dir). + var filtered []string + for _, n := range names { + if len(n) >= 3 && n[:3] == "pg_" { + filtered = append(filtered, n) + } + } + sort.Strings(filtered) + + if len(filtered) != len(expected) { + t.Fatalf("got %d entries, want %d: %v", len(filtered), len(expected), filtered) + } + for i := range expected { + if filtered[i] != expected[i] { + t.Fatalf("entry[%d] = %q, want %q", i, filtered[i], expected[i]) + } + } + + for _, name := range expected { + cleanupViaNFS(t, conn, &xid, name) + } +} + +// --- Nested directories + LOOKUPP --- + +func TestTernNestedDirectories(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create a/b/c directory hierarchy. + mkdirViaNFS(t, conn, &xid, "nest_a") + + // Create "nest_a/b" — need to LOOKUP nest_a first, then CREATE inside it. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("nest_a")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + cw := w.AppendArgarray_Create() + cw.SetObjtype_Nf4dir() + nameW := cw.StartObjname() + buf = nameW.SetData([]byte("b")).Finish() + cw.Resume(buf) + faw := cw.StartCreateattrs() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + cw.Resume(buf) + buf = cw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP + createRes := nextOp(t, &iter).Value().AsCREATE4resEntry() + if createRes.Disc() != NFS4_OK { + t.Fatalf("CREATE nest_a/b status = %s", Nfsstat4Name(createRes.Disc())) + } + + // Create a file inside nest_a/b via OPEN. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + for _, name := range []string{"nest_a", "b"} { + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte(name)).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + } + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("deep.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP nest_a + nextOp(t, &iter) // LOOKUP b + openOK := nextOp(t, &iter).Value().AsOPEN4resEntry().Value().AsOPEN4resok() + stateid := openOK.Stateid() + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // Write data. + deepData := []byte("deep nested content") + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + copyStateid(ww.Stateid(), stateid) + ww = ww.SetOffset(0) + ww = ww.SetStable(2) + ww = ww.SetData(deepData) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // CLOSE. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + xid++ + expectOK(t, res) + + // Navigate root → nest_a → b → deep.txt and read. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + for _, name := range []string{"nest_a", "b", "deep.txt"} { + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte(name)).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + } + rw := w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) + rw.SetOffset(0) + rw.SetCount(4096) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP nest_a + nextOp(t, &iter) // LOOKUP b + nextOp(t, &iter) // LOOKUP deep.txt + readOK := nextOp(t, &iter).Value().AsREAD4resEntry().Value().AsREAD4resok() + if string(readOK.Data()) != string(deepData) { + t.Fatalf("deep READ = %q, want %q", readOK.Data(), deepData) + } + + // LOOKUPP from b should go back to nest_a, then LOOKUPP again to root. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("nest_a")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + lw = w.AppendArgarray_Lookup() + nw = lw.StartObjname() + buf = nw.SetData([]byte("b")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + w.AppendArgarray_Lookupp() // b → nest_a + w.AppendArgarray_Lookupp() // nest_a → root + + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(1 << FATTR4_TYPE) + buf = bw.Finish() + gw.Resume(buf) + buf = gw.Finish() + w.Resume(buf) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP nest_a + nextOp(t, &iter) // LOOKUP b + nextOp(t, &iter) // LOOKUPP → nest_a + nextOp(t, &iter) // LOOKUPP → root + attrData := getAttrData(t, nextOp(t, &iter).Value().AsGETATTR4resEntry().Value().AsGETATTR4resok()) + if len(attrData) < 4 { + t.Fatal("attr data too short") + } + ftype := binary.BigEndian.Uint32(attrData[:4]) + if ftype != uint32(NF4DIR) { + t.Fatalf("LOOKUPP result type = %d, want NF4DIR", ftype) + } + + // Cleanup: remove deep.txt, b, nest_a. + // Remove deep.txt from nest_a/b. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + for _, name := range []string{"nest_a", "b"} { + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte(name)).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + } + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf := tw.SetData([]byte("deep.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // Remove b from nest_a. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("nest_a")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf = tw.SetData([]byte("b")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + cleanupViaNFS(t, conn, &xid, "nest_a") +} + +// --- OPEN existing file for write → NFS4ERR_PERM --- + +func TestTernOpenExistingForWriteRejected(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create a file first. + createFileViaNFS(t, conn, &xid, clientid, "existing.txt", []byte("original content")) + + // Try to OPEN existing file for write (NOCREATE). + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_WRITE) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf := ownerW.Finish() + ow.Resume(buf) + ow.SetOpenhow_Default(OPEN4_NOCREATE) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("existing.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + }) + xid++ + + if res.Status() != NFS4ERR_PERM { + t.Fatalf("OPEN existing for write: got status %s, want NFS4ERR_PERM", + Nfsstat4Name(res.Status())) + } + + cleanupViaNFS(t, conn, &xid, "existing.txt") +} + +// --- Remove non-empty directory → NFS4ERR_NOTEMPTY --- + +func TestTernRemoveNonEmptyDir(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + mkdirViaNFS(t, conn, &xid, "notempty_dir") + + // Create a file inside the directory. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("notempty_dir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf = ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("child.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP + openOK := nextOp(t, &iter).Value().AsOPEN4resEntry().Value().AsOPEN4resok() + stateid := openOK.Stateid() + childFH := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // CLOSE the file. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(childFH).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + xid++ + expectOK(t, res) + + // Try to remove non-empty directory. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf := tw.SetData([]byte("notempty_dir")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + if res.Status() != NFS4ERR_NOTEMPTY { + t.Fatalf("REMOVE non-empty dir: got status %s, want NFS4ERR_NOTEMPTY", + Nfsstat4Name(res.Status())) + } + + // Clean up: remove child, then directory. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("notempty_dir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf = tw.SetData([]byte("child.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + cleanupViaNFS(t, conn, &xid, "notempty_dir") +} + +// --- COMMIT test --- + +func TestTernCommit(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // OPEN CREATE + GETFH + OPEN_CONFIRM + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf := ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("commit.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + w.AppendArgarray_Getfh() + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + openOK := nextOp(t, &iter).Value().AsOPEN4resEntry().Value().AsOPEN4resok() + stateid := openOK.Stateid() + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + // WRITE with UNSTABLE4. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + copyStateid(ww.Stateid(), stateid) + ww = ww.SetOffset(0) + ww = ww.SetStable(0) // UNSTABLE4 + ww = ww.SetData([]byte("commit test data")) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + // COMMIT — should succeed and return a non-zero verifier. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + w.AppendArgarray_Commit() + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + commitOk := nextOp(t, &iter).Value().AsCOMMIT4resEntry().Value().AsCOMMIT4resok() + verf := commitOk.Writeverf() + allZero := true + for i := 0; i < 8; i++ { + if verf.Data(i) != 0 { + allZero = false + break + } + } + if allZero { + t.Fatal("COMMIT write verifier should be non-zero") + } + + // CLOSE. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + xid++ + expectOK(t, res) + + cleanupViaNFS(t, conn, &xid, "commit.txt") +} + +// --- SETATTR: set mtime --- + +func TestTernSetattr(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + createFileViaNFS(t, conn, &xid, clientid, "setattr.txt", []byte("setattr test")) + fh := lookupFH(t, conn, &xid, "setattr.txt") + + // Set mtime to 2020-01-01 00:00:00 UTC. + targetTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + targetSec := targetTime.Unix() + + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + saw := w.AppendArgarray_Setattr() + saw.Stateid().SetSeqid(0) + faw := saw.StartObjAttributes() + bmW := faw.StartAttrmask() + bmW.AppendData(0) // word 0: nothing + bmW.AppendData(1 << (FATTR4_TIME_MODIFY_SET - 32)) + buf = bmW.Finish() + faw.Resume(buf) + attrData := make([]byte, 4+8+4) + binary.BigEndian.PutUint32(attrData[0:4], SET_TO_CLIENT_TIME4) + binary.BigEndian.PutUint64(attrData[4:12], uint64(targetSec)) + binary.BigEndian.PutUint32(attrData[12:16], 0) + alW := faw.StartAttrVals() + buf = alW.SetData(attrData).Finish() + faw.Resume(buf) + buf = faw.Finish() + saw.Resume(buf) + buf = saw.Finish() + w.Resume(buf) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTFH + entry := nextOp(t, &iter) + if entry.Value().AsSETATTR4res().Status() != NFS4_OK { + t.Fatalf("SETATTR status = %s", Nfsstat4Name(entry.Value().AsSETATTR4res().Status())) + } + + // Verify mtime via GETATTR. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + + gw := w.AppendArgarray_Getattr() + bw := gw.StartAttrRequest() + bw.AppendData(0) // word 0 + bw.AppendData(1 << (FATTR4_TIME_MODIFY - 32)) + buf = bw.Finish() + gw.Resume(buf) + buf = gw.Finish() + w.Resume(buf) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTFH + attrBytes := getAttrData(t, nextOp(t, &iter).Value().AsGETATTR4resEntry().Value().AsGETATTR4resok()) + if len(attrBytes) < 12 { + t.Fatal("attr data too short for time_modify") + } + gotSec := int64(binary.BigEndian.Uint64(attrBytes[:8])) + if gotSec != targetSec { + t.Fatalf("mtime seconds = %d, want %d", gotSec, targetSec) + } + + cleanupViaNFS(t, conn, &xid, "setattr.txt") +} + +// --- File replacement: delete + recreate same name --- + +func TestTernFileReplacement(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + // Create file with original content. + createFileViaNFS(t, conn, &xid, clientid, "replace.txt", []byte("version 1")) + + // Verify original. + fh1 := lookupFH(t, conn, &xid, "replace.txt") + got, _ := readFileData(t, conn, &xid, fh1, 0, 4096) + if string(got) != "version 1" { + t.Fatalf("original data = %q, want %q", got, "version 1") + } + + // Delete and recreate with new content. + cleanupViaNFS(t, conn, &xid, "replace.txt") + createFileViaNFS(t, conn, &xid, clientid, "replace.txt", []byte("version 2")) + + // Verify replacement. + fh2 := lookupFH(t, conn, &xid, "replace.txt") + got, _ = readFileData(t, conn, &xid, fh2, 0, 4096) + if string(got) != "version 2" { + t.Fatalf("replacement data = %q, want %q", got, "version 2") + } + + // Old handle should no longer work (file was deleted and a new inode created). + if string(fh1) == string(fh2) { + // If handles happen to be the same (unlikely), we can't test staleness. + t.Log("handles are identical (inode reuse), skipping stale handle check") + } + + cleanupViaNFS(t, conn, &xid, "replace.txt") +} + +// --- Cross-directory rename --- + +func TestTernCrossDirectoryRename(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + mkdirViaNFS(t, conn, &xid, "srcdir") + mkdirViaNFS(t, conn, &xid, "dstdir") + + // Create a file in srcdir. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("srcdir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + ow := w.AppendArgarray_Open() + ow.SetSeqid(1) + ow.SetShareAccess(OPEN4_SHARE_ACCESS_BOTH) + ow.SetShareDeny(OPEN4_SHARE_DENY_NONE) + ownerW := ow.StartOwner() + ownerW = ownerW.SetClientid(clientid) + ownerW = ownerW.SetOwner([]byte("nfstest")) + buf = ownerW.Finish() + ow.Resume(buf) + chw := ow.SetOpenhow_Create() + faw := chw.SetValue_Unchecked4() + bmW := faw.StartAttrmask() + buf = bmW.Finish() + faw.Resume(buf) + alW := faw.StartAttrVals() + buf = alW.SetData(nil).Finish() + faw.Resume(buf) + buf = faw.Finish() + chw.Resume(buf) + buf = chw.Finish() + ow.Resume(buf) + cw := ow.SetClaim_Null() + buf = cw.SetData([]byte("moved.txt")).Finish() + ow.Resume(buf) + buf = ow.Finish() + w.Resume(buf) + + w.AppendArgarray_Getfh() + ocw := w.AppendArgarray_OpenConfirm() + ocw.OpenStateid().SetSeqid(1) + ocw.SetSeqid(2) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP srcdir + openOK := nextOp(t, &iter).Value().AsOPEN4resEntry().Value().AsOPEN4resok() + stateid := openOK.Stateid() + fh := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + nextOp(t, &iter) // OPEN_CONFIRM + + movedData := []byte("cross-dir rename data") + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + ww := w.AppendArgarray_Write() + copyStateid(ww.Stateid(), stateid) + ww = ww.SetOffset(0) + ww = ww.SetStable(2) + ww = ww.SetData(movedData) + buf = ww.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + pfW := w.AppendArgarray_Putfh() + buf := pfW.StartObject().SetData(fh).Finish() + pfW.Resume(buf) + buf = pfW.Finish() + w.Resume(buf) + caw := w.AppendArgarray_Close() + caw.SetSeqid(3) + copyStateid(caw.OpenStateid(), stateid) + }) + xid++ + expectOK(t, res) + + // RENAME: PUTROOTFH + LOOKUP(srcdir) + SAVEFH + PUTROOTFH + LOOKUP(dstdir) + RENAME + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("srcdir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + w.AppendArgarray_Savefh() + + w.AppendArgarray_Putrootfh() + lw = w.AppendArgarray_Lookup() + nw = lw.StartObjname() + buf = nw.SetData([]byte("dstdir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + + rw := w.AppendArgarray_Rename() + oldW := rw.StartOldname() + buf = oldW.SetData([]byte("moved.txt")).Finish() + rw.Resume(buf) + newW := rw.StartNewname() + buf = newW.SetData([]byte("arrived.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP srcdir + nextOp(t, &iter) // SAVEFH + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP dstdir + renameRes := nextOp(t, &iter).Value().AsRENAME4resEntry() + if renameRes.Disc() != NFS4_OK { + t.Fatalf("RENAME status = %s", Nfsstat4Name(renameRes.Disc())) + } + + // Verify file is in dstdir. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("dstdir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + lw = w.AppendArgarray_Lookup() + nw = lw.StartObjname() + buf = nw.SetData([]byte("arrived.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + rw := w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) + rw.SetOffset(0) + rw.SetCount(4096) + }) + xid++ + iter = expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP dstdir + nextOp(t, &iter) // LOOKUP arrived.txt + readOK := nextOp(t, &iter).Value().AsREAD4resEntry().Value().AsREAD4resok() + if string(readOK.Data()) != string(movedData) { + t.Fatalf("cross-dir rename data = %q, want %q", readOK.Data(), movedData) + } + + // Cleanup. + res = sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("dstdir")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + rw := w.AppendArgarray_Remove() + tw := rw.StartTarget() + buf = tw.SetData([]byte("arrived.txt")).Finish() + rw.Resume(buf) + buf = rw.Finish() + w.Resume(buf) + }) + xid++ + expectOK(t, res) + cleanupViaNFS(t, conn, &xid, "srcdir") + cleanupViaNFS(t, conn, &xid, "dstdir") +} + +// --- Concurrent writers on separate files --- + +func TestTernConcurrentWrites(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + + const numClients = 4 + var wg sync.WaitGroup + errors := make(chan error, numClients) + + for i := 0; i < numClients; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupNamedClient(t, conn, &xid, fmt.Sprintf("concurrent-%d", idx)) + + name := fmt.Sprintf("concurrent_%d.txt", idx) + data := []byte(fmt.Sprintf("data from client %d, with some padding to make it non-trivial", idx)) + + fh := createFileViaNFS(t, conn, &xid, clientid, name, data) + + // Read back and verify. + got, _ := readFileData(t, conn, &xid, fh, 0, 4096) + if string(got) != string(data) { + errors <- fmt.Errorf("client %d: got %q, want %q", idx, got, data) + return + } + }(i) + } + + wg.Wait() + close(errors) + for err := range errors { + t.Fatal(err) + } + + // Cleanup. + conn := dial(t, addr) + defer conn.Close() + xid := uint32(1) + for i := 0; i < numClients; i++ { + cleanupViaNFS(t, conn, &xid, fmt.Sprintf("concurrent_%d.txt", i)) + } +} + +// --- .nfs directory should be hidden in READDIR --- + +func TestTernNfsDirHidden(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + + // Get root FH. + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + w.AppendArgarray_Getfh() + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) + rootFH := append([]byte(nil), nextOp(t, &iter).Value().AsGETFH4resEntry().Value().AsGETFH4resok().Object().Data()...) + + names := collectTernReaddirNames(t, conn, &xid, rootFH) + for _, n := range names { + if n == ".nfs" { + t.Fatal(".nfs directory should be hidden in READDIR") + } + } +} + +// --- Lookup non-existent file → NFS4ERR_NOENT --- + +func TestTernLookupNonExistent(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("nonexistent-file-12345.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + }) + xid++ + + if res.Status() != NFS4ERR_NOENT { + t.Fatalf("LOOKUP non-existent: got status %s, want NFS4ERR_NOENT", + Nfsstat4Name(res.Status())) + } +} + +// --- Empty file creation and read --- + +func TestTernCreateEmptyFile(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + fh := createFileViaNFS(t, conn, &xid, clientid, "empty.txt", nil) + + // Read — should get 0 bytes with EOF. + got, eof := readFileData(t, conn, &xid, fh, 0, 4096) + if len(got) != 0 { + t.Fatalf("empty file READ returned %d bytes", len(got)) + } + if !eof { + t.Fatal("expected EOF on empty file") + } + + // GETATTR size should be 0. + size := getAttrSize(t, conn, &xid, fh) + if size != 0 { + t.Fatalf("empty file size = %d, want 0", size) + } + + cleanupViaNFS(t, conn, &xid, "empty.txt") +} + +// --- Multiple operations in single COMPOUND --- + +func TestTernCompoundChaining(t *testing.T) { + addr, cleanup := startTernTestServer(t) + defer cleanup() + conn := dial(t, addr) + defer conn.Close() + + xid := uint32(1) + clientid := setupClient(t, conn, &xid) + + createFileViaNFS(t, conn, &xid, clientid, "chain-a.txt", []byte("aaa")) + createFileViaNFS(t, conn, &xid, clientid, "chain-b.txt", []byte("bbb")) + + // Single compound: PUTROOTFH + LOOKUP(a) + READ + PUTROOTFH + LOOKUP(b) + READ + res := sendCompound(t, conn, xid, func(w *COMPOUND4argsWriter) { + w.AppendArgarray_Putrootfh() + lw := w.AppendArgarray_Lookup() + nw := lw.StartObjname() + buf := nw.SetData([]byte("chain-a.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + rw := w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) + rw.SetOffset(0) + rw.SetCount(4096) + + w.AppendArgarray_Putrootfh() + lw = w.AppendArgarray_Lookup() + nw = lw.StartObjname() + buf = nw.SetData([]byte("chain-b.txt")).Finish() + lw.Resume(buf) + buf = lw.Finish() + w.Resume(buf) + rw = w.AppendArgarray_Read() + rw.Stateid().SetSeqid(0) + rw.SetOffset(0) + rw.SetCount(4096) + }) + xid++ + iter := expectOK(t, res) + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP a + readA := nextOp(t, &iter).Value().AsREAD4resEntry().Value().AsREAD4resok() + if string(readA.Data()) != "aaa" { + t.Fatalf("file a = %q, want %q", readA.Data(), "aaa") + } + nextOp(t, &iter) // PUTROOTFH + nextOp(t, &iter) // LOOKUP b + readB := nextOp(t, &iter).Value().AsREAD4resEntry().Value().AsREAD4resok() + if string(readB.Data()) != "bbb" { + t.Fatalf("file b = %q, want %q", readB.Data(), "bbb") + } + + cleanupViaNFS(t, conn, &xid, "chain-a.txt") + cleanupViaNFS(t, conn, &xid, "chain-b.txt") +}