mirror of
https://github.com/soypat/lneto.git
synced 2026-09-01 04:19:05 +00:00
fix bug being unable to read from closed tcp.Conn
This commit is contained in:
+12
-3
@@ -260,6 +260,9 @@ func (conn *Conn) Flush() error {
|
|||||||
func (conn *Conn) Read(b []byte) (int, error) {
|
func (conn *Conn) Read(b []byte) (int, error) {
|
||||||
connid, err := conn.lockPipeConnID()
|
connid, err := conn.lockPipeConnID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if conn.BufferedInput() > 0 {
|
||||||
|
return conn.handlerRead(b) // Ensure remaining buffered data is read.
|
||||||
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
lport := conn.LocalPort()
|
lport := conn.LocalPort()
|
||||||
@@ -272,14 +275,20 @@ func (conn *Conn) Read(b []byte) (int, error) {
|
|||||||
// No use waiting for data, jump to read and return corresponding error from there.
|
// No use waiting for data, jump to read and return corresponding error from there.
|
||||||
break
|
break
|
||||||
} else if err := conn.checkPipe(connid, &conn.rdead); err != nil {
|
} else if err := conn.checkPipe(connid, &conn.rdead); err != nil {
|
||||||
|
if conn.BufferedInput() > 0 {
|
||||||
|
return conn.handlerRead(b) // Ensure remaining buffered data is read.
|
||||||
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
backoff.Miss()
|
backoff.Miss()
|
||||||
}
|
}
|
||||||
|
return conn.handlerRead(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) handlerRead(b []byte) (int, error) {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
n, err := conn.h.Read(b)
|
defer conn.mu.Unlock()
|
||||||
conn.mu.Unlock()
|
return conn.h.Read(b)
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) lockPipeConnID() (uint64, error) {
|
func (conn *Conn) lockPipeConnID() (uint64, error) {
|
||||||
|
|||||||
+1
-8
@@ -182,8 +182,7 @@ func (h *Handler) Recv(incomingPacket []byte) error {
|
|||||||
}
|
}
|
||||||
if h.scb.State() == StateClosed {
|
if h.scb.State() == StateClosed {
|
||||||
// TCB aborted, likely because it received an ACK in LastAck state.
|
// TCB aborted, likely because it received an ACK in LastAck state.
|
||||||
// Clean up connection now.
|
// Clean up connection now unless read pending.
|
||||||
h.reset(0, 0, 0)
|
|
||||||
return net.ErrClosed
|
return net.ErrClosed
|
||||||
}
|
}
|
||||||
if prevState != h.scb.State() {
|
if prevState != h.scb.State() {
|
||||||
@@ -332,18 +331,12 @@ func (h *Handler) Read(b []byte) (n int, err error) {
|
|||||||
// BufferedInput returns amount of bytes buffered in receive(input) buffer and ready to read
|
// BufferedInput returns amount of bytes buffered in receive(input) buffer and ready to read
|
||||||
// with a [Handler.Read] call.
|
// with a [Handler.Read] call.
|
||||||
func (h *Handler) BufferedInput() int {
|
func (h *Handler) BufferedInput() int {
|
||||||
if h.State().IsClosed() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return h.bufRx.Buffered()
|
return h.bufRx.Buffered()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufferedUnsent returns the number of bytes in the socket's transmit(output) buffer
|
// BufferedUnsent returns the number of bytes in the socket's transmit(output) buffer
|
||||||
// that has yet to be sent.
|
// that has yet to be sent.
|
||||||
func (h *Handler) BufferedUnsent() int {
|
func (h *Handler) BufferedUnsent() int {
|
||||||
if h.State().IsClosed() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return h.bufTx.BufferedUnsent()
|
return h.bufTx.BufferedUnsent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,3 +147,164 @@ func clear[E any, T []E](s T) {
|
|||||||
s[i] = zero
|
s[i] = zero
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBufferNotClearedOnPassiveClose tests that data remains readable after
|
||||||
|
// the TCP connection is closed by the remote peer. This is a regression test
|
||||||
|
// for a bug where the receive buffer was cleared when the connection transitioned
|
||||||
|
// to CLOSED state, causing data loss.
|
||||||
|
//
|
||||||
|
// The sequence is:
|
||||||
|
// 1. Server sends DATA + initiates close (FIN)
|
||||||
|
// 2. Client receives data, enters CLOSE_WAIT
|
||||||
|
// 3. Client sends ACK, then FIN+ACK (enters LAST_ACK)
|
||||||
|
// 4. Server sends final ACK
|
||||||
|
// 5. Client receives ACK in LAST_ACK -> state becomes CLOSED
|
||||||
|
// 6. At this point, client.Read() should still return the buffered data
|
||||||
|
//
|
||||||
|
// The bug was that reset() cleared bufRx when state became CLOSED.
|
||||||
|
func TestBufferNotClearedOnPassiveClose(t *testing.T) {
|
||||||
|
const mtu = 1500
|
||||||
|
const maxpackets = 3
|
||||||
|
rng := rand.New(rand.NewSource(1))
|
||||||
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
||||||
|
setupClientServer(t, rng, client, server)
|
||||||
|
var rawbuf [mtu]byte
|
||||||
|
establish(t, client, server, rawbuf[:])
|
||||||
|
|
||||||
|
// Server writes data to be sent.
|
||||||
|
data := []byte("hello world - this data should survive close")
|
||||||
|
n, err := server.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server write:", err)
|
||||||
|
} else if n != len(data) {
|
||||||
|
t.Fatal("expected server to write full data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server sends DATA packet.
|
||||||
|
clear(rawbuf[:])
|
||||||
|
n, err = server.Send(rawbuf[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server sending data:", err)
|
||||||
|
} else if n < len(data)+sizeHeaderTCP {
|
||||||
|
t.Fatal("expected server to send full data packet")
|
||||||
|
}
|
||||||
|
dataPacket := append([]byte(nil), rawbuf[:n]...) // Save for later use.
|
||||||
|
|
||||||
|
// Client receives DATA.
|
||||||
|
err = client.Recv(dataPacket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client receiving data:", err)
|
||||||
|
}
|
||||||
|
if client.BufferedInput() != len(data) {
|
||||||
|
t.Fatalf("client did not buffer data: got %d, want %d", client.BufferedInput(), len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server initiates close (will send FIN on next Send).
|
||||||
|
err = server.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server close:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server sends FIN (enters FIN_WAIT_1).
|
||||||
|
clear(rawbuf[:])
|
||||||
|
n, err = server.Send(rawbuf[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server sending FIN:", err)
|
||||||
|
}
|
||||||
|
if server.State() != StateFinWait1 {
|
||||||
|
t.Fatalf("expected server in FIN_WAIT_1, got %s", server.State())
|
||||||
|
}
|
||||||
|
finPacket := append([]byte(nil), rawbuf[:n]...)
|
||||||
|
|
||||||
|
// Client receives FIN (enters CLOSE_WAIT).
|
||||||
|
err = client.Recv(finPacket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client receiving FIN:", err)
|
||||||
|
}
|
||||||
|
if client.State() != StateCloseWait {
|
||||||
|
t.Fatalf("expected client in CLOSE_WAIT, got %s", client.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client sends ACK for FIN.
|
||||||
|
clear(rawbuf[:])
|
||||||
|
n, err = client.Send(rawbuf[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client sending ACK:", err)
|
||||||
|
}
|
||||||
|
ackPacket := append([]byte(nil), rawbuf[:n]...)
|
||||||
|
|
||||||
|
// Server receives ACK (enters FIN_WAIT_2).
|
||||||
|
err = server.Recv(ackPacket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server receiving ACK:", err)
|
||||||
|
}
|
||||||
|
if server.State() != StateFinWait2 {
|
||||||
|
t.Fatalf("expected server in FIN_WAIT_2, got %s", server.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client initiates its own close (will send FIN on next Send).
|
||||||
|
err = client.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client close:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client sends FIN (enters LAST_ACK).
|
||||||
|
clear(rawbuf[:])
|
||||||
|
n, err = client.Send(rawbuf[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client sending FIN:", err)
|
||||||
|
}
|
||||||
|
if client.State() != StateLastAck {
|
||||||
|
t.Fatalf("expected client in LAST_ACK, got %s", client.State())
|
||||||
|
}
|
||||||
|
clientFinPacket := append([]byte(nil), rawbuf[:n]...)
|
||||||
|
|
||||||
|
// Server receives client's FIN (enters TIME_WAIT).
|
||||||
|
err = server.Recv(clientFinPacket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server receiving client FIN:", err)
|
||||||
|
}
|
||||||
|
if server.State() != StateTimeWait {
|
||||||
|
t.Fatalf("expected server in TIME_WAIT, got %s", server.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server sends final ACK.
|
||||||
|
clear(rawbuf[:])
|
||||||
|
n, err = server.Send(rawbuf[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server sending final ACK:", err)
|
||||||
|
}
|
||||||
|
finalAckPacket := append([]byte(nil), rawbuf[:n]...)
|
||||||
|
if client.BufferedInput() == 0 {
|
||||||
|
t.Fatal("emptied buffer")
|
||||||
|
}
|
||||||
|
// Client receives final ACK (should enter CLOSED).
|
||||||
|
// This is where the bug manifests: the buffer gets cleared.
|
||||||
|
err = client.Recv(finalAckPacket)
|
||||||
|
// Note: client.Recv returns net.ErrClosed when state becomes CLOSED, that's expected.
|
||||||
|
if err != nil && err.Error() != "use of closed network connection" {
|
||||||
|
t.Fatal("client receiving final ACK:", err)
|
||||||
|
}
|
||||||
|
if client.State() != StateClosed {
|
||||||
|
t.Fatalf("expected client in CLOSED, got %s", client.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE BUG: At this point, the data should still be readable, but the
|
||||||
|
// buffer was cleared by reset() when state transitioned to CLOSED.
|
||||||
|
//
|
||||||
|
// This test will FAIL until the bug is fixed.
|
||||||
|
readBuf := make([]byte, mtu)
|
||||||
|
n, err = client.Read(readBuf)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
t.Fatalf("BUG: Could not read buffered data after connection closed: %v\n"+
|
||||||
|
"Expected to read %d bytes of data that was received before the connection closed.\n"+
|
||||||
|
"The receive buffer was incorrectly cleared when the connection transitioned to CLOSED state.",
|
||||||
|
err, len(data))
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
t.Fatalf("read wrong amount: got %d, want %d", n, len(data))
|
||||||
|
}
|
||||||
|
if !bytes.Equal(readBuf[:n], data) {
|
||||||
|
t.Fatalf("read wrong data: got %q, want %q", readBuf[:n], data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+196
-10
@@ -339,10 +339,6 @@ func (tst *tester) TestTCPEstablishedSingleData(srcStack, dstStack *StackAsync,
|
|||||||
func (tst *tester) TestTCPClose(stack1, stack2 *StackAsync, conn1, conn2 *tcp.Conn) {
|
func (tst *tester) TestTCPClose(stack1, stack2 *StackAsync, conn1, conn2 *tcp.Conn) {
|
||||||
t := tst.t
|
t := tst.t
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cid1 := conn1.ConnectionID()
|
|
||||||
cid2 := conn2.ConnectionID()
|
|
||||||
cid1v := *cid1
|
|
||||||
cid2v := *cid2
|
|
||||||
err := conn1.Close()
|
err := conn1.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -394,12 +390,6 @@ func (tst *tester) TestTCPClose(stack1, stack2 *StackAsync, conn1, conn2 *tcp.Co
|
|||||||
if !state2.IsClosed() {
|
if !state2.IsClosed() {
|
||||||
t.Errorf("expected closed state2, got %s", state2.String())
|
t.Errorf("expected closed state2, got %s", state2.String())
|
||||||
}
|
}
|
||||||
if cid1v == *cid1 {
|
|
||||||
t.Error("no cid1 change")
|
|
||||||
}
|
|
||||||
if cid2v == *cid2 {
|
|
||||||
t.Error("no cid2 change")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAsync) tcp.Segment {
|
func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAsync) tcp.Segment {
|
||||||
@@ -705,3 +695,199 @@ func (tst *tester) getARPOperation() arp.Operation {
|
|||||||
tst.t.Helper()
|
tst.t.Helper()
|
||||||
return arp.Operation(tst.getInt(ethernet.TypeARP, pcap.FieldClassOperation))
|
return arp.Operation(tst.getInt(ethernet.TypeARP, pcap.FieldClassOperation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTCPConn_BufferNotClearedOnPassiveClose tests that data remains readable after
|
||||||
|
// the TCP connection is closed by the remote peer. This is a regression test
|
||||||
|
// for a bug where the receive buffer was cleared when the connection transitioned
|
||||||
|
// to CLOSED state, causing data loss.
|
||||||
|
//
|
||||||
|
// The sequence is:
|
||||||
|
// 1. Server sends DATA then initiates close (FIN)
|
||||||
|
// 2. Client receives data, enters CLOSE_WAIT
|
||||||
|
// 3. Client sends ACK, then FIN+ACK (enters LAST_ACK)
|
||||||
|
// 4. Server sends final ACK
|
||||||
|
// 5. Client receives ACK in LAST_ACK -> state becomes CLOSED
|
||||||
|
// 6. At this point, client.Read() should still return the buffered data
|
||||||
|
//
|
||||||
|
// The bug was that reset() cleared bufRx when state became CLOSED.
|
||||||
|
func TestTCPConn_BufferNotClearedOnPassiveClose(t *testing.T) {
|
||||||
|
const seed = 9999
|
||||||
|
const MTU = 1500
|
||||||
|
const svPort = 8080
|
||||||
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
|
tst := testerFrom(t, MTU)
|
||||||
|
|
||||||
|
tst.TestTCPSetupAndEstablish(sv, client, svconn, clconn, svPort, 1337)
|
||||||
|
|
||||||
|
// Server writes data to be sent.
|
||||||
|
sendData := []byte("this data should survive close handshake")
|
||||||
|
_, err := svconn.Write(sendData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server write:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server sends DATA packet to client.
|
||||||
|
tst.bufmu.Lock()
|
||||||
|
buf := tst.buf[:cap(tst.buf)]
|
||||||
|
n, err := sv.Encapsulate(buf, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("server encapsulate data:", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("expected data packet from server")
|
||||||
|
}
|
||||||
|
err = client.Demux(buf[:n], 0)
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client demux data:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify client buffered the data.
|
||||||
|
if clconn.BufferedInput() != len(sendData) {
|
||||||
|
t.Fatalf("client did not buffer data: got %d, want %d", clconn.BufferedInput(), len(sendData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client sends ACK for data.
|
||||||
|
tst.bufmu.Lock()
|
||||||
|
buf = tst.buf[:cap(tst.buf)]
|
||||||
|
n, err = client.Encapsulate(buf, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("client encapsulate ACK:", err)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
err = sv.Demux(buf[:n], 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("server demux ACK:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
|
||||||
|
// Server initiates close.
|
||||||
|
err = svconn.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server close:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server sends FIN (enters FIN_WAIT_1).
|
||||||
|
tst.bufmu.Lock()
|
||||||
|
buf = tst.buf[:cap(tst.buf)]
|
||||||
|
n, err = sv.Encapsulate(buf, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("server encapsulate FIN:", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("expected FIN packet from server")
|
||||||
|
}
|
||||||
|
err = client.Demux(buf[:n], 0)
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client demux FIN:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if svconn.State() != tcp.StateFinWait1 {
|
||||||
|
t.Fatalf("expected server in FIN_WAIT_1, got %s", svconn.State())
|
||||||
|
}
|
||||||
|
if clconn.State() != tcp.StateCloseWait {
|
||||||
|
t.Fatalf("expected client in CLOSE_WAIT, got %s", clconn.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client sends ACK for FIN.
|
||||||
|
tst.bufmu.Lock()
|
||||||
|
buf = tst.buf[:cap(tst.buf)]
|
||||||
|
n, err = client.Encapsulate(buf, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("client encapsulate ACK:", err)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
err = sv.Demux(buf[:n], 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("server demux ACK:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
|
||||||
|
if svconn.State() != tcp.StateFinWait2 {
|
||||||
|
t.Fatalf("expected server in FIN_WAIT_2, got %s", svconn.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client initiates its close.
|
||||||
|
err = clconn.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client close:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client sends FIN (enters LAST_ACK).
|
||||||
|
tst.bufmu.Lock()
|
||||||
|
buf = tst.buf[:cap(tst.buf)]
|
||||||
|
n, err = client.Encapsulate(buf, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("client encapsulate FIN:", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("expected FIN packet from client")
|
||||||
|
}
|
||||||
|
err = sv.Demux(buf[:n], 0)
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("server demux client FIN:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if clconn.State() != tcp.StateLastAck {
|
||||||
|
t.Fatalf("expected client in LAST_ACK, got %s", clconn.State())
|
||||||
|
}
|
||||||
|
if svconn.State() != tcp.StateTimeWait {
|
||||||
|
t.Fatalf("expected server in TIME_WAIT, got %s", svconn.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server sends final ACK.
|
||||||
|
tst.bufmu.Lock()
|
||||||
|
buf = tst.buf[:cap(tst.buf)]
|
||||||
|
n, err = sv.Encapsulate(buf, -1, 0)
|
||||||
|
if err != nil {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("server encapsulate final ACK:", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
t.Fatal("expected final ACK from server")
|
||||||
|
}
|
||||||
|
err = client.Demux(buf[:n], 0)
|
||||||
|
tst.bufmu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("client demux final ACK:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client should now be CLOSED.
|
||||||
|
if clconn.State() != tcp.StateClosed {
|
||||||
|
t.Fatalf("expected client in CLOSED, got %s", clconn.State())
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE BUG: At this point, the data should still be readable, but the
|
||||||
|
// buffer was cleared by reset() when state transitioned to CLOSED.
|
||||||
|
//
|
||||||
|
// This test will FAIL until the bug is fixed.
|
||||||
|
readBuf := make([]byte, MTU)
|
||||||
|
n, err = clconn.Read(readBuf)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
t.Fatalf("BUG: Could not read buffered data after connection closed: %v\n"+
|
||||||
|
"Expected to read %d bytes of data that was received before the connection closed.\n"+
|
||||||
|
"The receive buffer was incorrectly cleared when the connection transitioned to CLOSED state.",
|
||||||
|
err, len(sendData))
|
||||||
|
}
|
||||||
|
if n != len(sendData) {
|
||||||
|
t.Fatalf("read wrong amount: got %d, want %d", n, len(sendData))
|
||||||
|
}
|
||||||
|
if !bytes.Equal(readBuf[:n], sendData) {
|
||||||
|
t.Fatalf("read wrong data: got %q, want %q", readBuf[:n], sendData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user