├── .github └── workflows │ └── test.yaml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── bus.go ├── client.go ├── errors.go ├── go.mod ├── go.sum ├── info.go ├── internal ├── bus │ ├── bus.go │ ├── bus_interceptor.go │ ├── bus_local.go │ ├── bus_nats.go │ ├── bus_redis.go │ ├── bus_redis_test.go │ ├── bus_test.go │ ├── bustest │ │ ├── bustest.go │ │ ├── local.go │ │ ├── nats.go │ │ └── redis.go │ ├── empty.go │ ├── helpers_test.go │ ├── serialization.go │ ├── serialization_test.go │ └── subscription.go ├── interceptors │ ├── interceptor.go │ └── interceptor_test.go ├── internal.pb.go ├── internal.proto ├── logger │ └── logger.go ├── stream │ ├── options.go │ ├── stream.go │ └── stream_test.go └── test │ ├── .gitignore │ ├── empty_service │ ├── compile_test.go │ ├── empty_service.pb.go │ ├── empty_service.proto │ └── gen.go │ ├── errors_test.go │ ├── google_protobuf_imports │ ├── compile_test.go │ ├── gen.go │ ├── service.pb.go │ └── service.proto │ ├── importable │ ├── compile_test.go │ ├── gen.go │ ├── importable.pb.go │ └── importable.proto │ ├── importer │ ├── compile_test.go │ ├── gen.go │ ├── importer.pb.go │ └── importer.proto │ ├── importer_local │ ├── compile_test.go │ ├── gen.go │ ├── importer_local.pb.go │ ├── importer_local.proto │ ├── importer_local_msgdef.pb.go │ └── importer_local_msgdef.proto │ ├── importmapping │ ├── compile_test.go │ ├── gen.go │ ├── gen.sh │ ├── protoc_gen-x.sh │ ├── x │ │ ├── x.pb.go │ │ └── x.proto │ └── y │ │ ├── y.pb.go │ │ └── y.proto │ ├── multiple │ ├── compile_test.go │ ├── gen.go │ ├── multiple1.pb.go │ ├── multiple1.proto │ ├── multiple2.pb.go │ └── multiple2.proto │ ├── my_service │ ├── gen.go │ ├── my_service.pb.go │ ├── my_service.proto │ └── my_service_test.go │ ├── no_package_name │ ├── gen.go │ ├── no_package_name.pb.go │ └── no_package_name.proto │ ├── no_package_name_importer │ ├── compile_test.go │ ├── gen.go │ ├── no_package_name_importer.pb.go │ └── no_package_name_importer.proto │ ├── psrpc_test.go │ ├── service_method_same_name │ ├── compile_test.go │ ├── gen.go │ ├── service_method_same_name.pb.go │ └── service_method_same_name.proto │ └── snake_case_names │ ├── compile_test.go │ ├── gen.go │ ├── snake_case_names.pb.go │ └── snake_case_names.proto ├── logger.go ├── magefile.go ├── pkg ├── client │ ├── client.go │ ├── client_test.go │ ├── multi.go │ ├── options.go │ ├── rpc.go │ ├── stream.go │ └── subscription.go ├── info │ ├── channels.go │ ├── channels_test.go │ └── info.go ├── metadata │ └── metadata.go ├── middleware │ ├── metrics.go │ ├── recovery.go │ ├── retry.go │ └── retry_test.go ├── rand │ └── id.go └── server │ ├── options.go │ ├── registration.go │ ├── rpc.go │ ├── server.go │ └── stream.go ├── protoc-gen-psrpc ├── command_line.go ├── command_line_test.go ├── generator.go ├── generator_test.go ├── go_naming.go ├── go_naming_test.go ├── internal │ └── gen │ │ ├── logging.go │ │ ├── main.go │ │ ├── stringutils │ │ └── stringutils.go │ │ └── typemap │ │ ├── testdata │ │ ├── fileset.pb │ │ ├── gen.go │ │ ├── importer.proto │ │ ├── public_importer.proto │ │ ├── public_reimporter.proto │ │ ├── root_pkg.proto │ │ └── service.proto │ │ ├── typemap.go │ │ └── typemap_test.go ├── main.go └── options │ ├── options.pb.go │ └── options.proto ├── request.go ├── server.go ├── stream.go ├── testutils ├── bus.go ├── laggybus.go ├── testutils.pb.go ├── testutils.proto └── unreliablebus.go ├── types.go └── version └── version.go /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 LiveKit, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Test 16 | 17 | on: 18 | workflow_dispatch: 19 | push: 20 | branches: [ main ] 21 | pull_request: 22 | branches: [ main ] 23 | 24 | jobs: 25 | test: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Install Protoc 31 | uses: arduino/setup-protoc@v1 32 | with: 33 | version: '3.x' 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Set up Go 37 | uses: actions/setup-go@v4 38 | with: 39 | go-version: '>=1.22' 40 | 41 | - name: Download Go modules 42 | run: go mod download 43 | 44 | - name: Install protoc-gen-go 45 | run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 46 | 47 | - name: Generate Protobuf 48 | uses: magefile/mage-action@v2 49 | with: 50 | version: latest 51 | args: proto 52 | 53 | - name: Mage Test 54 | uses: magefile/mage-action@v2 55 | with: 56 | version: latest 57 | args: testall 58 | 59 | - name: Add changes 60 | uses: EndBug/add-and-commit@v9 61 | with: 62 | add: livekit 63 | default_author: github_actions 64 | message: generated protobuf 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 LiveKit, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | Portions of internal and protoc-gen-psrpc originated from Twirp (https://github.com/twitchtv/twirp/). 16 | Copyright 2018 Twitch Interactive, Inc. under Apache-2.0 License. 17 | -------------------------------------------------------------------------------- /bus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "github.com/nats-io/nats.go" 19 | "github.com/redis/go-redis/v9" 20 | 21 | "github.com/livekit/psrpc/internal/bus" 22 | ) 23 | 24 | type Channel = bus.Channel 25 | type MessageBus bus.MessageBus 26 | 27 | func NewLocalMessageBus() MessageBus { 28 | return bus.NewLocalMessageBus() 29 | } 30 | 31 | func NewNatsMessageBus(nc *nats.Conn) MessageBus { 32 | return bus.NewNatsMessageBus(nc) 33 | } 34 | 35 | func NewRedisMessageBus(rc redis.UniversalClient) MessageBus { 36 | return bus.NewRedisMessageBus(rc) 37 | } 38 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | const ( 25 | DefaultClientTimeout = time.Second * 3 26 | DefaultAffinityTimeout = time.Second 27 | DefaultAffinityShortCircuit = time.Millisecond * 200 28 | ) 29 | 30 | type ClientOption func(*ClientOpts) 31 | 32 | type ClientOpts struct { 33 | ClientID string 34 | Timeout time.Duration 35 | SelectionTimeout time.Duration 36 | ChannelSize int 37 | EnableStreams bool 38 | RequestHooks []ClientRequestHook 39 | ResponseHooks []ClientResponseHook 40 | RpcInterceptors []ClientRPCInterceptor 41 | MultiRPCInterceptors []ClientMultiRPCInterceptor 42 | StreamInterceptors []StreamInterceptor 43 | } 44 | 45 | func WithClientID(id string) ClientOption { 46 | return func(o *ClientOpts) { 47 | o.ClientID = id 48 | } 49 | } 50 | 51 | func WithClientTimeout(timeout time.Duration) ClientOption { 52 | return func(o *ClientOpts) { 53 | o.Timeout = timeout 54 | } 55 | } 56 | 57 | func WithClientSelectTimeout(timeout time.Duration) ClientOption { 58 | return func(o *ClientOpts) { 59 | o.SelectionTimeout = timeout 60 | } 61 | } 62 | 63 | func WithClientChannelSize(size int) ClientOption { 64 | return func(o *ClientOpts) { 65 | o.ChannelSize = size 66 | } 67 | } 68 | 69 | // Request hooks are called as soon as the request is made 70 | type ClientRequestHook func(ctx context.Context, req proto.Message, info RPCInfo) 71 | 72 | func WithClientRequestHooks(hooks ...ClientRequestHook) ClientOption { 73 | return func(o *ClientOpts) { 74 | o.RequestHooks = append(o.RequestHooks, hooks...) 75 | } 76 | } 77 | 78 | // Response hooks are called just before responses are returned 79 | // For multi-requests, response hooks are called on every response, and block while executing 80 | type ClientResponseHook func(ctx context.Context, req proto.Message, info RPCInfo, res proto.Message, err error) 81 | 82 | func WithClientResponseHooks(hooks ...ClientResponseHook) ClientOption { 83 | return func(o *ClientOpts) { 84 | o.ResponseHooks = append(o.ResponseHooks, hooks...) 85 | } 86 | } 87 | 88 | type ClientRPCInterceptor func(info RPCInfo, next ClientRPCHandler) ClientRPCHandler 89 | type ClientRPCHandler func(ctx context.Context, req proto.Message, opts ...RequestOption) (proto.Message, error) 90 | 91 | func WithClientRPCInterceptors(interceptors ...ClientRPCInterceptor) ClientOption { 92 | return func(o *ClientOpts) { 93 | o.RpcInterceptors = append(o.RpcInterceptors, interceptors...) 94 | } 95 | } 96 | 97 | type ClientMultiRPCInterceptor func(info RPCInfo, next ClientMultiRPCHandler) ClientMultiRPCHandler 98 | type ClientMultiRPCHandler interface { 99 | Send(ctx context.Context, msg proto.Message, opts ...RequestOption) error 100 | Recv(msg proto.Message, err error) 101 | Close() 102 | } 103 | 104 | func WithClientMultiRPCInterceptors(interceptors ...ClientMultiRPCInterceptor) ClientOption { 105 | return func(o *ClientOpts) { 106 | o.MultiRPCInterceptors = append(o.MultiRPCInterceptors, interceptors...) 107 | } 108 | } 109 | 110 | func WithClientStreamInterceptors(interceptors ...StreamInterceptor) ClientOption { 111 | return func(o *ClientOpts) { 112 | o.StreamInterceptors = append(o.StreamInterceptors, interceptors...) 113 | } 114 | } 115 | 116 | func WithClientOptions(opts ...ClientOption) ClientOption { 117 | return func(o *ClientOpts) { 118 | for _, opt := range opts { 119 | opt(o) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/livekit/psrpc 2 | 3 | go 1.22 4 | 5 | toolchain go1.23.5 6 | 7 | require ( 8 | github.com/frostbyte73/core v0.0.10 9 | github.com/gammazero/deque v0.2.1 10 | github.com/go-logr/logr v1.3.0 11 | github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 12 | github.com/nats-io/nats.go v1.31.0 13 | github.com/ory/dockertest/v3 v3.11.0 14 | github.com/pkg/errors v0.9.1 15 | github.com/redis/go-redis/v9 v9.3.0 16 | github.com/stretchr/testify v1.9.0 17 | github.com/twitchtv/twirp v8.1.3+incompatible 18 | go.uber.org/atomic v1.11.0 19 | go.uber.org/multierr v1.11.0 20 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa 21 | golang.org/x/mod v0.14.0 22 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 23 | google.golang.org/grpc v1.59.0 24 | google.golang.org/protobuf v1.31.0 25 | ) 26 | 27 | require ( 28 | dario.cat/mergo v1.0.0 // indirect 29 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 30 | github.com/Microsoft/go-winio v0.6.2 // indirect 31 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 32 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 33 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 34 | github.com/containerd/continuity v0.4.3 // indirect 35 | github.com/davecgh/go-spew v1.1.1 // indirect 36 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 37 | github.com/docker/cli v26.1.4+incompatible // indirect 38 | github.com/docker/docker v27.1.1+incompatible // indirect 39 | github.com/docker/go-connections v0.5.0 // indirect 40 | github.com/docker/go-units v0.5.0 // indirect 41 | github.com/gogo/protobuf v1.3.2 // indirect 42 | github.com/golang/protobuf v1.5.3 // indirect 43 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 44 | github.com/klauspost/compress v1.17.2 // indirect 45 | github.com/mitchellh/mapstructure v1.5.0 // indirect 46 | github.com/moby/docker-image-spec v1.3.1 // indirect 47 | github.com/moby/term v0.5.0 // indirect 48 | github.com/nats-io/nkeys v0.4.6 // indirect 49 | github.com/nats-io/nuid v1.0.1 // indirect 50 | github.com/opencontainers/go-digest v1.0.0 // indirect 51 | github.com/opencontainers/image-spec v1.1.0 // indirect 52 | github.com/opencontainers/runc v1.1.13 // indirect 53 | github.com/pmezard/go-difflib v1.0.0 // indirect 54 | github.com/sirupsen/logrus v1.9.3 // indirect 55 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 56 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 57 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 58 | golang.org/x/crypto v0.22.0 // indirect 59 | golang.org/x/sync v0.5.0 // indirect 60 | golang.org/x/sys v0.21.0 // indirect 61 | gopkg.in/yaml.v2 v2.4.0 // indirect 62 | gopkg.in/yaml.v3 v3.0.1 // indirect 63 | ) 64 | -------------------------------------------------------------------------------- /info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | type RPCInfo struct { 18 | Service string 19 | Method string 20 | Topic []string 21 | Multi bool 22 | } 23 | -------------------------------------------------------------------------------- /internal/bus/bus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/protobuf/proto" 21 | ) 22 | 23 | const ( 24 | DefaultChannelSize = 100 25 | ) 26 | 27 | type Channel struct { 28 | Legacy, Server, Local string 29 | } 30 | 31 | type MessageBus interface { 32 | Publish(ctx context.Context, channel Channel, msg proto.Message) error 33 | Subscribe(ctx context.Context, channel Channel, channelSize int) (Reader, error) 34 | SubscribeQueue(ctx context.Context, channel Channel, channelSize int) (Reader, error) 35 | } 36 | 37 | type Reader interface { 38 | read() ([]byte, bool) 39 | Close() error 40 | } 41 | 42 | func Subscribe[MessageType proto.Message]( 43 | ctx context.Context, 44 | bus MessageBus, 45 | channel Channel, 46 | channelSize int, 47 | ) (Subscription[MessageType], error) { 48 | 49 | sub, err := bus.Subscribe(ctx, channel, channelSize) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return newSubscription[MessageType](sub, channelSize), nil 55 | } 56 | 57 | func SubscribeQueue[MessageType proto.Message]( 58 | ctx context.Context, 59 | bus MessageBus, 60 | channel Channel, 61 | channelSize int, 62 | ) (Subscription[MessageType], error) { 63 | 64 | sub, err := bus.SubscribeQueue(ctx, channel, channelSize) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return newSubscription[MessageType](sub, channelSize), nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/bus/bus_interceptor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/protobuf/proto" 21 | ) 22 | 23 | type PublishInterceptor func(next PublishHandler) PublishHandler 24 | type SubscribeInterceptor func(ctx context.Context, channel Channel, next ReadHandler) ReadHandler 25 | 26 | type PublishHandler func(ctx context.Context, channel Channel, msg proto.Message) error 27 | type ReadHandler func() ([]byte, bool) 28 | 29 | type TestBusOption func(*TestBusOpts) 30 | 31 | type TestBusOpts struct { 32 | PublishInterceptors []PublishInterceptor 33 | SubscribeInterceptors []SubscribeInterceptor 34 | } 35 | 36 | func NewTestBus(bus MessageBus, opts ...TestBusOption) MessageBus { 37 | o := &TestBusOpts{} 38 | for _, opt := range opts { 39 | opt(o) 40 | } 41 | 42 | var publishHandler PublishHandler = bus.Publish 43 | for i := len(o.PublishInterceptors) - 1; i >= 0; i-- { 44 | publishHandler = o.PublishInterceptors[i](publishHandler) 45 | } 46 | 47 | return &testBus{ 48 | bus: bus, 49 | publishHandler: publishHandler, 50 | subscribeInterceptors: o.SubscribeInterceptors, 51 | } 52 | } 53 | 54 | type testBus struct { 55 | bus MessageBus 56 | publishHandler PublishHandler 57 | subscribeInterceptors []SubscribeInterceptor 58 | } 59 | 60 | func (l *testBus) Publish(ctx context.Context, channel Channel, msg proto.Message) error { 61 | return l.publishHandler(ctx, channel, msg) 62 | } 63 | 64 | func (l *testBus) Subscribe(ctx context.Context, channel Channel, size int) (Reader, error) { 65 | r, err := l.bus.Subscribe(ctx, channel, size) 66 | if err != nil { 67 | return nil, err 68 | } 69 | return &testReader{r, l.chainSubscribeInterceptors(ctx, channel, r.read)}, nil 70 | } 71 | 72 | func (l *testBus) SubscribeQueue(ctx context.Context, channel Channel, size int) (Reader, error) { 73 | r, err := l.bus.SubscribeQueue(ctx, channel, size) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return &testReader{r, l.chainSubscribeInterceptors(ctx, channel, r.read)}, nil 78 | } 79 | 80 | func (l *testBus) chainSubscribeInterceptors(ctx context.Context, channel Channel, handler ReadHandler) ReadHandler { 81 | for i := len(l.subscribeInterceptors) - 1; i >= 0; i-- { 82 | handler = l.subscribeInterceptors[i](ctx, channel, handler) 83 | } 84 | return handler 85 | } 86 | 87 | type testReader struct { 88 | Reader 89 | readHandler ReadHandler 90 | } 91 | 92 | func (r *testReader) read() ([]byte, bool) { 93 | return r.readHandler() 94 | } 95 | -------------------------------------------------------------------------------- /internal/bus/bus_local.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | type localMessageBus struct { 25 | sync.RWMutex 26 | subs map[string]*localSubList 27 | queues map[string]*localSubList 28 | } 29 | 30 | func NewLocalMessageBus() MessageBus { 31 | return &localMessageBus{ 32 | subs: make(map[string]*localSubList), 33 | queues: make(map[string]*localSubList), 34 | } 35 | } 36 | 37 | func (l *localMessageBus) Publish(_ context.Context, channel Channel, msg proto.Message) error { 38 | b, err := serialize(msg, "") 39 | if err != nil { 40 | return err 41 | } 42 | 43 | l.RLock() 44 | subs := l.subs[channel.Legacy] 45 | queues := l.queues[channel.Legacy] 46 | l.RUnlock() 47 | 48 | if subs != nil { 49 | subs.dispatch(b) 50 | } 51 | if queues != nil { 52 | queues.dispatch(b) 53 | } 54 | return nil 55 | } 56 | 57 | func (l *localMessageBus) Subscribe(ctx context.Context, channel Channel, size int) (Reader, error) { 58 | return l.subscribe(ctx, l.subs, channel.Legacy, size, false) 59 | } 60 | 61 | func (l *localMessageBus) SubscribeQueue(ctx context.Context, channel Channel, size int) (Reader, error) { 62 | return l.subscribe(ctx, l.queues, channel.Legacy, size, true) 63 | } 64 | 65 | func (l *localMessageBus) subscribe(ctx context.Context, subLists map[string]*localSubList, channel string, size int, queue bool) (Reader, error) { 66 | l.Lock() 67 | defer l.Unlock() 68 | 69 | subList := subLists[channel] 70 | if subList == nil { 71 | subList = &localSubList{queue: queue} 72 | subList.onUnsubscribe = func(index int) { 73 | // lock localMessageBus before localSubList 74 | l.Lock() 75 | subList.Lock() 76 | 77 | subList.subs[index] = nil 78 | subList.subCount-- 79 | if subList.subCount == 0 { 80 | delete(subLists, channel) 81 | } 82 | 83 | subList.Unlock() 84 | l.Unlock() 85 | } 86 | subLists[channel] = subList 87 | } 88 | 89 | return subList.create(ctx, size), nil 90 | } 91 | 92 | type localSubList struct { 93 | sync.RWMutex // locking while holding localMessageBus lock is allowed 94 | subs []*localSubscription 95 | subCount int 96 | queue bool 97 | next int 98 | onUnsubscribe func(int) 99 | } 100 | 101 | func (l *localSubList) create(ctx context.Context, size int) *localSubscription { 102 | ctx, cancel := context.WithCancel(ctx) 103 | sub := &localSubscription{ 104 | ctx: ctx, 105 | cancel: cancel, 106 | msgChan: make(chan []byte, size), 107 | } 108 | 109 | l.Lock() 110 | defer l.Unlock() 111 | 112 | l.subCount++ 113 | added := false 114 | index := 0 115 | for i, s := range l.subs { 116 | if s == nil { 117 | added = true 118 | index = i 119 | l.subs[i] = sub 120 | break 121 | } 122 | } 123 | 124 | if !added { 125 | index = len(l.subs) 126 | l.subs = append(l.subs, sub) 127 | } 128 | 129 | sub.onClose = func() { 130 | l.onUnsubscribe(index) 131 | } 132 | 133 | return sub 134 | } 135 | 136 | func (l *localSubList) dispatch(b []byte) { 137 | if l.queue { 138 | l.Lock() 139 | defer l.Unlock() 140 | 141 | // round-robin 142 | for i := 0; i <= len(l.subs); i++ { 143 | if l.next >= len(l.subs) { 144 | l.next = 0 145 | } 146 | s := l.subs[l.next] 147 | l.next++ 148 | if s != nil { 149 | s.write(b) 150 | return 151 | } 152 | } 153 | } else { 154 | l.RLock() 155 | defer l.RUnlock() 156 | 157 | // send to all 158 | for _, s := range l.subs { 159 | if s != nil { 160 | s.write(b) 161 | } 162 | } 163 | } 164 | } 165 | 166 | type localSubscription struct { 167 | ctx context.Context 168 | cancel context.CancelFunc 169 | msgChan chan []byte 170 | onClose func() 171 | } 172 | 173 | func (l *localSubscription) write(b []byte) { 174 | select { 175 | case l.msgChan <- b: 176 | case <-l.ctx.Done(): 177 | } 178 | } 179 | 180 | func (l *localSubscription) read() ([]byte, bool) { 181 | msg, ok := <-l.msgChan 182 | if !ok { 183 | return nil, false 184 | } 185 | return msg, true 186 | } 187 | 188 | func (l *localSubscription) Close() error { 189 | l.cancel() 190 | l.onClose() 191 | close(l.msgChan) 192 | return nil 193 | } 194 | -------------------------------------------------------------------------------- /internal/bus/bus_redis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "testing" 21 | "time" 22 | "unsafe" 23 | 24 | "github.com/stretchr/testify/require" 25 | "go.uber.org/atomic" 26 | "google.golang.org/protobuf/types/known/wrapperspb" 27 | 28 | "github.com/livekit/psrpc/internal/bus" 29 | "github.com/livekit/psrpc/internal/bus/bustest" 30 | ) 31 | 32 | func redisTestChannel(channel string) bus.Channel { 33 | return bus.Channel{Legacy: channel} 34 | } 35 | 36 | func TestRedisMessageBus(t *testing.T) { 37 | srv := bustest.NewRedis(t, bustest.Docker(t)) 38 | 39 | t.Run("published messages are received by subscribers", func(t *testing.T) { 40 | b0 := srv.Connect(t) 41 | b1 := srv.Connect(t) 42 | 43 | r, err := b0.Subscribe(context.Background(), redisTestChannel("test"), 100) 44 | require.NoError(t, err) 45 | 46 | time.Sleep(100 * time.Millisecond) 47 | 48 | src := wrapperspb.String("test") 49 | 50 | err = b1.Publish(context.Background(), redisTestChannel("test"), src) 51 | require.NoError(t, err) 52 | 53 | b, ok := bus.RawRead(r) 54 | require.True(t, ok) 55 | 56 | dst, err := bus.Deserialize(b) 57 | require.NoError(t, err) 58 | require.Equal(t, src.Value, dst.(*wrapperspb.StringValue).Value) 59 | }) 60 | 61 | t.Run("published messages are received by only one queue subscriber", func(t *testing.T) { 62 | b0 := srv.Connect(t) 63 | b1 := srv.Connect(t) 64 | b2 := srv.Connect(t) 65 | 66 | r1, err := b1.SubscribeQueue(context.Background(), redisTestChannel("test"), 100) 67 | require.NoError(t, err) 68 | r2, err := b2.SubscribeQueue(context.Background(), redisTestChannel("test"), 100) 69 | require.NoError(t, err) 70 | 71 | time.Sleep(100 * time.Millisecond) 72 | 73 | src := wrapperspb.String("test") 74 | 75 | err = b0.Publish(context.Background(), redisTestChannel("test"), src) 76 | require.NoError(t, err) 77 | 78 | var n atomic.Int64 79 | 80 | go func() { 81 | if _, ok := bus.RawRead(r1); ok { 82 | n.Inc() 83 | } 84 | }() 85 | go func() { 86 | if _, ok := bus.RawRead(r2); ok { 87 | n.Inc() 88 | } 89 | }() 90 | 91 | time.Sleep(time.Second) 92 | 93 | require.EqualValues(t, 1, n.Load()) 94 | }) 95 | 96 | t.Run("closed subscriptions are unreadable", func(t *testing.T) { 97 | b0 := srv.Connect(t) 98 | b1 := srv.Connect(t) 99 | b2 := srv.Connect(t) 100 | 101 | r1, err := b1.Subscribe(context.Background(), redisTestChannel("test"), 100) 102 | require.NoError(t, err) 103 | r2, err := b2.Subscribe(context.Background(), redisTestChannel("test"), 100) 104 | require.NoError(t, err) 105 | 106 | time.Sleep(100 * time.Millisecond) 107 | 108 | src := wrapperspb.String("test") 109 | 110 | err = b0.Publish(context.Background(), redisTestChannel("test"), src) 111 | require.NoError(t, err) 112 | 113 | _, ok := bus.RawRead(r1) 114 | require.True(t, ok) 115 | _, ok = bus.RawRead(r2) 116 | require.True(t, ok) 117 | 118 | err = r1.Close() 119 | require.NoError(t, err) 120 | 121 | time.Sleep(time.Second) 122 | 123 | err = b0.Publish(context.Background(), redisTestChannel("test"), src) 124 | require.NoError(t, err) 125 | 126 | _, ok = bus.RawRead(r1) 127 | require.False(t, ok) 128 | _, ok = bus.RawRead(r2) 129 | require.True(t, ok) 130 | }) 131 | } 132 | 133 | func BenchmarkRedisMessageBus(b *testing.B) { 134 | srv := bustest.NewRedis(b, bustest.Docker(b)) 135 | 136 | b0 := srv.Connect(b) 137 | b1 := srv.Connect(b) 138 | 139 | r, _ := b0.Subscribe(context.Background(), redisTestChannel("test"), 100) 140 | 141 | time.Sleep(100 * time.Millisecond) 142 | 143 | done := make(chan struct{}) 144 | go func() { 145 | for i := 0; i < b.N; i++ { 146 | bus.RawRead(r) 147 | } 148 | close(done) 149 | }() 150 | 151 | b.ResetTimer() 152 | 153 | src := wrapperspb.String("test") 154 | for i := 0; i < b.N; i++ { 155 | b1.Publish(context.Background(), redisTestChannel("test"), src) 156 | } 157 | 158 | <-done 159 | } 160 | 161 | func TestFoo(t *testing.T) { 162 | foo := "asdf" 163 | fmt.Println(*(*[]byte)(unsafe.Pointer(&foo))) 164 | } 165 | -------------------------------------------------------------------------------- /internal/bus/bus_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus_test 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/require" 23 | 24 | "github.com/livekit/psrpc/internal" 25 | "github.com/livekit/psrpc/internal/bus" 26 | "github.com/livekit/psrpc/internal/bus/bustest" 27 | "github.com/livekit/psrpc/pkg/rand" 28 | ) 29 | 30 | const defaultClientTimeout = time.Second * 3 31 | 32 | func busTestChannel(channel string) bus.Channel { 33 | return bus.Channel{ 34 | Legacy: channel, 35 | Server: channel, 36 | } 37 | } 38 | 39 | func TestMessageBus(t *testing.T) { 40 | bustest.TestAll(t, func(t *testing.T, bus func(t testing.TB) bus.MessageBus) { 41 | b := bus(t) 42 | t.Run("testSubscribe", func(t *testing.T) { testSubscribe(t, b) }) 43 | t.Run("testSubscribeQueue", func(t *testing.T) { testSubscribeQueue(t, b) }) 44 | t.Run("testSubscribeClose", func(t *testing.T) { testSubscribeClose(t, b) }) 45 | }) 46 | } 47 | 48 | func testSubscribe(t *testing.T, b bus.MessageBus) { 49 | ctx := context.Background() 50 | 51 | channel := rand.NewString() 52 | subA, err := bus.Subscribe[*internal.Request](ctx, b, busTestChannel(channel), bus.DefaultChannelSize) 53 | require.NoError(t, err) 54 | subB, err := bus.Subscribe[*internal.Request](ctx, b, busTestChannel(channel), bus.DefaultChannelSize) 55 | require.NoError(t, err) 56 | time.Sleep(time.Millisecond * 100) 57 | 58 | require.NoError(t, b.Publish(ctx, busTestChannel(channel), &internal.Request{ 59 | RequestId: "1", 60 | })) 61 | 62 | msgA := <-subA.Channel() 63 | msgB := <-subB.Channel() 64 | require.NotNil(t, msgA) 65 | require.NotNil(t, msgB) 66 | require.Equal(t, "1", msgA.RequestId) 67 | require.Equal(t, "1", msgB.RequestId) 68 | } 69 | 70 | func testSubscribeQueue(t *testing.T, b bus.MessageBus) { 71 | ctx := context.Background() 72 | 73 | channel := rand.NewString() 74 | subA, err := bus.SubscribeQueue[*internal.Request](ctx, b, busTestChannel(channel), bus.DefaultChannelSize) 75 | require.NoError(t, err) 76 | subB, err := bus.SubscribeQueue[*internal.Request](ctx, b, busTestChannel(channel), bus.DefaultChannelSize) 77 | require.NoError(t, err) 78 | time.Sleep(time.Millisecond * 100) 79 | 80 | require.NoError(t, b.Publish(ctx, busTestChannel(channel), &internal.Request{ 81 | RequestId: "2", 82 | })) 83 | 84 | received := 0 85 | select { 86 | case m := <-subA.Channel(): 87 | if m != nil { 88 | received++ 89 | } 90 | case <-time.After(defaultClientTimeout): 91 | // continue 92 | } 93 | 94 | select { 95 | case m := <-subB.Channel(): 96 | if m != nil { 97 | received++ 98 | } 99 | case <-time.After(defaultClientTimeout): 100 | // continue 101 | } 102 | 103 | require.Equal(t, 1, received) 104 | } 105 | 106 | func testSubscribeClose(t *testing.T, b bus.MessageBus) { 107 | ctx := context.Background() 108 | 109 | channel := rand.NewString() 110 | sub, err := bus.Subscribe[*internal.Request](ctx, b, busTestChannel(channel), bus.DefaultChannelSize) 111 | require.NoError(t, err) 112 | 113 | require.NoError(t, sub.Close()) 114 | time.Sleep(time.Millisecond * 100) 115 | 116 | select { 117 | case _, ok := <-sub.Channel(): 118 | require.False(t, ok) 119 | default: 120 | require.FailNow(t, "closed subscription channel should not block") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /internal/bus/bustest/bustest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bustest 16 | 17 | import ( 18 | "math/rand/v2" 19 | "net" 20 | "testing" 21 | 22 | "github.com/ory/dockertest/v3" 23 | 24 | "github.com/livekit/psrpc/internal/bus" 25 | ) 26 | 27 | var ( 28 | baseID = rand.Uint32N(1000) 29 | servers []serverInfo 30 | ) 31 | 32 | type serverInfo struct { 33 | Name string 34 | Func ServerFunc 35 | } 36 | 37 | type ServerFunc func(t testing.TB, pool *dockertest.Pool) Server 38 | 39 | func RegisterServer(name string, fnc ServerFunc) { 40 | servers = append(servers, serverInfo{ 41 | Name: name, 42 | Func: fnc, 43 | }) 44 | } 45 | 46 | func waitTCPPort(t testing.TB, pool *dockertest.Pool, addr string) { 47 | if err := pool.Retry(func() error { 48 | conn, err := net.Dial("tcp", addr) 49 | if err != nil { 50 | t.Log(err) 51 | return err 52 | } 53 | _ = conn.Close() 54 | return nil 55 | }); err != nil { 56 | t.Fatal(err) 57 | } 58 | } 59 | 60 | func Docker(t testing.TB) *dockertest.Pool { 61 | pool, err := dockertest.NewPool("") 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | err = pool.Client.Ping() 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | return pool 70 | } 71 | 72 | type Server interface { 73 | Connect(t testing.TB) bus.MessageBus 74 | } 75 | 76 | func TestAll(t *testing.T, test func(t *testing.T, bus func(t testing.TB) bus.MessageBus)) { 77 | pool := Docker(t) 78 | for _, c := range servers { 79 | c := c 80 | t.Run(c.Name, func(t *testing.T) { 81 | s := c.Func(t, pool) 82 | test(t, s.Connect) 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /internal/bus/bustest/local.go: -------------------------------------------------------------------------------- 1 | package bustest 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ory/dockertest/v3" 7 | 8 | "github.com/livekit/psrpc/internal/bus" 9 | ) 10 | 11 | func init() { 12 | RegisterServer("Local", func(t testing.TB, pool *dockertest.Pool) Server { 13 | return NewLocalBus() 14 | }) 15 | } 16 | 17 | func NewLocalBus() Server { 18 | b := bus.NewLocalMessageBus() 19 | return &localBus{b: b} 20 | } 21 | 22 | type localBus struct { 23 | b bus.MessageBus 24 | } 25 | 26 | func (s *localBus) Connect(t testing.TB) bus.MessageBus { 27 | return s.b 28 | } 29 | -------------------------------------------------------------------------------- /internal/bus/bustest/nats.go: -------------------------------------------------------------------------------- 1 | package bustest 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | "testing" 7 | 8 | "github.com/nats-io/nats.go" 9 | "github.com/ory/dockertest/v3" 10 | 11 | "github.com/livekit/psrpc/internal/bus" 12 | ) 13 | 14 | func init() { 15 | RegisterServer("NATS", NewNATS) 16 | } 17 | 18 | var natsLast = baseID 19 | 20 | func NewNATS(t testing.TB, pool *dockertest.Pool) Server { 21 | c, err := pool.RunWithOptions(&dockertest.RunOptions{ 22 | Name: fmt.Sprintf("psrpc-nats-%d", atomic.AddUint32(&natsLast, 1)), 23 | Repository: "nats", Tag: "latest", 24 | }) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | t.Cleanup(func() { 29 | _ = pool.Purge(c) 30 | }) 31 | addr := c.GetHostPort("4222/tcp") 32 | waitTCPPort(t, pool, addr) 33 | 34 | t.Log("NATS running on", addr) 35 | 36 | s := &natsServer{addr: "nats://" + addr} 37 | 38 | err = pool.Retry(func() error { 39 | nc, err := s.connect() 40 | if err != nil { 41 | return err 42 | } 43 | nc.Close() 44 | return nil 45 | }) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | return s 50 | } 51 | 52 | type natsServer struct { 53 | addr string 54 | } 55 | 56 | func (s *natsServer) connect() (*nats.Conn, error) { 57 | nc, err := nats.Connect(s.addr) 58 | if err != nil { 59 | return nil, err 60 | } 61 | if err := nc.Flush(); err != nil { 62 | nc.Close() 63 | return nil, err 64 | } 65 | return nc, nil 66 | } 67 | 68 | func (s *natsServer) Connect(t testing.TB) bus.MessageBus { 69 | nc, err := s.connect() 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | return bus.NewNatsMessageBus(nc) 74 | } 75 | -------------------------------------------------------------------------------- /internal/bus/bustest/redis.go: -------------------------------------------------------------------------------- 1 | package bustest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | 10 | "github.com/ory/dockertest/v3" 11 | "github.com/redis/go-redis/v9" 12 | 13 | "github.com/livekit/psrpc/internal/bus" 14 | ) 15 | 16 | func init() { 17 | RegisterServer("Redis", NewRedis) 18 | } 19 | 20 | var redisLast = baseID 21 | 22 | func NewRedis(t testing.TB, pool *dockertest.Pool) Server { 23 | c, err := pool.RunWithOptions(&dockertest.RunOptions{ 24 | Name: fmt.Sprintf("psrpc-redis-%d", atomic.AddUint32(&redisLast, 1)), 25 | Repository: "redis", Tag: "latest", 26 | }) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | t.Cleanup(func() { 31 | _ = pool.Purge(c) 32 | }) 33 | addr := c.GetHostPort("6379/tcp") 34 | waitTCPPort(t, pool, addr) 35 | 36 | t.Log("Redis running on", addr) 37 | 38 | s := &redisServer{addr: addr} 39 | 40 | err = pool.Retry(func() error { 41 | rc, err := s.connect() 42 | if err != nil { 43 | return err 44 | } 45 | _ = rc.Close() 46 | return nil 47 | }) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | return s 53 | } 54 | 55 | type redisServer struct { 56 | addr string 57 | } 58 | 59 | func (s *redisServer) connect() (redis.UniversalClient, error) { 60 | rc := redis.NewUniversalClient(&redis.UniversalOptions{Addrs: []string{s.addr}}) 61 | 62 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 63 | defer cancel() 64 | 65 | if err := rc.Ping(ctx).Err(); err != nil { 66 | _ = rc.Close() 67 | return nil, err 68 | } 69 | 70 | return rc, nil 71 | } 72 | 73 | func (s *redisServer) Connect(t testing.TB) bus.MessageBus { 74 | rc, err := s.connect() 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | return bus.NewRedisMessageBus(rc) 79 | } 80 | -------------------------------------------------------------------------------- /internal/bus/empty.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | type EmptySubscription[MessageType any] struct{} 18 | 19 | func (s EmptySubscription[MessageType]) Channel() <-chan MessageType { 20 | return nil 21 | } 22 | 23 | func (s EmptySubscription[MessageType]) Close() error { 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/bus/helpers_test.go: -------------------------------------------------------------------------------- 1 | package bus 2 | 3 | import "google.golang.org/protobuf/proto" 4 | 5 | // These helpers are only exposed during tests. 6 | 7 | func RawRead(r Reader) ([]byte, bool) { 8 | return r.read() 9 | } 10 | 11 | func Deserialize(b []byte) (proto.Message, error) { 12 | return deserialize(b) 13 | } 14 | -------------------------------------------------------------------------------- /internal/bus/serialization.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | import ( 18 | "google.golang.org/protobuf/proto" 19 | "google.golang.org/protobuf/types/known/anypb" 20 | 21 | "github.com/livekit/psrpc/internal" 22 | ) 23 | 24 | func serialize(msg proto.Message, channel string) ([]byte, error) { 25 | value, err := proto.Marshal(msg) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return proto.Marshal(&internal.Msg{ 31 | TypeUrl: "type.googleapis.com/" + string(msg.ProtoReflect().Descriptor().FullName()), 32 | Value: value, 33 | Channel: channel, 34 | }) 35 | } 36 | 37 | func deserializeChannel(b []byte) (string, error) { 38 | c := &internal.Channel{} 39 | opt := proto.UnmarshalOptions{ 40 | DiscardUnknown: true, 41 | } 42 | err := opt.Unmarshal(b, c) 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | return c.Channel, nil 48 | } 49 | 50 | func deserialize(b []byte) (proto.Message, error) { 51 | a := &anypb.Any{} 52 | opt := proto.UnmarshalOptions{ 53 | DiscardUnknown: true, 54 | } 55 | err := opt.Unmarshal(b, a) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return a.UnmarshalNew() 61 | } 62 | 63 | func SerializePayload(m proto.Message) ([]byte, error) { 64 | return proto.Marshal(m) 65 | } 66 | 67 | func DeserializePayload[T proto.Message](buf []byte) (T, error) { 68 | var v T 69 | v = v.ProtoReflect().New().Interface().(T) 70 | return v, proto.Unmarshal(buf, v) 71 | } 72 | -------------------------------------------------------------------------------- /internal/bus/serialization_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | "github.com/stretchr/testify/require" 22 | "google.golang.org/protobuf/proto" 23 | 24 | "github.com/livekit/psrpc/internal" 25 | ) 26 | 27 | func TestSerialization(t *testing.T) { 28 | msg := &internal.Request{ 29 | RequestId: "reid", 30 | ClientId: "clid", 31 | SentAt: time.Now().UnixNano(), 32 | Multi: true, 33 | } 34 | 35 | b, err := serialize(msg, "channel") 36 | require.NoError(t, err) 37 | 38 | m, err := deserialize(b) 39 | require.NoError(t, err) 40 | 41 | channel, err := deserializeChannel(b) 42 | require.NoError(t, err) 43 | 44 | require.Equal(t, m.(*internal.Request).RequestId, msg.RequestId) 45 | require.Equal(t, m.(*internal.Request).ClientId, msg.ClientId) 46 | require.Equal(t, m.(*internal.Request).SentAt, msg.SentAt) 47 | require.Equal(t, m.(*internal.Request).Multi, msg.Multi) 48 | require.Equal(t, "channel", channel) 49 | } 50 | 51 | func TestRawSerialization(t *testing.T) { 52 | msg := &internal.Request{ 53 | RequestId: "reid", 54 | ClientId: "clid", 55 | SentAt: time.Now().UnixNano(), 56 | Multi: true, 57 | } 58 | 59 | b, err := SerializePayload(msg) 60 | require.NoError(t, err) 61 | 62 | msg0, err := DeserializePayload[*internal.Request](b) 63 | require.NoError(t, err) 64 | require.True(t, proto.Equal(msg, msg0), "expected deserialized payload to match source") 65 | 66 | msg1, err := DeserializePayload[*internal.Request](b) 67 | require.NoError(t, err) 68 | require.True(t, proto.Equal(msg, msg1), "expected deserialized payload to match source") 69 | } 70 | -------------------------------------------------------------------------------- /internal/bus/subscription.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bus 16 | 17 | import ( 18 | "google.golang.org/protobuf/proto" 19 | ) 20 | 21 | type Subscription[MessageType proto.Message] interface { 22 | Channel() <-chan MessageType 23 | Close() error 24 | } 25 | 26 | type subscription[MessageType proto.Message] struct { 27 | Reader 28 | c <-chan MessageType 29 | } 30 | 31 | func newSubscription[MessageType proto.Message](sub Reader, size int) Subscription[MessageType] { 32 | msgChan := make(chan MessageType, size) 33 | go func() { 34 | for { 35 | b, ok := sub.read() 36 | if !ok { 37 | close(msgChan) 38 | return 39 | } 40 | 41 | p, err := deserialize(b) 42 | if err != nil { 43 | continue 44 | } 45 | msgChan <- p.(MessageType) 46 | } 47 | }() 48 | 49 | return &subscription[MessageType]{ 50 | Reader: sub, 51 | c: msgChan, 52 | } 53 | } 54 | 55 | func (s *subscription[MessageType]) Channel() <-chan MessageType { 56 | return s.c 57 | } 58 | -------------------------------------------------------------------------------- /internal/interceptors/interceptor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package interceptors 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/protobuf/proto" 21 | 22 | "github.com/livekit/psrpc" 23 | "github.com/livekit/psrpc/pkg/info" 24 | ) 25 | 26 | func ChainClientInterceptors[HandlerType any, InterceptorType ~func(psrpc.RPCInfo, HandlerType) HandlerType]( 27 | interceptors []InterceptorType, 28 | requestInfo *info.RequestInfo, 29 | handler HandlerType, 30 | ) HandlerType { 31 | for i := len(interceptors) - 1; i >= 0; i-- { 32 | handler = interceptors[i](requestInfo.RPCInfo, handler) 33 | } 34 | return handler 35 | } 36 | 37 | func ChainServerInterceptors(interceptors []psrpc.ServerRPCInterceptor) psrpc.ServerRPCInterceptor { 38 | switch n := len(interceptors); n { 39 | case 0: 40 | return nil 41 | case 1: 42 | return interceptors[0] 43 | default: 44 | return func(ctx context.Context, req proto.Message, rpcInfo psrpc.RPCInfo, handler psrpc.ServerRPCHandler) (proto.Message, error) { 45 | // the struct ensures the variables are allocated together, rather than separately, since we 46 | // know they should be garbage collected together. This saves 1 allocation and decreases 47 | // time/call by about 10% on the microbenchmark. 48 | var state struct { 49 | i int 50 | next psrpc.ServerRPCHandler 51 | } 52 | state.next = func(ctx context.Context, req proto.Message) (proto.Message, error) { 53 | if state.i == len(interceptors)-1 { 54 | return interceptors[state.i](ctx, req, rpcInfo, handler) 55 | } 56 | state.i++ 57 | return interceptors[state.i-1](ctx, req, rpcInfo, state.next) 58 | } 59 | return state.next(ctx, req) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /internal/internal.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package internal; 18 | option go_package = "github.com/livekit/psrpc/internal"; 19 | 20 | import "google/protobuf/any.proto"; 21 | 22 | message Msg { 23 | string type_url = 1; 24 | bytes value = 2; 25 | string channel = 3; 26 | } 27 | 28 | message Channel { 29 | string channel = 3; 30 | } 31 | 32 | message Request { 33 | string request_id = 1; 34 | string client_id = 2; 35 | int64 sent_at = 3; 36 | int64 expiry = 4; 37 | bool multi = 5; 38 | google.protobuf.Any request = 6; 39 | map metadata = 7; 40 | bytes raw_request = 8; 41 | } 42 | 43 | message Response { 44 | string request_id = 1; 45 | string server_id = 2; 46 | int64 sent_at = 3; 47 | google.protobuf.Any response = 4; 48 | string error = 5; 49 | string code = 6; 50 | bytes raw_response = 7; 51 | repeated google.protobuf.Any error_details = 8; 52 | } 53 | 54 | message ClaimRequest { 55 | string request_id = 1; 56 | string server_id = 2; 57 | float affinity = 3; 58 | } 59 | 60 | message ClaimResponse { 61 | string request_id = 1; 62 | string server_id = 2; 63 | } 64 | 65 | message Stream { 66 | string stream_id = 1; 67 | string request_id = 2; 68 | int64 sent_at = 3; 69 | int64 expiry = 4; 70 | oneof body { 71 | StreamOpen open = 6; 72 | StreamMessage message = 7; 73 | StreamAck ack = 8; 74 | StreamClose close = 9; 75 | } 76 | } 77 | 78 | message StreamOpen { 79 | string node_id = 1; 80 | map metadata = 7; 81 | } 82 | 83 | message StreamMessage { 84 | google.protobuf.Any message = 1; 85 | bytes raw_message = 2; 86 | } 87 | 88 | message StreamAck {} 89 | 90 | message StreamClose { 91 | string error = 1; 92 | string code = 2; 93 | } 94 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logger 16 | 17 | import "github.com/go-logr/logr" 18 | 19 | var logger = logr.Discard() 20 | 21 | func SetLogger(l logr.Logger) { 22 | logger = l 23 | } 24 | 25 | func Error(err error, msg string, values ...interface{}) { 26 | logger.Error(err, msg, values...) 27 | } 28 | -------------------------------------------------------------------------------- /internal/stream/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "github.com/livekit/psrpc" 19 | ) 20 | 21 | func getStreamOpts(options psrpc.StreamOpts, opts ...psrpc.StreamOption) psrpc.StreamOpts { 22 | o := &psrpc.StreamOpts{ 23 | Timeout: options.Timeout, 24 | } 25 | for _, opt := range opts { 26 | opt(o) 27 | } 28 | return *o 29 | } 30 | -------------------------------------------------------------------------------- /internal/stream/stream_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stream 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | "go.uber.org/atomic" 24 | "google.golang.org/protobuf/proto" 25 | 26 | "github.com/livekit/psrpc" 27 | "github.com/livekit/psrpc/internal" 28 | "github.com/livekit/psrpc/pkg/info" 29 | "github.com/livekit/psrpc/pkg/rand" 30 | ) 31 | 32 | func TestClosePendingSend(t *testing.T) { 33 | s := NewStream[*internal.Request, *internal.Response]( 34 | context.Background(), 35 | &info.RequestInfo{}, 36 | rand.NewStreamID(), 37 | psrpc.DefaultClientTimeout, 38 | &testStreamAdapter{}, 39 | nil, 40 | make(chan *internal.Response), 41 | make(map[string]chan struct{}), 42 | ) 43 | 44 | var err atomic.Value 45 | ready := make(chan struct{}) 46 | 47 | var wg sync.WaitGroup 48 | wg.Add(2) 49 | go func() { 50 | close(ready) 51 | err.Store(s.Send(&internal.Request{})) 52 | wg.Done() 53 | }() 54 | 55 | var handleErr error 56 | go func() { 57 | <-ready 58 | handleErr = s.HandleStream(&internal.Stream{ 59 | Body: &internal.Stream_Close{ 60 | Close: &internal.StreamClose{ 61 | Error: psrpc.ErrStreamClosed.Error(), 62 | Code: string(psrpc.ErrStreamClosed.Code()), 63 | }, 64 | }, 65 | }) 66 | wg.Done() 67 | }() 68 | wg.Wait() 69 | 70 | require.NoError(t, handleErr) 71 | require.EqualValues(t, psrpc.ErrStreamClosed, err.Load()) 72 | } 73 | 74 | type testStreamAdapter struct { 75 | sendCalls atomic.Int32 76 | closeCalls atomic.Int32 77 | } 78 | 79 | func (a *testStreamAdapter) Send(ctx context.Context, msg *internal.Stream) error { 80 | a.sendCalls.Inc() 81 | return nil 82 | } 83 | 84 | func (a *testStreamAdapter) Close(streamID string) { 85 | a.closeCalls.Inc() 86 | } 87 | 88 | func TestClosePanic(t *testing.T) { 89 | for i := 0; i < 1000; i++ { 90 | s := NewStream[*internal.Request, *internal.Response]( 91 | context.Background(), 92 | &info.RequestInfo{}, 93 | rand.NewStreamID(), 94 | psrpc.DefaultClientTimeout, 95 | &testStreamAdapter{}, 96 | nil, 97 | make(chan *internal.Response, 1), 98 | make(map[string]chan struct{}), 99 | ) 100 | 101 | ready := make(chan struct{}) 102 | var wg sync.WaitGroup 103 | wg.Add(2) 104 | 105 | go func() { 106 | close(ready) 107 | s.Close(nil) 108 | wg.Done() 109 | }() 110 | 111 | go func() { 112 | b, _ := proto.Marshal(&internal.Response{}) 113 | 114 | <-ready 115 | s.HandleStream(&internal.Stream{ 116 | Body: &internal.Stream_Message{ 117 | Message: &internal.StreamMessage{ 118 | RawMessage: b, 119 | }, 120 | }, 121 | }) 122 | wg.Done() 123 | }() 124 | 125 | wg.Wait() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /internal/test/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.psrpc.go 2 | -------------------------------------------------------------------------------- /internal/test/empty_service/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package empty_service 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/empty_service/empty_service.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: empty_service.proto 20 | 21 | package empty_service 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | reflect "reflect" 27 | unsafe "unsafe" 28 | ) 29 | 30 | const ( 31 | // Verify that this generated code is sufficiently up-to-date. 32 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 33 | // Verify that runtime/protoimpl is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 35 | ) 36 | 37 | var File_empty_service_proto protoreflect.FileDescriptor 38 | 39 | var file_empty_service_proto_rawDesc = string([]byte{ 40 | 0x0a, 0x13, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 41 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 42 | 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x65, 0x6d, 0x70, 0x74, 0x79, 43 | 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x32, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 44 | 0x42, 0x10, 0x5a, 0x0e, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 45 | 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 46 | }) 47 | 48 | var file_empty_service_proto_goTypes = []any{} 49 | var file_empty_service_proto_depIdxs = []int32{ 50 | 0, // [0:0] is the sub-list for method output_type 51 | 0, // [0:0] is the sub-list for method input_type 52 | 0, // [0:0] is the sub-list for extension type_name 53 | 0, // [0:0] is the sub-list for extension extendee 54 | 0, // [0:0] is the sub-list for field type_name 55 | } 56 | 57 | func init() { file_empty_service_proto_init() } 58 | func file_empty_service_proto_init() { 59 | if File_empty_service_proto != nil { 60 | return 61 | } 62 | type x struct{} 63 | out := protoimpl.TypeBuilder{ 64 | File: protoimpl.DescBuilder{ 65 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 66 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_empty_service_proto_rawDesc), len(file_empty_service_proto_rawDesc)), 67 | NumEnums: 0, 68 | NumMessages: 0, 69 | NumExtensions: 0, 70 | NumServices: 1, 71 | }, 72 | GoTypes: file_empty_service_proto_goTypes, 73 | DependencyIndexes: file_empty_service_proto_depIdxs, 74 | }.Build() 75 | File_empty_service_proto = out.File 76 | file_empty_service_proto_goTypes = nil 77 | file_empty_service_proto_depIdxs = nil 78 | } 79 | -------------------------------------------------------------------------------- /internal/test/empty_service/empty_service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.emptyservice; 18 | 19 | // Test to make sure that a service with no methods doesn't break. 20 | option go_package = "/empty_service"; 21 | 22 | service Empty{} 23 | -------------------------------------------------------------------------------- /internal/test/empty_service/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package empty_service 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. empty_service.proto 17 | -------------------------------------------------------------------------------- /internal/test/errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package test 16 | 17 | import ( 18 | "errors" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/twitchtv/twirp" 23 | 24 | "github.com/livekit/psrpc" 25 | ) 26 | 27 | func TestAs(t *testing.T) { 28 | err := psrpc.NewErrorf(psrpc.NotFound, "test error") 29 | 30 | var twErr twirp.Error 31 | var psrpcErr psrpc.Error 32 | 33 | ret := errors.As(err, &twErr) 34 | assert.True(t, ret) 35 | assert.Equal(t, twirp.NotFound, twErr.Code()) 36 | 37 | ret = errors.As(err, &psrpcErr) 38 | assert.True(t, ret) 39 | assert.Equal(t, err, psrpcErr) 40 | } 41 | -------------------------------------------------------------------------------- /internal/test/google_protobuf_imports/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package google_protobuf_imports 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/google_protobuf_imports/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package google_protobuf_imports 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. service.proto 17 | -------------------------------------------------------------------------------- /internal/test/google_protobuf_imports/service.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: service.proto 20 | 21 | package google_protobuf_imports 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | emptypb "google.golang.org/protobuf/types/known/emptypb" 27 | wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" 28 | reflect "reflect" 29 | unsafe "unsafe" 30 | ) 31 | 32 | const ( 33 | // Verify that this generated code is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 35 | // Verify that runtime/protoimpl is sufficiently up-to-date. 36 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 37 | ) 38 | 39 | var File_service_proto protoreflect.FileDescriptor 40 | 41 | var file_service_proto_rawDesc = string([]byte{ 42 | 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 43 | 0x1d, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 44 | 0x74, 0x65, 0x73, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x5f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 45 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 46 | 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 47 | 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 48 | 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x43, 0x0a, 0x03, 0x53, 49 | 0x76, 0x63, 0x12, 0x3c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 50 | 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 51 | 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 52 | 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 53 | 0x42, 0x1a, 0x5a, 0x18, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 54 | 0x6f, 0x62, 0x75, 0x66, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 55 | 0x6f, 0x74, 0x6f, 0x33, 56 | }) 57 | 58 | var file_service_proto_goTypes = []any{ 59 | (*wrapperspb.StringValue)(nil), // 0: google.protobuf.StringValue 60 | (*emptypb.Empty)(nil), // 1: google.protobuf.Empty 61 | } 62 | var file_service_proto_depIdxs = []int32{ 63 | 0, // 0: psrpc.internal.test.use_empty.Svc.Send:input_type -> google.protobuf.StringValue 64 | 1, // 1: psrpc.internal.test.use_empty.Svc.Send:output_type -> google.protobuf.Empty 65 | 1, // [1:2] is the sub-list for method output_type 66 | 0, // [0:1] is the sub-list for method input_type 67 | 0, // [0:0] is the sub-list for extension type_name 68 | 0, // [0:0] is the sub-list for extension extendee 69 | 0, // [0:0] is the sub-list for field type_name 70 | } 71 | 72 | func init() { file_service_proto_init() } 73 | func file_service_proto_init() { 74 | if File_service_proto != nil { 75 | return 76 | } 77 | type x struct{} 78 | out := protoimpl.TypeBuilder{ 79 | File: protoimpl.DescBuilder{ 80 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 81 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_service_proto_rawDesc), len(file_service_proto_rawDesc)), 82 | NumEnums: 0, 83 | NumMessages: 0, 84 | NumExtensions: 0, 85 | NumServices: 1, 86 | }, 87 | GoTypes: file_service_proto_goTypes, 88 | DependencyIndexes: file_service_proto_depIdxs, 89 | }.Build() 90 | File_service_proto = out.File 91 | file_service_proto_goTypes = nil 92 | file_service_proto_depIdxs = nil 93 | } 94 | -------------------------------------------------------------------------------- /internal/test/google_protobuf_imports/service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.use_empty; 18 | option go_package = "/google_protobuf_imports"; 19 | 20 | import "google/protobuf/empty.proto"; 21 | import "google/protobuf/wrappers.proto"; 22 | 23 | service Svc { 24 | rpc Send(google.protobuf.StringValue) returns(google.protobuf.Empty); 25 | } 26 | -------------------------------------------------------------------------------- /internal/test/importable/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importable 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/importable/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importable 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. importable.proto 17 | -------------------------------------------------------------------------------- /internal/test/importable/importable.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Test to make sure that importing other packages doesnt break 18 | package psrpc.internal.test.importable; 19 | option go_package = "github.com/livekit/psrpc/internal/test/importable"; 20 | 21 | message Msg {} 22 | 23 | service Svc { 24 | rpc Send(Msg) returns(Msg); 25 | } 26 | -------------------------------------------------------------------------------- /internal/test/importer/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importer 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/importer/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importer 15 | 16 | //go:generate protoc -I=. -I=../importable --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. importer.proto 17 | -------------------------------------------------------------------------------- /internal/test/importer/importer.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: importer.proto 20 | 21 | // Test to make sure that importing other packages doesnt break 22 | 23 | package importer 24 | 25 | import ( 26 | importable "github.com/livekit/psrpc/internal/test/importable" 27 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 28 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 29 | reflect "reflect" 30 | unsafe "unsafe" 31 | ) 32 | 33 | const ( 34 | // Verify that this generated code is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 36 | // Verify that runtime/protoimpl is sufficiently up-to-date. 37 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 38 | ) 39 | 40 | var File_importer_proto protoreflect.FileDescriptor 41 | 42 | var file_importer_proto_rawDesc = string([]byte{ 43 | 0x0a, 0x0e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 44 | 0x12, 0x1c, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 45 | 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x1a, 0x10, 46 | 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 47 | 0x32, 0x58, 0x0a, 0x04, 0x53, 0x76, 0x63, 0x32, 0x12, 0x50, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 48 | 0x12, 0x23, 0x2e, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 49 | 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x62, 0x6c, 50 | 0x65, 0x2e, 0x4d, 0x73, 0x67, 0x1a, 0x23, 0x2e, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 51 | 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 52 | 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 53 | 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 54 | 0x2f, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 55 | 0x74, 0x65, 0x73, 0x74, 0x2f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 56 | 0x72, 0x6f, 0x74, 0x6f, 0x33, 57 | }) 58 | 59 | var file_importer_proto_goTypes = []any{ 60 | (*importable.Msg)(nil), // 0: psrpc.internal.test.importable.Msg 61 | } 62 | var file_importer_proto_depIdxs = []int32{ 63 | 0, // 0: psrpc.internal.test.importer.Svc2.Send:input_type -> psrpc.internal.test.importable.Msg 64 | 0, // 1: psrpc.internal.test.importer.Svc2.Send:output_type -> psrpc.internal.test.importable.Msg 65 | 1, // [1:2] is the sub-list for method output_type 66 | 0, // [0:1] is the sub-list for method input_type 67 | 0, // [0:0] is the sub-list for extension type_name 68 | 0, // [0:0] is the sub-list for extension extendee 69 | 0, // [0:0] is the sub-list for field type_name 70 | } 71 | 72 | func init() { file_importer_proto_init() } 73 | func file_importer_proto_init() { 74 | if File_importer_proto != nil { 75 | return 76 | } 77 | type x struct{} 78 | out := protoimpl.TypeBuilder{ 79 | File: protoimpl.DescBuilder{ 80 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 81 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_importer_proto_rawDesc), len(file_importer_proto_rawDesc)), 82 | NumEnums: 0, 83 | NumMessages: 0, 84 | NumExtensions: 0, 85 | NumServices: 1, 86 | }, 87 | GoTypes: file_importer_proto_goTypes, 88 | DependencyIndexes: file_importer_proto_depIdxs, 89 | }.Build() 90 | File_importer_proto = out.File 91 | file_importer_proto_goTypes = nil 92 | file_importer_proto_depIdxs = nil 93 | } 94 | -------------------------------------------------------------------------------- /internal/test/importer/importer.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Test to make sure that importing other packages doesnt break 18 | package psrpc.internal.test.importer; 19 | option go_package = "github.com/livekit/psrpc/internal/test/importer"; 20 | 21 | import "importable.proto"; 22 | 23 | service Svc2 { 24 | rpc Send(importable.Msg) returns(importable.Msg); 25 | } 26 | -------------------------------------------------------------------------------- /internal/test/importer_local/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importer_local 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/importer_local/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importer_local 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. importer_local_msgdef.proto 17 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. importer_local.proto 18 | -------------------------------------------------------------------------------- /internal/test/importer_local/importer_local.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: importer_local.proto 20 | 21 | package importer_local 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | reflect "reflect" 27 | unsafe "unsafe" 28 | ) 29 | 30 | const ( 31 | // Verify that this generated code is sufficiently up-to-date. 32 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 33 | // Verify that runtime/protoimpl is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 35 | ) 36 | 37 | var File_importer_local_proto protoreflect.FileDescriptor 38 | 39 | var file_importer_local_proto_rawDesc = string([]byte{ 40 | 0x0a, 0x14, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 41 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 42 | 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 43 | 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x1a, 0x1b, 0x69, 0x6d, 0x70, 0x6f, 44 | 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x67, 0x64, 0x65, 45 | 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x5f, 0x0a, 0x03, 0x53, 0x76, 0x63, 0x12, 0x58, 46 | 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x27, 0x2e, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 47 | 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 48 | 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x2e, 0x4d, 0x73, 0x67, 0x1a, 49 | 0x27, 0x2e, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 50 | 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6c, 51 | 0x6f, 0x63, 0x61, 0x6c, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 52 | 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 0x65, 0x6b, 0x69, 0x74, 0x2f, 0x70, 53 | 0x73, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 54 | 0x73, 0x74, 0x2f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 55 | 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 56 | }) 57 | 58 | var file_importer_local_proto_goTypes = []any{ 59 | (*Msg)(nil), // 0: psrpc.internal.test.importer_local.Msg 60 | } 61 | var file_importer_local_proto_depIdxs = []int32{ 62 | 0, // 0: psrpc.internal.test.importer_local.Svc.Send:input_type -> psrpc.internal.test.importer_local.Msg 63 | 0, // 1: psrpc.internal.test.importer_local.Svc.Send:output_type -> psrpc.internal.test.importer_local.Msg 64 | 1, // [1:2] is the sub-list for method output_type 65 | 0, // [0:1] is the sub-list for method input_type 66 | 0, // [0:0] is the sub-list for extension type_name 67 | 0, // [0:0] is the sub-list for extension extendee 68 | 0, // [0:0] is the sub-list for field type_name 69 | } 70 | 71 | func init() { file_importer_local_proto_init() } 72 | func file_importer_local_proto_init() { 73 | if File_importer_local_proto != nil { 74 | return 75 | } 76 | file_importer_local_msgdef_proto_init() 77 | type x struct{} 78 | out := protoimpl.TypeBuilder{ 79 | File: protoimpl.DescBuilder{ 80 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 81 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_importer_local_proto_rawDesc), len(file_importer_local_proto_rawDesc)), 82 | NumEnums: 0, 83 | NumMessages: 0, 84 | NumExtensions: 0, 85 | NumServices: 1, 86 | }, 87 | GoTypes: file_importer_local_proto_goTypes, 88 | DependencyIndexes: file_importer_local_proto_depIdxs, 89 | }.Build() 90 | File_importer_local_proto = out.File 91 | file_importer_local_proto_goTypes = nil 92 | file_importer_local_proto_depIdxs = nil 93 | } 94 | -------------------------------------------------------------------------------- /internal/test/importer_local/importer_local.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.importer_local; 18 | option go_package = "github.com/livekit/psrpc/internal/test/importer_local"; 19 | 20 | import "importer_local_msgdef.proto"; 21 | 22 | service Svc { 23 | rpc Send(Msg) returns (Msg); 24 | } 25 | -------------------------------------------------------------------------------- /internal/test/importer_local/importer_local_msgdef.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.importer_local; 18 | option go_package = "github.com/livekit/psrpc/internal/test/importer_local"; 19 | 20 | message Msg {} 21 | -------------------------------------------------------------------------------- /internal/test/importmapping/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importmapping 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/importmapping/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package importmapping 15 | 16 | //go:generate ./gen.sh 17 | -------------------------------------------------------------------------------- /internal/test/importmapping/gen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2023 LiveKit, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -euo pipefail 17 | 18 | protoc --go_out=. --psrpc_out=. \ 19 | --go_opt=paths=source_relative \ 20 | --go_opt=My/y.proto=github.com/livekit/psrpc/internal/test/importmapping/y \ 21 | --psrpc_opt=paths=source_relative \ 22 | --psrpc_opt=My/y.proto=github.com/livekit/psrpc/internal/test/importmapping/y \ 23 | y/y.proto 24 | 25 | protoc --go_out=. --psrpc_out=. \ 26 | --go_opt=paths=source_relative \ 27 | --go_opt=My/y.proto=github.com/livekit/psrpc/internal/test/importmapping/y \ 28 | --go_opt=Mx/x.proto=github.com/livekit/psrpc/internal/test/importmapping/x \ 29 | --psrpc_opt=paths=source_relative \ 30 | --psrpc_opt=My/y.proto=github.com/livekit/psrpc/internal/test/importmapping/y \ 31 | --psrpc_opt=Mx/x.proto=github.com/livekit/psrpc/internal/test/importmapping/x \ 32 | x/x.proto 33 | -------------------------------------------------------------------------------- /internal/test/importmapping/protoc_gen-x.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2023 LiveKit, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | set -euo pipefail 18 | 19 | # Pull into variable since both protoc-gen-go and protoc-gen-psrpc need the map. 20 | Y_IMPORT_MAPPING="y/y.proto=github.com/livekit/psrpc/internal/test/importmapping/y" 21 | 22 | PROTOC_GEN_GO_PARAMS="M${Y_IMPORT_MAPPING}" \ 23 | PROTOC_GEN_PSRPC_PARAMS="go_import_mapping@${Y_IMPORT_MAPPING}" \ 24 | ../../protoc_gen.sh x/x.proto 25 | -------------------------------------------------------------------------------- /internal/test/importmapping/x/x.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: x/x.proto 20 | 21 | package x 22 | 23 | import ( 24 | y "github.com/livekit/psrpc/internal/test/importmapping/y" 25 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 26 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 27 | reflect "reflect" 28 | unsafe "unsafe" 29 | ) 30 | 31 | const ( 32 | // Verify that this generated code is sufficiently up-to-date. 33 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 34 | // Verify that runtime/protoimpl is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 36 | ) 37 | 38 | var File_x_x_proto protoreflect.FileDescriptor 39 | 40 | var file_x_x_proto_rawDesc = string([]byte{ 41 | 0x0a, 0x09, 0x78, 0x2f, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x23, 0x70, 0x73, 0x72, 42 | 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 43 | 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x78, 44 | 0x1a, 0x09, 0x79, 0x2f, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x64, 0x0a, 0x04, 0x53, 45 | 0x76, 0x63, 0x31, 0x12, 0x5c, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x29, 0x2e, 0x70, 0x73, 46 | 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 47 | 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 48 | 0x79, 0x2e, 0x4d, 0x73, 0x67, 0x59, 0x1a, 0x29, 0x2e, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x69, 49 | 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x69, 0x6d, 0x70, 50 | 0x6f, 0x72, 0x74, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x79, 0x2e, 0x4d, 0x73, 0x67, 51 | 0x59, 0x42, 0x1f, 0x5a, 0x1d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 52 | 0x2f, 0x77, 0x69, 0x6c, 0x6c, 0x2f, 0x62, 0x65, 0x2f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 53 | 0x2f, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 54 | }) 55 | 56 | var file_x_x_proto_goTypes = []any{ 57 | (*y.MsgY)(nil), // 0: psrpc.internal.test.importmapping.y.MsgY 58 | } 59 | var file_x_x_proto_depIdxs = []int32{ 60 | 0, // 0: psrpc.internal.test.importmapping.x.Svc1.Send:input_type -> psrpc.internal.test.importmapping.y.MsgY 61 | 0, // 1: psrpc.internal.test.importmapping.x.Svc1.Send:output_type -> psrpc.internal.test.importmapping.y.MsgY 62 | 1, // [1:2] is the sub-list for method output_type 63 | 0, // [0:1] is the sub-list for method input_type 64 | 0, // [0:0] is the sub-list for extension type_name 65 | 0, // [0:0] is the sub-list for extension extendee 66 | 0, // [0:0] is the sub-list for field type_name 67 | } 68 | 69 | func init() { file_x_x_proto_init() } 70 | func file_x_x_proto_init() { 71 | if File_x_x_proto != nil { 72 | return 73 | } 74 | type x struct{} 75 | out := protoimpl.TypeBuilder{ 76 | File: protoimpl.DescBuilder{ 77 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 78 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_x_x_proto_rawDesc), len(file_x_x_proto_rawDesc)), 79 | NumEnums: 0, 80 | NumMessages: 0, 81 | NumExtensions: 0, 82 | NumServices: 1, 83 | }, 84 | GoTypes: file_x_x_proto_goTypes, 85 | DependencyIndexes: file_x_x_proto_depIdxs, 86 | }.Build() 87 | File_x_x_proto = out.File 88 | file_x_x_proto_goTypes = nil 89 | file_x_x_proto_depIdxs = nil 90 | } 91 | -------------------------------------------------------------------------------- /internal/test/importmapping/x/x.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.importmapping.x; 18 | option go_package = "example.com/will/be/ignored/x"; 19 | 20 | import "y/y.proto"; 21 | 22 | service Svc1 { 23 | rpc Send(y.MsgY) returns (y.MsgY); 24 | } 25 | -------------------------------------------------------------------------------- /internal/test/importmapping/y/y.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: y/y.proto 20 | 21 | package y 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | reflect "reflect" 27 | sync "sync" 28 | unsafe "unsafe" 29 | ) 30 | 31 | const ( 32 | // Verify that this generated code is sufficiently up-to-date. 33 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 34 | // Verify that runtime/protoimpl is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 36 | ) 37 | 38 | type MsgY struct { 39 | state protoimpl.MessageState `protogen:"open.v1"` 40 | unknownFields protoimpl.UnknownFields 41 | sizeCache protoimpl.SizeCache 42 | } 43 | 44 | func (x *MsgY) Reset() { 45 | *x = MsgY{} 46 | mi := &file_y_y_proto_msgTypes[0] 47 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 48 | ms.StoreMessageInfo(mi) 49 | } 50 | 51 | func (x *MsgY) String() string { 52 | return protoimpl.X.MessageStringOf(x) 53 | } 54 | 55 | func (*MsgY) ProtoMessage() {} 56 | 57 | func (x *MsgY) ProtoReflect() protoreflect.Message { 58 | mi := &file_y_y_proto_msgTypes[0] 59 | if x != nil { 60 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 61 | if ms.LoadMessageInfo() == nil { 62 | ms.StoreMessageInfo(mi) 63 | } 64 | return ms 65 | } 66 | return mi.MessageOf(x) 67 | } 68 | 69 | // Deprecated: Use MsgY.ProtoReflect.Descriptor instead. 70 | func (*MsgY) Descriptor() ([]byte, []int) { 71 | return file_y_y_proto_rawDescGZIP(), []int{0} 72 | } 73 | 74 | var File_y_y_proto protoreflect.FileDescriptor 75 | 76 | var file_y_y_proto_rawDesc = string([]byte{ 77 | 0x0a, 0x09, 0x79, 0x2f, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x23, 0x70, 0x73, 0x72, 78 | 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 79 | 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x79, 80 | 0x22, 0x06, 0x0a, 0x04, 0x4d, 0x73, 0x67, 0x59, 0x42, 0x1f, 0x5a, 0x1d, 0x65, 0x78, 0x61, 0x6d, 81 | 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x77, 0x69, 0x6c, 0x6c, 0x2f, 0x62, 0x65, 0x2f, 82 | 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x2f, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 83 | 0x33, 84 | }) 85 | 86 | var ( 87 | file_y_y_proto_rawDescOnce sync.Once 88 | file_y_y_proto_rawDescData []byte 89 | ) 90 | 91 | func file_y_y_proto_rawDescGZIP() []byte { 92 | file_y_y_proto_rawDescOnce.Do(func() { 93 | file_y_y_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_y_y_proto_rawDesc), len(file_y_y_proto_rawDesc))) 94 | }) 95 | return file_y_y_proto_rawDescData 96 | } 97 | 98 | var file_y_y_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 99 | var file_y_y_proto_goTypes = []any{ 100 | (*MsgY)(nil), // 0: psrpc.internal.test.importmapping.y.MsgY 101 | } 102 | var file_y_y_proto_depIdxs = []int32{ 103 | 0, // [0:0] is the sub-list for method output_type 104 | 0, // [0:0] is the sub-list for method input_type 105 | 0, // [0:0] is the sub-list for extension type_name 106 | 0, // [0:0] is the sub-list for extension extendee 107 | 0, // [0:0] is the sub-list for field type_name 108 | } 109 | 110 | func init() { file_y_y_proto_init() } 111 | func file_y_y_proto_init() { 112 | if File_y_y_proto != nil { 113 | return 114 | } 115 | type x struct{} 116 | out := protoimpl.TypeBuilder{ 117 | File: protoimpl.DescBuilder{ 118 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 119 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_y_y_proto_rawDesc), len(file_y_y_proto_rawDesc)), 120 | NumEnums: 0, 121 | NumMessages: 1, 122 | NumExtensions: 0, 123 | NumServices: 0, 124 | }, 125 | GoTypes: file_y_y_proto_goTypes, 126 | DependencyIndexes: file_y_y_proto_depIdxs, 127 | MessageInfos: file_y_y_proto_msgTypes, 128 | }.Build() 129 | File_y_y_proto = out.File 130 | file_y_y_proto_goTypes = nil 131 | file_y_y_proto_depIdxs = nil 132 | } 133 | -------------------------------------------------------------------------------- /internal/test/importmapping/y/y.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.importmapping.y; 18 | option go_package = "example.com/will/be/ignored/y"; 19 | 20 | message MsgY {} 21 | -------------------------------------------------------------------------------- /internal/test/multiple/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package multiple 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/multiple/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package multiple 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. multiple1.proto multiple2.proto 17 | -------------------------------------------------------------------------------- /internal/test/multiple/multiple1.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Multiple proto files in one package 18 | package psrpc.internal.test.multiple; 19 | option go_package = "/multiple"; 20 | 21 | message Msg1 {} 22 | 23 | service Svc1 { 24 | rpc Send(Msg1) returns (Msg1); 25 | } 26 | -------------------------------------------------------------------------------- /internal/test/multiple/multiple2.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Multiple proto files in one package 18 | package psrpc.internal.test.multiple; 19 | option go_package = "/multiple"; 20 | 21 | import "multiple1.proto"; 22 | 23 | message Msg2 {} 24 | 25 | service Svc2 { 26 | rpc Send(Msg2) returns (Msg2); 27 | rpc SamePackageProtoImport(Msg1) returns (Msg1); 28 | } 29 | -------------------------------------------------------------------------------- /internal/test/my_service/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package my_service 16 | 17 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. -I ../../../protoc-gen-psrpc/options -I=. my_service.proto 18 | -------------------------------------------------------------------------------- /internal/test/my_service/my_service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.test.customservice; 18 | 19 | import "options.proto"; 20 | 21 | // Test to make sure that a service with no methods doesn't break. 22 | option go_package = "/my_service"; 23 | 24 | service MyService { 25 | // A normal RPC - one request, one response. The request will be handled by the first available server 26 | rpc NormalRPC(MyRequest) returns (MyResponse); 27 | 28 | // An RPC with a server affinity function for handler selection. 29 | rpc IntensiveRPC(MyRequest) returns (MyResponse) { 30 | option (psrpc.options).type = AFFINITY; 31 | }; 32 | 33 | // A multi-rpc - a client will send one request, and receive one response each from every server 34 | rpc GetStats(MyRequest) returns (MyResponse) { 35 | option (psrpc.options).type = MULTI; 36 | }; 37 | 38 | // A streaming RPC - a client opens a stream, the first server to respond accepts it and both send and 39 | // receive messages until one side closes the stream. 40 | rpc ExchangeUpdates(MyClientMessage) returns (MyServerMessage) { 41 | option (psrpc.options).stream = true; 42 | }; 43 | 44 | // An RPC with topics - a client can send one request, and receive one response from each server in one region 45 | rpc GetRegionStats(MyRequest) returns (MyResponse) { 46 | option (psrpc.options).type = MULTI; 47 | option (psrpc.options).topics = true; 48 | } 49 | 50 | // A queue subscription - even if multiple clients are subscribed, only one will receive this update. 51 | // The request parameter (Ignored) will be ignored when generating go files. 52 | rpc ProcessUpdate(Ignored) returns (MyUpdate) { 53 | option (psrpc.options).subscription = true; 54 | }; 55 | 56 | // A subscription with topics - every client subscribed to the topic will receive every update. 57 | // The request parameter (Ignored) will be ignored when generating go files. 58 | rpc UpdateRegionState(Ignored) returns (MyUpdate) { 59 | option (psrpc.options).type = MULTI; 60 | option (psrpc.options).subscription = true; 61 | option (psrpc.options).topics = true; 62 | } 63 | } 64 | 65 | message Ignored {} 66 | message MyRequest { 67 | int32 return_error = 1; 68 | } 69 | message MyResponse {} 70 | message MyUpdate {} 71 | message MyClientMessage {} 72 | message MyServerMessage {} 73 | -------------------------------------------------------------------------------- /internal/test/no_package_name/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package no_package_name 15 | 16 | //go:generate protoc --go_out=module=github.com/livekit/psrpc/internal/test/no_package_name:. --psrpc_out=module=github.com/livekit/psrpc/internal/test/no_package_name:. no_package_name.proto 17 | -------------------------------------------------------------------------------- /internal/test/no_package_name/no_package_name.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // No package name 18 | 19 | option go_package = "github.com/livekit/psrpc/internal/test/no_package_name"; 20 | 21 | message Msg {} 22 | 23 | service Svc { 24 | rpc Send(Msg) returns(Msg); 25 | } 26 | -------------------------------------------------------------------------------- /internal/test/no_package_name_importer/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package no_package_name_importer 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/no_package_name_importer/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package no_package_name_importer 15 | 16 | //go:generate protoc --proto_path=../ --go_out=module=github.com/livekit/psrpc/internal/test:../ --psrpc_out=module=github.com/livekit/psrpc/internal/test:../ ../no_package_name_importer/no_package_name_importer.proto 17 | -------------------------------------------------------------------------------- /internal/test/no_package_name_importer/no_package_name_importer.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.36.4 18 | // protoc v4.23.4 19 | // source: no_package_name_importer/no_package_name_importer.proto 20 | 21 | package no_package_name_importer 22 | 23 | import ( 24 | no_package_name "github.com/livekit/psrpc/internal/test/no_package_name" 25 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 26 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 27 | reflect "reflect" 28 | unsafe "unsafe" 29 | ) 30 | 31 | const ( 32 | // Verify that this generated code is sufficiently up-to-date. 33 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 34 | // Verify that runtime/protoimpl is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 36 | ) 37 | 38 | var File_no_package_name_importer_no_package_name_importer_proto protoreflect.FileDescriptor 39 | 40 | var file_no_package_name_importer_no_package_name_importer_proto_rawDesc = string([]byte{ 41 | 0x0a, 0x37, 0x6e, 0x6f, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 42 | 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2f, 0x6e, 0x6f, 0x5f, 0x70, 0x61, 43 | 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 44 | 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x25, 0x6e, 0x6f, 0x5f, 0x70, 0x61, 45 | 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x2f, 0x6e, 0x6f, 0x5f, 0x70, 0x61, 46 | 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 47 | 0x32, 0x1c, 0x0a, 0x04, 0x53, 0x76, 0x63, 0x32, 0x12, 0x14, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 48 | 0x6f, 0x64, 0x12, 0x04, 0x2e, 0x4d, 0x73, 0x67, 0x1a, 0x04, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x41, 49 | 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x76, 50 | 0x65, 0x6b, 0x69, 0x74, 0x2f, 0x70, 0x73, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 51 | 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x6e, 0x6f, 0x5f, 0x70, 0x61, 0x63, 0x6b, 52 | 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 53 | 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 54 | }) 55 | 56 | var file_no_package_name_importer_no_package_name_importer_proto_goTypes = []any{ 57 | (*no_package_name.Msg)(nil), // 0: Msg 58 | } 59 | var file_no_package_name_importer_no_package_name_importer_proto_depIdxs = []int32{ 60 | 0, // 0: Svc2.Method:input_type -> Msg 61 | 0, // 1: Svc2.Method:output_type -> Msg 62 | 1, // [1:2] is the sub-list for method output_type 63 | 0, // [0:1] is the sub-list for method input_type 64 | 0, // [0:0] is the sub-list for extension type_name 65 | 0, // [0:0] is the sub-list for extension extendee 66 | 0, // [0:0] is the sub-list for field type_name 67 | } 68 | 69 | func init() { file_no_package_name_importer_no_package_name_importer_proto_init() } 70 | func file_no_package_name_importer_no_package_name_importer_proto_init() { 71 | if File_no_package_name_importer_no_package_name_importer_proto != nil { 72 | return 73 | } 74 | type x struct{} 75 | out := protoimpl.TypeBuilder{ 76 | File: protoimpl.DescBuilder{ 77 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 78 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_no_package_name_importer_no_package_name_importer_proto_rawDesc), len(file_no_package_name_importer_no_package_name_importer_proto_rawDesc)), 79 | NumEnums: 0, 80 | NumMessages: 0, 81 | NumExtensions: 0, 82 | NumServices: 1, 83 | }, 84 | GoTypes: file_no_package_name_importer_no_package_name_importer_proto_goTypes, 85 | DependencyIndexes: file_no_package_name_importer_no_package_name_importer_proto_depIdxs, 86 | }.Build() 87 | File_no_package_name_importer_no_package_name_importer_proto = out.File 88 | file_no_package_name_importer_no_package_name_importer_proto_goTypes = nil 89 | file_no_package_name_importer_no_package_name_importer_proto_depIdxs = nil 90 | } 91 | -------------------------------------------------------------------------------- /internal/test/no_package_name_importer/no_package_name_importer.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Import another proto file that doesn't have a package name 18 | 19 | import "no_package_name/no_package_name.proto"; // import is relative to protoc's option --proto_path 20 | 21 | option go_package = "github.com/livekit/psrpc/internal/test/no_package_name_importer"; 22 | 23 | service Svc2 { 24 | rpc Method(Msg) returns (Msg); 25 | } 26 | -------------------------------------------------------------------------------- /internal/test/service_method_same_name/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package service_method_same_name 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/service_method_same_name/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package service_method_same_name 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. service_method_same_name.proto 17 | -------------------------------------------------------------------------------- /internal/test/service_method_same_name/service_method_same_name.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Test to make sure that a method with the same name as its service doesn't break. 18 | option go_package = "/service_method_same_name"; 19 | 20 | message Msg {} 21 | 22 | service Echo { 23 | rpc Echo(Msg) returns (Msg) {} 24 | } 25 | -------------------------------------------------------------------------------- /internal/test/snake_case_names/compile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package snake_case_names 15 | 16 | import "testing" 17 | 18 | func TestCompilation(t *testing.T) { 19 | // Test passes if this package compiles 20 | } 21 | -------------------------------------------------------------------------------- /internal/test/snake_case_names/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package snake_case_names 15 | 16 | //go:generate protoc --go_out=paths=source_relative:. --psrpc_out=paths=source_relative:. snake_case_names.proto 17 | -------------------------------------------------------------------------------- /internal/test/snake_case_names/snake_case_names.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | // Test that protoc-gen-psrpc follows the same behavior as protoc-gen-go 18 | // for converting RPCs and message names from snake case to camel case. 19 | package psrpc.internal.test.snake_case_names; 20 | option go_package = "/;snake_case_names"; 21 | 22 | message MakeHatArgs_v1 { 23 | message Hat_v1 { 24 | int32 size = 1; 25 | string color = 2; 26 | string name = 3; 27 | } 28 | 29 | message Size_v1 { 30 | int32 inches = 1; 31 | } 32 | } 33 | 34 | // A Haberdasher makes hats for clients. 35 | service Haberdasher_v1 { 36 | rpc MakeHat_v1 (MakeHatArgs_v1.Size_v1) returns (MakeHatArgs_v1.Hat_v1); 37 | } 38 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "github.com/go-logr/logr" 19 | 20 | "github.com/livekit/psrpc/internal/logger" 21 | ) 22 | 23 | func SetLogger(l logr.Logger) { 24 | logger.SetLogger(l) 25 | } 26 | -------------------------------------------------------------------------------- /magefile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build mage 16 | // +build mage 17 | 18 | package main 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "os" 24 | "os/exec" 25 | 26 | "github.com/livekit/mageutil" 27 | ) 28 | 29 | var Default = Test 30 | 31 | func Install() error { 32 | return mageutil.Run(context.Background(), "go install ./protoc-gen-psrpc") 33 | } 34 | 35 | func Proto() error { 36 | fmt.Println("generating protobuf") 37 | 38 | protoc, err := mageutil.GetToolPath("protoc") 39 | if err != nil { 40 | return err 41 | } 42 | protocGoPath, err := mageutil.GetToolPath("protoc-gen-go") 43 | if err != nil { 44 | return err 45 | } 46 | 47 | protos := []struct { 48 | importPath, outputPath, filename string 49 | }{ 50 | {"./internal", "internal", "internal.proto"}, 51 | {"./protoc-gen-psrpc/options", "protoc-gen-psrpc/options", "options.proto"}, 52 | {"./testutils", "testutils", "testutils.proto"}, 53 | } 54 | for _, p := range protos { 55 | cmd := exec.Command(protoc, 56 | "--go_out", p.outputPath, 57 | "--go_opt=paths=source_relative", 58 | "--plugin=go="+protocGoPath, 59 | "-I="+p.importPath, 60 | p.filename, 61 | ) 62 | mageutil.ConnectStd(cmd) 63 | if err = cmd.Run(); err != nil { 64 | return err 65 | } 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func Generate() error { 72 | ctx := context.Background() 73 | 74 | err := mageutil.Run(ctx, "go install ./protoc-gen-psrpc") 75 | if err != nil { 76 | return err 77 | } 78 | 79 | base := "./internal/test" 80 | dirs, err := os.ReadDir(base) 81 | if err != nil { 82 | return err 83 | } 84 | for _, dir := range dirs { 85 | if dir.IsDir() { 86 | err = mageutil.RunDir(ctx, fmt.Sprintf("%s/%s", base, dir.Name()), "go generate .") 87 | if err != nil { 88 | return err 89 | } 90 | } 91 | } 92 | return nil 93 | } 94 | 95 | func Test() error { 96 | return mageutil.Run(context.Background(), "go test -count=1 -v . ./internal/test") 97 | } 98 | 99 | func TestAll() error { 100 | if err := Generate(); err != nil { 101 | return err 102 | } 103 | return mageutil.Run(context.Background(), "go test -count=1 ./...") 104 | } 105 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | 21 | "github.com/frostbyte73/core" 22 | 23 | "github.com/livekit/psrpc" 24 | "github.com/livekit/psrpc/internal" 25 | "github.com/livekit/psrpc/internal/bus" 26 | "github.com/livekit/psrpc/pkg/info" 27 | ) 28 | 29 | type RPCClient struct { 30 | *info.ServiceDefinition 31 | psrpc.ClientOpts 32 | 33 | bus bus.MessageBus 34 | 35 | mu sync.RWMutex 36 | claimRequests map[string]chan *internal.ClaimRequest 37 | responseChannels map[string]chan *internal.Response 38 | streamChannels map[string]chan *internal.Stream 39 | closed core.Fuse 40 | } 41 | 42 | func NewRPCClientWithStreams( 43 | sd *info.ServiceDefinition, 44 | b bus.MessageBus, 45 | opts ...psrpc.ClientOption, 46 | ) (*RPCClient, error) { 47 | return NewRPCClient(sd, b, append(opts, withStreams())...) 48 | } 49 | 50 | func NewRPCClient( 51 | sd *info.ServiceDefinition, 52 | b bus.MessageBus, 53 | opts ...psrpc.ClientOption, 54 | ) (*RPCClient, error) { 55 | c := &RPCClient{ 56 | ServiceDefinition: sd, 57 | ClientOpts: getClientOpts(opts...), 58 | bus: b, 59 | claimRequests: make(map[string]chan *internal.ClaimRequest), 60 | responseChannels: make(map[string]chan *internal.Response), 61 | streamChannels: make(map[string]chan *internal.Stream), 62 | } 63 | if c.ClientID != "" { 64 | c.ID = c.ClientID 65 | } 66 | 67 | ctx := context.Background() 68 | responses, err := bus.Subscribe[*internal.Response]( 69 | ctx, c.bus, info.GetResponseChannel(c.Name, c.ID), c.ChannelSize, 70 | ) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | claims, err := bus.Subscribe[*internal.ClaimRequest]( 76 | ctx, c.bus, info.GetClaimRequestChannel(c.Name, c.ID), c.ChannelSize, 77 | ) 78 | if err != nil { 79 | _ = responses.Close() 80 | return nil, err 81 | } 82 | 83 | var streams bus.Subscription[*internal.Stream] 84 | if c.EnableStreams { 85 | streams, err = bus.Subscribe[*internal.Stream]( 86 | ctx, c.bus, info.GetStreamChannel(c.Name, c.ID), c.ChannelSize, 87 | ) 88 | if err != nil { 89 | _ = responses.Close() 90 | _ = claims.Close() 91 | return nil, err 92 | } 93 | } else { 94 | streams = bus.EmptySubscription[*internal.Stream]{} 95 | } 96 | 97 | go func() { 98 | closed := c.closed.Watch() 99 | for { 100 | select { 101 | case <-closed: 102 | _ = claims.Close() 103 | _ = responses.Close() 104 | _ = streams.Close() 105 | return 106 | 107 | case claim := <-claims.Channel(): 108 | if claim == nil { 109 | c.Close() 110 | continue 111 | } 112 | c.mu.RLock() 113 | claimChan, ok := c.claimRequests[claim.RequestId] 114 | c.mu.RUnlock() 115 | if ok { 116 | claimChan <- claim 117 | } 118 | 119 | case res := <-responses.Channel(): 120 | if res == nil { 121 | c.Close() 122 | continue 123 | } 124 | c.mu.RLock() 125 | resChan, ok := c.responseChannels[res.RequestId] 126 | c.mu.RUnlock() 127 | if ok { 128 | resChan <- res 129 | } 130 | 131 | case msg := <-streams.Channel(): 132 | if msg == nil { 133 | c.Close() 134 | continue 135 | } 136 | c.mu.RLock() 137 | streamChan, ok := c.streamChannels[msg.StreamId] 138 | c.mu.RUnlock() 139 | if ok { 140 | streamChan <- msg 141 | } 142 | } 143 | } 144 | }() 145 | 146 | return c, nil 147 | } 148 | 149 | func (c *RPCClient) Close() { 150 | c.closed.Break() 151 | } 152 | -------------------------------------------------------------------------------- /pkg/client/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/require" 23 | 24 | "github.com/livekit/psrpc" 25 | "github.com/livekit/psrpc/internal" 26 | ) 27 | 28 | func TestAffinity(t *testing.T) { 29 | testAffinity(t, psrpc.SelectionOpts{ 30 | AcceptFirstAvailable: true, 31 | }, "1") 32 | 33 | testAffinity(t, psrpc.SelectionOpts{ 34 | AcceptFirstAvailable: true, 35 | MinimumAffinity: 0.5, 36 | }, "2") 37 | 38 | testAffinity(t, psrpc.SelectionOpts{ 39 | ShortCircuitTimeout: time.Millisecond * 150, 40 | }, "2") 41 | 42 | testAffinity(t, psrpc.SelectionOpts{ 43 | MinimumAffinity: 0.4, 44 | AffinityTimeout: 0, 45 | ShortCircuitTimeout: time.Millisecond * 250, 46 | }, "3") 47 | 48 | testAffinity(t, psrpc.SelectionOpts{ 49 | MinimumAffinity: 0.3, 50 | AffinityTimeout: time.Millisecond * 250, 51 | ShortCircuitTimeout: time.Millisecond * 200, 52 | }, "2") 53 | 54 | testAffinity(t, psrpc.SelectionOpts{ 55 | AffinityTimeout: time.Millisecond * 600, 56 | }, "5") 57 | } 58 | 59 | func testAffinity(t *testing.T, opts psrpc.SelectionOpts, expectedID string) { 60 | c := make(chan *internal.ClaimRequest, 100) 61 | go func() { 62 | c <- &internal.ClaimRequest{ 63 | RequestId: "1", 64 | ServerId: "1", 65 | Affinity: 0.1, 66 | } 67 | time.Sleep(time.Millisecond * 100) 68 | c <- &internal.ClaimRequest{ 69 | RequestId: "1", 70 | ServerId: "2", 71 | Affinity: 0.5, 72 | } 73 | time.Sleep(time.Millisecond * 200) 74 | c <- &internal.ClaimRequest{ 75 | RequestId: "1", 76 | ServerId: "3", 77 | Affinity: 0.7, 78 | } 79 | c <- &internal.ClaimRequest{ 80 | RequestId: "1", 81 | ServerId: "4", 82 | Affinity: 0.1, 83 | } 84 | time.Sleep(time.Millisecond * 200) 85 | c <- &internal.ClaimRequest{ 86 | RequestId: "1", 87 | ServerId: "5", 88 | Affinity: 0.9, 89 | } 90 | }() 91 | serverID, err := selectServer(context.Background(), c, nil, opts) 92 | require.NoError(t, err) 93 | require.Equal(t, expectedID, serverID) 94 | } 95 | -------------------------------------------------------------------------------- /pkg/client/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | 22 | "github.com/livekit/psrpc" 23 | "github.com/livekit/psrpc/internal/bus" 24 | "github.com/livekit/psrpc/pkg/info" 25 | ) 26 | 27 | func withStreams() psrpc.ClientOption { 28 | return func(o *psrpc.ClientOpts) { 29 | o.EnableStreams = true 30 | } 31 | } 32 | 33 | func getClientOpts(opts ...psrpc.ClientOption) psrpc.ClientOpts { 34 | o := &psrpc.ClientOpts{ 35 | SelectionTimeout: psrpc.DefaultAffinityTimeout, 36 | ChannelSize: bus.DefaultChannelSize, 37 | } 38 | for _, opt := range opts { 39 | opt(o) 40 | } 41 | return *o 42 | } 43 | 44 | func getRequestOpts(ctx context.Context, i *info.RequestInfo, options psrpc.ClientOpts, opts ...psrpc.RequestOption) psrpc.RequestOpts { 45 | o := &psrpc.RequestOpts{ 46 | Timeout: options.Timeout, 47 | } 48 | if deadline, ok := ctx.Deadline(); ok { 49 | if dt := time.Until(deadline); o.Timeout == 0 || o.Timeout > dt { 50 | o.Timeout = dt 51 | } 52 | } 53 | if o.Timeout == 0 { 54 | o.Timeout = psrpc.DefaultClientTimeout 55 | } 56 | if i.AffinityEnabled { 57 | o.SelectionOpts = psrpc.SelectionOpts{ 58 | AffinityTimeout: options.SelectionTimeout, 59 | ShortCircuitTimeout: psrpc.DefaultAffinityShortCircuit, 60 | } 61 | } else { 62 | o.SelectionOpts = psrpc.SelectionOpts{ 63 | AffinityTimeout: options.SelectionTimeout, 64 | AcceptFirstAvailable: true, 65 | } 66 | } 67 | 68 | for _, opt := range opts { 69 | opt(o) 70 | } 71 | 72 | return *o 73 | } 74 | 75 | func getRequestInterceptors[T psrpc.RequestInterceptor](base []T, as []any) []T { 76 | if as == nil { 77 | return base 78 | } 79 | 80 | c := make([]T, len(base), len(base)+len(as)) 81 | copy(c, base) 82 | for _, a := range as { 83 | i, ok := a.(T) 84 | if !ok { 85 | var ex T 86 | panic(fmt.Sprintf("cannot use %T as %T in psrpc request", a, ex)) 87 | } 88 | c = append(c, i) 89 | } 90 | return c 91 | } 92 | -------------------------------------------------------------------------------- /pkg/client/subscription.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/protobuf/proto" 21 | 22 | "github.com/livekit/psrpc" 23 | "github.com/livekit/psrpc/internal/bus" 24 | ) 25 | 26 | func Join[ResponseType proto.Message]( 27 | ctx context.Context, 28 | c *RPCClient, 29 | rpc string, 30 | topic []string, 31 | ) (bus.Subscription[ResponseType], error) { 32 | if c.closed.IsBroken() { 33 | return nil, psrpc.ErrClientClosed 34 | } 35 | 36 | i := c.GetInfo(rpc, topic) 37 | sub, err := bus.Subscribe[ResponseType](ctx, c.bus, i.GetRPCChannel(), c.ChannelSize) 38 | if err != nil { 39 | return nil, psrpc.NewError(psrpc.Internal, err) 40 | } 41 | return sub, nil 42 | } 43 | 44 | func JoinQueue[ResponseType proto.Message]( 45 | ctx context.Context, 46 | c *RPCClient, 47 | rpc string, 48 | topic []string, 49 | ) (bus.Subscription[ResponseType], error) { 50 | if c.closed.IsBroken() { 51 | return nil, psrpc.ErrClientClosed 52 | } 53 | 54 | i := c.GetInfo(rpc, topic) 55 | sub, err := bus.SubscribeQueue[ResponseType](ctx, c.bus, i.GetRPCChannel(), c.ChannelSize) 56 | if err != nil { 57 | return nil, psrpc.NewError(psrpc.Internal, err) 58 | } 59 | return sub, nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/info/channels_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package info 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | 22 | "github.com/livekit/psrpc" 23 | ) 24 | 25 | func TestChannelFormatters(t *testing.T) { 26 | i := &RequestInfo{ 27 | RPCInfo: psrpc.RPCInfo{ 28 | Service: "foo", 29 | Method: "bar", 30 | Topic: nil, 31 | }, 32 | } 33 | 34 | require.Equal(t, "foo|bar|RES", GetResponseChannel("foo", "bar").Legacy) 35 | require.Equal(t, "CLI.foo.bar.RES", GetResponseChannel("foo", "bar").Server) 36 | require.Equal(t, "foo|bar|CLAIM", GetClaimRequestChannel("foo", "bar").Legacy) 37 | require.Equal(t, "CLI.foo.bar.CLAIM", GetClaimRequestChannel("foo", "bar").Server) 38 | require.Equal(t, "foo|bar|STR", GetStreamChannel("foo", "bar").Legacy) 39 | require.Equal(t, "CLI.foo.bar.STR", GetStreamChannel("foo", "bar").Server) 40 | 41 | require.Equal(t, "foo|bar|REQ", i.GetRPCChannel().Legacy) 42 | require.Equal(t, "SRV.foo", i.GetRPCChannel().Server) 43 | require.Equal(t, "bar.REQ", i.GetRPCChannel().Local) 44 | require.Equal(t, "foo|bar|RCLAIM", i.GetClaimResponseChannel().Legacy) 45 | require.Equal(t, "SRV.foo", i.GetClaimResponseChannel().Server) 46 | require.Equal(t, "bar.RCLAIM", i.GetClaimResponseChannel().Local) 47 | require.Equal(t, "foo|bar|STR", i.GetStreamServerChannel().Legacy) 48 | require.Equal(t, "SRV.foo", i.GetStreamServerChannel().Server) 49 | require.Equal(t, "bar.STR", i.GetStreamServerChannel().Local) 50 | 51 | i.Topic = []string{"a", "b", "c"} 52 | 53 | require.Equal(t, "foo|bar|a|b|c|REQ", i.GetRPCChannel().Legacy) 54 | require.Equal(t, "SRV.foo.a.b.c", i.GetRPCChannel().Server) 55 | require.Equal(t, "bar.REQ", i.GetRPCChannel().Local) 56 | require.Equal(t, "bar.a.b.c", i.GetHandlerKey()) 57 | require.Equal(t, "foo|bar|a|b|c|RCLAIM", i.GetClaimResponseChannel().Legacy) 58 | require.Equal(t, "SRV.foo.a.b.c", i.GetClaimResponseChannel().Server) 59 | require.Equal(t, "bar.RCLAIM", i.GetClaimResponseChannel().Local) 60 | require.Equal(t, "foo|bar|a|b|c|STR", i.GetStreamServerChannel().Legacy) 61 | require.Equal(t, "SRV.foo.a.b.c", i.GetStreamServerChannel().Server) 62 | require.Equal(t, "bar.STR", i.GetStreamServerChannel().Local) 63 | 64 | i.Queue = true 65 | 66 | require.Equal(t, "foo|bar|a|b|c|REQ", i.GetRPCChannel().Legacy) 67 | require.Equal(t, "SRV.foo.a.b.c.Q", i.GetRPCChannel().Server) 68 | require.Equal(t, "bar.REQ", i.GetRPCChannel().Local) 69 | require.Equal(t, "foo|bar|a|b|c|RCLAIM", i.GetClaimResponseChannel().Legacy) 70 | require.Equal(t, "SRV.foo.a.b.c", i.GetClaimResponseChannel().Server) 71 | require.Equal(t, "bar.RCLAIM", i.GetClaimResponseChannel().Local) 72 | require.Equal(t, "foo|bar|a|b|c|STR", i.GetStreamServerChannel().Legacy) 73 | require.Equal(t, "SRV.foo.a.b.c", i.GetStreamServerChannel().Server) 74 | require.Equal(t, "bar.STR", i.GetStreamServerChannel().Local) 75 | 76 | require.Equal(t, "U+0001f680_u+00c9.U+0001f6f0_bar.u+8f6fu+4ef6.END", formatChannel('.', "🚀_É", "🛰_bar", []string{"软件"}, "END")) 77 | } 78 | -------------------------------------------------------------------------------- /pkg/info/info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package info 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/livekit/psrpc" 21 | ) 22 | 23 | type ServiceDefinition struct { 24 | Name string 25 | ID string 26 | Methods sync.Map 27 | } 28 | 29 | type MethodInfo struct { 30 | AffinityEnabled bool 31 | Multi bool 32 | RequireClaim bool 33 | Queue bool 34 | } 35 | 36 | type RequestInfo struct { 37 | psrpc.RPCInfo 38 | AffinityEnabled bool 39 | RequireClaim bool 40 | Queue bool 41 | } 42 | 43 | func (s *ServiceDefinition) RegisterMethod(name string, affinityEnabled, multi, requireClaim, queue bool) { 44 | s.Methods.Store(name, &MethodInfo{ 45 | AffinityEnabled: affinityEnabled, 46 | Multi: multi, 47 | RequireClaim: requireClaim, 48 | Queue: queue, 49 | }) 50 | } 51 | 52 | func (s *ServiceDefinition) GetInfo(rpc string, topic []string) *RequestInfo { 53 | v, _ := s.Methods.Load(rpc) 54 | m := v.(*MethodInfo) 55 | 56 | return &RequestInfo{ 57 | RPCInfo: psrpc.RPCInfo{ 58 | Service: s.Name, 59 | Method: rpc, 60 | Topic: topic, 61 | Multi: m.Multi, 62 | }, 63 | AffinityEnabled: m.AffinityEnabled, 64 | RequireClaim: m.RequireClaim, 65 | Queue: m.Queue, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/metadata/metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metadata 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "golang.org/x/exp/maps" 22 | ) 23 | 24 | type Metadata map[string]string 25 | 26 | type Header struct { 27 | RemoteID string 28 | SentAt time.Time 29 | Metadata Metadata 30 | } 31 | 32 | type ctxMD struct { 33 | md Metadata 34 | added [][]string 35 | } 36 | 37 | type headerKey struct{} 38 | type metadataKey struct{} 39 | 40 | func NewContextWithIncomingHeader(ctx context.Context, head *Header) context.Context { 41 | return context.WithValue(ctx, headerKey{}, head) 42 | } 43 | 44 | func IncomingHeader(ctx context.Context) *Header { 45 | head, ok := ctx.Value(headerKey{}).(*Header) 46 | if !ok { 47 | return nil 48 | } 49 | return &Header{ 50 | RemoteID: head.RemoteID, 51 | SentAt: head.SentAt, 52 | Metadata: maps.Clone(head.Metadata), 53 | } 54 | } 55 | 56 | func NewContextWithOutgoingMetadata(ctx context.Context, md Metadata) context.Context { 57 | return context.WithValue(ctx, metadataKey{}, ctxMD{md: md}) 58 | } 59 | 60 | func AppendMetadataToOutgoingContext(ctx context.Context, kv ...string) context.Context { 61 | md, ok := ctx.Value(metadataKey{}).(ctxMD) 62 | if !ok || md.md == nil { 63 | md = ctxMD{md: Metadata{}} 64 | } 65 | added := make([][]string, len(md.added)+1) 66 | copy(added, md.added) 67 | added[len(added)-1] = make([]string, len(kv)) 68 | copy(added[len(added)-1], kv) 69 | return context.WithValue(ctx, metadataKey{}, ctxMD{md.md, added}) 70 | } 71 | 72 | func OutgoingContextMetadata(ctx context.Context) Metadata { 73 | md, ok := ctx.Value(metadataKey{}).(ctxMD) 74 | if !ok { 75 | return nil 76 | } 77 | clone := maps.Clone(md.md) 78 | for _, a := range md.added { 79 | for i := 1; i < len(a); i += 2 { 80 | clone[a[i-1]] = a[i] 81 | } 82 | } 83 | return clone 84 | } 85 | -------------------------------------------------------------------------------- /pkg/middleware/recovery.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "context" 19 | "runtime/debug" 20 | 21 | "google.golang.org/protobuf/proto" 22 | 23 | "github.com/livekit/psrpc" 24 | ) 25 | 26 | // Recover from server panics. Should always be the last interceptor 27 | func WithServerRecovery() psrpc.ServerRPCInterceptor { 28 | return func(ctx context.Context, req proto.Message, _ psrpc.RPCInfo, handler psrpc.ServerRPCHandler) (resp proto.Message, err error) { 29 | defer func() { 30 | if r := recover(); r != nil { 31 | err = psrpc.NewErrorf(psrpc.Internal, "Caught server panic. Stack trace:\n%s", string(debug.Stack())) 32 | } 33 | }() 34 | 35 | resp, err = handler(ctx, req) 36 | return 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/middleware/retry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "time" 21 | 22 | "google.golang.org/protobuf/proto" 23 | 24 | "github.com/livekit/psrpc" 25 | ) 26 | 27 | type RetryOptions struct { 28 | MaxAttempts int 29 | Timeout time.Duration 30 | Backoff time.Duration 31 | IsRecoverable func(err error) bool 32 | GetRetryParameters func(err error, attempt int) (retry bool, timeout time.Duration, waitTime time.Duration) // will override the MaxAttempts, Timeout and Backoff parameters 33 | } 34 | 35 | func WithRPCRetries(opt RetryOptions) psrpc.ClientOption { 36 | return psrpc.WithClientRPCInterceptors(NewRPCRetryInterceptor(opt)) 37 | } 38 | 39 | func NewRPCRetryInterceptor(opt RetryOptions) psrpc.ClientRPCInterceptor { 40 | return func(rpcInfo psrpc.RPCInfo, next psrpc.ClientRPCHandler) psrpc.ClientRPCHandler { 41 | return func(ctx context.Context, req proto.Message, opts ...psrpc.RequestOption) (res proto.Message, err error) { 42 | err = retry(opt, ctx.Done(), func(timeout time.Duration) error { 43 | nextOpts := opts 44 | if timeout > 0 { 45 | nextOpts = make([]psrpc.RequestOption, len(opts)+1) 46 | copy(nextOpts, opts) 47 | nextOpts[len(opts)] = psrpc.WithRequestTimeout(timeout) 48 | } 49 | 50 | res, err = next(ctx, req, nextOpts...) 51 | return err 52 | }) 53 | return 54 | } 55 | } 56 | } 57 | 58 | func isTimeout(err error) bool { 59 | var e psrpc.Error 60 | if !errors.As(err, &e) { 61 | return true 62 | } 63 | return e.Code() == psrpc.DeadlineExceeded || e.Code() == psrpc.Unavailable 64 | } 65 | 66 | func getRetryWithBackoffParameters(o RetryOptions) func(err error, attempt int) (retry bool, timeout time.Duration, waitTime time.Duration) { 67 | timeout := o.Timeout 68 | 69 | return func(err error, attempt int) (bool, time.Duration, time.Duration) { 70 | if !o.IsRecoverable(err) || attempt == o.MaxAttempts { 71 | return false, 0, 0 72 | } 73 | 74 | timeout += o.Backoff 75 | 76 | return true, timeout, 0 77 | } 78 | } 79 | 80 | func retry(opt RetryOptions, done <-chan struct{}, fn func(timeout time.Duration) error) error { 81 | timeout := opt.Timeout 82 | attempt := 1 83 | if opt.IsRecoverable == nil { 84 | opt.IsRecoverable = isTimeout 85 | } 86 | 87 | if opt.GetRetryParameters == nil { 88 | opt.GetRetryParameters = getRetryWithBackoffParameters(opt) 89 | } 90 | 91 | for { 92 | err := fn(timeout) 93 | if err == nil { 94 | return nil 95 | } 96 | 97 | var retry bool 98 | var waitTime time.Duration 99 | retry, timeout, waitTime = opt.GetRetryParameters(err, attempt) 100 | if !retry { 101 | return err 102 | } 103 | 104 | attempt++ 105 | 106 | select { 107 | case <-done: 108 | return psrpc.ErrRequestCanceled 109 | case <-time.After(waitTime): 110 | } 111 | } 112 | } 113 | 114 | func WithStreamRetries(opt RetryOptions) psrpc.ClientOption { 115 | return psrpc.WithClientStreamInterceptors(NewStreamRetryInterceptor(opt)) 116 | } 117 | 118 | func NewStreamRetryInterceptor(opt RetryOptions) psrpc.StreamInterceptor { 119 | return func(rpcInfo psrpc.RPCInfo, next psrpc.StreamHandler) psrpc.StreamHandler { 120 | return &streamRetryInterceptor{ 121 | StreamHandler: next, 122 | opt: opt, 123 | } 124 | } 125 | } 126 | 127 | type streamRetryInterceptor struct { 128 | psrpc.StreamHandler 129 | opt RetryOptions 130 | } 131 | 132 | func (s *streamRetryInterceptor) Send(msg proto.Message, opts ...psrpc.StreamOption) (err error) { 133 | return retry(s.opt, nil, func(timeout time.Duration) error { 134 | nextOpts := opts 135 | if timeout > 0 { 136 | nextOpts = make([]psrpc.StreamOption, len(opts)+1) 137 | copy(nextOpts, opts) 138 | nextOpts[len(opts)] = psrpc.WithTimeout(timeout) 139 | } 140 | 141 | return s.StreamHandler.Send(msg, nextOpts...) 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /pkg/middleware/retry_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | "time" 8 | 9 | "github.com/livekit/psrpc" 10 | "github.com/stretchr/testify/require" 11 | "google.golang.org/protobuf/proto" 12 | ) 13 | 14 | func TestRetryBackoff(t *testing.T) { 15 | ro := RetryOptions{ 16 | MaxAttempts: 3, 17 | Timeout: 100 * time.Millisecond, 18 | Backoff: 200 * time.Millisecond, 19 | } 20 | 21 | var timeouts []time.Duration 22 | getClientRpcHandler := func(errors []error) func(ctx context.Context, req proto.Message, opts ...psrpc.RequestOption) (res proto.Message, err error) { 23 | timeouts = nil 24 | attempt := 0 25 | 26 | return func(ctx context.Context, req proto.Message, opts ...psrpc.RequestOption) (res proto.Message, err error) { 27 | o := &psrpc.RequestOpts{} 28 | 29 | for _, f := range opts { 30 | f(o) 31 | } 32 | 33 | timeouts = append(timeouts, o.Timeout) 34 | 35 | err = errors[attempt] 36 | attempt++ 37 | 38 | return nil, err 39 | } 40 | } 41 | 42 | t.Run("TestSuccess", func(t *testing.T) { 43 | ro.IsRecoverable = func(err error) bool { return true } 44 | ri := NewRPCRetryInterceptor(ro) 45 | 46 | errs := []error{nil} 47 | h := ri(psrpc.RPCInfo{}, getClientRpcHandler(errs)) 48 | h(context.Background(), nil) 49 | 50 | require.Equal(t, 1, len(timeouts)) 51 | require.Equal(t, ro.Timeout, timeouts[0]) 52 | }) 53 | 54 | t.Run("TestFailureAllErrorsRetryable", func(t *testing.T) { 55 | ro.IsRecoverable = func(err error) bool { return true } 56 | ri := NewRPCRetryInterceptor(ro) 57 | 58 | errs := make([]error, 3) 59 | for i, _ := range errs { 60 | errs[i] = errors.New("test error") 61 | } 62 | h := ri(psrpc.RPCInfo{}, getClientRpcHandler(errs)) 63 | h(context.Background(), nil) 64 | 65 | require.Equal(t, ro.MaxAttempts, len(timeouts)) 66 | 67 | expectedTimeout := ro.Timeout 68 | for _, timeout := range timeouts { 69 | require.Equal(t, expectedTimeout, timeout) 70 | expectedTimeout += ro.Backoff 71 | } 72 | }) 73 | 74 | t.Run("TestFailureNoErrorRetryable", func(t *testing.T) { 75 | ro.IsRecoverable = func(err error) bool { return false } 76 | ri := NewRPCRetryInterceptor(ro) 77 | 78 | errs := []error{errors.New("test error")} 79 | h := ri(psrpc.RPCInfo{}, getClientRpcHandler(errs)) 80 | h(context.Background(), nil) 81 | 82 | require.Equal(t, 1, len(timeouts)) 83 | require.Equal(t, ro.Timeout, timeouts[0]) 84 | }) 85 | 86 | t.Run("TestCustomParameters", func(t *testing.T) { 87 | lastTry := time.Now() 88 | 89 | ro.GetRetryParameters = func(err error, attempt int) (retry bool, timeout time.Duration, waitTime time.Duration) { 90 | if attempt > 1 { 91 | now := time.Now() 92 | require.InDelta(t, 500*time.Millisecond, now.Sub(lastTry), float64(20*time.Millisecond), "Retry didn't wait for required interval") 93 | lastTry = now 94 | } 95 | 96 | if attempt == 3 { 97 | return false, 0, 0 98 | } 99 | 100 | return true, 100 * time.Millisecond, 500 * time.Millisecond 101 | } 102 | 103 | ri := NewRPCRetryInterceptor(ro) 104 | 105 | errs := make([]error, 3) 106 | for i, _ := range errs { 107 | errs[i] = errors.New("test error") 108 | } 109 | h := ri(psrpc.RPCInfo{}, getClientRpcHandler(errs)) 110 | h(context.Background(), nil) 111 | 112 | require.Equal(t, ro.MaxAttempts, len(timeouts)) 113 | 114 | for _, timeout := range timeouts { 115 | require.Equal(t, 100*time.Millisecond, timeout) 116 | } 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /pkg/rand/id.go: -------------------------------------------------------------------------------- 1 | package rand 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 10 | 11 | var idRNG = rand.New(&lockedRandSource{src: rand.NewSource(time.Now().UnixNano())}) 12 | 13 | type lockedRandSource struct { 14 | mu sync.Mutex 15 | src rand.Source 16 | } 17 | 18 | func (s *lockedRandSource) Int63() int64 { 19 | s.mu.Lock() 20 | defer s.mu.Unlock() 21 | return s.src.Int63() 22 | } 23 | 24 | func (s *lockedRandSource) Seed(seed int64) { 25 | s.mu.Lock() 26 | s.src.Seed(seed) 27 | s.mu.Unlock() 28 | } 29 | 30 | func NewClientID() string { 31 | return formatID("CLI_") 32 | } 33 | 34 | func NewServerID() string { 35 | return formatID("SRV_") 36 | } 37 | 38 | func NewRequestID() string { 39 | return formatID("REQ_") 40 | } 41 | 42 | func NewStreamID() string { 43 | return formatID("STR_") 44 | } 45 | 46 | func NewString() string { 47 | return formatID("") 48 | } 49 | 50 | func formatID(prefix string) string { 51 | b := make([]byte, len(prefix)+12) 52 | copy(b, prefix) 53 | readIDChars(b[len(prefix):]) 54 | return string(b) 55 | } 56 | 57 | func readIDChars(b []byte) { 58 | var n int 59 | for { 60 | r := idRNG.Int63() 61 | for i := 0; i < 10; i++ { 62 | if int(r&0x3f) < len(alphabet) { 63 | b[n] = alphabet[r&0x3f] 64 | n++ 65 | if n == len(b) { 66 | return 67 | } 68 | } 69 | r >>= 6 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /pkg/server/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package server 16 | 17 | import ( 18 | "github.com/livekit/psrpc" 19 | "github.com/livekit/psrpc/internal/bus" 20 | "github.com/livekit/psrpc/internal/interceptors" 21 | ) 22 | 23 | func getServerOpts(opts ...psrpc.ServerOption) psrpc.ServerOpts { 24 | o := &psrpc.ServerOpts{ 25 | Timeout: psrpc.DefaultServerTimeout, 26 | ChannelSize: bus.DefaultChannelSize, 27 | } 28 | for _, opt := range opts { 29 | opt(o) 30 | } 31 | 32 | o.ChainedInterceptor = interceptors.ChainServerInterceptors(o.Interceptors) 33 | return *o 34 | } 35 | -------------------------------------------------------------------------------- /pkg/server/registration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package server 16 | 17 | import "reflect" 18 | 19 | type Registerer struct { 20 | register any 21 | deregister any 22 | } 23 | 24 | func NewRegisterer(register, deregister any) Registerer { 25 | return Registerer{register, deregister} 26 | } 27 | 28 | func anySliceReflectValues(anys []any) []reflect.Value { 29 | vals := make([]reflect.Value, len(anys)) 30 | for i, a := range anys { 31 | vals[i] = reflect.ValueOf(a) 32 | } 33 | return vals 34 | } 35 | 36 | type RegistererSlice []Registerer 37 | 38 | func (rs RegistererSlice) Register(params ...any) error { 39 | paramVals := anySliceReflectValues(params) 40 | for i, r := range rs { 41 | ret := reflect.ValueOf(r.register).Call(paramVals) 42 | if !ret[0].IsNil() { 43 | rs[:i].Deregister(params...) 44 | return ret[0].Interface().(error) 45 | } 46 | } 47 | return nil 48 | } 49 | 50 | func (rs RegistererSlice) Deregister(params ...any) { 51 | paramVals := anySliceReflectValues(params) 52 | for _, r := range rs { 53 | reflect.ValueOf(r.deregister).Call(paramVals) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package server 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "sync" 21 | 22 | "github.com/frostbyte73/core" 23 | "golang.org/x/exp/maps" 24 | "google.golang.org/protobuf/proto" 25 | 26 | "github.com/livekit/psrpc" 27 | "github.com/livekit/psrpc/internal/bus" 28 | "github.com/livekit/psrpc/pkg/info" 29 | ) 30 | 31 | type rpcHandler interface { 32 | close(force bool) 33 | } 34 | 35 | type RPCServer struct { 36 | *info.ServiceDefinition 37 | psrpc.ServerOpts 38 | 39 | bus bus.MessageBus 40 | 41 | mu sync.RWMutex 42 | handlers map[string]rpcHandler 43 | active sync.WaitGroup 44 | shutdown core.Fuse 45 | } 46 | 47 | func NewRPCServer(sd *info.ServiceDefinition, b bus.MessageBus, opts ...psrpc.ServerOption) *RPCServer { 48 | s := &RPCServer{ 49 | ServiceDefinition: sd, 50 | ServerOpts: getServerOpts(opts...), 51 | bus: b, 52 | handlers: make(map[string]rpcHandler), 53 | } 54 | if s.ServerID != "" { 55 | s.ID = s.ServerID 56 | } 57 | 58 | return s 59 | } 60 | 61 | func RegisterHandler[RequestType proto.Message, ResponseType proto.Message]( 62 | s *RPCServer, 63 | rpc string, 64 | topic []string, 65 | svcImpl func(context.Context, RequestType) (ResponseType, error), 66 | affinityFunc AffinityFunc[RequestType], 67 | ) error { 68 | if s.shutdown.IsBroken() { 69 | return psrpc.ErrServerClosed 70 | } 71 | 72 | i := s.GetInfo(rpc, topic) 73 | 74 | key := i.GetHandlerKey() 75 | s.mu.RLock() 76 | _, ok := s.handlers[key] 77 | s.mu.RUnlock() 78 | if ok { 79 | return errors.New("handler already exists") 80 | } 81 | 82 | // create handler 83 | h, err := newRPCHandler(s, i, svcImpl, s.ChainedInterceptor, affinityFunc) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | s.active.Add(1) 89 | h.onCompleted = func() { 90 | s.active.Done() 91 | s.mu.Lock() 92 | delete(s.handlers, key) 93 | s.mu.Unlock() 94 | } 95 | 96 | s.mu.Lock() 97 | s.handlers[key] = h 98 | s.mu.Unlock() 99 | 100 | h.run(s) 101 | return nil 102 | } 103 | 104 | func RegisterStreamHandler[RequestType proto.Message, ResponseType proto.Message]( 105 | s *RPCServer, 106 | rpc string, 107 | topic []string, 108 | svcImpl func(psrpc.ServerStream[ResponseType, RequestType]) error, 109 | affinityFunc StreamAffinityFunc, 110 | ) error { 111 | if s.shutdown.IsBroken() { 112 | return psrpc.ErrServerClosed 113 | } 114 | 115 | i := s.GetInfo(rpc, topic) 116 | 117 | key := i.GetHandlerKey() 118 | s.mu.RLock() 119 | _, ok := s.handlers[key] 120 | s.mu.RUnlock() 121 | if ok { 122 | return errors.New("handler already exists") 123 | } 124 | 125 | // create handler 126 | h, err := newStreamRPCHandler(s, i, svcImpl, affinityFunc) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | s.active.Add(1) 132 | h.onCompleted = func() { 133 | s.active.Done() 134 | s.mu.Lock() 135 | delete(s.handlers, key) 136 | s.mu.Unlock() 137 | } 138 | 139 | s.mu.Lock() 140 | s.handlers[key] = h 141 | s.mu.Unlock() 142 | 143 | h.run(s) 144 | return nil 145 | } 146 | 147 | func (s *RPCServer) DeregisterHandler(rpc string, topic []string) { 148 | i := s.GetInfo(rpc, topic) 149 | key := i.GetHandlerKey() 150 | s.mu.RLock() 151 | h, ok := s.handlers[key] 152 | s.mu.RUnlock() 153 | if ok { 154 | h.close(true) 155 | } 156 | } 157 | 158 | func (s *RPCServer) Publish(ctx context.Context, rpc string, topic []string, msg proto.Message) error { 159 | i := s.GetInfo(rpc, topic) 160 | return s.bus.Publish(ctx, i.GetRPCChannel(), msg) 161 | } 162 | 163 | func (s *RPCServer) Close(force bool) { 164 | s.shutdown.Once(func() { 165 | s.mu.RLock() 166 | handlers := maps.Values(s.handlers) 167 | s.mu.RUnlock() 168 | 169 | var wg sync.WaitGroup 170 | for _, h := range handlers { 171 | wg.Add(1) 172 | h := h 173 | go func() { 174 | h.close(force) 175 | wg.Done() 176 | }() 177 | } 178 | wg.Wait() 179 | }) 180 | 181 | if !force { 182 | s.active.Wait() 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/command_line.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "strings" 19 | ) 20 | 21 | type commandLineParams struct { 22 | importMap map[string]string // Mapping from .proto file name to import path. 23 | paths string // paths flag, used to control file output directory. 24 | module string // module flag, Go import path prefix that is removed from the output filename. 25 | importPrefix string // prefix added to imported package file names. 26 | } 27 | 28 | // parseCommandLineParams breaks the comma-separated list of key=value pairs 29 | // in the parameter (a member of the request protobuf) into a key/value map. 30 | // It then sets command line parameter mappings defined by those entries. 31 | func parseCommandLineParams(parameter string) (*commandLineParams, error) { 32 | ps := make(map[string]string) 33 | for _, p := range strings.Split(parameter, ",") { 34 | if p == "" { 35 | continue 36 | } 37 | i := strings.Index(p, "=") 38 | if i < 0 { 39 | return nil, fmt.Errorf("invalid parameter %q: expected format of parameter to be k=v", p) 40 | } 41 | k := p[0:i] 42 | v := p[i+1:] 43 | if v == "" { 44 | return nil, fmt.Errorf("invalid parameter %q: expected format of parameter to be k=v", k) 45 | } 46 | ps[k] = v 47 | } 48 | 49 | clp := &commandLineParams{ 50 | importMap: make(map[string]string), 51 | } 52 | for k, v := range ps { 53 | switch { 54 | // Support import map 'M' prefix: https://developers.google.com/protocol-buffers/docs/reference/go-generated 55 | case len(k) > 0 && k[0] == 'M': 56 | clp.importMap[k[1:]] = v // 1 is the length of 'M'. 57 | case len(k) > 0 && strings.HasPrefix(k, "go_import_mapping@"): // twirp specific version of M parameters 58 | clp.importMap[k[18:]] = v // 18 is the length of 'go_import_mapping@'. 59 | 60 | case k == "paths": 61 | switch v { 62 | case "import": 63 | // this is the default behavior; the output file is placed in a directory named after the option go_package 64 | case "source_relative": 65 | // the directory prefix on the option go_package is removed from the output filename (only the last part is used) 66 | clp.paths = "source_relative" 67 | default: 68 | return nil, fmt.Errorf("invalid command line flag %s=%s", k, v) 69 | } 70 | 71 | // If the module={PREFIX} flag is specified, the prefix is removed from the option go_package on the output filename 72 | case k == "module": 73 | clp.module = v 74 | 75 | // Deprecated, but may still be useful when working with old versions of protoc-gen-go 76 | case k == "import_prefix": 77 | clp.importPrefix = v 78 | 79 | default: 80 | return nil, fmt.Errorf("invalid command line flag %s=%s", k, v) 81 | } 82 | } 83 | return clp, nil 84 | } 85 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/command_line_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "errors" 18 | "reflect" 19 | "testing" 20 | ) 21 | 22 | func TestParseCommandLineParams(t *testing.T) { 23 | tests := []struct { 24 | name string 25 | parameter string 26 | params *commandLineParams 27 | err error 28 | }{ 29 | { 30 | "no parameters", 31 | "", 32 | &commandLineParams{ 33 | importMap: map[string]string{}, 34 | }, 35 | nil, 36 | }, 37 | { 38 | "unknown parameter", 39 | "kkk=vvv", 40 | nil, 41 | errors.New(`invalid command line flag kkk=vvv`), 42 | }, 43 | { 44 | "empty parameter value - no equals sign", 45 | "import_prefix", 46 | nil, 47 | errors.New(`invalid parameter "import_prefix": expected format of parameter to be k=v`), 48 | }, 49 | { 50 | "empty parameter value - no value", 51 | "import_prefix=", 52 | nil, 53 | errors.New(`invalid parameter "import_prefix": expected format of parameter to be k=v`), 54 | }, 55 | { 56 | "import_prefix parameter", 57 | "import_prefix=github.com/example/repo", 58 | &commandLineParams{ 59 | importMap: map[string]string{}, 60 | importPrefix: "github.com/example/repo", 61 | }, 62 | nil, 63 | }, 64 | { 65 | "single import parameter starting with 'M'", 66 | "Mrpcutil/empty.proto=github.com/example/rpcutil", 67 | &commandLineParams{ 68 | importMap: map[string]string{ 69 | "rpcutil/empty.proto": "github.com/example/rpcutil", 70 | }, 71 | }, 72 | nil, 73 | }, 74 | { 75 | "multiple import parameters starting with 'M'", 76 | "Mrpcutil/empty.proto=github.com/example/rpcutil,Mrpc/haberdasher/service.proto=github.com/example/rpc/haberdasher", 77 | &commandLineParams{ 78 | importMap: map[string]string{ 79 | "rpcutil/empty.proto": "github.com/example/rpcutil", 80 | "rpc/haberdasher/service.proto": "github.com/example/rpc/haberdasher", 81 | }, 82 | }, 83 | nil, 84 | }, 85 | { 86 | "single import parameter starting with 'go_import_mapping@'", 87 | "go_import_mapping@rpcutil/empty.proto=github.com/example/rpcutil", 88 | &commandLineParams{ 89 | importMap: map[string]string{ 90 | "rpcutil/empty.proto": "github.com/example/rpcutil", 91 | }, 92 | }, 93 | nil, 94 | }, 95 | { 96 | "multiple import parameters starting with 'go_import_mapping@'", 97 | "go_import_mapping@rpcutil/empty.proto=github.com/example/rpcutil,go_import_mapping@rpc/haberdasher/service.proto=github.com/example/rpc/haberdasher", 98 | &commandLineParams{ 99 | importMap: map[string]string{ 100 | "rpcutil/empty.proto": "github.com/example/rpcutil", 101 | "rpc/haberdasher/service.proto": "github.com/example/rpc/haberdasher", 102 | }, 103 | }, 104 | nil, 105 | }, 106 | { 107 | "paths import", 108 | "paths=import", 109 | &commandLineParams{ 110 | importMap: map[string]string{}, 111 | }, 112 | nil, 113 | }, 114 | { 115 | "paths source_relative", 116 | "paths=source_relative", 117 | &commandLineParams{ 118 | importMap: map[string]string{}, 119 | paths: "source_relative", 120 | }, 121 | nil, 122 | }, 123 | { 124 | "paths invalidstuff", 125 | "paths=invalidstuff", 126 | nil, 127 | errors.New(`invalid command line flag paths=invalidstuff`), 128 | }, 129 | { 130 | "module parameter", 131 | "module=foo/bar/fizz", 132 | &commandLineParams{ 133 | importMap: map[string]string{}, 134 | module: "foo/bar/fizz", 135 | }, 136 | nil, 137 | }, 138 | } 139 | for _, tt := range tests { 140 | t.Run(tt.name, func(t *testing.T) { 141 | params, err := parseCommandLineParams(tt.parameter) 142 | switch { 143 | case err != nil: 144 | if tt.err == nil { 145 | t.Fatal(err) 146 | } 147 | if err.Error() != tt.err.Error() { 148 | t.Errorf("got error = %v, want %v", err, tt.err) 149 | } 150 | case err == nil: 151 | if tt.err != nil { 152 | t.Errorf("got error = %v, want %v", err, tt.err) 153 | } 154 | } 155 | if !reflect.DeepEqual(params, tt.params) { 156 | t.Errorf("got params = %+v, want %+v", params, tt.params) 157 | } 158 | }) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/generator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "os" 18 | "os/exec" 19 | "testing" 20 | 21 | "google.golang.org/protobuf/proto" 22 | plugin "google.golang.org/protobuf/types/pluginpb" 23 | ) 24 | 25 | func TestGenerateParseCommandLineParamsError(t *testing.T) { 26 | if os.Getenv("BE_CRASHER") == "1" { 27 | g := &psrpc{} 28 | g.Generate(&plugin.CodeGeneratorRequest{ 29 | Parameter: proto.String("invalid"), 30 | }) 31 | return 32 | } 33 | cmd := exec.Command(os.Args[0], "-test.run=TestGenerateParseCommandLineParamsError") 34 | cmd.Env = append(os.Environ(), "BE_CRASHER=1") 35 | err := cmd.Run() 36 | if e, ok := err.(*exec.ExitError); ok && !e.Success() { 37 | return 38 | } 39 | t.Fatalf("process ran with err %v, want exit status 1", err) 40 | } 41 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/go_naming.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "path" 18 | "strings" 19 | 20 | descriptor "google.golang.org/protobuf/types/descriptorpb" 21 | 22 | "github.com/livekit/psrpc/protoc-gen-psrpc/internal/gen/stringutils" 23 | ) 24 | 25 | // goPackageOption interprets the file's go_package option. 26 | // If there is no go_package, it returns ("", "", false). 27 | // If there's a simple name, it returns ("", pkg, true). 28 | // If the option implies an import path, it returns (impPath, pkg, true). 29 | func goPackageOption(f *descriptor.FileDescriptorProto) (impPath, pkg string, ok bool) { 30 | pkg = f.GetOptions().GetGoPackage() 31 | if pkg == "" { 32 | return "", "", false 33 | } 34 | if bits := strings.Split(pkg, ";"); len(bits) == 2 { 35 | return bits[0], bits[1], true 36 | } 37 | // The presence of a slash implies there's an import path. 38 | slash := strings.LastIndex(pkg, "/") 39 | if slash < 0 { 40 | return "", pkg, true 41 | } 42 | impPath, pkg = pkg, pkg[slash+1:] 43 | // A semicolon-delimited suffix overrides the package name. 44 | sc := strings.IndexByte(impPath, ';') 45 | if sc < 0 { 46 | return impPath, pkg, true 47 | } 48 | impPath, pkg = impPath[:sc], impPath[sc+1:] 49 | return impPath, pkg, true 50 | } 51 | 52 | // goPackageName returns the Go package name to use in the generated Go file. 53 | // The result explicitly reports whether the name came from an option go_package 54 | // statement. If explicit is false, the name was derived from the protocol 55 | // buffer's package statement or the input file name. 56 | func goPackageName(f *descriptor.FileDescriptorProto) (name string, explicit bool) { 57 | // Does the file have a "go_package" option? 58 | if _, pkg, ok := goPackageOption(f); ok { 59 | return pkg, true 60 | } 61 | 62 | // Does the file have a package clause? 63 | if pkg := f.GetPackage(); pkg != "" { 64 | return pkg, false 65 | } 66 | // Use the file base name. 67 | return stringutils.BaseName(f.GetName()), false 68 | } 69 | 70 | // goFileName returns the output name for the generated Go file. 71 | func (t *psrpc) goFileName(f *descriptor.FileDescriptorProto) string { 72 | name := *f.Name // proto file name 73 | if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" { 74 | name = name[:len(name)-len(ext)] // remove extension 75 | } 76 | name += ".psrpc.go" // add psrpc extension 77 | 78 | // with paths=source_relative, the directory is the same as the proto file 79 | if t.sourceRelativePaths { 80 | return name 81 | } 82 | // otherwise, the directory is taken from the option go_package 83 | if impPath, _, ok := goPackageOption(f); ok && impPath != "" { 84 | if t.modulePrefix != "" { 85 | impPath = strings.TrimPrefix(strings.TrimPrefix(impPath, t.modulePrefix), "/") 86 | } 87 | 88 | // Replace the existing dirname with the import path from go_package 89 | _, name = path.Split(name) 90 | name = path.Join(impPath, name) 91 | return name 92 | } 93 | 94 | return name 95 | } 96 | 97 | func parseGoPackageOption(v string) (importPath, packageName string) { 98 | // Allowed formats: 99 | // option go_package = "foo"; 100 | // option go_package = "github.com/example/foo"; 101 | // option go_package = "github.com/example/foo;bar"; 102 | 103 | semicolonPos := strings.Index(v, ";") 104 | if semicolonPos > -1 { 105 | importPath = v[:semicolonPos] 106 | packageName = v[semicolonPos+1:] 107 | return 108 | } 109 | 110 | if strings.Contains(v, "/") { 111 | importPath = v 112 | _, packageName = path.Split(v) 113 | return 114 | } 115 | 116 | importPath = "" 117 | packageName = v 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/go_naming_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "testing" 18 | 19 | descriptor "google.golang.org/protobuf/types/descriptorpb" 20 | ) 21 | 22 | func TestParseGoPackageOption(t *testing.T) { 23 | testcase := func(in, wantImport, wantPkg string) func(*testing.T) { 24 | return func(t *testing.T) { 25 | haveImport, havePkg := parseGoPackageOption(in) 26 | if haveImport != wantImport { 27 | t.Errorf("wrong importPath, have=%q want=%q", haveImport, wantImport) 28 | } 29 | if havePkg != wantPkg { 30 | t.Errorf("wrong packageName, have=%q want=%q", havePkg, wantPkg) 31 | } 32 | } 33 | } 34 | 35 | t.Run("empty string", testcase("", "", "")) 36 | t.Run("bare package", testcase("foo", "", "foo")) 37 | t.Run("full import", testcase("github.com/example/foo", "github.com/example/foo", "foo")) 38 | t.Run("full import with override", 39 | testcase("github.com/example/foo;bar", "github.com/example/foo", "bar")) 40 | t.Run("non dotted import with package", testcase("foo;bar", "foo", "bar")) 41 | } 42 | 43 | func TestGoPackageOption(t *testing.T) { 44 | testcase := func(in, wantImport, wantPkg string, wantOK bool) func(*testing.T) { 45 | return func(t *testing.T) { 46 | haveImport, havePkg, haveOK := goPackageOption(&descriptor.FileDescriptorProto{ 47 | Options: &descriptor.FileOptions{ 48 | GoPackage: &in, 49 | }, 50 | }) 51 | if wantOK != haveOK { 52 | t.Errorf("wrong ok, have=%t want=%t", haveOK, wantOK) 53 | } 54 | if haveImport != wantImport { 55 | t.Errorf("wrong importPath, have=%q want=%q", haveImport, wantImport) 56 | } 57 | if havePkg != wantPkg { 58 | t.Errorf("wrong packageName, have=%q want=%q", havePkg, wantPkg) 59 | } 60 | } 61 | } 62 | 63 | t.Run("empty string", testcase("", "", "", false)) 64 | t.Run("bare package", testcase("foo", "", "foo", true)) 65 | t.Run("full import", testcase("github.com/example/foo", "github.com/example/foo", "foo", true)) 66 | t.Run("full import with override", 67 | testcase("github.com/example/foo;bar", "github.com/example/foo", "bar", true)) 68 | t.Run("non dotted import with package", testcase("foo;bar", "foo", "bar", true)) 69 | } 70 | 71 | func TestGoFileName(t *testing.T) { 72 | testcase := func(srcrelpaths bool, modprefix, fname, gopkg, wantName string) func(t2 *testing.T) { 73 | return func(t *testing.T) { 74 | f := &descriptor.FileDescriptorProto{ 75 | Name: &fname, 76 | Options: &descriptor.FileOptions{ 77 | GoPackage: &gopkg, 78 | }, 79 | } 80 | 81 | tw := &psrpc{ 82 | sourceRelativePaths: srcrelpaths, 83 | modulePrefix: modprefix, 84 | } 85 | 86 | if name := tw.goFileName(f); name != wantName { 87 | t.Errorf("wrong goFileName, have=%q want=%q", name, wantName) 88 | } 89 | } 90 | } 91 | 92 | t.Run("paths=source_relative", 93 | testcase(true, "", 94 | "rpc/v1/service.proto", "example.com/module/package/rpc/v1", 95 | "rpc/v1/service.psrpc.go")) 96 | 97 | t.Run("paths=import,module=example.com/module/package", 98 | testcase(false, "example.com/module/package", 99 | "rpc/v1/service.proto", "example.com/module/package/rpc/v1", 100 | "rpc/v1/service.psrpc.go")) 101 | 102 | t.Run("paths=import,module=example.com/module/package/", 103 | testcase(false, "example.com/module/package/", 104 | "rpc/v1/service.proto", "example.com/module/package/rpc/v1", 105 | "rpc/v1/service.psrpc.go")) 106 | } 107 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package gen 15 | 16 | import ( 17 | "log" 18 | "os" 19 | "strings" 20 | ) 21 | 22 | func Fail(msgs ...string) { 23 | s := strings.Join(msgs, " ") 24 | log.Print("error:", s) 25 | os.Exit(1) 26 | } 27 | 28 | func Error(err error, msgs ...string) { 29 | s := strings.Join(msgs, " ") + ":" + err.Error() 30 | log.Print("error:", s) 31 | os.Exit(1) 32 | } 33 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package gen 15 | 16 | import ( 17 | "io" 18 | "os" 19 | 20 | "google.golang.org/protobuf/proto" 21 | descriptor "google.golang.org/protobuf/types/descriptorpb" 22 | plugin "google.golang.org/protobuf/types/pluginpb" 23 | ) 24 | 25 | type Generator interface { 26 | Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse 27 | } 28 | 29 | func Main(g Generator) { 30 | req := readGenRequest(os.Stdin) 31 | resp := g.Generate(req) 32 | writeResponse(os.Stdout, resp) 33 | } 34 | 35 | func FilesToGenerate(req *plugin.CodeGeneratorRequest) []*descriptor.FileDescriptorProto { 36 | genFiles := make([]*descriptor.FileDescriptorProto, 0) 37 | Outer: 38 | for _, name := range req.FileToGenerate { 39 | for _, f := range req.ProtoFile { 40 | if f.GetName() == name { 41 | genFiles = append(genFiles, f) 42 | continue Outer 43 | } 44 | } 45 | Fail("could not find file named", name) 46 | } 47 | 48 | return genFiles 49 | } 50 | 51 | func readGenRequest(r io.Reader) *plugin.CodeGeneratorRequest { 52 | data, err := io.ReadAll(r) 53 | if err != nil { 54 | Error(err, "reading input") 55 | } 56 | 57 | req := new(plugin.CodeGeneratorRequest) 58 | if err = proto.Unmarshal(data, req); err != nil { 59 | Error(err, "parsing input proto") 60 | } 61 | 62 | if len(req.FileToGenerate) == 0 { 63 | Fail("no files to generate") 64 | } 65 | 66 | return req 67 | } 68 | 69 | func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) { 70 | data, err := proto.Marshal(resp) 71 | if err != nil { 72 | Error(err, "marshaling response") 73 | } 74 | _, err = w.Write(data) 75 | if err != nil { 76 | Error(err, "writing response") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/stringutils/stringutils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package stringutils 15 | 16 | import ( 17 | "strings" 18 | "unicode" 19 | ) 20 | 21 | // Is c an ASCII lower-case letter? 22 | func isASCIILower(c byte) bool { 23 | return 'a' <= c && c <= 'z' 24 | } 25 | 26 | // Is c an ASCII digit? 27 | func isASCIIDigit(c byte) bool { 28 | return '0' <= c && c <= '9' 29 | } 30 | 31 | // CamelCase converts a string from snake_case to CamelCased. 32 | // 33 | // If there is an interior underscore followed by a lower case letter, drop the 34 | // underscore and convert the letter to upper case. There is a remote 35 | // possibility of this rewrite causing a name collision, but it's so remote 36 | // we're prepared to pretend it's nonexistent - since the C++ generator 37 | // lowercases names, it's extremely unlikely to have two fields with different 38 | // capitalizations. In short, _my_field_name_2 becomes XMyFieldName_2. 39 | func CamelCase(s string) string { 40 | if s == "" { 41 | return "" 42 | } 43 | t := make([]byte, 0, 32) 44 | i := 0 45 | if s[0] == '_' { 46 | // Need a capital letter; drop the '_'. 47 | t = append(t, 'X') 48 | i++ 49 | } 50 | // Invariant: if the next letter is lower case, it must be converted 51 | // to upper case. 52 | // 53 | // That is, we process a word at a time, where words are marked by _ or upper 54 | // case letter. Digits are treated as words. 55 | for ; i < len(s); i++ { 56 | c := s[i] 57 | if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { 58 | continue // Skip the underscore in s. 59 | } 60 | if isASCIIDigit(c) { 61 | t = append(t, c) 62 | continue 63 | } 64 | // Assume we have a letter now - if not, it's a bogus identifier. The next 65 | // word is a sequence of characters that must start upper case. 66 | if isASCIILower(c) { 67 | c ^= ' ' // Make it a capital letter. 68 | } 69 | t = append(t, c) // Guaranteed not lower case. 70 | // Accept lower case sequence that follows. 71 | for i+1 < len(s) && isASCIILower(s[i+1]) { 72 | i++ 73 | t = append(t, s[i]) 74 | } 75 | } 76 | return string(t) 77 | } 78 | 79 | // LowerCamelCase converts a snake_case string to camelCase 80 | func LowerCamelCase(s string) string { 81 | t := []byte(CamelCase(s)) 82 | t[0] ^= ' ' 83 | return string(t) 84 | } 85 | 86 | // AlphaDigitize replaces non-letter, non-digit, non-underscore characters with 87 | // underscore. 88 | func AlphaDigitize(r rune) rune { 89 | if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' { 90 | return r 91 | } 92 | return '_' 93 | } 94 | 95 | // CleanIdentifier makes sure s is a valid 'identifier' string: it contains only 96 | // letters, numbers, and underscore. 97 | func CleanIdentifier(s string) string { 98 | return strings.Map(AlphaDigitize, s) 99 | } 100 | 101 | // BaseName the last path element of a slash-delimited name, with the last 102 | // dotted suffix removed. 103 | func BaseName(name string) string { 104 | // First, find the last element 105 | if i := strings.LastIndex(name, "/"); i >= 0 { 106 | name = name[i+1:] 107 | } 108 | // Now drop the suffix 109 | if i := strings.LastIndex(name, "."); i >= 0 { 110 | name = name[0:i] 111 | } 112 | return name 113 | } 114 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/fileset.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livekit/psrpc/465289d72c3cc663c6376ee33df16cd0a2146299/protoc-gen-psrpc/internal/gen/typemap/testdata/fileset.pb -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package testdata 15 | 16 | //go:generate protoc --descriptor_set_out=fileset.pb --include_imports --include_source_info ./service.proto 17 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/importer.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.gen.typemap.testdata.importer; 18 | 19 | import "root_pkg.proto"; 20 | 21 | message ImporterMsg { 22 | root_pkg.RootMsg a = 1; 23 | 24 | message ImporterInner {} 25 | } 26 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/public_importer.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.gen.typemap.testdata.public_importer; 18 | 19 | import public "root_pkg.proto"; 20 | import public "importer.proto"; 21 | 22 | message PublicImporterMsgA { 23 | importer.ImporterMsg a = 1; 24 | } 25 | 26 | message PublicImporterMsgB { 27 | root_pkg.RootMsg a = 1; 28 | } 29 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/public_reimporter.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.gen.typemap.testdata.public_reimporter; 18 | 19 | import public "public_importer.proto"; 20 | 21 | message PublicReimporterMsg { 22 | public_importer.PublicImporterMsgA a = 1; 23 | public_importer.PublicImporterMsgB b = 2; 24 | root_pkg.RootMsg c = 3; 25 | importer.ImporterMsg d = 4; 26 | } 27 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/root_pkg.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.gen.typemap.testdata.root_pkg; 18 | 19 | // RootMsg leading 20 | message RootMsg {} 21 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/testdata/service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc.internal.gen.typemap.testdata.public_reimporter; 18 | 19 | import public "public_reimporter.proto"; 20 | 21 | message ServiceMsg { 22 | public_importer.PublicImporterMsgA a = 1; 23 | public_importer.PublicImporterMsgB b = 2; 24 | root_pkg.RootMsg c = 3; 25 | importer.ImporterMsg d = 4; 26 | } 27 | 28 | message Parent { 29 | message NestedOuter{ 30 | message NestedInner{} 31 | } 32 | } 33 | 34 | service EmptyService {} 35 | 36 | service ServiceWithOneMethod{ 37 | // Method1 leading 38 | rpc Method1(root_pkg.RootMsg) returns (importer.ImporterMsg); 39 | // Method1 trailing 40 | } 41 | 42 | // ServiceWithManyMethods leading 43 | service ServiceWithManyMethods{ 44 | // Method1 leading 45 | rpc Method1(root_pkg.RootMsg) returns (importer.ImporterMsg); 46 | // Method2 leading 47 | rpc Method2(Parent) returns (Parent.NestedOuter); 48 | // Method2 trailing 49 | 50 | rpc Method3(importer.ImporterMsg.ImporterInner) returns (Parent.NestedOuter.NestedInner); 51 | } 52 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/internal/gen/typemap/typemap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package typemap 15 | 16 | import ( 17 | "os" 18 | "path/filepath" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | "google.golang.org/protobuf/proto" 24 | descriptor "google.golang.org/protobuf/types/descriptorpb" 25 | ) 26 | 27 | func loadTestPb(t *testing.T) []*descriptor.FileDescriptorProto { 28 | f, err := os.ReadFile(filepath.Join("testdata", "fileset.pb")) 29 | require.NoError(t, err, "unable to read testdata protobuf file") 30 | 31 | set := new(descriptor.FileDescriptorSet) 32 | err = proto.Unmarshal(f, set) 33 | require.NoError(t, err, "unable to unmarshal testdata protobuf file") 34 | 35 | return set.File 36 | } 37 | 38 | func protoFile(files []*descriptor.FileDescriptorProto, name string) *descriptor.FileDescriptorProto { 39 | for _, f := range files { 40 | if filepath.Base(f.GetName()) == name { 41 | return f 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | func service(f *descriptor.FileDescriptorProto, name string) *descriptor.ServiceDescriptorProto { 48 | for _, s := range f.Service { 49 | if s.GetName() == name { 50 | return s 51 | } 52 | } 53 | return nil 54 | } 55 | 56 | func method(s *descriptor.ServiceDescriptorProto, name string) *descriptor.MethodDescriptorProto { 57 | for _, m := range s.Method { 58 | if m.GetName() == name { 59 | return m 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | func TestNewRegistry(t *testing.T) { 66 | files := loadTestPb(t) 67 | file := protoFile(files, "service.proto") 68 | service := service(file, "ServiceWithManyMethods") 69 | 70 | reg := New(files) 71 | 72 | comments, err := reg.ServiceComments(file, service) 73 | require.NoError(t, err, "unable to load service comments") 74 | assert.Equal(t, " ServiceWithManyMethods leading\n", comments.Leading) 75 | 76 | method1 := method(service, "Method1") 77 | require.NotNil(t, method1) 78 | 79 | method1Input := reg.MethodInputDefinition(method1) 80 | require.NotNil(t, method1Input) 81 | assert.Equal(t, "RootMsg", method1Input.Descriptor.GetName()) 82 | } 83 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "os" 21 | 22 | "github.com/livekit/psrpc/protoc-gen-psrpc/internal/gen" 23 | "github.com/livekit/psrpc/version" 24 | ) 25 | 26 | func main() { 27 | versionFlag := flag.Bool("version", false, "print version and exit") 28 | flag.Parse() 29 | if *versionFlag { 30 | fmt.Println(version.Version) 31 | os.Exit(0) 32 | } 33 | 34 | g := newGenerator() 35 | gen.Main(g) 36 | } 37 | -------------------------------------------------------------------------------- /protoc-gen-psrpc/options/options.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package psrpc; 18 | option go_package = "github.com/livekit/psrpc/protoc-gen-psrpc/options"; 19 | 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | extend google.protobuf.MethodOptions { 23 | optional Options options = 2198; 24 | } 25 | 26 | // RPC types 27 | enum Routing { 28 | QUEUE = 0; // Servers will join a queue, and only one will receive each request 29 | AFFINITY = 1; // Servers will implement an affinity function for handler selection 30 | MULTI = 2; // Every server will respond to every request (for subscriptions, all clients will receive every message) 31 | } 32 | 33 | message Options { 34 | // This method is a pub/sub. 35 | bool subscription = 1; 36 | 37 | // This method uses topics. 38 | bool topics = 2; 39 | 40 | TopicParamOptions topic_params = 3; 41 | 42 | // The method uses bidirectional streaming. 43 | bool stream = 4; 44 | 45 | // RPC type 46 | Routing type = 8; 47 | 48 | // deprecated 49 | oneof routing { 50 | // For RPCs, each client request will receive a response from every server. 51 | // For subscriptions, every client will receive every update. 52 | bool multi = 5; 53 | 54 | // Your service will supply an affinity function for handler selection. 55 | bool affinity_func = 6; 56 | 57 | // Requests load balancing is provided by a pub/sub server queue 58 | bool queue = 7; 59 | } 60 | } 61 | 62 | message TopicParamOptions { 63 | // The rpc can be registered/deregistered atomically with other group members 64 | string group = 1; 65 | 66 | // The topic is composed of one or more string-like parameters. 67 | repeated string names = 2; 68 | 69 | // The topic parameters have associated string-like type parameters 70 | bool typed = 3; 71 | 72 | // At most one server will be registered for each topic 73 | bool single_server = 4; 74 | } 75 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "time" 19 | 20 | "golang.org/x/exp/slices" 21 | ) 22 | 23 | type RequestOption func(*RequestOpts) 24 | 25 | type RequestOpts struct { 26 | Timeout time.Duration 27 | SelectionOpts SelectionOpts 28 | Interceptors []any 29 | } 30 | 31 | type SelectionOpts struct { 32 | MinimumAffinity float32 // minimum affinity for a server to be considered a valid handler 33 | MaximumAffinity float32 // if > 0, any server returning a max score will be selected immediately 34 | AcceptFirstAvailable bool // go fast 35 | AffinityTimeout time.Duration // server selection deadline 36 | ShortCircuitTimeout time.Duration // deadline imposed after receiving first response 37 | SelectionFunc func([]*Claim) (string, error) // custom server selection function 38 | } 39 | 40 | type Claim struct { 41 | ServerID string 42 | Affinity float32 43 | } 44 | 45 | func WithRequestTimeout(timeout time.Duration) RequestOption { 46 | return func(o *RequestOpts) { 47 | o.Timeout = timeout 48 | } 49 | } 50 | 51 | func WithSelectionOpts(opts SelectionOpts) RequestOption { 52 | return func(o *RequestOpts) { 53 | o.SelectionOpts = opts 54 | } 55 | } 56 | 57 | type RequestInterceptor interface { 58 | ClientRPCInterceptor | ClientMultiRPCInterceptor | StreamInterceptor 59 | } 60 | 61 | func WithRequestInterceptors[T RequestInterceptor](interceptors ...T) RequestOption { 62 | return func(o *RequestOpts) { 63 | o.Interceptors = slices.Grow(o.Interceptors, len(interceptors)) 64 | for _, i := range interceptors { 65 | o.Interceptors = append(o.Interceptors, i) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | const DefaultServerTimeout = time.Second * 3 25 | 26 | type ServerOption func(*ServerOpts) 27 | 28 | type ServerOpts struct { 29 | ServerID string 30 | Timeout time.Duration 31 | ChannelSize int 32 | Interceptors []ServerRPCInterceptor 33 | StreamInterceptors []StreamInterceptor 34 | ChainedInterceptor ServerRPCInterceptor 35 | } 36 | 37 | func WithServerID(id string) ServerOption { 38 | return func(o *ServerOpts) { 39 | o.ServerID = id 40 | } 41 | } 42 | 43 | func WithServerTimeout(timeout time.Duration) ServerOption { 44 | return func(o *ServerOpts) { 45 | o.Timeout = timeout 46 | } 47 | } 48 | 49 | func WithServerChannelSize(size int) ServerOption { 50 | return func(o *ServerOpts) { 51 | if size > 0 { 52 | o.ChannelSize = size 53 | } 54 | } 55 | } 56 | 57 | // Server interceptors wrap the service implementation 58 | type ServerRPCInterceptor func(ctx context.Context, req proto.Message, info RPCInfo, handler ServerRPCHandler) (proto.Message, error) 59 | type ServerRPCHandler func(context.Context, proto.Message) (proto.Message, error) 60 | 61 | func WithServerRPCInterceptors(interceptors ...ServerRPCInterceptor) ServerOption { 62 | return func(o *ServerOpts) { 63 | for _, interceptor := range interceptors { 64 | if interceptor != nil { 65 | o.Interceptors = append(o.Interceptors, interceptor) 66 | } 67 | } 68 | } 69 | } 70 | 71 | func WithServerStreamInterceptors(interceptors ...StreamInterceptor) ServerOption { 72 | return func(o *ServerOpts) { 73 | o.StreamInterceptors = append(o.StreamInterceptors, interceptors...) 74 | } 75 | } 76 | 77 | func WithServerOptions(opts ...ServerOption) ServerOption { 78 | return func(o *ServerOpts) { 79 | for _, opt := range opts { 80 | opt(o) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "time" 19 | 20 | "google.golang.org/protobuf/proto" 21 | ) 22 | 23 | type StreamOption func(*StreamOpts) 24 | 25 | type StreamOpts struct { 26 | Timeout time.Duration 27 | } 28 | 29 | func WithTimeout(timeout time.Duration) StreamOption { 30 | return func(o *StreamOpts) { 31 | o.Timeout = timeout 32 | } 33 | } 34 | 35 | type StreamInterceptor func(info RPCInfo, next StreamHandler) StreamHandler 36 | type StreamHandler interface { 37 | Recv(msg proto.Message) error 38 | Send(msg proto.Message, opts ...StreamOption) error 39 | Close(cause error) error 40 | } 41 | -------------------------------------------------------------------------------- /testutils/bus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testutils 16 | 17 | import ( 18 | "github.com/livekit/psrpc" 19 | "github.com/livekit/psrpc/internal/bus" 20 | ) 21 | 22 | type Channel = bus.Channel 23 | 24 | type PublishInterceptor = bus.PublishInterceptor 25 | type SubscribeInterceptor = bus.SubscribeInterceptor 26 | 27 | type PublishHandler = bus.PublishHandler 28 | type ReadHandler = bus.ReadHandler 29 | 30 | type TestBusOption = bus.TestBusOption 31 | 32 | func WithPublishInterceptor(interceptor PublishInterceptor) TestBusOption { 33 | return func(o *bus.TestBusOpts) { 34 | o.PublishInterceptors = append(o.PublishInterceptors, interceptor) 35 | } 36 | } 37 | 38 | func WithSubscribeInterceptor(interceptor SubscribeInterceptor) TestBusOption { 39 | return func(o *bus.TestBusOpts) { 40 | o.SubscribeInterceptors = append(o.SubscribeInterceptors, interceptor) 41 | } 42 | } 43 | 44 | func WithBusOptions(opts ...TestBusOption) TestBusOption { 45 | return func(o *bus.TestBusOpts) { 46 | for _, opt := range opts { 47 | opt(o) 48 | } 49 | } 50 | } 51 | 52 | func NewTestBus(next psrpc.MessageBus, opts ...TestBusOption) psrpc.MessageBus { 53 | return bus.NewTestBus(next, opts...) 54 | } 55 | -------------------------------------------------------------------------------- /testutils/laggybus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testutils 16 | 17 | import ( 18 | "container/heap" 19 | "context" 20 | "math" 21 | sync "sync" 22 | "time" 23 | 24 | "google.golang.org/protobuf/proto" 25 | "google.golang.org/protobuf/types/known/anypb" 26 | ) 27 | 28 | func WithLaggyBus(id string, latency latencyFunc) TestBusOption { 29 | return WithBusOptions( 30 | WithPublishInterceptor(func(next PublishHandler) PublishHandler { 31 | return func(ctx context.Context, channel Channel, msg proto.Message) error { 32 | a, err := anypb.New(msg) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | b, err := proto.Marshal(a) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | return next(ctx, channel, &LaggyMessage{ 43 | Origin: id, 44 | SentAt: time.Now().UnixNano(), 45 | Body: b, 46 | }) 47 | } 48 | }), 49 | WithSubscribeInterceptor(func(ctx context.Context, channel Channel, next ReadHandler) ReadHandler { 50 | l := newLaggySubscribeInterceptor() 51 | go l.Copy(ctx, id, latency, next) 52 | return l.Read 53 | }), 54 | ) 55 | } 56 | 57 | type latencyFunc func(a, b string) time.Duration 58 | 59 | type laggySubscribeInterceptor struct { 60 | mu sync.Mutex 61 | rh readResultHeap 62 | t *time.Timer 63 | } 64 | 65 | func newLaggySubscribeInterceptor() *laggySubscribeInterceptor { 66 | return &laggySubscribeInterceptor{ 67 | t: time.NewTimer(math.MaxInt64), 68 | } 69 | } 70 | 71 | func (l *laggySubscribeInterceptor) pushRead(r *readResult) { 72 | l.mu.Lock() 73 | defer l.mu.Unlock() 74 | heap.Push(&l.rh, r) 75 | if l.rh.Peek() == r && l.t.Stop() { 76 | l.t.Reset(time.Until(r.time)) 77 | } 78 | } 79 | 80 | func (l *laggySubscribeInterceptor) popRead() ([]byte, bool) { 81 | l.mu.Lock() 82 | defer l.mu.Unlock() 83 | r := heap.Pop(&l.rh).(*readResult) 84 | if l.rh.Len() > 0 { 85 | l.t.Reset(time.Until(l.rh.Peek().time)) 86 | } else if r.ok { 87 | l.t.Reset(math.MaxInt64) 88 | } 89 | return r.body, r.ok 90 | } 91 | 92 | func (l *laggySubscribeInterceptor) Copy( 93 | ctx context.Context, 94 | id string, 95 | latency latencyFunc, 96 | read ReadHandler, 97 | ) { 98 | for ctx.Err() == nil { 99 | b, ok := read() 100 | if !ok { 101 | break 102 | } 103 | 104 | a := &anypb.Any{} 105 | if proto.Unmarshal(b, a) != nil { 106 | break 107 | } 108 | 109 | m := &LaggyMessage{} 110 | if a.UnmarshalTo(m) != nil { 111 | break 112 | } 113 | 114 | l.pushRead(&readResult{ 115 | time.Unix(0, m.SentAt).Add(latency(m.Origin, id)), 116 | m.Body, 117 | ok, 118 | }) 119 | } 120 | 121 | l.pushRead(&readResult{time: time.Now(), ok: false}) 122 | } 123 | 124 | func (l *laggySubscribeInterceptor) Read() (b []byte, open bool) { 125 | <-l.t.C 126 | return l.popRead() 127 | } 128 | 129 | type readResult struct { 130 | time time.Time 131 | body []byte 132 | ok bool 133 | } 134 | 135 | type readResultHeap struct { 136 | v []*readResult 137 | } 138 | 139 | func (h *readResultHeap) Peek() *readResult { return h.v[0] } 140 | func (h *readResultHeap) Len() int { return len(h.v) } 141 | func (h *readResultHeap) Less(i, j int) bool { return h.v[i].time.Before(h.v[j].time) } 142 | func (h *readResultHeap) Swap(i, j int) { h.v[i], h.v[j] = h.v[j], h.v[i] } 143 | func (h *readResultHeap) Push(r any) { h.v = append(h.v, r.(*readResult)) } 144 | func (h *readResultHeap) Pop() any { 145 | r := h.v[len(h.v)-1] 146 | h.v[len(h.v)-1] = nil 147 | h.v = h.v[:len(h.v)-1] 148 | return r 149 | } 150 | -------------------------------------------------------------------------------- /testutils/testutils.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package testutils; 18 | option go_package = "github.com/livekit/psrpc/testutils"; 19 | 20 | message LaggyMessage { 21 | string origin = 1; 22 | int64 sent_at = 2; 23 | bytes body = 3; 24 | } 25 | -------------------------------------------------------------------------------- /testutils/unreliablebus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testutils 16 | 17 | import ( 18 | "context" 19 | "math/rand" 20 | 21 | "go.uber.org/atomic" 22 | ) 23 | 24 | type AtomicFailureRate struct { 25 | v *atomic.Float64 26 | } 27 | 28 | func NewAtomicFailureRate(v float64) AtomicFailureRate { 29 | return AtomicFailureRate{v: atomic.NewFloat64(v)} 30 | } 31 | 32 | func (g *AtomicFailureRate) Rate() float64 { 33 | return g.v.Load() 34 | } 35 | 36 | func (g *AtomicFailureRate) SetRate(v float64) { 37 | g.v.Store(v) 38 | } 39 | 40 | func WithUnreliableBus(rate AtomicFailureRate) TestBusOption { 41 | return WithUnreliableBusChannel(rate, "") 42 | } 43 | 44 | func WithUnreliableBusChannel(rate AtomicFailureRate, channelFilter string) TestBusOption { 45 | return WithSubscribeInterceptor(func(_ context.Context, channel Channel, next ReadHandler) ReadHandler { 46 | if channelFilter != "" && channelFilter != channel.Legacy { 47 | return next 48 | } 49 | 50 | rng := rand.New(rand.NewSource(0)) 51 | return func() ([]byte, bool) { 52 | for { 53 | b, ok := next() 54 | if ok && rate.Rate() > rng.Float64() { 55 | continue 56 | } 57 | return b, ok 58 | } 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package psrpc 16 | 17 | import ( 18 | "context" 19 | 20 | "google.golang.org/protobuf/proto" 21 | 22 | "github.com/livekit/psrpc/internal/bus" 23 | ) 24 | 25 | type Subscription[MessageType proto.Message] bus.Subscription[MessageType] 26 | 27 | type Response[ResponseType proto.Message] struct { 28 | Result ResponseType 29 | Err error 30 | } 31 | 32 | type Stream[SendType, RecvType proto.Message] interface { 33 | Context() context.Context 34 | Channel() <-chan RecvType 35 | Send(msg SendType, opts ...StreamOption) error 36 | Close(cause error) error 37 | Err() error 38 | } 39 | 40 | type ClientStream[SendType, RecvType proto.Message] interface { 41 | Stream[SendType, RecvType] 42 | } 43 | 44 | type ServerStream[SendType, RecvType proto.Message] interface { 45 | Stream[SendType, RecvType] 46 | Hijack() 47 | } 48 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 LiveKit, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | const ( 18 | Version = "v0.6.0" 19 | PsrpcVersion_0_6 = true 20 | ) 21 | --------------------------------------------------------------------------------