Documentation
¶
Overview ¶
Package wire is the SFTP version 3 codec: packet framing, message types, and the SSH binary encoding they are built from.
Why this is its own package ¶
It knows nothing about filesystems, servers, or SSH transports. It depends on nothing but the standard library. That is deliberate and it is a structural commitment, not a stylistic one: an SFTP *client* speaks exactly these bytes, and a client belongs somewhere else entirely (a `sftp://` source for a downloader, say). Keeping the codec free of any reference to github.com/go-filesystems/interface or to the server package means moving it is a move, not a rewrite.
The same split is why github.com/go-filesystems/nfs keeps `xdr` and `rpc` out of its server package.
The encoding ¶
SFTP is layered on the SSH binary encoding of RFC 4251 §5, not on XDR, and the difference matters in exactly one place: a string is a 4-byte big-endian length followed by that many bytes with *no padding*. Everything is big-endian. There are no alignment rules, so there is no padding to validate — the one XDR hazard this codec does not have.
The protocol version ¶
This implements draft-ietf-secsh-filexfer-02, "SFTP version 3". Later drafts exist (4 through 6, and the version 6 draft is what the IETF stopped at) but essentially nothing speaks them: OpenSSH's client and server, FileZilla, WinSCP, Cyberduck, libssh, JSch, paramiko and Go's own github.com/pkg/sftp all negotiate 3. Implementing a version nobody speaks would be a way to be correct and unreachable at the same time.
Hostile input ¶
Every byte a Decoder reads came off a network. The decoder therefore never allocates on a length it has not first compared against a ceiling — a 4 GiB length prefix on a 40-byte packet costs one comparison — and every method returns an error rather than panicking, because a panic in a per-connection goroutine takes down every other session in the process.
Index ¶
- Constants
- Variables
- func ReadPacket(r io.Reader, buf []byte, max int) (typ uint8, payload, next []byte, err error)
- func Send(w io.Writer, m Message, buf []byte) ([]byte, error)
- func WritePacket(w io.Writer, typ uint8, payload []byte, out []byte) ([]byte, error)
- type Attributes
- type AttrsReply
- type DataReply
- type Decoder
- func (d *Decoder) Blob() ([]byte, error)
- func (d *Decoder) Byte() (uint8, error)
- func (d *Decoder) End() error
- func (d *Decoder) Int64() (int64, error)
- func (d *Decoder) Remaining() int
- func (d *Decoder) SetLimit(n int)
- func (d *Decoder) String() (string, error)
- func (d *Decoder) Uint32() (uint32, error)
- func (d *Decoder) Uint64() (uint64, error)
- type Encoder
- type ExtendedPair
- type ExtendedReply
- type ExtendedRequest
- type FsetstatRequest
- type HandleReply
- type HandleRequest
- type InitRequest
- type Message
- type NameItem
- type NameReply
- type OpenRequest
- type PathRequest
- type ReadRequest
- type RenameRequest
- type SetstatRequest
- type Status
- type StatusReply
- type SymlinkRequest
- type VersionReply
- type WriteRequest
Constants ¶
const ( FxpInit uint8 = 1 FxpVersion uint8 = 2 FxpOpen uint8 = 3 FxpClose uint8 = 4 FxpRead uint8 = 5 FxpWrite uint8 = 6 FxpLstat uint8 = 7 FxpFstat uint8 = 8 FxpSetstat uint8 = 9 FxpFsetstat uint8 = 10 FxpOpendir uint8 = 11 FxpReaddir uint8 = 12 FxpRemove uint8 = 13 FxpMkdir uint8 = 14 FxpRmdir uint8 = 15 FxpRealpath uint8 = 16 FxpStat uint8 = 17 FxpRename uint8 = 18 FxpReadlink uint8 = 19 FxpSymlink uint8 = 20 FxpStatus uint8 = 101 FxpHandle uint8 = 102 FxpData uint8 = 103 FxpName uint8 = 104 FxpAttrs uint8 = 105 FxpExtended uint8 = 200 FxpExtendedReply uint8 = 201 )
Packet types (draft-ietf-secsh-filexfer-02 §3, §4).
The numbering has a shape worth knowing when reading a hex dump: 1..20 are requests, 101..105 are replies, and 200/201 are the extension escape hatch.
const ( FxfRead uint32 = 0x00000001 FxfWrite uint32 = 0x00000002 FxfAppend uint32 = 0x00000004 FxfCreat uint32 = 0x00000008 FxfTrunc uint32 = 0x00000010 FxfExcl uint32 = 0x00000020 )
Open flags (SSH_FXF_*), the pflags field of SSH_FXP_OPEN.
const ( AttrSize uint32 = 0x00000001 AttrUIDGID uint32 = 0x00000002 AttrPermissions uint32 = 0x00000004 AttrACModTime uint32 = 0x00000008 AttrExtended uint32 = 0x80000000 )
Attribute presence flags (SSH_FILEXFER_ATTR_*).
Every field of an ATTRS structure is optional and announced by one of these. That is the whole reason Attributes carries a Flags field rather than being a plain struct of values: "size 0" and "size not stated" are different answers, and conflating them is how a client comes to believe a file is empty.
const ( ModeFmt uint32 = 0o170000 ModeFifo uint32 = 0o010000 ModeChar uint32 = 0o020000 ModeDir uint32 = 0o040000 ModeBlock uint32 = 0o060000 ModeReg uint32 = 0o100000 ModeLink uint32 = 0o120000 ModeSock uint32 = 0o140000 // ModePerm masks the permission and setuid/setgid/sticky bits. ModePerm uint32 = 0o7777 )
POSIX file-type bits, as they appear in the permissions field of an ATTRS structure.
SFTP version 3 has no separate file-type field: a client learns that something is a directory by masking these out of the permissions, which is why a server that sends permissions without type bits produces a listing where nothing is a directory.
const DefaultLimit = 256 << 10
DefaultLimit is the ceiling applied to a single variable-length item when a Decoder has no explicit one.
256 KiB is comfortably above the largest thing version 3 legitimately carries: OpenSSH's client reads and writes in 32 KiB chunks and its largest is 256 KiB, and a path cannot sensibly approach either. Anything larger is malformed or hostile.
const MaxPacket = 260 << 10
MaxPacket is the largest SFTP packet this codec will read or write, counting the type byte and the payload but not the 4-byte length prefix.
The value is not arbitrary. OpenSSH's sftp client sends SSH_FXP_WRITE payloads of 32 KiB by default and can be pushed to 256 KiB with -B; the prefix and the fixed fields around the data add well under 4 KiB. 260 KiB therefore accepts everything a real client sends and rejects everything else before allocating for it.
const Version uint32 = 3
Version is the SFTP protocol version this codec implements: draft-ietf-secsh-filexfer-02. See the package documentation for why 3 and not a later draft.
Variables ¶
var ( // ErrShort reports that the buffer ended in the middle of an item. ErrShort = errors.New("wire: buffer too short") // ErrLimit reports a length prefix larger than the ceiling in force. // It is returned *before* any allocation, which is the entire point: // an attacker's 4 GiB length prefix must cost a comparison, not 4 GiB. ErrLimit = errors.New("wire: length exceeds limit") // ErrTrailing reports bytes left over after a message decoded. A // well-formed peer sends none, and accepting them would let two // implementations disagree about what they just exchanged while both // believe they succeeded. ErrTrailing = errors.New("wire: trailing bytes after message") )
Errors returned by a Decoder.
They are deliberately few: a server turns any of them into the single wire answer SSH_FX_BAD_MESSAGE, so splitting "ran out of bytes" from "length prefix is absurd" buys a caller nothing and costs a reader a branch to think about. They are distinct types only so a *test* can tell which rejection it provoked.
var ErrPacketEmpty = errors.New("wire: empty packet")
ErrPacketEmpty reports a packet whose length prefix is zero, which leaves no room for the mandatory type byte.
var ErrPacketTooLarge = errors.New("wire: packet too large")
ErrPacketTooLarge reports a length prefix above the ceiling in force.
It is checked before the body is read, so a hostile 4 GiB prefix costs one comparison rather than 4 GiB of buffer and a stalled connection. The connection is not recoverable afterwards — the stream is now misframed — so a server that sees this must close, not reply.
Functions ¶
func ReadPacket ¶
ReadPacket reads one packet from r, reusing buf when it is large enough.
It returns the packet type, the payload (which aliases buf and is valid only until the next call), and the possibly-grown buffer for the caller to pass back in. Reusing the buffer is what keeps a bulk transfer from allocating once per 32 KiB chunk.
max caps the packet; a non-positive value means MaxPacket.
io.EOF is returned unwrapped when the stream ends cleanly at a packet boundary, which is how a client says goodbye; a stream that ends *inside* a packet yields io.ErrUnexpectedEOF, and the difference is exactly the one a caller needs to decide whether to log an error.
func WritePacket ¶
WritePacket frames payload as a packet of the given type and writes it to w in a single Write call.
The single call matters: SFTP runs inside an SSH channel, and splitting a packet across two writes turns into two SSH_MSG_CHANNEL_DATA messages, each with its own MAC and its own round of framing. It is also what makes the writer safe to reason about when a caller serialises access with one mutex.
out is a scratch buffer; the possibly-grown buffer is returned for reuse.
Types ¶
type Attributes ¶
type Attributes struct {
// Flags is the bitmask of ATTR_* constants saying which fields below
// carry meaning.
Flags uint32
// Size is the file length in bytes. Valid when AttrSize is set.
Size uint64
// UID and GID are the numeric owner ids. Valid when AttrUIDGID is set.
UID, GID uint32
// Permissions carries both the POSIX type bits and the mode bits.
// Version 3 has no separate file-type field, so the type bits here are
// the *only* way a client learns that something is a directory. Valid
// when AttrPermissions is set.
Permissions uint32
// Atime and Mtime are seconds since the Unix epoch. Version 3 states
// them as 32-bit, which runs out in 2106; later drafts widened the
// field, and this codec does not get to fix that on its own.
// Valid when AttrACModTime is set.
Atime, Mtime uint32
// Extended carries vendor extensions as (type, data) pairs. Valid when
// AttrExtended is set. Nothing here produces any; they are decoded
// rather than dropped so that a request carrying one round-trips
// truthfully instead of being silently truncated.
Extended []ExtendedPair
}
Attributes is an SFTP version 3 ATTRS structure (draft-ietf-secsh-filexfer-02 §5).
Every field is optional and its presence is announced in Flags. That is why this is not a plain struct of values: "size 0" and "size not stated" are different answers, and a decoder that conflates them tells a client a file is empty when the server never claimed anything of the sort. Read a field only after checking its flag; write one only together with setting it.
func DecodeAttributes ¶
func DecodeAttributes(d *Decoder) (Attributes, error)
DecodeAttributes reads an ATTRS structure.
func (Attributes) Encode ¶
func (a Attributes) Encode(e *Encoder)
Encode writes the ATTRS structure.
func (Attributes) IsDir ¶
func (a Attributes) IsDir() bool
IsDir reports whether the permissions say directory. It answers false when permissions were not stated at all, which is the safe direction: a client that wrongly believes something is a directory will try to enumerate it.
func (Attributes) IsLink ¶
func (a Attributes) IsLink() bool
IsLink reports whether the permissions say symbolic link, on the same terms as Attributes.IsDir.
type AttrsReply ¶
type AttrsReply struct {
ID uint32
Attrs Attributes
}
AttrsReply is SSH_FXP_ATTRS: the answer to STAT, LSTAT and FSTAT.
func DecodeAttrs ¶
func DecodeAttrs(payload []byte) (AttrsReply, error)
DecodeAttrs reads an SSH_FXP_ATTRS payload.
func (AttrsReply) EncodePayload ¶
func (m AttrsReply) EncodePayload(e *Encoder)
EncodePayload implements Message.
type DataReply ¶
DataReply is SSH_FXP_DATA, the answer to READ.
func DecodeData ¶
DecodeData reads an SSH_FXP_DATA payload.
func (DataReply) EncodePayload ¶
EncodePayload implements Message.
type Decoder ¶
type Decoder struct {
// contains filtered or unexported fields
}
Decoder reads SSH-encoded values from a byte slice.
Every method returns an error rather than panicking: the bytes come from the network, and a panic in a per-connection goroutine would take down every other session the process is serving.
func NewDecoder ¶
NewDecoder returns a Decoder reading buf with DefaultLimit.
func (*Decoder) Blob ¶
Blob decodes a variable-length string as bytes.
The result is a copy. The Decoder's buffer is a reusable per-connection read buffer, so handing out an alias would let the next request rewrite data the caller is still holding — a bug that shows up as one client's filename appearing in another's reply, long after the code that caused it.
func (*Decoder) End ¶
End reports ErrTrailing if anything is left undecoded, and nil otherwise. Message decoders call it last so that a peer sending extra bytes is caught rather than silently tolerated.
func (*Decoder) SetLimit ¶
SetLimit caps the accepted length of any single variable-length item. A non-positive value restores DefaultLimit.
func (*Decoder) String ¶
String decodes a variable-length string.
The bytes are returned as-is, with no validation of UTF-8 or of path syntax. That is deliberate: this layer's job is framing. Whether a filename may contain a slash is an SFTP question, answered once, in the server's path code — answering it here would silently change what the layer above thinks it received.
type Encoder ¶
type Encoder struct {
// contains filtered or unexported fields
}
Encoder accumulates SSH-encoded values in a buffer.
No method returns an error, and that is a design choice with a consequence worth stating: every input is already a valid Go value of a type the encoding can represent, and the buffer grows, so encoding genuinely cannot fail. Reply-building code is therefore free of error checks on a path where nothing can go wrong, which is what keeps the message constructors readable.
func NewEncoder ¶
NewEncoder returns an Encoder appending to buf, whose existing contents are discarded. Passing a recycled buffer with spare capacity avoids an allocation per reply.
func (*Encoder) Blob ¶
Bytes writes a variable-length string: a 4-byte length, then the bytes. There is no padding — this is SSH's encoding, not XDR's.
func (*Encoder) Bytes ¶
Bytes returns the encoded message. The slice aliases the Encoder's buffer and stays valid only until the next write.
func (*Encoder) Int64 ¶
Int64 encodes a signed 64-bit integer. SFTP's signed and unsigned 64-bit values share a representation, so this is a two's-complement reinterpret.
func (*Encoder) String ¶
String writes an SFTP string, which is wire-identical to Encoder.Blob.
Version 3 says nothing about the character set of a filename, and this encoder imposes none: names go out exactly as the filesystem reported them. Guessing at UTF-8 here would corrupt names on the images this is pointed at, where a FAT32 short name is CP437 and an ISO 9660 name is US-ASCII.
type ExtendedPair ¶
type ExtendedPair struct {
Type, Data string
}
ExtendedPair is one vendor-extension attribute.
type ExtendedReply ¶
ExtendedReply is SSH_FXP_EXTENDED_REPLY. Like ExtendedRequest, its body is opaque here.
func DecodeExtendedReply ¶
func DecodeExtendedReply(payload []byte) (ExtendedReply, error)
DecodeExtendedReply reads an SSH_FXP_EXTENDED_REPLY payload.
func (ExtendedReply) EncodePayload ¶
func (m ExtendedReply) EncodePayload(e *Encoder)
EncodePayload implements Message.
type ExtendedRequest ¶
ExtendedRequest is SSH_FXP_EXTENDED, the escape hatch by which vendors add operations. Data is left uninterpreted: what it means depends entirely on Name, and this codec does not pretend to know every vendor's.
func DecodeExtended ¶
func DecodeExtended(payload []byte) (ExtendedRequest, error)
DecodeExtended reads an SSH_FXP_EXTENDED payload. The remainder after the name is returned raw, since its shape is the extension's business.
func (ExtendedRequest) EncodePayload ¶
func (m ExtendedRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type FsetstatRequest ¶
type FsetstatRequest struct {
ID uint32
Handle string
Attrs Attributes
}
FsetstatRequest is SSH_FXP_FSETSTAT: SETSTAT against an open handle.
func DecodeFsetstat ¶
func DecodeFsetstat(payload []byte) (FsetstatRequest, error)
DecodeFsetstat reads an SSH_FXP_FSETSTAT payload.
func (FsetstatRequest) EncodePayload ¶
func (m FsetstatRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type HandleReply ¶
HandleReply is SSH_FXP_HANDLE: the opaque token a client presents for every subsequent operation on what it just opened.
func DecodeHandleReply ¶
func DecodeHandleReply(payload []byte) (HandleReply, error)
DecodeHandleReply reads an SSH_FXP_HANDLE payload.
func (HandleReply) EncodePayload ¶
func (m HandleReply) EncodePayload(e *Encoder)
EncodePayload implements Message.
type HandleRequest ¶
HandleRequest is any request that is an id and a handle: SSH_FXP_CLOSE, SSH_FXP_READDIR and SSH_FXP_FSTAT.
func DecodeHandle ¶
func DecodeHandle(typ uint8, payload []byte) (HandleRequest, error)
DecodeHandle reads the payload of any HandleRequest-shaped message.
func (HandleRequest) EncodePayload ¶
func (m HandleRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type InitRequest ¶
type InitRequest struct {
Version uint32
Extensions []ExtendedPair
}
InitRequest is SSH_FXP_INIT, the first packet a client sends. It has no request id: nothing has been established yet for an id to refer to.
func DecodeInit ¶
func DecodeInit(payload []byte) (InitRequest, error)
DecodeInit reads an SSH_FXP_INIT payload.
Trailing extension pairs are read until the payload is exhausted; unlike every other message there is no count, so "ran out" is the terminator and a half-pair is an error rather than a silent truncation.
func (InitRequest) EncodePayload ¶
func (m InitRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type Message ¶
type Message interface {
// Type returns the SSH_FXP_* number this message is sent as. For the
// shared shapes (PathRequest, HandleRequest) it is a field, because the
// wire format cannot tell you which of the seven it was.
Type() uint8
// EncodePayload writes everything after the type byte.
EncodePayload(e *Encoder)
}
Message is anything that can be sent as an SFTP packet: it knows its own packet type and how to write its payload.
type NameItem ¶
type NameItem struct {
// Filename is the bare name for a READDIR entry, or the resolved path
// for a REALPATH answer.
Filename string
// Longname is a human-readable line in `ls -l` form. Version 3 leaves
// its format entirely unspecified, which means no client may parse it
// and every client displays it — so it is presentation, and the
// authoritative values are in Attrs.
Longname string
Attrs Attributes
}
NameItem is one entry of a NAME reply.
type NameReply ¶
NameReply is SSH_FXP_NAME: the answer to READDIR and to REALPATH.
func DecodeName ¶
DecodeName reads an SSH_FXP_NAME payload.
func (NameReply) EncodePayload ¶
EncodePayload implements Message.
type OpenRequest ¶
type OpenRequest struct {
ID uint32
Path string
PFlags uint32
Attrs Attributes
}
OpenRequest is SSH_FXP_OPEN.
func DecodeOpen ¶
func DecodeOpen(payload []byte) (OpenRequest, error)
DecodeOpen reads an SSH_FXP_OPEN payload.
func (OpenRequest) EncodePayload ¶
func (m OpenRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type PathRequest ¶
PathRequest is any request that is an id and a single path: SSH_FXP_LSTAT, SSH_FXP_STAT, SSH_FXP_OPENDIR, SSH_FXP_REMOVE, SSH_FXP_RMDIR, SSH_FXP_REALPATH and SSH_FXP_READLINK.
PacketType carries which one, because the payload cannot say.
func DecodePath ¶
func DecodePath(typ uint8, payload []byte) (PathRequest, error)
DecodePath reads the payload of any PathRequest-shaped message.
func (PathRequest) EncodePayload ¶
func (m PathRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type ReadRequest ¶
ReadRequest is SSH_FXP_READ: read Length bytes at Offset.
This is the request that maps exactly onto github.com/go-filesystems/interface.File.ReadAt, which is why a driver implementing the optional Opener capability can serve it without reading anything it was not asked for.
func DecodeRead ¶
func DecodeRead(payload []byte) (ReadRequest, error)
DecodeRead reads an SSH_FXP_READ payload.
func (ReadRequest) EncodePayload ¶
func (m ReadRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type RenameRequest ¶
RenameRequest is SSH_FXP_RENAME.
func DecodeRename ¶
func DecodeRename(payload []byte) (RenameRequest, error)
DecodeRename reads an SSH_FXP_RENAME payload.
func (RenameRequest) EncodePayload ¶
func (m RenameRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type SetstatRequest ¶
type SetstatRequest struct {
PacketType uint8
ID uint32
Path string
Attrs Attributes
}
SetstatRequest is SSH_FXP_SETSTAT (by path) or SSH_FXP_MKDIR, both of which are an id, a path and an ATTRS.
func DecodeSetstat ¶
func DecodeSetstat(typ uint8, payload []byte) (SetstatRequest, error)
DecodeSetstat reads an SSH_FXP_SETSTAT or SSH_FXP_MKDIR payload.
func (SetstatRequest) EncodePayload ¶
func (m SetstatRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type Status ¶
type Status uint32
Status is an SSH_FX_* code: the answer carried by an SSH_FXP_STATUS packet.
Version 3 defines exactly the nine below. Later drafts add finer codes (SSH_FX_NO_SUCH_PATH, SSH_FX_DIR_NOT_EMPTY, ...) and it is tempting to send them anyway, but a version-3 client is entitled to treat any code it does not know as a hard failure, and some do. The finer distinction goes in the human-readable message instead, where it costs nothing and reaches the user.
const ( // StatusOK means the operation completed. It is also what a // write-shaped request returns on success, since there is nothing else // to send back. StatusOK Status = 0 // StatusEOF ends a READ or a READDIR. It is not an error: it is the // only way either of those two loops terminates. StatusEOF Status = 1 // StatusNoSuchFile is ENOENT. StatusNoSuchFile Status = 2 // StatusPermissionDenied is EACCES/EPERM, and is what a read-only // export answers to every mutating request. StatusPermissionDenied Status = 3 // StatusFailure is the catch-all. A client shows its message to the // user, so the message is the only place the real cause can go. StatusFailure Status = 4 // StatusBadMessage reports a packet that did not decode. It is the // codec's verdict, not the filesystem's. StatusBadMessage Status = 5 // StatusNoConnection and StatusConnectionLost are client-side codes in // practice; they are defined for completeness of the enum. StatusNoConnection Status = 6 StatusConnectionLost Status = 7 // StatusOpUnsupported reports an operation the server will not do. // Answering it honestly is what lets a client fall back instead of // hanging or believing a lie. StatusOpUnsupported Status = 8 )
The version 3 status codes.
type StatusReply ¶
StatusReply is SSH_FXP_STATUS.
Message is shown to the user by every client worth using, so it is the one place a cause that version 3's nine status codes cannot express still reaches a person. Lang is an RFC 1766 tag and is universally empty.
func DecodeStatus ¶
func DecodeStatus(payload []byte) (StatusReply, error)
DecodeStatus reads an SSH_FXP_STATUS payload.
The message and language fields were added in version 3 and some version-2 era peers omit them, so a payload that ends after the code decodes as a status with an empty message rather than as a protocol error.
func (StatusReply) EncodePayload ¶
func (m StatusReply) EncodePayload(e *Encoder)
EncodePayload implements Message.
type SymlinkRequest ¶
SymlinkRequest is SSH_FXP_SYMLINK.
The field order on the wire is target-then-link, which is the REVERSE of what draft-ietf-secsh-filexfer-02 §6.10 says. That is not a mistake here: OpenSSH's server shipped with the arguments swapped, the error was not noticed until it was deployed everywhere, and every client now sends the swapped order. OpenSSH documents the discrepancy in its own PROTOCOL file, §4.1. Following the draft instead of the deployed reality would create symlinks pointing the wrong way for every real client — the field names below are the truth on the wire, and the order they are written in is why.
func DecodeSymlink ¶
func DecodeSymlink(payload []byte) (SymlinkRequest, error)
DecodeSymlink reads an SSH_FXP_SYMLINK payload.
func (SymlinkRequest) EncodePayload ¶
func (m SymlinkRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.
type VersionReply ¶
type VersionReply struct {
Version uint32
Extensions []ExtendedPair
}
VersionReply is SSH_FXP_VERSION, the server's answer to INIT. It too has no request id.
func DecodeVersion ¶
func DecodeVersion(payload []byte) (VersionReply, error)
DecodeVersion reads an SSH_FXP_VERSION payload. It shares INIT's shape, so it shares its decoder.
func (VersionReply) EncodePayload ¶
func (m VersionReply) EncodePayload(e *Encoder)
EncodePayload implements Message.
type WriteRequest ¶
WriteRequest is SSH_FXP_WRITE: put Data at Offset.
The offset is the hard part of serving this over the current github.com/go-filesystems/interface contract; see the server package's documentation for the cost and its measurement.
func DecodeWrite ¶
func DecodeWrite(payload []byte) (WriteRequest, error)
DecodeWrite reads an SSH_FXP_WRITE payload.
func (WriteRequest) EncodePayload ¶
func (m WriteRequest) EncodePayload(e *Encoder)
EncodePayload implements Message.