use BigEndian.Append funcs and change MarshalSize semantics to include the whole packet
diff --git a/encoding/ssh/filexfer/attrs.go b/encoding/ssh/filexfer/attrs.go
index a1aec30..5054d6d 100644
--- a/encoding/ssh/filexfer/attrs.go
+++ b/encoding/ssh/filexfer/attrs.go
@@ -2,6 +2,7 @@
 
 import (
 	"io/fs"
+	"iter"
 	"path"
 	"time"
 )
@@ -212,29 +213,30 @@
 
 // MarshalSize returns the number of bytes the attributes would marshal into.
 func (a *Attributes) MarshalSize() int {
-	length := 4
+	// uint32(flags)
+	size := 4
 
 	if a.HasSize() {
-		length += 8
+		size += 8 // uint64(size)
 	}
 
 	if a.HasUserGroup() {
-		length += 4 + 4
+		size += 4 + 4 // uint32(uid) + uint32(gid)
 	}
 
 	if a.HasPermissions() {
-		length += 4
+		size += 4 // uint32(permissions)
 	}
 
 	if a.HasACModTime() {
-		length += 4 + 4
+		size += 4 + 4 // uint32(atime) + uint32(mtime)
 	}
 
 	if a.HasExtended() {
-		length += a.Extended.MarshalSize()
+		size += a.Extended.MarshalSize()
 	}
 
-	return length
+	return size
 }
 
 // MarshalInto marshals the attributes onto the end of the buffer.
