starpc 0.0.1 → 0.1.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.
Files changed (63) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +122 -27
  3. package/dist/echo/echo.d.ts +59 -0
  4. package/dist/echo/echo.js +85 -0
  5. package/dist/srpc/broadcast-channel.d.ts +16 -0
  6. package/dist/srpc/broadcast-channel.js +60 -0
  7. package/dist/srpc/client-rpc.d.ts +31 -0
  8. package/dist/srpc/client-rpc.js +176 -0
  9. package/dist/srpc/client.d.ts +12 -0
  10. package/dist/srpc/client.js +129 -0
  11. package/dist/srpc/conn.d.ts +14 -0
  12. package/dist/srpc/conn.js +38 -0
  13. package/dist/srpc/index.d.ts +3 -0
  14. package/dist/srpc/index.js +2 -0
  15. package/dist/srpc/packet.d.ts +9 -0
  16. package/dist/srpc/packet.js +89 -0
  17. package/dist/srpc/rpcproto.d.ts +194 -0
  18. package/dist/srpc/rpcproto.js +322 -0
  19. package/dist/srpc/stream.d.ts +5 -0
  20. package/dist/srpc/stream.js +1 -0
  21. package/dist/srpc/ts-proto-rpc.d.ts +7 -0
  22. package/dist/srpc/ts-proto-rpc.js +1 -0
  23. package/dist/srpc/websocket.d.ts +7 -0
  24. package/dist/srpc/websocket.js +18 -0
  25. package/echo/echo.go +1 -0
  26. package/echo/echo.pb.go +165 -0
  27. package/echo/echo.proto +19 -0
  28. package/echo/echo.ts +191 -0
  29. package/echo/echo_srpc.pb.go +333 -0
  30. package/echo/echo_vtproto.pb.go +271 -0
  31. package/echo/server.go +73 -0
  32. package/package.json +71 -9
  33. package/srpc/broadcast-channel.ts +72 -0
  34. package/srpc/client-rpc.go +163 -0
  35. package/srpc/client-rpc.ts +197 -0
  36. package/srpc/client.go +96 -0
  37. package/srpc/client.ts +182 -0
  38. package/srpc/conn.go +7 -0
  39. package/srpc/conn.ts +49 -0
  40. package/srpc/errors.go +20 -0
  41. package/srpc/handler.go +13 -0
  42. package/srpc/index.ts +3 -0
  43. package/srpc/message.go +7 -0
  44. package/srpc/mux.go +76 -0
  45. package/srpc/packet-rw.go +102 -0
  46. package/srpc/packet.go +71 -0
  47. package/srpc/packet.ts +105 -0
  48. package/srpc/rpc-stream.go +76 -0
  49. package/srpc/rpcproto.pb.go +455 -0
  50. package/srpc/rpcproto.proto +46 -0
  51. package/srpc/rpcproto.ts +467 -0
  52. package/srpc/rpcproto_vtproto.pb.go +1094 -0
  53. package/srpc/server-http.go +66 -0
  54. package/srpc/server-pipe.go +26 -0
  55. package/srpc/server-rpc.go +160 -0
  56. package/srpc/server.go +29 -0
  57. package/srpc/stream-pipe.go +86 -0
  58. package/srpc/stream.go +24 -0
  59. package/srpc/stream.ts +11 -0
  60. package/srpc/ts-proto-rpc.ts +29 -0
  61. package/srpc/websocket.go +68 -0
  62. package/srpc/websocket.ts +22 -0
  63. package/srpc/writer.go +9 -0
