// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssh

import (
	"errors"
	"fmt"
	"io"
	"sync"
	"testing"
	"time"
)

func muxPair() (*mux, *mux) {
	a, b := memPipe()

	s := newMux(a)
	c := newMux(b)

	return s, c
}

// Returns both ends of a channel, and the mux for the 2nd
// channel.
func channelPair(t *testing.T) (*channel, *channel, *mux) {
	c, s := muxPair()

	res := make(chan *channel, 1)
	go func() {
		newCh, ok := <-s.incomingChannels
		if !ok {
			t.Error("no incoming channel")
			close(res)
			return
		}
		if newCh.ChannelType() != "chan" {
			t.Errorf("got type %q want chan", newCh.ChannelType())
			newCh.Reject(Prohibited, fmt.Sprintf("got type %q want chan", newCh.ChannelType()))
			close(res)
			return
		}
		ch, _, err := newCh.Accept()
		if err != nil {
			t.Errorf("accept: %v", err)
			close(res)
			return
		}
		res <- ch.(*channel)
	}()

	ch, err := c.openChannel("chan", nil)
	if err != nil {
		t.Fatalf("OpenChannel: %v", err)
	}
	w := <-res
	if w == nil {
		t.Fatal("unable to get write channel")
	}

	return w, ch, c
}

// Test that stderr and stdout can be addressed from different
// goroutines. This is intended for use with the race detector.
func TestMuxChannelExtendedThreadSafety(t *testing.T) {
	writer, reader, mux := channelPair(t)
	defer writer.Close()
	defer reader.Close()
	defer mux.Close()

	var wr, rd sync.WaitGroup
	magic := "hello world"

	wr.Add(2)
	go func() {
		io.WriteString(writer, magic)
		wr.Done()
	}()
	go func() {
		io.WriteString(writer.Stderr(), magic)
		wr.Done()
	}()

	rd.Add(2)
	go func() {
		c, err := io.ReadAll(reader)
		if string(c) != magic {
			t.Errorf("stdout read got %q, want %q (error %s)", c, magic, err)
		}
		rd.Done()
	}()
	go func() {
		c, err := io.ReadAll(reader.Stderr())
		if string(c) != magic {
			t.Errorf("stderr read got %q, want %q (error %s)", c, magic, err)
		}
		rd.Done()
	}()

	wr.Wait()
	writer.CloseWrite()
	rd.Wait()
}

func TestMuxReadWrite(t *testing.T) {
	s, c, mux := channelPair(t)
	defer s.Close()
	defer c.Close()
	defer mux.Close()

	magic := "hello world"
	magicExt := "hello stderr"
	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		defer wg.Done()
		_, err := s.Write([]byte(magic))
		if err != nil {
			t.Errorf("Write: %v", err)
			return
		}
		_, err = s.Extended(1).Write([]byte(magicExt))
		if err != nil {
			t.Errorf("Write: %v", err)
			return
		}
	}()

	var buf [1024]byte
	n, err := c.Read(buf[:])
	if err != nil {
		t.Fatalf("server Read: %v", err)
	}
	got := string(buf[:n])
	if got != magic {
		t.Fatalf("server: got %q want %q", got, magic)
	}

	n, err = c.Extended(1).Read(buf[:])
	if err != nil {
		t.Fatalf("server Read: %v", err)
	}

	got = string(buf[:n])
	if got != magicExt {
		t.Fatalf("server: got %q want %q", got, magic)
	}
}

func TestMuxChannelOverflow(t *testing.T) {
	reader, writer, mux := channelPair(t)
	defer reader.Close()
	defer writer.Close()
	defer mux.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		defer wg.Done()
		if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
			t.Errorf("could not fill window: %v", err)
		}
		writer.Write(make([]byte, 1))
	}()
	writer.remoteWin.waitWriterBlocked()

	// Send 1 byte.
	packet := make([]byte, 1+4+4+1)
	packet[0] = msgChannelData
	marshalUint32(packet[1:], writer.remoteId)
	marshalUint32(packet[5:], uint32(1))
	packet[9] = 42

	if err := writer.mux.conn.writePacket(packet); err != nil {
		t.Errorf("could not send packet")
	}
	if _, err := reader.SendRequest("hello", true, nil); err == nil {
		t.Errorf("SendRequest succeeded.")
	}
}

func TestMuxChannelReadUnblock(t *testing.T) {
	reader, writer, mux := channelPair(t)
	defer reader.Close()
	defer writer.Close()
	defer mux.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		defer wg.Done()
		if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
			t.Errorf("could not fill window: %v", err)
		}
		if _, err := writer.Write(make([]byte, 1)); err != nil {
			t.Errorf("Write: %v", err)
		}
		writer.Close()
	}()

	writer.remoteWin.waitWriterBlocked()

	buf := make([]byte, 32768)
	for {
		_, err := reader.Read(buf)
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatalf("Read: %v", err)
		}
	}
}

