package capnp

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"testing"

	"capnproto.org/go/capnp/v3/exp/bufferpool"
	"github.com/stretchr/testify/require"
)

func TestSegmentInBounds(t *testing.T) {
	tests := []struct {
		n    int
		addr address
		ok   bool
	}{
		{0, 0, false},
		{0, 1, false},
		{0, 2, false},
		{1, 0, true},
		{1, 1, false},
		{1, 2, false},
		{2, 0, true},
		{2, 1, true},
		{2, 2, false},
	}
	for _, test := range tests {
		seg := &Segment{data: make([]byte, test.n)}
		if ok := seg.inBounds(test.addr); ok != test.ok {
			t.Errorf("&Segment{data: make([]byte, %d)}.inBounds(%#v) = %t; want %t", test.n, test.addr, ok, test.ok)
		}
	}
}

func TestSegmentRegionInBounds(t *testing.T) {
	tests := []struct {
		n    int
		addr address
		sz   Size
		ok   bool
	}{
		{0, 0, 0, true}, // zero-sized region <= len is okay
		{0, 0, 1, false},
		{0, 1, 0, false},
		{0, 1, 1, false},
		{1, 0, 0, true},
		{1, 0, 1, true},
		{1, 1, 0, true},
		{1, 1, 1, false},
		{2, 0, 0, true},
		{2, 0, 1, true},
		{2, 0, 2, true},
		{2, 0, 3, false},
		{2, 1, 0, true},
		{2, 1, 1, true},
		{2, 1, 2, false},
		{2, 1, 3, false},
		{2, 2, 0, true},
		{2, 2, 1, false},
	}
	for _, test := range tests {
		seg := &Segment{data: make([]byte, test.n)}
		if ok := seg.regionInBounds(test.addr, test.sz); ok != test.ok {
			t.Errorf("&Segment{data: make([]byte, %d)}.regionInBounds(%#v, %#v) = %t; want %t", test.n, test.addr, test.sz, ok, test.ok)
		}
	}
}

func TestSegmentReadUint8(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint8
		panics bool
	}{
		{data: []byte{}, addr: 0, panics: true},
		{data: []byte{42}, addr: 0, val: 42},
		{data: []byte{42}, addr: 1, panics: true},
		{data: []byte{1, 42, 2}, addr: 0, val: 1},
		{data: []byte{1, 42, 2}, addr: 1, val: 42},
		{data: []byte{1, 42, 2}, addr: 2, val: 2},
		{data: []byte{1, 42, 2}, addr: 3, panics: true},
	}
	for _, test := range tests {
		seg := &Segment{data: test.data}
		var val uint8
		err := catchPanic(func() {
			val = seg.readUint8(test.addr)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.readUint8(%v) unexpected panic: %v", test.data, test.addr, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.readUint8(%v) did not panic as expected", test.data, test.addr)
			continue
		}
		if val != test.val {
			t.Errorf("&Segment{data: % x}.readUint8(%v) = %#x; want %#x", test.data, test.addr, val, test.val)
		}
	}
}

func TestSegmentReadUint16(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint16
		panics bool
	}{
		{data: []byte{}, addr: 0, panics: true},
		{data: []byte{0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00}, addr: 0, val: 0},
		{data: []byte{0x01, 0x00}, addr: 0, val: 1},
		{data: []byte{0x34, 0x12}, addr: 0, val: 0x1234},
		{data: []byte{0x34, 0x12, 0x56}, addr: 0, val: 0x1234},
		{data: []byte{0x34, 0x12, 0x56}, addr: 1, val: 0x5612},
		{data: []byte{0x34, 0x12, 0x56}, addr: 2, panics: true},
	}
	for _, test := range tests {
		seg := &Segment{data: test.data}
		var val uint16
		err := catchPanic(func() {
			val = seg.readUint16(test.addr)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.readUint16(%v) unexpected panic: %v", test.data, test.addr, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.readUint16(%v) did not panic as expected", test.data, test.addr)
			continue
		}
		if val != test.val {
			t.Errorf("&Segment{data: % x}.readUint16(%v) = %#x; want %#x", test.data, test.addr, val, test.val)
		}
	}
}

