From 931465c83e41e66d48493758da106c6db48799e4 Mon Sep 17 00:00:00 2001 From: cblgh Date: Thu, 25 Jun 2026 15:13:36 +0200 Subject: [PATCH 1/2] examples: add timeserve to demonstrate writing to multiple live peers --- examples/timeserve/README.md | 43 ++++++++++ examples/timeserve/timeserve.go | 140 ++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 examples/timeserve/README.md create mode 100644 examples/timeserve/timeserve.go 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..f57c975 --- /dev/null +++ b/examples/timeserve/timeserve.go @@ -0,0 +1,140 @@ +package main + +import ( + "flag" + "fmt" + "time" + "strings" + "os" + "os/signal" + "syscall" + + 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() +} -- 2.52.0 From 186f3ac9aad025d972ded62d0fce5f1d8b83e55a Mon Sep 17 00:00:00 2001 From: cblgh Date: Thu, 25 Jun 2026 15:18:55 +0200 Subject: [PATCH 2/2] fmt --- examples/timeserve/timeserve.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/timeserve/timeserve.go b/examples/timeserve/timeserve.go index f57c975..fd0b252 100644 --- a/examples/timeserve/timeserve.go +++ b/examples/timeserve/timeserve.go @@ -3,11 +3,11 @@ package main import ( "flag" "fmt" - "time" - "strings" "os" "os/signal" + "strings" "syscall" + "time" iroh "git.coopcloud.tech/decentral1se/iroh-go" ) @@ -45,7 +45,7 @@ func receiver(e *iroh.Endpoint, id string) { conn, err := e.Connect(addr, alpn) check(err) - // from docs: + // 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 @@ -86,7 +86,6 @@ func checkForTimeout(err error) bool { return false } - func listen(e *iroh.Endpoint) { for { incoming := *e.AcceptNext() @@ -95,7 +94,7 @@ func listen(e *iroh.Endpoint) { } } -func handleIncoming (incoming *iroh.Incoming) { +func handleIncoming(incoming *iroh.Incoming) { accepting, err := incoming.Accept() check(err) -- 2.52.0