func TestMuxChannelCloseWriteUnblock(t *testing.T) {
	reader, writer, mux := channelPair(t)
	defer reader.Close()
	defer writer.Close()
	defer mux.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		defer wg.Done()
		if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
			t.Errorf("could not fill window: %v", err)
		}
		if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
			t.Errorf("got %v, want EOF for unblock write", err)
		}
	}()

	writer.remoteWin.waitWriterBlocked()
	reader.Close()
}

func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
	reader, writer, mux := channelPair(t)
	defer reader.Close()
	defer writer.Close()
	defer mux.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		defer wg.Done()
		if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
			t.Errorf("could not fill window: %v", err)
		}
		if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
			t.Errorf("got %v, want EOF for unblock write", err)
		}
	}()

	writer.remoteWin.waitWriterBlocked()
	mux.Close()
}

func TestMuxReject(t *testing.T) {
	client, server := muxPair()
	defer server.Close()
	defer client.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		defer wg.Done()

		ch, ok := <-server.incomingChannels
		if !ok {
			t.Error("cannot accept channel")
			return
		}
		if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
			t.Errorf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
			ch.Reject(RejectionReason(UnknownChannelType), UnknownChannelType.String())
			return
		}
		ch.Reject(RejectionReason(42), "message")
	}()

	ch, err := client.openChannel("ch", []byte("extra"))
	if ch != nil {
		t.Fatal("openChannel not rejected")
	}

	ocf, ok := err.(*OpenChannelError)
	if !ok {
		t.Errorf("got %#v want *OpenChannelError", err)
	} else if ocf.Reason != 42 || ocf.Message != "message" {
		t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
	}

	want := "ssh: rejected: unknown reason 42 (\"message\")"
	if err.Error() != want {
		t.Errorf("got %q, want %q", err.Error(), want)
	}
}

func TestMuxChannelRequest(t *testing.T) {
	client, server, mux := channelPair(t)
	defer server.Close()
	defer client.Close()
	defer mux.Close()

	var received int
	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		for r := range server.incomingRequests {
			received++
			r.Reply(r.Type == "yes", nil)
		}
		wg.Done()
	}()
	_, err := client.SendRequest("yes", false, nil)
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}
	ok, err := client.SendRequest("yes", true, nil)
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}

	if !ok {
		t.Errorf("SendRequest(yes): %v", ok)

	}

	ok, err = client.SendRequest("no", true, nil)
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}
	if ok {
		t.Errorf("SendRequest(no): %v", ok)
	}

	client.Close()
	wg.Wait()

	if received != 3 {
		t.Errorf("got %d requests, want %d", received, 3)
	}
}

func TestMuxUnknownChannelRequests(t *testing.T) {
	clientPipe, serverPipe := memPipe()
	client := newMux(clientPipe)
	defer serverPipe.Close()
	defer client.Close()

	kDone := make(chan error, 1)
	go func() {
		// Ignore unknown channel messages that don't want a reply.
		err := serverPipe.writePacket(Marshal(channelRequestMsg{
			PeersID:             1,
			Request:             "keepalive@openssh.com",
			WantReply:           false,
			RequestSpecificData: []byte{},
		}))
		if err != nil {
			kDone <- fmt.Errorf("send: %w", err)
			return
		}

		// Send a keepalive, which should get a channel failure message
		// in response.
		err = serverPipe.writePacket(Marshal(channelRequestMsg{
			PeersID:             2,
			Request:             "keepalive@openssh.com",
			WantReply:           true,
			RequestSpecificData: []byte{},
		}))
		if err != nil {
			kDone <- fmt.Errorf("send: %w", err)
			return
		}

		packet, err := serverPipe.readPacket()
		if err != nil {
			kDone <- fmt.Errorf("read packet: %w", err)
			return
		}
		decoded, err := decode(packet)
		if err != nil {
			kDone <- fmt.Errorf("decode failed: %w", err)
			return
		}

		switch msg := decoded.(type) {
		case *channelRequestFailureMsg:
			if msg.PeersID != 2 {
				kDone <- fmt.Errorf("received response to wrong message: %v", msg)
				return

			}
		default:
			kDone <- fmt.Errorf("unexpected channel message: %v", msg)
			return
		}

		kDone <- nil

		// Receive and respond to the keepalive to confirm the mux is
		// still processing requests.
		packet, err = serverPipe.readPacket()
		if err != nil {
			kDone <- fmt.Errorf("read packet: %w", err)
			return
		}
		if packet[0] != msgGlobalRequest {
			kDone <- errors.New("expected global request")
			return
		}

		err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
			Data: []byte{},
		}))
		if err != nil {
			kDone <- fmt.Errorf("failed to send failure msg: %w", err)
			return
		}

		close(kDone)
	}()

	// Wait for the server to send the keepalive message and receive back a
	// response.
	if err := <-kDone; err != nil {
		t.Fatal(err)
	}

	// Confirm client hasn't closed.
	if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil {
		t.Fatalf("failed to send keepalive: %v", err)
	}

	// Wait for the server to shut down.
	if err := <-kDone; err != nil {
		t.Fatal(err)
	}
}

