starpc 0.18.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -31,7 +31,7 @@ export async function openRpcStream(componentId, caller, waitAck) {
31
31
  }
32
32
  }
33
33
  // build & return the data stream
34
- return new RpcStream(packetSink, packetIt); // packetSource)
34
+ return new RpcStream(packetSink, packetIt);
35
35
  }
36
36
  // buildRpcStreamOpenStream builds a OpenStream func with a RpcStream.
37
37
  export function buildRpcStreamOpenStream(componentId, caller) {
@@ -3,8 +3,7 @@ import { pushable } from 'it-pushable';
3
3
  import { ERR_RPC_ABORT } from './errors.js';
4
4
  import { ClientRPC } from './client-rpc.js';
5
5
  import { writeToPushable } from './pushable.js';
6
- import { decodePacketSource, encodePacketSource, parseLengthPrefixTransform, prependLengthPrefixTransform, } from './packet.js';
7
- import { combineUint8ArrayListTransform } from './array-list.js';
6
+ import { decodePacketSource, encodePacketSource, } from './packet.js';
8
7
  import { OpenStreamCtr } from './open-stream-ctr.js';
9
8
  // Client implements the ts-proto Rpc interface with the drpcproto protocol.
10
9
  export class Client {
@@ -75,12 +74,12 @@ export class Client {
75
74
  throw new Error(ERR_RPC_ABORT);
76
75
  }
77
76
  const openStreamFn = await this.openStreamCtr.wait();
78
- const conn = await openStreamFn();
77
+ const stream = await openStreamFn();
79
78
  const call = new ClientRPC(rpcService, rpcMethod);
80
79
  abortSignal?.addEventListener('abort', () => {
81
80
  call.close(new Error(ERR_RPC_ABORT));
82
81
  });
83
- pipe(conn, parseLengthPrefixTransform(), combineUint8ArrayListTransform(), decodePacketSource, call, encodePacketSource, prependLengthPrefixTransform(), conn);
82
+ pipe(stream, decodePacketSource, call, encodePacketSource, stream);
84
83
  await call.writeCallStart(data || undefined);
85
84
  return call;
86
85
  }
@@ -77,7 +77,7 @@ export class CommonRPC {
77
77
  asError = new Error('error handling packet');
78
78
  }
79
79
  this.close(asError);
80
- throw asError;
80
+ // throw asError
81
81
  }
82
82
  }
83
83
  // handleCallStart handles a CallStart packet.
@@ -9,7 +9,7 @@ export interface ConnParams {
9
9
  direction?: Direction;
10
10
  }
11
11
  export interface StreamHandler {
12
- handleStream(strm: SRPCStream): void;
12
+ handlePacketStream(strm: SRPCStream): void;
13
13
  }
14
14
  export declare function streamToSRPCStream(stream: Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array>): SRPCStream;