@@ -266,7 +268,7 @@
 
 // MarshalBinary returns the binary encoding of attributes.
 func (a *Attributes) MarshalBinary() ([]byte, error) {
-	buf := NewBuffer(make([]byte, 0, a.MarshalSize()))
+	buf := NewMarshalBuffer(a.MarshalSize())
 	a.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
@@ -319,18 +321,19 @@
 
 // MarshalSize returns the number of bytes the extended attributes would marshal into.
 func (a ExtendedAttributes) MarshalSize() int {
-	length := 4
+	// uint32(extended_count)
+	size := 4
 
 	for _, ext := range a {
-		length += ext.MarshalSize()
+		size += ext.MarshalSize()
 	}
 
-	return length
+	return size
 }
 
 // MarshalInto marshals the extended attributes onto the end of the buffer.
 func (a ExtendedAttributes) MarshalInto(buf *Buffer) {
-	buf.AppendUint32(uint32(len(a)))
+	buf.AppendCount(len(a))
 
 	for _, ext := range a {
 		ext.MarshalInto(buf)
@@ -339,7 +342,7 @@
 
 // MarshalBinary returns the binary encoding of the extended attributes.
 func (a ExtendedAttributes) MarshalBinary() ([]byte, error) {
-	buf := NewBuffer(make([]byte, 0, a.MarshalSize()))
+	buf := NewMarshalBuffer(a.MarshalSize())
 	a.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
@@ -411,20 +414,24 @@
 	return "", false
 }
 
-// Seq is an iterator that yields the type field from each extended attribute.
-func (a ExtendedAttributes) Seq(yield func(string) bool) {
-	for _, ext := range a {
-		if !yield(ext.Type) {
-			return
+// Types returns an iterator that yields the type field from each extended attribute.
+func (a ExtendedAttributes) Types() iter.Seq[string] {
+	return func(yield func(string) bool) {
+		for _, ext := range a {
+			if !yield(ext.Type) {
+				return
+			}
 		}
 	}
 }
 
-// Seq2 is an iterator that yields the type and data fields from each extended attribute.
-func (a ExtendedAttributes) Seq2(yield func(string, string) bool) {
-	for _, ext := range a {
-		if !yield(ext.Type, ext.Data) {
-			return
+// All returns an iterator that yields the type and data fields from each extended attribute.
+func (a ExtendedAttributes) All() iter.Seq2[string, string] {
+	return func(yield func(string, string) bool) {
+		for _, ext := range a {
+			if !yield(ext.Type, ext.Data) {
+				return
+			}
 		}
 	}
 }
@@ -451,7 +458,7 @@
 
 // MarshalBinary returns the binary encoding of the extended attribute.
 func (e *ExtendedAttribute) MarshalBinary() ([]byte, error) {
-	buf := NewBuffer(make([]byte, 0, e.MarshalSize()))
+	buf := NewMarshalBuffer(e.MarshalSize())
 	e.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
@@ -539,7 +546,7 @@
 
 // MarshalBinary returns the binary encoding of the name entry.
 func (e *NameEntry) MarshalBinary() ([]byte, error) {
-	buf := NewBuffer(make([]byte, 0, e.MarshalSize()))
+	buf := NewMarshalBuffer(e.MarshalSize())
 	e.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
diff --git a/encoding/ssh/filexfer/buffer.go b/encoding/ssh/filexfer/buffer.go
index d2450a6..0d8cec1 100644
--- a/encoding/ssh/filexfer/buffer.go
+++ b/encoding/ssh/filexfer/buffer.go
@@ -38,9 +38,9 @@
 }
 
 // NewMarshalBuffer creates a new buffer ready to start marshaling a Packet into.
-// It preallocates enough space for uint32(length), and size more bytes.
+// It preallocates enough capacity for size bytes.
 func NewMarshalBuffer(size int) *Buffer {
-	return NewBuffer(make([]byte, 4+size))
+	return NewBuffer(make([]byte, 0, size))
 }
 
 // Bytes returns a slice of length b.Len() holding the unconsumed bytes in the buffer.
@@ -131,8 +131,8 @@
 		return 0
 	}
 
-	var v uint8
-	v, b.off = b.b[b.off], b.off+1
+	v := b.b[b.off]
+	b.off++
 	return v
 }
 
@@ -171,10 +171,7 @@
 
 // AppendUint16 appends single uint16 into the buffer, in network byte order (big-endian).
 func (b *Buffer) AppendUint16(v uint16) {
-	b.b = append(b.b,
-		byte(v>>8),
-		byte(v>>0),
-	)
+	b.b = binary.BigEndian.AppendUint16(b.b, v)
 }
 
 // unmarshalPacketLength is used internally to read the packet length.
@@ -198,14 +195,9 @@
 
 // AppendUint32 appends a single uint32 into the buffer, in network byte order (big-endian).
 func (b *Buffer) AppendUint32(v uint32) {
-	b.b = append(b.b,
-		byte(v>>24),
-		byte(v>>16),
-		byte(v>>8),
-		byte(v>>0),
-	)
+	b.b = binary.BigEndian.AppendUint32(b.b, v)
 }
-
+//*/
 // ConsumeCount consumes a single uint32 count from the buffer, in network byte order (big-endian) as an int.
 // If the buffer does not have enough data, it will set Err to ErrShortPacket.
 func (b *Buffer) ConsumeCount() (int, error) {
@@ -232,16 +224,7 @@
 
 // AppendUint64 appends a single uint64 into the buffer, in network byte order (big-endian).
 func (b *Buffer) AppendUint64(v uint64) {
-	b.b = append(b.b,
-		byte(v>>56),
-		byte(v>>48),
-		byte(v>>40),
-		byte(v>>32),
-		byte(v>>24),
-		byte(v>>16),
-		byte(v>>8),
-		byte(v>>0),
-	)
+	b.b = binary.BigEndian.AppendUint64(b.b, v)
 }
 
 // ConsumeInt64 consumes a single int64 from the buffer, in network byte order (big-endian) with two’s complement.
@@ -257,7 +240,8 @@
 
 // ConsumeBytes consumes a single string of raw binary data from the buffer.
 // A string is a uint32 length, followed by that number of raw bytes.
-// If the buffer does not have enough data, it will set Err to ErrShortPacket.
+// If the buffer does not have enough data, it will set Err to ErrShortPacket,
+// and return as much data as is available.
 //
 // The returned slice aliases the buffer contents, and is valid only as long as the buffer is not reused;
 // that is, only until the next call to [Reset], [PutLength], [StartPacket], or [UnmarshalBinary].
@@ -265,18 +249,20 @@
 // In no case will consuming calls return overlapping slice aliases,
 // and append calls are guaranteed to not disturb this slice alias.
 func (b *Buffer) ConsumeBytes() []byte {
-	length := int(b.ConsumeUint32())
+	length, _ := b.ConsumeCount()
 
 	if length == 0 {
-		// Short-circuit empty strings.
-		return nil
-	}
-
-	if !b.checkLen(length) {
+		// Short-circuit empty strings, or errors from ConsumeCount.
 		return nil
 	}
 
 	v := b.b[b.off:]
+
+	if !b.checkLen(length) {
+		// Return whatever was left, this might possibly help with debugging.
+		return slices.Clip(v)
+	}
+
 	if len(v) > length || cap(v) > length {
 		v = slices.Clip(v[:length])
 	}
@@ -311,7 +297,7 @@
 	// uint32(length) + raw(data)
 	b.Grow(4 + len(v)) // ensure at most one allocation
 
-	b.AppendUint32(uint32(len(v)))
+	b.AppendCount(len(v))
 	b.b = append(b.b, v...)
 }
 
@@ -342,8 +328,8 @@
 	binary.BigEndian.PutUint32(b.b, uint32(size))
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the buffer would marshal into.
+// This is the whole size of the buffer, including any uint32(length) that might exist.
 func (b *Buffer) MarshalSize() int {
 	// raw(data)
 	return len(b.b)
@@ -356,7 +342,8 @@
 
 // UnmarshalBinary sets the internal buffer of b to be a clone of data, and zeros the internal offset.
 func (b *Buffer) UnmarshalBinary(data []byte) error {
-	b.b = append(b.b[:0], data...)
-	b.off = 0
+	*b = Buffer{
+		b: append(b.b[:0], data...),
+	}
 	return nil
 }
diff --git a/encoding/ssh/filexfer/buffer_test.go b/encoding/ssh/filexfer/buffer_test.go
new file mode 100644
index 0000000..9f35dc7
--- /dev/null
+++ b/encoding/ssh/filexfer/buffer_test.go
@@ -0,0 +1,21 @@
+package sshfx
+
+import (
+	"testing"
+)
+
+func BenchmarkAppendCount(b *testing.B) {
+	buf := NewBuffer(make([]byte, 0, b.N*4))
+
+	for i := range b.N {
+		buf.AppendCount(i)
+	}
+}
+
+func BenchmarkAppendString(b *testing.B) {
+	buf := NewBuffer(make([]byte, 0, b.N*(4+3)))
+
+	for range b.N {
+		buf.AppendString("foo")
+	}
+}
diff --git a/encoding/ssh/filexfer/extended_packets.go b/encoding/ssh/filexfer/extended_packets.go
index b44b202..d80fb03 100644
--- a/encoding/ssh/filexfer/extended_packets.go
+++ b/encoding/ssh/filexfer/extended_packets.go
@@ -96,10 +96,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *ExtendedPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(extended-request)
-	size := 1 + 4 + 4 + len(p.ExtendedRequest)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(extended-request)
+	size := 4 + 1 + 4 + 4 + len(p.ExtendedRequest)
 	if p.Data != nil {
 		size += p.Data.MarshalSize()
 	}
@@ -158,10 +157,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *ExtendedReplyPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id)
-	size := 1 + 4
+	// uint32(length) + uint8(type) + uint32(request-id)
+	size := 4 + 1 + 4
 	if p.Data != nil {
 		size += p.Data.MarshalSize()
 	}
diff --git a/encoding/ssh/filexfer/extended_packets_test.go b/encoding/ssh/filexfer/extended_packets_test.go
index 9d564fb..e097e5b 100644
--- a/encoding/ssh/filexfer/extended_packets_test.go
+++ b/encoding/ssh/filexfer/extended_packets_test.go
@@ -9,10 +9,18 @@
 	value uint8
 }
 
+func (d *testExtendedData) Type() PacketType {
+	return PacketTypeExtended
+}
+
 func (d *testExtendedData) MarshalSize() int {
 	return 1
 }
 
+func (d *testExtendedData) ExtendedRequest() string {
+	return "bar@example"
+}
+
 func (d *testExtendedData) MarshalBinary() ([]byte, error) {
 	buf := NewBuffer(make([]byte, 0, d.MarshalSize()))
 
@@ -34,6 +42,8 @@
 	return nil
 }
 
+var _ ExtendedData = &testExtendedData{}
+
 var _ Packet = &ExtendedPacket{}
 
 func TestExtendedPacketNoData(t *testing.T) {
@@ -81,7 +91,7 @@
 func TestExtendedPacketTestData(t *testing.T) {
 	const (
 		id              = 42
-		extendedRequest = "foo@example"
+		extendedRequest = "bar@example"
 		textValue       = 13
 	)
 
@@ -109,7 +119,7 @@
 		0x00, 0x00, 0x00, 21,
 		200,
 		0x00, 0x00, 0x00, 42,
-		0x00, 0x00, 0x00, 11, 'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+		0x00, 0x00, 0x00, 11, 'b', 'a', 'r', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
 		0x27,
 	}
 
@@ -117,6 +127,7 @@
 		t.Fatalf("MarshalPacket() = %X, but wanted %X", buf, want)
 	}
 
+	// Even when unregistered, if we give a hint type, it should work.
 	*p = ExtendedPacket{
 		Data: new(testExtendedData),
 	}
@@ -137,6 +148,9 @@
 		t.Errorf("UnmarshalPacketBody(): Data.value was %#x, but expected %#x", buf.value, value)
 	}
 
+	// Test that when rregistered without a hint type, it should work.
+	RegisterExtendedPacketType[testExtendedData]()
+
 	*p = ExtendedPacket{}
 
 	// UnmarshalPacketBody assumes the (length, type, request-id) have already been consumed.
@@ -148,7 +162,88 @@
 		t.Errorf("UnmarshalPacketBody(): ExtendedRequest was %q, but expected %q", p.ExtendedRequest, extendedRequest)
 	}
 
-	wantBuffer := []byte{0x27}
+	if buf, ok := p.Data.(*testExtendedData); !ok {
+		t.Errorf("UnmarshalPacketBody(): Data was type %T, but expected %T", p.Data, buf)
+
+	} else if buf.value != value {
+		t.Errorf("UnmarshalPacketBody(): Data.value was %#x, but expected %#x", buf.value, value)
+	}
+
+	// Test that even registered, a specified data hint will override it.
+	*p = ExtendedPacket{
+		Data: new(Buffer),
+	}
+
+	// UnmarshalPacketBody assumes the (length, type, request-id) have already been consumed.
+	if err := p.UnmarshalPacketBody(NewBuffer(buf[9:])); err != nil {
+		t.Fatal("unexpected error:", err)
+	}
+
+	if p.ExtendedRequest != extendedRequest {
+		t.Errorf("UnmarshalPacketBody(): ExtendedRequest was %q, but expected %q", p.ExtendedRequest, extendedRequest)
+	}
+
+	wantBuffer := []byte{ textValue^0x2a }
+
+	if buf, ok := p.Data.(*Buffer); !ok {
+		t.Errorf("UnmarshalPacketBody(): Data was type %T, but expected %T", p.Data, buf)
+
+	} else if !bytes.Equal(buf.b, wantBuffer) {
+		t.Errorf("UnmarshalPacketBody(): Data was %X, but expected %X", buf.b, wantBuffer)
+	}
+}
+
+func TestExtendedPacketTestBuffer(t *testing.T) {
+	const (
+		id              = 42
+		extendedRequest = "undef@example"
+		textValue       = 13
+	)
+
+	const value = 13
+
+	p := &ExtendedPacket{
+		ExtendedRequest: extendedRequest,
+		Data: &Buffer{
+			b: []byte("\x00\x00\x00\x03bar"),
+		},
+	}
+
+	expectAllocs(t, 2, func() {
+		// header should be allocated with enough space to cover the test data,
+		// but test data will still be separately allocated.
+		_, _ = ComposePacket(p.MarshalPacket(id, nil))
+	})
+
+	buf, err := ComposePacket(p.MarshalPacket(id, nil))
+	if err != nil {
+		t.Fatal("unexpected error:", err)
+	}
+
+	want := []byte{
+		0x00, 0x00, 0x00, 29,
+		200,
+		0x00, 0x00, 0x00, 42,
+		0x00, 0x00, 0x00, 13, 'u', 'n', 'd', 'e', 'f', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+		0x00, 0x00, 0x00, 0x03, 'b', 'a', 'r',
+	}
+
+	if !bytes.Equal(buf, want) {
+		t.Fatalf("MarshalPacket() = %X, but wanted %X", buf, want)
+	}
+
+	*p = ExtendedPacket{}
+
+	// UnmarshalPacketBody assumes the (length, type, request-id) have already been consumed.
+	if err := p.UnmarshalPacketBody(NewBuffer(buf[9:])); err != nil {
+		t.Fatal("unexpected error:", err)
+	}
+
+	if p.ExtendedRequest != extendedRequest {
+		t.Errorf("UnmarshalPacketBody(): ExtendedRequest was %q, but expected %q", p.ExtendedRequest, extendedRequest)
+	}
+
+	wantBuffer := []byte{ 0x00, 0x00, 0x00, 0x03, 'b', 'a', 'r' }
 
 	if buf, ok := p.Data.(*Buffer); !ok {
 		t.Errorf("UnmarshalPacketBody(): Data was type %T, but expected %T", p.Data, buf)
diff --git a/encoding/ssh/filexfer/extensions.go b/encoding/ssh/filexfer/extensions.go
index ccc6765..1c22ff8 100644
--- a/encoding/ssh/filexfer/extensions.go
+++ b/encoding/ssh/filexfer/extensions.go
@@ -11,6 +11,7 @@
 
 // MarshalSize returns the number of bytes e would marshal into.
 func (e *ExtensionPair) MarshalSize() int {
+	// string(name) + string(data)
 	return 4 + len(e.Name) + 4 + len(e.Data)
 }
 
diff --git a/encoding/ssh/filexfer/handle_packets.go b/encoding/ssh/filexfer/handle_packets.go
index c041eda..79458c2 100644
--- a/encoding/ssh/filexfer/handle_packets.go
+++ b/encoding/ssh/filexfer/handle_packets.go
@@ -11,10 +11,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *ClosePacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle)
-	return 1 + 4 + 4 + len(p.Handle)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle)
+	return 4 + 1 + 4 + 4 + len(p.Handle)
 }
 
 // GetHandle returns the handle field of the packet.
@@ -58,10 +57,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *ReadPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle) + uint64(offset) + uint32(len)
-	return 1 + 4 + 4 + len(p.Handle) + 8 + 4
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle) + uint64(offset) + uint32(len)
+	return 4 + 1 + 4 + 4 + len(p.Handle) + 8 + 4
 }
 
 // GetHandle returns the handle field of the packet.
@@ -109,10 +107,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *WritePacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle) + uint64(offset) + bytes(data)
-	return 1 + 4 + 4 + len(p.Handle) + 8 + 4 + len(p.Data)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle) + uint64(offset) + bytes(data)
+	return 4 + 1 + 4 + 4 + len(p.Handle) + 8 + 4 + len(p.Data)
 }
 
 // GetHandle returns the handle field of the packet.