func TestMuxClosedChannel(t *testing.T) {
	clientPipe, serverPipe := memPipe()
	client := newMux(clientPipe)
	defer serverPipe.Close()
	defer client.Close()

	kDone := make(chan error, 1)
	go func() {
		// Open the channel.
		packet, err := serverPipe.readPacket()
		if err != nil {
			kDone <- fmt.Errorf("read packet: %w", err)
			return
		}
		if packet[0] != msgChannelOpen {
			kDone <- errors.New("expected chan open")
			return
		}

		var openMsg channelOpenMsg
		if err := Unmarshal(packet, &openMsg); err != nil {
			kDone <- fmt.Errorf("unmarshal: %w", err)
			return
		}

		// Send back the opened channel confirmation.
		err = serverPipe.writePacket(Marshal(channelOpenConfirmMsg{
			PeersID:       openMsg.PeersID,
			MyID:          0,
			MyWindow:      0,
			MaxPacketSize: channelMaxPacket,
		}))
		if err != nil {
			kDone <- fmt.Errorf("send: %w", err)
			return
		}

		// Close the channel.
		err = serverPipe.writePacket(Marshal(channelCloseMsg{
			PeersID: openMsg.PeersID,
		}))
		if err != nil {
			kDone <- fmt.Errorf("send: %w", err)
			return
		}

		// Send a keepalive message on the channel we just closed.
		err = serverPipe.writePacket(Marshal(channelRequestMsg{
			PeersID:             openMsg.PeersID,
			Request:             "keepalive@openssh.com",
			WantReply:           true,
			RequestSpecificData: []byte{},
		}))
		if err != nil {
			kDone <- fmt.Errorf("send: %w", err)
			return
		}

		// Receive the channel closed response.
		packet, err = serverPipe.readPacket()
		if err != nil {
			kDone <- fmt.Errorf("read packet: %w", err)
			return
		}
		if packet[0] != msgChannelClose {
			kDone <- errors.New("expected channel close")
			return
		}

		// Receive the keepalive response failure.
		packet, err = serverPipe.readPacket()
		if err != nil {
			kDone <- fmt.Errorf("read packet: %w", err)
			return
		}
		if packet[0] != msgChannelFailure {
			kDone <- errors.New("expected channel failure")
			return
		}
		kDone <- nil

		// Receive and respond to the keepalive to confirm the mux is
		// still processing requests.
		packet, err = serverPipe.readPacket()
		if err != nil {
			kDone <- fmt.Errorf("read packet: %w", err)
			return
		}
		if packet[0] != msgGlobalRequest {
			kDone <- errors.New("expected global request")
			return
		}

		err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
			Data: []byte{},
		}))
		if err != nil {
			kDone <- fmt.Errorf("failed to send failure msg: %w", err)
			return
		}

		close(kDone)
	}()

	// Open a channel.
	ch, err := client.openChannel("chan", nil)
	if err != nil {
		t.Fatalf("OpenChannel: %v", err)
	}
	defer ch.Close()

	// Wait for the server to close the channel and send the keepalive.
	<-kDone

	// Make sure the channel closed.
	if _, ok := <-ch.incomingRequests; ok {
		t.Fatalf("channel not closed")
	}

	// Confirm client hasn't closed
	if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil {
		t.Fatalf("failed to send keepalive: %v", err)
	}

	// Wait for the server to shut down.
	<-kDone
}

func TestMuxGlobalRequest(t *testing.T) {
	var sawPeek bool
	var wg sync.WaitGroup
	defer func() {
		wg.Wait()
		if !sawPeek {
			t.Errorf("never saw 'peek' request")
		}
	}()

	clientMux, serverMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	wg.Add(1)
	go func() {
		defer wg.Done()
		for r := range serverMux.incomingRequests {
			sawPeek = sawPeek || r.Type == "peek"
			if r.WantReply {
				err := r.Reply(r.Type == "yes",
					append([]byte(r.Type), r.Payload...))
				if err != nil {
					t.Errorf("AckRequest: %v", err)
				}
			}
		}
	}()

	_, _, err := clientMux.SendRequest("peek", false, nil)
	if err != nil {
		t.Errorf("SendRequest: %v", err)
	}

	ok, data, err := clientMux.SendRequest("yes", true, []byte("a"))
	if !ok || string(data) != "yesa" || err != nil {
		t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
			ok, data, err)
	}
	if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil {
		t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
			ok, data, err)
	}

	if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil {
		t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
			ok, data, err)
	}
}

