diff --git a/examples/timeserve/README.md b/examples/timeserve/README.md new file mode 100644 index 0000000..48166cf --- /dev/null +++ b/examples/timeserve/README.md @@ -0,0 +1,43 @@ +# timeserve + +This example demonstrates that an endpoint can support multiple live connections at once. The +endpoint aka listener writes the time, once every 2 seconds, to every connected peer. + +Try connecting to a running listener with 3 (or more!) receiver peers at once. + +## Build a binary + +``` +go build --ldflags '-linkmode external -extldflags=-static -s -w' ./timeserve.go +``` + +If you run into `glibc` incompatibility, you can compile with `zig`/`musl`. + +``` +CC="zig cc -target x86_64-linux-musl -lunwind" \ +CGO_ENABLED=1 \ +CGO_LDFLAGS="-static" \ +GOOS=linux GOARCH=amd64 \ +go build -v -a -ldflags '-extldflags "-static"' ./timeserve.go +``` + +## Terminal 1 (listener) + +``` +./timeserve +``` + +## Terminal 2 (receiver) + +``` +./timeserve -endpoint +``` + +## Terminal 3 (receiver) + +``` +./timeserve -endpoint +``` + +You'll see a connection notice if a receiver peer manages to connect to the listener. +Receivers (Terminal 2 and Terminal 3) will display the time sent by the listener (Terminal 1). diff --git a/examples/timeserve/timeserve.go b/examples/timeserve/timeserve.go new file mode 100644 index 0000000..fd0b252 --- /dev/null +++ b/examples/timeserve/timeserve.go @@ -0,0 +1,139 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "strings" + "syscall" + "time" + + iroh "git.coopcloud.tech/decentral1se/iroh-go" +) + +var ( + alpn = []byte("iroh-go-timeserve/0") + frameSize uint32 = 256 + endpointPeer *string +) + +func check(err error) { + if err != nil { + irohErr := err.(*iroh.IrohError) + panic(irohErr.Message()) + } +} + +func awaitInterrupt() { + done := make(chan os.Signal, 1) + signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) + <-done + os.Exit(0) +} + +func parseFlags() { + endpointPeer = flag.String("endpoint", "", "endpoint id of peer") + flag.Parse() +} + +func receiver(e *iroh.Endpoint, id string) { + remote, err := iroh.EndpointIdFromString(id) + check(err) + + addr := iroh.NewEndpointAddr(remote, nil, nil) + conn, err := e.Connect(addr, alpn) + check(err) + + // from docs: + // "The peer that calls open_bi must write to its SendStream before the peer Connection is able to accept the stream + // using accept_bi()" + // https://docs.rs/iroh/latest/iroh/endpoint/struct.Connection.html#method.accept_bi + stream, err := conn.OpenBi() + check(err) + // NOTE: for the other peer to realize the bidi stream is open, you have to send some data! the data sent cannot be + // the empty string ""; empty string == no data + err = stream.Send().WriteAll([]byte{1}) + + check(err) + handleReading(stream.Recv()) +} + +func handleReading(recv *iroh.RecvStream) { + for { + frame, err := recv.Read(frameSize) + if checkForTimeout(err) { + break + } + fmt.Println(string(frame)) + check(err) + } +} + +func checkForTimeout(err error) bool { + // handle timeout + if err != nil { + irohErr := err.(*iroh.IrohError) + errMsg := irohErr.Message() + if strings.HasPrefix(errMsg, "ConnectionLost") { + fmt.Println("lost connection") + return true + } else { + // panics and exits bc unhandled err + check(err) + } + } + return false +} + +func listen(e *iroh.Endpoint) { + for { + incoming := *e.AcceptNext() + fmt.Println("new connection") + go handleIncoming(incoming) + } +} + +func handleIncoming(incoming *iroh.Incoming) { + accepting, err := incoming.Accept() + check(err) + + conn, err := accepting.Connect() + check(err) + + stream, err := conn.AcceptBi() + check(err) + + for { + err := stream.Send().WriteAll([]byte(time.Now().Format(time.DateTime))) + if checkForTimeout(err) { + break + } + // prints every two seconds (not for any technical reason, 1/sec just feels hectic!) + <-time.After(2 * time.Second) + } +} + +func main() { + parseFlags() + + preset := iroh.PresetN0() + opts := iroh.EndpointOptions{ + Preset: &preset, + Alpns: &[][]byte{alpn}, + } + + endpoint, err := iroh.EndpointBind(opts) + check(err) + endpoint.Online() + + if *endpointPeer == "" { + fmt.Println("this endpoint is online and listening! use this id to connect via a peer:") + fmt.Printf("timeserve\n -endpoint %s\n", endpoint.Id()) + go func() { listen(endpoint) }() + } else { + go func() { receiver(endpoint, *endpointPeer) }() + } + + awaitInterrupt() +}