@@ -0,0 +1,271 @@
1
+ // Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
2
+ // protoc-gen-go-vtproto version: v0.3.1-0.20220531071333-dfd3d322ffb6
3
+ // source: github.com/aperturerobotics/starpc/echo/echo.proto
4
+
5
+ package echo
6
+
7
+ import (
8
+ fmt "fmt"
9
+ io "io"
10
+ bits "math/bits"
11
+
12
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
13
+ )
14
+
15
+ const (
16
+ // Verify that this generated code is sufficiently up-to-date.
17
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
18
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
19
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
20
+ )
21
+
22
+ func (this *EchoMsg) EqualVT(that *EchoMsg) bool {
23
+ if this == nil {
24
+ return that == nil || that.String() == ""
25
+ } else if that == nil {
26
+ return this.String() == ""
27
+ }
28
+ if this.Body != that.Body {
29
+ return false
30
+ }
31
+ return string(this.unknownFields) == string(that.unknownFields)
32
+ }
33
+
34
+ func (m *EchoMsg) MarshalVT() (dAtA []byte, err error) {
35
+ if m == nil {
36
+ return nil, nil
37
+ }
38
+ size := m.SizeVT()
39
+ dAtA = make([]byte, size)
40
+ n, err := m.MarshalToSizedBufferVT(dAtA[:size])
41
+ if err != nil {
42
+ return nil, err
43
+ }
44
+ return dAtA[:n], nil
45
+ }
46
+
47
+ func (m *EchoMsg) MarshalToVT(dAtA []byte) (int, error) {
48
+ size := m.SizeVT()
49
+ return m.MarshalToSizedBufferVT(dAtA[:size])
50
+ }
51
+
52
+ func (m *EchoMsg) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
53
+ if m == nil {
54
+ return 0, nil
55
+ }
56
+ i := len(dAtA)
57
+ _ = i
58
+ var l int
59
+ _ = l
60
+ if m.unknownFields != nil {
61
+ i -= len(m.unknownFields)
62
+ copy(dAtA[i:], m.unknownFields)
63
+ }
64
+ if len(m.Body) > 0 {
65
+ i -= len(m.Body)
66
+ copy(dAtA[i:], m.Body)
67
+ i = encodeVarint(dAtA, i, uint64(len(m.Body)))
68
+ i--
69
+ dAtA[i] = 0xa
70
+ }
71
+ return len(dAtA) - i, nil
72
+ }
73
+
74
+ func encodeVarint(dAtA []byte, offset int, v uint64) int {
75
+ offset -= sov(v)
76
+ base := offset
77
+ for v >= 1<<7 {
78
+ dAtA[offset] = uint8(v&0x7f | 0x80)
79
+ v >>= 7
80
+ offset++
81
+ }
82
+ dAtA[offset] = uint8(v)
83
+ return base
84
+ }
85
+ func (m *EchoMsg) SizeVT() (n int) {
86
+ if m == nil {
87
+ return 0
88
+ }
89
+ var l int
90
+ _ = l
91
+ l = len(m.Body)
92
+ if l > 0 {
93
+ n += 1 + l + sov(uint64(l))
94
+ }
95
+ n += len(m.unknownFields)
96
+ return n
97
+ }
98
+
99
+ func sov(x uint64) (n int) {
100
+ return (bits.Len64(x|1) + 6) / 7
101
+ }
102
+ func soz(x uint64) (n int) {
103
+ return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
104
+ }
105
+ func (m *EchoMsg) UnmarshalVT(dAtA []byte) error {
106
+ l := len(dAtA)
107
+ iNdEx := 0
108
+ for iNdEx < l {
109
+ preIndex := iNdEx
110
+ var wire uint64
111
+ for shift := uint(0); ; shift += 7 {
112
+ if shift >= 64 {
113
+ return ErrIntOverflow
114
+ }
115
+ if iNdEx >= l {
116
+ return io.ErrUnexpectedEOF
117
+ }
118
+ b := dAtA[iNdEx]
119
+ iNdEx++
120
+ wire |= uint64(b&0x7F) << shift
121
+ if b < 0x80 {
122
+ break
123
+ }
124
+ }
125
+ fieldNum := int32(wire >> 3)
126
+ wireType := int(wire & 0x7)
127
+ if wireType == 4 {
128
+ return fmt.Errorf("proto: EchoMsg: wiretype end group for non-group")
129
+ }
130
+ if fieldNum <= 0 {
131
+ return fmt.Errorf("proto: EchoMsg: illegal tag %d (wire type %d)", fieldNum, wire)
132
+ }
133
+ switch fieldNum {
134
+ case 1:
135
+ if wireType != 2 {
136
+ return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
137
+ }
138
+ var stringLen uint64
139
+ for shift := uint(0); ; shift += 7 {
140
+ if shift >= 64 {
141
+ return ErrIntOverflow
142
+ }
143
+ if iNdEx >= l {
144
+ return io.ErrUnexpectedEOF
145
+ }
146
+ b := dAtA[iNdEx]
147
+ iNdEx++
148
+ stringLen |= uint64(b&0x7F) << shift
149
+ if b < 0x80 {
150
+ break
151
+ }
152
+ }
153
+ intStringLen := int(stringLen)
154
+ if intStringLen < 0 {
155
+ return ErrInvalidLength
156
+ }
157
+ postIndex := iNdEx + intStringLen
158
+ if postIndex < 0 {
159
+ return ErrInvalidLength
160
+ }
161
+ if postIndex > l {
162
+ return io.ErrUnexpectedEOF
163
+ }
164
+ m.Body = string(dAtA[iNdEx:postIndex])
165
+ iNdEx = postIndex
166
+ default:
167
+ iNdEx = preIndex
168
+ skippy, err := skip(dAtA[iNdEx:])
169
+ if err != nil {
170
+ return err
171
+ }
172
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
173
+ return ErrInvalidLength
174
+ }
175
+ if (iNdEx + skippy) > l {
176
+ return io.ErrUnexpectedEOF
177
+ }
178
+ m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
179
+ iNdEx += skippy
180
+ }
181
+ }
182
+
183
+ if iNdEx > l {
184
+ return io.ErrUnexpectedEOF
185
+ }
186
+ return nil
187
+ }
188
+ func skip(dAtA []byte) (n int, err error) {
189
+ l := len(dAtA)
190
+ iNdEx := 0
191
+ depth := 0
192
+ for iNdEx < l {
193
+ var wire uint64
194
+ for shift := uint(0); ; shift += 7 {
195
+ if shift >= 64 {
196
+ return 0, ErrIntOverflow
197
+ }
198
+ if iNdEx >= l {
199
+ return 0, io.ErrUnexpectedEOF
200
+ }
201
+ b := dAtA[iNdEx]
202
+ iNdEx++
203
+ wire |= (uint64(b) & 0x7F) << shift
204
+ if b < 0x80 {
205
+ break
206
+ }
207
+ }
208
+ wireType := int(wire & 0x7)
209
+ switch wireType {
210
+ case 0:
211
+ for shift := uint(0); ; shift += 7 {
212
+ if shift >= 64 {
213
+ return 0, ErrIntOverflow
214
+ }
215
+ if iNdEx >= l {
216
+ return 0, io.ErrUnexpectedEOF
217
+ }
218
+ iNdEx++
219
+ if dAtA[iNdEx-1] < 0x80 {
220
+ break
221
+ }
222
+ }
223
+ case 1:
224
+ iNdEx += 8
225
+ case 2:
226
+ var length int
227
+ for shift := uint(0); ; shift += 7 {
228
+ if shift >= 64 {
229
+ return 0, ErrIntOverflow
230
+ }
231
+ if iNdEx >= l {
232
+ return 0, io.ErrUnexpectedEOF
233
+ }
234
+ b := dAtA[iNdEx]
235
+ iNdEx++
236
+ length |= (int(b) & 0x7F) << shift
237
+ if b < 0x80 {
238
+ break
239
+ }
240
+ }
241
+ if length < 0 {
242
+ return 0, ErrInvalidLength
243
+ }
244
+ iNdEx += length
245
+ case 3:
246
+ depth++
247
+ case 4:
248
+ if depth == 0 {
249
+ return 0, ErrUnexpectedEndOfGroup
250
+ }
251
+ depth--
252
+ case 5:
253
+ iNdEx += 4
254
+ default:
255
+ return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
256
+ }
257
+ if iNdEx < 0 {
258
+ return 0, ErrInvalidLength
259
+ }
260
+ if depth == 0 {
261
+ return iNdEx, nil
262
+ }
263
+ }
264
+ return 0, io.ErrUnexpectedEOF
265
+ }
266
+
267
+ var (
268
+ ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
269
+ ErrIntOverflow = fmt.Errorf("proto: integer overflow")
270
+ ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
271
+ )
package/echo/server.go ADDED
@@ -0,0 +1,73 @@
1
+ package echo
2
+
3
+ import (
4
+ context "context"
5
+ "errors"
6
+ "io"
7
+ "time"
8
+
9
+ "google.golang.org/protobuf/proto"
10
+ )
11
+
12
+ // EchoServer implements the server side of Echo.
13
+ type EchoServer struct {
14
+ }
15
+
16
+ // Echo implements echo.SRPCEchoerServer
17
+ func (*EchoServer) Echo(ctx context.Context, msg *EchoMsg) (*EchoMsg, error) {
18
+ return proto.Clone(msg).(*EchoMsg), nil
19
+ }
20
+
21
+ // EchoServerStream implements SRPCEchoerServer
22
+ func (*EchoServer) EchoServerStream(msg *EchoMsg, strm SRPCEchoer_EchoServerStreamStream) error {
23
+ // send 5 responses, with a 200ms delay for each
24
+ responses := 5
25
+ tkr := time.NewTicker(time.Millisecond * 200)
26
+ defer tkr.Stop()
27
+ for i := 0; i < responses; i++ {
28
+ if err := strm.MsgSend(msg); err != nil {
29
+ return err
30
+ }
31
+ select {
32
+ case <-strm.Context().Done():
33
+ return context.Canceled
34
+ case <-tkr.C:
35
+ }
36
+ }
37
+ return nil
38
+ }
39
+
40
+ // EchoClientStream implements SRPCEchoerServer
41
+ func (*EchoServer) EchoClientStream(strm SRPCEchoer_EchoClientStreamStream) error {
42
+ msg, err := strm.Recv()
43
+ if err != nil {
44
+ return err
45
+ }
46
+ return strm.SendAndClose(msg)
47
+ }
48
+
49
+ // EchoBidiStream implements SRPCEchoerServer
50
+ func (s *EchoServer) EchoBidiStream(strm SRPCEchoer_EchoBidiStreamStream) error {
51
+ // server sends initial message
52
+ if err := strm.MsgSend(&EchoMsg{Body: "hello from server"}); err != nil {
53
+ return err
54
+ }
55
+ for {
56
+ msg, err := strm.Recv()
57
+ if err != nil {
58
+ if err == io.EOF {
59
+ return nil
60
+ }
61
+ return err
62
+ }
63
+ if len(msg.GetBody()) == 0 {
64
+ return errors.New("got message with empty body")
65
+ }
66
+ if err := strm.Send(msg); err != nil {
67
+ return err
68
+ }
69
+ }
70
+ }
71
+
72
+ // _ is a type assertion
73
+ var _ SRPCEchoerServer = ((*EchoServer)(nil))
package/package.json CHANGED
@@ -1,15 +1,77 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
- "repository": "github:paralin/starpc",
6
- "license": "Apache-2.0",
5
+ "license": "MIT",
7
6
  "author": {
8
- "name": "Christian Stewart",
9
- "email": "christian@aperture.us",
10
- "url": "http://github.com/paralin"
7
+ "name": "Aperture Robotics LLC.",
8
+ "email": "support@aperture.us",
9
+ "url": "http://aperture.us"
11
10
  },
