From b9005326750d87e3138d9b019c2077082e9c6a04 Mon Sep 17 00:00:00 2001 From: decentral1se Date: Mon, 9 Nov 2026 15:23:29 +0100 Subject: [PATCH 1/3] docs: LLM policy to CONTRIBUTING.md --- CONTRIBUTING.md | 6 ++++++ README.md | 8 +------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b28717..e9f4d04 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,12 @@ Please read the [README](./README.md) before diving in. Please [raise an issue before starting on large changes](https://git.coopcloud.tech/decentral1se/iroh-go/issues/new). +## LLM Policy + +We adopt the [Strict No LLM / No AI +Policy](https://ziglang.org/code-of-conduct/#strict-no-llm-no-ai-policy) of +[Zig](https://ziglang.org). + ## Conventions * API wrappers (such as `endpoint.go`) should return upstream errors even diff --git a/README.md b/README.md index 2b3f7d3..35ab954 100644 --- a/README.md +++ b/README.md @@ -123,10 +123,4 @@ Apache 2.0 & MIT, following the upstream licensing choices in ## Contributing -[`CONTRIBUTING.md`](./CONTRIBUTING.md). - -## LLM Policy - -We adopt the [Strict No LLM / No AI -Policy](https://ziglang.org/code-of-conduct/#strict-no-llm-no-ai-policy) of -[Zig](https://ziglang.org). +> [`CONTRIBUTING.md`](./CONTRIBUTING.md). -- 2.54.0 From 1c141203ca9783c2e52cb0b45323136c1d64e67c Mon Sep 17 00:00:00 2001 From: decentral1se Date: Mon, 9 Nov 2026 15:23:39 +0100 Subject: [PATCH 2/3] docs: bring example when jasmine-teehaus lands --- README.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/README.md b/README.md index 35ab954..6ba8739 100644 --- a/README.md +++ b/README.md @@ -56,34 +56,6 @@ See [`examples`](./examples). See also the [upstream examples](https://github.com/n0-computer/iroh/tree/main/iroh/examples) (in Rust) for inspiration. -## Run your own `iroh-relay` - -Each [`iroh` release](https://github.com/n0-computer/iroh/releases) features a -relay binary. You can also make use of the upstream provided docker image: -`n0computer/iroh-relay`. - -### Local hacking - -First, run `iroh-relay` in development mode with noisy logging. - -``` -RUST_LOG=info,iroh_relay=debug ./iroh-relay --dev -``` - -You can then use `http://localhost:3340` as the default location of your own -relay in the construction of your endpoint as follows. Once you create an -endpoint, you should see your relay respond locally with corresponding logs. - -```go -preset := iroh.PresetN0DisableRelay() -relay, _ := iroh.RelayModeCustomFromUrls([]string{"http://localhost:3340"}) -opts := iroh.EndpointOptions{ - Preset: &preset, - RelayMode: &relay, - // ...moar config... -} -``` - ## Generate bindings See [`iroh-go`](./iroh-go) for the Rust toolchain configuration. Run -- 2.54.0 From b5ee706b5b0840d4739c433bb0629a1825848d4e Mon Sep 17 00:00:00 2001 From: decentral1se Date: Mon, 9 Nov 2026 15:24:16 +0100 Subject: [PATCH 3/3] feat: pipe lands as read/writer for bistream --- examples/simple-pipe/simple-pipe.go | 127 ++++------------------------ pipe.go | 67 +++++++++++++++ 2 files changed, 82 insertions(+), 112 deletions(-) create mode 100644 pipe.go diff --git a/examples/simple-pipe/simple-pipe.go b/examples/simple-pipe/simple-pipe.go index 145aa8d..6144b13 100644 --- a/examples/simple-pipe/simple-pipe.go +++ b/examples/simple-pipe/simple-pipe.go @@ -22,106 +22,6 @@ var ( secretFlag *string ) -type SimplePipe struct { - recv *iroh.RecvStream - send *iroh.SendStream - totalBytesRead int -} - -func (sp *SimplePipe) setRecv(recv *iroh.RecvStream) { - sp.recv = recv -} - -func (sp *SimplePipe) setSend(send *iroh.SendStream) { - sp.send = send -} - -// (NOTE): cblgh: inlining go's docs for io.Reader and io.Writer in case of any -// other future bugs with my implementations of the interface contract -// -// Read reads up to len(buf) bytes into buf. It returns the number of bytes read (0 <= -// n <= len(buf)) and any error encountered. -// -// Even if Read returns n < len(buf), it may use all of buf as scratch space during the -// call. If some data is available but not len(buf) bytes, Read conventionally returns -// what is available instead of waiting for more. -// -// When Read encounters an error or end-of-file condition after successfully -// reading n > 0 bytes, it returns the number of bytes read. It may return the -// (non-nil) error from the same call or return the error (and n == 0) from a -// subsequent call . An instance of this general case is that a Reader returning a -// non-zero number of bytes at the end of the input stream may return either -// err == EOF or err == nil. The next Read should return 0, EOF. -// -// Callers should always process the n > 0 bytes returned before considering the -// error err. Doing so correctly handles I/O errors that happen after reading some -// bytes and also both of the allowed EOF behaviors. -// -// If len(buf) == 0, Read should always return n == 0. It may return a non-nil error -// if some error condition is known, such as EOF. -// -// Implementations of Read are discouraged from returning a zero byte count with a -// nil error, except when len(buf) == 0. Callers should treat a return of 0 and nil a -// s indicating that nothing happened; in particular it does not indicate EOF. -// -// Implementations must not retain buf. -func (sp *SimplePipe) Read(buf []byte) (int, error) { - // fmt.Println("READ size of buf", len(buf)) - if len(buf) == 0 { - return 0, nil - } - - retbuf, err := sp.recv.Read(uint32(len(buf))) - - // fmt.Println("retbuf", retbuf, "len(retbuf)", len(retbuf), "err", err) - - // signal end of file - if sp.totalBytesRead > 0 && len(retbuf) == 0 { - return 0, io.EOF - } - - sp.totalBytesRead += len(retbuf) - - copyBytes := func() { - for i, d := range retbuf { - buf[i] = d - } - } - - if err != nil { - copyBytes() - return len(retbuf), err - } - copyBytes() - return len(retbuf), err -} - -// Write writes len(buf) bytes from buf to the underlying data stream. It -// returns the number of bytes written from buf (0 <= n <= len(buf)) and any -// error encountered that caused the write to stop early. Write must return a -// non-nil error if it returns n < len(buf). Write must not modify the slice -// data, even temporarily. -// -// Implementations must not retain buf. -func (sp *SimplePipe) Write(buf []byte) (int, error) { - // fmt.Println("WRITE size of buf", len(buf)) - n64, err := sp.send.Write(buf) - n := int(n64) - - if n < len(buf) { - return n, errors.New("SimplePipe.Write exited before writing all bytes") - } - - return n, err -} - -// use as: -// var sp SimplePipe -// sp.setRecv(stream.Recv()) or sp.setSend(stream.Send()) -// -// io.Copy(*sp, os.Stdin) -// io.Copy(os.Stdout, *sp) - var done = make(chan os.Signal, 1) func awaitInterrupt() { @@ -143,7 +43,7 @@ func log(s string) { } } -func send(e *iroh.Endpoint, id string, sp *SimplePipe) { +func send(e *iroh.Endpoint, id string) { remote, err := iroh.EndpointIdFromString(id) if err := iroh.As(err); err != nil { panic(err) @@ -162,17 +62,20 @@ func send(e *iroh.Endpoint, id string, sp *SimplePipe) { log("connection established") - sp.setSend(stream.Send()) + p := iroh.NewPipe(stream) // write initial data to open the connection - sp.Write([]byte{1}) + if err := p.Ready(); err != nil { + panic(iroh.As(err)) + } // copy stdin directly to opened connection's send stream - n, err := io.Copy(sp, os.Stdin) + n, err := io.Copy(p, os.Stdin) log(fmt.Sprintf("wrote %d", n)) if err = iroh.As(err); err != nil { panic(err) } + // mark send stream as done stream.Send().Finish() exit(conn) @@ -186,12 +89,12 @@ func exit(conn *iroh.Connection) { done <- os.Interrupt } -func listen(e *iroh.Endpoint, sp *SimplePipe) { +func listen(e *iroh.Endpoint) { incoming := *e.AcceptNext() - handleIncoming(incoming, sp) + handleIncoming(incoming) } -func handleIncoming(incoming *iroh.Incoming, sp *SimplePipe) { +func handleIncoming(incoming *iroh.Incoming) { accepting, err := incoming.Accept() if err := iroh.As(err); err != nil { panic(err) @@ -207,8 +110,9 @@ func handleIncoming(incoming *iroh.Incoming, sp *SimplePipe) { panic(err) } - sp.setRecv(stream.Recv()) - n, err := io.Copy(os.Stdout, sp) + p := iroh.NewPipe(stream) + + n, err := io.Copy(os.Stdout, p) log(fmt.Sprintf("read n %d", n)) if errors.Is(err, iroh.TimeoutError) || errors.Is(err, iroh.StreamError) { @@ -220,7 +124,6 @@ func handleIncoming(incoming *iroh.Incoming, sp *SimplePipe) { } func main() { - var sp SimplePipe parseFlags() preset := iroh.PresetN0() @@ -246,9 +149,9 @@ func main() { if *endpointPeer == "" { log("this endpoint is online and listening! use this id to connect via a peer:") os.Stderr.Write([]byte(fmt.Sprintf("simple-pipe -endpoint %s\n", endpoint.Id()))) - go func() { listen(endpoint, &sp) }() + go func() { listen(endpoint) }() } else { - go func() { send(endpoint, *endpointPeer, &sp) }() + go func() { send(endpoint, *endpointPeer) }() } awaitInterrupt() diff --git a/pipe.go b/pipe.go new file mode 100644 index 0000000..ca6da2f --- /dev/null +++ b/pipe.go @@ -0,0 +1,67 @@ +package iroh_ffi + +import ( + "errors" + "io" +) + +type Pipe struct { + stream *BiStream + recv *RecvStream + send *SendStream + totalBytesRead int +} + +func NewPipe(stream *BiStream) *Pipe { + return &Pipe{ + stream: stream, + recv: stream.Recv(), + send: stream.Send(), + } +} + +func (p *Pipe) Ready() error { + if err := p.stream.Ready(); err != nil { + return err + } + return nil +} + +func (p *Pipe) Read(buf []byte) (int, error) { + if len(buf) == 0 { + return 0, nil + } + + retbuf, err := p.recv.Read(uint32(len(buf))) + + if p.totalBytesRead > 0 && len(retbuf) == 0 { + return 0, io.EOF + } + + p.totalBytesRead += len(retbuf) + + copyBytes := func() { + for i, d := range retbuf { + buf[i] = d + } + } + + if err != nil { + copyBytes() + return len(retbuf), err + } + + copyBytes() + return len(retbuf), err +} + +func (p *Pipe) Write(buf []byte) (int, error) { + n64, err := p.send.Write(buf) + n := int(n64) + + if n < len(buf) { + return n, errors.New("Pipe.Write: exited before writing all bytes") + } + + return n, err +} -- 2.54.0