Compare commits

..

No commits in common. "bb14ec20a712ae4d079f9aba5cd4f0e59930b13c" and "1b43b9268741aaa8aa0ed93d95efcdde0eb8b3f5" have entirely different histories.

9 changed files with 30 additions and 649 deletions

View File

@ -1,71 +0,0 @@
package main
import "os"
import "io"
import "fmt"
import "log"
import "net"
import "time"
import "errors"
import "context"
import "git.tebibyte.media/sashakoshka/hopp"
import "git.tebibyte.media/sashakoshka/hopp/examples/ping"
func main() {
name := os.Args[0]
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s HOST:PORT\n", name)
os.Exit(2)
}
address := os.Args[1]
conn, err := dial(address)
handleErr(1, err)
trans, err := conn.OpenTrans()
handleErr(1, err)
go func() {
defer fmt.Fprintf(os.Stdout, "(i) disconnected\n")
for {
message, _, err := ping.Receive(trans)
if err != nil {
if !errors.Is(err, io.EOF) {
handleErr(1, err)
}
break
}
switch message := message.(type) {
case *ping.MessagePong:
log.Printf("--> pong (%d) from %v", message, address)
}
}
}()
message := ping.MessagePing(0)
for {
log.Printf("<-- ping (%d)", message)
_, err := ping.Send(trans, &message)
handleErr(1, err)
message ++
time.Sleep(time.Second)
}
}
func dial(address string) (hopp.Conn, error) {
// ctx, done := context.WithTimeout(context.Background(), 16 * time.Second)
// defer done()
// conn, err := hopp.Dial(ctx, "tcp", address, nil)
// if err != nil { return nil, err }
// return conn, nil
underlying, err := net.Dial("tcp", address)
if err != nil { return nil, err }
conn := hopp.AdaptA(underlying, hopp.PartyClient)
return conn, nil
}
func handleErr(code int, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
os.Exit(code)
}
}

View File

@ -1,143 +0,0 @@
package ping
// Code generated by the Holanet PDL compiler. DO NOT EDIT.
// The source file is located at <path>
// Please edit that file instead, and re-compile it to this location.
// HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz
import "fmt"
import "git.tebibyte.media/sashakoshka/hopp"
import "git.tebibyte.media/sashakoshka/hopp/tape"
// Table is a KTV table with an undefined schema.
type Table = map[uint16] any
// Message is any message that can be sent along this protocol.
type Message interface {
tape.Encodable
tape.Decodable
// Method returns the method code of the message.
Method() uint16
}
// Send encodes a message and sends it along a transaction.
func Send(trans hopp.Trans, message Message) (n int, err error) {
writer, err := trans.SendWriter(message.Method())
if err != nil { return n, err }
defer writer.Close()
encoder := tape.NewEncoder(writer)
n, err = message.Encode(encoder)
if err != nil { return n, err }
return n, encoder.Flush()
}
// canAssign determines if data from the given source tag can be assigned to
// a Go type represented by destination. It is designed to receive destination
// values from [generate.Generator.generateCanAssign]. The eventual Go type and
// the destination tag must come from the same (or hash-equivalent) PDL type.
func canAssign(destination, source tape.Tag) bool {
if destination.Is(source) { return true }
if (destination.Is(tape.SBA) || destination.Is(tape.LBA)) &&
(source.Is(tape.SBA) || source.Is(tape.LBA)) {
return true
}
return false
}
// boolInt converts a bool to an integer.
func boolInt(input bool) int {
if input {
return 1
} else {
return 0
}
}
// ensure ucontainer is always imported
var _ hopp.Option[int]
// Ping is sent by the client to the server. It may contain any number. This
// number will be returned to the client via a [Pong] message.
type MessagePing int32
// Method returns the message's method number.
func(this *MessagePing) Method() uint16 { return 0x0000 }
// Encode encodes this message's tag and value.
func(this *MessagePing) Encode(encoder *tape.Encoder) (n int, err error) {
tag_1 := tape.LSI.WithCN(3)
nn, err := encoder.WriteTag(tag_1)
n += nn; if err != nil { return n, err }
nn, err = encoder.WriteInt32(int32((*this)))
n += nn; if err != nil { return n, err }
return n, nil
}
// Decode decodes this message's tag and value.
func(this *MessagePing) Decode(decoder *tape.Decoder) (n int, err error) {
tag, nn, err := decoder.ReadTag()
n += nn; if err != nil { return n, err }
if !(canAssign(tape.LSI, tag)) {
nn, err = tape.Skim(decoder, tag)
n += nn; if err != nil { return n, err }
return n, nil
}
destination_2, nn, err := decoder.ReadInt32()
n += nn; if err != nil { return n, err }
*this = MessagePing(destination_2)
return n, nil
}
// Pong is sent by the server to the client in response to a [Ping] message, It
// will contain the same number as that message.
type MessagePong int32
// Method returns the message's method number.
func(this *MessagePong) Method() uint16 { return 0x0001 }
// Encode encodes this message's tag and value.
func(this *MessagePong) Encode(encoder *tape.Encoder) (n int, err error) {
tag_3 := tape.LSI.WithCN(3)
nn, err := encoder.WriteTag(tag_3)
n += nn; if err != nil { return n, err }
nn, err = encoder.WriteInt32(int32((*this)))
n += nn; if err != nil { return n, err }
return n, nil
}
// Decode decodes this message's tag and value.
func(this *MessagePong) Decode(decoder *tape.Decoder) (n int, err error) {
tag, nn, err := decoder.ReadTag()
n += nn; if err != nil { return n, err }
if !(canAssign(tape.LSI, tag)) {
nn, err = tape.Skim(decoder, tag)
n += nn; if err != nil { return n, err }
return n, nil
}
destination_4, nn, err := decoder.ReadInt32()
n += nn; if err != nil { return n, err }
*this = MessagePong(destination_4)
return n, nil
}
// Receive decodes a message from a transaction and returns it as a value.
// Use a type switch to determine what type of message it is.
func Receive(trans hopp.Trans) (message any, n int, err error) {
method, reader, err := trans.ReceiveReader()
decoder := tape.NewDecoder(reader)
if err != nil { return nil, n, err }
switch method {
case 0x0000:
var message MessagePing
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
case 0x0001:
var message MessagePong
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
}
return nil, n, fmt.Errorf("%w: M%04X", hopp.ErrUnknownMethod, method)
}

