Compare commits

...

3 Commits

Author SHA1 Message Date
14a317c2ab examples/chat: Make chat example compile 2025-10-19 13:18:24 -04:00
e5d7ad0702 generate: Emit Send/Receive functions 2025-10-19 13:17:21 -04:00
bb520976be Fix dialTLS 2025-10-17 21:48:11 -04:00
9 changed files with 193 additions and 179 deletions

View File

@ -43,7 +43,7 @@ func (diale Dialer) dialQUIC(ctx context.Context, network, address string) (Conn
} }
func (diale Dialer) dialTLS(ctx context.Context, network, address string) (Conn, error) { func (diale Dialer) dialTLS(ctx context.Context, network, address string) (Conn, error) {
conn, err := tls.Dial(network, nil, addr, diale.TLSConfig) conn, err := tls.Dial(network, address, diale.TLSConfig)
if err != nil { return nil, err } if err != nil { return nil, err }
return AdaptA(conn, ClientSide), nil return AdaptA(conn, ClientSide), nil
} }

View File

@ -17,8 +17,8 @@ func main() {
} }
address := os.Args[1] address := os.Args[1]
room := os.Args[2] room := os.Args[2]
var nickname hopp.Option[string]; if len(os.Args) >= 4 { nickname := "Anonymous"; if len(os.Args) >= 4 {
nickname = hopp.O(os.Args[3]) nickname = os.Args[3]
} }
trans, err := join(address, room, nickname) trans, err := join(address, room, nickname)
handleErr(1, err) handleErr(1, err)
@ -31,15 +31,11 @@ func main() {
} }
}() }()
for { for {
message, err := chat.Receive(trans) message, _, err := chat.Receive(trans)
handleErr(1, err) handleErr(1, err)
switch message := message.(type) { switch message := message.(type) {
case *chat.MessageChat: case *chat.MessageChat:
nickname := "Anonymous" fmt.Fprintf(os.Stdout, "%s: %s\n", message.Nickname, message.Content)
if value, ok := message.Nickname.Get(); ok {
nickname = value
}
fmt.Fprintf(os.Stdout, "%s: %s\n", nickname, message.Content)
case *chat.MessageJoinNotify: case *chat.MessageJoinNotify:
fmt.Fprintf(os.Stdout, "(i) %s joined the room\n", message.Nickname) fmt.Fprintf(os.Stdout, "(i) %s joined the room\n", message.Nickname)
case *chat.MessageLeaveNotify: case *chat.MessageLeaveNotify:
@ -48,7 +44,7 @@ func main() {
} }
} }
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) ctx, done := context.WithTimeout(context.Background(), 16 * time.Second)
defer done() defer done()
dialer := hopp.Dialer { dialer := hopp.Dialer {
@ -57,41 +53,24 @@ func join(address string, room string, nickname hopp.Option[string]) (hopp.Trans
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
} }
conn, err := dialer.Dial(ctx, "quic", address) conn, err := dialer.Dial(ctx, "tcp", address)
if err != nil { return nil, err }
err = updateProfile(conn, nickname)
if err != nil { return nil, err } if err != nil { return nil, err }
transRoom, err := conn.OpenTrans() transRoom, err := conn.OpenTrans()
if err != nil { return nil, err } if err != nil { return nil, err }
err = chat.Send(transRoom, &chat.MessageJoin { _, err = chat.Send(transRoom, &chat.MessageJoin {
Room: room, Room: room,
Nickname: nickname,
}) })
if err != nil { return nil, err } if err != nil { return nil, err }
return transRoom, nil return transRoom, nil
} }
func send(trans hopp.Trans, content string) error { func send(trans hopp.Trans, content string) error {
return chat.Send(trans, &chat.MessageChat { _, err := chat.Send(trans, &chat.MessageChat {
Content: content, Content: content,
}) })
} return err
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
}
} }
func handleErr(code int, err error) { func handleErr(code int, err error) {

View File

@ -2,5 +2,5 @@
// source files, run this command from within the root directory of the // source files, run this command from within the root directory of the
// repository: // 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 package chat

View File

@ -3,8 +3,8 @@ package chat
import "fmt" import "fmt"
func (msg *MessageError) Error() string { func (msg *MessageError) Error() string {
if description, ok := msg.Description.Get(); ok { if description, ok := msg.Description.Value(); ok {
return fmt.Sprintf("other party sent error: %d %s", msg.Error, description) return fmt.Sprintf("other party sent error: %d %s", msg.Code, description)
} else { } else {
return fmt.Sprintf("other party sent error: %d", msg.Code) return fmt.Sprintf("other party sent error: %d", msg.Code)
} }

View File

@ -20,6 +20,15 @@ type Message interface {
Method() uint16 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)
return message.Encode(encoder)
}
// canAssign determines if data from the given source tag can be assigned to // 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 // a Go type represented by destination. It is designed to receive destination
// values from [generate.Generator.generateCanAssign]. The eventual Go type and // values from [generate.Generator.generateCanAssign]. The eventual Go type and
@ -225,7 +234,7 @@ func(this *MessageJoin) Decode(decoder *tape.Decoder) (n int, err error) {
// must be sent within a room transaction. // must be sent within a room transaction.
type MessageChat struct { type MessageChat struct {
Content string Content string
Nickname hopp.Option[string] Nickname string
} }
// Method returns the message's method number. // Method returns the message's method number.
@ -242,36 +251,34 @@ func(this *MessageChat) Encode(encoder *tape.Encoder) (n int, err error) {
nn, err = encoder.WriteUintN(2, tag_8.CN() + 1) nn, err = encoder.WriteUintN(2, tag_8.CN() + 1)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
{ {
nn, err = encoder.WriteUint16(0x0001)
n += nn; if err != nil { return n, err }
tag_9 := tape.StringTag(string((*this).Nickname))
nn, err = encoder.WriteUint8(uint8(tag_9))
n += nn; if err != nil { return n, err }
if len((*this).Nickname) > tape.MaxStructureLength {
return n, tape.ErrTooLong
}
if tag_9.Is(tape.LBA) {
nn, err = encoder.WriteUintN(uint64(len((*this).Nickname)), tag_9.CN())
n += nn; if err != nil { return n, err }
}
nn, err = encoder.Write([]byte((*this).Nickname))
n += nn; if err != nil { return n, err }
nn, err = encoder.WriteUint16(0x0000) nn, err = encoder.WriteUint16(0x0000)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
tag_9 := tape.StringTag(string((*this).Content)) tag_10 := tape.StringTag(string((*this).Content))
nn, err = encoder.WriteUint8(uint8(tag_9)) nn, err = encoder.WriteUint8(uint8(tag_10))
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
if len((*this).Content) > tape.MaxStructureLength { if len((*this).Content) > tape.MaxStructureLength {
return n, tape.ErrTooLong return n, tape.ErrTooLong
} }
if tag_9.Is(tape.LBA) { if tag_10.Is(tape.LBA) {
nn, err = encoder.WriteUintN(uint64(len((*this).Content)), tag_9.CN()) nn, err = encoder.WriteUintN(uint64(len((*this).Content)), tag_10.CN())
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
} }
nn, err = encoder.Write([]byte((*this).Content)) nn, err = encoder.Write([]byte((*this).Content))
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
if value, ok := (*this).Nickname.Value(); ok {
nn, err = encoder.WriteUint16(0x0001)
n += nn; if err != nil { return n, err }
tag_10 := tape.StringTag(string(value))
nn, err = encoder.WriteUint8(uint8(tag_10))
n += nn; if err != nil { return n, err }
if len(value) > tape.MaxStructureLength {
return n, tape.ErrTooLong
}
if tag_10.Is(tape.LBA) {
nn, err = encoder.WriteUintN(uint64(len(value)), tag_10.CN())
n += nn; if err != nil { return n, err }
}
nn, err = encoder.Write([]byte(value))
n += nn; if err != nil { return n, err }
}
} }
return n, nil return n, nil
} }
@ -573,22 +580,20 @@ func decodeBranch_5c1cf9347bb6d9f41cee64b186392d24_MessageChat(this *MessageChat
tape.Skim(decoder, fieldTag_31) tape.Skim(decoder, fieldTag_31)
continue continue
} }
var destination_33 string var length_33 uint64
var length_34 uint64
if fieldTag_31.Is(tape.LBA) { if fieldTag_31.Is(tape.LBA) {
length_34, nn, err = decoder.ReadUintN(int(fieldTag_31.CN())) length_33, nn, err = decoder.ReadUintN(int(fieldTag_31.CN()))
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
} else { } else {
length_34 = uint64(fieldTag_31.CN()) length_33 = uint64(fieldTag_31.CN())
} }
if length_34 > uint64(tape.MaxStructureLength) { if length_33 > uint64(tape.MaxStructureLength) {
return n, tape.ErrTooLong return n, tape.ErrTooLong
} }
buffer := make([]byte, length_34) buffer := make([]byte, length_33)
nn, err = decoder.Read(buffer) nn, err = decoder.Read(buffer)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
*(&destination_33) = string(buffer) *(&(this.Nickname)) = string(buffer)
this.Nickname = hopp.O(destination_33)
default: default:
tape.Skim(decoder, fieldTag_31) tape.Skim(decoder, fieldTag_31)
continue continue
@ -599,41 +604,41 @@ func decodeBranch_5c1cf9347bb6d9f41cee64b186392d24_MessageChat(this *MessageChat
func decodeBranch_68c536511e6d598462efc482144438e9_MessageJoinNotify(this *MessageJoinNotify, decoder *tape.Decoder, tag tape.Tag) (n int, err error) { func decodeBranch_68c536511e6d598462efc482144438e9_MessageJoinNotify(this *MessageJoinNotify, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
var nn int var nn int
var length_35 uint64 var length_34 uint64
if length_35 > uint64(tape.MaxStructureLength) { if length_34 > uint64(tape.MaxStructureLength) {
return n, tape.ErrTooLong return n, tape.ErrTooLong
} }
length_35, nn, err = decoder.ReadUintN(int(tag.CN()) + 1) length_34, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
for _ = range length_35 { for _ = range length_34 {
var fieldKey_36 uint16 var fieldKey_35 uint16
fieldKey_36, nn, err = decoder.ReadUint16() fieldKey_35, nn, err = decoder.ReadUint16()
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
var fieldTag_37 tape.Tag var fieldTag_36 tape.Tag
fieldTag_37, nn, err = decoder.ReadTag() fieldTag_36, nn, err = decoder.ReadTag()
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
switch fieldKey_36 { switch fieldKey_35 {
case 0x0000: case 0x0000:
if !(canAssign(tape.LBA, fieldTag_37)) { if !(canAssign(tape.LBA, fieldTag_36)) {
tape.Skim(decoder, fieldTag_37) tape.Skim(decoder, fieldTag_36)
continue continue
} }
var length_38 uint64 var length_37 uint64
if fieldTag_37.Is(tape.LBA) { if fieldTag_36.Is(tape.LBA) {
length_38, nn, err = decoder.ReadUintN(int(fieldTag_37.CN())) length_37, nn, err = decoder.ReadUintN(int(fieldTag_36.CN()))
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
} else { } else {
length_38 = uint64(fieldTag_37.CN()) length_37 = uint64(fieldTag_36.CN())
} }
if length_38 > uint64(tape.MaxStructureLength) { if length_37 > uint64(tape.MaxStructureLength) {
return n, tape.ErrTooLong return n, tape.ErrTooLong
} }
buffer := make([]byte, length_38) buffer := make([]byte, length_37)
nn, err = decoder.Read(buffer) nn, err = decoder.Read(buffer)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
*(&(this.Nickname)) = string(buffer) *(&(this.Nickname)) = string(buffer)
default: default:
tape.Skim(decoder, fieldTag_37) tape.Skim(decoder, fieldTag_36)
continue continue
} }
} }
@ -642,43 +647,84 @@ func decodeBranch_68c536511e6d598462efc482144438e9_MessageJoinNotify(this *Messa
func decodeBranch_68c536511e6d598462efc482144438e9_MessageLeaveNotify(this *MessageLeaveNotify, decoder *tape.Decoder, tag tape.Tag) (n int, err error) { func decodeBranch_68c536511e6d598462efc482144438e9_MessageLeaveNotify(this *MessageLeaveNotify, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {
var nn int var nn int
var length_39 uint64 var length_38 uint64
if length_39 > uint64(tape.MaxStructureLength) { if length_38 > uint64(tape.MaxStructureLength) {
return n, tape.ErrTooLong return n, tape.ErrTooLong
} }
length_39, nn, err = decoder.ReadUintN(int(tag.CN()) + 1) length_38, nn, err = decoder.ReadUintN(int(tag.CN()) + 1)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
for _ = range length_39 { for _ = range length_38 {
var fieldKey_40 uint16 var fieldKey_39 uint16
fieldKey_40, nn, err = decoder.ReadUint16() fieldKey_39, nn, err = decoder.ReadUint16()
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
var fieldTag_41 tape.Tag var fieldTag_40 tape.Tag
fieldTag_41, nn, err = decoder.ReadTag() fieldTag_40, nn, err = decoder.ReadTag()
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
switch fieldKey_40 { switch fieldKey_39 {
case 0x0000: case 0x0000:
if !(canAssign(tape.LBA, fieldTag_41)) { if !(canAssign(tape.LBA, fieldTag_40)) {
tape.Skim(decoder, fieldTag_41) tape.Skim(decoder, fieldTag_40)
continue continue
} }
var length_42 uint64 var length_41 uint64
if fieldTag_41.Is(tape.LBA) { if fieldTag_40.Is(tape.LBA) {
length_42, nn, err = decoder.ReadUintN(int(fieldTag_41.CN())) length_41, nn, err = decoder.ReadUintN(int(fieldTag_40.CN()))
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
} else { } else {
length_42 = uint64(fieldTag_41.CN()) length_41 = uint64(fieldTag_40.CN())
} }
if length_42 > uint64(tape.MaxStructureLength) { if length_41 > uint64(tape.MaxStructureLength) {
return n, tape.ErrTooLong return n, tape.ErrTooLong
} }
buffer := make([]byte, length_42) buffer := make([]byte, length_41)
nn, err = decoder.Read(buffer) nn, err = decoder.Read(buffer)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
*(&(this.Nickname)) = string(buffer) *(&(this.Nickname)) = string(buffer)
default: default:
tape.Skim(decoder, fieldTag_41) tape.Skim(decoder, fieldTag_40)
continue continue
} }
} }
return n, nil 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 0001:
var message MessageSuccess
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
case 0200:
var message MessageJoin
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
case 0300:
var message MessageChat
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
case 0400:
var message MessageJoinNotify
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
case 0401:
var message MessageLeaveNotify
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
case 0000:
var message MessageError
nn, err := message.Decode(decoder)
n += nn; if err != nil { return nil, n, err }
return message, n, nil
}
return nil, n, hopp.ErrUnknownMethod
}

View File

@ -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.

View File

@ -32,7 +32,7 @@ M0200 Join {
// must be sent within a room transaction. // must be sent within a room transaction.
M0300 Chat { M0300 Chat {
0000 Content String, 0000 Content String,
0001 Nickname ?String, 0001 Nickname String,
} }
// JoinNotify is sent by the server when another client joins the room. It must be // JoinNotify is sent by the server when another client joins the room. It must be

View File

@ -28,7 +28,7 @@ func main() {
func host(address string, certPath, keyPath string) error { func host(address string, certPath, keyPath string) error {
keyPair, err := tls.LoadX509KeyPair(certPath, keyPath) keyPair, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil { return err } if err != nil { return err }
listener, err := hopp.ListenQUIC("quic", address, &tls.Config { listener, err := hopp.ListenTLS("tcp", address, &tls.Config {
InsecureSkipVerify: true, InsecureSkipVerify: true,
Certificates: []tls.Certificate { keyPair }, Certificates: []tls.Certificate { keyPair },
}) })
@ -63,6 +63,7 @@ func (this *client) run() {
log.Println("accepted transaction") log.Println("accepted transaction")
if err != nil { if err != nil {
log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err) log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err)
continue
} }
go this.runTrans(trans) go this.runTrans(trans)
} }
@ -70,7 +71,7 @@ func (this *client) run() {
func (this *client) runTrans(trans hopp.Trans) { func (this *client) runTrans(trans hopp.Trans) {
defer trans.Close() defer trans.Close()
message, err := chat.Receive(trans) message, _, err := chat.Receive(trans)
if err != nil { if err != nil {
log.Printf( log.Printf(
"XXX %v transaction failed: %v", "XXX %v transaction failed: %v",
@ -97,7 +98,7 @@ func (this *client) transTalk(trans hopp.Trans, initial *chat.MessageJoin) error
if err != nil { return err } if err != nil { return err }
defer this.leaveRoom(trans, room) defer this.leaveRoom(trans, room)
for { for {
message, err := chat.Receive(trans) message, _, err := chat.Receive(trans)
if err != nil { return err } if err != nil { return err }
switch message := message.(type) { switch message := message.(type) {
case *chat.MessageChat: case *chat.MessageChat:
@ -110,7 +111,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 { 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.Println("(). %s #%s: %s", this.nickname, room, message.Content)
clients, done := clients.RBorrow() clients, done := clients.RBorrow()
defer done() defer done()
for client := range clients { for client := range clients {
@ -126,7 +127,7 @@ func (this *client) relayMessage(room string, message *chat.MessageChat) error {
rooms, done := this.rooms.RBorrow() rooms, done := this.rooms.RBorrow()
defer done() defer done()
if trans, ok := rooms[room]; ok { if trans, ok := rooms[room]; ok {
err := chat.Send(trans, message) _, err := chat.Send(trans, message)
if err != nil { if err != nil {
return fmt.Errorf("could not relay message: %w", err) return fmt.Errorf("could not relay message: %w", err)
} }
@ -141,7 +142,7 @@ func (this *client) joinRoom(trans hopp.Trans, room string) error {
return fmt.Errorf("already joined %s", room) return fmt.Errorf("already joined %s", room)
} }
rooms[room] = trans 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 return nil
} }
@ -152,7 +153,7 @@ func (this *client) leaveRoom(trans hopp.Trans, room string) error {
return fmt.Errorf("not in %s", room) return fmt.Errorf("not in %s", room)
} }
delete(rooms, 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 return nil
} }

View File

@ -35,6 +35,15 @@ type Message interface {
Method() uint16 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)
return message.Encode(encoder)
}
// canAssign determines if data from the given source tag can be assigned to // 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 // a Go type represented by destination. It is designed to receive destination
// values from [generate.Generator.generateCanAssign]. The eventual Go type and // values from [generate.Generator.generateCanAssign]. The eventual Go type and
@ -121,6 +130,10 @@ func (this *Generator) Generate(protocol *Protocol) (n int, err error) {
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
} }
// receive
nn, err = this.generateReceive()
n += nn; if err != nil { return n, err }
return n, nil return n, nil
} }
@ -1184,6 +1197,51 @@ func (this *Generator) generateCanAssign(typ Type, tagSource string) (n int, err
return n, nil 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 %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 }
nn, err = this.iprintf("return nil, n, hopp.ErrUnknownMethod\n")
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 { func (this *Generator) validateIntBitSize(size int) error {
switch size { switch size {
case 5, 8, 16, 32, 64: return nil case 5, 8, 16, 32, 64: return nil