15
15
  export declare class Conn implements Duplex<Uint8Array> {
package/dist/srpc/conn.js CHANGED
@@ -1,13 +1,19 @@
1
1
  import { pipe } from 'it-pipe';
2
2
  import { yamux } from '@chainsafe/libp2p-yamux';
3
3
  import isPromise from 'is-promise';
4
+ import { pushable } from 'it-pushable';
4
5
  import { Client } from './client.js';
5
6
  import { combineUint8ArrayListTransform } from './array-list.js';
7
+ import { parseLengthPrefixTransform, prependLengthPrefixTransform } from './packet.js';
8
+ import { buildPushableSink } from './pushable.js';
6
9
  // streamToSRPCStream converts a Stream to a SRPCStream.
10
+ // uses length-prefix for packet framing
7
11
  export function streamToSRPCStream(stream) {
12
+ const pushSink = pushable({ objectMode: true });
13
+ pipe(pushSink, prependLengthPrefixTransform(), stream.sink);
8
14
  return {
9
- source: pipe(stream, combineUint8ArrayListTransform()),
10
- sink: stream.sink,
15
+ source: pipe(stream, parseLengthPrefixTransform(), combineUint8ArrayListTransform()),
16
+ sink: buildPushableSink(pushSink),
11
17
  };
12
18
  }
13
19
  // Conn implements a generic connection with a two-way stream.
@@ -46,9 +52,13 @@ export class Conn {
46
52
  }
47
53
  // openStream implements the client open stream function.
48
54
  async openStream() {
49
- const stream = this.muxer.newStream();
50
- if (isPromise(stream)) {
51
- return streamToSRPCStream(await stream);
55
+ const streamPromise = this.muxer.newStream();
56
+ let stream;
57
+ if (isPromise(streamPromise)) {
58
+ stream = await streamPromise;
59
+ }
60
+ else {
61
+ stream = streamPromise;
52
62
  }
53
63
  return streamToSRPCStream(stream);
54
64
  }
@@ -62,6 +72,6 @@ export class Conn {
62
72
  if (!server) {
63
73
  return strm.abort(new Error('server not implemented'));
64
74
  }
65
- server.handleStream(streamToSRPCStream(strm));
75
+ server.handlePacketStream(streamToSRPCStream(strm));
66
76
  }
67
77
  }
@@ -1,2 +1,3 @@
1
1
  export declare const ERR_RPC_ABORT = "ERR_RPC_ABORT";
2
2
  export declare function isAbortError(err: unknown): boolean;
3
+ export declare function castToError(err: any, defaultMsg?: string): Error;
@@ -8,3 +8,26 @@ export function isAbortError(err) {
8
8
  const message = err.message;
9
9
  return message === ERR_RPC_ABORT;
10
10
  }
11
+ // castToError casts an object to an Error.
12
+ // if err is a string, uses it as the message.
13
+ // if err is undefined, returns new Error(defaultMsg)
14
+ export function castToError(err, defaultMsg) {
15
+ defaultMsg = defaultMsg || 'error';
16
+ if (!err) {
17
+ return new Error(defaultMsg);
18
+ }
19
+ if (typeof err === 'string') {
20
+ return new Error(err);
21
+ }
22
+ const asError = err;
23
+ if (asError.message) {
24
+ return asError;
25
+ }
26
+ if (err.toString) {
27
+ const errString = err.toString();
28
+ if (errString) {
29
+ return new Error(errString);
30
+ }
31
+ }
32
+ return new Error(defaultMsg);
33
+ }
@@ -1,5 +1,5 @@
1
1
  export type { PacketHandler, Stream, OpenStreamFunc } from './stream.js';
2
- export { ERR_RPC_ABORT, isAbortError } from './errors.js';
2
+ export { ERR_RPC_ABORT, isAbortError, castToError } from './errors.js';
3
3
  export { Client } from './client.js';
4
4
  export { Server } from './server.js';
5
5
  export { Conn, ConnParams } from './conn.js';
@@ -9,6 +9,8 @@ export { Mux, StaticMux, createMux } from './mux.js';
9
9
  export { BroadcastChannelDuplex, newBroadcastChannelDuplex, BroadcastChannelConn, } from './broadcast-channel.js';
10
10
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
11
11
  export { MessageDefinition, DecodeMessageTransform, buildDecodeMessageTransform, EncodeMessageTransform, buildEncodeMessageTransform, memoProto, memoProtoDecode, } from './message.js';
12
+ export { parseLengthPrefixTransform, prependLengthPrefixTransform, decodePacketSource, encodePacketSource, } from './packet.js';
13
+ export { combineUint8ArrayListTransform } from './array-list.js';
12
14
  export { ValueCtr } from './value-ctr.js';
13
15
  export { OpenStreamCtr } from './open-stream-ctr.js';
14
- export { writeToPushable } from './pushable';
16
+ export { writeToPushable, buildPushableSink } from './pushable.js';
@@ -1,4 +1,4 @@
1
- export { ERR_RPC_ABORT, isAbortError } from './errors.js';
1
+ export { ERR_RPC_ABORT, isAbortError, castToError } from './errors.js';
2
2
  export { Client } from './client.js';
3
3
  export { Server } from './server.js';
4
4
  export { Conn } from './conn.js';
@@ -8,6 +8,8 @@ export { StaticMux, createMux } from './mux.js';
8
8
  export { BroadcastChannelDuplex, newBroadcastChannelDuplex, BroadcastChannelConn, } from './broadcast-channel.js';
9
9
  export { MessagePortIterable, newMessagePortIterable, MessagePortConn, } from './message-port.js';
10
10
  export { buildDecodeMessageTransform, buildEncodeMessageTransform, memoProto, memoProtoDecode, } from './message.js';
11
+ export { parseLengthPrefixTransform, prependLengthPrefixTransform, decodePacketSource, encodePacketSource, } from './packet.js';
12
+ export { combineUint8ArrayListTransform } from './array-list.js';
11
13
  export { ValueCtr } from './value-ctr.js';
12
14
  export { OpenStreamCtr } from './open-stream-ctr.js';
13
- export { writeToPushable } from './pushable';
15
+ export { writeToPushable, buildPushableSink } from './pushable.js';
@@ -1,2 +1,4 @@
1
1
  import { Pushable } from 'it-pushable';
2
+ import { Sink } from 'it-stream-types';
2
3
  export declare function writeToPushable<T>(dataSource: AsyncIterable<T>, out: Pushable<T>): Promise<void>;
4
+ export declare function buildPushableSink<T>(target: Pushable<T>): Sink<T>;
@@ -1,3 +1,4 @@
1
+ import { castToError } from './errors.js';
1
2
  // writeToPushable writes the incoming server data to the pushable.
2
3
  export async function writeToPushable(dataSource, out) {
3
4
  try {
@@ -11,3 +12,25 @@ export async function writeToPushable(dataSource, out) {
11
12
  throw err;
12
13
  }
13
14
  }
15
+ // buildPushableSink builds a Sink from a Pushable.
16
+ export function buildPushableSink(target) {
17
+ return async function pushableSink(source) {
18
+ try {
19
+ for await (const pkt of source) {
20
+ if (Array.isArray(pkt)) {
21
+ for (const p of pkt) {
22
+ target.push(p);
23
+ }
24
+ }
25
+ else {
26
+ target.push(pkt);
27
+ }
28
+ }
29
+ target.end();
30
+ }
31
+ catch (err) {
32
+ const error = castToError(err);
33
+ target.end(error);
34
+ }
35
+ };
36
+ }
@@ -10,8 +10,7 @@ export declare class Server implements StreamHandler {
10
10
  constructor(lookupMethod: LookupMethod);
11
11
  get rpcStreamHandler(): RpcStreamHandler;
12
12
  startRpc(): ServerRPC;
13
- handleStream(stream: Stream): ServerRPC;
14
- handleDuplex(stream: Duplex<Uint8Array, Uint8Array>): ServerRPC;
15
- handlePacketDuplex(stream: Duplex<Uint8Array>): ServerRPC;
16
- handlePacketStream(stream: Duplex<Packet>): ServerRPC;
13
+ handleFragmentStream(stream: Stream): ServerRPC;
14
+ handlePacketStream(stream: Stream): ServerRPC;
15
+ handlePacketDuplex(stream: Duplex<Packet>): ServerRPC;
17
16
  }
@@ -8,33 +8,32 @@ export class Server {
8
8
  this.lookupMethod = lookupMethod;
9
9
  }
10
10
  // rpcStreamHandler implements the RpcStreamHandler interface.
11
+ // uses handlePacketDuplex (expects 1 buf = 1 Packet)
11
12
  get rpcStreamHandler() {
12
- return this.handleStream.bind(this);
13
+ return this.handlePacketStream.bind(this);
13
14
  }
14
15
  // startRpc starts a new server-side RPC.
15
16
  // the returned RPC handles incoming Packets.
16
17
  startRpc() {
17
18
  return new ServerRPC(this.lookupMethod);
18
19
  }
19
- // handleStream handles an incoming Uint8Array message duplex.
20
- handleStream(stream) {
21
- return this.handleDuplex(stream);
22
- }
23
- // handleDuplex handles an incoming message duplex.
24
- handleDuplex(stream) {
20
+ // handleFragmentStream handles an incoming stream.
21
+ // assumes that stream does not maintain packet framing.
22
+ // uses length-prefixed packets for packet framing.
23
+ handleFragmentStream(stream) {
25
24
  const rpc = this.startRpc();
26
25
  pipe(stream, parseLengthPrefixTransform(), combineUint8ArrayListTransform(), decodePacketSource, rpc, encodePacketSource, prependLengthPrefixTransform(), combineUint8ArrayListTransform(), stream);
27
26
  return rpc;
28
27
  }
29
- // handlePacketDuplex handles an incoming Uint8Array duplex.
30
- // skips the packet length prefix transform.
31
- handlePacketDuplex(stream) {
28
+ // handlePacketStream handles an incoming Uint8Array duplex.
29
+ // the stream has one Uint8Array per packet w/o length prefix.
30
+ handlePacketStream(stream) {
32
31
  const rpc = this.startRpc();
33
32
  pipe(stream, decodePacketSource, rpc, encodePacketSource, stream);
34
33
  return rpc;
35
34
  }
36
- // handlePacketStream handles an incoming Packet duplex.
37
- handlePacketStream(stream) {
35
+ // handlePacketDuplex handles an incoming Packet duplex.
36
+ handlePacketDuplex(stream) {
38
37
  const rpc = this.startRpc();
39
38
  pipe(stream, rpc, stream);
40
39
  return rpc;
package/go.mod CHANGED
@@ -9,8 +9,8 @@ require (
9
9
  )
10
10
 
11
11
  require (
12
- github.com/aperturerobotics/util v1.0.6-0.20230313071030-6260e05b62e5 // latest
13
- github.com/libp2p/go-libp2p v0.26.3 // latest
12
+ github.com/aperturerobotics/util v1.0.6-0.20230323123147-d1b8fef6a782 // latest
13
+ github.com/libp2p/go-libp2p v0.26.1-0.20230320233358-9e208249b588 // latest
14
14
  github.com/libp2p/go-yamux/v4 v4.0.1-0.20220919134236-1c09f2ab3ec1 // master
15
15
  github.com/sirupsen/logrus v1.9.0 // latest
16
16
  )
package/go.sum CHANGED
@@ -1,5 +1,5 @@
1
- github.com/aperturerobotics/util v1.0.6-0.20230313071030-6260e05b62e5 h1:SVQsecow4oFN4ra6SURikAJROdvSYY2lI0vujic5veU=
2
- github.com/aperturerobotics/util v1.0.6-0.20230313071030-6260e05b62e5/go.mod h1:45zfYCir2T5/KE+O9CyYdZPrsXZtOEAuA4UU79rRMN4=
1
+ github.com/aperturerobotics/util v1.0.6-0.20230323123147-d1b8fef6a782 h1:kKuKxMVd1N+aT9+hQQaIc5n2AkD6smFTSw6/Jl4N8NA=
2
+ github.com/aperturerobotics/util v1.0.6-0.20230323123147-d1b8fef6a782/go.mod h1:VB5a5FURpm7EujJsErIY8UZrhrLPN/bj84H8FxN0gD4=
3
3
  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4
4
  github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5
5
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -48,8 +48,8 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
48
48
  github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
49
49
  github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
50
50
  github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
51
- github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM=
52
- github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8=
51
+ github.com/libp2p/go-libp2p v0.26.1-0.20230320233358-9e208249b588 h1:hY4aHvA59tTkxKZxSyHWnhbQ+/ULgYwaNW1GLqJy2YU=
52
+ github.com/libp2p/go-libp2p v0.26.1-0.20230320233358-9e208249b588/go.mod h1:HKQUKIQ5NhzabNMWq9Wczcs5Ksdx4LQ/ISAq4MRqBHQ=
53
53
  github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
54
54
  github.com/libp2p/go-yamux/v4 v4.0.1-0.20220919134236-1c09f2ab3ec1 h1:fxyHejZvFIBqlznQDuHUkw9BThYKyoT3DclP/C2g8Wc=
55
55
  github.com/libp2p/go-yamux/v4 v4.0.1-0.20220919134236-1c09f2ab3ec1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.18.3",
3
+ "version": "0.19.0",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -60,18 +60,18 @@
60
60
  "singleQuote": true
61
61
  },
62
62
  "devDependencies": {
63
- "@aperturerobotics/ts-common": "^0.3.4",
64
- "@typescript-eslint/eslint-plugin": "^5.56.0",
65
- "@typescript-eslint/parser": "^5.56.0",
63
+ "@aperturerobotics/ts-common": "^0.4.2",
64
+ "@typescript-eslint/eslint-plugin": "^5.57.0",
65
+ "@typescript-eslint/parser": "^5.57.0",
66
66
  "bufferutil": "^4.0.7",
67
67
  "depcheck": "^1.4.3",
68
- "esbuild": "^0.17.12",
69
- "eslint": "^8.36.0",
68
+ "esbuild": "^0.17.15",
69
+ "eslint": "^8.37.0",
70
70
  "eslint-config-prettier": "^8.8.0",
71
- "prettier": "^2.8.5",
71
+ "prettier": "^2.8.7",
72
72
  "rimraf": "^4.4.0",
73
- "ts-proto": "^1.143.0",
74
- "typescript": "^5.0.2",
73
+ "ts-proto": "^1.146.0",
74
+ "typescript": "^5.0.3",
75
75
  "utf-8-validate": "^6.0.3"
76
76
  },
77
77
  "dependencies": {
@@ -82,15 +82,15 @@
82
82
  "is-promise": "^4.0.0",
83
83
  "isomorphic-ws": "^5.0.0",
84
84
  "it-first": "^2.0.1",
85
- "it-length-prefixed": "^8.0.4",
86
- "it-pipe": "^2.0.5",
85
+ "it-length-prefixed": "^8.0.0",
86
+ "it-pipe": "^2.0.0",
87
87
  "it-pushable": "^3.1.2",
88
88
  "it-stream-types": "^1.0.5",
89
89
  "it-ws": "^5.0.6",
90
90
  "long": "^5.2.1",
91
91
  "memoize-one": "^6.0.0",
92
92
  "patch-package": "^6.5.1",
93
- "protobufjs": "^7.1.2",
93
+ "protobufjs": "^7.2.0",
94
94
  "uint8arraylist": "^2.4.3",
95
95
  "ws": "^8.13.0"
96
96
  }
@@ -54,5 +54,12 @@ func (i *PrefixClient) stripCheckServiceIDPrefix(service string) (string, error)
54
54
  return service, nil
55
55
  }
56
56
 
57
+ // NewRawStream opens a new raw stream with the remote.
58
+ // Implements OpenStreamFunc.
59
+ // msgHandler must not be called concurrently.
60
+ func (i *PrefixClient) NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (Writer, error) {
61
+ return i.client.NewRawStream(ctx, msgHandler, closeHandler)
62
+ }
63
+
57
64
  // _ is a type assertion
58
65
  var _ Client = ((*PrefixClient)(nil))
@@ -2,8 +2,6 @@ package srpc
2
2
 
3
3
  import (
4
4
  "context"
5
-
6
- "github.com/pkg/errors"
7
5
  )
8
6
 
9
7
  // ClientSet wraps a list of clients into one Client.
@@ -43,6 +41,23 @@ func (c *ClientSet) NewStream(
43
41
  return strm, err
44
42
  }
45
43
 
44
+ // NewRawStream opens a new raw stream with the remote.
45
+ // Implements OpenStreamFunc.
46
+ // msgHandler must not be called concurrently.
47
+ func (c *ClientSet) NewRawStream(
48
+ ctx context.Context,
49
+ msgHandler PacketDataHandler,
50
+ closeHandler CloseHandler,
51
+ ) (Writer, error) {
52
+ for _, client := range c.clients {
53
+ if client == nil {
54
+ continue
55
+ }
56
+ return client.NewRawStream(ctx, msgHandler, closeHandler)
57
+ }
58
+ return nil, ErrNoAvailableClients
59
+ }
60
+
46
61
  // execCall executes the call conditionally retrying against subsequent client handles.
47
62
  func (c *ClientSet) execCall(ctx context.Context, doCall func(client Client) error) error {
48
63
  var any bool
@@ -70,7 +85,7 @@ func (c *ClientSet) execCall(ctx context.Context, doCall func(client Client) err
70
85
  }
71
86
 
72
87
  if !any {
73
- return errors.New("no available rpc clients")
88
+ return ErrNoAvailableClients
74
89
  }
75
90
 
76
91
  return ErrUnimplemented
package/srpc/client.go CHANGED
@@ -14,13 +14,18 @@ type Client interface {
14
14
  // NewStream starts a streaming RPC with the remote & returns the stream.
15
15
  // firstMsg is optional.
16
16
  NewStream(ctx context.Context, service, method string, firstMsg Message) (Stream, error)
17
+
18
+ // NewRawStream opens a new raw stream with the remote.
19
+ // Implements OpenStreamFunc.
20
+ // msgHandler must not be called concurrently.
21
+ NewRawStream(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (Writer, error)
17
22
  }
18
23
 
19
24
  // OpenStreamFunc opens a stream with a remote.
20
25
  // msgHandler must not be called concurrently.
21
26
  type OpenStreamFunc = func(
22
27
  ctx context.Context,
23
- msgHandler PacketHandler,
28
+ msgHandler PacketDataHandler,
24
29
  closeHandler CloseHandler,
25
30
  ) (Writer, error)
26
31
 
@@ -47,7 +52,7 @@ func (c *client) ExecCall(ctx context.Context, service, method string, in, out M
47
52
  clientRPC := NewClientRPC(ctx, service, method)
48
53
  defer clientRPC.Close()
49
54
 
50
- writer, err := c.openStream(ctx, clientRPC.HandlePacket, clientRPC.HandleStreamClose)
55
+ writer, err := c.openStream(ctx, clientRPC.HandlePacketData, clientRPC.HandleStreamClose)
51
56
  if err != nil {
52
57
  return err
53
58
  }
@@ -79,7 +84,7 @@ func (c *client) NewStream(ctx context.Context, service, method string, firstMsg
79
84
  }
80
85
 
81
86
  clientRPC := NewClientRPC(ctx, service, method)
82
- writer, err := c.openStream(ctx, clientRPC.HandlePacket, clientRPC.HandleStreamClose)
87
+ writer, err := c.openStream(ctx, clientRPC.HandlePacketData, clientRPC.HandleStreamClose)
83
88
  if err != nil {
84
89
  return nil, err
85
90
  }
@@ -90,5 +95,16 @@ func (c *client) NewStream(ctx context.Context, service, method string, firstMsg
90
95
  return NewMsgStream(ctx, clientRPC, clientRPC.ctxCancel), nil
91
96
  }
92
97
 
98
+ // NewRawStream opens a new raw stream with the remote.
99
+ // Implements OpenStreamFunc.
100
+ // msgHandler must not be called concurrently.
101
+ func (c *client) NewRawStream(
102
+ ctx context.Context,
103
+ msgHandler PacketDataHandler,
104
+ closeHandler CloseHandler,
105
+ ) (Writer, error) {
106
+ return c.openStream(ctx, msgHandler, closeHandler)
107
+ }
108
+
93
109
  // _ is a type assertion
94
110
  var _ Client = ((*client)(nil))
package/srpc/client.ts CHANGED
@@ -9,10 +9,7 @@ import { writeToPushable } from './pushable.js'
9
9
  import {
10
10
  decodePacketSource,
11
11
  encodePacketSource,
12
- parseLengthPrefixTransform,
13
- prependLengthPrefixTransform,
14
12
  } from './packet.js'
15
- import { combineUint8ArrayListTransform } from './array-list.js'
16
13
  import { OpenStreamCtr } from './open-stream-ctr.js'
17
14
 
18
15
  // Client implements the ts-proto Rpc interface with the drpcproto protocol.
@@ -117,20 +114,17 @@ export class Client implements TsProtoRpc {
117
114
  throw new Error(ERR_RPC_ABORT)
118
115
  }
119
116
  const openStreamFn = await this.openStreamCtr.wait()
120
- const conn = await openStreamFn()
117
+ const stream = await openStreamFn()
121
118
  const call = new ClientRPC(rpcService, rpcMethod)
122
119
  abortSignal?.addEventListener('abort', () => {
123
120
  call.close(new Error(ERR_RPC_ABORT))
124
121
  })
125
122
  pipe(
126
- conn,
127
- parseLengthPrefixTransform(),
128
- combineUint8ArrayListTransform(),
123
+ stream,
129
124
  decodePacketSource,
130
125
  call,
131
126
  encodePacketSource,
132
- prependLengthPrefixTransform(),
133
- conn
127
+ stream
134
128
  )
135
129
  await call.writeCallStart(data || undefined)
136
130
  return call
@@ -113,7 +113,7 @@ export class CommonRPC {
113
113
  asError = new Error('error handling packet')
114
114
  }
115
115
  this.close(asError)
116
- throw asError
116
+ // throw asError
117
117
  }
118
118
  }
119
119
 
package/srpc/conn.ts CHANGED
@@ -8,10 +8,13 @@ import type { Duplex } from 'it-stream-types'
8
8
  import { yamux } from '@chainsafe/libp2p-yamux'
9
9
  import { Uint8ArrayList } from 'uint8arraylist'
10
10
  import isPromise from 'is-promise'
11
+ import { pushable, Pushable } from 'it-pushable'
11
12
 
12
13
  import type { OpenStreamFunc, Stream as SRPCStream } from './stream.js'
13
14
  import { Client } from './client.js'
14
15
  import { combineUint8ArrayListTransform } from './array-list.js'
16
+ import { parseLengthPrefixTransform, prependLengthPrefixTransform } from './packet.js'
17
+ import { buildPushableSink } from './pushable.js'
15
18
 
16
19
  // ConnParams are parameters that can be passed to the Conn constructor.
17
20
  export interface ConnParams {
@@ -25,17 +28,25 @@ export interface ConnParams {
25
28
  // StreamHandler handles incoming streams.
26
29
  // Implemented by Server.
27
30
  export interface StreamHandler {
28
- // handleStream handles an incoming stream.
29
- handleStream(strm: SRPCStream): void
31
+ // handlePacketStream handles an incoming Uint8Array duplex.
32
+ // the stream has one Uint8Array per packet w/o length prefix.
33
+ handlePacketStream(strm: SRPCStream): void
30
34
  }
31
35
 
32
36
  // streamToSRPCStream converts a Stream to a SRPCStream.
37
+ // uses length-prefix for packet framing
33
38
  export function streamToSRPCStream(
34
39
  stream: Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array>
35
40
  ): SRPCStream {
41
+ const pushSink: Pushable<Uint8Array> = pushable({ objectMode: true })
42
+ pipe(pushSink, prependLengthPrefixTransform(), stream.sink)
36
43
  return {
37
- source: pipe(stream, combineUint8ArrayListTransform()),
38
- sink: stream.sink,
44
+ source: pipe(
45
+ stream,
46
+ parseLengthPrefixTransform(),
47
+ combineUint8ArrayListTransform(),
48
+ ),
49
+ sink: buildPushableSink(pushSink),
39
50
  }
40
51
  }
41
52
 
@@ -85,9 +96,12 @@ export class Conn implements Duplex<Uint8Array> {
85
96
 
86
97
  // openStream implements the client open stream function.
87
98
  public async openStream(): Promise<SRPCStream> {
88
- const stream = this.muxer.newStream()
89
- if (isPromise(stream)) {
90
- return streamToSRPCStream(await stream)
99
+ const streamPromise = this.muxer.newStream()
100
+ let stream: Stream
101
+ if (isPromise(streamPromise)) {
102
+ stream = await streamPromise
103
+ } else {
104
+ stream = streamPromise
91
105
  }
92
106
  return streamToSRPCStream(stream)
93
107
  }
@@ -103,6 +117,6 @@ export class Conn implements Duplex<Uint8Array> {
103
117
  if (!server) {
104
118
  return strm.abort(new Error('server not implemented'))
105
119
  }
106
- server.handleStream(streamToSRPCStream(strm))
120
+ server.handlePacketStream(streamToSRPCStream(strm))
107
121
  }
108
122
  }
package/srpc/errors.go CHANGED
@@ -17,4 +17,6 @@ var (
17
17
  ErrEmptyMethodID = errors.New("method id empty")
18
18
  // ErrEmptyServiceID is returned if the service id was empty.
19
19
  ErrEmptyServiceID = errors.New("service id empty")
20
+ // ErrNoAvailableClients is returned if no clients were available.
21
+ ErrNoAvailableClients = errors.New("no available rpc clients")
20
22
  )
package/srpc/errors.ts CHANGED
@@ -9,3 +9,27 @@ export function isAbortError(err: unknown): boolean {
9
9
  const message = (err as Error).message
10
10
  return message === ERR_RPC_ABORT
11
11
  }
12
+
13
+ // castToError casts an object to an Error.
14
+ // if err is a string, uses it as the message.
15
+ // if err is undefined, returns new Error(defaultMsg)
16
+ export function castToError(err: any, defaultMsg?: string): Error {
17
+ defaultMsg = defaultMsg || 'error'
18
+ if (!err) {
19
+ return new Error(defaultMsg)
20
+ }
21
+ if (typeof err === 'string') {
22
+ return new Error(err)
23
+ }
24
+ const asError = err as Error
25
+ if (asError.message) {
26
+ return asError
27
+ }
28
+ if (err.toString) {
29
+ const errString = err.toString()
30
+ if (errString) {
31
+ return new Error(errString)
32
+ }
33
+ }
34
+ return new Error(defaultMsg)
35
+ }
package/srpc/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { PacketHandler, Stream, OpenStreamFunc } from './stream.js'
2
- export { ERR_RPC_ABORT, isAbortError } from './errors.js'
2
+ export { ERR_RPC_ABORT, isAbortError, castToError } from './errors.js'
3
3
  export { Client } from './client.js'
4
4
  export { Server } from './server.js'
5
5
  export { Conn, ConnParams } from './conn.js'
@@ -25,6 +25,13 @@ export {
25
25
  memoProto,
26
26
  memoProtoDecode,
27
27
  } from './message.js'
28
+ export {
29
+ parseLengthPrefixTransform,
30
+ prependLengthPrefixTransform,
31
+ decodePacketSource,
32
+ encodePacketSource,
33
+ } from './packet.js'
34
+ export { combineUint8ArrayListTransform } from './array-list.js'
28
35
  export { ValueCtr } from './value-ctr.js'
29
36
  export { OpenStreamCtr } from './open-stream-ctr.js'
30
- export { writeToPushable } from './pushable'
37
+ export { writeToPushable, buildPushableSink } from './pushable.js'
package/srpc/message.go CHANGED
@@ -7,6 +7,8 @@ type Message interface {
7
7
  }
8
8
 
9
9
  // RawMessage is a raw protobuf message container.
10
+ //
11
+ // The empty value is valid with copy=false.
10
12
  type RawMessage struct {
11
13
  data []byte
12
14
  copy bool
@@ -41,6 +43,16 @@ func (m *RawMessage) SetData(data []byte) {
41
43
  }
42
44
  }
43
45
 
46
+ // Clear sets the length of the data buffer to 0 without releasing it.
47
+ func (m *RawMessage) Clear() {
48
+ m.data = m.data[:0]
49
+ }
50
+
51
+ // Reset releases the data buffer.
52
+ func (m *RawMessage) Reset() {
53
+ m.data = nil
54
+ }
55
+
44
56
  func (m *RawMessage) MarshalVT() ([]byte, error) {
45
57
  if !m.copy {
46
58
  return m.data, nil
@@ -72,7 +72,7 @@ func NewClientWithMuxedConn(conn network.MuxedConn) Client {
72
72
 
73
73
  // NewOpenStreamWithMuxedConn constructs a OpenStream func with a MuxedConn.
74
74
  func NewOpenStreamWithMuxedConn(conn network.MuxedConn) OpenStreamFunc {
75
- return func(ctx context.Context, msgHandler PacketHandler, closeHandler CloseHandler) (Writer, error) {
75
+ return func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (Writer, error) {
76
76
  mstrm, err := conn.OpenStream(ctx)
77
77
  if err != nil {
78
78
  return nil, err
package/srpc/packet-rw.go CHANGED
@@ -13,9 +13,9 @@ import (
13
13
  // maxMessageSize is the max message size in bytes
14
14
  var maxMessageSize = 1e7
15
15
 
16
- // PacketReaderWriter reads and writes packets from a io.ReadWriter.
16
+ // PacketReadWriter reads and writes packets from a io.ReadWriter.
17
17
  // Uses a LittleEndian uint32 length prefix.
18
- type PacketReaderWriter struct {
18
+ type PacketReadWriter struct {
19
19
  // rw is the io.ReadWriterCloser
20
20
  rw io.ReadWriteCloser
21
21
  // buf is the buffered data
@@ -25,12 +25,19 @@ type PacketReaderWriter struct {
25
25
  }
26
26
 
27
27
  // NewPacketReadWriter constructs a new read/writer.
28
- func NewPacketReadWriter(rw io.ReadWriteCloser) *PacketReaderWriter {
29
- return &PacketReaderWriter{rw: rw}
28
+ func NewPacketReadWriter(rw io.ReadWriteCloser) *PacketReadWriter {
29
+ return &PacketReadWriter{rw: rw}
30
+ }
31
+
32
+ // Write writes raw data to the remote.
33
+ func (r *PacketReadWriter) Write(p []byte) (n int, err error) {
34
+ r.writeMtx.Lock()
35
+ defer r.writeMtx.Unlock()
36
+ return r.rw.Write(p)
30
37
  }
31
38
 
32
39
  // WritePacket writes a packet to the writer.
33
- func (r *PacketReaderWriter) WritePacket(p *Packet) error {
40
+ func (r *PacketReadWriter) WritePacket(p *Packet) error {
34
41
  r.writeMtx.Lock()
35
42
  defer r.writeMtx.Unlock()
36
43
 
@@ -55,7 +62,7 @@ func (r *PacketReaderWriter) WritePacket(p *Packet) error {
55
62
  // ReadPump executes the read pump in a goroutine.
56
63
  //
57
64
  // calls the handler when closed or returning an error
58
- func (r *PacketReaderWriter) ReadPump(cb PacketHandler, closed CloseHandler) {
65
+ func (r *PacketReadWriter) ReadPump(cb PacketDataHandler, closed CloseHandler) {
59
66
  err := r.ReadToHandler(cb)
60
67
  // signal that the stream is now closed.
61
68
  if closed != nil {
@@ -65,7 +72,7 @@ func (r *PacketReaderWriter) ReadPump(cb PacketHandler, closed CloseHandler) {
65
72
 
66
73
  // ReadToHandler reads data to the given handler.
67
74
  // Does not handle closing the stream, use ReadPump instead.
68
- func (r *PacketReaderWriter) ReadToHandler(cb PacketHandler) error {
75
+ func (r *PacketReadWriter) ReadToHandler(cb PacketDataHandler) error {
69
76
  var currLen uint32
70
77
  buf := make([]byte, 2048)
71
78
  isOpen := true
@@ -107,11 +114,7 @@ func (r *PacketReaderWriter) ReadToHandler(cb PacketHandler) error {
107
114
  if currLen != 0 && bufLen >= int(currLen)+4 {
108
115
  pkt := r.buf.Next(int(currLen + 4))[4:]
109
116
  currLen = 0
110
- npkt := &Packet{}
111
- if err := npkt.UnmarshalVT(pkt); err != nil {
112
- return err
113
- }
114
- if err := cb(npkt); err != nil {
117
+ if err := cb(pkt); err != nil {
115
118
  return err
116
119
  }
117
120
  }
@@ -122,12 +125,12 @@ func (r *PacketReaderWriter) ReadToHandler(cb PacketHandler) error {
122
125
  }
123
126
 
124
127
  // Close closes the packet rw.
125
- func (r *PacketReaderWriter) Close() error {
128
+ func (r *PacketReadWriter) Close() error {
126
129
  return r.rw.Close()
127
130
  }
128
131
 
129
132
  // readLengthPrefix reads the length prefix.
130
- func (r *PacketReaderWriter) readLengthPrefix(b []byte) uint32 {
133
+ func (r *PacketReadWriter) readLengthPrefix(b []byte) uint32 {
131
134
  if len(b) < 4 {
132
135
  return 0
133
136
  }
@@ -135,4 +138,4 @@ func (r *PacketReaderWriter) readLengthPrefix(b []byte) uint32 {
135
138
  }
136
139
 
137
140
  // _ is a type assertion
138
- var _ Writer = (*PacketReaderWriter)(nil)
141
+ var _ Writer = (*PacketReadWriter)(nil)
package/srpc/packet.go CHANGED
@@ -1,14 +1,33 @@
1
1
  package srpc
2
2
 
3
+ import "context"
4
+
3
5
  // PacketHandler handles a packet.
4
6
  //
5
7
  // pkt is optional (can be nil)
6
8
  // if closeErr is set, the stream is closed after pkt.
7
9
  type PacketHandler = func(pkt *Packet) error
8
10
 
11
+ // PacketDataHandler handles a packet before it is parsed.
12
+ type PacketDataHandler = func(data []byte) error
13
+
9
14
  // CloseHandler handles the stream closing with an optional error.
10
15
  type CloseHandler = func(closeErr error)
11
16
 
17
+ // RawStreamCtor is a function that builds a raw stream.
18
+ type RawStreamCtor func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (Writer, error)
19
+
20
+ // NewPacketDataHandler wraps a PacketHandler with a decoding step.
21
+ func NewPacketDataHandler(handler PacketHandler) PacketDataHandler {
22
+ return func(data []byte) error {
23
+ pkt := &Packet{}
24
+ if err := pkt.UnmarshalVT(data); err != nil {
25
+ return err
26
+ }
27
+ return handler(pkt)
28
+ }
29
+ }
30
+
12
31
  // Validate performs cursory validation of the packet.
13
32
  func (p *Packet) Validate() error {
14
33
  switch b := p.GetBody().(type) {
package/srpc/pushable.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { Pushable } from 'it-pushable'
2
+ import { Source, Sink } from 'it-stream-types'
3
+ import { castToError } from './errors.js'
2
4
 
3
5
  // writeToPushable writes the incoming server data to the pushable.
4
6
  export async function writeToPushable<T>(
@@ -15,3 +17,24 @@ export async function writeToPushable<T>(
15
17
  throw err
16
18
  }
17
19
  }
20
+
21
+ // buildPushableSink builds a Sink from a Pushable.
22
+ export function buildPushableSink<T>(target: Pushable<T>): Sink<T> {
23
+ return async function pushableSink(source: Source<T>): Promise<void> {
24
+ try {
25
+ for await (const pkt of source) {
26
+ if (Array.isArray(pkt)) {
27
+ for (const p of pkt) {
28
+ target.push(p)
29
+ }
30
+ } else {
31
+ target.push(pkt)
32
+ }
33
+ }
34
+ target.end()
35
+ } catch (err) {
36
+ const error = castToError(err)
37
+ target.end(error)
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,153 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "io"
6
+ "sync"
7
+
8
+ "github.com/aperturerobotics/util/broadcast"
9
+ )
10
+
11
+ // RawStreamRwc implements io.ReadWriteCloser with a raw stream.
12
+ type RawStreamRwc struct {
13
+ // writer is used to write messages to the remote.
14
+ writer Writer
15
+ // mtx guards below fields
16
+ mtx sync.Mutex
17
+ // bcast is broadcasted when data is added to readQueue
18
+ bcast broadcast.Broadcast
19
+ // readQueue holds incoming data to read
20
+ readQueue [][]byte
21
+ // closed indicates whether the stream is closed
22
+ closed bool
23
+ // closeErr stores the error, if any, when closing the stream
24
+ closeErr error
25
+ }
26
+
27
+ func NewRawStreamRwc(ctx context.Context, ctorFn RawStreamCtor) (*RawStreamRwc, error) {
28
+ rwc := &RawStreamRwc{}
29
+ var err error
30
+ rwc.writer, err = ctorFn(ctx, rwc.handlePacketData, rwc.handleClose)
31
+ if err != nil {
32
+ return nil, err
33
+ }
34
+ return rwc, nil
35
+ }
36
+
37
+ // handlePacketData implements PacketDataHandler.
38
+ func (r *RawStreamRwc) handlePacketData(pkt []byte) error {
39
+ r.mtx.Lock()
40
+ defer r.mtx.Unlock()
41
+
42
+ if r.closed {
43
+ return io.ErrClosedPipe
44
+ }
45
+
46
+ r.readQueue = append(r.readQueue, pkt)
47
+ r.bcast.Broadcast()
48
+ return nil
49
+ }
50
+
51
+ // handleClose handles the stream closing with an optional error.
52
+ func (r *RawStreamRwc) handleClose(closeErr error) {
53
+ r.mtx.Lock()
54
+ defer r.mtx.Unlock()
55
+ if r.closed {
56
+ return
57
+ }
58
+ r.closed = true
59
+ r.closeErr = closeErr
60
+ r.bcast.Broadcast()
61
+ }
62
+
63
+ // Read reads data from the stream to p.
64
+ // Implements io.Reader.
65
+ func (r *RawStreamRwc) Read(p []byte) (n int, err error) {
66
+ readBuf := p
67
+ for len(readBuf) != 0 && err == nil {
68
+ // if the buffer has data, read from it.
69
+ var rn int
70
+ var read []byte
71
+
72
+ r.mtx.Lock()
73
+ if len(r.readQueue) != 0 {
74
+ nrq := r.readQueue[0]
75
+ rn = len(readBuf)
76
+ if nrLen := len(nrq); nrLen < rn {
77
+ rn = nrLen
78
+ }
79
+ read = nrq[:rn]
80
+ nrq = nrq[rn:]
81
+ if len(nrq) == 0 {
82
+ r.readQueue[0] = nil
83
+ r.readQueue = r.readQueue[1:]
84
+ } else {
85
+ r.readQueue[0] = nrq
86
+ }
87
+ }
88
+ closed, closedErr := r.closed, r.closeErr
89
+ var wait <-chan struct{}
90
+ if rn == 0 && !closed {
91
+ wait = r.bcast.GetWaitCh()
92
+ }
93
+ r.mtx.Unlock()
94
+
95
+ // if we read data, copy it to the output buf
96
+ if rn != 0 {
97
+ // advance readBuf by rn
98
+ copy(readBuf, read)
99
+ n += rn
100
+ readBuf = readBuf[rn:]
101
+ continue
102
+ }
103
+
104
+ // if we read data to p already, return now.
105
+ if n != 0 {
106
+ break
107
+ }
108
+
109
+ // if closed or error, return.
110
+ if closed {
111
+ if closedErr != nil {
112
+ return n, closedErr
113
+ }
114
+ return n, io.EOF
115
+ }
116
+
117
+ // wait for data or closed
118
+ <-wait
119
+ }
120
+
121
+ return n, err
122
+ }
123
+
124
+ // Write writes data to the stream.
125
+ func (r *RawStreamRwc) Write(p []byte) (int, error) {
126
+ return r.writer.Write(p)
127
+ }
128
+
129
+ // WritePacket writes a packet to the remote.
130
+ func (r *RawStreamRwc) WritePacket(p *Packet) error {
131
+ return r.writer.WritePacket(p)
132
+ }
133
+
134
+ // Close closes the stream.
135
+ func (r *RawStreamRwc) Close() error {
136
+ r.mtx.Lock()
137
+ defer r.mtx.Unlock()
138
+
139
+ if r.closed {
140
+ return r.closeErr
141
+ }
142
+
143
+ r.closed = true
144
+ r.closeErr = r.writer.Close()
145
+ r.bcast.Broadcast()
146
+ return r.closeErr
147
+ }
148
+
149
+ // _ is a type assertion
150
+ var (
151
+ _ io.ReadWriteCloser = ((*RawStreamRwc)(nil))
152
+ _ Writer = ((*RawStreamRwc)(nil))
153
+ )
@@ -9,7 +9,7 @@ import (
9
9
  // Stream with the given Server. Starts read pumps for both. Starts the
10
10
  // HandleStream function on the server in a separate goroutine.
11
11
  func NewServerPipe(server *Server) OpenStreamFunc {
12
- return func(ctx context.Context, msgHandler PacketHandler, closeHandler CloseHandler) (Writer, error) {
12
+ return func(ctx context.Context, msgHandler PacketDataHandler, closeHandler CloseHandler) (Writer, error) {
13
13
  srvPipe, clientPipe := net.Pipe()
14
14
  go server.HandleStream(ctx, srvPipe)
15
15
  clientPrw := NewPacketReadWriter(clientPipe)
@@ -22,6 +22,15 @@ func NewServerRPC(ctx context.Context, invoker Invoker, writer Writer) *ServerRP
22
22
  return rpc
23
23
  }
24
24
 
25
+ // HandlePacketData handles an incoming unparsed message packet.
26
+ func (r *ServerRPC) HandlePacketData(data []byte) error {
27
+ msg := &Packet{}
28
+ if err := msg.UnmarshalVT(data); err != nil {
29
+ return err
30
+ }
31
+ return r.HandlePacket(msg)
32
+ }
33
+
25
34
  // HandlePacket handles an incoming parsed message packet.
26
35
  func (r *ServerRPC) HandlePacket(msg *Packet) error {
27
36
  if msg == nil {
package/srpc/server.go CHANGED
@@ -26,12 +26,13 @@ func (s *Server) GetInvoker() Invoker {
26
26
  }
27
27
 
28
28
  // HandleStream handles an incoming stream and runs the read loop.
29
+ // Uses length-prefixed packets.
29
30
  func (s *Server) HandleStream(ctx context.Context, rwc io.ReadWriteCloser) {
30
31
  subCtx, subCtxCancel := context.WithCancel(ctx)
31
32
  defer subCtxCancel()
32
33
  prw := NewPacketReadWriter(rwc)
33
34
  serverRPC := NewServerRPC(subCtx, s.invoker, prw)
34
- prw.ReadPump(serverRPC.HandlePacket, serverRPC.HandleStreamClose)
35
+ prw.ReadPump(serverRPC.HandlePacketData, serverRPC.HandleStreamClose)
35
36
  }
36
37
 
37
38
  // AcceptMuxedConn runs a loop which calls Accept on a muxer to handle streams.
package/srpc/server.ts CHANGED
@@ -25,8 +25,9 @@ export class Server implements StreamHandler {
25
25
  }
26
26
 
27
27
  // rpcStreamHandler implements the RpcStreamHandler interface.
28
+ // uses handlePacketDuplex (expects 1 buf = 1 Packet)
28
29
  public get rpcStreamHandler(): RpcStreamHandler {
29
- return this.handleStream.bind(this)
30
+ return this.handlePacketStream.bind(this)
30
31
  }
31
32
 
32
33
  // startRpc starts a new server-side RPC.
@@ -35,13 +36,10 @@ export class Server implements StreamHandler {
35
36
  return new ServerRPC(this.lookupMethod)
36
37
  }
37
38
 
38
- // handleStream handles an incoming Uint8Array message duplex.
39
- public handleStream(stream: Stream): ServerRPC {
40
- return this.handleDuplex(stream)
41
- }
42
-
43
- // handleDuplex handles an incoming message duplex.
44
- public handleDuplex(stream: Duplex<Uint8Array, Uint8Array>): ServerRPC {
39
+ // handleFragmentStream handles an incoming stream.
40
+ // assumes that stream does not maintain packet framing.
41
+ // uses length-prefixed packets for packet framing.
42
+ public handleFragmentStream(stream: Stream): ServerRPC {
45
43
  const rpc = this.startRpc()
46
44
  pipe(
47
45
  stream,
@@ -57,16 +55,16 @@ export class Server implements StreamHandler {
57
55
  return rpc
58
56
  }
59
57
 
60
- // handlePacketDuplex handles an incoming Uint8Array duplex.
61
- // skips the packet length prefix transform.
62
- public handlePacketDuplex(stream: Duplex<Uint8Array>): ServerRPC {
58
+ // handlePacketStream handles an incoming Uint8Array duplex.
59
+ // the stream has one Uint8Array per packet w/o length prefix.
60
+ public handlePacketStream(stream: Stream): ServerRPC {
63
61
  const rpc = this.startRpc()
64
62
  pipe(stream, decodePacketSource, rpc, encodePacketSource, stream)
65
63
  return rpc
66
64
  }
67
65
 
68
- // handlePacketStream handles an incoming Packet duplex.
69
- public handlePacketStream(stream: Duplex<Packet>): ServerRPC {
66
+ // handlePacketDuplex handles an incoming Packet duplex.
67
+ public handlePacketDuplex(stream: Duplex<Packet>): ServerRPC {
70
68
  const rpc = this.startRpc()
71
69
  pipe(stream, rpc, stream)
72
70
  return rpc
@@ -0,0 +1,85 @@
1
+ package srpc
2
+
3
+ import (
4
+ "bytes"
5
+ "io"
6
+ )
7
+
8
+ // StreamRwc implements an io.ReadWriteCloser with a srpc.Stream.
9
+ type StreamRwc struct {
10
+ // Stream is the base stream interface.
11
+ Stream
12
+
13
+ // buf is the incoming data buffer
14
+ buf bytes.Buffer
15
+ // readMsg is the raw read message
16
+ readMsg RawMessage
17
+ // writeMsg is the raw write message
18
+ writeMsg RawMessage
19
+ }
20
+
21
+ // NewStreamRwc constructs a new stream read write closer.
22
+ func NewStreamRwc(strm Stream) *StreamRwc {
23
+ rwc := &StreamRwc{Stream: strm}
24
+ rwc.readMsg.copy = true
25
+ rwc.writeMsg.copy = true
26
+ return rwc
27
+ }
28
+
29
+ // Read reads data from the stream to p.
30
+ // Implements io.Reader.
31
+ func (s *StreamRwc) Read(p []byte) (n int, err error) {
32
+ readBuf := p
33
+ for len(readBuf) != 0 && err == nil {
34
+ var rn int
35
+
36
+ // if the buffer has data, read from it.
37
+ if s.buf.Len() != 0 {
38
+ rn, err = s.buf.Read(readBuf)
39
+ } else {
40
+ if n != 0 {
41
+ // if we read data to p already, return now.
42
+ break
43
+ }
44
+
45
+ s.readMsg.Clear()
46
+ if err := s.Stream.MsgRecv(&s.readMsg); err != nil {
47
+ return n, err
48
+ }
49
+ data := s.readMsg.GetData()
50
+ if len(data) == 0 {
51
+ continue
52
+ }
53
+
54
+ // read as much as possible directly to the output
55
+ copy(readBuf, data)
56
+ if len(data) > len(readBuf) {
57
+ // we read some of the data, buffer the rest.
58
+ rn = len(readBuf)
59
+ _, _ = s.buf.Write(data[rn:]) // never returns an error
60
+ } else {
61
+ // we read all of data
62
+ rn = len(data)
63
+ }
64
+ }
65
+
66
+ // advance readBuf by rn
67
+ n += rn
68
+ readBuf = readBuf[rn:]
69
+ }
70
+ return n, err
71
+ }
72
+
73
+ // Write writes data to the stream.
74
+ func (s *StreamRwc) Write(p []byte) (n int, err error) {
75
+ s.writeMsg.SetData(p)
76
+ err = s.Stream.MsgSend(&s.writeMsg)
77
+ s.writeMsg.Clear()
78
+ if err != nil {
79
+ return 0, err
80
+ }
81
+ return len(p), nil
82
+ }
83
+
84
+ // _ is a type assertion
85
+ var _ io.ReadWriteCloser = ((*StreamRwc)(nil))
package/srpc/stream.ts CHANGED
@@ -5,6 +5,7 @@ import type { Duplex } from 'it-stream-types'
5
5
  export type PacketHandler = (packet: Packet) => Promise<void>
6
6
 
7
7
  // Stream is an open connection.
8
+ // This stream type generally assumes that each Uint8Array corresponds to a Packet.
8
9
  export type Stream = Duplex<Uint8Array>
9
10
 
10
11
  // OpenStreamFunc is a function to start a new RPC by opening a Stream.
package/srpc/writer.go CHANGED
@@ -2,6 +2,8 @@ package srpc
2
2
 
3
3
  // Writer is the interface used to write messages to the remote.
4
4
  type Writer interface {
5
+ // Write writes raw data to the remote.
6
+ Write(p []byte) (n int, err error)
5
7
  // WritePacket writes a packet to the remote.
6
8
  WritePacket(p *Packet) error
7
9
  // Close closes the writer.