@@ -169,10 +166,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *FStatPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle)
-	return 1 + 4 + 4 + len(p.Handle)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle)
+	return 4 + 1 + 4 + 4 + len(p.Handle)
 }
 
 // GetHandle returns the handle field of the packet.
@@ -215,10 +211,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *FSetStatPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle) ATTRS(attrs)
-	return 1 + 4 + 4 + len(p.Handle) + p.Attrs.MarshalSize()
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle) ATTRS(attrs)
+	return 4 + 1 + 4 + 4 + len(p.Handle) + p.Attrs.MarshalSize()
 }
 
 // GetHandle returns the handle field of the packet.
@@ -262,10 +257,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *ReadDirPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle)
-	return 1 + 4 + 4 + len(p.Handle)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle)
+	return 4 + 1 + 4 + 4 + len(p.Handle)
 }
 
 // GetHandle returns the handle field of the packet.
diff --git a/encoding/ssh/filexfer/init_packets.go b/encoding/ssh/filexfer/init_packets.go
index d3fc578..8cd739d 100644
--- a/encoding/ssh/filexfer/init_packets.go
+++ b/encoding/ssh/filexfer/init_packets.go
@@ -11,15 +11,25 @@
 	Extensions []*ExtensionPair
 }
 
-// MarshalBinary returns p as the binary encoding of p.
-func (p *InitPacket) MarshalBinary() ([]byte, error) {
-	size := 1 + 4 // byte(type) + uint32(version)
+// MarshalSize returns the number of bytes that the packet would marshal into.
+func (p *InitPacket) MarshalSize() int {
+	// uint32(length) + byte(type) + uint32(version)
+	size := 4 + 1 + 4
 
 	for _, ext := range p.Extensions {
 		size += ext.MarshalSize()
 	}
 
-	b := NewBuffer(make([]byte, 4, 4+size))
+	return size
+}
+
+// MarshalBinary returns p as the binary encoding of p.
+func (p *InitPacket) MarshalBinary() ([]byte, error) {
+	b := NewMarshalBuffer(p.MarshalSize())
+
+	b.Reset()
+
+	b.AppendUint32(uint32(0)) // will be overwritten with size.
 	b.AppendUint8(uint8(PacketTypeInit))
 	b.AppendUint32(p.Version)
 
@@ -27,9 +37,8 @@
 		ext.MarshalInto(b)
 	}
 
-	b.PutLength(size)
-
-	return b.Bytes(), nil
+	data, _, _ := b.Packet(nil)
+	return data, nil
 }
 
 // UnmarshalBinary unmarshals a full raw packet out of the given data.