func TestSegmentReadUint32(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint32
		panics bool
	}{
		{data: []byte{}, addr: 0, panics: true},
		{data: []byte{0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00, 0x00}, addr: 0, val: 0},
		{data: []byte{0x78, 0x56, 0x34, 0x12}, addr: 0, val: 0x12345678},
		{data: []byte{0xff, 0x78, 0x56, 0x34, 0x12, 0xff}, addr: 1, val: 0x12345678},
		{data: []byte{0xff, 0x78, 0x56, 0x34, 0x12, 0xff}, addr: 2, val: 0xff123456},
		{data: []byte{0xff, 0x78, 0x56, 0x34, 0x12, 0xff}, addr: 3, panics: true},
	}
	for _, test := range tests {
		seg := &Segment{data: test.data}
		var val uint32
		err := catchPanic(func() {
			val = seg.readUint32(test.addr)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.readUint32(%v) unexpected panic: %v", test.data, test.addr, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.readUint32(%v) did not panic as expected", test.data, test.addr)
			continue
		}
		if val != test.val {
			t.Errorf("&Segment{data: % x}.readUint32(%v) = %#x; want %#x", test.data, test.addr, val, test.val)
		}
	}
}

func TestSegmentReadUint64(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint64
		panics bool
	}{
		{data: []byte{}, addr: 0, panics: true},
		{data: []byte{0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00, 0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, addr: 0, panics: true},
		{data: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, addr: 0, val: 0},
		{data: []byte{0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01}, addr: 0, val: 0x0123456789abcdef},
		{data: []byte{0xff, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xff}, addr: 0, val: 0x23456789abcdefff},
		{data: []byte{0xff, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xff}, addr: 1, val: 0x0123456789abcdef},
		{data: []byte{0xff, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xff}, addr: 2, val: 0xff0123456789abcd},
		{data: []byte{0xff, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xff}, addr: 3, panics: true},
	}
	for _, test := range tests {
		seg := &Segment{data: test.data}
		var val uint64
		err := catchPanic(func() {
			val = seg.readUint64(test.addr)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.readUint64(%v) unexpected panic: %v", test.data, test.addr, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.readUint64(%v) did not panic as expected", test.data, test.addr)
			continue
		}
		if val != test.val {
			t.Errorf("&Segment{data: % x}.readUint64(%v) = %#x; want %#x", test.data, test.addr, val, test.val)
		}
	}
}