func TestMuxGlobalRequestUnblock(t *testing.T) {
	clientMux, serverMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	result := make(chan error, 1)
	go func() {
		_, _, err := clientMux.SendRequest("hello", true, nil)
		result <- err
	}()

	<-serverMux.incomingRequests
	serverMux.conn.Close()
	err := <-result

	if err != io.EOF {
		t.Errorf("want EOF, got %v", io.EOF)
	}
}

func TestMuxChannelRequestUnblock(t *testing.T) {
	a, b, connB := channelPair(t)
	defer a.Close()
	defer b.Close()
	defer connB.Close()

	result := make(chan error, 1)
	go func() {
		_, err := a.SendRequest("hello", true, nil)
		result <- err
	}()

	<-b.incomingRequests
	connB.conn.Close()
	err := <-result

	if err != io.EOF {
		t.Errorf("want EOF, got %v", err)
	}
}

func TestMuxCloseChannel(t *testing.T) {
	r, w, mux := channelPair(t)
	defer mux.Close()
	defer r.Close()
	defer w.Close()

	result := make(chan error, 1)
	go func() {
		var b [1024]byte
		_, err := r.Read(b[:])
		result <- err
	}()
	if err := w.Close(); err != nil {
		t.Errorf("w.Close: %v", err)
	}

	if _, err := w.Write([]byte("hello")); err != io.EOF {
		t.Errorf("got err %v, want io.EOF after Close", err)
	}

	if err := <-result; err != io.EOF {
		t.Errorf("got %v (%T), want io.EOF", err, err)
	}
}

func TestMuxCloseWriteChannel(t *testing.T) {
	r, w, mux := channelPair(t)
	defer mux.Close()

	result := make(chan error, 1)
	go func() {
		var b [1024]byte
		_, err := r.Read(b[:])
		result <- err
	}()
	if err := w.CloseWrite(); err != nil {
		t.Errorf("w.CloseWrite: %v", err)
	}

	if _, err := w.Write([]byte("hello")); err != io.EOF {
		t.Errorf("got err %v, want io.EOF after CloseWrite", err)
	}

	if err := <-result; err != io.EOF {
		t.Errorf("got %v (%T), want io.EOF", err, err)
	}
}

func TestMuxInvalidRecord(t *testing.T) {
	a, b := muxPair()
	defer a.Close()
	defer b.Close()

	packet := make([]byte, 1+4+4+1)
	packet[0] = msgChannelData
	marshalUint32(packet[1:], 29348723 /* invalid channel id */)
	marshalUint32(packet[5:], 1)
	packet[9] = 42

	a.conn.writePacket(packet)
	go a.SendRequest("hello", false, nil)
	// 'a' wrote an invalid packet, so 'b' has exited.
	req, ok := <-b.incomingRequests
	if ok {
		t.Errorf("got request %#v after receiving invalid packet", req)
	}
}

func TestZeroWindowAdjust(t *testing.T) {
	a, b, mux := channelPair(t)
	defer a.Close()
	defer b.Close()
	defer mux.Close()

	go func() {
		io.WriteString(a, "hello")
		// bogus adjust.
		a.sendMessage(windowAdjustMsg{})
		io.WriteString(a, "world")
		a.Close()
	}()

	want := "helloworld"
	c, _ := io.ReadAll(b)
	if string(c) != want {
		t.Errorf("got %q want %q", c, want)
	}
}

func TestMuxMaxPacketSize(t *testing.T) {
	a, b, mux := channelPair(t)
	defer a.Close()
	defer b.Close()
	defer mux.Close()

	large := make([]byte, a.maxRemotePayload+1)
	packet := make([]byte, 1+4+4+1+len(large))
	packet[0] = msgChannelData
	marshalUint32(packet[1:], a.remoteId)
	marshalUint32(packet[5:], uint32(len(large)))
	packet[9] = 42

	if err := a.mux.conn.writePacket(packet); err != nil {
		t.Errorf("could not send packet")
	}

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)
	go func() {
		a.SendRequest("hello", false, nil)
		wg.Done()
	}()

	_, ok := <-b.incomingRequests
	if ok {
		t.Errorf("connection still alive after receiving large packet.")
	}
}

