Compare commits
31 Commits
option-typ
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8add67c5de | |||
| 5f503021bf | |||
| 0727609067 | |||
| 968e145cda | |||
| bb14ec20a7 | |||
| a00e9d3183 | |||
| 4f4443069d | |||
| 10d84c2184 | |||
| 67881a455a | |||
| fb374c5cd5 | |||
| 1b43b92687 | |||
| 932e076113 | |||
| 5217f65cb8 | |||
| 26b8174f92 | |||
| 3daa66c4bc | |||
| c5154b3d85 | |||
| c2ce95021c | |||
| d4ccdb282e | |||
| 2e4c693174 | |||
| c9480ba016 | |||
| 09b2259a8c | |||
| da01a0d119 | |||
| c326a2b6b9 | |||
| 14a317c2ab | |||
| e5d7ad0702 | |||
| bb520976be | |||
| b3dc633abe | |||
| 476833709e | |||
| 75810bfda1 | |||
| 207627c428 | |||
| e6266e500c |
@ -37,7 +37,7 @@ func main() {
|
||||
absDestination, err := filepath.Abs(destination)
|
||||
handleErr(command, 1, err)
|
||||
base := filepath.Base(absDestination)
|
||||
if scrounged, ok := scroungeForPackageName(base); ok {
|
||||
if scrounged, ok := scroungeForPackageName(filepath.Dir(absDestination)); ok {
|
||||
packageName = scrounged
|
||||
} else {
|
||||
packageName = strings.ReplaceAll(
|
||||
|
||||
77
dial.go
77
dial.go
@ -5,38 +5,59 @@ import "errors"
|
||||
import "context"
|
||||
import "crypto/tls"
|
||||
|
||||
// Dial opens a connection to a server. The network must be one of "quic",
|
||||
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, "quic4" and
|
||||
// "quic6" don't do anything as the quic-go package doesn't seem to support this
|
||||
// behavior.
|
||||
func Dial(ctx context.Context, network, address string) (Conn, error) {
|
||||
return (Dialer { }).Dial(ctx, network, address)
|
||||
}
|
||||
|
||||
// Dialer allows for further configuration of the dialing process.
|
||||
type Dialer struct {
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// Dial opens a connection to a server. The network must be one of "quic",
|
||||
// "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is not
|
||||
// supported.
|
||||
func (diale Dialer) Dial(ctx context.Context, network, address string) (Conn, error) {
|
||||
// Dial opens a connection to a server. The network must be one of:
|
||||
//
|
||||
// - "quic"
|
||||
// - "quic4" (IPv4-only)
|
||||
// - "quic6" (IPv6-only)
|
||||
// - "tls"
|
||||
// - "tls4" (IPv4-only)
|
||||
// - "tls6" (IPv6-only)
|
||||
// - "tcp"
|
||||
// - "tcp4" (IPv4-only)
|
||||
// - "tcp6" (IPv6-only)
|
||||
// - "unix"
|
||||
//
|
||||
// For now, QUIC is unsupported.
|
||||
func Dial(ctx context.Context, network, address string, tlsConf *tls.Config) (Conn, error) {
|
||||
switch network {
|
||||
case "quic", "quic4", "quic6": return diale.dialQUIC(ctx, network, address)
|
||||
case "unix": return diale.dialUnix(ctx, network, address)
|
||||
case "quic", "quic4", "quic6": return DialQUIC(ctx, network, address, tlsConf)
|
||||
case "tls", "tls4", "tls6": return DialTLS(ctx, network, address, tlsConf)
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
addr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil { return nil, err }
|
||||
return DialTCP(ctx, network, nil, addr)
|
||||
case "unix":
|
||||
addr, err := net.ResolveUnixAddr(network, address)
|
||||
if err != nil { return nil, err }
|
||||
return DialUnix(ctx, network, addr)
|
||||
default: return nil, ErrUnknownNetwork
|
||||
}
|
||||
}
|
||||
|
||||
func (diale Dialer) dialQUIC(ctx context.Context, network, address string) (Conn, error) {
|
||||
// DialQUIC opens a connection to a server over QUIC.
|
||||
func DialQUIC(ctx context.Context, network, address string, tlsConf *tls.Config) (Conn, error) {
|
||||
return nil, errors.New("quic is not yet implemented")
|
||||
}
|
||||
|
||||
func (diale Dialer) dialUnix(ctx context.Context, network, address string) (Conn, error) {
|
||||
if network != "unix" { return nil, ErrUnknownNetwork }
|
||||
addr, err := net.ResolveUnixAddr(network, address)
|
||||
// DialTLS opens a connection to a server over TLS.
|
||||
func DialTLS(ctx context.Context, network, address string, tlsConf *tls.Config) (Conn, error) {
|
||||
network, err := tlsNetworkToTCPNetwork(network)
|
||||
if err != nil { return nil, err }
|
||||
conn, err := tls.Dial(network, address, tlsConf)
|
||||
if err != nil { return nil, err }
|
||||
return AdaptA(conn, ClientSide), nil
|
||||
}
|
||||
|
||||
// DialTCP opens a connection to a server over TCP.
|
||||
func DialTCP(ctx context.Context, network string, laddr, raddr *net.TCPAddr) (Conn, error) {
|
||||
conn, err := net.DialTCP(network, laddr, raddr)
|
||||
if err != nil { return nil, err }
|
||||
return AdaptA(conn, ClientSide), nil
|
||||
}
|
||||
|
||||
// DialUnix opens a connection to a server over a Unix domain socket.
|
||||
func DialUnix(ctx context.Context, network string, addr *net.UnixAddr) (Conn, error) {
|
||||
conn, err := net.DialUnix(network, nil, addr)
|
||||
if err != nil { return nil, err }
|
||||
return AdaptA(conn, ClientSide), nil
|
||||
@ -54,7 +75,6 @@ func tlsConfig(conf *tls.Config) *tls.Config {
|
||||
return conf
|
||||
}
|
||||
|
||||
|
||||
func quicNetworkToUDPNetwork(network string) (string, error) {
|
||||
switch network {
|
||||
case "quic4": return "udp4", nil
|
||||
@ -63,3 +83,12 @@ func quicNetworkToUDPNetwork(network string) (string, error) {
|
||||
default: return "", ErrUnknownNetwork
|
||||
}
|
||||
}
|
||||
|
||||
func tlsNetworkToTCPNetwork(network string) (string, error) {
|
||||
switch network {
|
||||
case "tls4": return "tcp4", nil
|
||||
case "tls6": return "tcp6", nil
|
||||
case "tls": return "tcp", nil
|
||||
default: return "", ErrUnknownNetwork
|
||||
}
|
||||
}
|
||||
|
||||
86
examples/1proc/main.go
Normal file
86
examples/1proc/main.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import "io"
|
||||
import "log"
|
||||
import "time"
|
||||
import "errors"
|
||||
import "context"
|
||||
import "git.tebibyte.media/sashakoshka/hopp"
|
||||
|
||||
var network = "tcp"
|
||||
var addr = "localhost:7959"
|
||||
|
||||
func main() {
|
||||
go func() {
|
||||
defer log.Println("SERVER closing")
|
||||
|
||||
listener, err := hopp.Listen(network, addr, nil)
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
log.Println("SERVER listening")
|
||||
conn, err := listener.Accept()
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
defer conn.Close()
|
||||
|
||||
trans, err := conn.AcceptTrans()
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
defer trans.Close()
|
||||
|
||||
for {
|
||||
method, data, err := trans.Receive()
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
log.Println("SERVER got", method, data)
|
||||
log.Println("SERVER send", method, data)
|
||||
err = trans.Send(1, data[:])
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
func() {
|
||||
log.Println("CLIENT dialing")
|
||||
conn, err := hopp.Dial(context.Background(), network, addr, nil)
|
||||
if err != nil { log.Fatalln("CLIENT", err) }
|
||||
log.Println("CLIENT dialed")
|
||||
|
||||
trans, err := conn.OpenTrans()
|
||||
if err != nil {
|
||||
log.Println("CLIENT", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
method, data, err := trans.Receive()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
log.Printf("CLIENT failed to receive message: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Println("CLIENT got", method, data)
|
||||
}
|
||||
}()
|
||||
|
||||
data := [1]byte { }
|
||||
for {
|
||||
log.Println("CLIENT send", 1, data)
|
||||
err := trans.Send(1, data[:])
|
||||
if err != nil {
|
||||
log.Println("CLIENT", err)
|
||||
return
|
||||
}
|
||||
data[0] ++
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
log.Println("CLIENT waiting for connection close...")
|
||||
trans, err = conn.AcceptTrans()
|
||||
if !errors.Is(err, io.EOF) {
|
||||
log.Println("CLIENT wrong error:", err)
|
||||
log.Fatalln("CLIENT trans:", trans)
|
||||
}
|
||||
log.Println("CLIENT DONE")
|
||||
conn.Close()
|
||||
}()
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "time"
|
||||
import "bufio"
|
||||
import "errors"
|
||||
import "context"
|
||||
import "crypto/tls"
|
||||
import "git.tebibyte.media/sashakoshka/hopp"
|
||||
@ -17,11 +19,12 @@ func main() {
|
||||
}
|
||||
address := os.Args[1]
|
||||
room := os.Args[2]
|
||||
var nickname hopp.Option[string]; if len(os.Args) >= 4 {
|
||||
nickname = hopp.O(os.Args[3])
|
||||
nickname := "Anonymous"; if len(os.Args) >= 4 {
|
||||
nickname = os.Args[3]
|
||||
}
|
||||
trans, err := join(address, room, nickname)
|
||||
handleErr(1, err)
|
||||
fmt.Fprintf(os.Stdout, "(i) connected to %s/%s\n", address, room)
|
||||
go func() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
@ -31,67 +34,49 @@ func main() {
|
||||
}
|
||||
}()
|
||||
for {
|
||||
message, err := chat.Receive(trans)
|
||||
handleErr(1, err)
|
||||
message, _, err := chat.Receive(trans)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
handleErr(1, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
switch message := message.(type) {
|
||||
case *chat.MessageChat:
|
||||
nickname := "Anonymous"
|
||||
if value, ok := message.Nickname.Get(); ok {
|
||||
nickname = value
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "%s: %s\n", nickname, message.Content)
|
||||
fmt.Fprintf(os.Stdout, "%s: %s\n", message.Nickname, message.Content)
|
||||
case *chat.MessageJoinNotify:
|
||||
fmt.Fprintf(os.Stdout, "(i) %s joined the room\n", message.Nickname)
|
||||
case *chat.MessageLeaveNotify:
|
||||
fmt.Fprintf(os.Stdout, "(i) %s left the room\n", message.Nickname)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "(i) disconnected\n")
|
||||
}
|
||||
|
||||
func join(address string, room string, nickname hopp.Option[string]) (hopp.Trans, error) {
|
||||
func join(address string, room string, nickname string) (hopp.Trans, error) {
|
||||
ctx, done := context.WithTimeout(context.Background(), 16 * time.Second)
|
||||
defer done()
|
||||
dialer := hopp.Dialer {
|
||||
TLSConfig: &tls.Config {
|
||||
// don't actually do this in real life
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
conn, err := dialer.Dial(ctx, "quic", address)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = updateProfile(conn, nickname)
|
||||
conn, err := hopp.Dial(ctx, "tls", address, &tls.Config {
|
||||
// don't actually do this in real life
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
if err != nil { return nil, err }
|
||||
|
||||
transRoom, err := conn.OpenTrans()
|
||||
if err != nil { return nil, err }
|
||||
err = chat.Send(transRoom, &chat.MessageJoin {
|
||||
Room: room,
|
||||
_, err = chat.Send(transRoom, &chat.MessageJoin {
|
||||
Room: room,
|
||||
Nickname: nickname,
|
||||
})
|
||||
if err != nil { return nil, err }
|
||||
return transRoom, nil
|
||||
}
|
||||
|
||||
func send(trans hopp.Trans, content string) error {
|
||||
return chat.Send(trans, &chat.MessageChat {
|
||||
_, err := chat.Send(trans, &chat.MessageChat {
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
|
||||
func updateProfile(conn hopp.Conn, nickname hopp.Option[string]) error {
|
||||
trans, err := conn.OpenTrans()
|
||||
if err != nil { return err }
|
||||
defer trans.Close()
|
||||
err = chat.Send(trans, &chat.MessageUpdateProfile {
|
||||
Nickname: nickname,
|
||||
})
|
||||
if err != nil { return err }
|
||||
message, err := chat.Receive(trans)
|
||||
if err != nil { return err }
|
||||
switch message := message.(type) {
|
||||
case *chat.MessageError: return message
|
||||
default: return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func handleErr(code int, err error) {
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
// source files, run this command from within the root directory of the
|
||||
// repository:
|
||||
//
|
||||
// go run ./cmd/hopp-generate examples/chat/protocol.md examples/chat/protocol
|
||||
// go run ./cmd/hopp-generate examples/chat/protocol.pdl -o examples/chat/protocol/protocol.go
|
||||
package chat
|
||||
|
||||
@ -3,8 +3,8 @@ package chat
|
||||
import "fmt"
|
||||
|
||||
func (msg *MessageError) Error() string {
|
||||
if description, ok := msg.Description.Get(); ok {
|
||||
return fmt.Sprintf("other party sent error: %d %s", msg.Error, description)
|
||||
if description, ok := msg.Description.Value(); ok {
|
||||
return fmt.Sprintf("other party sent error: %d %s", msg.Code, description)
|
||||
} else {
|
||||
return fmt.Sprintf("other party sent error: %d", msg.Code)
|
||||
}
|
||||
|
||||
@ -1,369 +0,0 @@
|
||||
package chat
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/hopp"
|
||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
|
||||
// Send sends one message along a transaction.
|
||||
func Send(trans hopp.Trans, message hopp.Message) error {
|
||||
buffer, err := message.MarshalBinary()
|
||||
if err != nil { return err }
|
||||
return trans.Send(message.Method(), buffer)
|
||||
}
|
||||
|
||||
// Receive receives one message from a transaction.
|
||||
func Receive(trans hopp.Trans) (hopp.Message, error) {
|
||||
method, data, err := trans.Receive()
|
||||
if err != nil { return nil, err }
|
||||
switch method {
|
||||
case 0x0000:
|
||||
message := &MessageError { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
case 0x0001:
|
||||
message := &MessageSuccess { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
case 0x0100:
|
||||
message := &MessageUpdateProfile { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
case 0x0200:
|
||||
message := &MessageJoin { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
case 0x0201:
|
||||
message := &MessageChat { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
case 0x0300:
|
||||
message := &MessageJoinNotify { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
case 0x0301:
|
||||
message := &MessageLeaveNotify { }
|
||||
err := message.UnmarshalBinary(data)
|
||||
if err != nil { return nil, err }
|
||||
return message, nil
|
||||
default: return nil, hopp.ErrUnknownMethod
|
||||
}
|
||||
}
|
||||
|
||||
// (0) Error is sent by a party when the other party has done something erroneous. The valid error codes are:
|
||||
//
|
||||
// 0: General, unspecified error
|
||||
//
|
||||
// The description field, if specified, determines a human-readable error to be shown to the user. The sending party must immediately close the transaction after this message is sent.
|
||||
type MessageError struct {
|
||||
/* 0 */ Code uint16
|
||||
/* 1 */ Description hopp.Option[string]
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageError) Method() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageError) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 1
|
||||
offsetCode := size
|
||||
{ value := msg.Code
|
||||
size += 2; _ = value }
|
||||
offsetDescription := size
|
||||
if value, ok := msg.Description.Get(); ok {
|
||||
count ++
|
||||
size += len(value) }
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
{ value := msg.Code
|
||||
tape.EncodeI16(buffer[offsetCode:], value)}
|
||||
if value, ok := msg.Description.Get(); ok {
|
||||
tape.EncodeString(buffer[offsetDescription:], value)}
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageError) UnmarshalBinary(buffer []byte) error {
|
||||
pairs, err := tape.DecodePairs(buffer)
|
||||
if err != nil { return err }
|
||||
foundRequired := 0
|
||||
for tag, data := range pairs {
|
||||
switch tag {
|
||||
case 0:
|
||||
value, err := tape.DecodeI16[uint16](data)
|
||||
if err != nil { return err }
|
||||
msg.Code = value
|
||||
foundRequired ++
|
||||
case 1:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Description = hopp.O(value)
|
||||
}
|
||||
}
|
||||
if foundRequired != 1 { return hopp.ErrTablePairMissing }
|
||||
return nil
|
||||
}
|
||||
|
||||
// (1) Success is sent by a party when it has successfully completed a task given to it by the other party. The sending party must immediately close the transaction after this message is sent.
|
||||
type MessageSuccess struct {
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageSuccess) Method() uint16 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageSuccess) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 0
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageSuccess) UnmarshalBinary(buffer []byte) error {
|
||||
// no fields
|
||||
return nil
|
||||
}
|
||||
|
||||
// (256) UpdateProfile is sent by the client in a new transaction to update the profile details that will be shown to other connected clients.
|
||||
type MessageUpdateProfile struct {
|
||||
/* 0 */ Nickname hopp.Option[string]
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageUpdateProfile) Method() uint16 {
|
||||
return 256
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageUpdateProfile) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 0
|
||||
offsetNickname := size
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
count ++
|
||||
size += len(value) }
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
tape.EncodeString(buffer[offsetNickname:], value)}
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageUpdateProfile) UnmarshalBinary(buffer []byte) error {
|
||||
pairs, err := tape.DecodePairs(buffer)
|
||||
if err != nil { return err }
|
||||
foundRequired := 0
|
||||
for tag, data := range pairs {
|
||||
switch tag {
|
||||
case 0:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Nickname = hopp.O(value)
|
||||
}
|
||||
}
|
||||
if foundRequired != 1 { return hopp.ErrTablePairMissing }
|
||||
return nil
|
||||
}
|
||||
|
||||
// (512) Join is sent by the client when it wishes to join a room. It must begin a new transaction, and that transaction will persist while the user is in that room. Messages having to do with the room will be sent along this transaction. To leave the room, the client must close the transaction.
|
||||
type MessageJoin struct {
|
||||
/* 0 */ Room string
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageJoin) Method() uint16 {
|
||||
return 512
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageJoin) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 1
|
||||
offsetRoom := size
|
||||
{ value := msg.Room
|
||||
size += len(value) }
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
{ value := msg.Room
|
||||
tape.EncodeString(buffer[offsetRoom:], value)}
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageJoin) UnmarshalBinary(buffer []byte) error {
|
||||
pairs, err := tape.DecodePairs(buffer)
|
||||
if err != nil { return err }
|
||||
for tag, data := range pairs {
|
||||
switch tag {
|
||||
case 0:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Room = value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// (513) Chat is sent by the client when it wishes to post a message to the room. It is also relayed by the server to other clients to notify them of the message. It must be sent within a room transaction.
|
||||
type MessageChat struct {
|
||||
/* 0 */ Nickname hopp.Option[string]
|
||||
/* 1 */ Content string
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageChat) Method() uint16 {
|
||||
return 513
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageChat) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 1
|
||||
offsetNickname := size
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
count ++
|
||||
size += len(value) }
|
||||
offsetContent := size
|
||||
{ value := msg.Content
|
||||
size += len(value) }
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
tape.EncodeString(buffer[offsetNickname:], value)}
|
||||
{ value := msg.Content
|
||||
tape.EncodeString(buffer[offsetContent:], value)}
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageChat) UnmarshalBinary(buffer []byte) error {
|
||||
pairs, err := tape.DecodePairs(buffer)
|
||||
if err != nil { return err }
|
||||
foundRequired := 0
|
||||
for tag, data := range pairs {
|
||||
switch tag {
|
||||
case 0:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Nickname = hopp.O(value)
|
||||
case 1:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Content = value
|
||||
foundRequired ++
|
||||
}
|
||||
}
|
||||
if foundRequired != 1 { return hopp.ErrTablePairMissing }
|
||||
return nil
|
||||
}
|
||||
|
||||
// (768) JoinNotify is sent by the server when another client joins the room. It must be sent within a room transaction.
|
||||
type MessageJoinNotify struct {
|
||||
/* 0 */ Nickname hopp.Option[string]
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageJoinNotify) Method() uint16 {
|
||||
return 768
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageJoinNotify) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 0
|
||||
offsetNickname := size
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
count ++
|
||||
size += len(value) }
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
tape.EncodeString(buffer[offsetNickname:], value)}
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageJoinNotify) UnmarshalBinary(buffer []byte) error {
|
||||
pairs, err := tape.DecodePairs(buffer)
|
||||
if err != nil { return err }
|
||||
foundRequired := 0
|
||||
for tag, data := range pairs {
|
||||
switch tag {
|
||||
case 0:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Nickname = hopp.O(value)
|
||||
}
|
||||
}
|
||||
if foundRequired != 1 { return hopp.ErrTablePairMissing }
|
||||
return nil
|
||||
}
|
||||
|
||||
// (769) LeaveNotify is sent by the server when another client leaves the room. It must be sent within a room transaction.
|
||||
type MessageLeaveNotify struct {
|
||||
/* 0 */ Nickname hopp.Option[string]
|
||||
}
|
||||
|
||||
// Method returns the method number of the message.
|
||||
func (msg MessageLeaveNotify) Method() uint16 {
|
||||
return 769
|
||||
}
|
||||
|
||||
// MarshalBinary encodes the data in this message into a buffer.
|
||||
func (msg *MessageLeaveNotify) MarshalBinary() ([]byte, error) {
|
||||
size := 0
|
||||
count := 0
|
||||
offsetNickname := size
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
count ++
|
||||
size += len(value) }
|
||||
if size > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
if count > 0xFFFF { return nil, hopp.ErrPayloadTooLarge}
|
||||
buffer := make([]byte, 2 + 4 * count + size)
|
||||
tape.EncodeI16(buffer[:2], uint16(count))
|
||||
if value, ok := msg.Nickname.Get(); ok {
|
||||
tape.EncodeString(buffer[offsetNickname:], value)}
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary dencodes the data from a buffer int this message.
|
||||
func (msg *MessageLeaveNotify) UnmarshalBinary(buffer []byte) error {
|
||||
pairs, err := tape.DecodePairs(buffer)
|
||||
if err != nil { return err }
|
||||
foundRequired := 0
|
||||
for tag, data := range pairs {
|
||||
switch tag {
|
||||
case 0:
|
||||
value, err := tape.DecodeString[string](data)
|
||||
if err != nil { return err }
|
||||
msg.Nickname = hopp.O(value)
|
||||
}
|
||||
}
|
||||
if foundRequired != 1 { return hopp.ErrTablePairMissing }
|
||||
return nil
|
||||
}
|
||||
|
||||
733
examples/chat/protocol.go
Normal file
733
examples/chat/protocol.go
Normal file
@ -0,0 +1,733 @@
|
||||
package chat
|
||||
|
||||
// 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]
|
||||
|
||||
// Error is sent by a party when the other party has done something erroneous. The
|
||||
// valid error codes are:
|
||||
//
|
||||
// - 0: General, unspecified error
|
||||
//
|
||||
// The description field, if specified, determines a human-readable error to be
|
||||
// shown to the user. The sending party must immediately close the transaction
|
||||
// after this message is sent.
|
||||
type MessageError struct {
|
||||
Code uint16
|
||||
Description hopp.Option[string]
|
||||
}
|
||||
|
||||
// Method returns the message's method number.
|
||||
func(this *MessageError) Method() uint16 { return 0x0000 }
|
||||
|
||||
// Encode encodes this message's tag and value.
|
||||
func(this *MessageError) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
tag_1 := tape.KTV.WithCN(0)
|
||||
nn, err := encoder.WriteTag(tag_1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if 2 > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
nn, err = encoder.WriteUintN(2, tag_1.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
{
|
||||
if value, ok := (*this).Description.Value(); ok {
|
||||
nn, err = encoder.WriteUint16(0x0001)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_2 := tape.StringTag(string(value))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_2))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len(value) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_2.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len(value)), tag_2.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte(value))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_3 := tape.LI.WithCN(1)
|
||||
nn, err = encoder.WriteUint8(uint8(tag_3))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = encoder.WriteUint16(uint16((*this).Code))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode decodes this message's tag and value.
|
||||
func(this *MessageError) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
tag, nn, err := decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if !(canAssign(tape.KTV, tag)) {
|
||||
nn, err = tape.Skim(decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
nn, err = decodeBranch_1d505103df99c95e6bed0800d0ea881a_MessageError(this, decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Success is sent by a party when it has successfully completed a task given to it
|
||||
// by the other party. The sending party must immediately close the transaction
|
||||
// after this message is sent.
|
||||
type MessageSuccess struct {
|
||||
}
|
||||
|
||||
// Method returns the message's method number.
|
||||
func(this *MessageSuccess) Method() uint16 { return 0x0001 }
|
||||
|
||||
// Encode encodes this message's tag and value.
|
||||
func(this *MessageSuccess) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
tag_4 := tape.KTV.WithCN(0)
|
||||
nn, err := encoder.WriteTag(tag_4)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if 0 > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
nn, err = encoder.WriteUintN(0, tag_4.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
{
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode decodes this message's tag and value.
|
||||
func(this *MessageSuccess) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
tag, nn, err := decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if !(canAssign(tape.KTV, tag)) {
|
||||
nn, err = tape.Skim(decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
nn, err = decodeBranch_99914b932bd37a50b983c5e7c90ae93b_MessageSuccess(this, decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Join is sent by the client when it wishes to join a room. It must begin a new
|
||||
// transaction, and that transaction will persist while the user is in that room.
|
||||
// Messages having to do with the room will be sent along this transaction. To
|
||||
// leave the room, the client must close the transaction.
|
||||
type MessageJoin struct {
|
||||
Room string
|
||||
Nickname string
|
||||
}
|
||||
|
||||
// Method returns the message's method number.
|
||||
func(this *MessageJoin) Method() uint16 { return 0x0200 }
|
||||
|
||||
// Encode encodes this message's tag and value.
|
||||
func(this *MessageJoin) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
tag_5 := tape.KTV.WithCN(0)
|
||||
nn, err := encoder.WriteTag(tag_5)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if 2 > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
nn, err = encoder.WriteUintN(2, tag_5.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
{
|
||||
nn, err = encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_6 := tape.StringTag(string((*this).Room))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_6))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len((*this).Room) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_6.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len((*this).Room)), tag_6.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte((*this).Room))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = encoder.WriteUint16(0x0001)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_7 := tape.StringTag(string((*this).Nickname))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_7))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len((*this).Nickname) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_7.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len((*this).Nickname)), tag_7.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte((*this).Nickname))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode decodes this message's tag and value.
|
||||
func(this *MessageJoin) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
tag, nn, err := decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if !(canAssign(tape.KTV, tag)) {
|
||||
nn, err = tape.Skim(decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
nn, err = decodeBranch_2c5f22d9503118676b4c5584211a4a95_MessageJoin(this, decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Chat is sent by the client when it wishes to post a message to the room. It is
|
||||
// also relayed by the server to other clients to notify them of the message. It
|
||||
// must be sent within a room transaction.
|
||||
type MessageChat struct {
|
||||
Content string
|
||||
Nickname string
|
||||
}
|
||||
|
||||
// Method returns the message's method number.
|
||||
func(this *MessageChat) Method() uint16 { return 0x0300 }
|
||||
|
||||
// Encode encodes this message's tag and value.
|
||||
func(this *MessageChat) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
tag_8 := tape.KTV.WithCN(0)
|
||||
nn, err := encoder.WriteTag(tag_8)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if 2 > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
nn, err = encoder.WriteUintN(2, tag_8.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
{
|
||||
nn, err = encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_9 := tape.StringTag(string((*this).Content))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_9))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len((*this).Content) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_9.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len((*this).Content)), tag_9.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte((*this).Content))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = encoder.WriteUint16(0x0001)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_10 := tape.StringTag(string((*this).Nickname))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_10))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len((*this).Nickname) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_10.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len((*this).Nickname)), tag_10.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte((*this).Nickname))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode decodes this message's tag and value.
|
||||
func(this *MessageChat) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
tag, nn, err := decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if !(canAssign(tape.KTV, tag)) {
|
||||
nn, err = tape.Skim(decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
nn, err = decodeBranch_5c1cf9347bb6d9f41cee64b186392d24_MessageChat(this, decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// JoinNotify is sent by the server when another client joins the room. It must be
|
||||
// sent within a room transaction.
|
||||
type MessageJoinNotify struct {
|
||||
Nickname string
|
||||
}
|
||||
|
||||
// Method returns the message's method number.
|
||||
func(this *MessageJoinNotify) Method() uint16 { return 0x0400 }
|
||||
|
||||
// Encode encodes this message's tag and value.
|
||||
func(this *MessageJoinNotify) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
tag_11 := tape.KTV.WithCN(0)
|
||||
nn, err := encoder.WriteTag(tag_11)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if 1 > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
nn, err = encoder.WriteUintN(1, tag_11.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
{
|
||||
nn, err = encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_12 := tape.StringTag(string((*this).Nickname))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_12))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len((*this).Nickname) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_12.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len((*this).Nickname)), tag_12.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte((*this).Nickname))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode decodes this message's tag and value.
|
||||
func(this *MessageJoinNotify) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
tag, nn, err := decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if !(canAssign(tape.KTV, tag)) {
|
||||
nn, err = tape.Skim(decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
nn, err = decodeBranch_68c536511e6d598462efc482144438e9_MessageJoinNotify(this, decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// LeaveNotify is sent by the server when another client leaves the room. It must
|
||||
// be sent within a room transaction.
|
||||
type MessageLeaveNotify struct {
|
||||
Nickname string
|
||||
}
|
||||
|
||||
// Method returns the message's method number.
|
||||
func(this *MessageLeaveNotify) Method() uint16 { return 0x0401 }
|
||||
|
||||
// Encode encodes this message's tag and value.
|
||||
func(this *MessageLeaveNotify) Encode(encoder *tape.Encoder) (n int, err error) {
|
||||
tag_13 := tape.KTV.WithCN(0)
|
||||
nn, err := encoder.WriteTag(tag_13)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if 1 > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
nn, err = encoder.WriteUintN(1, tag_13.CN() + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
{
|
||||
nn, err = encoder.WriteUint16(0x0000)
|
||||
n += nn; if err != nil { return n, err }
|
||||
tag_14 := tape.StringTag(string((*this).Nickname))
|
||||
nn, err = encoder.WriteUint8(uint8(tag_14))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if len((*this).Nickname) > tape.MaxStructureLength {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
if tag_14.Is(tape.LBA) {
|
||||
nn, err = encoder.WriteUintN(uint64(len((*this).Nickname)), tag_14.CN())
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = encoder.Write([]byte((*this).Nickname))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Decode decodes this message's tag and value.
|
||||
func(this *MessageLeaveNotify) Decode(decoder *tape.Decoder) (n int, err error) {
|
||||
tag, nn, err := decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
if !(canAssign(tape.KTV, tag)) {
|
||||
nn, err = tape.Skim(decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
nn, err = decodeBranch_68c536511e6d598462efc482144438e9_MessageLeaveNotify(this, decoder, tag)
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func decodeBranch_1d505103df99c95e6bed0800d0ea881a_MessageError(this *MessageError, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
|
||||
var nn int
|
||||
var length_15 uint64
|
||||
if length_15 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
length_15, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
for _ = range length_15 {
|
||||
var fieldKey_16 uint16
|
||||
fieldKey_16, nn, err = decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
var fieldTag_17 tape.Tag
|
||||
fieldTag_17, nn, err = decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
switch fieldKey_16 {
|
||||
case 0x0000:
|
||||
if !(canAssign(tape.LI, fieldTag_17)) {
|
||||
tape.Skim(decoder, fieldTag_17)
|
||||
continue
|
||||
}
|
||||
destination_18, nn, err := decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Code)) = destination_18
|
||||
case 0x0001:
|
||||
if !(canAssign(tape.LBA, fieldTag_17)) {
|
||||
tape.Skim(decoder, fieldTag_17)
|
||||
continue
|
||||
}
|
||||
var destination_19 string
|
||||
var length_20 uint64
|
||||
if fieldTag_17.Is(tape.LBA) {
|
||||
length_20, nn, err = decoder.ReadUintN(int(fieldTag_17.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_20 = uint64(fieldTag_17.CN())
|
||||
}
|
||||
if length_20 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_20)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&destination_19) = string(buffer)
|
||||
this.Description = hopp.O(destination_19)
|
||||
default:
|
||||
tape.Skim(decoder, fieldTag_17)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func decodeBranch_99914b932bd37a50b983c5e7c90ae93b_MessageSuccess(this *MessageSuccess, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
|
||||
var nn int
|
||||
var length_21 uint64
|
||||
if length_21 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
length_21, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
for _ = range length_21 {
|
||||
var fieldKey_22 uint16
|
||||
fieldKey_22, nn, err = decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
var fieldTag_23 tape.Tag
|
||||
fieldTag_23, nn, err = decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
switch fieldKey_22 {
|
||||
default:
|
||||
tape.Skim(decoder, fieldTag_23)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func decodeBranch_2c5f22d9503118676b4c5584211a4a95_MessageJoin(this *MessageJoin, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
|
||||
var nn int
|
||||
var length_24 uint64
|
||||
if length_24 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
length_24, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
for _ = range length_24 {
|
||||
var fieldKey_25 uint16
|
||||
fieldKey_25, nn, err = decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
var fieldTag_26 tape.Tag
|
||||
fieldTag_26, nn, err = decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
switch fieldKey_25 {
|
||||
case 0x0000:
|
||||
if !(canAssign(tape.LBA, fieldTag_26)) {
|
||||
tape.Skim(decoder, fieldTag_26)
|
||||
continue
|
||||
}
|
||||
var length_27 uint64
|
||||
if fieldTag_26.Is(tape.LBA) {
|
||||
length_27, nn, err = decoder.ReadUintN(int(fieldTag_26.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_27 = uint64(fieldTag_26.CN())
|
||||
}
|
||||
if length_27 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_27)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Room)) = string(buffer)
|
||||
case 0x0001:
|
||||
if !(canAssign(tape.LBA, fieldTag_26)) {
|
||||
tape.Skim(decoder, fieldTag_26)
|
||||
continue
|
||||
}
|
||||
var length_28 uint64
|
||||
if fieldTag_26.Is(tape.LBA) {
|
||||
length_28, nn, err = decoder.ReadUintN(int(fieldTag_26.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_28 = uint64(fieldTag_26.CN())
|
||||
}
|
||||
if length_28 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_28)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Nickname)) = string(buffer)
|
||||
default:
|
||||
tape.Skim(decoder, fieldTag_26)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func decodeBranch_5c1cf9347bb6d9f41cee64b186392d24_MessageChat(this *MessageChat, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
|
||||
var nn int
|
||||
var length_29 uint64
|
||||
if length_29 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
length_29, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
for _ = range length_29 {
|
||||
var fieldKey_30 uint16
|
||||
fieldKey_30, nn, err = decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
var fieldTag_31 tape.Tag
|
||||
fieldTag_31, nn, err = decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
switch fieldKey_30 {
|
||||
case 0x0000:
|
||||
if !(canAssign(tape.LBA, fieldTag_31)) {
|
||||
tape.Skim(decoder, fieldTag_31)
|
||||
continue
|
||||
}
|
||||
var length_32 uint64
|
||||
if fieldTag_31.Is(tape.LBA) {
|
||||
length_32, nn, err = decoder.ReadUintN(int(fieldTag_31.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_32 = uint64(fieldTag_31.CN())
|
||||
}
|
||||
if length_32 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_32)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Content)) = string(buffer)
|
||||
case 0x0001:
|
||||
if !(canAssign(tape.LBA, fieldTag_31)) {
|
||||
tape.Skim(decoder, fieldTag_31)
|
||||
continue
|
||||
}
|
||||
var length_33 uint64
|
||||
if fieldTag_31.Is(tape.LBA) {
|
||||
length_33, nn, err = decoder.ReadUintN(int(fieldTag_31.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_33 = uint64(fieldTag_31.CN())
|
||||
}
|
||||
if length_33 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_33)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Nickname)) = string(buffer)
|
||||
default:
|
||||
tape.Skim(decoder, fieldTag_31)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func decodeBranch_68c536511e6d598462efc482144438e9_MessageJoinNotify(this *MessageJoinNotify, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
|
||||
var nn int
|
||||
var length_34 uint64
|
||||
if length_34 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
length_34, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
for _ = range length_34 {
|
||||
var fieldKey_35 uint16
|
||||
fieldKey_35, nn, err = decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
var fieldTag_36 tape.Tag
|
||||
fieldTag_36, nn, err = decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
switch fieldKey_35 {
|
||||
case 0x0000:
|
||||
if !(canAssign(tape.LBA, fieldTag_36)) {
|
||||
tape.Skim(decoder, fieldTag_36)
|
||||
continue
|
||||
}
|
||||
var length_37 uint64
|
||||
if fieldTag_36.Is(tape.LBA) {
|
||||
length_37, nn, err = decoder.ReadUintN(int(fieldTag_36.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_37 = uint64(fieldTag_36.CN())
|
||||
}
|
||||
if length_37 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_37)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Nickname)) = string(buffer)
|
||||
default:
|
||||
tape.Skim(decoder, fieldTag_36)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func decodeBranch_68c536511e6d598462efc482144438e9_MessageLeaveNotify(this *MessageLeaveNotify, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
|
||||
var nn int
|
||||
var length_38 uint64
|
||||
if length_38 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
length_38, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
|
||||
n += nn; if err != nil { return n, err }
|
||||
for _ = range length_38 {
|
||||
var fieldKey_39 uint16
|
||||
fieldKey_39, nn, err = decoder.ReadUint16()
|
||||
n += nn; if err != nil { return n, err }
|
||||
var fieldTag_40 tape.Tag
|
||||
fieldTag_40, nn, err = decoder.ReadTag()
|
||||
n += nn; if err != nil { return n, err }
|
||||
switch fieldKey_39 {
|
||||
case 0x0000:
|
||||
if !(canAssign(tape.LBA, fieldTag_40)) {
|
||||
tape.Skim(decoder, fieldTag_40)
|
||||
continue
|
||||
}
|
||||
var length_41 uint64
|
||||
if fieldTag_40.Is(tape.LBA) {
|
||||
length_41, nn, err = decoder.ReadUintN(int(fieldTag_40.CN()))
|
||||
n += nn; if err != nil { return n, err }
|
||||
} else {
|
||||
length_41 = uint64(fieldTag_40.CN())
|
||||
}
|
||||
if length_41 > uint64(tape.MaxStructureLength) {
|
||||
return n, tape.ErrTooLong
|
||||
}
|
||||
buffer := make([]byte, length_41)
|
||||
nn, err = decoder.Read(buffer)
|
||||
n += nn; if err != nil { return n, err }
|
||||
*(&(this.Nickname)) = string(buffer)
|
||||
default:
|
||||
tape.Skim(decoder, fieldTag_40)
|
||||
continue
|
||||
}
|
||||
}
|
||||
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 0x0401:
|
||||
var message MessageLeaveNotify
|
||||
nn, err := message.Decode(decoder)
|
||||
n += nn; if err != nil { return nil, n, err }
|
||||
return message, n, nil
|
||||
case 0x0000:
|
||||
var message MessageError
|
||||
nn, err := message.Decode(decoder)
|
||||
n += nn; if err != nil { return nil, n, err }
|
||||
return message, n, nil
|
||||
case 0x0001:
|
||||
var message MessageSuccess
|
||||
nn, err := message.Decode(decoder)
|
||||
n += nn; if err != nil { return nil, n, err }
|
||||
return message, n, nil
|
||||
case 0x0200:
|
||||
var message MessageJoin
|
||||
nn, err := message.Decode(decoder)
|
||||
n += nn; if err != nil { return nil, n, err }
|
||||
return message, n, nil
|
||||
case 0x0300:
|
||||
var message MessageChat
|
||||
nn, err := message.Decode(decoder)
|
||||
n += nn; if err != nil { return nil, n, err }
|
||||
return message, n, nil
|
||||
case 0x0400:
|
||||
var message MessageJoinNotify
|
||||
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)
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
# Chat Protocol
|
||||
|
||||
This document describes a simple chat protocol. To re-generate the source files,
|
||||
run `go run ./cmd/hopp-generate examples/chat/protocol.md examples/chat/protocol`
|
||||
|
||||
## Messages
|
||||
|
||||
### 0000 Error
|
||||
| Tag | Name | Type | Required |
|
||||
| --: | ----------- | ------ | -------- |
|
||||
| 0 | Code | U16 | Yes |
|
||||
| 1 | Description | String | No |
|
||||
|
||||
Error is sent by a party when the other party has done something erroneous. The
|
||||
valid error codes are:
|
||||
|
||||
- 0: General, unspecified error
|
||||
|
||||
The description field, if specified, determines a human-readable error to be
|
||||
shown to the user. The sending party must immediately close the transaction
|
||||
after this message is sent.
|
||||
|
||||
### 0001 Success
|
||||
Success is sent by a party when it has successfully completed a task given to it
|
||||
by the other party. The sending party must immediately close the transaction
|
||||
after this message is sent.
|
||||
|
||||
### 0100 UpdateProfile
|
||||
| Tag | Name | Type | Required |
|
||||
| --: | -------- | ------ | -------- |
|
||||
| 0 | Nickname | String | No |
|
||||
|
||||
UpdateProfile is sent by the client in a new transaction to update the profile
|
||||
details that will be shown to other connected clients.
|
||||
|
||||
### 0200 Join
|
||||
| Tag | Name | Type | Required |
|
||||
| --: | -------- | ------ | -------- |
|
||||
| 0 | Room | String | Yes |
|
||||
|
||||
Join is sent by the client when it wishes to join a room. It must begin a new
|
||||
transaction, and that transaction will persist while the user is in that room.
|
||||
Messages having to do with the room will be sent along this transaction. To
|
||||
leave the room, the client must close the transaction.
|
||||
|
||||
### 0201 Chat
|
||||
| Tag | Name | Type | Required |
|
||||
| --: | -------- | ------ | -------- |
|
||||
| 0 | Nickname | String | No |
|
||||
| 1 | Content | String | Yes |
|
||||
|
||||
Chat is sent by the client when it wishes to post a message to the room. It is
|
||||
also relayed by the server to other clients to notify them of the message. It
|
||||
must be sent within a room transaction.
|
||||
|
||||
### 0300 JoinNotify
|
||||
| Tag | Name | Type | Required |
|
||||
| --: | -------- | ------ | -------- |
|
||||
| 0 | Nickname | String | No |
|
||||
|
||||
JoinNotify is sent by the server when another client joins the room. It must be
|
||||
sent within a room transaction.
|
||||
|
||||
### 0301 LeaveNotify
|
||||
| Tag | Name | Type | Required |
|
||||
| --: | -------- | ------ | -------- |
|
||||
| 0 | Nickname | String | No |
|
||||
|
||||
LeaveNotify is sent by the server when another client leaves the room. It must
|
||||
be sent within a room transaction.
|
||||
48
examples/chat/protocol.pdl
Normal file
48
examples/chat/protocol.pdl
Normal file
@ -0,0 +1,48 @@
|
||||
// Error is sent by a party when the other party has done something erroneous. The
|
||||
// valid error codes are:
|
||||
//
|
||||
// - 0: General, unspecified error
|
||||
//
|
||||
// The description field, if specified, determines a human-readable error to be
|
||||
// shown to the user. The sending party must immediately close the transaction
|
||||
// after this message is sent.
|
||||
M0000 Error {
|
||||
0000 Code U16,
|
||||
0001 Description ?String,
|
||||
}
|
||||
|
||||
// Success is sent by a party when it has successfully completed a task given to it
|
||||
// by the other party. The sending party must immediately close the transaction
|
||||
// after this message is sent.
|
||||
M0001 Success {
|
||||
|
||||
}
|
||||
|
||||
// Join is sent by the client when it wishes to join a room. It must begin a new
|
||||
// transaction, and that transaction will persist while the user is in that room.
|
||||
// Messages having to do with the room will be sent along this transaction. To
|
||||
// leave the room, the client must close the transaction.
|
||||
M0200 Join {
|
||||
0000 Room String,
|
||||
0001 Nickname String,
|
||||
}
|
||||
|
||||
// Chat is sent by the client when it wishes to post a message to the room. It is
|
||||
// also relayed by the server to other clients to notify them of the message. It
|
||||
// must be sent within a room transaction.
|
||||
M0300 Chat {
|
||||
0000 Content String,
|
||||
0001 Nickname String,
|
||||
}
|
||||
|
||||
// JoinNotify is sent by the server when another client joins the room. It must be
|
||||
// sent within a room transaction.
|
||||
M0400 JoinNotify {
|
||||
0000 Nickname String,
|
||||
}
|
||||
|
||||
// LeaveNotify is sent by the server when another client leaves the room. It must
|
||||
// be sent within a room transaction.
|
||||
M0401 LeaveNotify {
|
||||
0000 Nickname String,
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "log"
|
||||
import "errors"
|
||||
@ -28,7 +29,7 @@ func main() {
|
||||
func host(address string, certPath, keyPath string) error {
|
||||
keyPair, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil { return err }
|
||||
listener, err := hopp.ListenQUIC("quic", address, &tls.Config {
|
||||
listener, err := hopp.Listen("tls", address, &tls.Config {
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate { keyPair },
|
||||
})
|
||||
@ -48,7 +49,7 @@ func host(address string, certPath, keyPath string) error {
|
||||
|
||||
type client struct {
|
||||
conn hopp.Conn
|
||||
nickname hopp.Option[string]
|
||||
nickname string
|
||||
rooms usync.RWMonitor[map[string] hopp.Trans]
|
||||
}
|
||||
|
||||
@ -58,11 +59,12 @@ func (this *client) run() {
|
||||
defer this.conn.Close()
|
||||
|
||||
for {
|
||||
log.Println("accepting transaction")
|
||||
trans, err := this.conn.AcceptTrans()
|
||||
log.Println("accepted transaction")
|
||||
if err != nil {
|
||||
log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err)
|
||||
if !errors.Is(err, io.EOF) {
|
||||
log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
go this.runTrans(trans)
|
||||
}
|
||||
@ -70,7 +72,7 @@ func (this *client) run() {
|
||||
|
||||
func (this *client) runTrans(trans hopp.Trans) {
|
||||
defer trans.Close()
|
||||
message, err := chat.Receive(trans)
|
||||
message, _, err := chat.Receive(trans)
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"XXX %v transaction failed: %v",
|
||||
@ -97,7 +99,7 @@ func (this *client) transTalk(trans hopp.Trans, initial *chat.MessageJoin) error
|
||||
if err != nil { return err }
|
||||
defer this.leaveRoom(trans, room)
|
||||
for {
|
||||
message, err := chat.Receive(trans)
|
||||
message, _, err := chat.Receive(trans)
|
||||
if err != nil { return err }
|
||||
switch message := message.(type) {
|
||||
case *chat.MessageChat:
|
||||
@ -110,7 +112,7 @@ func (this *client) transTalk(trans hopp.Trans, initial *chat.MessageJoin) error
|
||||
}
|
||||
|
||||
func (this *client) handleMessageChat(trans hopp.Trans, room string, message *chat.MessageChat) error {
|
||||
log.Println("(). %s #%s: %s", this.nickname.Default("Anonymous"), room, message.Content)
|
||||
log.Printf("(). %s #%s: %s", this.nickname, room, message.Content)
|
||||
clients, done := clients.RBorrow()
|
||||
defer done()
|
||||
for client := range clients {
|
||||
@ -126,7 +128,7 @@ func (this *client) relayMessage(room string, message *chat.MessageChat) error {
|
||||
rooms, done := this.rooms.RBorrow()
|
||||
defer done()
|
||||
if trans, ok := rooms[room]; ok {
|
||||
err := chat.Send(trans, message)
|
||||
_, err := chat.Send(trans, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not relay message: %w", err)
|
||||
}
|
||||
@ -141,7 +143,7 @@ func (this *client) joinRoom(trans hopp.Trans, room string) error {
|
||||
return fmt.Errorf("already joined %s", room)
|
||||
}
|
||||
rooms[room] = trans
|
||||
log.Printf("--> user %s joined #%s", this.nickname.Default("Anonymous"), room)
|
||||
log.Printf("--> user %s joined #%s", this.nickname, room)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -152,7 +154,7 @@ func (this *client) leaveRoom(trans hopp.Trans, room string) error {
|
||||
return fmt.Errorf("not in %s", room)
|
||||
}
|
||||
delete(rooms, room)
|
||||
log.Printf("<-- user %s left #%s", this.nickname.Default("Anonymous"), room)
|
||||
log.Printf("<-- user %s left #%s", this.nickname, room)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
84
examples/min/client/main.go
Normal file
84
examples/min/client/main.go
Normal file
@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
import "io"
|
||||
import "os"
|
||||
import "log"
|
||||
import "fmt"
|
||||
import "time"
|
||||
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)
|
||||
// }
|
||||
method, reader, err := trans.ReceiveReader()
|
||||
if err != nil {
|
||||
log.Printf("CLIENT recv: %v", err)
|
||||
return
|
||||
}
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
log.Printf("CLIENT recv: %v", err)
|
||||
return
|
||||
}
|
||||
log.Println("CLIENT got", method, data)
|
||||
}
|
||||
}()
|
||||
|
||||
// message := ping.MessagePing(0)
|
||||
// for {
|
||||
// log.Printf("<-- ping (%d)", message)
|
||||
// _, err := ping.Send(trans, &message)
|
||||
// handleErr(1, err)
|
||||
// message ++
|
||||
// time.Sleep(time.Second)
|
||||
// }
|
||||
data := [1]byte { }
|
||||
for {
|
||||
log.Println("CLIENT send", 1, data)
|
||||
err := trans.Send(1, data[:])
|
||||
handleErr(1, err)
|
||||
data[0] ++
|
||||
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
|
||||
}
|
||||
|
||||
func handleErr(code int, err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
97
examples/min/server/main.go
Normal file
97
examples/min/server/main.go
Normal file
@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import "io"
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "log"
|
||||
import "errors"
|
||||
import "git.tebibyte.media/sashakoshka/hopp"
|
||||
// import "git.tebibyte.media/sashakoshka/hopp/examples/ping"
|
||||
|
||||
var network = "tcp"
|
||||
var addr = "localhost:7959"
|
||||
|
||||
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(addr string) error {
|
||||
defer log.Println("(i) closing")
|
||||
|
||||
listener, err := hopp.Listen(network, addr, nil)
|
||||
if err != nil { return err }
|
||||
log.Printf("(i) hosting on %s", addr)
|
||||
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) {
|
||||
defer trans.Close()
|
||||
|
||||
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
|
||||
// }
|
||||
// }
|
||||
method, reader, err := trans.ReceiveReader()
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
log.Println("SERVER got", method, data)
|
||||
log.Println("SERVER send", method, data)
|
||||
func (){
|
||||
writer, err := trans.SendWriter(1)
|
||||
if err != nil { log.Println("SERVER", err); return }
|
||||
defer writer.Close()
|
||||
_, err = writer.Write(data[:])
|
||||
if err != nil { log.Println("SERVER", 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)
|
||||
}
|
||||
}
|
||||
66
examples/ping/client/main.go
Normal file
66
examples/ping/client/main.go
Normal file
@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "log"
|
||||
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() {
|
||||
message := ping.MessagePing(0)
|
||||
for {
|
||||
log.Printf("<-- ping (%d)", message)
|
||||
_, err := ping.Send(trans, &message)
|
||||
handleErr(1, err)
|
||||
message ++
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func handleErr(code int, err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
143
examples/ping/protocol.go
Normal file
143
examples/ping/protocol.go
Normal file
@ -0,0 +1,143 @@
|
||||
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)
|
||||
}
|
||||
7
examples/ping/protocol.pdl
Normal file
7
examples/ping/protocol.pdl
Normal file
@ -0,0 +1,7 @@
|
||||
// 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
|
||||
77
examples/ping/server/main.go
Normal file
77
examples/ping/server/main.go
Normal file
@ -0,0 +1,77 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
|
||||
const imports =
|
||||
`
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/sashakoshka/hopp"
|
||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||
`
|
||||
@ -20,7 +21,6 @@ const preamble = `
|
||||
// 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
|
||||
|
||||
`
|
||||
|
||||
const static = `
|
||||
@ -36,6 +36,17 @@ type Message interface {
|
||||
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
|
||||
@ -58,6 +69,7 @@ func boolInt(input bool) int {
|
||||
}
|
||||
}
|
||||
|
||||
// ensure ucontainer is always imported
|
||||
var _ hopp.Option[int]
|
||||
`
|
||||
|
||||
@ -121,6 +133,10 @@ func (this *Generator) Generate(protocol *Protocol) (n int, err error) {
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
|
||||
// receive
|
||||
nn, err = this.generateReceive()
|
||||
n += nn; if err != nil { return n, err }
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@ -184,7 +200,7 @@ func (this *Generator) generateTypedef(name string, typedef Typedef) (n int, err
|
||||
|
||||
// DecodeValue method
|
||||
nn, err = this.iprintf(
|
||||
"\n // DecodeValue decodes the value of this type without " +
|
||||
"\n// DecodeValue decodes the value of this type without " +
|
||||
"the tag. The value is\n// decoded according to the " +
|
||||
"parameters specified by the tag, if possible.\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
@ -1140,8 +1156,10 @@ func (this *Generator) generateTypeTableDefined(typ TypeTableDefined) (n int, er
|
||||
|
||||
for _, key := range slices.Sorted(maps.Keys(typ.Fields)) {
|
||||
field := typ.Fields[key]
|
||||
nn, err := this.iprintf("%s\n", this.formatComment(field.Doc))
|
||||
n += nn; if err != nil { return n, err }
|
||||
if field.Doc != "" {
|
||||
nn, err := this.iprintf("%s\n", this.formatComment(field.Doc))
|
||||
n += nn; if err != nil { return n, err }
|
||||
}
|
||||
nn, err = this.iprintf("%s ", field.Name)
|
||||
n += nn; if err != nil { return n, err }
|
||||
if field.Option {
|
||||
@ -1182,6 +1200,53 @@ func (this *Generator) generateCanAssign(typ Type, tagSource string) (n int, err
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// generateReceive generates a function Receive(hopp.Trans) (Message, int, error)
|
||||
func (this *Generator) generateReceive() (n int, err error) {
|
||||
nn, err := this.iprintf(
|
||||
"\n// Receive decodes a message from a transaction and returns it as a value.\n" +
|
||||
"// Use a type switch to determine what type of message it is.\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf(
|
||||
"func Receive(trans hopp.Trans) (message any, n int, err error) {\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf("method, reader, err := trans.ReceiveReader()\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("decoder := tape.NewDecoder(reader)\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("if err != nil { return nil, n, err }\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("switch method {\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
for method, message := range this.protocol.Messages {
|
||||
nn, err = this.iprintf("case 0x%04X:\n", method)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.push()
|
||||
nn, err = this.iprintf(
|
||||
"var message %s\n",
|
||||
this.resolveMessageName(message.Name))
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err := this.iprintf(
|
||||
"nn, err := message.Decode(decoder)\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("n += nn; if err != nil { return nil, n, err }\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
nn, err = this.iprintf("return message, n, nil\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.pop()
|
||||
}
|
||||
nn, err = this.iprint("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
// fuck off go vet
|
||||
str := `return nil, n, fmt.Errorf("%w: M%04X", hopp.ErrUnknownMethod, method)`
|
||||
nn, err = this.iprintln(str)
|
||||
n += nn; if err != nil { return n, err }
|
||||
this.pop()
|
||||
nn, err = this.iprint("}\n")
|
||||
n += nn; if err != nil { return n, err }
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (this *Generator) validateIntBitSize(size int) error {
|
||||
switch size {
|
||||
case 5, 8, 16, 32, 64: return nil
|
||||
|
||||
@ -276,31 +276,32 @@ func TestGenerateRunEncodeDecode(test *testing.T) {
|
||||
}
|
||||
testEncodeDecode(
|
||||
&messageDynamic,
|
||||
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,
|
||||
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,
|
||||
0x00, 0x07,
|
||||
0x00, 0x06,
|
||||
0x00, 0x05,
|
||||
0x00, 0x04 },
|
||||
[]byte { 0x00, 0x0C, 0xE0, 0x02,
|
||||
0x00, 0x04),
|
||||
snake.L(0x00, 0x0C, 0xE0, 0x02,
|
||||
0x00, 0x01, 0x40, 0x08,
|
||||
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 },
|
||||
))
|
||||
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)),
|
||||
)))
|
||||
`)
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ 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 ---")`
|
||||
@ -61,8 +62,9 @@ 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 []byte: flat = data
|
||||
case tu.Snake: flat = data.Flatten()
|
||||
case snake.Snake: flat = data.Flatten()
|
||||
}
|
||||
message := reflect.New(reflect.ValueOf(correct).Elem().Type()).Interface().(Message)
|
||||
log.Println("before: ", message)
|
||||
@ -79,9 +81,7 @@ func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCa
|
||||
}
|
||||
}
|
||||
|
||||
// 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 { }
|
||||
func testEncodeDecode(message Message, data any) {buffer := bytes.Buffer { }
|
||||
log.Println("encoding:")
|
||||
encoder := tape.NewEncoder(&buffer)
|
||||
n, err := message.Encode(encoder)
|
||||
@ -93,13 +93,30 @@ func testGenerateRun(test *testing.T, protocol *Protocol, title, imports, testCa
|
||||
if n != len(got) {
|
||||
log.Fatalf("n incorrect: %d != %d\n", n, len(got))
|
||||
}
|
||||
if ok, n := data.Check(got); !ok {
|
||||
log.Fatalln("not equal at", n)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
56
internal/connshark/connshark.go
Normal file
56
internal/connshark/connshark.go
Normal file
@ -0,0 +1,56 @@
|
||||
package connshark
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "net"
|
||||
import "log"
|
||||
import "sync"
|
||||
import "math/rand"
|
||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||
|
||||
type insert struct {
|
||||
net.Conn
|
||||
output io.WriteCloser
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func LogDebugFile(underlying net.Conn) net.Conn {
|
||||
file, err := os.Create(fmt.Sprintf("connshark-%08X.log", rand.Uint32()))
|
||||
if err != nil {
|
||||
log.Println("XXX COULD NOT OPEN DEBUG FILE! reason: ", err)
|
||||
return underlying
|
||||
}
|
||||
return Log(underlying, file)
|
||||
}
|
||||
|
||||
func Log(underlying net.Conn, output io.WriteCloser) net.Conn {
|
||||
return &insert {
|
||||
Conn: underlying,
|
||||
output: output,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *insert) Read(buffer []byte) (n int, err error) {
|
||||
if n > 0 {
|
||||
this.lock.Lock()
|
||||
defer this.lock.Unlock()
|
||||
fmt.Fprintf(this.output, "TX: %s\n", tu.HexBytes(buffer[:n]))
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (this *insert) Write(buffer []byte) (n int, err error) {
|
||||
n, err = this.Conn.Write(buffer)
|
||||
if n > 0 {
|
||||
this.lock.Lock()
|
||||
defer this.lock.Unlock()
|
||||
fmt.Fprintf(this.output, "RX: %s\n", tu.HexBytes(buffer[:n]))
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (this *insert) Close() error {
|
||||
this.output.Close()
|
||||
return this.Conn.Close()
|
||||
}
|
||||
214
internal/testutil/snake/snake.go
Normal file
214
internal/testutil/snake/snake.go
Normal file
@ -0,0 +1,214 @@
|
||||
// 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
|
||||
}
|
||||
89
internal/testutil/snake/snake_test.go
Normal file
89
internal/testutil/snake/snake_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
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) }
|
||||
}
|
||||
79
listen.go
79
listen.go
@ -15,14 +15,34 @@ type Listener interface {
|
||||
Addr() net.Addr
|
||||
}
|
||||
|
||||
// Listen listens for incoming HOPP connections. The network must be one of
|
||||
// "quic", "quic4", (IPv4-only) "quic6" (IPv6-only), or "unix". For now, quic is
|
||||
// not supported.
|
||||
func Listen(network, address string) (Listener, error) {
|
||||
// Listen listens for incoming HOPP connections. The network must be one of:
|
||||
//
|
||||
// - "quic"
|
||||
// - "quic4" (IPv4-only)
|
||||
// - "quic6" (IPv6-only)
|
||||
// - "tls"
|
||||
// - "tls4" (IPv4-only)
|
||||
// - "tls6" (IPv6-only)
|
||||
// - "tcp"
|
||||
// - "tcp4" (IPv4-only)
|
||||
// - "tcp6" (IPv6-only)
|
||||
// - "unix"
|
||||
//
|
||||
// For now, QUIC is unsupported.
|
||||
func Listen(network, address string, tlsConf *tls.Config) (Listener, error) {
|
||||
switch network {
|
||||
case "quic", "quic4", "quic6": return ListenQUIC(network, address, nil)
|
||||
case "unix": return ListenUnix(network, address)
|
||||
default: return nil, ErrUnknownNetwork
|
||||
case "quic", "quic4", "quic6": return ListenQUIC(network, address, tlsConf)
|
||||
case "tls", "tls4", "tls6": return ListenTLS(network, address, tlsConf)
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
addr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil { return nil, err }
|
||||
return ListenTCP(network, addr)
|
||||
case "unix":
|
||||
addr, err := net.ResolveUnixAddr(network, address)
|
||||
if err != nil { return nil, err }
|
||||
return ListenUnix(network, addr)
|
||||
default:
|
||||
return nil, ErrUnknownNetwork
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,33 +54,52 @@ func ListenQUIC(network, address string, tlsConf *tls.Config) (Listener, error)
|
||||
return nil, errors.New("quic is not yet implemented")
|
||||
}
|
||||
|
||||
// ListenUnix listens for incoming HOPP connections using a Unix domain socket
|
||||
// as a transport. The network must be "unix".
|
||||
func ListenUnix(network, address string) (Listener, error) {
|
||||
if network != "unix" { return nil, ErrUnknownNetwork }
|
||||
addr, err := net.ResolveUnixAddr(network, address)
|
||||
// ListenTLS listens for incoming HOPP connections using a TLS socket as a
|
||||
// transport. The network must be "tcp".
|
||||
func ListenTLS(network, address string, tlsConf *tls.Config) (Listener, error) {
|
||||
network, err := tlsNetworkToTCPNetwork(network)
|
||||
if err != nil { return nil, err }
|
||||
unixListener, err := net.ListenUnix(network, addr)
|
||||
listener, err := tls.Listen(network, address, tlsConf)
|
||||
if err != nil { return nil, err }
|
||||
return &listenerUnix {
|
||||
underlying: unixListener,
|
||||
return &netListenerWrapper {
|
||||
underlying: listener,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type listenerUnix struct {
|
||||
underlying *net.UnixListener
|
||||
// ListenTCP listens for incoming HOPP connections using a TCP socket as a
|
||||
// transport. The network must be "tcp".
|
||||
func ListenTCP(network string, laddr *net.TCPAddr) (Listener, error) {
|
||||
listener, err := net.ListenTCP(network, laddr)
|
||||
if err != nil { return nil, err }
|
||||
return &netListenerWrapper {
|
||||
underlying: listener,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *listenerUnix) Accept() (Conn, error) {
|
||||
// ListenUnix listens for incoming HOPP connections using a Unix domain socket
|
||||
// as a transport. The network must be "unix".
|
||||
func ListenUnix(network string, addr *net.UnixAddr) (Listener, error) {
|
||||
listener, err := net.ListenUnix(network, addr)
|
||||
if err != nil { return nil, err }
|
||||
return &netListenerWrapper {
|
||||
underlying: listener,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type netListenerWrapper struct {
|
||||
underlying net.Listener
|
||||
}
|
||||
|
||||
func (this *netListenerWrapper) Accept() (Conn, error) {
|
||||
conn, err := this.underlying.Accept()
|
||||
if err != nil { return nil, err }
|
||||
return AdaptA(conn, ServerSide), nil
|
||||
}
|
||||
|
||||
func (this *listenerUnix) Close() error {
|
||||
func (this *netListenerWrapper) Close() error {
|
||||
return this.underlying.Close()
|
||||
}
|
||||
|
||||
func (this *listenerUnix) Addr() net.Addr {
|
||||
func (this *netListenerWrapper) Addr() net.Addr {
|
||||
return this.underlying.Addr()
|
||||
}
|
||||
|
||||
42
metadapta.go
42
metadapta.go
@ -6,6 +6,7 @@ import "fmt"
|
||||
import "net"
|
||||
import "sync"
|
||||
import "time"
|
||||
import "context"
|
||||
import "sync/atomic"
|
||||
import "git.tebibyte.media/sashakoshka/go-util/sync"
|
||||
|
||||
@ -39,20 +40,23 @@ type a struct {
|
||||
sendLock sync.Mutex
|
||||
transMap map[int64] *transA
|
||||
transChan chan *transA
|
||||
done chan struct { }
|
||||
ctx context.Context
|
||||
done func()
|
||||
err error
|
||||
}
|
||||
|
||||
// AdaptA returns a connection implementing METADAPT-A over a singular stream-
|
||||
// oriented transport such as TCP or UNIX domain stream sockets.
|
||||
func AdaptA(underlying net.Conn, party Party) Conn {
|
||||
ctx, done := context.WithCancel(context.Background())
|
||||
conn := &a {
|
||||
sizeLimit: defaultSizeLimit,
|
||||
underlying: underlying,
|
||||
party: party,
|
||||
transMap: make(map[int64] *transA),
|
||||
transChan: make(chan *transA),
|
||||
done: make(chan struct { }),
|
||||
ctx: ctx,
|
||||
done: done,
|
||||
}
|
||||
if party == ClientSide {
|
||||
conn.transID = 1
|
||||
@ -60,11 +64,15 @@ func AdaptA(underlying net.Conn, party Party) Conn {
|
||||
conn.transID = -1
|
||||
}
|
||||
go conn.receive()
|
||||
go func() {
|
||||
<- ctx.Done()
|
||||
underlying.Close()
|
||||
}()
|
||||
return conn
|
||||
}
|
||||
|
||||
func (this *a) Close() error {
|
||||
close(this.done)
|
||||
this.done()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -105,7 +113,7 @@ func (this *a) AcceptTrans() (Trans, error) {
|
||||
return nil, eof
|
||||
}
|
||||
return trans, nil
|
||||
case <- this.done:
|
||||
case <- this.ctx.Done():
|
||||
return nil, eof
|
||||
}
|
||||
}
|
||||
@ -124,10 +132,10 @@ func (this *a) unlistTransactionSafe(id int64) {
|
||||
delete(this.transMap, id)
|
||||
}
|
||||
|
||||
func (this *a) sendMessageSafe(trans int64, method uint16, data []byte) error {
|
||||
func (this *a) sendMessageSafe(trans int64, method uint16, ccb uint64, data []byte) error {
|
||||
this.sendLock.Lock()
|
||||
defer this.sendLock.Unlock()
|
||||
return encodeMessageA(this.underlying, this.sizeLimit, trans, method, data)
|
||||
return encodeMessageA(this.underlying, this.sizeLimit, trans, method, 0, data)
|
||||
}
|
||||
|
||||
func (this *a) receive() {
|
||||
@ -251,7 +259,7 @@ func (this *transA) ID() int64 {
|
||||
}
|
||||
|
||||
func (this *transA) Send(method uint16, data []byte) error {
|
||||
return this.parent.sendMessageSafe(this.id, method, data)
|
||||
return this.parent.sendMessageSafe(this.id, method, 0, data)
|
||||
}
|
||||
|
||||
func (this *transA) SendWriter(method uint16) (io.WriteCloser, error) {
|
||||
@ -382,7 +390,7 @@ func (this *readerA) pull() (uint16, error) {
|
||||
// close and return error on failure
|
||||
this.eof = true
|
||||
this.parent.Close()
|
||||
return 0, fmt.Errorf("could not receive message: %w", this.parent.bestErr())
|
||||
return 0, this.parent.bestErr()
|
||||
}
|
||||
|
||||
func (this *readerA) Read(buffer []byte) (int, error) {
|
||||
@ -405,7 +413,7 @@ type writerA struct {
|
||||
}
|
||||
|
||||
func (this *writerA) Write(data []byte) (n int, err error) {
|
||||
if !this.open { return 0, io.EOF }
|
||||
if !this.open || this.parent.closed.Load() { return 0, io.EOF }
|
||||
toSend := data
|
||||
for len(toSend) > 0 {
|
||||
nn, err := this.writeOne(toSend)
|
||||
@ -417,6 +425,7 @@ func (this *writerA) Write(data []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (this *writerA) Close() error {
|
||||
this.flush(0)
|
||||
this.open = false
|
||||
return nil
|
||||
}
|
||||
@ -430,21 +439,21 @@ func (this *writerA) writeOne(data []byte) (n int, err error) {
|
||||
n = len(data)
|
||||
// if have a full chunk, flush
|
||||
if int64(len(this.buffer)) == this.chunkSize {
|
||||
err = this.flush()
|
||||
err = this.flush(1)
|
||||
if err != nil { return n, err }
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// if not, flush and store as much as we can in the buffer
|
||||
err = this.flush()
|
||||
err = this.flush(1)
|
||||
if err != nil { return n, err }
|
||||
this.buffer = append(this.buffer, data...)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (this *writerA) flush() error {
|
||||
return this.parent.parent.sendMessageSafe(this.parent.id, this.method, this.buffer)
|
||||
func (this *writerA) flush(ccb uint64) error {
|
||||
return this.parent.parent.sendMessageSafe(this.parent.id, this.method, ccb, this.buffer)
|
||||
}
|
||||
|
||||
type incomingMessage struct {
|
||||
@ -458,15 +467,19 @@ func encodeMessageA(
|
||||
sizeLimit int64,
|
||||
trans int64,
|
||||
method uint16,
|
||||
ccb uint64,
|
||||
data []byte,
|
||||
) error {
|
||||
if int64(len(data)) > sizeLimit {
|
||||
return ErrPayloadTooLarge
|
||||
}
|
||||
buffer := make([]byte, 18 + len(data))
|
||||
// transaction ID field
|
||||
encodeI64(buffer[:8], trans)
|
||||
// method field
|
||||
encodeI16(buffer[8:10], method)
|
||||
encodeI64(buffer[10:18], uint64(len(data)))
|
||||
// payload size field
|
||||
encodeI64(buffer[10:18], uint64(len(data)) & 0x7FFFFFFFFFFFFFFF | ccb << 63)
|
||||
copy(buffer[18:], data)
|
||||
_, err := writer.Write(buffer)
|
||||
return err
|
||||
@ -485,6 +498,7 @@ func decodeMessageA(
|
||||
headerBuffer := [18]byte { }
|
||||
_, err = io.ReadFull(reader, headerBuffer[:])
|
||||
if err != nil { return 0, 0, false, nil, err }
|
||||
|
||||
transID, err = decodeI64[int64](headerBuffer[:8])
|
||||
if err != nil { return 0, 0, false, nil, err }
|
||||
method, err = decodeI16[uint16](headerBuffer[8:10])
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package hopp
|
||||
|
||||
import "io"
|
||||
import "net"
|
||||
import "bytes"
|
||||
import "errors"
|
||||
import "slices"
|
||||
import "testing"
|
||||
import "context"
|
||||
|
||||
// some of these tests spawn goroutines that can signal a failure.
|
||||
// abide by the documentation for testing.T (https://pkg.go.dev/testing#T):
|
||||
@ -52,6 +52,7 @@ func TestConnA(test *testing.T) {
|
||||
test.Error("CLIENT payload:", gotPayload)
|
||||
test.Fatal("CLIENT ok byeeeeeeeeeeeee")
|
||||
}
|
||||
test.Log("CLIENT transaction has closed")
|
||||
}
|
||||
|
||||
serverFunc := func(a Conn) {
|
||||
@ -70,20 +71,6 @@ func TestConnA(test *testing.T) {
|
||||
}
|
||||
|
||||
func TestTransOpenCloseA(test *testing.T) {
|
||||
// currently:
|
||||
//
|
||||
// | data sent | data recvd | close sent | close recvd
|
||||
// 10 | X | X | X | server hangs
|
||||
// 20 | X | X | X | client hangs
|
||||
// 30 | X | | X |
|
||||
//
|
||||
// when a close message is recvd, it tries to push to the trans and
|
||||
// hangs on trans.incoming.Send, which hangs on sending the value to the
|
||||
// underlying channel. why is this?
|
||||
//
|
||||
// check if we are really getting values from the channel when pulling
|
||||
// from the trans channel when we are expecting a close.
|
||||
|
||||
clientFunc := func(conn Conn) {
|
||||
// 10
|
||||
trans, err := conn.OpenTrans()
|
||||
@ -142,10 +129,69 @@ func TestTransOpenCloseA(test *testing.T) {
|
||||
clientServerEnvironment(test, clientFunc, serverFunc)
|
||||
}
|
||||
|
||||
func TestReadWriteA(test *testing.T) {
|
||||
payloads := []string {
|
||||
"hello",
|
||||
"world",
|
||||
"When the impostor is sus!",
|
||||
}
|
||||
|
||||
clientFunc := func(a Conn) {
|
||||
test.Log("CLIENT accepting transaction")
|
||||
trans, err := a.AcceptTrans()
|
||||
if err != nil { test.Fatal("CLIENT", err) }
|
||||
test.Log("CLIENT accepted transaction")
|
||||
test.Cleanup(func() { trans.Close() })
|
||||
for method, payload := range payloads {
|
||||
test.Log("CLIENT waiting...")
|
||||
gotMethod, gotReader, err := trans.ReceiveReader()
|
||||
if err != nil { test.Fatal("CLIENT", err) }
|
||||
gotPayloadBytes, err := io.ReadAll(gotReader)
|
||||
if err != nil { test.Fatal("CLIENT", err) }
|
||||
gotPayload := string(gotPayloadBytes)
|
||||
test.Log("CLIENT m:", gotMethod, "p:", gotPayload)
|
||||
if int(gotMethod) != method {
|
||||
test.Errorf("CLIENT method not equal")
|
||||
}
|
||||
if gotPayload != payload {
|
||||
test.Errorf("CLIENT payload not equal")
|
||||
}
|
||||
}
|
||||
test.Log("CLIENT waiting for transaction close...")
|
||||
gotMethod, gotPayload, err := trans.Receive()
|
||||
if !errors.Is(err, io.EOF) {
|
||||
test.Error("CLIENT wrong error:", err)
|
||||
test.Error("CLIENT method:", gotMethod)
|
||||
test.Error("CLIENT payload:", gotPayload)
|
||||
test.Fatal("CLIENT ok byeeeeeeeeeeeee")
|
||||
}
|
||||
test.Log("CLIENT transaction has closed")
|
||||
}
|
||||
|
||||
serverFunc := func(a Conn) {
|
||||
defer test.Log("SERVER closing connection")
|
||||
trans, err := a.OpenTrans()
|
||||
if err != nil { test.Error("SERVER", err); return }
|
||||
test.Cleanup(func() { trans.Close() })
|
||||
for method, payload := range payloads {
|
||||
test.Log("SERVER m:", method, "p:", payload)
|
||||
func() {
|
||||
writer, err := trans.SendWriter(uint16(method))
|
||||
if err != nil { test.Error("SERVER", err); return }
|
||||
defer writer.Close()
|
||||
_, err = writer.Write([]byte(payload))
|
||||
if err != nil { test.Error("SERVER", err); return }
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
clientServerEnvironment(test, clientFunc, serverFunc)
|
||||
}
|
||||
|
||||
func TestEncodeMessageA(test *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
payload := []byte { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }
|
||||
err := encodeMessageA(buffer, defaultSizeLimit, 0x5800FEABC3104F04, 0x6B12, payload)
|
||||
err := encodeMessageA(buffer, defaultSizeLimit, 0x5800FEABC3104F04, 0x6B12, 0, payload)
|
||||
correct := []byte {
|
||||
0x58, 0x00, 0xFE, 0xAB, 0xC3, 0x10, 0x4F, 0x04,
|
||||
0x6B, 0x12,
|
||||
@ -163,7 +209,7 @@ func TestEncodeMessageA(test *testing.T) {
|
||||
func TestEncodeMessageAErr(test *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
payload := make([]byte, 0x10000)
|
||||
err := encodeMessageA(buffer, 0x20, 0x5800FEABC3104F04, 0x6B12, payload)
|
||||
err := encodeMessageA(buffer, 0x20, 0x5800FEABC3104F04, 0x6B12, 0, payload)
|
||||
if !errors.Is(err, ErrPayloadTooLarge) {
|
||||
test.Fatalf("wrong error: %v", err)
|
||||
}
|
||||
@ -208,7 +254,7 @@ func TestEncodeDecodeMessageA(test *testing.T) {
|
||||
correctMethod := uint16(30)
|
||||
correctPayload := []byte("good")
|
||||
buffer := bytes.Buffer { }
|
||||
err := encodeMessageA(&buffer, defaultSizeLimit, correctTransID, correctMethod, correctPayload)
|
||||
err := encodeMessageA(&buffer, defaultSizeLimit, correctTransID, correctMethod, 0, correctPayload)
|
||||
if err != nil { test.Fatal(err) }
|
||||
transID, method, chunked, payload, err := decodeMessageA(&buffer, defaultSizeLimit)
|
||||
if got, correct := transID, int64(2); got != correct {
|
||||
@ -230,34 +276,31 @@ func clientServerEnvironment(test *testing.T, clientFunc func(conn Conn), server
|
||||
addr := "localhost:7959"
|
||||
|
||||
// server
|
||||
listener, err := net.Listen(network, addr)
|
||||
if err != nil { test.Fatal(err) }
|
||||
listener, err := Listen(network, addr, nil)
|
||||
test.Cleanup(func() { listener.Close() })
|
||||
go func() {
|
||||
test.Log("SERVER listening")
|
||||
conn, err := listener.Accept()
|
||||
if err != nil { test.Error("SERVER", err); return }
|
||||
|
||||
defer conn.Close()
|
||||
test.Cleanup(func() { conn.Close() })
|
||||
a := AdaptA(conn, ServerSide)
|
||||
test.Cleanup(func() { a.Close() })
|
||||
|
||||
serverFunc(a)
|
||||
|
||||
serverFunc(conn)
|
||||
test.Log("SERVER closing")
|
||||
}()
|
||||
|
||||
// client
|
||||
test.Log("CLIENT dialing")
|
||||
conn, err := net.Dial(network, addr)
|
||||
conn, err := Dial(context.Background(), network, addr, nil)
|
||||
if err != nil { test.Fatal("CLIENT", err) }
|
||||
test.Cleanup(func() { conn.Close() })
|
||||
test.Log("CLIENT dialed")
|
||||
a := AdaptA(conn, ClientSide)
|
||||
test.Cleanup(func() { a.Close() })
|
||||
|
||||
clientFunc(a)
|
||||
clientFunc(conn)
|
||||
|
||||
test.Log("CLIENT waiting for connection close...")
|
||||
trans, err := a.AcceptTrans()
|
||||
trans, err := conn.AcceptTrans()
|
||||
if !errors.Is(err, io.EOF) {
|
||||
test.Error("CLIENT wrong error:", err)
|
||||
test.Fatal("CLIENT trans:", trans)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user