starpc 0.18.3 → 0.19.1
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.
- package/dist/rpcstream/pushable-sink.d.ts +0 -0
- package/dist/rpcstream/pushable-sink.js +1 -0
- package/dist/rpcstream/rpcstream.js +1 -1
- package/dist/srpc/client.js +3 -4
- package/dist/srpc/common-rpc.js +1 -1
- package/dist/srpc/conn.d.ts +1 -1
- package/dist/srpc/conn.js +16 -6
- package/dist/srpc/errors.d.ts +1 -0
- package/dist/srpc/errors.js +23 -0
- package/dist/srpc/index.d.ts +4 -2
- package/dist/srpc/index.js +4 -2
- package/dist/srpc/pushable.d.ts +2 -0
- package/dist/srpc/pushable.js +23 -0
- package/dist/srpc/server.d.ts +3 -4
- package/dist/srpc/server.js +11 -12
- package/go.mod +3 -3
- package/go.sum +4 -4
- package/package.json +16 -16
- package/srpc/client-prefix.go +7 -0
- package/srpc/client-set.go +18 -3
- package/srpc/client.go +19 -3
- package/srpc/client.ts +3 -9
- package/srpc/common-rpc.ts +1 -1
- package/srpc/conn.ts +22 -8
- package/srpc/errors.go +2 -0
- package/srpc/errors.ts +24 -0
- package/srpc/index.ts +9 -2
- package/srpc/message.go +12 -0
- package/srpc/muxed-conn.go +1 -1
- package/srpc/packet-rw.go +18 -15
- package/srpc/packet.go +19 -0
- package/srpc/pushable.ts +23 -0
- package/srpc/raw-stream-rwc.go +153 -0
- package/srpc/server-pipe.go +1 -1
- package/srpc/server-rpc.go +9 -0
- package/srpc/server.go +2 -1
- package/srpc/server.ts +11 -13
- package/srpc/stream-rwc.go +85 -0
- package/srpc/stream.ts +1 -0
- package/srpc/writer.go +2 -0
|
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);
|
|
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) {
|
package/dist/srpc/client.js
CHANGED
|
@@ -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,
|
|
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
|
|
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(
|
|
82
|
+
pipe(stream, decodePacketSource, call, encodePacketSource, stream);
|
|
84
83
|
await call.writeCallStart(data || undefined);
|
|
85
84
|
return call;
|
|
86
85
|
}
|
package/dist/srpc/common-rpc.js
CHANGED
package/dist/srpc/conn.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface ConnParams {
|
|
|
9
9
|
direction?: Direction;
|
|
10
10
|
}
|
|
11
11
|
export interface StreamHandler {
|
|
12
|
-
|
|
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:
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
75
|
+
server.handlePacketStream(streamToSRPCStream(strm));
|
|
66
76
|
}
|
|
67
77
|
}
|
package/dist/srpc/errors.d.ts
CHANGED
package/dist/srpc/errors.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/srpc/index.d.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';
|
|
@@ -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';
|
package/dist/srpc/index.js
CHANGED
|
@@ -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';
|
package/dist/srpc/pushable.d.ts
CHANGED
package/dist/srpc/pushable.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/srpc/server.d.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
handlePacketDuplex(stream: Duplex<
|
|
16
|
-
handlePacketStream(stream: Duplex<Packet>): ServerRPC;
|
|
13
|
+
handleFragmentStream(stream: Stream): ServerRPC;
|
|
14
|
+
handlePacketStream(stream: Stream): ServerRPC;
|
|
15
|
+
handlePacketDuplex(stream: Duplex<Packet>): ServerRPC;
|
|
17
16
|
}
|
package/dist/srpc/server.js
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
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
|
-
//
|
|
37
|
-
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module github.com/aperturerobotics/starpc
|
|
2
2
|
|
|
3
|
-
go 1.
|
|
3
|
+
go 1.19
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
6
|
github.com/pkg/errors v0.9.1 // latest
|
|
@@ -9,8 +9,8 @@ require (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
require (
|
|
12
|
-
github.com/aperturerobotics/util v1.
|
|
13
|
-
github.com/libp2p/go-libp2p v0.26.
|
|
12
|
+
github.com/aperturerobotics/util v1.1.1 // latest
|
|
13
|
+
github.com/libp2p/go-libp2p v0.26.1-0.20230405232726-15ec1494039f // 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.
|
|
2
|
-
github.com/aperturerobotics/util v1.
|
|
1
|
+
github.com/aperturerobotics/util v1.1.1 h1:lZye9i6tBN36mIki/V1Mk5r5JcegkiQBeaME23UULG0=
|
|
2
|
+
github.com/aperturerobotics/util v1.1.1/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.
|
|
52
|
-
github.com/libp2p/go-libp2p v0.26.
|
|
51
|
+
github.com/libp2p/go-libp2p v0.26.1-0.20230405232726-15ec1494039f h1:lDBF6Sto3oiEXc+k9c57OAoxOqqJj7sXynw66TyRH+A=
|
|
52
|
+
github.com/libp2p/go-libp2p v0.26.1-0.20230405232726-15ec1494039f/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.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Streaming protobuf RPC service protocol over any two-way channel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -60,37 +60,37 @@
|
|
|
60
60
|
"singleQuote": true
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@aperturerobotics/ts-common": "^0.
|
|
64
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
65
|
-
"@typescript-eslint/parser": "^5.
|
|
63
|
+
"@aperturerobotics/ts-common": "^0.5.0",
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
|
65
|
+
"@typescript-eslint/parser": "^5.59.0",
|
|
66
66
|
"bufferutil": "^4.0.7",
|
|
67
67
|
"depcheck": "^1.4.3",
|
|
68
|
-
"esbuild": "^0.17.
|
|
69
|
-
"eslint": "^8.
|
|
68
|
+
"esbuild": "^0.17.17",
|
|
69
|
+
"eslint": "^8.38.0",
|
|
70
70
|
"eslint-config-prettier": "^8.8.0",
|
|
71
|
-
"prettier": "^2.8.
|
|
72
|
-
"rimraf": "^
|
|
73
|
-
"ts-proto": "^1.
|
|
74
|
-
"typescript": "^5.0.
|
|
71
|
+
"prettier": "^2.8.7",
|
|
72
|
+
"rimraf": "^5.0.0",
|
|
73
|
+
"ts-proto": "^1.146.0",
|
|
74
|
+
"typescript": "^5.0.4",
|
|
75
75
|
"utf-8-validate": "^6.0.3"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@chainsafe/libp2p-yamux": "^3.0.
|
|
78
|
+
"@chainsafe/libp2p-yamux": "^3.0.10",
|
|
79
79
|
"@libp2p/interface-connection": "^3.1.1",
|
|
80
80
|
"@libp2p/interface-stream-muxer": "^3.0.4",
|
|
81
81
|
"event-iterator": "^2.0.0",
|
|
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.
|
|
86
|
-
"it-pipe": "^2.0.
|
|
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
|
-
"it-ws": "^
|
|
90
|
-
"long": "^5.2.
|
|
89
|
+
"it-ws": "^6.0.1",
|
|
90
|
+
"long": "^5.2.3",
|
|
91
91
|
"memoize-one": "^6.0.0",
|
|
92
92
|
"patch-package": "^6.5.1",
|
|
93
|
-
"protobufjs": "^7.
|
|
93
|
+
"protobufjs": "^7.2.0",
|
|
94
94
|
"uint8arraylist": "^2.4.3",
|
|
95
95
|
"ws": "^8.13.0"
|
|
96
96
|
}
|
package/srpc/client-prefix.go
CHANGED
|
@@ -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))
|
package/srpc/client-set.go
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
127
|
-
parseLengthPrefixTransform(),
|
|
128
|
-
combineUint8ArrayListTransform(),
|
|
123
|
+
stream,
|
|
129
124
|
decodePacketSource,
|
|
130
125
|
call,
|
|
131
126
|
encodePacketSource,
|
|
132
|
-
|
|
133
|
-
conn
|
|
127
|
+
stream
|
|
134
128
|
)
|
|
135
129
|
await call.writeCallStart(data || undefined)
|
|
136
130
|
return call
|
package/srpc/common-rpc.ts
CHANGED
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
|
-
//
|
|
29
|
-
|
|
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(
|
|
38
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
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
|
package/srpc/muxed-conn.go
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
16
|
+
// PacketReadWriter reads and writes packets from a io.ReadWriter.
|
|
17
17
|
// Uses a LittleEndian uint32 length prefix.
|
|
18
|
-
type
|
|
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) *
|
|
29
|
-
return &
|
|
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 *
|
|
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 *
|
|
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 *
|
|
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
|
-
|
|
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 *
|
|
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 *
|
|
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 = (*
|
|
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
|
+
)
|
package/srpc/server-pipe.go
CHANGED
|
@@ -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
|
|
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)
|
package/srpc/server-rpc.go
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
//
|
|
61
|
-
//
|
|
62
|
-
public
|
|
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
|
-
//
|
|
69
|
-
public
|
|
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.
|