func TestMuxChannelWindowDeferredUpdates(t *testing.T) {
	s, c, mux := channelPair(t)
	cTransport := mux.conn.(*memTransport)
	defer s.Close()
	defer c.Close()
	defer mux.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)

	data := make([]byte, 1024)

	wg.Add(1)
	go func() {
		defer wg.Done()
		_, err := s.Write(data)
		if err != nil {
			t.Errorf("Write: %v", err)
			return
		}
	}()
	cWritesInit := cTransport.getWriteCount()
	buf := make([]byte, 1)
	for i := 0; i < len(data); i++ {
		n, err := c.Read(buf)
		if n != len(buf) || err != nil {
			t.Fatalf("Read: %v, %v", n, err)
		}
	}
	cWrites := cTransport.getWriteCount() - cWritesInit
	// reading 1 KiB should not cause any window updates to be sent, but allow
	// for some unexpected writes
	if cWrites > 30 {
		t.Fatalf("reading 1 KiB from channel caused %v writes", cWrites)
	}
}

func TestMuxChannelRejectRemovesFromMux(t *testing.T) {
	serverMux, clientMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	var wg sync.WaitGroup
	t.Cleanup(wg.Wait)
	wg.Add(1)

	go func() {
		defer wg.Done()

		// The server waits for the channel creation request
		newCh, ok := <-serverMux.incomingChannels
		if !ok {
			t.Error("failed to accept channel")
			return
		}
		ch := newCh.(*channel)

		if serverMux.chanList.getChan(ch.localId) == nil {
			t.Errorf("channel %d is not in the chanList before Reject", ch.localId)
		}

		if err := ch.Reject(Prohibited, "rejecting this channel"); err != nil {
			t.Errorf("Reject failed: %v", err)
		}

		if serverMux.chanList.getChan(ch.localId) != nil {
			t.Errorf("channel %d is still in the chanList after Reject", ch.localId)
		}
	}()

	_, _, err := clientMux.OpenChannel("test_leak", nil)

	if err == nil {
		t.Fatal("expected an error (channel rejected), but got nil")
	}

	if _, ok := err.(*OpenChannelError); !ok {
		t.Errorf("expected *OpenChannelError, got: %T", err)
	}
}

// Don't ship code with debug=true.
func TestDebug(t *testing.T) {
	if debugMux {
		t.Error("mux debug switched on")
	}
	if debugHandshake {
		t.Error("handshake debug switched on")
	}
	if debugTransport {
		t.Error("transport debug switched on")
	}
}

func TestMuxUnexpectedGlobalResponsesDiscarded(t *testing.T) {
	clientPipe, serverPipe := memPipe()
	client := newMux(clientPipe)
	defer serverPipe.Close()
	defer client.Close()

	done := make(chan error, 1)
	go func() {
		// Send multiple unexpected global responses, this should not block the
		// globalResponses channel.
		for i := range 5 {
			err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
				Data: []byte{byte(i)},
			}))
			if err != nil {
				done <- fmt.Errorf("send success msg %d: %w", i, err)
				return
			}
		}
		for i := range 5 {
			err := serverPipe.writePacket(Marshal(globalRequestFailureMsg{
				Data: []byte{byte(i)},
			}))
			if err != nil {
				done <- fmt.Errorf("send failure msg %d: %w", i, err)
				return
			}
		}

		// Now send a global request and wait for the response. This
		// verifies the mux is still processing packets.
		err := serverPipe.writePacket(Marshal(globalRequestMsg{
			Type:      "keepalive@golang.org",
			WantReply: true,
			Data:      nil,
		}))
		if err != nil {
			done <- fmt.Errorf("send global request: %w", err)
			return
		}

		packet, err := serverPipe.readPacket()
		if err != nil {
			done <- fmt.Errorf("read packet: %w", err)
			return
		}
		decoded, err := decode(packet)
		if err != nil {
			done <- fmt.Errorf("decode: %w", err)
			return
		}
		switch decoded.(type) {
		case *globalRequestSuccessMsg, *globalRequestFailureMsg:
			// Expected response
		default:
			done <- fmt.Errorf("unexpected packet type: %T", decoded)
			return
		}
		done <- nil
	}()

	// Handle the incoming request from the server and reply
	req, ok := <-client.incomingRequests
	if !ok {
		t.Fatal("incomingRequests channel closed unexpectedly")
	}
	if req.Type != "keepalive@golang.org" {
		t.Fatalf("unexpected request type: %s", req.Type)
	}
	if err := req.Reply(true, nil); err != nil {
		t.Fatalf("Reply: %v", err)
	}

	if err := <-done; err != nil {
		t.Fatal(err)
	}
}