12
- "scripts": {},
13
- "prettier": {},
14
- "dependencies": {}
11
+ "contributors": [
12
+ {
13
+ "name": "Christian Stewart",
14
+ "email": "christian@aperture.us",
15
+ "url": "http://github.com/paralin"
16
+ }
17
+ ],
18
+ "main": "dist/srpc/index.js",
19
+ "types": "./dist/srpc/index.d.ts",
20
+ "files": [
21
+ "srpc",
22
+ "echo",
23
+ "dist/srpc",
24
+ "dist/echo",
25
+ "!**/*.tsbuildinfo"
26
+ ],
27
+ "repository": {
28
+ "url": "git@github.com:aperturerobotics/starpc.git"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc --project tsconfig.build.json --outDir ./dist/",
32
+ "check": "tsc",
33
+ "codegen": "npm run gen",
34
+ "ci": "npm run build && npm run lint:js && npm run lint:go",
35
+ "format": "prettier --write './srpc/**/(*.ts|*.tsx|*.js|*.html|*.css)'",
36
+ "gen": "make genproto",
37
+ "test": "make test && npm run check",
38
+ "test:integration": "make integration",
39
+ "lint": "npm run lint:go && npm run lint:js",
40
+ "lint:go": "make lint",
41
+ "lint:js": "eslint -c .eslintrc.js --ext .ts ./{srpc,echo}/**/*.ts",
42
+ "postinstall": "patch-package",
43
+ "precommit": "npm run format"
44
+ },
45
+ "prettier": {
46
+ "semi": false,
47
+ "singleQuote": true
48
+ },
49
+ "devDependencies": {
50
+ "@types/varint": "^6.0.0",
51
+ "@typescript-eslint/eslint-plugin": "^5.27.1",
52
+ "@typescript-eslint/parser": "^5.27.1",
53
+ "esbuild": "^0.14.43",
54
+ "eslint": "^8.17.0",
55
+ "eslint-config-prettier": "^8.5.0",
56
+ "eslint-config-standard-with-typescript": "^21.0.1",
57
+ "eslint-plugin-react-hooks": "^4.6.0",
58
+ "husky": "^8.0.1",
59
+ "patch-package": "^6.4.7",
60
+ "prettier": "^2.7.0",
61
+ "ts-proto": "^1.115.4",
62
+ "typescript": "^4.7.3"
63
+ },
64
+ "dependencies": {
65
+ "@libp2p/mplex": "^1.2.1",
66
+ "event-iterator": "^2.0.0",
67
+ "isomorphic-ws": "^4.0.1",
68
+ "it-length-prefixed": "^7.0.1",
69
+ "it-pipe": "^2.0.3",
70
+ "it-pushable": "^3.0.0",
71
+ "it-stream-types": "^1.0.4",
72
+ "it-ws": "^5.0.2",
73
+ "protobufjs": "^6.11.3",
74
+ "rxjs": "^7.5.5",
75
+ "ws": "^8.8.0"
76
+ }
15
77
  }