@@ -99,15 +108,25 @@
 	Extensions []*ExtensionPair
 }
 
-// MarshalBinary returns p as the binary encoding of p.
-func (p *VersionPacket) MarshalBinary() ([]byte, error) {
-	size := 1 + 4 // byte(type) + uint32(version)
+// MarshalSize returns the number of bytes that the packet would marshal into.
+func (p *VersionPacket) MarshalSize() int {
+	// uint32(length) + byte(type) + uint32(version)
+	size := 4 + 1 + 4
 
 	for _, ext := range p.Extensions {
 		size += ext.MarshalSize()
 	}
 
-	b := NewBuffer(make([]byte, 4, 4+size))
+	return size
+}
+
+// MarshalBinary returns p as the binary encoding of p.
+func (p *VersionPacket) MarshalBinary() ([]byte, error) {
+	b := NewMarshalBuffer(p.MarshalSize())
+
+	b.Reset()
+
+	b.AppendUint32(uint32(0)) // will be overwritten with size.
 	b.AppendUint8(uint8(PacketTypeVersion))
 	b.AppendUint32(p.Version)
 
@@ -115,9 +134,8 @@
 		ext.MarshalInto(b)
 	}
 
-	b.PutLength(size)
-
-	return b.Bytes(), nil
+	data, _, _ := b.Packet(nil)
+	return data, nil
 }
 
 // UnmarshalBinary unmarshals a full raw packet out of the given data.