func TestMuxConcurrentGlobalRequests(t *testing.T) {
	clientMux, serverMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	const numRequests = 50

	serverDone := make(chan struct{})
	go func() {
		defer close(serverDone)
		for r := range serverMux.incomingRequests {
			if r.WantReply {
				replyData := append([]byte("reply:"), r.Payload...)
				r.Reply(true, replyData)
			}
		}
	}()

	var clientWg sync.WaitGroup
	clientWg.Add(numRequests)

	errCh := make(chan error, numRequests)

	for i := range numRequests {
		go func(id int) {
			defer clientWg.Done()

			payloadStr := fmt.Sprintf("req-%d", id)
			payload := []byte(payloadStr)

			// This call blocks until the globalSentMu is acquired.
			// The mutex ensures that even with many concurrent attempts,
			// the "drain" and "send" logic happens atomically per request.
			ok, data, err := clientMux.SendRequest("echo", true, payload)
			if err != nil {
				errCh <- fmt.Errorf("req %d error: %v", id, err)
				return
			}
			if !ok {
				errCh <- fmt.Errorf("req %d failed (want success)", id)
				return
			}

			expected := "reply:" + payloadStr
			if string(data) != expected {
				errCh <- fmt.Errorf("req %d mismatch: got %q, want %q", id, string(data), expected)
			}
		}(i)
	}

	clientWg.Wait()
	close(errCh)

	for err := range errCh {
		if err != nil {
			t.Fatal(err)
		}
	}

	clientMux.Close()
	<-serverDone
}

func TestMuxGlobalResponseDroppedWhenIdle(t *testing.T) {
	clientPipe, serverPipe := memPipe()
	clientMux := newMux(clientPipe)
	defer serverPipe.Close()
	defer clientMux.Close()

	errCh := make(chan error, 1)
	go func() {
		// Send a spurious response while no SendRequest is pending.
		if err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
			Data: []byte("spurious"),
		})); err != nil {
			errCh <- fmt.Errorf("send spurious: %w", err)
			return
		}
		// Follow with a global request; once the client observes this on
		// incomingRequests, the mux loop has necessarily processed (and
		// dropped) the prior spurious response.
		if err := serverPipe.writePacket(Marshal(globalRequestMsg{
			Type:      "sync@example.com",
			WantReply: false,
		})); err != nil {
			errCh <- fmt.Errorf("send sync request: %w", err)
			return
		}
		errCh <- nil
	}()

	if err := <-errCh; err != nil {
		t.Fatal(err)
	}

	req, ok := <-clientMux.incomingRequests
	if !ok {
		t.Fatal("incomingRequests closed unexpectedly")
	}
	if req.Type != "sync@example.com" {
		t.Fatalf("unexpected sync request type %q", req.Type)
	}

	// The spurious response preceded the sync request, so by now the mux
	// loop has processed it. The pending-gate must have caused it to be
	// dropped rather than buffered.
	if n := len(clientMux.globalResponses); n != 0 {
		t.Fatalf("globalResponses buffer should be empty after idle drop, has %d entries", n)
	}
}

func TestMuxStaleResponseDrained(t *testing.T) {
	// Simulate a stale response sitting in globalResponses (e.g. a response
	// that slipped in through the pending-gate on a prior SendRequest that
	// exited without consuming it). The drain step in the next SendRequest
	// must discard it so the caller receives the correct reply.
	clientMux, serverMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	clientMux.globalResponses <- &globalRequestSuccessMsg{Data: []byte("stale")}

	serverDone := make(chan struct{})
	go func() {
		defer close(serverDone)
		for req := range serverMux.incomingRequests {
			if req.WantReply {
				req.Reply(true, append([]byte("reply:"), req.Payload...))
			}
		}
	}()

	ok, data, err := clientMux.SendRequest("test", true, []byte("hello"))
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	if !ok {
		t.Fatal("expected success response")
	}
	if string(data) != "reply:hello" {
		t.Fatalf("got %q, want %q (drain did not remove stale response)", data, "reply:hello")
	}

	clientMux.Close()
	<-serverDone
}

func TestMuxGlobalResponseAcceptedWhilePending(t *testing.T) {
	// Positive control: when a SendRequest is actually pending, the
	// response must be delivered (the gate is open).
	clientMux, serverMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	serverDone := make(chan struct{})
	go func() {
		defer close(serverDone)
		for req := range serverMux.incomingRequests {
			if req.WantReply {
				req.Reply(true, []byte("pong"))
			}
		}
	}()

	ok, data, err := clientMux.SendRequest("ping", true, nil)
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}
	if !ok || string(data) != "pong" {
		t.Fatalf("unexpected response: ok=%v data=%q", ok, data)
	}

	clientMux.Close()
	<-serverDone
}

