diff --git a/examples/chat/client/main.go b/examples/chat/client/main.go index 8384076..67b910b 100644 --- a/examples/chat/client/main.go +++ b/examples/chat/client/main.go @@ -17,8 +17,8 @@ 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) @@ -31,15 +31,11 @@ func main() { } }() for { - message, err := chat.Receive(trans) + message, _, err := chat.Receive(trans) handleErr(1, err) 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: @@ -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) defer done() dialer := hopp.Dialer { @@ -57,41 +53,24 @@ func join(address string, room string, nickname hopp.Option[string]) (hopp.Trans InsecureSkipVerify: true, }, } - conn, err := dialer.Dial(ctx, "quic", address) - if err != nil { return nil, err } - - err = updateProfile(conn, nickname) + conn, err := dialer.Dial(ctx, "tcp", address) 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) { diff --git a/examples/chat/doc.go b/examples/chat/doc.go index 534e08b..9fff5a7 100644 --- a/examples/chat/doc.go +++ b/examples/chat/doc.go @@ -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 diff --git a/examples/chat/error.go b/examples/chat/error.go index 48c5da0..4c4b538 100644 --- a/examples/chat/error.go +++ b/examples/chat/error.go @@ -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) } diff --git a/examples/chat/protocol.go b/examples/chat/protocol.go index 2f1cc1c..0d7d344 100644 --- a/examples/chat/protocol.go +++ b/examples/chat/protocol.go @@ -20,6 +20,15 @@ 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) + return message.Encode(encoder) +} + // 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 @@ -225,7 +234,7 @@ func(this *MessageJoin) Decode(decoder *tape.Decoder) (n int, err error) { // must be sent within a room transaction. type MessageChat struct { Content string - Nickname hopp.Option[string] + Nickname string } // 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) 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) n += nn; if err != nil { return n, err } - tag_9 := tape.StringTag(string((*this).Content)) - nn, err = encoder.WriteUint8(uint8(tag_9)) + tag_10 := tape.StringTag(string((*this).Content)) + nn, err = encoder.WriteUint8(uint8(tag_10)) 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()) + if tag_10.Is(tape.LBA) { + nn, err = encoder.WriteUintN(uint64(len((*this).Content)), tag_10.CN()) n += nn; if err != nil { return n, err } } nn, err = encoder.Write([]byte((*this).Content)) 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 } @@ -573,22 +580,20 @@ func decodeBranch_5c1cf9347bb6d9f41cee64b186392d24_MessageChat(this *MessageChat tape.Skim(decoder, fieldTag_31) continue } - var destination_33 string - var length_34 uint64 + var length_33 uint64 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 } } 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 } - buffer := make([]byte, length_34) + buffer := make([]byte, length_33) nn, err = decoder.Read(buffer) n += nn; if err != nil { return n, err } - *(&destination_33) = string(buffer) - this.Nickname = hopp.O(destination_33) + *(&(this.Nickname)) = string(buffer) default: tape.Skim(decoder, fieldTag_31) 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) { var nn int - var length_35 uint64 - if length_35 > uint64(tape.MaxStructureLength) { + var length_34 uint64 + if length_34 > uint64(tape.MaxStructureLength) { 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 } - for _ = range length_35 { - var fieldKey_36 uint16 - fieldKey_36, nn, err = decoder.ReadUint16() + for _ = range length_34 { + var fieldKey_35 uint16 + fieldKey_35, nn, err = decoder.ReadUint16() n += nn; if err != nil { return n, err } - var fieldTag_37 tape.Tag - fieldTag_37, nn, err = decoder.ReadTag() + var fieldTag_36 tape.Tag + fieldTag_36, nn, err = decoder.ReadTag() n += nn; if err != nil { return n, err } - switch fieldKey_36 { + switch fieldKey_35 { case 0x0000: - if !(canAssign(tape.LBA, fieldTag_37)) { - tape.Skim(decoder, fieldTag_37) + if !(canAssign(tape.LBA, fieldTag_36)) { + tape.Skim(decoder, fieldTag_36) continue } - var length_38 uint64 - if fieldTag_37.Is(tape.LBA) { - length_38, nn, err = decoder.ReadUintN(int(fieldTag_37.CN())) + 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_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 } - buffer := make([]byte, length_38) + 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_37) + tape.Skim(decoder, fieldTag_36) 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) { var nn int - var length_39 uint64 - if length_39 > uint64(tape.MaxStructureLength) { + var length_38 uint64 + if length_38 > uint64(tape.MaxStructureLength) { 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 } - for _ = range length_39 { - var fieldKey_40 uint16 - fieldKey_40, nn, err = decoder.ReadUint16() + for _ = range length_38 { + var fieldKey_39 uint16 + fieldKey_39, nn, err = decoder.ReadUint16() n += nn; if err != nil { return n, err } - var fieldTag_41 tape.Tag - fieldTag_41, nn, err = decoder.ReadTag() + var fieldTag_40 tape.Tag + fieldTag_40, nn, err = decoder.ReadTag() n += nn; if err != nil { return n, err } - switch fieldKey_40 { + switch fieldKey_39 { case 0x0000: - if !(canAssign(tape.LBA, fieldTag_41)) { - tape.Skim(decoder, fieldTag_41) + if !(canAssign(tape.LBA, fieldTag_40)) { + tape.Skim(decoder, fieldTag_40) continue } - var length_42 uint64 - if fieldTag_41.Is(tape.LBA) { - length_42, nn, err = decoder.ReadUintN(int(fieldTag_41.CN())) + 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_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 } - buffer := make([]byte, length_42) + 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_41) + 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 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 +} diff --git a/examples/chat/protocol.md b/examples/chat/protocol.md deleted file mode 100644 index fd8f936..0000000 --- a/examples/chat/protocol.md +++ /dev/null @@ -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. diff --git a/examples/chat/protocol.pdl b/examples/chat/protocol.pdl index 24fc298..547f688 100644 --- a/examples/chat/protocol.pdl +++ b/examples/chat/protocol.pdl @@ -32,7 +32,7 @@ M0200 Join { // must be sent within a room transaction. M0300 Chat { 0000 Content String, - 0001 Nickname ?String, + 0001 Nickname String, } // JoinNotify is sent by the server when another client joins the room. It must be diff --git a/examples/chat/server/main.go b/examples/chat/server/main.go index 37fc405..e0d2e02 100644 --- a/examples/chat/server/main.go +++ b/examples/chat/server/main.go @@ -28,7 +28,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.ListenTLS("tcp", address, &tls.Config { InsecureSkipVerify: true, Certificates: []tls.Certificate { keyPair }, }) @@ -63,6 +63,7 @@ func (this *client) run() { log.Println("accepted transaction") if err != nil { log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err) + continue } go this.runTrans(trans) } @@ -70,7 +71,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 +98,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 +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 { - 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() defer done() for client := range clients { @@ -126,7 +127,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 +142,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 +153,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 }