func TestSegmentWriteUint8(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint8
		out    []byte
		panics bool
	}{
		{
			data:   []byte{},
			addr:   0,
			val:    0,
			panics: true,
		},
		{
			data: []byte{1},
			addr: 0,
			val:  42,
			out:  []byte{42},
		},
		{
			data:   []byte{42},
			addr:   1,
			val:    1,
			panics: true,
		},
		{
			data: []byte{1, 2, 3},
			addr: 0,
			val:  0xff,
			out:  []byte{0xff, 2, 3},
		},
		{
			data: []byte{1, 2, 3},
			addr: 1,
			val:  0xff,
			out:  []byte{1, 0xff, 3},
		},
		{
			data: []byte{1, 2, 3},
			addr: 2,
			val:  0xff,
			out:  []byte{1, 2, 0xff},
		},
		{
			data:   []byte{1, 2, 3},
			addr:   3,
			val:    0xff,
			panics: true,
		},
	}
	for _, test := range tests {
		out := make([]byte, len(test.data))
		copy(out, test.data)
		seg := &Segment{data: out}
		err := catchPanic(func() {
			seg.writeUint8(test.addr, test.val)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.writeUint8(%v, %#x) unexpected panic: %v", test.data, test.addr, test.val, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.writeUint8(%v, %#x) did not panic as expected", test.data, test.addr, test.val)
			continue
		}
		if !bytes.Equal(out, test.out) {
			t.Errorf("data after &Segment{data: % x}.writeUint8(%v, %#x) = % x; want % x", test.data, test.addr, test.val, out, test.out)
		}
	}
}

func TestSegmentWriteUint16(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint16
		out    []byte
		panics bool
	}{
		{
			data:   []byte{},
			addr:   0,
			val:    0,
			panics: true,
		},
		{
			data: []byte{1, 2, 3, 4},
			addr: 1,
			val:  0x1234,
			out:  []byte{1, 0x34, 0x12, 4},
		},
	}
	for _, test := range tests {
		out := make([]byte, len(test.data))
		copy(out, test.data)
		seg := &Segment{data: out}
		err := catchPanic(func() {
			seg.writeUint16(test.addr, test.val)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.writeUint16(%v, %#x) unexpected panic: %v", test.data, test.addr, test.val, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.writeUint16(%v, %#x) did not panic as expected", test.data, test.addr, test.val)
			continue
		}
		if !bytes.Equal(out, test.out) {
			t.Errorf("data after &Segment{data: % x}.writeUint16(%v, %#x) = % x; want % x", test.data, test.addr, test.val, out, test.out)
		}
	}
}

func TestSegmentWriteUint32(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint32
		out    []byte
		panics bool
	}{
		{
			data:   []byte{},
			addr:   0,
			val:    0,
			panics: true,
		},
		{
			data: []byte{1, 2, 3, 4, 5, 6},
			addr: 1,
			val:  0x01234567,
			out:  []byte{1, 0x67, 0x45, 0x23, 0x01, 6},
		},
	}
	for _, test := range tests {
		out := make([]byte, len(test.data))
		copy(out, test.data)
		seg := &Segment{data: out}
		err := catchPanic(func() {
			seg.writeUint32(test.addr, test.val)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.writeUint32(%v, %#x) unexpected panic: %v", test.data, test.addr, test.val, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.writeUint32(%v, %#x) did not panic as expected", test.data, test.addr, test.val)
			continue
		}
		if !bytes.Equal(out, test.out) {
			t.Errorf("data after &Segment{data: % x}.writeUint32(%v, %#x) = % x; want % x", test.data, test.addr, test.val, out, test.out)
		}
	}
}

func TestSegmentWriteUint64(t *testing.T) {
	tests := []struct {
		data   []byte
		addr   address
		val    uint64
		out    []byte
		panics bool
	}{
		{
			data:   []byte{},
			addr:   0,
			val:    0,
			panics: true,
		},
		{
			data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
			addr: 1,
			val:  0x0123456789abcdef,
			out:  []byte{1, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 10},
		},
	}
	for _, test := range tests {
		out := make([]byte, len(test.data))
		copy(out, test.data)
		seg := &Segment{data: out}
		err := catchPanic(func() {
			seg.writeUint64(test.addr, test.val)
		})
		if err != nil {
			if !test.panics {
				t.Errorf("&Segment{data: % x}.writeUint64(%v, %#x) unexpected panic: %v", test.data, test.addr, test.val, err)
			}
			continue
		}
		if test.panics {
			t.Errorf("&Segment{data: % x}.writeUint64(%v, %#x) did not panic as expected", test.data, test.addr, test.val)
			continue
		}
		if !bytes.Equal(out, test.out) {
			t.Errorf("data after &Segment{data: % x}.writeUint64(%v, %#x) = % x; want % x", test.data, test.addr, test.val, out, test.out)
		}
	}
}

func TestSetPtrCopyListMember(t *testing.T) {
	_, seg := NewSingleSegmentMessage(nil)
	root, err := NewRootStruct(seg, ObjectSize{PointerCount: 2})
	if err != nil {
		t.Fatal("NewRootStruct:", err)
	}
	plist, err := NewCompositeList(seg, ObjectSize{PointerCount: 1}, 1)
	if err != nil {
		t.Fatal("NewCompositeList:", err)
	}
	if err := root.SetPtr(0, plist.ToPtr()); err != nil {
		t.Fatal("root.SetPtr(0, plist):", err)
	}
	sub, err := NewStruct(seg, ObjectSize{DataSize: 8})
	if err != nil {
		t.Fatal("NewStruct:", err)
	}
	sub.SetUint64(0, 42)
	pl0 := plist.Struct(0)
	if err := pl0.SetPtr(0, sub.ToPtr()); err != nil {
		t.Fatal("pl0.SetPtr(0, sub.ToPtr()):", err)
	}

	if err := root.SetPtr(1, pl0.ToPtr()); err != nil {
		t.Error("root.SetPtr(1, pl0):", err)
	}

	p1, err := root.Ptr(1)
	if err != nil {
		t.Error("root.Ptr(1):", err)
	}
	s1 := p1.Struct()
	if !s1.IsValid() {
		t.Error("root.Ptr(1) is not a valid struct")
	}
	if SamePtr(s1.ToPtr(), pl0.ToPtr()) {
		t.Error("list member not copied; points to same object")
	}
	s1p0, err := s1.Ptr(0)
	if err != nil {
		t.Error("root.Ptr(1).Struct().Ptr(0):", err)
	}
	s1s0 := s1p0.Struct()
	if !s1s0.IsValid() {
		t.Error("root.Ptr(1).Struct().Ptr(0) is not a valid struct")
	}
	if SamePtr(s1s0.ToPtr(), sub.ToPtr()) {
		t.Error("sub-object not copied; points to same object")
	}
	if got := s1s0.Uint64(0); got != 42 {
		t.Errorf("sub-object data = %d; want 42", got)
	}
}

func TestSetPtrToZeroSizeStruct(t *testing.T) {
	_, seg := NewSingleSegmentMessage(nil)
	root, err := NewRootStruct(seg, ObjectSize{PointerCount: 1})
	if err != nil {
		t.Fatal("NewRootStruct:", err)
	}
	sub, err := NewStruct(seg, ObjectSize{})
	if err != nil {
		t.Fatal("NewStruct:", err)
	}
	if err := root.SetPtr(0, sub.ToPtr()); err != nil {
		t.Fatal("root.SetPtr(0, sub.ToPtr()):", err)
	}
	ptrSlice := seg.Data()[root.off : root.off+8]
	want := []byte{0xfc, 0xff, 0xff, 0xff, 0, 0, 0, 0}
	if !bytes.Equal(ptrSlice, want) {
		t.Errorf("SetPtr wrote % 02x; want % 02x", ptrSlice, want)
	}
}

func TestReadFarPointers(t *testing.T) {
	msg, _ := NewMultiSegmentMessage([][]byte{
		// Segment 0
		{
			// Double-far pointer: segment 2, offset 0
			0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
		},
		// Segment 1
		{
			// (Root) Struct data section
			0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			// Struct pointer section
			// Double-far pointer: segment 4, offset 0
			0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
		},
		// Segment 2
		{
			// Far pointer landing pad: segment 1, offset 0
			0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
			// Far pointer landing pad tag word: struct with 1 word data and 1 pointer
			0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
		},
		// Segment 3
		{
			// (Root>0) Struct data section
			0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
			0xaa, 0x70, 0x65, 0x21, 0xd7, 0x7b, 0x31, 0xa7,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			// Struct pointer section
			// Far pointer: segment 4, offset 4
			0x22, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
			// Far pointer: segment 4, offset 7
			0x3a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
			// Null
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		},
		// Segment 4
		{
			// Far pointer landing pad: segment 3, offset 0
			0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
			// Far pointer landing pad tag word: struct with 3 word data and 3 pointer
			0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
			// (Root>0>0) Struct data section
			0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			// Struct pointer section
			// Null
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			// Far pointer landing pad: struct pointer: offset -3, 1 word data, 1 pointer
			0xf4, 0xff, 0xff, 0xff, 0x01, 0x00, 0x01, 0x00,
			// (Root>0>1) Struct pointer section
			// Struct pointer: offset 2, 1 word data
			0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
			// Null
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			// Far pointer landing pad: struct pointer: offset -3, 2 pointers
			0xf4, 0xff, 0xff, 0xff, 0x00, 0x00, 0x02, 0x00,
			// (Root>0>1>0) Struct data section
			0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		},
	})
	rootp, err := msg.Root()
	if err != nil {
		t.Error("Root:", err)
	}
	root := rootp.Struct()
	if root.Uint16(0) != 2 {
		t.Errorf("root.Uint16(0) = %d; want 2", root.Uint16(0))
	}
	callp, err := root.Ptr(0)
	if err != nil {
		t.Error("root.Ptr(0):", err)
	}
	call := callp.Struct()
	targetp, err := call.Ptr(0)
	if err != nil {
		t.Error("root.Ptr(0).Ptr(0):", err)
	}
	if got := targetp.Struct().Uint32(0); got != 84 {
		t.Errorf("root.Ptr(0).Ptr(0).Uint32(0) = %d; want 84", got)
	}
	paramsp, err := call.Ptr(1)
	if err != nil {
		t.Error("root.Ptr(0).Ptr(1):", err)
	}
	contentp, err := paramsp.Struct().Ptr(0)
	if err != nil {
		t.Error("root.Ptr(0).Ptr(1).Ptr(0):", err)
	}
	if got := contentp.Struct().Uint64(0); got != 42 {
		t.Errorf("root.Ptr(0).Ptr(1).Ptr(0).Uint64(0) = %d; want 42", got)
	}
}

func TestWriteFarPointer(t *testing.T) {
	// TODO(someday): run same test with a two-word list

	msg, _ := NewMultiSegmentMessage([][]byte{
		make([]byte, 8),
		make([]byte, 0, 24),
	})
	seg1, err := msg.Segment(1)
	require.NoError(t, err)
	s, err := NewStruct(seg1, ObjectSize{DataSize: 8, PointerCount: 1})
	require.NoError(t, err)
	require.Equal(t, s.Segment(), seg1)
	err = msg.SetRoot(s.ToPtr())
	require.NoError(t, err)
	seg0, err := msg.Segment(0)
	require.NoError(t, err)

	root := rawPointer(binary.LittleEndian.Uint64(seg0.Data()))
	require.Equal(t, farPointer, root.pointerType())
	require.Equal(t, SegmentID(1), root.farSegment())
	padAddr := root.farAddress()
	require.LessOrEqual(t, padAddr, address(len(seg1.Data())-8))

	pad := rawPointer(binary.LittleEndian.Uint64(seg1.Data()[padAddr:]))
	if pad.pointerType() != structPointer {
		t.Errorf("landing pad (%#016x) type = %v; want %v (structPointer)", pad, pad.pointerType(), structPointer)
	}
	if got, ok := pad.offset().resolve(padAddr + 8); !ok || got != s.off {
		t.Errorf("landing pad (%#016x @ %v) resolved address = %v, %t; want %v, true", pad, padAddr, got, ok, s.off)
	}
	if got, want := pad.structSize(), (ObjectSize{DataSize: 8, PointerCount: 1}); got != want {
		t.Errorf("landing pad (%#016x) struct size = %v; want %v", pad, got, want)
	}
}

func TestWriteDoubleFarPointer(t *testing.T) {
	// TODO(someday): run same test with a two-word list

	msg, _ := NewMultiSegmentMessage([][]byte{
		make([]byte, 8),
		make([]byte, 0, 16),
	})

	// Make arena writable again.
	msg.Arena.(*MultiSegmentArena).bp = &bufferpool.Default
	seg1, err := msg.Segment(1)
	if err != nil {
		t.Fatal("msg.Segment(1):", err)
	}
	s, err := NewStruct(seg1, ObjectSize{DataSize: 8, PointerCount: 1})
	if err != nil {
		t.Fatal("NewStruct(msg.Segment(1), ObjectSize{8, 1}):", err)
	}
	if s.Segment() != seg1 {
		t.Fatalf("struct allocated in segment %d", s.Segment().ID())
	}
	if err := msg.SetRoot(s.ToPtr()); err != nil {
		t.Error("msg.SetRoot(...):", err)
	}
	seg0, err := msg.Segment(0)
	if err != nil {
		t.Fatal("msg.Segment(0):", err)
	}

	root := rawPointer(binary.LittleEndian.Uint64(seg0.Data()))
	if root.pointerType() != doubleFarPointer {
		t.Fatalf("root (%#016x) type = %v; want %v (doubleFarPointer)", root, root.pointerType(), doubleFarPointer)
	}
	if root.farSegment() == 0 || root.farSegment() == 1 {
		t.Fatalf("root points to segment %d; want !=0,1", root.farSegment())
	}
	padSeg, err := msg.Segment(root.farSegment())
	if err != nil {
		t.Fatalf("msg.Segment(%d): %v", root.farSegment(), err)
	}
	padAddr := root.farAddress()
	if padAddr > address(len(padSeg.Data())-16) {
		t.Fatalf("root points to out of bounds address %v; size of segment is %d", padAddr, len(padSeg.Data()))
	}

	pad1 := rawPointer(binary.LittleEndian.Uint64(padSeg.Data()[padAddr:]))
	if pad1.pointerType() != farPointer {
		t.Errorf("landing pad pointer 1 (%#016x) type = %v; want %v (farPointer)", pad1, pad1.pointerType(), farPointer)
	}
	if pad1.farSegment() != 1 {
		t.Fatalf("landing pad pointer 1 (%#016x) points to segment %d; want 1", pad1, pad1.farSegment())
	}
	if pad1.farAddress() != s.off {
		t.Fatalf("landing pad pointer 1 (%#016x) points to address %v; want %v", pad1, pad1.farAddress(), s.off)
	}

	pad2 := rawPointer(binary.LittleEndian.Uint64(padSeg.Data()[padAddr+8:]))
	if pad2.pointerType() != structPointer {
		t.Errorf("landing pad pointer 2 (%#016x) type = %v; want %v (structPointer)", pad2, pad2.pointerType(), structPointer)
	}
	if pad2.offset() != 0 {
		t.Errorf("landing pad pointer 2 (%#016x) offset = %d; want 0", pad2, pad2.offset())
	}
	if got, want := pad2.structSize(), (ObjectSize{DataSize: 8, PointerCount: 1}); got != want {
		t.Errorf("landing pad pointer 2 (%#016x) struct size = %v; want %v", pad2, got, want)
	}
}

func TestSetInterfacePtr(t *testing.T) {
	t.Run("SameMessage", func(t *testing.T) {
		msg, seg := NewSingleSegmentMessage(nil)
		msg.CapTable().Add(Client{}) // just to make the capability ID below non-zero
		root, err := NewRootStruct(seg, ObjectSize{PointerCount: 2})
		if err != nil {
			t.Fatal("NewRootStruct:", err)
		}
		hook := new(dummyHook)
		client := NewClient(hook)
		id := msg.CapTable().Add(client)
		iface := NewInterface(seg, id)
		defer func() {
			msg.CapTable().Reset()
			if hook.shutdowns == 0 {
				t.Error("client leaked")
			}
		}()

		if err := root.SetPtr(0, iface.ToPtr()); err != nil {
			t.Fatal("root.SetPtr(0, iface.ToPtr()):", err)
		}
		ptr0, err := root.Ptr(0)
		if err != nil {
			t.Fatal("root.Ptr(0):", err)
		}
		iface0 := ptr0.Interface()
		if !iface0.IsValid() {
			t.Error("root.Ptr(0) is not an interface")
		} else if iface0.Capability() != id {
			t.Errorf("root.Ptr(0).Interface().Capability() = %d; want %d", iface0.Capability(), id)
		}

		if err := root.SetPtr(1, iface.ToPtr()); err != nil {
			t.Fatal("root.SetPtr(1, iface.ToPtr()):", err)
		}
		ptr1, err := root.Ptr(1)
		if err != nil {
			t.Fatal("root.Ptr(1):", err)
		}
		iface1 := ptr1.Interface()
		if !iface1.IsValid() {
			t.Error("root.Ptr(1) is not an interface")
		} else if iface1.Capability() != id {
			t.Errorf("root.Ptr(1).Interface().Capability() = %d; want %d", iface1.Capability(), id)
		}
	})
	t.Run("DifferentMessages", func(t *testing.T) {
		msg1, seg1 := NewSingleSegmentMessage(nil)
		msg2, seg2 := NewSingleSegmentMessage(nil)
		root, err := NewRootStruct(seg2, ObjectSize{PointerCount: 1})
		if err != nil {
			t.Fatal("NewRootStruct:", err)
		}
		defer func() {
			msg1.CapTable().Reset()
			msg2.CapTable().Reset()
		}()

		hook := new(dummyHook)
		client := NewClient(hook)
		iface1 := NewInterface(seg1, msg1.CapTable().Add(client))
		if err := root.SetPtr(0, iface1.ToPtr()); err != nil {
			t.Fatal("root.SetPtr(0, iface1.ToPtr()):", err)
		}
		ptr, err := root.Ptr(0)
		if err != nil {
			t.Fatal("root.Ptr(0):", err)
		}
		iface2 := ptr.Interface()
		if !iface2.Client().IsSame(iface1.Client()) {
			t.Errorf("root.Ptr(0).Interface().Client() = %v; want %v", iface2.Client(), iface1.Client())
		}
		msg1.CapTable().Reset()
		if hook.shutdowns > 0 {
			t.Error("copying interface across messages did not add reference")
		}
		msg2.CapTable().Reset()
		if hook.shutdowns == 0 {
			t.Error("client not shut down after releasing both message capability tables")
		}
	})
}

func catchPanic(f func()) (err error) {
	defer func() {
		pval := recover()
		if pval == nil {
			return
		}
		e, ok := pval.(error)
		if !ok {
			err = fmt.Errorf("non-error panic: %#v", pval)
			return
		}
		err = e
	}()
	f()
	return nil
}