@@ -0,0 +1,72 @@
1
+ import type { Duplex, Sink } from 'it-stream-types'
2
+ import { Conn } from './conn'
3
+ import { EventIterator } from 'event-iterator'
4
+ import { pipe } from 'it-pipe'
5
+
6
+ // BroadcastChannelIterable is a AsyncIterable wrapper for BroadcastChannel.
7
+ export class BroadcastChannelIterable<T> implements Duplex<T> {
8
+ // channel is the broadcast channel
9
+ public readonly channel: BroadcastChannel
10
+ // sink is the sink for incoming messages.
11
+ public sink: Sink<T>
12
+ // source is the source for outgoing messages.
13
+ public source: AsyncIterable<T>
14
+
15
+ constructor(channel: BroadcastChannel) {
16
+ this.channel = channel
17
+ this.sink = this._createSink()
18
+ this.source = this._createSource()
19
+ }
20
+
21
+ // _createSink initializes the sink field.
22
+ private _createSink(): Sink<T> {
23
+ return async (source) => {
24
+ for await (const msg of source) {
25
+ this.channel.postMessage(msg)
26
+ }
27
+ }
28
+ }
29
+
30
+ // _createSource initializes the source field.
31
+ private _createSource() {
32
+ return new EventIterator<T>((queue) => {
33
+ const messageListener = (ev: MessageEvent<T>) => {
34
+ if (ev.data) {
35
+ queue.push(ev.data)
36
+ }
37
+ }
38
+ this.channel.addEventListener('message', messageListener)
39
+
40
+ return () => {
41
+ this.channel.removeEventListener('message', messageListener)
42
+ }
43
+ })
44
+ }
45
+ }
46
+
47
+ // newBroadcastChannelIterable constructs a BroadcastChannelIterable with a channel name.
48
+ export function newBroadcastChannelIterable<T>(
49
+ name: string
50
+ ): BroadcastChannelIterable<T> {
51
+ const channel = new BroadcastChannel(name)
52
+ return new BroadcastChannelIterable<T>(channel)
53
+ }
54
+
55
+ // BroadcastChannelConn implements a connection with a BroadcastChannel.
56
+ //
57
+ // expects Uint8Array objects over the BroadcastChannel.
58
+ export class BroadcastChannelConn extends Conn {
59
+ // channel is the broadcast channel iterable
60
+ private channel: BroadcastChannelIterable<Uint8Array>
61
+
62
+ constructor(channel: BroadcastChannel) {
63
+ super()
64
+ this.channel = new BroadcastChannelIterable<Uint8Array>(channel)
65
+ pipe(this, this.channel, this)
66
+ }
67
+
68
+ // getBroadcastChannel returns the BroadcastChannel.
69
+ public getBroadcastChannel(): BroadcastChannel {
70
+ return this.channel.channel
71
+ }
72
+ }
@@ -0,0 +1,163 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+
6
+ "github.com/pkg/errors"
7
+ )
8
+
9
+ // ClientRPC represents the client side of an on-going RPC call message stream.
10
+ // Not concurrency safe: use a mutex if calling concurrently.
11
+ type ClientRPC struct {
12
+ // ctx is the context, canceled when the rpc ends.
13
+ ctx context.Context
14
+ // ctxCancel is called when the rpc ends.
15
+ ctxCancel context.CancelFunc
16
+ // writer is the writer to write messages to
17
+ writer Writer
18
+ // service is the rpc service
19
+ service string
20
+ // method is the rpc method
21
+ method string
22
+ // dataCh contains queued data packets.
23
+ // closed when the client closes the channel.
24
+ dataCh chan []byte
25
+ // dataChClosed is a flag set after dataCh is closed.
26
+ // controlled by HandlePacket.
27
+ dataChClosed bool
28
+ // serverErr is an error set by the client.
29
+ // before dataCh is closed, managed by HandlePacket.
30
+ // immutable after dataCh is closed.
31
+ serverErr error
32
+ }
33
+
34
+ // NewClientRPC constructs a new ClientRPC session and writes CallStart.
35
+ // the writer will be closed when the ClientRPC completes.
36
+ // service and method must be specified.
37
+ // must call Start after creating the RPC object.
38
+ func NewClientRPC(ctx context.Context, service, method string) *ClientRPC {
39
+ rpc := &ClientRPC{
40
+ service: service,
41
+ method: method,
42
+ dataCh: make(chan []byte, 5),
43
+ }
44
+ rpc.ctx, rpc.ctxCancel = context.WithCancel(ctx)
45
+ return rpc
46
+ }
47
+
48
+ // Start sets the writer and writes the MsgSend message.
49
+ // firstMsg can be nil, but is usually set if this is a non-streaming rpc.
50
+ // must only be called once!
51
+ func (r *ClientRPC) Start(writer Writer, firstMsg []byte) error {
52
+ select {
53
+ case <-r.ctx.Done():
54
+ r.Close()
55
+ return context.Canceled
56
+ default:
57
+ }
58
+ r.writer = writer
59
+ pkt := NewCallStartPacket(r.service, r.method, firstMsg)
60
+ if err := writer.WritePacket(pkt); err != nil {
61
+ r.Close()
62
+ return err
63
+ }
64
+ return nil
65
+ }
66
+
67
+ // ReadAll reads all returned Data packets and returns any error.
68
+ // intended for use with unary rpcs.
69
+ func (r *ClientRPC) ReadAll() ([][]byte, error) {
70
+ msgs := make([][]byte, 0, 1)
71
+ for {
72
+ select {
73
+ case <-r.ctx.Done():
74
+ return msgs, context.Canceled
75
+ case data, ok := <-r.dataCh:
76
+ if !ok {
77
+ return msgs, r.serverErr
78
+ }
79
+ msgs = append(msgs, data)
80
+ }
81
+ }
82
+ }
83
+
84
+ // Context is canceled when the ClientRPC is no longer valid.
85
+ func (r *ClientRPC) Context() context.Context {
86
+ return r.ctx
87
+ }
88
+
89
+ // HandlePacketData handles an incoming unparsed message packet.
90
+ // Not concurrency safe: use a mutex if calling concurrently.
91
+ func (r *ClientRPC) HandlePacketData(data []byte) error {
92
+ pkt := &Packet{}
93
+ if err := pkt.UnmarshalVT(data); err != nil {
94
+ return err
95
+ }
96
+ return r.HandlePacket(pkt)
97
+ }
98
+
99
+ // HandlePacket handles an incoming parsed message packet.
100
+ // Not concurrency safe: use a mutex if calling concurrently.
101
+ func (r *ClientRPC) HandlePacket(msg *Packet) error {
102
+ if err := msg.Validate(); err != nil {
103
+ return err
104
+ }
105
+
106
+ switch b := msg.GetBody().(type) {
107
+ case *Packet_CallStart:
108
+ return r.HandleCallStart(b.CallStart)
109
+ case *Packet_CallData:
110
+ return r.HandleCallData(b.CallData)
111
+ case *Packet_CallStartResp:
112
+ return r.HandleCallStartResp(b.CallStartResp)
113
+ default:
114
+ return nil
115
+ }
116
+ }
117
+
118
+ // HandleCallStart handles the call start packet.
119
+ func (r *ClientRPC) HandleCallStart(pkt *CallStart) error {
120
+ // server-to-client calls not supported
121
+ return errors.Wrap(ErrUnrecognizedPacket, "call start packet unexpected")
122
+ }
123
+
124
+ // HandleCallData handles the call data packet.
125
+ func (r *ClientRPC) HandleCallData(pkt *CallData) error {
126
+ if r.dataChClosed {
127
+ return ErrCompleted
128
+ }
129
+
130
+ if data := pkt.GetData(); len(data) != 0 {
131
+ select {
132
+ case <-r.ctx.Done():
133
+ return context.Canceled
134
+ case r.dataCh <- data:
135
+ }
136
+ }
137
+
138
+ complete := pkt.GetComplete()
139
+ if err := pkt.GetError(); len(err) != 0 {
140
+ complete = true
141
+ r.serverErr = errors.New(err)
142
+ }
143
+
144
+ if complete {
145
+ r.dataChClosed = true
146
+ close(r.dataCh)
147
+ }
148
+
149
+ return nil
150
+ }
151
+
152
+ // HandleCallStartResp handles the CallStartResp packet.
153
+ func (r *ClientRPC) HandleCallStartResp(resp *CallStartResp) error {
154
+ // client-side calls not supported
155
+ return errors.Wrap(ErrUnrecognizedPacket, "call start resp packet unexpected")
156
+ }
157
+
158
+ // Close releases any resources held by the ClientRPC.
159
+ // not concurrency safe with HandlePacket.
160
+ func (r *ClientRPC) Close() {
161
+ r.ctxCancel()
162
+ _ = r.writer.Close()
163
+ }