func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
	// A malicious peer that spams channelRequestSuccess/Failure messages
	// for an open, idle channel must not be able to stall the mux read
	// loop by filling ch.msg. After the flood, the channel must still be
	// usable: a subsequent legitimate SendRequest receives its reply.
	clientMux, serverMux := muxPair()
	defer serverMux.Close()
	defer clientMux.Close()

	serverRes := make(chan *channel, 1)
	go func() {
		newCh, ok := <-serverMux.incomingChannels
		if !ok {
			close(serverRes)
			return
		}
		c, _, err := newCh.Accept()
		if err != nil {
			close(serverRes)
			return
		}
		serverRes <- c.(*channel)
	}()

	clientCh, err := clientMux.openChannel("chan", nil)
	if err != nil {
		t.Fatalf("openChannel: %v", err)
	}
	serverCh := <-serverRes
	if serverCh == nil {
		t.Fatal("server did not accept channel")
	}

	// Spam many unsolicited success/failure responses. More than chanSize
	// to ensure ch.msg would overflow without the pending-gate.
	const spam = chanSize * 4
	done := make(chan error, 1)
	go func() {
		for i := range spam {
			if err := serverCh.ackRequest(i%2 == 0); err != nil {
				done <- fmt.Errorf("ackRequest %d: %w", i, err)
				return
			}
		}
		// Echo any legitimate request back.
		for req := range serverCh.incomingRequests {
			if req.WantReply {
				if err := req.Reply(true, append([]byte("reply:"), req.Payload...)); err != nil {
					done <- fmt.Errorf("reply: %w", err)
					return
				}
			}
		}
		done <- nil
	}()

	// If the flood had wedged the mux loop, this SendRequest would never
	// receive a reply.
	ok, err := clientCh.SendRequest("ping", true, []byte("hello"))
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}
	if !ok {
		t.Fatal("expected success reply")
	}

	// Clean up so the server goroutine can exit.
	clientCh.Close()
	serverCh.Close()
	if err := <-done; err != nil {
		if !errors.Is(err, io.EOF) {
			t.Fatal(err)
		}
	}
}

func TestChannelCloseIdempotent(t *testing.T) {
	a, b := memPipe()
	defer a.Close()
	defer b.Close()

	m := newMux(a)
	ch := m.newChannel("session", channelOutbound, nil)

	ch.close()
	ch.close() // must be a no-op, not a panic
}

func TestNewChannelFullyInitializedWhenPublished(t *testing.T) {
	a, b := memPipe()
	defer a.Close()
	defer b.Close()

	m := newMux(a)

	const channels = 100
	stop := make(chan struct{})
	readerDone := make(chan struct{})
	go func() {
		// Mimic the mux loop: fetch channels by id and read the fields it
		// uses to route and validate packets.
		defer close(readerDone)
		for {
			select {
			case <-stop:
				return
			default:
			}
			for id := uint32(0); id < channels; id++ {
				ch := m.chanList.getChan(id)
				if ch == nil {
					continue
				}
				if got := ch.localId; got != id {
					t.Errorf("channel registered under id %d has localId %d", id, got)
					return
				}
				if got := ch.maxIncomingPayload; got != channelMaxPacket {
					t.Errorf("channel %d: maxIncomingPayload = %d, want %d", id, got, channelMaxPacket)
					return
				}
			}
		}
	}()

	for range channels {
		ch := m.newChannel("session", channelOutbound, nil)
		if m.chanList.getChan(ch.localId) != ch {
			t.Fatalf("channel %d not registered under its localId", ch.localId)
		}
	}
	close(stop)
	<-readerDone
}

func TestChannelConcurrentRequests(t *testing.T) {
	writer, reader, mux := channelPair(t)
	defer writer.Close()
	defer reader.Close()
	defer mux.Close()

	serverDone := make(chan struct{})
	go func() {
		defer close(serverDone)
		for req := range writer.incomingRequests {
			if req.WantReply {
				req.Reply(true, append([]byte("reply:"), req.Payload...))
			}
		}
	}()

	const numRequests = 50
	var wg sync.WaitGroup
	wg.Add(numRequests)
	errCh := make(chan error, numRequests)

	for i := 0; i < numRequests; i++ {
		go func(id int) {
			defer wg.Done()
			payload := []byte(fmt.Sprintf("req-%d", id))
			ok, err := reader.SendRequest("echo", true, payload)
			if err != nil {
				errCh <- fmt.Errorf("req %d: %v", id, err)
				return
			}
			if !ok {
				errCh <- fmt.Errorf("req %d: expected success", id)
			}
		}(i)
	}

	wg.Wait()
	close(errCh)

	for err := range errCh {
		if err != nil {
			t.Fatal(err)
		}
	}

	reader.Close()
	writer.Close()
	<-serverDone
}