diff --git a/encoding/ssh/filexfer/open_packets.go b/encoding/ssh/filexfer/open_packets.go
index 2cc64bf..ab9f1b5 100644
--- a/encoding/ssh/filexfer/open_packets.go
+++ b/encoding/ssh/filexfer/open_packets.go
@@ -23,10 +23,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *OpenPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(filename) + uint32(pflags) + ATTRS(attrs)
-	return 1 + 4 + 4 + len(p.Filename) + 4 + p.Attrs.MarshalSize()
+	// uint32(length) + uint8(type) + uint32(request-id) + string(filename) + uint32(pflags) + ATTRS(attrs)
+	return 4 + 1 + 4 + 4 + len(p.Filename) + 4 + p.Attrs.MarshalSize()
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -67,10 +66,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *OpenDirPacket) MarshalSize() int {
-	// uint8(type) + uint32(path) string(filename)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(path) string(filename)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
diff --git a/encoding/ssh/filexfer/openssh/fsync.go b/encoding/ssh/filexfer/openssh/fsync.go
index 2446afa..d314f1f 100644
--- a/encoding/ssh/filexfer/openssh/fsync.go
+++ b/encoding/ssh/filexfer/openssh/fsync.go
@@ -24,8 +24,7 @@
 	return sshfx.PacketTypeExtended
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the extended request data would marshal into.
 func (ep *FSyncExtendedPacket) MarshalSize() int {
 	// string(handle)
 	return 4 + len(ep.Handle)
@@ -60,8 +59,7 @@
 //
 // NOTE: This _only_ encodes the packet-specific data, it does not encode the full extended packet.
 func (ep *FSyncExtendedPacket) MarshalBinary() ([]byte, error) {
-
-	buf := sshfx.NewBuffer(make([]byte, 0, ep.MarshalSize()))
+	buf := sshfx.NewMarshalBuffer(ep.MarshalSize())
 	ep.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
diff --git a/encoding/ssh/filexfer/openssh/hardlink.go b/encoding/ssh/filexfer/openssh/hardlink.go
index 65b2850..ece8085 100644
--- a/encoding/ssh/filexfer/openssh/hardlink.go
+++ b/encoding/ssh/filexfer/openssh/hardlink.go
@@ -25,8 +25,7 @@
 	return sshfx.PacketTypeExtended
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the extended request data would marshal into.
 func (ep *HardlinkExtendedPacket) MarshalSize() int {
 	// string(oldpath) + string(newpath)
 	return 4 + len(ep.OldPath) + 4 + len(ep.NewPath)
@@ -57,7 +56,7 @@
 //
 // NOTE: This _only_ encodes the packet-specific data, it does not encode the full extended packet.
 func (ep *HardlinkExtendedPacket) MarshalBinary() ([]byte, error) {
-	buf := sshfx.NewBuffer(make([]byte, 0, ep.MarshalSize()))
+	buf := sshfx.NewMarshalBuffer(ep.MarshalSize())
 	ep.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
diff --git a/encoding/ssh/filexfer/openssh/posix-rename.go b/encoding/ssh/filexfer/openssh/posix-rename.go
index ad67ef6..fd1d074 100644
--- a/encoding/ssh/filexfer/openssh/posix-rename.go
+++ b/encoding/ssh/filexfer/openssh/posix-rename.go
@@ -25,8 +25,7 @@
 	return sshfx.PacketTypeExtended
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the extended request data would marshal into.
 func (ep *POSIXRenameExtendedPacket) MarshalSize() int {
 	// string(oldpath) + string(newpath)
 	return 4 + len(ep.OldPath) + 4 + len(ep.NewPath)
@@ -57,7 +56,7 @@
 //
 // NOTE: This _only_ encodes the packet-specific data, it does not encode the full extended packet.
 func (ep *POSIXRenameExtendedPacket) MarshalBinary() ([]byte, error) {
-	buf := sshfx.NewBuffer(make([]byte, 0, ep.MarshalSize()))
+	buf := sshfx.NewMarshalBuffer(ep.MarshalSize())
 	ep.MarshalInto(buf)
 	return buf.Bytes(), nil
 }
diff --git a/encoding/ssh/filexfer/openssh/statvfs.go b/encoding/ssh/filexfer/openssh/statvfs.go
index a2d23a9..33f2dcc 100644
--- a/encoding/ssh/filexfer/openssh/statvfs.go
+++ b/encoding/ssh/filexfer/openssh/statvfs.go
@@ -24,8 +24,7 @@
 	return sshfx.PacketTypeExtended
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the extended request data would marshal into.
 func (ep *StatVFSExtendedPacket) MarshalSize() int {
 	// string(path)
 	return 4 + len(ep.Path)
@@ -55,10 +54,8 @@
 //
 // NOTE: This _only_ encodes the packet-specific data, it does not encode the full extended packet.
 func (ep *StatVFSExtendedPacket) MarshalBinary() ([]byte, error) {
-	buf := sshfx.NewBuffer(make([]byte, 0, ep.MarshalSize()))
-
+	buf := sshfx.NewMarshalBuffer(ep.MarshalSize())
 	ep.MarshalInto(buf)
-
 	return buf.Bytes(), nil
 }
 
@@ -96,8 +93,7 @@
 	return sshfx.PacketTypeExtended
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the extended request data would marshal into.
 func (ep *FStatVFSExtendedPacket) MarshalSize() int {
 	// string(handle)
 	return 4 + len(ep.Handle)
@@ -132,10 +128,8 @@
 //
 // NOTE: This _only_ encodes the packet-specific data, it does not encode the full extended packet.
 func (ep *FStatVFSExtendedPacket) MarshalBinary() ([]byte, error) {
-	buf := sshfx.NewBuffer(make([]byte, 0, ep.MarshalSize()))
-
+	buf := sshfx.NewMarshalBuffer(ep.MarshalSize())
 	ep.MarshalInto(buf)
-
 	return buf.Bytes(), nil
 }
 
@@ -180,8 +174,7 @@
 	return sshfx.PacketTypeExtendedReply
 }
 
-// MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
+// MarshalSize returns the number of bytes that the extended request data would marshal into.
 func (ep *StatVFSExtendedReplyPacket) MarshalSize() int {
 	// 11 times uint64(fields)
 	return 11 * 8
@@ -222,9 +215,9 @@
 //
 // NOTE: This _only_ encodes the packet-specific data, it does not encode the full extended reply packet.
 func (ep *StatVFSExtendedReplyPacket) MarshalBinary() ([]byte, error) {
-	b := sshfx.NewBuffer(make([]byte, 0, ep.MarshalSize()))
-	ep.MarshalInto(b)
-	return b.Bytes(), nil
+	buf := sshfx.NewMarshalBuffer(ep.MarshalSize())
+	ep.MarshalInto(buf)
+	return buf.Bytes(), nil
 }
 
 // UnmarshalFrom decodes the [email protected] extended reply packet-specific data into ep.
diff --git a/encoding/ssh/filexfer/packets.go b/encoding/ssh/filexfer/packets.go
index 101d826..344b0bc 100644
--- a/encoding/ssh/filexfer/packets.go
+++ b/encoding/ssh/filexfer/packets.go
@@ -32,10 +32,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *RawPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + raw(buffer)
-	return 1 + 4 + p.Data.MarshalSize()
+	// uint32(length) + uint8(type) + uint32(request-id) + raw(buffer)
+	return 4 + 1 + 4 + p.Data.MarshalSize()
 }
 
 // Reset clears the pointers and reference-semantic variables of RawPacket,
@@ -231,14 +230,13 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *RequestPacket) MarshalSize() int {
 	if p.Request == nil {
-		// uint8(type) + uint32(request-id)
-		return 1 + 4
+		// uint32(length) + uint8(type) + uint32(request-id)
+		return 4 + 1 + 4
 	}
 
-	return 5 // p.Request.MarshalSize() TODO
+	return p.Request.MarshalSize()
 }
 
 // Reset clears the pointers and reference-semantic variables in RequestPacket,
diff --git a/encoding/ssh/filexfer/path_packets.go b/encoding/ssh/filexfer/path_packets.go
index 70b9a38..7cee10e 100644
--- a/encoding/ssh/filexfer/path_packets.go
+++ b/encoding/ssh/filexfer/path_packets.go
@@ -11,10 +11,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *LStatPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -52,10 +51,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *SetStatPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path) + ATTRS(attrs)
-	return 1 + 4 + 4 + len(p.Path) + p.Attrs.MarshalSize()
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path) + ATTRS(attrs)
+	return 4 + 1 + 4 + 4 + len(p.Path) + p.Attrs.MarshalSize()
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -94,10 +92,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *RemovePacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -135,10 +132,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *MkdirPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path) + ATTRS(attrs)
-	return 1 + 4 + 4 + len(p.Path) + p.Attrs.MarshalSize()
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path) + ATTRS(attrs)
+	return 4 + 1 + 4 + 4 + len(p.Path) + p.Attrs.MarshalSize()
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -177,10 +173,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *RmdirPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -217,10 +212,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *RealPathPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -257,10 +251,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *StatPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -298,10 +291,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *RenamePacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(oldpath) + string(newpath)
-	return 1 + 4 + 4 + len(p.OldPath) + 4 + len(p.NewPath)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(oldpath) + string(newpath)
+	return 4 + 1 + 4 + 4 + len(p.OldPath) + 4 + len(p.NewPath)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -340,10 +332,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *ReadLinkPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(path)
-	return 1 + 4 + 4 + len(p.Path)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(path)
+	return 4 + 1 + 4 + 4 + len(p.Path)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -385,10 +376,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *SymlinkPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(linkpath) + string(targetpath)
-	return 1 + 4 + 4 + len(p.LinkPath) + 4 + len(p.TargetPath)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(linkpath) + string(targetpath)
+	return 4 + 1 + 4 + 4 + len(p.LinkPath) + 4 + len(p.TargetPath)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
diff --git a/encoding/ssh/filexfer/response_packets.go b/encoding/ssh/filexfer/response_packets.go
index afa07ba..60ec6d0 100644
--- a/encoding/ssh/filexfer/response_packets.go
+++ b/encoding/ssh/filexfer/response_packets.go
@@ -40,10 +40,12 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *StatusPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + uint32(error/status code) + string(error message) + string(language tag)
-	return 1 + 4 + 4 + 4 + len(p.ErrorMessage) + 4 + len(p.LanguageTag)
+	// uint32(length) + uint8(type) + uint32(request-id)
+	const size = 4 + 1 + 4
+
+ 	// uint32(error/status code) + string(error message) + string(language tag)
+	return size + 4 + 4 + len(p.ErrorMessage) + 4 + len(p.LanguageTag)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -84,10 +86,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *HandlePacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + string(handle)
-	return 1 + 4 + 4 + len(p.Handle)
+	// uint32(length) + uint8(type) + uint32(request-id) + string(handle)
+	return 4 + 1 + 4 + 4 + len(p.Handle)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -124,10 +125,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *DataPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + bytes(data)
-	return 1 + 4 + 4 + len(p.Data)
+	// uint32(length) + uint8(type) + uint32(request-id) + bytes(data)
+	return 4 + 1 + 4 + 4 + len(p.Data)
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.
@@ -173,10 +173,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *NamePacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + uint32(len(entries))
-	size := 1 + 4 + 4
+	// uint32(length) + uint8(type) + uint32(request-id) + uint32(len(entries))
+	size := 4 + 1 + 4 + 4
 
 	for _, e := range p.Entries {
 		size += e.MarshalSize()
@@ -237,10 +236,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *PathPseudoPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) +
-	size := 1 + 4 + 4 // uint32(count = 1)
+	// uint32(length) + uint8(type) + uint32(request-id) +
+	size := 4 + 1 + 4 + 4 // uint32(count = 1)
 
 	size += 4 + len(p.Path) // string(path)
 
@@ -310,10 +308,9 @@
 }
 
 // MarshalSize returns the number of bytes that the packet would marshal into.
-// This excludes the uint32(length).
 func (p *AttrsPacket) MarshalSize() int {
-	// uint8(type) + uint32(request-id) + ATTRS(attrs)
-	return 1 + 4 + p.Attrs.MarshalSize()
+	// uint32(length) + uint8(type) + uint32(request-id) + ATTRS(attrs)
+	return 4 + 1 + 4 + p.Attrs.MarshalSize()
 }
 
 // MarshalPacket returns p as a two-part binary encoding of p.