iroh.Pipe #32

Merged
decentral1se merged 3 commits from reader-writer into main 2026-11-09 14:28:00 +00:00
4 changed files with 89 additions and 147 deletions
+6
View File
@@ -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
+1 -35
View File
@@ -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
@@ -123,10 +95,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).
+15 -112
View File
@@ -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()
+67
View File
@@ -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
}