func TestChannelResponseDroppedWhenIdle(t *testing.T) {
	// A spurious response arriving while no SendRequest is pending must
	// be dropped rather than buffered in ch.msg.
	writer, reader, mux := channelPair(t)
	defer writer.Close()
	defer reader.Close()
	defer mux.Close()

	// Server sends an unsolicited reply, then a request so we can
	// synchronise: once the client observes the request, the mux loop has
	// necessarily processed (and dropped) the prior spurious reply.
	errCh := make(chan error, 1)
	go func() {
		if err := writer.ackRequest(true); err != nil {
			errCh <- err
			return
		}
		if _, err := writer.SendRequest("sync", false, nil); err != nil {
			errCh <- err
			return
		}
		errCh <- nil
	}()

	req := <-reader.incomingRequests
	if req.Type != "sync" {
		t.Fatalf("unexpected request type %q", req.Type)
	}

	if n := len(reader.msg); n != 0 {
		t.Fatalf("ch.msg should be empty after idle drop, has %d entries", n)
	}

	if err := <-errCh; err != nil {
		t.Fatal(err)
	}
}

func TestChannelStaleResponseDrained(t *testing.T) {
	// Simulate a stale response sitting in ch.msg (e.g. a response that
	// slipped through the pending-gate on a prior SendRequest that exited
	// without consuming it). The drain step in the next SendRequest must
	// discard it so the caller receives the correct reply.
	writer, reader, mux := channelPair(t)
	defer writer.Close()
	defer reader.Close()
	defer mux.Close()

	reader.msg <- &channelRequestSuccessMsg{PeersID: reader.remoteId}

	serverDone := make(chan struct{})
	go func() {
		defer close(serverDone)
		for req := range writer.incomingRequests {
			if req.WantReply {
				req.Reply(false, append([]byte("nack:"), req.Payload...))
			}
		}
	}()

	ok, err := reader.SendRequest("test", true, []byte("hello"))
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}
	// If the stale success had been consumed, ok would be true.
	if ok {
		t.Fatal("got stale success response; drain did not remove it")
	}

	reader.Close()
	writer.Close()
	<-serverDone
}

func TestChannelResponseAcceptedWhilePending(t *testing.T) {
	// Positive control: when a SendRequest is actually pending, the
	// response must be delivered (the gate is open).
	writer, reader, mux := channelPair(t)
	defer writer.Close()
	defer reader.Close()
	defer mux.Close()

	serverDone := make(chan struct{})
	go func() {
		defer close(serverDone)
		for req := range writer.incomingRequests {
			if req.WantReply {
				req.Reply(true, nil)
			}
		}
	}()

	ok, err := reader.SendRequest("ping", true, nil)
	if err != nil {
		t.Fatalf("SendRequest: %v", err)
	}
	if !ok {
		t.Fatal("expected success")
	}

	reader.Close()
	writer.Close()
	<-serverDone
}

func TestChannelSendRequestAfterCloseNoSpinloop(t *testing.T) {
	writer, reader, mux := channelPair(t)
	defer writer.Close()
	defer mux.Close()

	// Close the channel, this closes reader.msg.
	if err := reader.Close(); err != nil {
		t.Fatalf("Close: %v", err)
	}
	// Wait for reader.msg to actually be closed by the mux loop.
	for {
		select {
		case _, ok := <-reader.msg:
			if !ok {
				goto closed
			}
		case <-time.After(2 * time.Second):
			t.Fatal("reader.msg was not closed")
		}
	}
closed:

	done := make(chan error, 1)
	go func() {
		_, err := reader.SendRequest("ping", true, nil)
		done <- err
	}()

	select {
	case err := <-done:
		if !errors.Is(err, io.EOF) {
			t.Fatalf("SendRequest after close: got %v, want io.EOF", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("SendRequest spinloop on closed ch.msg")
	}
}

func TestMuxSendRequestAfterCloseNoSpinloop(t *testing.T) {
	a, b := muxPair()
	defer a.Close()

	// Closing the underlying conn causes mux.loop to exit and close
	// b.globalResponses.
	if err := b.Close(); err != nil {
		t.Fatalf("Close: %v", err)
	}
	// Wait for globalResponses to actually be closed by loop().
	for {
		select {
		case _, ok := <-b.globalResponses:
			if !ok {
				goto closed
			}
		case <-time.After(2 * time.Second):
			t.Fatal("globalResponses was not closed")
		}
	}
closed:

	done := make(chan error, 1)
	go func() {
		_, _, err := b.SendRequest("ping", true, nil)
		done <- err
	}()

	select {
	case err := <-done:
		if err == nil {
			t.Fatal("SendRequest after close: got nil error")
		}
	case <-time.After(2 * time.Second):
		t.Fatal("SendRequest spinloop on closed globalResponses")
	}
}