View File

@ -1,7 +0,0 @@
// Ping is sent by the client to the server. It may contain any number. This
// number will be returned to the client via a [Pong] message.
M0000 Ping I32
// Pong is sent by the server to the client in response to a [Ping] message, It
// will contain the same number as that message.
M0001 Pong I32

View File

@ -1,77 +0,0 @@
package main
import "os"
import "io"
import "fmt"
import "log"
import "errors"
import "git.tebibyte.media/sashakoshka/hopp"
import "git.tebibyte.media/sashakoshka/hopp/examples/ping"
func main() {
name := os.Args[0]
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s HOST:PORT\n", name)
os.Exit(2)
}
address := os.Args[1]
err := listen(address)
handleErr(1, err)
}
func listen(address string) error {
listener, err := hopp.Listen("tcp", address, nil)
if err != nil { return err }
log.Printf("(i) hosting on %s", address)
for {
conn, err := listener.Accept()
if err != nil { return err }
go run(conn)
}
}
func run(conn hopp.Conn) {
log.Printf("-=E %v connected", conn.RemoteAddr())
defer log.Printf("X=- %v disconnected", conn.RemoteAddr())
defer conn.Close()
for {
trans, err := conn.AcceptTrans()
if err != nil {
if !errors.Is(err, io.EOF) {
log.Printf("XXX %v failed: %v", conn.RemoteAddr(), err)
}
return
}
go runTrans(conn, trans)
}
}
func runTrans(conn hopp.Conn, trans hopp.Trans) {
for {
message, _, err := ping.Receive(trans)
if err != nil {
if !errors.Is(err, io.EOF) {
log.Printf("XXX failed to receive message: %v", err)
}
return
}
switch message := message.(type) {
case *ping.MessagePing:
log.Printf("--> ping (%d) from %v", message, conn.RemoteAddr())
response := ping.MessagePong(*message)
_, err := ping.Send(trans, &response)
if err != nil {
log.Printf("XXX failed to send message: %v", err)
return
}
}
}
}
func handleErr(code int, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
os.Exit(code)
}
}

View File

