examples/ping: Add a ping example

This commit is contained in:
Sasha Koshka 2025-10-30 00:02:13 -04:00
parent a00e9d3183
commit bb14ec20a7
4 changed files with 298 additions and 0 deletions

View File

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

143
examples/ping/protocol.go Normal file
View 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)
}

View 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

View 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)
}
}