@ -276,32 +276,31 @@ func TestGenerateRunEncodeDecode(test *testing.T) {
}
testEncodeDecode(
&messageDynamic,
snake.O().AddL(0xE0, 14).AddS(
snake.L(0x00, 0x00, 0x20, 0x23),
snake.L(0x00, 0x01, 0x21, 0x32, 0x47),
snake.L(0x00, 0x02, 0x23, 0x87, 0x32, 0x45, 0x23),
snake.L(0x00, 0x03, 0x27, 0x32, 0x84, 0x02, 0x90, 0x34, 0x09, 0x82, 0x34),
snake.L(0x00, 0x04, 0x40, 0x23),
snake.L(0x00, 0x05, 0x41, 0x32, 0x47),
snake.L(0x00, 0x06, 0x43, 0x57, 0x32, 0x45, 0x23),
snake.L(0x00, 0x07, 0x47, 0x32, 0x84, 0x02, 0x90, 0x34, 0x09, 0x82, 0x34),
snake.L(0x00, 0x08, 0x63, 0x45, 0x12, 0x63, 0xCE),
snake.L(0x00, 0x09, 0x67, 0x40, 0x74, 0x4E, 0x3D, 0x6F, 0xCD, 0x17, 0x75),
snake.L(0x00, 0x0A, 0x87, 'f', 'o', 'x', ' ', 'b', 'e', 'd'),
snake.L(0x00, 0x0B, 0xC0, 0x04, 0x41,
tu.S(0xE0, 14).AddVar(
[]byte { 0x00, 0x00, 0x20, 0x23 },
[]byte { 0x00, 0x01, 0x21, 0x32, 0x47 },
[]byte { 0x00, 0x02, 0x23, 0x87, 0x32, 0x45, 0x23 },
[]byte { 0x00, 0x03, 0x27, 0x32, 0x84, 0x02, 0x90, 0x34, 0x09, 0x82, 0x34 },
[]byte { 0x00, 0x04, 0x40, 0x23 },
[]byte { 0x00, 0x05, 0x41, 0x32, 0x47 },
[]byte { 0x00, 0x06, 0x43, 0x57, 0x32, 0x45, 0x23 },
[]byte { 0x00, 0x07, 0x47, 0x32, 0x84, 0x02, 0x90, 0x34, 0x09, 0x82, 0x34 },
[]byte { 0x00, 0x08, 0x63, 0x45, 0x12, 0x63, 0xCE },
[]byte { 0x00, 0x09, 0x67, 0x40, 0x74, 0x4E, 0x3D, 0x6F, 0xCD, 0x17, 0x75 },
[]byte { 0x00, 0x0A, 0x87, 'f', 'o', 'x', ' ', 'b', 'e', 'd' },
[]byte { 0x00, 0x0B, 0xC0, 0x04, 0x41,
0x00, 0x07,
0x00, 0x06,
0x00, 0x05,
0x00, 0x04),
snake.L(0x00, 0x0C, 0xE0, 0x02,
0x00, 0x04 },
[]byte { 0x00, 0x0C, 0xE0, 0x02,
0x00, 0x01, 0x40, 0x08,
0x00, 0x02, 0x67, 0x40, 0x11, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A),
snake.O(snake.L(0x00, 0x0D, 0xE0, 0x03),
snake.S(
snake.L(0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00),
snake.L(0x00, 0x02, 0x82, 'h', 'i'),
snake.L(0x00, 0x03, 0x21, 0x39, 0x92)),
)))
0x00, 0x02, 0x67, 0x40, 0x11, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A },
[]byte { 0x00, 0x0D, 0xE0, 0x03, // ERR
0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00,
0x00, 0x02, 0x82, 'h', 'i',
0x00, 0x03, 0x21, 0x39, 0x92 },
))
`)
}

View File

@ -37,7 +37,6 @@ func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCa
import "reflect"
import "git.tebibyte.media/sashakoshka/hopp/tape"
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
import "git.tebibyte.media/sashakoshka/hopp/internal/testutil/snake"
` + imports
setup := `log.Println("*** BEGIN TEST CASE OUTPUT ***")`
teardown := `log.Println("--- END TEST CASE OUTPUT ---")`
@ -62,9 +61,8 @@ func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCa
func testDecode(correct Message, data any) {
var flat []byte
switch data := data.(type) {
case []byte: flat = data
case tu.Snake: flat = data.Flatten()
case snake.Snake: flat = data.Flatten()
case []byte: flat = data
case tu.Snake: flat = data.Flatten()
}
message := reflect.New(reflect.ValueOf(correct).Elem().Type()).Interface().(Message)
log.Println("before: ", message)
@ -81,7 +79,9 @@ func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCa
}
}
func testEncodeDecode(message Message, data any) {buffer := bytes.Buffer { }
// TODO: possibly combine the two above functions into this one,
// also take a data parameter here (snake)
func testEncodeDecode(message Message, data tu.Snake) {buffer := bytes.Buffer { }
log.Println("encoding:")
encoder := tape.NewEncoder(&buffer)
n, err := message.Encode(encoder)
@ -93,30 +93,13 @@ func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCa
if n != len(got) {
log.Fatalf("n incorrect: %d != %d\n", n, len(got))
}
var flat []byte
switch data := data.(type) {
case []byte:
flat = data
if ok, n := snake.Check(snake.L(data...), got); !ok {
log.Fatalln("not equal at", n)
}
case tu.Snake:
flat = data.Flatten()
if ok, n := data.Check(got); !ok {
log.Fatalln("not equal at", n)
}
case snake.Node:
flat = data.Flatten()
if ok, n := snake.Check(data, got); !ok {
log.Fatalln("not equal at", n)
}
default:
panic("AUSIAUGH AAAUUGUHGHGHH OUHGHGJDSGK")
if ok, n := data.Check(got); !ok {
log.Fatalln("not equal at", n)
}
log.Println("decoding:")
destination := reflect.New(reflect.ValueOf(message).Elem().Type()).Interface().(Message)
flat := data.Flatten()
log.Println("before: ", tu.Describe(destination))
decoder := tape.NewDecoder(bytes.NewBuffer(flat))
n, err = destination.Decode(decoder)

View File

@ -1,214 +0,0 @@
// Package snake lets you compare blocks of data where the ordering of certain
// parts may be swapped every which way. It is designed for comparing the
// encoding of maps where the ordering of individual elements is inconsistent.
package snake
import "fmt"
import "strings"
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
var _ Node = Order { }
var _ Node = Snake { }
var _ Node = Leaf { }
// Check checks the data against the specified node. If the data doesn't satisfy
// the node, or the comparison succeded but didn't consume all the data, this
// function returns false, and the index of the byte where the inequality is.
func Check(node Node, data []byte) (ok bool, n int) {
ok, n = node.Check(data)
if !ok {
return false, n
}
if n != len(data) {
return false, n
}
return true, n
}
// O returns a new order given a vararg node slice.
func O(nodes ...Node) Order {
return Order(nodes)
}
// S returns a new snake given a vararg node slice.
func S(nodes ...Node) Snake {
return Snake(nodes)
}
// L returns a new leaf given a vararg byte slice.
func L(data ...byte) Leaf {
return Leaf([]byte(data))
}
// Order is satisfied when the data satisfies each of its nodes in the order
// that they are specified in the slice.
type Order []Node
// Check determines if the data satisfies the Order.
func (this Order) Check(data []byte) (ok bool, n int) {
left := data
for _, node := range this {
ok, nn := node.Check(left)
n += nn; if !ok { return false, n }
left = left[nn:]
}
return true, n
}
// Flatten returns the Order flattened to a byte array. The result of this
// function always satisfies the Order.
func (this Order) Flatten() []byte {
flat := []byte { }
for _, node := range this {
flat = append(flat, node.Flatten()...)
}
return flat
}
func (this Order) String() string {
out := strings.Builder { }
for index, node := range this {
if index > 0 {
fmt.Fprint(&out, " :")
}
fmt.Fprintf(&out, " %v", node)
}
return out.String()
}
// Add returns a new order with the given nodes appended to it.
func (this Order) Add(nodes ...Node) Order {
newOrder := make(Order, len(this) + len(nodes))
copy(newOrder, this)
copy(newOrder[len(this):], Order(nodes))
return newOrder
}
// AddO returns a new order with the given order appended to it.
func (this Order) AddO(nodes ...Node) Order {
return this.Add(O(nodes...))
}
// AddS returns a new order with the given snake appended to it.
func (this Order) AddS(nodes ...Node) Order {
return this.Add(S(nodes...))
}
// AddL returns a new order with the given leaf appended to it.
func (this Order) AddL(data ...byte) Order {
return this.Add(L(data...))
}
// Snake is satisfied when the data satisfies each of its nodes in no particular
// order.
type Snake []Node
// Check determines if the data satisfies the snake.
func (this Snake) Check(data []byte) (ok bool, n int) {
fmt.Println("CHECKING SNAKE")
left := data
nodes := map[int] Node { }
for key, node := range this {
nodes[key] = node
}
for len(nodes) > 0 {
found := false
for key, node := range nodes {
fmt.Println(left, key, node)
ok, nn := node.Check(left)
fmt.Println(ok, nn)
if !ok { continue }
n += nn
left = data[n:]
delete(nodes, key)
found = true
break
}
if !found { return false, n }
}
return true, n
}
// Flatten returns the snake flattened to a byte array. The result of this
// function always satisfies the snake.
func (this Snake) Flatten() []byte {
flat := []byte { }
for _, node := range this {
flat = append(flat, node.Flatten()...)
}
return flat
}
func (this Snake) String() string {
out := strings.Builder { }
out.WriteString("[")
for index, node := range this {
if index > 0 {
fmt.Fprint(&out, " /")
}
fmt.Fprintf(&out, " %v", node)
}
out.WriteString(" ]")
return out.String()
}
// Add returns a new snake with the given nodes appended to it.
func (this Snake) Add(nodes ...Node) Snake {
newSnake := make(Snake, len(this) + len(nodes))
copy(newSnake, this)
copy(newSnake[len(this):], Snake(nodes))
return newSnake
}
// AddO returns a new snake with the given order appended to it.
func (this Snake) AddO(nodes ...Node) Snake {
return this.Add(O(nodes...))
}
// AddS returns a new snake with the given snake appended to it.
func (this Snake) AddS(nodes ...Node) Snake {
return this.Add(S(nodes...))
}
// AddL returns a new snake with the given leaf appended to it.
func (this Snake) AddL(data ... byte) Snake {
return this.Add(L(data...))
}
// Leaf is satisfied when the data matches it exactly.
type Leaf []byte
// Check determines if the data is equal to the leaf.
func (this Leaf) Check(data []byte) (ok bool, n int) {
if len(data) < len(this) {
return false, len(data)
}
for index, byt := range this {
if byt != data[index] {
return false, index
}
}
return true, len(this)
}
// This one's easy.
func (this Leaf) Flatten() []byte {
return []byte(this)
}
func (this Leaf) String() string {
return tu.HexBytes([]byte(this))
}
// Node represents a snake node.
type Node interface {
// Check determines if the data satisfies the node. If satisfied, the function
// will return true, and the index at which it stopped. If not, the
// function will return false, and the index of the first byte that
// didn't match. As long as the start of the data satisfies the node,
// whatever comes after it doesn't matter.
Check(data []byte) (ok bool, n int)
// Flatten returns the node flattened to a byte array. The result of
// this function always satisfies the node.
Flatten() []byte
}

View File

@ -1,89 +0,0 @@
package snake
import "testing"
func TestSnakeA(test *testing.T) {
snake := O().AddL(1, 6).AddS(
L(1),
L(2),
L(3),
L(4),
L(5),
).AddL(9)
test.Log(snake)
ok, n := Check(snake, []byte { 1, 6, 1, 2, 3, 4, 5, 9 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 1, 6, 5, 4, 3, 2, 1, 9 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 1, 6, 3, 1, 4, 2, 5, 9 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 1, 6, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 1, 2, 3, 4, 5, 6, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 0, 2, 3, 4, 5, 6, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 7, 1, 4, 2, 5, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 7, 3, 1, 4, 2, 5, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 7, 3, 1, 4, 2, 5, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 1, 2, 3, 4, 5, 9, 10})
if ok { test.Fatal("false positive:", n) }
}
func TestSnakeB(test *testing.T) {
snake := O().AddO(L(1), L(6)).AddS(
L(1),
L(2),
).AddL(9).AddS(
L(3, 2),
L(0),
L(1, 1, 2, 3),
)
test.Log(snake)
ok, n := Check(snake, []byte { 1, 6, 1, 2, 9, 3, 2, 0, 1, 1, 2, 3})
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 1, 6, 2, 1, 9, 0, 1, 1, 2, 3, 3, 2})
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 1, 6, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 1, 2, 9 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 9, 3, 2, 0, 1, 1, 2, 3})
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 2, 9, 0, 1, 1, 2, 3, 3, 2})
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 1, 6, 1, 2, 9, 3, 2, 1, 1, 2, 3})
if ok { test.Fatal("false positive:", n) }
}
func TestSnakeC(test *testing.T) {
snake := S(
L(1, 2, 3),
S(L(6), L(7), L(8)),
)
test.Log(snake)
ok, n := Check(snake, []byte { 1, 2, 3, 6, 7, 8 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 6, 7, 8, 1, 2, 3 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 7, 8, 6, 1, 2, 3 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 1, 2, 3, 8, 6, 7 })
if !ok { test.Fatal("false negative:", n) }
ok, n = Check(snake, []byte { 2, 1, 3, 6, 7, 8 })
if ok { test.Fatal("false positive:", n) }
ok, n = Check(snake, []byte { 6, 1, 2, 3, 7, 8 })
if ok { test.Fatal("false positive:", n) }
}

View File

@ -390,7 +390,7 @@ func (this *readerA) pull() (uint16, error) {
// close and return error on failure
this.eof = true
this.parent.Close()
return 0, this.parent.bestErr()
return 0, fmt.Errorf("could not receive message: %w", this.parent.bestErr())
}
func (this *readerA) Read(buffer []byte) (int, error) {