├── internal ├── backcompat │ ├── servicedefs │ │ ├── go.sum │ │ ├── go.mod │ │ └── empty.go │ ├── newservice │ │ ├── .gitignore │ │ ├── main.go │ │ ├── go.mod │ │ └── go.sum │ ├── oldservice │ │ ├── .gitignore │ │ ├── main.go │ │ └── go.mod │ ├── oldservicedefs │ │ ├── doc.go │ │ └── go.mod │ ├── newservicedefs │ │ ├── doc.go │ │ ├── go.mod │ │ └── go.sum │ ├── go.sum │ ├── go.mod │ ├── servicedefs.proto │ ├── doc.go │ ├── run_main.go │ ├── run_server.go │ └── run_client.go ├── .gitignore ├── grpccompat │ ├── README.md │ ├── doc.go │ ├── service.proto │ ├── error_test.go │ ├── basic_test.go │ ├── go.mod │ ├── web_test.go │ └── cancel_test.go ├── twirpcompat │ ├── doc.go │ ├── clientcompat.proto │ ├── go.mod │ ├── common_test.go │ ├── go.sum │ └── compat_test.go ├── drpcopts │ ├── doc.go │ ├── manager.go │ ├── stream.go │ └── README.md └── integration │ ├── go.mod │ ├── gogoservice │ └── export.go │ ├── service │ └── export.go │ ├── customservice │ └── export.go │ ├── service.proto │ ├── doc.go │ ├── alias.go │ ├── alias_gogo.go │ ├── alias_custom.go │ ├── customencoding │ └── customencoding.go │ ├── trace_test.go │ ├── cache_test.go │ ├── error_test.go │ ├── large_test.go │ └── http_test.go ├── staticcheck.conf ├── .gitignore ├── logo.png ├── drpcstream ├── state.png ├── doc.go ├── state.dot ├── inspectmu.go └── pktbuf.go ├── scripts ├── README.md ├── run.sh ├── cloc.sh └── docs.sh ├── doc.go ├── examples ├── drpc │ ├── README.md │ ├── go.mod │ ├── pb │ │ ├── gen.go │ │ └── sesamestreet.proto │ ├── go.sum │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── opentelemetry │ ├── README.md │ ├── pb │ │ ├── gen.go │ │ └── sesamestreet.proto │ ├── go.mod │ ├── server │ │ └── otel_handler.go │ ├── client │ │ ├── otel_conn.go │ │ └── main.go │ └── go.sum ├── grpc │ ├── README.md │ ├── pb │ │ ├── gen.go │ │ └── sesamestreet.proto │ ├── go.mod │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── grpc_and_drpc │ ├── README.md │ ├── pb │ │ ├── gen.go │ │ └── sesamestreet.proto │ ├── go.mod │ ├── grpc_client │ │ └── main.go │ ├── drpc_client │ │ └── main.go │ └── server │ │ └── main.go ├── drpc_and_http │ ├── pb │ │ ├── gen.go │ │ └── sesamestreet.proto │ ├── README.md │ ├── go.mod │ ├── http_client │ │ └── main.go │ ├── go.sum │ ├── drpc_client │ │ └── main.go │ └── server │ │ └── main.go └── README.md ├── drpccache ├── doc.go ├── README.md ├── cache_test.go └── cache.go ├── drpcerr ├── doc.go ├── README.md ├── err_test.go └── err.go ├── drpcstats ├── doc.go ├── README.md └── stats.go ├── go.mod ├── drpcctx ├── doc.go ├── transport.go ├── tracker.go └── README.md ├── drpcserver ├── doc.go ├── util.go ├── util_windows.go ├── server_test.go └── README.md ├── drpcsignal ├── doc.go ├── chan.go ├── README.md └── signal.go ├── drpcconn ├── doc.go ├── conn_test.go └── README.md ├── drpcenc ├── doc.go ├── README.md └── marshal.go ├── drpchttp ├── doc.go ├── context_test.go ├── context.go ├── protocol_grpc_web.go ├── options.go ├── protocol_twirp.go └── encoding.go ├── drpcmux ├── doc.go ├── README.md ├── handle_rpc.go └── mux.go ├── drpcwire ├── doc.go ├── error_test.go ├── packet_test.go ├── fuzz_test.go ├── writer_test.go ├── error.go ├── packet_string.go ├── split_test.go ├── varint.go ├── split.go ├── varint_test.go ├── rand_test.go └── writer.go ├── cmd └── protoc-gen-go-drpc │ └── README.md ├── drpcmetadata ├── doc.go ├── README.md ├── serialize_test.go ├── metadata_test.go ├── metadata.go └── serialize.go ├── drpcdebug ├── doc.go ├── README.md ├── log_disabled.go └── log_enabled.go ├── drpcmigrate ├── doc.go ├── prefixconn.go ├── listener_test.go ├── header.go ├── listener.go └── dial.go ├── drpcmanager ├── doc.go ├── fuzz_test.go └── streambuf.go ├── drpcpool ├── doc.go ├── entry.go ├── helpers_test.go └── README.md ├── go.sum ├── LICENSE ├── Jenkinsfile ├── drpctest ├── README.md └── tracker.go ├── Makefile └── flake.lock /internal/backcompat/servicedefs/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = ["all", "-ST1008"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | .vscode 3 | vendor 4 | result -------------------------------------------------------------------------------- /internal/backcompat/newservice/.gitignore: -------------------------------------------------------------------------------- 1 | newservice 2 | -------------------------------------------------------------------------------- /internal/backcompat/oldservice/.gitignore: -------------------------------------------------------------------------------- 1 | oldservice 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storj/drpc/HEAD/logo.png -------------------------------------------------------------------------------- /drpcstream/state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storj/drpc/HEAD/drpcstream/state.png -------------------------------------------------------------------------------- /internal/.gitignore: -------------------------------------------------------------------------------- 1 | fuzz-*/corpus 2 | fuzz-*/crashers 3 | fuzz-*/suppressions 4 | fuzz-*/*.zip 5 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # scripts 2 | 3 | This folder contains some useful scripts for the repository. 4 | -------------------------------------------------------------------------------- /internal/backcompat/servicedefs/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/backcompat/servicedefs 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpc is a light replacement for gprc. 5 | package drpc 6 | -------------------------------------------------------------------------------- /internal/grpccompat/README.md: -------------------------------------------------------------------------------- 1 | # package grpccompat 2 | 3 | `import "storj.io/drpc/internal/grpccompat"` 4 | 5 | package grpccompat holds compatability tests for grpc. 6 | -------------------------------------------------------------------------------- /examples/drpc/README.md: -------------------------------------------------------------------------------- 1 | # DRPC example 2 | 3 | This example is a bare-bones DRPC use case. It is intended 4 | to show the minimal differences from the gRPC basic example. 5 | 6 | -------------------------------------------------------------------------------- /drpccache/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpccache implements per stream cache for drpc. 5 | package drpccache 6 | -------------------------------------------------------------------------------- /drpcerr/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcerr lets one associate error codes with errors. 5 | package drpcerr 6 | -------------------------------------------------------------------------------- /drpcstats/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcstats contatins types for stat collection. 5 | package drpcstats 6 | -------------------------------------------------------------------------------- /examples/opentelemetry/README.md: -------------------------------------------------------------------------------- 1 | # DRPC example 2 | 3 | This example is a bare-bones DRPC use case. It is intended 4 | to show the minimal differences from the gRPC basic example. 5 | 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/zeebo/assert v1.3.0 7 | github.com/zeebo/errs v1.2.2 8 | google.golang.org/protobuf v1.27.1 9 | ) 10 | -------------------------------------------------------------------------------- /drpcctx/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcctx has helpers to interact with context.Context. 5 | package drpcctx 6 | -------------------------------------------------------------------------------- /drpcserver/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcserver allows one to execute registered rpcs. 5 | package drpcserver 6 | -------------------------------------------------------------------------------- /drpcsignal/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcsignal holds a helper type to signal errors. 5 | package drpcsignal 6 | -------------------------------------------------------------------------------- /examples/grpc/README.md: -------------------------------------------------------------------------------- 1 | # gRPC example 2 | 3 | This example is a baseline gRPC use case, for demonstration 4 | of the differences between other examples. This example does 5 | not use DRPC. 6 | -------------------------------------------------------------------------------- /drpcconn/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcconn creates a drpc client connection from a transport. 5 | package drpcconn 6 | -------------------------------------------------------------------------------- /drpcenc/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcenc holds some helper functions for encoding messages. 5 | package drpcenc 6 | -------------------------------------------------------------------------------- /drpchttp/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpchttp implements a net/http handler for unitary RPCs. 5 | package drpchttp 6 | -------------------------------------------------------------------------------- /drpcmux/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcmux is a handler to dispatch rpcs to implementations. 5 | package drpcmux 6 | -------------------------------------------------------------------------------- /drpcwire/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcwire provides low level helpers for the drpc wire protocol. 5 | package drpcwire 6 | -------------------------------------------------------------------------------- /cmd/protoc-gen-go-drpc/README.md: -------------------------------------------------------------------------------- 1 | # package protoc-gen-go-drpc 2 | 3 | `import "storj.io/drpc/cmd/protoc-gen-go-drpc"` 4 | 5 | protoc-gen-go-drpc generates DRPC code for protobuf services. 6 | 7 | ## Usage 8 | -------------------------------------------------------------------------------- /drpcmetadata/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcmetadata define the structure of the metadata supported by drpc library. 5 | package drpcmetadata 6 | -------------------------------------------------------------------------------- /internal/backcompat/oldservicedefs/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package servicedefs contains a service frozen to an older drpc version. 5 | package servicedefs 6 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/README.md: -------------------------------------------------------------------------------- 1 | # gRPC and DRPC example 2 | 3 | This example demonstrates a server that knows how to respond to 4 | RPC requests via gRPC or DRPC on the same port. 5 | 6 | This example demonstrates protocol multiplexing. 7 | -------------------------------------------------------------------------------- /internal/backcompat/newservicedefs/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package servicedefs contains a service running against the local drpc version. 5 | package servicedefs 6 | -------------------------------------------------------------------------------- /internal/backcompat/oldservicedefs/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/backcompat/oldservicedefs 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/gogo/protobuf v1.3.2 7 | storj.io/drpc v0.0.17 8 | ) 9 | 10 | require github.com/zeebo/errs v1.2.2 // indirect 11 | -------------------------------------------------------------------------------- /examples/drpc/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/examples/drpc 2 | 3 | go 1.19 4 | 5 | require ( 6 | google.golang.org/protobuf v1.27.1 7 | storj.io/drpc v0.0.17 8 | ) 9 | 10 | require github.com/zeebo/errs v1.2.2 // indirect 11 | 12 | replace storj.io/drpc => ../.. 13 | -------------------------------------------------------------------------------- /drpcdebug/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcdebug provides helpers for debugging. 5 | package drpcdebug 6 | 7 | // Enabled is a constant describing if logs are enabled or not. 8 | const Enabled = enabled 9 | -------------------------------------------------------------------------------- /examples/drpc/pb/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package pb includes protobufs for this example. 5 | package pb 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-drpc_out=paths=source_relative:. sesamestreet.proto 8 | -------------------------------------------------------------------------------- /examples/grpc/pb/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package pb includes protobufs for this example. 5 | package pb 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. sesamestreet.proto 8 | -------------------------------------------------------------------------------- /examples/drpc_and_http/pb/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package pb includes protobufs for this example. 5 | package pb 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-drpc_out=paths=source_relative:. sesamestreet.proto 8 | -------------------------------------------------------------------------------- /examples/opentelemetry/pb/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package pb includes protobufs for this example. 5 | package pb 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-drpc_out=paths=source_relative:. sesamestreet.proto 8 | -------------------------------------------------------------------------------- /drpcmigrate/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcmigrate provides tools to support drpc concurrently alongside gRPC 5 | // on the same ports. 6 | // See the grpc_and_drpc example in the examples folder for expected usage. 7 | package drpcmigrate 8 | -------------------------------------------------------------------------------- /examples/drpc_and_http/README.md: -------------------------------------------------------------------------------- 1 | # DRPC and HTTP example 2 | 3 | This example demonstrates a server that knows how to respond to 4 | RPC requests via DRPC or HTTP on the same port. 5 | 6 | This example demonstrates both protocol multiplexing, and DRPC's 7 | built in HTTP handling and JSON conversion of protobuf RPC 8 | definitions. 9 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/pb/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package pb includes protobufs for this example. 5 | package pb 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. --go-drpc_out=paths=source_relative:. sesamestreet.proto 8 | -------------------------------------------------------------------------------- /internal/backcompat/newservicedefs/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/backcompat/newservicedefs 2 | 3 | go 1.19 4 | 5 | require ( 6 | google.golang.org/protobuf v1.27.1 7 | storj.io/drpc v0.0.0-00010101000000-000000000000 8 | ) 9 | 10 | require github.com/zeebo/errs v1.2.2 // indirect 11 | 12 | replace storj.io/drpc => ../../.. 13 | -------------------------------------------------------------------------------- /examples/drpc_and_http/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/examples/drpc_and_http 2 | 3 | go 1.19 4 | 5 | require ( 6 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 7 | google.golang.org/protobuf v1.27.1 8 | storj.io/drpc v0.0.17 9 | ) 10 | 11 | require github.com/zeebo/errs v1.2.2 // indirect 12 | 13 | replace storj.io/drpc => ../.. 14 | -------------------------------------------------------------------------------- /internal/grpccompat/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package grpccompat holds compatibility tests for grpc. 5 | package grpccompat 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. --go-drpc_out=paths=source_relative:. service.proto 8 | -------------------------------------------------------------------------------- /internal/twirpcompat/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package twirpcompat holds compatibility tests for Twirp. 5 | package twirpcompat 6 | 7 | //go:generate protoc --go_out=paths=source_relative:. --go-drpc_out=paths=source_relative:. --twirp_out=paths=source_relative:. clientcompat.proto 8 | -------------------------------------------------------------------------------- /internal/backcompat/go.sum: -------------------------------------------------------------------------------- 1 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 2 | github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 3 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 4 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 5 | -------------------------------------------------------------------------------- /internal/drpcopts/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcopts contains internal options. 5 | // 6 | // This package allows options to exist that are too sharp 7 | // to provide to typical users of the library that are not 8 | // required to be backward compatible. 9 | package drpcopts 10 | -------------------------------------------------------------------------------- /drpcmanager/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcmanager reads packets from a transport to make streams. 5 | package drpcmanager 6 | 7 | // closedCh is an already closed channel. 8 | var closedCh = func() chan struct{} { 9 | ch := make(chan struct{}) 10 | close(ch) 11 | return ch 12 | }() 13 | -------------------------------------------------------------------------------- /internal/backcompat/newservice/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // newservice runs the new version of the backwards compatibility check. 5 | package main 6 | 7 | import ( 8 | "context" 9 | 10 | "storj.io/drpc/internal/backcompat" 11 | ) 12 | 13 | func main() { backcompat.Main(context.Background()) } 14 | -------------------------------------------------------------------------------- /internal/backcompat/oldservice/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // oldservice runs the old version of the backwards compatibility check. 5 | package main 6 | 7 | import ( 8 | "context" 9 | 10 | "storj.io/drpc/internal/backcompat" 11 | ) 12 | 13 | func main() { backcompat.Main(context.Background()) } 14 | -------------------------------------------------------------------------------- /internal/twirpcompat/clientcompat.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package compat; 4 | option go_package = "storj.io/drpc/internal/twirpcompat"; 5 | 6 | service CompatService { 7 | rpc Method(Req) returns (Resp); 8 | rpc NoopMethod(Empty) returns (Empty); 9 | } 10 | 11 | message Empty {} 12 | 13 | message Req { string v = 1; } 14 | message Resp { int32 v = 1; } 15 | -------------------------------------------------------------------------------- /internal/integration/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/integration 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/gogo/protobuf v1.3.2 7 | github.com/zeebo/assert v1.3.0 8 | github.com/zeebo/errs v1.2.2 9 | golang.org/x/exp v0.0.0-20240707233637-46b078467d37 10 | google.golang.org/protobuf v1.27.1 11 | storj.io/drpc v0.0.0-00010101000000-000000000000 12 | ) 13 | 14 | replace storj.io/drpc => ../.. 15 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | 7 | PATH_COND=(-path) 8 | if [ "$1" == "-v" ]; then 9 | PATH_COND=(! -path) 10 | shift 11 | fi 12 | 13 | GLOB="$1" 14 | shift 15 | 16 | find "${SCRIPTDIR}/.." "${PATH_COND[@]}" '*'"${GLOB}"'*' -name "go.mod" -exec dirname {} + \ 17 | | while read -r DIR; do 18 | (cd "${DIR}" || exit; "$@") 19 | done 20 | -------------------------------------------------------------------------------- /internal/backcompat/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/backcompat 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/zeebo/assert v1.3.0 7 | github.com/zeebo/errs v1.2.2 8 | storj.io/drpc v0.0.0-00010101000000-000000000000 9 | storj.io/drpc/internal/backcompat/servicedefs v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | replace ( 13 | storj.io/drpc => ../.. 14 | storj.io/drpc/internal/backcompat/servicedefs => ./servicedefs 15 | ) 16 | -------------------------------------------------------------------------------- /drpcdebug/README.md: -------------------------------------------------------------------------------- 1 | # package drpcdebug 2 | 3 | `import "storj.io/drpc/drpcdebug"` 4 | 5 | Package drpcdebug provides helpers for debugging. 6 | 7 | ## Usage 8 | 9 | ```go 10 | const Enabled = enabled 11 | ``` 12 | Enabled is a constant describing if logs are enabled or not. 13 | 14 | #### func Log 15 | 16 | ```go 17 | func Log(cb func() (who, what, why string)) 18 | ``` 19 | Log executes the callback for a string to log if built with the debug tag. 20 | -------------------------------------------------------------------------------- /drpcmanager/fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmanager 5 | 6 | import "testing" 7 | 8 | func FuzzClient(f *testing.F) { 9 | f.Fuzz(func(t *testing.T, prog []byte) { 10 | runRandomized(t, prog, new(randClient)) 11 | }) 12 | } 13 | 14 | func FuzzServer(f *testing.F) { 15 | f.Fuzz(func(t *testing.T, prog []byte) { 16 | runRandomized(t, prog, new(randServer)) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /drpcenc/README.md: -------------------------------------------------------------------------------- 1 | # package drpcenc 2 | 3 | `import "storj.io/drpc/drpcenc"` 4 | 5 | Package drpcenc holds some helper functions for encoding messages. 6 | 7 | ## Usage 8 | 9 | #### func MarshalAppend 10 | 11 | ```go 12 | func MarshalAppend(msg drpc.Message, enc drpc.Encoding, buf []byte) (data []byte, err error) 13 | ``` 14 | MarshalAppend calls enc.Marshal(msg) and returns the data appended to buf. If 15 | enc implements MarshalAppend, that is called instead. 16 | -------------------------------------------------------------------------------- /internal/twirpcompat/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/twirpcompat 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/twitchtv/twirp v8.1.0+incompatible 7 | github.com/zeebo/assert v1.3.0 8 | github.com/zeebo/hmux v0.3.1 9 | google.golang.org/protobuf v1.27.1 10 | storj.io/drpc v0.0.0-00010101000000-000000000000 11 | ) 12 | 13 | require ( 14 | github.com/pkg/errors v0.9.1 // indirect 15 | github.com/zeebo/errs v1.2.2 // indirect 16 | ) 17 | 18 | replace storj.io/drpc => ../.. 19 | -------------------------------------------------------------------------------- /drpcwire/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "errors" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | 12 | "storj.io/drpc/drpcerr" 13 | ) 14 | 15 | func TestError(t *testing.T) { 16 | data := MarshalError(drpcerr.WithCode(errors.New("test"), 5)) 17 | err := UnmarshalError(data) 18 | assert.Equal(t, drpcerr.Code(err), 5) 19 | assert.Equal(t, err.Error(), "test") 20 | } 21 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | * `grpc` - This is a vanilla "Hello world!" style example of gRPC use. It exists to contrast the other examples. 4 | * `drpc` - This is the grpc example converted directly to use DRPC only. 5 | * `grpc_and_drpc` - This example provides a server that speaks both gRPC *and* DRPC over the same port. 6 | * `drpc_and_http` - This example provides a server that demonstrates DRPC's built-in HTTP/JSON translation support, and serving HTTP and DRPC over the same port as well. 7 | -------------------------------------------------------------------------------- /drpcdebug/log_disabled.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | //go:build !debug 5 | // +build !debug 6 | 7 | package drpcdebug 8 | 9 | // Log executes the callback for a string to log if built with the debug tag. 10 | func Log(cb func() (who, what, why string)) {} 11 | 12 | // this exists to work around a bug in markdown doc generation so that it 13 | // does not generate two entries for Enabled, one set to true and one to false. 14 | const enabled = false 15 | -------------------------------------------------------------------------------- /examples/grpc/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/examples/grpc 2 | 3 | go 1.19 4 | 5 | require ( 6 | google.golang.org/grpc v1.36.0 7 | google.golang.org/protobuf v1.26.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.0 // indirect 12 | golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect 13 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a // indirect 14 | golang.org/x/text v0.3.0 // indirect 15 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /drpcwire/packet_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/zeebo/assert" 10 | ) 11 | 12 | func TestAppendParse(t *testing.T) { 13 | for i := 0; i < 1000; i++ { 14 | exp := RandFrame() 15 | rem, got, ok, err := ParseFrame(AppendFrame(nil, exp)) 16 | assert.NoError(t, err) 17 | assert.That(t, ok) 18 | assert.Equal(t, len(rem), 0) 19 | assert.DeepEqual(t, got, exp) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /drpcwire/fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import "testing" 7 | 8 | func FuzzParseFrame(f *testing.F) { 9 | f.Add(AppendFrame(nil, Frame{})) 10 | f.Add(AppendFrame(nil, Frame{ 11 | Data: []byte("foo"), 12 | ID: ID{1<<64 - 1, 1<<64 - 1}, 13 | Kind: 7, 14 | Done: true, 15 | Control: true, 16 | })) 17 | 18 | f.Fuzz(func(t *testing.T, data []byte) { 19 | _, _, _, _ = ParseFrame(data) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /drpcserver/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | //go:build !windows 5 | // +build !windows 6 | 7 | package drpcserver 8 | 9 | import ( 10 | "errors" 11 | "net" 12 | ) 13 | 14 | // isTemporary checks if an error is temporary. 15 | func isTemporary(err error) bool { 16 | var nErr net.Error 17 | if errors.As(err, &nErr) { 18 | //lint:ignore SA1019 while this is deprecated, there is no good replacement 19 | return nErr.Temporary() 20 | } 21 | 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /examples/drpc/pb/sesamestreet.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/examples/drpc/pb"; 6 | 7 | package sesamestreet; 8 | 9 | message Cookie { 10 | enum Type { 11 | Sugar = 0; 12 | Oatmeal = 1; 13 | Chocolate = 2; 14 | } 15 | 16 | Type type = 1; 17 | } 18 | 19 | message Crumbs { 20 | Cookie cookie = 1; 21 | } 22 | 23 | service CookieMonster { 24 | rpc EatCookie(Cookie) returns (Crumbs) {} 25 | } 26 | -------------------------------------------------------------------------------- /examples/grpc/pb/sesamestreet.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/examples/grpc/pb"; 6 | 7 | package sesamestreet; 8 | 9 | message Cookie { 10 | enum Type { 11 | Sugar = 0; 12 | Oatmeal = 1; 13 | Chocolate = 2; 14 | } 15 | 16 | Type type = 1; 17 | } 18 | 19 | message Crumbs { 20 | Cookie cookie = 1; 21 | } 22 | 23 | service CookieMonster { 24 | rpc EatCookie(Cookie) returns (Crumbs) {} 25 | } 26 | -------------------------------------------------------------------------------- /internal/integration/gogoservice/export.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package service is a testing service for the integration tests. 5 | package service 6 | 7 | import ( 8 | "reflect" 9 | 10 | "storj.io/drpc" 11 | ) 12 | 13 | // Equal returns true if the two messages are equal. 14 | func Equal(a, b drpc.Message) bool { return reflect.DeepEqual(a, b) } 15 | 16 | // Encoding is the drpc.Encoding used for this service. 17 | var Encoding drpcEncoding_File_service_proto 18 | -------------------------------------------------------------------------------- /examples/drpc_and_http/pb/sesamestreet.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/examples/drpc_and_http/pb"; 6 | 7 | package sesamestreet; 8 | 9 | message Cookie { 10 | enum Type { 11 | Sugar = 0; 12 | Oatmeal = 1; 13 | Chocolate = 2; 14 | } 15 | 16 | Type type = 1; 17 | } 18 | 19 | message Crumbs { 20 | Cookie cookie = 1; 21 | } 22 | 23 | service CookieMonster { 24 | rpc EatCookie(Cookie) returns (Crumbs) {} 25 | } 26 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/pb/sesamestreet.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/examples/grpc_and_drpc/pb"; 6 | 7 | package sesamestreet; 8 | 9 | message Cookie { 10 | enum Type { 11 | Sugar = 0; 12 | Oatmeal = 1; 13 | Chocolate = 2; 14 | } 15 | 16 | Type type = 1; 17 | } 18 | 19 | message Crumbs { 20 | Cookie cookie = 1; 21 | } 22 | 23 | service CookieMonster { 24 | rpc EatCookie(Cookie) returns (Crumbs) {} 25 | } 26 | -------------------------------------------------------------------------------- /examples/opentelemetry/pb/sesamestreet.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/examples/opentelemetry/pb"; 6 | 7 | package sesamestreet; 8 | 9 | message Cookie { 10 | enum Type { 11 | Sugar = 0; 12 | Oatmeal = 1; 13 | Chocolate = 2; 14 | } 15 | 16 | Type type = 1; 17 | } 18 | 19 | message Crumbs { 20 | Cookie cookie = 1; 21 | } 22 | 23 | service CookieMonster { 24 | rpc EatCookie(Cookie) returns (Crumbs) {} 25 | } 26 | -------------------------------------------------------------------------------- /drpcmigrate/prefixconn.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmigrate 5 | 6 | import ( 7 | "bytes" 8 | "io" 9 | "net" 10 | ) 11 | 12 | type prefixConn struct { 13 | io.Reader 14 | net.Conn 15 | } 16 | 17 | func newPrefixConn(data []byte, conn net.Conn) *prefixConn { 18 | return &prefixConn{ 19 | Reader: io.MultiReader(bytes.NewReader(data), conn), 20 | Conn: conn, 21 | } 22 | } 23 | 24 | func (pc *prefixConn) Read(p []byte) (n int, err error) { 25 | return pc.Reader.Read(p) 26 | } 27 | -------------------------------------------------------------------------------- /scripts/cloc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | ( 6 | echo '| Package | Lines |' 7 | echo '| --- | --- |' 8 | go list -f '{{.ImportPath}} {{.Dir}}' storj.io/drpc/... \ 9 | | while read -r -a line; do 10 | echo -n "| ${line[0]} | " 11 | find "${line[1]}" -maxdepth 1 -type f -name '*.go' ! -name '*_test.go' -print0 \ 12 | | xargs -0 cloc --json --quiet 2>/dev/null \ 13 | | jq -jr '.Go.code' 14 | echo " |" 15 | done \ 16 | | sort -rnk4 \ 17 | | awk '{total+=$4; print $0} END {print "| **Total** | **" total "** |"}' 18 | ) | column -to ' ' -------------------------------------------------------------------------------- /internal/backcompat/servicedefs.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/internal/backcompat/servicedefs"; 6 | 7 | package servicedefs; 8 | 9 | service Service { 10 | rpc Method1(In) returns (Out); 11 | rpc Method2(stream In) returns (Out); 12 | rpc Method3(In) returns (stream Out); 13 | rpc Method4(stream In) returns (stream Out); 14 | } 15 | 16 | message In { 17 | int64 in = 1; 18 | } 19 | 20 | message Out { 21 | int64 out = 1; 22 | } 23 | -------------------------------------------------------------------------------- /internal/integration/service/export.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package service is a testing service for the integration tests. 5 | package service 6 | 7 | import ( 8 | "google.golang.org/protobuf/proto" 9 | 10 | "storj.io/drpc" 11 | ) 12 | 13 | // Equal returns true if the two messages are equal. 14 | func Equal(a, b drpc.Message) bool { return proto.Equal(a.(proto.Message), b.(proto.Message)) } 15 | 16 | // Encoding is the drpc.Encoding used for this service. 17 | var Encoding drpcEncoding_File_service_proto 18 | -------------------------------------------------------------------------------- /internal/integration/customservice/export.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package service is a testing service for the integration tests. 5 | package service 6 | 7 | import ( 8 | "google.golang.org/protobuf/proto" 9 | 10 | "storj.io/drpc" 11 | ) 12 | 13 | // Equal returns true if the two messages are equal. 14 | func Equal(a, b drpc.Message) bool { return proto.Equal(a.(proto.Message), b.(proto.Message)) } 15 | 16 | // Encoding is the drpc.Encoding used for this service. 17 | var Encoding drpcEncoding_File_service_proto 18 | -------------------------------------------------------------------------------- /internal/integration/service.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/internal/integration/service"; 6 | 7 | package service; 8 | 9 | service Service { 10 | rpc Method1(In) returns (Out); 11 | rpc Method2(stream In) returns (Out); 12 | rpc Method3(In) returns (stream Out); 13 | rpc Method4(stream In) returns (stream Out); 14 | } 15 | 16 | message In { 17 | int64 in = 1; 18 | bytes data = 2; 19 | } 20 | 21 | message Out { 22 | int64 out = 1; 23 | bytes data = 2; 24 | } 25 | -------------------------------------------------------------------------------- /internal/backcompat/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package backcompat holds backwards-compatibility tests for drpc. 5 | package backcompat 6 | 7 | // In order to generate oldservice, run `go get storj.io/drpc/cmd/protoc-gen-drpc@v0.0.17` 8 | // and rename the resulting binary to be `protoc-gen-drpc17`. Then, execute the following 9 | // command: protoc --drpc17_out=paths=source_relative,plugins=drpc:oldservicedefs/. servicedefs.proto 10 | 11 | //go:generate protoc --go_out=paths=source_relative:newservicedefs/. --go-drpc_out=paths=source_relative:newservicedefs/. servicedefs.proto 12 | -------------------------------------------------------------------------------- /internal/backcompat/newservice/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/backcompat/newservice 2 | 3 | go 1.19 4 | 5 | require storj.io/drpc/internal/backcompat v0.0.0-00010101000000-000000000000 6 | 7 | require ( 8 | github.com/zeebo/errs v1.2.2 // indirect 9 | google.golang.org/protobuf v1.27.1 // indirect 10 | storj.io/drpc v0.0.0-00010101000000-000000000000 // indirect 11 | storj.io/drpc/internal/backcompat/servicedefs v0.0.0-00010101000000-000000000000 // indirect 12 | ) 13 | 14 | replace ( 15 | storj.io/drpc => ../../.. 16 | storj.io/drpc/internal/backcompat => ../ 17 | storj.io/drpc/internal/backcompat/servicedefs => ../newservicedefs 18 | ) 19 | -------------------------------------------------------------------------------- /examples/opentelemetry/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/examples/opentelemetry 2 | 3 | go 1.19 4 | 5 | require ( 6 | go.opentelemetry.io/otel v1.10.0 7 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.10.0 8 | go.opentelemetry.io/otel/sdk v1.10.0 9 | google.golang.org/protobuf v1.27.1 10 | storj.io/drpc v0.0.17 11 | ) 12 | 13 | require ( 14 | github.com/go-logr/logr v1.2.3 // indirect 15 | github.com/go-logr/stdr v1.2.2 // indirect 16 | github.com/zeebo/errs v1.2.2 // indirect 17 | go.opentelemetry.io/otel/trace v1.10.0 // indirect 18 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect 19 | ) 20 | 21 | replace storj.io/drpc => ../.. 22 | -------------------------------------------------------------------------------- /internal/grpccompat/service.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | syntax = "proto3"; 5 | option go_package = "storj.io/drpc/internal/grpccompat"; 6 | 7 | package service; 8 | 9 | service Service { 10 | rpc Method1(In) returns (Out); 11 | rpc Method2(stream In) returns (Out); 12 | rpc Method3(In) returns (stream Out); 13 | rpc Method4(stream In) returns (stream Out); 14 | } 15 | 16 | message In { 17 | int64 in = 1; 18 | bytes buf = 2; 19 | optional int64 opt = 3; 20 | } 21 | 22 | message Out { 23 | int64 out = 1; 24 | bytes buf = 2; 25 | optional int64 opt = 3; 26 | } 27 | -------------------------------------------------------------------------------- /drpcstream/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcstream sends protobufs using the dprc wire protocol. 5 | // 6 | // ![Stream state machine diagram](./state.png) 7 | package drpcstream 8 | 9 | // This go:generate directive creates the state.png from the state.dot file. Because the 10 | // generation outputs different binary data each time, it is protected by an if statement 11 | // to ensure that it only creates the png if the dot file has a newer modification time 12 | // somewhat like a Makefile. 13 | //go:generate bash -c "if [ state.dot -nt state.png ]; then dot -Tpng -o state.png state.dot; fi" 14 | -------------------------------------------------------------------------------- /internal/drpcopts/manager.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcopts 5 | 6 | import "storj.io/drpc/drpcstats" 7 | 8 | // Manager contains internal options for the drpcmanager package. 9 | type Manager struct { 10 | statsCB func(string) *drpcstats.Stats 11 | } 12 | 13 | // GetManagerStatsCB returns the stats callback stored in the options. 14 | func GetManagerStatsCB(opts *Manager) func(string) *drpcstats.Stats { return opts.statsCB } 15 | 16 | // SetManagerStatsCB sets the stats callback stored in the options. 17 | func SetManagerStatsCB(opts *Manager, statsCB func(string) *drpcstats.Stats) { opts.statsCB = statsCB } 18 | -------------------------------------------------------------------------------- /internal/integration/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package integration holds integration tests for drpc. 5 | package integration 6 | 7 | //go:generate protoc --go_out=paths=source_relative:service/. --go-drpc_out=paths=source_relative:service/. service.proto 8 | //go:generate protoc --gogo_out=paths=source_relative:gogoservice/. --go-drpc_out=paths=source_relative,protolib=github.com/gogo/protobuf:gogoservice/. service.proto 9 | //go:generate protoc --go_out=paths=source_relative:customservice/. --go-drpc_out=paths=source_relative,protolib=storj.io/drpc/internal/integration/customencoding:customservice/. service.proto 10 | -------------------------------------------------------------------------------- /drpcerr/README.md: -------------------------------------------------------------------------------- 1 | # package drpcerr 2 | 3 | `import "storj.io/drpc/drpcerr"` 4 | 5 | Package drpcerr lets one associate error codes with errors. 6 | 7 | ## Usage 8 | 9 | ```go 10 | const ( 11 | // Unimplemented is the code used by the generated unimplemented 12 | // servers when returning errors. 13 | Unimplemented = 12 14 | ) 15 | ``` 16 | 17 | #### func Code 18 | 19 | ```go 20 | func Code(err error) uint64 21 | ``` 22 | Code returns the error code associated with the error or 0 if none is. 23 | 24 | #### func WithCode 25 | 26 | ```go 27 | func WithCode(err error, code uint64) error 28 | ``` 29 | WithCode associates the code with the error if it is non nil and the code is 30 | non-zero. 31 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/examples/grpc_and_drpc 2 | 3 | go 1.19 4 | 5 | require ( 6 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 7 | google.golang.org/grpc v1.36.0 8 | google.golang.org/protobuf v1.27.1 9 | storj.io/drpc v0.0.17 10 | ) 11 | 12 | require ( 13 | github.com/golang/protobuf v1.5.0 // indirect 14 | github.com/zeebo/errs v1.2.2 // indirect 15 | golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect 16 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a // indirect 17 | golang.org/x/text v0.3.0 // indirect 18 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 19 | ) 20 | 21 | replace storj.io/drpc => ../.. 22 | -------------------------------------------------------------------------------- /internal/backcompat/oldservice/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/backcompat/oldservice 2 | 3 | go 1.19 4 | 5 | require storj.io/drpc/internal/backcompat v0.0.0-00010101000000-000000000000 6 | 7 | require ( 8 | github.com/gogo/protobuf v1.3.2 // indirect 9 | github.com/spacemonkeygo/monkit/v3 v3.0.7 // indirect 10 | github.com/zeebo/errs v1.2.2 // indirect 11 | storj.io/drpc v0.0.17 // indirect 12 | storj.io/drpc/internal/backcompat/servicedefs v0.0.0-00010101000000-000000000000 // indirect 13 | ) 14 | 15 | replace ( 16 | storj.io/drpc => storj.io/drpc v0.0.17 17 | storj.io/drpc/internal/backcompat => ../ 18 | storj.io/drpc/internal/backcompat/servicedefs => ../oldservicedefs 19 | ) 20 | -------------------------------------------------------------------------------- /drpcenc/marshal.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcenc 5 | 6 | import "storj.io/drpc" 7 | 8 | // MarshalAppend calls enc.Marshal(msg) and returns the data appended to buf. If 9 | // enc implements MarshalAppend, that is called instead. 10 | func MarshalAppend(msg drpc.Message, enc drpc.Encoding, buf []byte) (data []byte, err error) { 11 | if ma, ok := enc.(interface { 12 | MarshalAppend(buf []byte, msg drpc.Message) ([]byte, error) 13 | }); ok { 14 | return ma.MarshalAppend(buf, msg) 15 | } 16 | data, err = enc.Marshal(msg) 17 | if err != nil { 18 | return nil, err 19 | } 20 | return append(buf, data...), nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/backcompat/run_main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package backcompat 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "log" 10 | "os" 11 | ) 12 | 13 | // Main runs the service as either a client or server depending on os.Args. 14 | func Main(ctx context.Context) { 15 | var err error 16 | switch os.Args[1] { 17 | case "server": 18 | err = runServer(ctx, os.Args[2]) 19 | case "client": 20 | err = runClient(ctx, os.Args[2]) 21 | default: 22 | err = errors.New("unknown mode") 23 | } 24 | if err != nil { 25 | log.Fatalf("%+v", err) 26 | } else if err = ctx.Err(); err != nil && !errors.Is(err, context.Canceled) { 27 | log.Fatalf("%+v", err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /drpcwire/writer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "bytes" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | ) 12 | 13 | func TestWriter(t *testing.T) { 14 | run := func(size int) func(t *testing.T) { 15 | return func(t *testing.T) { 16 | var exp []byte 17 | var got bytes.Buffer 18 | 19 | wr := NewWriter(&got, size) 20 | for i := 0; i < 1000; i++ { 21 | fr := RandFrame() 22 | exp = AppendFrame(exp, fr) 23 | assert.NoError(t, wr.WriteFrame(fr)) 24 | } 25 | assert.NoError(t, wr.Flush()) 26 | assert.That(t, bytes.Equal(exp, got.Bytes())) 27 | } 28 | } 29 | 30 | t.Run("Size 0B", run(0)) 31 | t.Run("Size 1MB", run(1024*1024)) 32 | } 33 | -------------------------------------------------------------------------------- /drpcctx/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcctx 5 | 6 | import ( 7 | "context" 8 | 9 | "storj.io/drpc" 10 | ) 11 | 12 | // TransportKey is used to store the drpc.Transport with the context. 13 | type TransportKey struct{} 14 | 15 | // WithTransport associates the drpc.Transport as a value on the context. 16 | func WithTransport(ctx context.Context, tr drpc.Transport) context.Context { 17 | return context.WithValue(ctx, TransportKey{}, tr) 18 | } 19 | 20 | // Transport returns the drpc.Transport associated with the context and a bool if it 21 | // existed. 22 | func Transport(ctx context.Context) (drpc.Transport, bool) { 23 | tr, ok := ctx.Value(TransportKey{}).(drpc.Transport) 24 | return tr, ok 25 | } 26 | -------------------------------------------------------------------------------- /internal/grpccompat/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package grpccompat 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | "github.com/zeebo/errs" 12 | ) 13 | 14 | func TestError_ErrorPassedBack(t *testing.T) { 15 | impl := &serviceImpl{ 16 | Method4Fn: func(stream ServerMethod4Stream) error { 17 | return errs.New("marker") 18 | }, 19 | } 20 | 21 | testCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 22 | ctx, cancel := context.WithCancel(context.Background()) 23 | defer cancel() 24 | 25 | stream, err := cli.Method4(ctx) 26 | assert.NoError(t, err) 27 | 28 | ensure(stream.Recv()) 29 | ensure(nil, stream.Send(in(0))) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /drpcwire/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "encoding/binary" 8 | 9 | "github.com/zeebo/errs" 10 | 11 | "storj.io/drpc/drpcerr" 12 | ) 13 | 14 | // MarshalError returns a byte form of the error with any error code incorporated. 15 | func MarshalError(err error) []byte { 16 | var buf [8]byte 17 | binary.BigEndian.PutUint64(buf[:], drpcerr.Code(err)) 18 | return append(buf[:], err.Error()...) 19 | } 20 | 21 | // UnmarshalError unmarshals the marshaled error to one with a code. 22 | func UnmarshalError(data []byte) error { 23 | if len(data) < 8 { 24 | return errs.New("%s (drpcwire note: invalid error data)", data) 25 | } 26 | return drpcerr.WithCode(errs.New("%s", data[8:]), binary.BigEndian.Uint64(data[:8])) 27 | } 28 | -------------------------------------------------------------------------------- /scripts/docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | log() { 6 | echo "---" "$@" 7 | } 8 | 9 | # put the godocdown template in a temporary directory 10 | TEMPLATE=$(mktemp) 11 | trap 'rm ${TEMPLATE}' EXIT 12 | 13 | cat <"${TEMPLATE}" 14 | # package {{ .Name }} 15 | 16 | \`import "{{ .ImportPath }}"\` 17 | 18 | {{ .EmitSynopsis }} 19 | 20 | {{ .EmitUsage }} 21 | EOF 22 | 23 | # walk all the packages and generate docs 24 | PACKAGES=$(go list -f '{{ .ImportPath }}|{{ .Dir }}' storj.io/drpc/...) 25 | for DESC in ${PACKAGES}; do 26 | PACKAGE="$(echo "${DESC}" | cut -d '|' -f 1)" 27 | DIR="$(echo "${DESC}" | cut -d '|' -f 2)" 28 | 29 | if [[ "$PACKAGE" != "storj.io/drpc" ]]; then 30 | log "generating docs for ${PACKAGE}..." 31 | godocdown -template "${TEMPLATE}" "${DIR}" > "${DIR}/README.md" 32 | fi 33 | done 34 | -------------------------------------------------------------------------------- /drpcstats/README.md: -------------------------------------------------------------------------------- 1 | # package drpcstats 2 | 3 | `import "storj.io/drpc/drpcstats"` 4 | 5 | Package drpcstats contatins types for stat collection. 6 | 7 | ## Usage 8 | 9 | #### type Stats 10 | 11 | ```go 12 | type Stats struct { 13 | Read uint64 14 | Written uint64 15 | } 16 | ``` 17 | 18 | Stats keeps counters of read and written bytes. 19 | 20 | #### func (*Stats) AddRead 21 | 22 | ```go 23 | func (s *Stats) AddRead(n uint64) 24 | ``` 25 | AddRead atomically adds n bytes to the Read counter. 26 | 27 | #### func (*Stats) AddWritten 28 | 29 | ```go 30 | func (s *Stats) AddWritten(n uint64) 31 | ``` 32 | AddWritten atomically adds n bytes to the Written counter. 33 | 34 | #### func (*Stats) AtomicClone 35 | 36 | ```go 37 | func (s *Stats) AtomicClone() Stats 38 | ``` 39 | AtomicClone returns a copy of the stats that is safe to use concurrently with 40 | Add methods. 41 | -------------------------------------------------------------------------------- /drpcdebug/log_enabled.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | //go:build debug 5 | // +build debug 6 | 7 | package drpcdebug 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "os" 13 | "path/filepath" 14 | "runtime" 15 | ) 16 | 17 | var logger = log.New(os.Stderr, "", 0) 18 | 19 | // Log executes the callback for a string to log if built with the debug tag. 20 | func Log(cb func() (who, what, why string)) { 21 | _, file, line, _ := runtime.Caller(1) 22 | where := fmt.Sprintf("%s:%d", filepath.Base(file), line) 23 | who, what, why := cb() 24 | logger.Output(2, fmt.Sprintf("%24s | %-32s | %-6s | %s", 25 | where, who, what, why)) 26 | } 27 | 28 | // this exists to work around a bug in markdown doc generation so that it 29 | // does not generate two entries for Enabled, one set to true and one to false. 30 | const enabled = true 31 | -------------------------------------------------------------------------------- /internal/grpccompat/basic_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package grpccompat 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | "google.golang.org/protobuf/proto" 12 | ) 13 | 14 | func TestBasic(t *testing.T) { 15 | impl := &serviceImpl{ 16 | Method1Fn: func(ctx context.Context, in *In) (*Out, error) { 17 | return asOut(in), nil 18 | }, 19 | } 20 | 21 | testCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 22 | ctx, cancel := context.WithCancel(context.Background()) 23 | defer cancel() 24 | 25 | in := &In{ 26 | In: 5, 27 | Buf: []byte("foo"), 28 | Opt: proto.Int64(8), 29 | } 30 | 31 | out, err := cli.Method1(ctx, in) 32 | assert.NoError(t, err) 33 | assert.That(t, proto.Equal(out, asOut(in))) 34 | 35 | ensure(out, err) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /drpcpool/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpcpool is a simple connection pool for clients. 5 | // 6 | // It has the ability to maintain a cache of connections with a 7 | // maximum size on both the total and per key basis. It also 8 | // can expire cached connections if they have been inactive in 9 | // the pool for long enough. 10 | package drpcpool 11 | 12 | // closed is a helper to check if a notification channel has been closed. 13 | // It should not be called on channels that can have send operations 14 | // performed on it. 15 | func closed(ch <-chan struct{}) bool { 16 | select { 17 | case <-ch: 18 | return true 19 | default: 20 | return false 21 | } 22 | } 23 | 24 | // closedCh is an already closed channel. 25 | var closedCh = func() chan struct{} { 26 | ch := make(chan struct{}) 27 | close(ch) 28 | return ch 29 | }() 30 | -------------------------------------------------------------------------------- /drpcstats/stats.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2024 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcstats 5 | 6 | import ( 7 | "sync/atomic" 8 | ) 9 | 10 | // Stats keeps counters of read and written bytes. 11 | type Stats struct { 12 | Read uint64 13 | Written uint64 14 | } 15 | 16 | // AddRead atomically adds n bytes to the Read counter. 17 | func (s *Stats) AddRead(n uint64) { 18 | if s != nil { 19 | atomic.AddUint64(&s.Read, n) 20 | } 21 | } 22 | 23 | // AddWritten atomically adds n bytes to the Written counter. 24 | func (s *Stats) AddWritten(n uint64) { 25 | if s != nil { 26 | atomic.AddUint64(&s.Written, n) 27 | } 28 | } 29 | 30 | // AtomicClone returns a copy of the stats that is safe to use concurrently with Add methods. 31 | func (s *Stats) AtomicClone() Stats { 32 | return Stats{ 33 | Read: atomic.LoadUint64(&s.Read), 34 | Written: atomic.LoadUint64(&s.Written), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /drpcwire/packet_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Kind -trimprefix=Kind -output=packet_string.go"; DO NOT EDIT. 2 | 3 | package drpcwire 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[KindInvoke-1] 12 | _ = x[KindMessage-2] 13 | _ = x[KindError-3] 14 | _ = x[KindCancel-4] 15 | _ = x[KindClose-5] 16 | _ = x[KindCloseSend-6] 17 | _ = x[KindInvokeMetadata-7] 18 | } 19 | 20 | const _Kind_name = "InvokeMessageErrorCancelCloseCloseSendInvokeMetadata" 21 | 22 | var _Kind_index = [...]uint8{0, 6, 13, 18, 24, 29, 38, 52} 23 | 24 | func (i Kind) String() string { 25 | i -= 1 26 | if i >= Kind(len(_Kind_index)-1) { 27 | return "Kind(" + strconv.FormatInt(int64(i+1), 10) + ")" 28 | } 29 | return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] 30 | } 31 | -------------------------------------------------------------------------------- /drpcmux/README.md: -------------------------------------------------------------------------------- 1 | # package drpcmux 2 | 3 | `import "storj.io/drpc/drpcmux"` 4 | 5 | Package drpcmux is a handler to dispatch rpcs to implementations. 6 | 7 | ## Usage 8 | 9 | #### type Mux 10 | 11 | ```go 12 | type Mux struct { 13 | } 14 | ``` 15 | 16 | Mux is an implementation of Handler to serve drpc connections to the appropriate 17 | Receivers registered by Descriptions. 18 | 19 | #### func New 20 | 21 | ```go 22 | func New() *Mux 23 | ``` 24 | New constructs a new Mux. 25 | 26 | #### func (*Mux) HandleRPC 27 | 28 | ```go 29 | func (m *Mux) HandleRPC(stream drpc.Stream, rpc string) (err error) 30 | ``` 31 | HandleRPC handles the rpc that has been requested by the stream. 32 | 33 | #### func (*Mux) Register 34 | 35 | ```go 36 | func (m *Mux) Register(srv interface{}, desc drpc.Description) error 37 | ``` 38 | Register associates the RPCs described by the description in the server. It 39 | returns an error if there was a problem registering it. 40 | -------------------------------------------------------------------------------- /drpcwire/split_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "bytes" 8 | "math/rand" 9 | "testing" 10 | 11 | "github.com/zeebo/assert" 12 | ) 13 | 14 | func TestSplit(t *testing.T) { 15 | for i := 0; i < 1000; i++ { 16 | pkt, done, n := RandPacket(), false, rand.Intn(10)-1 17 | if size := rand.Intn(100); size < len(pkt.Data) { 18 | pkt.Data = pkt.Data[:size] 19 | } 20 | 21 | var buf []byte 22 | assert.NoError(t, SplitN(pkt, n, func(fr Frame) error { 23 | assert.That(t, !done) 24 | assert.That(t, len(fr.Data) <= n || 25 | (n == -1 && len(pkt.Data) == len(fr.Data)) || 26 | (n == 0 && len(fr.Data) <= 1024)) 27 | assert.Equal(t, pkt.Kind, fr.Kind) 28 | done = fr.Done 29 | buf = append(buf, fr.Data...) 30 | return nil 31 | })) 32 | 33 | assert.That(t, done) 34 | assert.That(t, bytes.Equal(pkt.Data, buf)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/grpccompat/go.mod: -------------------------------------------------------------------------------- 1 | module storj.io/drpc/internal/grpccompat 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/improbable-eng/grpc-web v0.15.0 7 | github.com/zeebo/assert v1.3.0 8 | github.com/zeebo/errs v1.2.2 9 | google.golang.org/grpc v1.64.0 10 | google.golang.org/protobuf v1.34.1 11 | storj.io/drpc v0.0.0-00010101000000-000000000000 12 | ) 13 | 14 | require ( 15 | github.com/cenkalti/backoff/v4 v4.1.1 // indirect 16 | github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect 17 | github.com/golang/protobuf v1.5.4 // indirect 18 | github.com/klauspost/compress v1.11.7 // indirect 19 | github.com/rs/cors v1.8.0 // indirect 20 | golang.org/x/net v0.22.0 // indirect 21 | golang.org/x/sys v0.18.0 // indirect 22 | golang.org/x/text v0.14.0 // indirect 23 | google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 // indirect 24 | nhooyr.io/websocket v1.8.7 // indirect 25 | ) 26 | 27 | replace storj.io/drpc => ../.. 28 | -------------------------------------------------------------------------------- /drpcserver/util_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | //go:build windows 5 | // +build windows 6 | 7 | package drpcserver 8 | 9 | import ( 10 | "errors" 11 | "net" 12 | "os" 13 | "syscall" 14 | ) 15 | 16 | const ( 17 | _WSAEMFILE syscall.Errno = 10024 18 | _WSAENETRESET syscall.Errno = 10052 19 | _WSAENOBUFS syscall.Errno = 10055 20 | ) 21 | 22 | // isTemporary checks if an error is temporary. 23 | // see related go issue for more detail: https://go-review.googlesource.com/c/go/+/208537/ 24 | func isTemporary(err error) bool { 25 | var nErr net.Error 26 | if !errors.As(err, &nErr) { 27 | return false 28 | } 29 | 30 | if nErr.Temporary() { 31 | return true 32 | } 33 | 34 | var sErr *os.SyscallError 35 | if errors.As(err, &sErr) { 36 | switch sErr.Err { 37 | case _WSAENETRESET, 38 | _WSAEMFILE, 39 | _WSAENOBUFS: 40 | return true 41 | } 42 | } 43 | 44 | return false 45 | } 46 | -------------------------------------------------------------------------------- /internal/twirpcompat/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package twirpcompat 5 | 6 | import ( 7 | "context" 8 | "net/http/httptest" 9 | 10 | "github.com/zeebo/hmux" 11 | 12 | "storj.io/drpc/drpchttp" 13 | "storj.io/drpc/drpcmux" 14 | ) 15 | 16 | type compatService struct { 17 | method func(context.Context, *Req) (*Resp, error) 18 | noop func(context.Context, *Empty) (*Empty, error) 19 | } 20 | 21 | func (cs *compatService) Method(ctx context.Context, req *Req) (*Resp, error) { 22 | return cs.method(ctx, req) 23 | } 24 | 25 | func (cs *compatService) NoopMethod(ctx context.Context, req *Empty) (*Empty, error) { 26 | return cs.noop(ctx, req) 27 | } 28 | 29 | func newServer() (*compatService, *httptest.Server) { 30 | cs := new(compatService) 31 | mux := drpcmux.New() 32 | _ = DRPCRegisterCompatService(mux, cs) 33 | return cs, httptest.NewServer(hmux.Dir{"/twirp": drpchttp.New(mux)}) 34 | } 35 | -------------------------------------------------------------------------------- /drpcmigrate/listener_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmigrate 5 | 6 | import ( 7 | "net" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | ) 12 | 13 | func TestListener(t *testing.T) { 14 | type addr struct{ net.Addr } 15 | type conn struct{ net.Conn } 16 | 17 | lis := newListener(addr{}) 18 | 19 | { // ensure the addr is the same we passed in 20 | assert.Equal(t, lis.Addr(), addr{}) 21 | } 22 | 23 | { // ensure that we can accept a connection from the listener 24 | go func() { lis.Conns() <- conn{} }() 25 | c, err := lis.Accept() 26 | assert.NoError(t, err) 27 | assert.DeepEqual(t, c, conn{}) 28 | } 29 | 30 | { // ensure that closing the listener is no problem 31 | assert.NoError(t, lis.Close()) 32 | } 33 | 34 | { // ensure that accept after close returns the right error 35 | c, err := lis.Accept() 36 | assert.Equal(t, err, Closed) 37 | assert.Nil(t, c) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/opentelemetry/server/otel_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/propagation" 11 | "storj.io/drpc" 12 | "storj.io/drpc/drpcmetadata" 13 | ) 14 | 15 | type streamWrapper struct { 16 | drpc.Stream 17 | ctx context.Context 18 | } 19 | 20 | func (s *streamWrapper) Context() context.Context { return s.ctx } 21 | 22 | type otelHandler struct { 23 | handler drpc.Handler 24 | } 25 | 26 | func (t *otelHandler) HandleRPC(stream drpc.Stream, rpc string) (err error) { 27 | metadata, ok := drpcmetadata.Get(stream.Context()) 28 | if ok { 29 | ctx := otel.GetTextMapPropagator().Extract(stream.Context(), propagation.MapCarrier(metadata)) 30 | ctx, span := tracer.Start(ctx, "HandleRPC") 31 | defer span.End() 32 | stream = &streamWrapper{Stream: stream, ctx: ctx} 33 | } 34 | return t.handler.HandleRPC(stream, rpc) 35 | } 36 | -------------------------------------------------------------------------------- /examples/drpc/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 5 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 6 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 7 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 8 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 9 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 10 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 11 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 12 | -------------------------------------------------------------------------------- /internal/backcompat/newservice/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 5 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 6 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 7 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 8 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 9 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 10 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 11 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 12 | -------------------------------------------------------------------------------- /internal/backcompat/newservicedefs/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 5 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 6 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 7 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 8 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 9 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 10 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 11 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 5 | github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 6 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 7 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 8 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 9 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 10 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 11 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 12 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 13 | -------------------------------------------------------------------------------- /drpcwire/varint.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "storj.io/drpc" 8 | ) 9 | 10 | // ReadVarint reads a varint encoded integer from the front of buf, returning the 11 | // remaining bytes, the value, and if there was a success. if ok is false, the 12 | // returned buffer is the same as the passed in buffer. 13 | func ReadVarint(buf []byte) (rem []byte, out uint64, ok bool, err error) { 14 | rem = buf 15 | for shift := uint(0); shift < 64; shift += 7 { 16 | if len(rem) == 0 { 17 | return buf, 0, false, nil 18 | } 19 | val := uint64(rem[0]) 20 | out, rem = out|((val&127)<= 128 { 31 | buf = append(buf, byte(x&127|128)) 32 | x >>= 7 33 | } 34 | return append(buf, byte(x)) 35 | } 36 | -------------------------------------------------------------------------------- /internal/integration/alias.go: -------------------------------------------------------------------------------- 1 | // Code generated by hand. DO NOT EDIT. 2 | // 3 | // I lied. It's not generated, but we use names from generated code, and linting 4 | // hates it. So, just pretend this is generated and everything will be fine. 5 | 6 | // Copyright (C) 2021 Storj Labs, Inc. 7 | // See LICENSE for copying information. 8 | 9 | //go:build !gogo && !custom 10 | // +build !gogo,!custom 11 | 12 | package integration 13 | 14 | import ( 15 | "storj.io/drpc/internal/integration/service" 16 | ) 17 | 18 | type ( 19 | In = service.In 20 | Out = service.Out 21 | 22 | DRPCServiceServer = service.DRPCServiceServer 23 | DRPCServiceClient = service.DRPCServiceClient 24 | 25 | DRPCService_Method2Stream = service.DRPCService_Method2Stream 26 | DRPCService_Method3Stream = service.DRPCService_Method3Stream 27 | DRPCService_Method4Stream = service.DRPCService_Method4Stream 28 | ) 29 | 30 | var ( 31 | NewDRPCServiceClient = service.NewDRPCServiceClient 32 | DRPCRegisterService = service.DRPCRegisterService 33 | Equal = service.Equal 34 | Encoding = service.Encoding 35 | ) 36 | -------------------------------------------------------------------------------- /internal/integration/alias_gogo.go: -------------------------------------------------------------------------------- 1 | // Code generated by hand. DO NOT EDIT. 2 | // 3 | // I lied. It's not generated, but we use names from generated code, and linting 4 | // hates it. So, just pretend this is generated and everything will be fine. 5 | 6 | // Copyright (C) 2021 Storj Labs, Inc. 7 | // See LICENSE for copying information. 8 | 9 | //go:build gogo && !custom 10 | // +build gogo,!custom 11 | 12 | package integration 13 | 14 | import ( 15 | "storj.io/drpc/internal/integration/gogoservice" 16 | ) 17 | 18 | type ( 19 | In = service.In 20 | Out = service.Out 21 | 22 | DRPCServiceServer = service.DRPCServiceServer 23 | DRPCServiceClient = service.DRPCServiceClient 24 | 25 | DRPCService_Method2Stream = service.DRPCService_Method2Stream 26 | DRPCService_Method3Stream = service.DRPCService_Method3Stream 27 | DRPCService_Method4Stream = service.DRPCService_Method4Stream 28 | ) 29 | 30 | var ( 31 | NewDRPCServiceClient = service.NewDRPCServiceClient 32 | DRPCRegisterService = service.DRPCRegisterService 33 | Equal = service.Equal 34 | Encoding = service.Encoding 35 | ) 36 | -------------------------------------------------------------------------------- /internal/integration/alias_custom.go: -------------------------------------------------------------------------------- 1 | // Code generated by hand. DO NOT EDIT. 2 | // 3 | // I lied. It's not generated, but we use names from generated code, and linting 4 | // hates it. So, just pretend this is generated and everything will be fine. 5 | 6 | // Copyright (C) 2021 Storj Labs, Inc. 7 | // See LICENSE for copying information. 8 | 9 | //go:build !gogo && custom 10 | // +build !gogo,custom 11 | 12 | package integration 13 | 14 | import ( 15 | "storj.io/drpc/internal/integration/customservice" 16 | ) 17 | 18 | type ( 19 | In = service.In 20 | Out = service.Out 21 | 22 | DRPCServiceServer = service.DRPCServiceServer 23 | DRPCServiceClient = service.DRPCServiceClient 24 | 25 | DRPCService_Method2Stream = service.DRPCService_Method2Stream 26 | DRPCService_Method3Stream = service.DRPCService_Method3Stream 27 | DRPCService_Method4Stream = service.DRPCService_Method4Stream 28 | ) 29 | 30 | var ( 31 | NewDRPCServiceClient = service.NewDRPCServiceClient 32 | DRPCRegisterService = service.DRPCRegisterService 33 | Equal = service.Equal 34 | Encoding = service.Encoding 35 | ) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Storj Labs, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /examples/grpc/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "time" 10 | 11 | "google.golang.org/grpc" 12 | 13 | "storj.io/drpc/examples/grpc/pb" 14 | ) 15 | 16 | func main() { 17 | err := Main(context.Background()) 18 | if err != nil { 19 | panic(err) 20 | } 21 | } 22 | 23 | func Main(ctx context.Context) error { 24 | // dial the grpc server (without TLS) 25 | conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure(), grpc.WithBlock()) 26 | if err != nil { 27 | return err 28 | } 29 | defer conn.Close() 30 | 31 | // make a grpc proto-specific client 32 | client := pb.NewCookieMonsterClient(conn) 33 | 34 | // set a deadline for the operation 35 | ctx, cancel := context.WithTimeout(ctx, time.Second) 36 | defer cancel() 37 | 38 | // run the RPC 39 | crumbs, err := client.EatCookie(ctx, &pb.Cookie{ 40 | Type: pb.Cookie_Oatmeal, 41 | }) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | // check the results 47 | _, err = fmt.Println(crumbs.Cookie.Type.String()) 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /drpcmetadata/README.md: -------------------------------------------------------------------------------- 1 | # package drpcmetadata 2 | 3 | `import "storj.io/drpc/drpcmetadata"` 4 | 5 | Package drpcmetadata define the structure of the metadata supported by drpc 6 | library. 7 | 8 | ## Usage 9 | 10 | #### func Add 11 | 12 | ```go 13 | func Add(ctx context.Context, key, value string) context.Context 14 | ``` 15 | Add associates a key/value pair on the context. 16 | 17 | #### func AddPairs 18 | 19 | ```go 20 | func AddPairs(ctx context.Context, metadata map[string]string) context.Context 21 | ``` 22 | AddPairs attaches metadata onto a context and return the context. 23 | 24 | #### func Decode 25 | 26 | ```go 27 | func Decode(buf []byte) (map[string]string, error) 28 | ``` 29 | Decode translate byte form of metadata into key/value metadata. 30 | 31 | #### func Encode 32 | 33 | ```go 34 | func Encode(buf []byte, metadata map[string]string) ([]byte, error) 35 | ``` 36 | Encode generates byte form of the metadata and appends it onto the passed in 37 | buffer. 38 | 39 | #### func Get 40 | 41 | ```go 42 | func Get(ctx context.Context) (map[string]string, bool) 43 | ``` 44 | Get returns all key/value pairs on the given context. 45 | -------------------------------------------------------------------------------- /drpcmux/handle_rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmux 5 | 6 | import ( 7 | "reflect" 8 | 9 | "github.com/zeebo/errs" 10 | 11 | "storj.io/drpc" 12 | ) 13 | 14 | // HandleRPC handles the rpc that has been requested by the stream. 15 | func (m *Mux) HandleRPC(stream drpc.Stream, rpc string) (err error) { 16 | data, ok := m.rpcs[rpc] 17 | if !ok { 18 | return drpc.ProtocolError.New("unknown rpc: %q", rpc) 19 | } 20 | 21 | in := interface{}(stream) 22 | if data.in1 != streamType { 23 | msg, ok := reflect.New(data.in1.Elem()).Interface().(drpc.Message) 24 | if !ok { 25 | return drpc.InternalError.New("invalid rpc input type") 26 | } 27 | if err := stream.MsgRecv(msg, data.enc); err != nil { 28 | return errs.Wrap(err) 29 | } 30 | in = msg 31 | } 32 | 33 | out, err := data.receiver(data.srv, stream.Context(), in, stream) 34 | switch { 35 | case err != nil: 36 | return errs.Wrap(err) 37 | case out != nil && !reflect.ValueOf(out).IsNil(): 38 | return stream.MsgSend(out, data.enc) 39 | default: 40 | return stream.CloseSend() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /drpcmanager/streambuf.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmanager 5 | 6 | import ( 7 | "sync" 8 | "sync/atomic" 9 | 10 | "storj.io/drpc/drpcstream" 11 | ) 12 | 13 | type streamBuffer struct { 14 | mu sync.Mutex 15 | cond sync.Cond 16 | stream atomic.Pointer[drpcstream.Stream] 17 | closed bool 18 | } 19 | 20 | func (sb *streamBuffer) init() { 21 | sb.cond.L = &sb.mu 22 | } 23 | 24 | func (sb *streamBuffer) Close() { 25 | sb.mu.Lock() 26 | defer sb.mu.Unlock() 27 | 28 | sb.closed = true 29 | sb.cond.Broadcast() 30 | } 31 | 32 | func (sb *streamBuffer) Get() *drpcstream.Stream { 33 | return sb.stream.Load() 34 | } 35 | 36 | func (sb *streamBuffer) Set(stream *drpcstream.Stream) { 37 | sb.mu.Lock() 38 | defer sb.mu.Unlock() 39 | 40 | if sb.closed { 41 | return 42 | } 43 | 44 | sb.stream.Store(stream) 45 | sb.cond.Broadcast() 46 | } 47 | 48 | func (sb *streamBuffer) Wait(sid uint64) bool { 49 | sb.mu.Lock() 50 | defer sb.mu.Unlock() 51 | 52 | for !sb.closed && sb.Get().ID() == sid { 53 | sb.cond.Wait() 54 | } 55 | 56 | return !sb.closed 57 | } 58 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { 3 | docker { 4 | label 'main' 5 | image 'storjlabs/ci:latest' 6 | alwaysPull true 7 | args '-u root:root --cap-add SYS_PTRACE -v "/tmp/gomod":/go/pkg/mod' 8 | } 9 | } 10 | 11 | options { 12 | timeout(time: 26, unit: 'MINUTES') 13 | } 14 | 15 | environment { 16 | GOTRACEBACK = 'all' 17 | } 18 | 19 | stages { 20 | stage('Checkout') { 21 | steps { 22 | checkout scm 23 | } 24 | } 25 | 26 | stage('Test') { 27 | steps { 28 | sh 'make test' 29 | } 30 | } 31 | 32 | stage('Lint') { 33 | steps { 34 | sh 'make lint' 35 | } 36 | } 37 | 38 | stage('Vet') { 39 | steps { 40 | sh 'make vet' 41 | } 42 | } 43 | } 44 | 45 | post { 46 | always { 47 | sh "chmod -R 777 ." // ensure Jenkins agent can delete the working directory 48 | deleteDir() 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/grpc_client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "time" 10 | 11 | "google.golang.org/grpc" 12 | 13 | "storj.io/drpc/examples/grpc_and_drpc/pb" 14 | ) 15 | 16 | func main() { 17 | err := Main(context.Background()) 18 | if err != nil { 19 | panic(err) 20 | } 21 | } 22 | 23 | func Main(ctx context.Context) error { 24 | // dial the grpc server (without TLS or a connection header) 25 | conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure(), grpc.WithBlock()) 26 | if err != nil { 27 | return err 28 | } 29 | defer conn.Close() 30 | 31 | // make a grpc proto-specific client 32 | client := pb.NewCookieMonsterClient(conn) 33 | 34 | // set a deadline for the operation 35 | ctx, cancel := context.WithTimeout(ctx, time.Second) 36 | defer cancel() 37 | 38 | // run the RPC 39 | crumbs, err := client.EatCookie(ctx, &pb.Cookie{ 40 | Type: pb.Cookie_Oatmeal, 41 | }) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | // check the results 47 | _, err = fmt.Println(crumbs.Cookie.Type.String()) 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /drpcctx/tracker.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcctx 5 | 6 | import ( 7 | "context" 8 | "sync" 9 | ) 10 | 11 | // Tracker keeps track of launched goroutines with a context. 12 | type Tracker struct { 13 | context.Context 14 | cancel func() 15 | wg sync.WaitGroup 16 | } 17 | 18 | // NewTracker creates a Tracker bound to the provided context. 19 | func NewTracker(ctx context.Context) *Tracker { 20 | ctx, cancel := context.WithCancel(ctx) 21 | return &Tracker{ 22 | Context: ctx, 23 | cancel: cancel, 24 | } 25 | } 26 | 27 | // Run starts a goroutine running the callback with the tracker as the context. 28 | func (t *Tracker) Run(cb func(ctx context.Context)) { 29 | t.wg.Add(1) 30 | go t.track(cb) 31 | } 32 | 33 | // track is a helper to call done on the waitgroup after the callback returns. 34 | func (t *Tracker) track(cb func(ctx context.Context)) { 35 | cb(t) 36 | t.wg.Done() 37 | } 38 | 39 | // Cancel cancels the tracker's context. 40 | func (t *Tracker) Cancel() { t.cancel() } 41 | 42 | // Wait blocks until all callbacks started with Run have exited. 43 | func (t *Tracker) Wait() { t.wg.Wait() } 44 | -------------------------------------------------------------------------------- /drpcstream/state.dot: -------------------------------------------------------------------------------- 1 | digraph stream { 2 | rankdir=LR 3 | open -> "send-closed" [label="{CloseSend}",color=blue,fontcolor=blue]; 4 | open -> "recv-closed" [label="{RecvCloseSend}",color=green,fontcolor=green]; 5 | open -> terminated [label="{Close, SendError}",color=red,fontcolor=red]; 6 | open -> canceled [label="{Cancel}",color=orange,fontcolor=orange]; 7 | open -> open [label="{MsgSend, MsgRecv}"]; 8 | 9 | "send-closed" -> terminated [label="{Close, SendError}",color=red,fontcolor=red]; 10 | "send-closed" -> terminated [label="{RecvCloseSend}",color=green,fontcolor=green] 11 | "send-closed" -> canceled [label="{Cancel}",color=orange,fontcolor=orange]; 12 | "send-closed" -> "send-closed" [label="{MsgRecv}"]; 13 | 14 | "recv-closed" -> terminated [label="{Close, SendError}",color=red,fontcolor=red]; 15 | "recv-closed" -> terminated [label="{CloseSend}",color=blue,fontcolor=blue] 16 | "recv-closed" -> canceled [label="{Cancel}",color=orange,fontcolor=orange]; 17 | "recv-closed" -> "recv-closed" [label="{MsgSend}"]; 18 | 19 | canceled -> finished [label="{Quiescence}"]; 20 | terminated -> finished [label="{Quiescence}"]; 21 | } 22 | -------------------------------------------------------------------------------- /drpcstream/inspectmu.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcstream 5 | 6 | import ( 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | type inspectMutex struct { 12 | sync.Mutex 13 | held uint32 14 | } 15 | 16 | func (m *inspectMutex) Lock() { 17 | m.Mutex.Lock() 18 | atomic.StoreUint32(&m.held, 1) 19 | } 20 | 21 | func (m *inspectMutex) TryLock() bool { 22 | if m.Mutex.TryLock() { 23 | atomic.StoreUint32(&m.held, 1) 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | func (m *inspectMutex) Unlock() { 30 | atomic.StoreUint32(&m.held, 0) 31 | m.Mutex.Unlock() 32 | } 33 | 34 | // Unlocked returns true if the mutex is either currently unlocked or in the 35 | // process of unlocking, meaning that no potentially blocking operations will be 36 | // executed before the mutex is unlocked. In the presence of concurrent Lock and 37 | // Unlock calls this function can only be advisory at best. Any information 38 | // returned from it is potentially stale and does not necessarily reflect the 39 | // current state of the mutex. 40 | func (m *inspectMutex) Unlocked() bool { 41 | return atomic.LoadUint32(&m.held) == 0 42 | } 43 | -------------------------------------------------------------------------------- /examples/grpc/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "net" 9 | 10 | "google.golang.org/grpc" 11 | 12 | "storj.io/drpc/examples/grpc/pb" 13 | ) 14 | 15 | type CookieMonsterServer struct { 16 | pb.UnimplementedCookieMonsterServer 17 | // struct fields 18 | } 19 | 20 | // EatCookie turns a cookie into crumbs. 21 | func (s *CookieMonsterServer) EatCookie(ctx context.Context, cookie *pb.Cookie) (*pb.Crumbs, error) { 22 | return &pb.Crumbs{ 23 | Cookie: cookie, 24 | }, nil 25 | } 26 | 27 | func main() { 28 | err := Main(context.Background()) 29 | if err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | func Main(ctx context.Context) error { 35 | // create an RPC server 36 | cookieMonster := &CookieMonsterServer{} 37 | 38 | // create a grpc server (without TLS) 39 | s := grpc.NewServer() 40 | 41 | // register the proto-specific methods on the server 42 | pb.RegisterCookieMonsterServer(s, cookieMonster) 43 | 44 | // listen on a tcp socket 45 | lis, err := net.Listen("tcp", ":8080") 46 | if err != nil { 47 | return err 48 | } 49 | 50 | // run the server 51 | return s.Serve(lis) 52 | } 53 | -------------------------------------------------------------------------------- /drpcserver/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcserver 5 | 6 | import ( 7 | "net" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | 12 | "storj.io/drpc/drpctest" 13 | ) 14 | 15 | func init() { temporarySleep = 0 } 16 | 17 | func TestServerTemporarySleep(t *testing.T) { 18 | ctx := drpctest.NewTracker(t) 19 | defer ctx.Close() 20 | 21 | calls := 0 22 | l := listener(func() (net.Conn, error) { 23 | calls++ 24 | switch calls { 25 | case 1: 26 | case 2: 27 | ctx.Cancel() 28 | default: 29 | panic("spinning on temporary error") 30 | } 31 | 32 | return nil, new(temporaryError) 33 | }) 34 | 35 | assert.NoError(t, New(nil).Serve(ctx, l)) 36 | } 37 | 38 | type listener func() (net.Conn, error) 39 | 40 | func (l listener) Accept() (net.Conn, error) { return l() } 41 | func (l listener) Close() error { return nil } 42 | func (l listener) Addr() net.Addr { return nil } 43 | 44 | type temporaryError struct{} 45 | 46 | func (temporaryError) Error() string { return "temporary error" } 47 | func (temporaryError) Timeout() bool { return false } 48 | func (temporaryError) Temporary() bool { return true } 49 | -------------------------------------------------------------------------------- /examples/drpc_and_http/http_client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "net/http" 11 | "strings" 12 | ) 13 | 14 | func main() { 15 | err := Main(context.Background()) 16 | if err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | func Main(ctx context.Context) error { 22 | const baseURL = "http://localhost:8080" 23 | 24 | // make the http request 25 | resp, err := http.Post(baseURL+"/sesamestreet.CookieMonster/EatCookie", 26 | "application/json", strings.NewReader(`{"type": "Chocolate"}`)) 27 | if err != nil { 28 | return err 29 | } 30 | defer resp.Body.Close() 31 | 32 | // confirm the http layer worked okay 33 | if resp.StatusCode != http.StatusOK { 34 | return fmt.Errorf("unexpected http status %q", resp.StatusCode) 35 | } 36 | 37 | // parse the response 38 | var data struct { 39 | Cookie struct { 40 | Type string `json:"type"` 41 | } `json:"cookie"` 42 | } 43 | err = json.NewDecoder(resp.Body).Decode(&data) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | // check the results 49 | _, err = fmt.Println(data.Cookie.Type) 50 | return err 51 | } 52 | -------------------------------------------------------------------------------- /drpctest/README.md: -------------------------------------------------------------------------------- 1 | # package drpctest 2 | 3 | `import "storj.io/drpc/drpctest"` 4 | 5 | Package drpctest provides test related helpers. 6 | 7 | ## Usage 8 | 9 | #### type Tracker 10 | 11 | ```go 12 | type Tracker struct { 13 | context.Context 14 | } 15 | ``` 16 | 17 | Tracker keeps track of launched goroutines with a context. 18 | 19 | #### func NewTracker 20 | 21 | ```go 22 | func NewTracker(tb testing.TB) *Tracker 23 | ``` 24 | NewTracker creates a new tracker that inspects the provided TB to see if tests 25 | have failed in any of its launched goroutines. 26 | 27 | #### func (*Tracker) Cancel 28 | 29 | ```go 30 | func (t *Tracker) Cancel() 31 | ``` 32 | Cancel cancels the tracker's context. 33 | 34 | #### func (*Tracker) Close 35 | 36 | ```go 37 | func (t *Tracker) Close() 38 | ``` 39 | Close cancels the context and waits for all of the goroutines started by Run to 40 | finish. 41 | 42 | #### func (*Tracker) Run 43 | 44 | ```go 45 | func (t *Tracker) Run(cb func(ctx context.Context)) 46 | ``` 47 | Run starts a goroutine running the callback with the tracker as the context. 48 | 49 | #### func (*Tracker) Wait 50 | 51 | ```go 52 | func (t *Tracker) Wait() 53 | ``` 54 | Wait blocks until all callbacks started with Run have exited. 55 | -------------------------------------------------------------------------------- /examples/drpc_and_http/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 5 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 6 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 7 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 8 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 9 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 10 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 11 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 12 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 13 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 14 | -------------------------------------------------------------------------------- /drpcwire/split.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | // SplitN splits the marshaled form of the Packet into a number of 7 | // frames such that each frame is at most n bytes. It calls 8 | // the callback with every such frame. If n is zero, a reasonable 9 | // default is used. 10 | func SplitN(pkt Packet, n int, cb func(fr Frame) error) error { 11 | for { 12 | fr := Frame{ 13 | Data: pkt.Data, 14 | ID: pkt.ID, 15 | Kind: pkt.Kind, 16 | Control: pkt.Control, 17 | Done: true, 18 | } 19 | 20 | fr.Data, pkt.Data = SplitData(pkt.Data, n) 21 | fr.Done = len(pkt.Data) == 0 22 | 23 | if err := cb(fr); err != nil { 24 | return err 25 | } 26 | if fr.Done { 27 | return nil 28 | } 29 | } 30 | } 31 | 32 | // SplitData is used to split a buffer if it is larger than n bytes. 33 | // If n is zero, a reasonable default is used. If n is less than zero 34 | // then it does not split. 35 | func SplitData(buf []byte, n int) (prefix, suffix []byte) { 36 | switch { 37 | case n == 0: 38 | n = 64 * 1024 39 | case n < 0: 40 | n = 0 41 | } 42 | 43 | if len(buf) > n && n > 0 { 44 | return buf[:n], buf[n:] 45 | } 46 | return buf, nil 47 | } 48 | -------------------------------------------------------------------------------- /internal/integration/customencoding/customencoding.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package customencoding is a testing custom encoding for the integration tests. 5 | package customencoding 6 | 7 | import ( 8 | "google.golang.org/protobuf/encoding/protojson" 9 | "google.golang.org/protobuf/proto" 10 | 11 | "storj.io/drpc" 12 | ) 13 | 14 | // Marshal returns the encoded form of msg. 15 | func Marshal(msg drpc.Message) ([]byte, error) { 16 | return proto.Marshal(msg.(proto.Message)) 17 | } 18 | 19 | // Unmarshal reads the encoded form of some Message into msg. 20 | // The buf is expected to contain only a single complete Message. 21 | func Unmarshal(buf []byte, msg drpc.Message) error { 22 | return proto.Unmarshal(buf, msg.(proto.Message)) 23 | } 24 | 25 | // JSONMarshal returns the json encoded form of msg. 26 | func JSONMarshal(msg drpc.Message) ([]byte, error) { 27 | return protojson.Marshal(msg.(proto.Message)) 28 | } 29 | 30 | // JSONUnmarshal reads the json encoded form of some Message into msg. 31 | // The buf is expected to contain only a single complete Message. 32 | func JSONUnmarshal(buf []byte, msg drpc.Message) error { 33 | return protojson.Unmarshal(buf, msg.(proto.Message)) 34 | } 35 | -------------------------------------------------------------------------------- /examples/drpc/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "time" 11 | 12 | "storj.io/drpc/drpcconn" 13 | 14 | "storj.io/drpc/examples/drpc/pb" 15 | ) 16 | 17 | func main() { 18 | err := Main(context.Background()) 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | func Main(ctx context.Context) error { 25 | // dial the drpc server 26 | rawconn, err := net.Dial("tcp", "localhost:8080") 27 | if err != nil { 28 | return err 29 | } 30 | // N.B.: If you want TLS, you need to wrap the net.Conn with TLS before 31 | // making a DRPC conn. 32 | 33 | // convert the net.Conn to a drpc.Conn 34 | conn := drpcconn.New(rawconn) 35 | defer conn.Close() 36 | 37 | // make a drpc proto-specific client 38 | client := pb.NewDRPCCookieMonsterClient(conn) 39 | 40 | // set a deadline for the operation 41 | ctx, cancel := context.WithTimeout(ctx, time.Second) 42 | defer cancel() 43 | 44 | // run the RPC 45 | crumbs, err := client.EatCookie(ctx, &pb.Cookie{ 46 | Type: pb.Cookie_Oatmeal, 47 | }) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // check the results 53 | _, err = fmt.Println(crumbs.Cookie.Type.String()) 54 | return err 55 | } 56 | -------------------------------------------------------------------------------- /drpcerr/err_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcerr 5 | 6 | import ( 7 | "errors" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | "github.com/zeebo/errs" 12 | ) 13 | 14 | func TestCode(t *testing.T) { 15 | // no error should still be nil 16 | assert.Nil(t, WithCode(nil, 5)) 17 | 18 | // no wrapping should be ok 19 | assert.Equal(t, Code(WithCode(errors.New("test"), 5)), 5) 20 | 21 | // one layer of wrapping that can be unwrapped should be ok 22 | assert.Equal(t, Code(errs.Wrap(WithCode(errors.New("test"), 5))), 5) 23 | 24 | // not implementing any interface should be ok 25 | assert.Equal(t, Code(errors.New("foo")), 0) 26 | 27 | // cycles should be handled ok 28 | assert.Equal(t, Code(cycle{}), 0) 29 | 30 | // uncomparable should be ok 31 | assert.Equal(t, Code(uncomparable{}), 0) 32 | 33 | // opaque should remove the code 34 | assert.Equal(t, Code(opaque{WithCode(errors.New("test"), 5)}), 0) 35 | } 36 | 37 | type cycle struct{} 38 | 39 | func (s cycle) Error() string { return "cycle" } 40 | func (s cycle) Unwrap() error { return s } 41 | 42 | type uncomparable struct{ _ [0]func() } 43 | 44 | func (u uncomparable) Error() string { return "uncomparable" } 45 | func (u uncomparable) Unwrap() error { return u } 46 | 47 | type opaque struct{ error } 48 | -------------------------------------------------------------------------------- /drpcmigrate/header.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmigrate 5 | 6 | import ( 7 | "net" 8 | "sync" 9 | ) 10 | 11 | // DRPCHeader is a header for DRPC connections to use. This is designed 12 | // to not conflict with a headerless gRPC, HTTP, or TLS request. 13 | var DRPCHeader = "DRPC!!!1" 14 | 15 | // HeaderConn fulfills the net.Conn interface. On the first call to Write 16 | // it will write the Header. 17 | type HeaderConn struct { 18 | net.Conn 19 | once sync.Once 20 | header string 21 | } 22 | 23 | // NewHeaderConn returns a new *HeaderConn that writes the provided header 24 | // as part of the first Write. 25 | func NewHeaderConn(conn net.Conn, header string) *HeaderConn { 26 | return &HeaderConn{ 27 | Conn: conn, 28 | header: header, 29 | } 30 | } 31 | 32 | // Write will write buf to the underlying conn. If this is the first time Write 33 | // is called it will prepend the Header to the beginning of the write. 34 | func (d *HeaderConn) Write(buf []byte) (n int, err error) { 35 | var didOnce bool 36 | d.once.Do(func() { 37 | didOnce = true 38 | n, err = d.Conn.Write(append([]byte(d.header), buf...)) 39 | }) 40 | if didOnce { 41 | n -= len(d.header) 42 | if n < 0 { 43 | n = 0 44 | } 45 | return n, err 46 | } 47 | return d.Conn.Write(buf) 48 | } 49 | -------------------------------------------------------------------------------- /drpcmigrate/listener.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmigrate 5 | 6 | import ( 7 | "net" 8 | "sync" 9 | ) 10 | 11 | type listener struct { 12 | addr net.Addr 13 | conns chan net.Conn 14 | once sync.Once 15 | done chan struct{} 16 | err error 17 | } 18 | 19 | func newListener(addr net.Addr) *listener { 20 | return &listener{ 21 | addr: addr, 22 | conns: make(chan net.Conn), 23 | done: make(chan struct{}), 24 | } 25 | } 26 | 27 | // Conns returns the channel of net.Conn that the listener reads from. 28 | func (l *listener) Conns() chan net.Conn { return l.conns } 29 | 30 | // Accept waits for and returns the next connection to the listener. 31 | func (l *listener) Accept() (conn net.Conn, err error) { 32 | select { 33 | case <-l.done: 34 | return nil, l.err 35 | default: 36 | } 37 | select { 38 | case <-l.done: 39 | return nil, l.err 40 | case conn = <-l.conns: 41 | return conn, nil 42 | } 43 | } 44 | 45 | // Close closes the listener. 46 | // Any blocked Accept operations will be unblocked and return errors. 47 | func (l *listener) Close() error { 48 | l.once.Do(func() { 49 | l.err = Closed 50 | close(l.done) 51 | }) 52 | return nil 53 | } 54 | 55 | // Addr returns the listener's network address. 56 | func (l *listener) Addr() net.Addr { 57 | return l.addr 58 | } 59 | -------------------------------------------------------------------------------- /examples/drpc_and_http/drpc_client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "time" 10 | 11 | "storj.io/drpc/drpcconn" 12 | "storj.io/drpc/drpcmigrate" 13 | 14 | "storj.io/drpc/examples/drpc_and_http/pb" 15 | ) 16 | 17 | func main() { 18 | err := Main(context.Background()) 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | func Main(ctx context.Context) error { 25 | // dial the drpc server with the drpc connection header 26 | rawconn, err := drpcmigrate.DialWithHeader(ctx, "tcp", "localhost:8080", drpcmigrate.DRPCHeader) 27 | if err != nil { 28 | return err 29 | } 30 | // N.B.: If you want TLS, you need to wrap the net.Conn with TLS before 31 | // making a DRPC conn. 32 | 33 | // convert the net.Conn to a drpc.Conn 34 | conn := drpcconn.New(rawconn) 35 | defer conn.Close() 36 | 37 | // make a drpc proto-specific client 38 | client := pb.NewDRPCCookieMonsterClient(conn) 39 | 40 | // set a deadline for the operation 41 | ctx, cancel := context.WithTimeout(ctx, time.Second) 42 | defer cancel() 43 | 44 | // run the RPC 45 | crumbs, err := client.EatCookie(ctx, &pb.Cookie{ 46 | Type: pb.Cookie_Oatmeal, 47 | }) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // check the results 53 | _, err = fmt.Println(crumbs.Cookie.Type.String()) 54 | return err 55 | } 56 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/drpc_client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "time" 10 | 11 | "storj.io/drpc/drpcconn" 12 | "storj.io/drpc/drpcmigrate" 13 | 14 | "storj.io/drpc/examples/grpc_and_drpc/pb" 15 | ) 16 | 17 | func main() { 18 | err := Main(context.Background()) 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | func Main(ctx context.Context) error { 25 | // dial the drpc server with the drpc connection header 26 | rawconn, err := drpcmigrate.DialWithHeader(ctx, "tcp", "localhost:8080", drpcmigrate.DRPCHeader) 27 | if err != nil { 28 | return err 29 | } 30 | // N.B.: If you want TLS, you need to wrap the net.Conn with TLS before 31 | // making a DRPC conn. 32 | 33 | // convert the net.Conn to a drpc.Conn 34 | conn := drpcconn.New(rawconn) 35 | defer conn.Close() 36 | 37 | // make a drpc proto-specific client 38 | client := pb.NewDRPCCookieMonsterClient(conn) 39 | 40 | // set a deadline for the operation 41 | ctx, cancel := context.WithTimeout(ctx, time.Second) 42 | defer cancel() 43 | 44 | // run the RPC 45 | crumbs, err := client.EatCookie(ctx, &pb.Cookie{ 46 | Type: pb.Cookie_Oatmeal, 47 | }) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // check the results 53 | _, err = fmt.Println(crumbs.Cookie.Type.String()) 54 | return err 55 | } 56 | -------------------------------------------------------------------------------- /drpcmigrate/dial.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmigrate 5 | 6 | import ( 7 | "context" 8 | "net" 9 | ) 10 | 11 | // HeaderDialer is a net.Dialer-like that prefixes all conns with the provided 12 | // header. 13 | type HeaderDialer struct { 14 | net.Dialer 15 | Header string 16 | } 17 | 18 | // Dial will dial the address on the named network, creating a connection 19 | // that will write the configured Header on the first user-requested write. 20 | func (d *HeaderDialer) Dial(network, address string) (net.Conn, error) { 21 | return d.DialContext(context.Background(), network, address) 22 | } 23 | 24 | // DialContext will dial the address on the named network, creating a connection 25 | // that will write the configured Header on the first user-requested write. 26 | func (d *HeaderDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 27 | conn, err := d.Dialer.DialContext(ctx, network, address) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return NewHeaderConn(conn, d.Header), nil 32 | } 33 | 34 | // DialWithHeader is like net.Dial, but uses HeaderConns with the provided header. 35 | func DialWithHeader(ctx context.Context, network, address string, header string) (net.Conn, error) { 36 | conn, err := (&HeaderDialer{Header: header}).DialContext(ctx, network, address) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return conn, nil 41 | } 42 | -------------------------------------------------------------------------------- /examples/drpc/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "net" 9 | 10 | "storj.io/drpc/drpcmux" 11 | "storj.io/drpc/drpcserver" 12 | 13 | "storj.io/drpc/examples/drpc/pb" 14 | ) 15 | 16 | type CookieMonsterServer struct { 17 | pb.DRPCCookieMonsterUnimplementedServer 18 | // struct fields 19 | } 20 | 21 | // EatCookie turns a cookie into crumbs. 22 | func (s *CookieMonsterServer) EatCookie(ctx context.Context, cookie *pb.Cookie) (*pb.Crumbs, error) { 23 | return &pb.Crumbs{ 24 | Cookie: cookie, 25 | }, nil 26 | } 27 | 28 | func main() { 29 | err := Main(context.Background()) 30 | if err != nil { 31 | panic(err) 32 | } 33 | } 34 | 35 | func Main(ctx context.Context) error { 36 | // create an RPC server 37 | cookieMonster := &CookieMonsterServer{} 38 | 39 | // create a drpc RPC mux 40 | m := drpcmux.New() 41 | 42 | // register the proto-specific methods on the mux 43 | err := pb.DRPCRegisterCookieMonster(m, cookieMonster) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | // create a drpc server 49 | s := drpcserver.New(m) 50 | 51 | // listen on a tcp socket 52 | lis, err := net.Listen("tcp", ":8080") 53 | if err != nil { 54 | return err 55 | } 56 | 57 | // run the server 58 | // N.B.: if you want TLS, you need to wrap the net.Listener with 59 | // TLS before passing to Serve here. 60 | return s.Serve(ctx, lis) 61 | } 62 | -------------------------------------------------------------------------------- /drpcmetadata/serialize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmetadata 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/zeebo/assert" 10 | ) 11 | 12 | func TestCompatibility_Append(t *testing.T) { 13 | assert.Equal(t, 14 | appendEntry(nil, "key", "value"), 15 | []byte{ 16 | 0x0a, 0x0c, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x12, 17 | 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 18 | }, 19 | ) 20 | } 21 | 22 | func TestCompatibility_Parse(t *testing.T) { 23 | order := []string{"5", "1", "2", "3", "4"} 24 | buf := []byte{ 25 | 0x0a, 0x0e, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x35, 26 | 0x12, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x35, 27 | 0x0a, 0x0e, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x31, 28 | 0x12, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 29 | 0x0a, 0x0e, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x32, 30 | 0x12, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 31 | 0x0a, 0x0e, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x33, 32 | 0x12, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x33, 33 | 0x0a, 0x0e, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x34, 34 | 0x12, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x34, 35 | } 36 | 37 | var key, value []byte 38 | var ok bool 39 | var err error 40 | 41 | for i := 0; len(buf) > 0; i++ { 42 | buf, key, value, ok, err = readEntry(buf) 43 | assert.That(t, ok) 44 | assert.NoError(t, err) 45 | assert.Equal(t, string(key), "key"+order[i]) 46 | assert.Equal(t, string(value), "value"+order[i]) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /drpcpool/entry.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcpool 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | type entry[K comparable, V Conn] struct { 12 | key K 13 | val V 14 | exp *time.Timer 15 | global node[K, V] 16 | local node[K, V] 17 | } 18 | 19 | func (e *entry[K, V]) String() string { 20 | return fmt.Sprintf("", 21 | e, e.key, closed(e.val.Closed()), closed(e.val.Unblocked())) 22 | } 23 | 24 | type node[K comparable, V Conn] struct { 25 | next *entry[K, V] 26 | prev *entry[K, V] 27 | } 28 | 29 | type list[K comparable, V Conn] struct { 30 | head *entry[K, V] 31 | tail *entry[K, V] 32 | count int 33 | } 34 | 35 | func (e *entry[K, V]) globalList() *node[K, V] { return &e.global } 36 | func (e *entry[K, V]) localList() *node[K, V] { return &e.local } 37 | 38 | func (l *list[K, V]) appendEntry(ent *entry[K, V], node func(*entry[K, V]) *node[K, V]) { 39 | if l.head == nil { 40 | l.head = ent 41 | } 42 | if l.tail != nil { 43 | node(l.tail).next = ent 44 | node(ent).prev = l.tail 45 | } 46 | l.tail = ent 47 | l.count++ 48 | } 49 | 50 | func (l *list[K, V]) removeEntry(ent *entry[K, V], node func(*entry[K, V]) *node[K, V]) { 51 | n := node(ent) 52 | if l.head == ent { 53 | l.head = n.next 54 | } 55 | if n.next != nil { 56 | node(n.next).prev = n.prev 57 | } 58 | if l.tail == ent { 59 | l.tail = n.prev 60 | } 61 | if n.prev != nil { 62 | node(n.prev).next = n.next 63 | } 64 | l.count-- 65 | } 66 | -------------------------------------------------------------------------------- /examples/opentelemetry/client/otel_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/propagation" 11 | "storj.io/drpc" 12 | "storj.io/drpc/drpcmetadata" 13 | ) 14 | 15 | // otelConn wraps a drpc.Conn with tracing information. 16 | type otelConn struct { 17 | drpc.Conn 18 | } 19 | 20 | // Invoke implements drpc.Conn's Invoke method with tracing information injected into the context. 21 | func (c *otelConn) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in drpc.Message, out drpc.Message) (err error) { 22 | ctx, span := tracer.Start(ctx, rpc) 23 | defer span.End() 24 | 25 | return c.Conn.Invoke(addMetadata(ctx), rpc, enc, in, out) 26 | } 27 | 28 | // NewStream implements drpc.Conn's NewStream method with tracing information injected into the context. 29 | func (c *otelConn) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (_ drpc.Stream, err error) { 30 | ctx, span := tracer.Start(ctx, rpc) 31 | defer span.End() 32 | 33 | return c.Conn.NewStream(addMetadata(ctx), rpc, enc) 34 | } 35 | 36 | // addMetadata propagates the headers into a map that we inject into drpc metadata so they are 37 | // sent across the wire for the server to get. 38 | func addMetadata(ctx context.Context) context.Context { 39 | metadata := make(map[string]string) 40 | otel.GetTextMapPropagator().Inject(ctx, propagation.MapCarrier(metadata)) 41 | return drpcmetadata.AddPairs(ctx, metadata) 42 | } 43 | -------------------------------------------------------------------------------- /drpcstream/pktbuf.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcstream 5 | 6 | import ( 7 | "sync" 8 | ) 9 | 10 | type packetBuffer struct { 11 | mu sync.Mutex 12 | cond sync.Cond 13 | err error 14 | data []byte 15 | set bool 16 | held bool 17 | } 18 | 19 | func (pb *packetBuffer) init() { 20 | pb.cond.L = &pb.mu 21 | } 22 | 23 | func (pb *packetBuffer) Close(err error) { 24 | pb.mu.Lock() 25 | defer pb.mu.Unlock() 26 | 27 | for pb.held { 28 | pb.cond.Wait() 29 | } 30 | 31 | if pb.err == nil { 32 | pb.data = nil 33 | pb.set = false 34 | pb.err = err 35 | pb.cond.Broadcast() 36 | } 37 | } 38 | 39 | func (pb *packetBuffer) Put(data []byte) { 40 | pb.mu.Lock() 41 | defer pb.mu.Unlock() 42 | 43 | for pb.set && pb.err == nil { 44 | pb.cond.Wait() 45 | } 46 | if pb.err != nil { 47 | return 48 | } 49 | 50 | pb.data = data 51 | pb.set = true 52 | pb.held = false 53 | pb.cond.Broadcast() 54 | 55 | for pb.set || pb.held { 56 | pb.cond.Wait() 57 | } 58 | } 59 | 60 | func (pb *packetBuffer) Get() ([]byte, error) { 61 | pb.mu.Lock() 62 | defer pb.mu.Unlock() 63 | 64 | for !pb.set && pb.err == nil { 65 | pb.cond.Wait() 66 | } 67 | if pb.err != nil { 68 | return nil, pb.err 69 | } 70 | 71 | pb.held = true 72 | pb.cond.Broadcast() 73 | 74 | return pb.data, nil 75 | } 76 | 77 | func (pb *packetBuffer) Done() { 78 | pb.mu.Lock() 79 | defer pb.mu.Unlock() 80 | 81 | pb.data = nil 82 | pb.set = false 83 | pb.held = false 84 | pb.cond.Broadcast() 85 | } 86 | -------------------------------------------------------------------------------- /drpcctx/README.md: -------------------------------------------------------------------------------- 1 | # package drpcctx 2 | 3 | `import "storj.io/drpc/drpcctx"` 4 | 5 | Package drpcctx has helpers to interact with context.Context. 6 | 7 | ## Usage 8 | 9 | #### func Transport 10 | 11 | ```go 12 | func Transport(ctx context.Context) (drpc.Transport, bool) 13 | ``` 14 | Transport returns the drpc.Transport associated with the context and a bool if 15 | it existed. 16 | 17 | #### func WithTransport 18 | 19 | ```go 20 | func WithTransport(ctx context.Context, tr drpc.Transport) context.Context 21 | ``` 22 | WithTransport associates the drpc.Transport as a value on the context. 23 | 24 | #### type Tracker 25 | 26 | ```go 27 | type Tracker struct { 28 | context.Context 29 | } 30 | ``` 31 | 32 | Tracker keeps track of launched goroutines with a context. 33 | 34 | #### func NewTracker 35 | 36 | ```go 37 | func NewTracker(ctx context.Context) *Tracker 38 | ``` 39 | NewTracker creates a Tracker bound to the provided context. 40 | 41 | #### func (*Tracker) Cancel 42 | 43 | ```go 44 | func (t *Tracker) Cancel() 45 | ``` 46 | Cancel cancels the tracker's context. 47 | 48 | #### func (*Tracker) Run 49 | 50 | ```go 51 | func (t *Tracker) Run(cb func(ctx context.Context)) 52 | ``` 53 | Run starts a goroutine running the callback with the tracker as the context. 54 | 55 | #### func (*Tracker) Wait 56 | 57 | ```go 58 | func (t *Tracker) Wait() 59 | ``` 60 | Wait blocks until all callbacks started with Run have exited. 61 | 62 | #### type TransportKey 63 | 64 | ```go 65 | type TransportKey struct{} 66 | ``` 67 | 68 | TransportKey is used to store the drpc.Transport with the context. 69 | -------------------------------------------------------------------------------- /internal/integration/trace_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package integration 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "io" 10 | "runtime/trace" 11 | "sort" 12 | "testing" 13 | 14 | "github.com/zeebo/assert" 15 | exptrace "golang.org/x/exp/trace" 16 | 17 | "storj.io/drpc/drpctest" 18 | ) 19 | 20 | func TestRuntimeTracing(t *testing.T) { 21 | if trace.IsEnabled() { 22 | t.Skip("tracing already enabled") 23 | } 24 | 25 | ctx := drpctest.NewTracker(t) 26 | defer ctx.Close() 27 | 28 | cli, close := createConnection(t, standardImpl) 29 | defer close() 30 | 31 | var buf bytes.Buffer 32 | assert.NoError(t, trace.Start(&buf)) 33 | 34 | out, err := cli.Method1(ctx, &In{In: 1}) 35 | close() 36 | 37 | trace.Stop() 38 | 39 | assert.NoError(t, err) 40 | assert.True(t, Equal(out, &Out{Out: 1})) 41 | 42 | r, err := exptrace.NewReader(&buf) 43 | assert.NoError(t, err) 44 | 45 | var events []string 46 | for { 47 | ev, err := r.ReadEvent() 48 | if errors.Is(err, io.EOF) { 49 | break 50 | } 51 | assert.NoError(t, err) 52 | 53 | switch ev.Kind() { 54 | case exptrace.EventTaskBegin: 55 | events = append(events, "begin "+ev.Task().Type) 56 | case exptrace.EventTaskEnd: 57 | events = append(events, "end "+ev.Task().Type) 58 | } 59 | } 60 | 61 | sort.Strings(events) // srv and client end events can be in any order 62 | 63 | assert.Equal(t, events, []string{ 64 | "begin cli/service.Service/Method1", 65 | "begin srv/service.Service/Method1", 66 | "end cli/service.Service/Method1", 67 | "end srv/service.Service/Method1", 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /drpccache/README.md: -------------------------------------------------------------------------------- 1 | # package drpccache 2 | 3 | `import "storj.io/drpc/drpccache"` 4 | 5 | Package drpccache implements per stream cache for drpc. 6 | 7 | ## Usage 8 | 9 | #### func WithContext 10 | 11 | ```go 12 | func WithContext(parent context.Context, cache *Cache) context.Context 13 | ``` 14 | WithContext returns a context with the value cache associated with the context. 15 | 16 | #### type Cache 17 | 18 | ```go 19 | type Cache struct { 20 | } 21 | ``` 22 | 23 | Cache is a per stream cache. 24 | 25 | #### func FromContext 26 | 27 | ```go 28 | func FromContext(ctx context.Context) *Cache 29 | ``` 30 | FromContext returns a cache from a context. 31 | 32 | Example usage: 33 | 34 | cache := drpccache.FromContext(stream.Context()) 35 | if cache != nil { 36 | value := cache.LoadOrCreate("initialized", func() (interface{}) { 37 | return 42 38 | }) 39 | } 40 | 41 | #### func New 42 | 43 | ```go 44 | func New() *Cache 45 | ``` 46 | New returns a new cache. 47 | 48 | #### func (*Cache) Clear 49 | 50 | ```go 51 | func (cache *Cache) Clear() 52 | ``` 53 | Clear clears the cache. 54 | 55 | #### func (*Cache) Load 56 | 57 | ```go 58 | func (cache *Cache) Load(key interface{}) interface{} 59 | ``` 60 | Load returns the value with the given key. 61 | 62 | #### func (*Cache) LoadOrCreate 63 | 64 | ```go 65 | func (cache *Cache) LoadOrCreate(key interface{}, fn func() interface{}) interface{} 66 | ``` 67 | LoadOrCreate returns the value with the given key. 68 | 69 | #### func (*Cache) Store 70 | 71 | ```go 72 | func (cache *Cache) Store(key, value interface{}) 73 | ``` 74 | Store sets the value at a key. 75 | -------------------------------------------------------------------------------- /internal/grpccompat/web_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package grpccompat 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | "github.com/zeebo/errs" 12 | ) 13 | 14 | func TestWebCompat_Simple(t *testing.T) { 15 | impl := &serviceImpl{ 16 | Method1Fn: func(ctx context.Context, in *In) (*Out, error) { 17 | if in.In == 5 { 18 | return nil, errs.New("marker") 19 | } 20 | return out(in.In), nil 21 | }, 22 | } 23 | 24 | testWebCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 25 | ctx, cancel := context.WithCancel(context.Background()) 26 | defer cancel() 27 | 28 | ensure(cli.Method1(ctx, in(5))) 29 | ensure(cli.Method1(ctx, in(4))) 30 | ensure(cli.Method1(ctx, in(0))) 31 | }) 32 | } 33 | 34 | func TestWebCompat_Streaming(t *testing.T) { 35 | impl := &serviceImpl{ 36 | Method3Fn: func(in *In, stream ServerMethod3Stream) error { 37 | if in.In == 1 { 38 | return errs.New("marker") 39 | } 40 | for i := int64(0); i < 5; i++ { 41 | if err := stream.Send(out(i)); err != nil { 42 | return err 43 | } 44 | } 45 | if in.In == 2 { 46 | return errs.New("marker") 47 | } 48 | return nil 49 | }, 50 | } 51 | 52 | for i := int64(0); i < 3; i++ { 53 | testWebCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 54 | ctx, cancel := context.WithCancel(context.Background()) 55 | defer cancel() 56 | 57 | stream, err := cli.Method3(ctx, in(i)) 58 | assert.NoError(t, err) 59 | 60 | for i := 0; i < 6; i++ { 61 | ensure(stream.Recv()) 62 | } 63 | }) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL = all 2 | 3 | .PHONY: all 4 | all: tidy docs generate lint vet test 5 | 6 | .PHONY: check 7 | check: generate vet 8 | 9 | .PHONY: tidy 10 | tidy: 11 | ./scripts/run.sh '*' go mod tidy 12 | 13 | .PHONY: docs 14 | docs: 15 | ./scripts/docs.sh 16 | 17 | .PHONY: generate 18 | generate: 19 | ./scripts/run.sh '*' go generate ./... 20 | 21 | .PHONY: lint 22 | lint: 23 | ./scripts/run.sh -v 'examples' check-copyright 24 | ./scripts/run.sh -v 'examples' check-large-files 25 | ./scripts/run.sh -v 'examples' check-imports ./... 26 | ./scripts/run.sh -v 'examples' check-atomic-align ./... 27 | ./scripts/run.sh -v 'examples' staticcheck ./... 28 | ./scripts/run.sh -v 'examples' golangci-lint run 29 | 30 | .PHONY: vet 31 | vet: 32 | ./scripts/run.sh '*' go vet ./... 33 | GOOS=linux GOARCH=386 ./scripts/run.sh '*' go vet ./... 34 | GOOS=linux GOARCH=amd64 ./scripts/run.sh '*' go vet ./... 35 | GOOS=linux GOARCH=arm ./scripts/run.sh '*' go vet ./... 36 | GOOS=linux GOARCH=arm64 ./scripts/run.sh '*' go vet ./... 37 | GOOS=windows GOARCH=386 ./scripts/run.sh '*' go vet ./... 38 | GOOS=windows GOARCH=amd64 ./scripts/run.sh '*' go vet ./... 39 | GOOS=windows GOARCH=arm64 ./scripts/run.sh '*' go vet ./... 40 | GOOS=darwin GOARCH=amd64 ./scripts/run.sh '*' go vet ./... 41 | GOOS=darwin GOARCH=arm64 ./scripts/run.sh '*' go vet ./... 42 | 43 | .PHONY: test 44 | test: 45 | ./scripts/run.sh '*' go test ./... -race -count=1 -bench=. -benchtime=1x 46 | ./scripts/run.sh 'integration' go test ./... -tags=gogo -race -count=1 -bench=. -benchtime=1x 47 | ./scripts/run.sh 'integration' go test ./... -tags=custom -race -count=1 -bench=. -benchtime=1x 48 | -------------------------------------------------------------------------------- /drpctest/tracker.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | // Package drpctest provides test related helpers. 5 | package drpctest 6 | 7 | import ( 8 | "context" 9 | "runtime" 10 | "sync" 11 | "testing" 12 | ) 13 | 14 | // Tracker keeps track of launched goroutines with a context. 15 | type Tracker struct { 16 | context.Context 17 | tb testing.TB 18 | cancel func() 19 | wg sync.WaitGroup 20 | } 21 | 22 | // NewTracker creates a new tracker that inspects the provided TB to see if 23 | // tests have failed in any of its launched goroutines. 24 | func NewTracker(tb testing.TB) *Tracker { 25 | ctx, cancel := context.WithCancel(context.Background()) 26 | return &Tracker{ 27 | Context: ctx, 28 | tb: tb, 29 | cancel: cancel, 30 | } 31 | } 32 | 33 | // Close cancels the context and waits for all of the goroutines started by Run 34 | // to finish. 35 | func (t *Tracker) Close() { 36 | t.Cancel() 37 | t.Wait() 38 | } 39 | 40 | // Run starts a goroutine running the callback with the tracker as the context. 41 | func (t *Tracker) Run(cb func(ctx context.Context)) { 42 | t.wg.Add(1) 43 | go t.track(cb) 44 | } 45 | 46 | // track is a helper to call done on the waitgroup after the callback returns. 47 | func (t *Tracker) track(cb func(ctx context.Context)) { 48 | defer func() { 49 | if t.tb.Failed() { 50 | t.cancel() 51 | } 52 | t.wg.Done() 53 | }() 54 | cb(t) 55 | } 56 | 57 | // Cancel cancels the tracker's context. 58 | func (t *Tracker) Cancel() { t.cancel() } 59 | 60 | // Wait blocks until all callbacks started with Run have exited. 61 | func (t *Tracker) Wait() { 62 | t.wg.Wait() 63 | if t.tb.Failed() { 64 | runtime.Goexit() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/twirpcompat/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 5 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 6 | github.com/twitchtv/twirp v8.1.0+incompatible h1:KGXanpa9LXdVE/V5P/tA27rkKFmXRGCtSNT7zdeeVOY= 7 | github.com/twitchtv/twirp v8.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= 8 | github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 9 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 10 | github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 11 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 12 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 13 | github.com/zeebo/hmux v0.3.1 h1:thnR8xoJv0dyWGZfsyprcDZBqg2Aam2WF101puJTOEo= 14 | github.com/zeebo/hmux v0.3.1/go.mod h1:qHOtFf8FmwXOwAb8/Vq7VOBenZSTPGrduR7Qkwpivf4= 15 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 16 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 17 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 18 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 19 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 20 | -------------------------------------------------------------------------------- /drpcwire/varint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "math/bits" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | ) 12 | 13 | func varintSize(x uint64) uint { return (9*uint(bits.Len64(x)) + 64) / 64 } 14 | 15 | func TestVarint(t *testing.T) { 16 | t.Run("Round Trip", func(t *testing.T) { 17 | var vals = []uint64{ 18 | 0, 1, 2, 19 | 1<<7 - 1, 1 << 7, 1<<7 + 1, 20 | 1<<14 - 1, 1 << 14, 1<<14 + 1, 21 | 1<<21 - 1, 1 << 21, 1<<21 + 1, 22 | 1<<28 - 1, 1 << 28, 1<<28 + 1, 23 | 1<<35 - 1, 1 << 35, 1<<35 + 1, 24 | 1<<42 - 1, 1 << 42, 1<<42 + 1, 25 | 1<<49 - 1, 1 << 49, 1<<49 + 1, 26 | 1<<56 - 1, 1 << 56, 1<<56 + 1, 27 | 1<<63 - 1, 1 << 63, 1<<63 + 1, 28 | 1<<64 - 1, 29 | } 30 | for i := 0; i < 64; i++ { 31 | // val has i+1 lower bits set 32 | vals = append(vals, (uint64(1)< 0) 63 | }) 64 | } 65 | 66 | func TestDecode(t *testing.T) { 67 | t.Run("Empty Metadata", func(t *testing.T) { 68 | metadata, err := Decode(nil) 69 | assert.NoError(t, err) 70 | assert.Nil(t, metadata) 71 | }) 72 | 73 | t.Run("With Metadata", func(t *testing.T) { 74 | data := []byte{0xa, 0x9, 0xa, 0x4, 0x74, 0x65, 0x73, 0x74, 0x12, 0x1, 0x61} 75 | metadata, err := Decode(data) 76 | assert.NoError(t, err) 77 | assert.DeepEqual(t, metadata, map[string]string{"test": "a"}) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /internal/integration/cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package integration 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | "github.com/zeebo/errs" 12 | 13 | "storj.io/drpc/drpccache" 14 | "storj.io/drpc/drpctest" 15 | ) 16 | 17 | func TestCache(t *testing.T) { 18 | ctx := drpctest.NewTracker(t) 19 | defer ctx.Close() 20 | 21 | // create a server that signals then waits for the context to die 22 | cli, close := createConnection(t, impl{ 23 | Method1Fn: func(ctx context.Context, _ *In) (*Out, error) { 24 | cache := drpccache.FromContext(ctx) 25 | if cache == nil { 26 | return nil, errs.New("no cache associated with context") 27 | } 28 | cache.LoadOrCreate("value", func() interface{} { return 42 }) 29 | return &Out{Out: 123}, nil 30 | }, 31 | Method2Fn: func(stream DRPCService_Method2Stream) error { 32 | cache := drpccache.FromContext(stream.Context()) 33 | if cache == nil { 34 | return errs.New("no cache associated with context") 35 | } 36 | value, _ := cache.Load("value").(int) 37 | return stream.SendAndClose(&Out{Out: int64(value)}) 38 | }, 39 | }) 40 | defer close() 41 | 42 | { // value not yet cached 43 | stream, err := cli.Method2(ctx) 44 | assert.NoError(t, err) 45 | out, err := stream.CloseAndRecv() 46 | assert.NoError(t, err) 47 | assert.True(t, Equal(out, &Out{Out: 0})) 48 | } 49 | 50 | { // store value in the cache 51 | out, err := cli.Method1(ctx, in(1)) 52 | assert.NoError(t, err) 53 | assert.True(t, Equal(out, &Out{Out: 123})) 54 | } 55 | 56 | { // expected value in the cache 57 | stream, err := cli.Method2(ctx) 58 | assert.NoError(t, err) 59 | out, err := stream.CloseAndRecv() 60 | assert.NoError(t, err) 61 | assert.True(t, Equal(out, &Out{Out: 42})) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/backcompat/servicedefs/empty.go: -------------------------------------------------------------------------------- 1 | // Code generated by a human. DO NOT EDIT. 2 | // While this is not generated code, it contains stubs that match some generated code. 3 | // Linters not treating this like generated code causes them to be very unhappy. 4 | 5 | // Copyright (C) 2021 Storj Labs, Inc. 6 | // See LICENSE for copying information. 7 | 8 | // Package servicedefs contains empty stubs so that backcompat can compile. 9 | package servicedefs 10 | 11 | import ( 12 | "context" 13 | ) 14 | 15 | func NewDRPCServiceClient(_ interface{}) DRPCServiceClient { return nil } 16 | 17 | func DRPCRegisterService(_, _ interface{}) error { return nil } 18 | 19 | type In struct{ In int64 } 20 | type Out struct{ Out int64 } 21 | 22 | type DRPCServiceClient interface { 23 | Method1(ctx context.Context, in *In) (*Out, error) 24 | Method2(ctx context.Context) (DRPCService_Method2Client, error) 25 | Method3(ctx context.Context, in *In) (DRPCService_Method3Client, error) 26 | Method4(ctx context.Context) (DRPCService_Method4Client, error) 27 | } 28 | 29 | type DRPCService_Method2Client interface { 30 | Send(*In) error 31 | CloseAndRecv() (*Out, error) 32 | } 33 | 34 | type DRPCService_Method3Client interface { 35 | Recv() (*Out, error) 36 | } 37 | 38 | type DRPCService_Method4Client interface { 39 | Send(*In) error 40 | Recv() (*Out, error) 41 | CloseSend() error 42 | } 43 | 44 | type DRPCServiceServer interface { 45 | Method1(context.Context, *In) (*Out, error) 46 | Method2(DRPCService_Method2Stream) error 47 | Method3(*In, DRPCService_Method3Stream) error 48 | Method4(DRPCService_Method4Stream) error 49 | } 50 | 51 | type DRPCService_Method2Stream interface { 52 | SendAndClose(*Out) error 53 | Recv() (*In, error) 54 | } 55 | 56 | type DRPCService_Method3Stream interface { 57 | Send(*Out) error 58 | } 59 | 60 | type DRPCService_Method4Stream interface { 61 | Send(*Out) error 62 | Recv() (*In, error) 63 | } 64 | -------------------------------------------------------------------------------- /drpcmetadata/metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmetadata 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/zeebo/errs" 10 | ) 11 | 12 | // AddPairs attaches metadata onto a context and return the context. 13 | func AddPairs(ctx context.Context, metadata map[string]string) context.Context { 14 | for key, val := range metadata { 15 | ctx = Add(ctx, key, val) 16 | } 17 | return ctx 18 | } 19 | 20 | // Encode generates byte form of the metadata and appends it onto the passed in buffer. 21 | func Encode(buf []byte, metadata map[string]string) ([]byte, error) { 22 | for key, value := range metadata { 23 | buf = appendEntry(buf, key, value) 24 | } 25 | return buf, nil 26 | } 27 | 28 | // Decode translate byte form of metadata into key/value metadata. 29 | func Decode(buf []byte) (map[string]string, error) { 30 | var out map[string]string 31 | var key, value []byte 32 | var ok bool 33 | var err error 34 | 35 | for len(buf) > 0 { 36 | buf, key, value, ok, err = readEntry(buf) 37 | if err != nil { 38 | return nil, err 39 | } else if !ok { 40 | return nil, errs.New("invalid data") 41 | } 42 | if out == nil { 43 | out = make(map[string]string) 44 | } 45 | out[string(key)] = string(value) 46 | } 47 | 48 | return out, nil 49 | } 50 | 51 | type metadataKey struct{} 52 | 53 | // Add associates a key/value pair on the context. 54 | func Add(ctx context.Context, key, value string) context.Context { 55 | metadata, ok := Get(ctx) 56 | if !ok { 57 | metadata = make(map[string]string) 58 | ctx = context.WithValue(ctx, metadataKey{}, metadata) 59 | } 60 | metadata[key] = value 61 | return ctx 62 | } 63 | 64 | // Get returns all key/value pairs on the given context. 65 | func Get(ctx context.Context) (map[string]string, bool) { 66 | metadata, ok := ctx.Value(metadataKey{}).(map[string]string) 67 | return metadata, ok 68 | } 69 | -------------------------------------------------------------------------------- /drpchttp/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpchttp 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | 12 | "storj.io/drpc/drpcmetadata" 13 | ) 14 | 15 | func TestBuildContext(t *testing.T) { 16 | { // check all the edge cases 17 | ctx, err := buildContext(context.Background(), []string{ 18 | "key1=val1", // basic 19 | "key2_%3d=val2_%25", // encoded = and encoded % 20 | "key3", // no equals 21 | "=val4", // empty key 22 | "key5=", // empty value 23 | "key6=val6", // duplicate key 24 | "key6=val7", // duplicate key 25 | "key8=foo=val8", // multiple equals 26 | "key9=%3d%25%3D%25", // multiple escapes 27 | }) 28 | assert.NoError(t, err) 29 | 30 | metadata, ok := drpcmetadata.Get(ctx) 31 | assert.That(t, ok) 32 | assert.DeepEqual(t, metadata, map[string]string{ 33 | "key1": "val1", 34 | "key2_=": "val2_%", 35 | "key3": "", 36 | "": "val4", 37 | "key5": "", 38 | "key6": "val7", 39 | "key8": "foo=val8", 40 | "key9": "=%=%", 41 | }) 42 | } 43 | 44 | { // no entries associates no metadata 45 | ctx, err := buildContext(context.Background(), nil) 46 | assert.NoError(t, err) 47 | _, ok := drpcmetadata.Get(ctx) 48 | assert.That(t, !ok) 49 | } 50 | 51 | // check error cases 52 | cases := []string{ 53 | "key%", // truncated escape in key 54 | "key=val%", // truncated escape in value 55 | "key%z1=val", // invalid hex in key in first byte 56 | "key%1z=val", // invalid hex in key in second byte 57 | "key=val%z1", // invalid hex in value in first byte 58 | "key=val%1x", // invalid hex in value in second byte 59 | } 60 | for _, entry := range cases { 61 | _, err := buildContext(context.Background(), []string{entry}) 62 | assert.Error(t, err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /internal/drpcopts/stream.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcopts 5 | 6 | import ( 7 | "storj.io/drpc" 8 | "storj.io/drpc/drpcstats" 9 | ) 10 | 11 | // Stream contains internal options for the drpcstream package. 12 | type Stream struct { 13 | transport drpc.Transport 14 | fin chan<- struct{} 15 | kind string 16 | rpc string 17 | stats *drpcstats.Stats 18 | } 19 | 20 | // GetStreamTransport returns the drpc.Transport stored in the options. 21 | func GetStreamTransport(opts *Stream) drpc.Transport { return opts.transport } 22 | 23 | // SetStreamTransport sets the drpc.Transport stored in the options. 24 | func SetStreamTransport(opts *Stream, tr drpc.Transport) { opts.transport = tr } 25 | 26 | // GetStreamFin returns the chan<- struct{} stored in the options. 27 | func GetStreamFin(opts *Stream) chan<- struct{} { return opts.fin } 28 | 29 | // SetStreamFin sets the chan<- struct{} stored in the options. 30 | func SetStreamFin(opts *Stream, fin chan<- struct{}) { opts.fin = fin } 31 | 32 | // GetStreamKind returns the kind debug string stored in the options. 33 | func GetStreamKind(opts *Stream) string { return opts.kind } 34 | 35 | // SetStreamKind sets the kind debug string stored in the options. 36 | func SetStreamKind(opts *Stream, kind string) { opts.kind = kind } 37 | 38 | // GetStreamRPC returns the RPC debug string stored in the options. 39 | func GetStreamRPC(opts *Stream) string { return opts.rpc } 40 | 41 | // SetStreamRPC sets the RPC debug string stored in the options. 42 | func SetStreamRPC(opts *Stream, rpc string) { opts.rpc = rpc } 43 | 44 | // GetStreamStats returns the Stats stored in the options. 45 | func GetStreamStats(opts *Stream) *drpcstats.Stats { return opts.stats } 46 | 47 | // SetStreamStats sets the Stats stored in the options. 48 | func SetStreamStats(opts *Stream, stats *drpcstats.Stats) { opts.stats = stats } 49 | -------------------------------------------------------------------------------- /drpcwire/rand_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "math" 8 | "math/rand" 9 | "sync" 10 | ) 11 | 12 | var ( 13 | mu sync.Mutex 14 | streamID uint64 = 1 15 | messageID uint64 = 1 16 | ) 17 | 18 | func RandID() ID { 19 | mu.Lock() 20 | if rand.Intn(100) == 0 { 21 | streamID++ 22 | messageID = 1 23 | } else { 24 | messageID++ 25 | } 26 | id := ID{ 27 | Stream: streamID, 28 | Message: messageID, 29 | } 30 | mu.Unlock() 31 | return id 32 | } 33 | 34 | func RandBytes(n int) []byte { 35 | out := make([]byte, n) 36 | for i := range out { 37 | out[i] = byte(rand.Intn(256)) 38 | } 39 | return out 40 | } 41 | 42 | func RandBool() bool { 43 | return rand.Intn(2) == 0 44 | } 45 | 46 | func RandUint64() uint64 { 47 | return uint64(rand.Int63n(math.MaxInt64))<<1 + uint64(rand.Intn(2)) 48 | } 49 | 50 | func RandKind() Kind { 51 | for { 52 | kind := Kind(rand.Intn(7)) 53 | if _, ok := payloadSize[kind]; ok { 54 | return kind 55 | } 56 | } 57 | } 58 | 59 | var payloadSize = map[Kind]func() int{ 60 | KindInvoke: func() int { return rand.Intn(1023) + 1 }, 61 | KindMessage: func() int { return rand.Intn(1023) + 1 }, 62 | KindError: func() int { return rand.Intn(1023) + 1 }, 63 | KindCancel: func() int { return 0 }, 64 | KindClose: func() int { return 0 }, 65 | KindCloseSend: func() int { return 0 }, 66 | KindInvokeMetadata: func() int { return rand.Intn(1023) + 1 }, 67 | } 68 | 69 | func RandFrame() Frame { 70 | kind := RandKind() 71 | return Frame{ 72 | Data: RandBytes(payloadSize[kind]()), 73 | ID: RandID(), 74 | Kind: kind, 75 | Done: RandBool(), 76 | } 77 | } 78 | 79 | func RandPacket() Packet { 80 | kind := RandKind() 81 | return Packet{ 82 | Data: RandBytes(10 * payloadSize[kind]()), 83 | ID: RandID(), 84 | Kind: kind, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /drpcserver/README.md: -------------------------------------------------------------------------------- 1 | # package drpcserver 2 | 3 | `import "storj.io/drpc/drpcserver"` 4 | 5 | Package drpcserver allows one to execute registered rpcs. 6 | 7 | ## Usage 8 | 9 | #### type Options 10 | 11 | ```go 12 | type Options struct { 13 | // Manager controls the options we pass to the managers this server creates. 14 | Manager drpcmanager.Options 15 | 16 | // Log is called when errors happen that can not be returned up, like 17 | // temporary network errors when accepting connections, or errors 18 | // handling individual clients. It is not called if nil. 19 | Log func(error) 20 | 21 | // CollectStats controls whether the server should collect stats on the 22 | // rpcs it serves. 23 | CollectStats bool 24 | } 25 | ``` 26 | 27 | Options controls configuration settings for a server. 28 | 29 | #### type Server 30 | 31 | ```go 32 | type Server struct { 33 | } 34 | ``` 35 | 36 | Server is an implementation of drpc.Server to serve drpc connections. 37 | 38 | #### func New 39 | 40 | ```go 41 | func New(handler drpc.Handler) *Server 42 | ``` 43 | New constructs a new Server. 44 | 45 | #### func NewWithOptions 46 | 47 | ```go 48 | func NewWithOptions(handler drpc.Handler, opts Options) *Server 49 | ``` 50 | NewWithOptions constructs a new Server using the provided options to tune how 51 | the drpc connections are handled. 52 | 53 | #### func (*Server) Serve 54 | 55 | ```go 56 | func (s *Server) Serve(ctx context.Context, lis net.Listener) (err error) 57 | ``` 58 | Serve listens for connections on the listener and serves the drpc request on new 59 | connections. 60 | 61 | #### func (*Server) ServeOne 62 | 63 | ```go 64 | func (s *Server) ServeOne(ctx context.Context, tr drpc.Transport) (err error) 65 | ``` 66 | ServeOne serves a single set of rpcs on the provided transport. 67 | 68 | #### func (*Server) Stats 69 | 70 | ```go 71 | func (s *Server) Stats() map[string]drpcstats.Stats 72 | ``` 73 | Stats returns the collected stats grouped by rpc. 74 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 11 | "revCount": 102, 12 | "type": "tarball", 13 | "url": "https://api.flakehub.com/f/pinned/numtide/flake-utils/0.1.102%2Brev-11707dc2f618dd54ca8739b309ec4fc024de578b/0193276d-5b8f-7dbc-acf1-41cb7b54ad2e/source.tar.gz" 14 | }, 15 | "original": { 16 | "type": "tarball", 17 | "url": "https://flakehub.com/f/numtide/flake-utils/%2A.tar.gz" 18 | } 19 | }, 20 | "nixpkgs": { 21 | "locked": { 22 | "lastModified": 1746957726, 23 | "narHash": "sha256-k9ut1LSfHCr0AW82ttEQzXVCqmyWVA5+SHJkS5ID/Jo=", 24 | "rev": "a39ed32a651fdee6842ec930761e31d1f242cb94", 25 | "revCount": 717837, 26 | "type": "tarball", 27 | "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2411.717837%2Brev-a39ed32a651fdee6842ec930761e31d1f242cb94/0196c53c-7a4e-7500-910f-55c1ddbd2de5/source.tar.gz" 28 | }, 29 | "original": { 30 | "type": "tarball", 31 | "url": "https://flakehub.com/f/NixOS/nixpkgs/%2A.tar.gz" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | }, 40 | "systems": { 41 | "locked": { 42 | "lastModified": 1681028828, 43 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 44 | "owner": "nix-systems", 45 | "repo": "default", 46 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "nix-systems", 51 | "repo": "default", 52 | "type": "github" 53 | } 54 | } 55 | }, 56 | "root": "root", 57 | "version": 7 58 | } 59 | -------------------------------------------------------------------------------- /internal/grpccompat/cancel_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package grpccompat 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/zeebo/assert" 11 | "github.com/zeebo/errs" 12 | ) 13 | 14 | func TestCancel_ErrorAfterCancel(t *testing.T) { 15 | impl := &serviceImpl{ 16 | Method4Fn: func(stream ServerMethod4Stream) error { 17 | <-stream.Context().Done() 18 | return errs.New("marker") 19 | }, 20 | } 21 | 22 | testCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 23 | ctx, cancel := context.WithCancel(context.Background()) 24 | defer cancel() 25 | 26 | stream, err := cli.Method4(ctx) 27 | assert.NoError(t, err) 28 | 29 | cancel() 30 | ensure(stream.Recv()) 31 | ensure(nil, stream.Send(in(0))) 32 | }) 33 | } 34 | 35 | func TestCancel_CancelAfterError(t *testing.T) { 36 | impl := &serviceImpl{ 37 | Method4Fn: func(stream ServerMethod4Stream) error { 38 | return errs.New("marker") 39 | }, 40 | } 41 | 42 | testCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 43 | ctx, cancel := context.WithCancel(context.Background()) 44 | defer cancel() 45 | 46 | stream, err := cli.Method4(ctx) 47 | assert.NoError(t, err) 48 | 49 | ensure(stream.Recv()) 50 | ensure(nil, stream.Send(in(0))) 51 | cancel() 52 | ensure(stream.Recv()) 53 | ensure(nil, stream.Send(in(1))) 54 | }) 55 | } 56 | 57 | func TestCancel_CancelAfterSuccess(t *testing.T) { 58 | impl := &serviceImpl{ 59 | Method4Fn: func(stream ServerMethod4Stream) error { 60 | _ = stream.Send(out(2)) 61 | _, _ = stream.Recv() 62 | <-stream.Context().Done() 63 | return nil 64 | }, 65 | } 66 | 67 | testCompat(t, impl, func(t *testing.T, cli Client, ensure func(*Out, error)) { 68 | ctx, cancel := context.WithCancel(context.Background()) 69 | defer cancel() 70 | 71 | stream, err := cli.Method4(ctx) 72 | assert.NoError(t, err) 73 | 74 | ensure(stream.Recv()) 75 | ensure(nil, stream.Send(in(0))) 76 | cancel() 77 | ensure(stream.Recv()) 78 | ensure(nil, stream.Send(in(1))) 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /drpcmetadata/serialize.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmetadata 5 | 6 | import ( 7 | "math/bits" 8 | 9 | "storj.io/drpc/drpcwire" 10 | ) 11 | 12 | func varintSize(n uint64) uint64 { 13 | return (9*uint64(bits.Len64(n)) + 64) / 64 14 | } 15 | 16 | func encodedStringSize(x string) uint64 { 17 | return 1 + varintSize(uint64(len(x))) + uint64(len(x)) 18 | } 19 | 20 | func appendEntry(buf []byte, key, value string) []byte { 21 | buf = append(buf, 10) // 1<<3 | 2 22 | buf = drpcwire.AppendVarint(buf, encodedStringSize(key)+encodedStringSize(value)) 23 | 24 | buf = append(buf, 10) // 1<<3 | 2 25 | buf = drpcwire.AppendVarint(buf, uint64(len(key))) 26 | buf = append(buf, key...) 27 | 28 | buf = append(buf, 18) // 2<<3 | 2 29 | buf = drpcwire.AppendVarint(buf, uint64(len(value))) 30 | buf = append(buf, value...) 31 | 32 | return buf 33 | } 34 | 35 | func readEntry(buf []byte) (rem, key, value []byte, ok bool, err error) { 36 | var length uint64 37 | 38 | if len(buf) < 1 || buf[0] != 10 { 39 | goto bad 40 | } 41 | buf, length, ok, err = drpcwire.ReadVarint(buf[1:]) 42 | if !ok || err != nil || length > uint64(len(buf)) { 43 | goto bad 44 | } 45 | 46 | key, value, ok, err = readKeyValue(buf[:length]) 47 | if !ok || err != nil { 48 | goto bad 49 | } 50 | 51 | return buf[length:], key, value, true, nil 52 | bad: 53 | return nil, nil, nil, false, err 54 | } 55 | 56 | func readKeyValue(buf []byte) (key, value []byte, ok bool, err error) { 57 | var length uint64 58 | 59 | if len(buf) < 1 || buf[0] != 10 { 60 | goto bad 61 | } 62 | buf, length, ok, err = drpcwire.ReadVarint(buf[1:]) 63 | if !ok || err != nil || length > uint64(len(buf)) { 64 | goto bad 65 | } 66 | buf, key = buf[length:], buf[:length] 67 | 68 | if len(buf) < 1 || buf[0] != 18 { 69 | goto bad 70 | } 71 | buf, length, ok, err = drpcwire.ReadVarint(buf[1:]) 72 | if !ok || err != nil || length > uint64(len(buf)) { 73 | goto bad 74 | } 75 | buf, value = buf[length:], buf[:length] 76 | 77 | if len(buf) != 0 { 78 | goto bad 79 | } 80 | 81 | return key, value, true, nil 82 | bad: 83 | return nil, nil, false, err 84 | } 85 | -------------------------------------------------------------------------------- /internal/integration/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package integration 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "io" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/zeebo/assert" 14 | 15 | "storj.io/drpc/drpcerr" 16 | "storj.io/drpc/drpctest" 17 | ) 18 | 19 | func TestError(t *testing.T) { 20 | ctx := drpctest.NewTracker(t) 21 | defer ctx.Close() 22 | 23 | cli, close := createConnection(t, standardImpl) 24 | defer close() 25 | 26 | for i := int64(2); i < 20; i++ { 27 | out, err := cli.Method1(ctx, in(i)) 28 | assert.Nil(t, out) 29 | assert.Error(t, err) 30 | assert.Equal(t, drpcerr.Code(err), i) 31 | } 32 | } 33 | 34 | func TestError_Context(t *testing.T) { 35 | ctx := drpctest.NewTracker(t) 36 | defer ctx.Close() 37 | 38 | cli, close := createConnection(t, impl{ 39 | Method1Fn: func(ctx context.Context, in *In) (*Out, error) { 40 | return nil, [...]error{ 41 | context.Canceled, 42 | context.DeadlineExceeded, 43 | }[in.In%2] 44 | }, 45 | }) 46 | defer close() 47 | 48 | for i := int64(2); i < 20; i++ { 49 | out, err := cli.Method1(ctx, in(i)) 50 | assert.Nil(t, out) 51 | assert.Error(t, err) 52 | assert.That(t, strings.Contains(err.Error(), "context")) 53 | } 54 | } 55 | 56 | func TestError_UnitaryNilResponse(t *testing.T) { 57 | ctx := drpctest.NewTracker(t) 58 | defer ctx.Close() 59 | 60 | cli, close := createConnection(t, impl{ 61 | Method1Fn: func(ctx context.Context, in *In) (*Out, error) { 62 | return nil, nil 63 | }, 64 | }) 65 | defer close() 66 | 67 | out, err := cli.Method1(ctx, in(1)) 68 | assert.Equal(t, err, io.EOF) 69 | assert.Nil(t, out) 70 | } 71 | 72 | func TestError_Message(t *testing.T) { 73 | ctx := drpctest.NewTracker(t) 74 | defer ctx.Close() 75 | 76 | cli, close := createConnection(t, impl{ 77 | Method1Fn: func(ctx context.Context, in *In) (*Out, error) { 78 | return nil, errors.New("some unique error message") 79 | }, 80 | }) 81 | defer close() 82 | 83 | out, err := cli.Method1(ctx, in(1)) 84 | assert.Nil(t, out) 85 | assert.Error(t, err) 86 | assert.Equal(t, err.Error(), "some unique error message") 87 | } 88 | -------------------------------------------------------------------------------- /internal/backcompat/run_server.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package backcompat 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | 13 | "github.com/zeebo/errs" 14 | 15 | "storj.io/drpc/drpcmux" 16 | "storj.io/drpc/drpcserver" 17 | "storj.io/drpc/internal/backcompat/servicedefs" 18 | ) 19 | 20 | func runServer(ctx context.Context, addr string) error { 21 | ctx, cancel := context.WithCancel(ctx) 22 | defer cancel() 23 | 24 | lis, err := net.Listen("tcp", addr) 25 | if err != nil { 26 | return errs.Wrap(err) 27 | } 28 | defer func() { _ = lis.Close() }() 29 | 30 | fmt.Println(lis.Addr()) 31 | 32 | conn, err := lis.Accept() 33 | if err != nil { 34 | return errs.Wrap(err) 35 | } 36 | defer func() { _ = conn.Close() }() 37 | 38 | mux := drpcmux.New() 39 | _ = servicedefs.DRPCRegisterService(mux, server{}) 40 | _ = drpcserver.New(mux).ServeOne(ctx, conn) 41 | return nil 42 | } 43 | 44 | type server struct{} 45 | 46 | func (server) Method1(ctx context.Context, in *servicedefs.In) (*servicedefs.Out, error) { 47 | return &servicedefs.Out{Out: in.In}, nil 48 | } 49 | 50 | func (server) Method2(stream servicedefs.DRPCService_Method2Stream) error { 51 | var i int64 52 | for { 53 | _, err := stream.Recv() 54 | if errors.Is(err, io.EOF) { 55 | break 56 | } else if err != nil { 57 | return errs.Wrap(err) 58 | } 59 | i++ 60 | } 61 | return errs.Wrap(stream.SendAndClose(&servicedefs.Out{Out: i})) 62 | } 63 | 64 | func (server) Method3(in *servicedefs.In, stream servicedefs.DRPCService_Method3Stream) error { 65 | for ; in.In > 0; in.In-- { 66 | if err := stream.Send(&servicedefs.Out{Out: in.In}); err != nil { 67 | return errs.Wrap(err) 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | func (server) Method4(stream servicedefs.DRPCService_Method4Stream) error { 74 | var i int64 75 | for { 76 | _, err := stream.Recv() 77 | if errors.Is(err, io.EOF) { 78 | break 79 | } else if err != nil { 80 | return errs.Wrap(err) 81 | } 82 | i++ 83 | } 84 | for ; i > 0; i-- { 85 | if err := stream.Send(&servicedefs.Out{Out: i}); err != nil { 86 | return errs.Wrap(err) 87 | } 88 | } 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /drpccache/cache.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpccache 5 | 6 | import ( 7 | "context" 8 | "sync" 9 | ) 10 | 11 | type cacheKey struct{} 12 | 13 | // Cache is a per stream cache. 14 | type Cache struct { 15 | mu sync.Mutex 16 | values map[interface{}]interface{} 17 | } 18 | 19 | // New returns a new cache. 20 | func New() *Cache { return &Cache{} } 21 | 22 | // FromContext returns a cache from a context. 23 | // 24 | // Example usage: 25 | // 26 | // cache := drpccache.FromContext(stream.Context()) 27 | // if cache != nil { 28 | // value := cache.LoadOrCreate("initialized", func() (interface{}) { 29 | // return 42 30 | // }) 31 | // } 32 | func FromContext(ctx context.Context) *Cache { 33 | cache, _ := ctx.Value(cacheKey{}).(*Cache) 34 | return cache 35 | } 36 | 37 | // WithContext returns a context with the value cache associated with the context. 38 | func WithContext(parent context.Context, cache *Cache) context.Context { 39 | return context.WithValue(parent, cacheKey{}, cache) 40 | } 41 | 42 | // init ensures that the values map exist. 43 | func (cache *Cache) init() { 44 | if cache.values == nil { 45 | cache.values = map[interface{}]interface{}{} 46 | } 47 | } 48 | 49 | // Clear clears the cache. 50 | func (cache *Cache) Clear() { 51 | cache.mu.Lock() 52 | defer cache.mu.Unlock() 53 | 54 | cache.values = nil 55 | } 56 | 57 | // Store sets the value at a key. 58 | func (cache *Cache) Store(key, value interface{}) { 59 | cache.mu.Lock() 60 | defer cache.mu.Unlock() 61 | 62 | cache.init() 63 | cache.values[key] = value 64 | } 65 | 66 | // Load returns the value with the given key. 67 | func (cache *Cache) Load(key interface{}) interface{} { 68 | cache.mu.Lock() 69 | defer cache.mu.Unlock() 70 | 71 | if cache.values == nil { 72 | return nil 73 | } 74 | 75 | return cache.values[key] 76 | } 77 | 78 | // LoadOrCreate returns the value with the given key. 79 | func (cache *Cache) LoadOrCreate(key interface{}, fn func() interface{}) interface{} { 80 | cache.mu.Lock() 81 | defer cache.mu.Unlock() 82 | 83 | cache.init() 84 | 85 | value, ok := cache.values[key] 86 | if !ok { 87 | value = fn() 88 | cache.values[key] = value 89 | } 90 | 91 | return value 92 | } 93 | -------------------------------------------------------------------------------- /drpcsignal/chan.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcsignal 5 | 6 | import ( 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | var closed = make(chan struct{}) 12 | 13 | func init() { close(closed) } 14 | 15 | // Chan is a lazily allocated chan struct{} that avoids allocating if 16 | // it is closed before being used for anything. 17 | type Chan struct { 18 | done uint32 19 | mu sync.Mutex 20 | ch chan struct{} 21 | } 22 | 23 | func (c *Chan) do(f func()) bool { 24 | return atomic.LoadUint32(&c.done) == 0 && c.doSlow(f) 25 | } 26 | 27 | func (c *Chan) doSlow(f func()) bool { 28 | c.mu.Lock() 29 | defer c.mu.Unlock() 30 | 31 | if c.done == 0 { 32 | defer atomic.StoreUint32(&c.done, 1) 33 | f() 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | // setFresh sets the channel to a freshly allocated one. 40 | func (c *Chan) setFresh() { 41 | c.ch = make(chan struct{}) 42 | } 43 | 44 | // setClosed sets the channel to an already closed one. 45 | func (c *Chan) setClosed() { 46 | c.ch = closed 47 | } 48 | 49 | // Close tries to set the channel to an already closed one if 50 | // a fresh one has not already been set, and closes the fresh 51 | // one otherwise. 52 | func (c *Chan) Close() { 53 | if !c.do(c.setClosed) { 54 | close(c.ch) 55 | } 56 | } 57 | 58 | // Make sets the channel to a freshly allocated channel with the 59 | // provided capacity. It is a no-op if called after any other 60 | // methods. 61 | func (c *Chan) Make(cap uint) { 62 | c.do(func() { c.ch = make(chan struct{}, cap) }) 63 | } 64 | 65 | // Get returns the channel, allocating if necessary. 66 | func (c *Chan) Get() chan struct{} { 67 | c.do(c.setFresh) 68 | return c.ch 69 | } 70 | 71 | // Send sends a value on the channel, allocating if necessary. 72 | func (c *Chan) Send() { 73 | c.do(c.setFresh) 74 | c.ch <- struct{}{} 75 | } 76 | 77 | // Recv receives a value on the channel, allocating if necessary. 78 | func (c *Chan) Recv() { 79 | c.do(c.setFresh) 80 | <-c.ch 81 | } 82 | 83 | // Full returns true if the channel is currently full. The information 84 | // is immediately invalid in the sense that a send could always block. 85 | func (c *Chan) Full() bool { 86 | c.do(c.setFresh) 87 | 88 | select { 89 | case c.ch <- struct{}{}: 90 | <-c.ch 91 | return false 92 | default: 93 | return true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /drpcconn/conn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcconn 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "testing" 10 | "time" 11 | 12 | "github.com/zeebo/assert" 13 | 14 | "storj.io/drpc" 15 | "storj.io/drpc/drpctest" 16 | "storj.io/drpc/drpcwire" 17 | ) 18 | 19 | // Dummy encoding, which assumes the drpc.Message is a *string. 20 | type testEncoding struct{} 21 | 22 | func (testEncoding) Marshal(msg drpc.Message) ([]byte, error) { 23 | return []byte(*msg.(*string)), nil 24 | } 25 | 26 | func (testEncoding) Unmarshal(buf []byte, msg drpc.Message) error { 27 | *msg.(*string) = string(buf) 28 | return nil 29 | } 30 | 31 | func TestConn_InvokeFlushesSendClose(t *testing.T) { 32 | ctx := drpctest.NewTracker(t) 33 | defer ctx.Close() 34 | 35 | pc, ps := net.Pipe() 36 | defer func() { assert.NoError(t, pc.Close()) }() 37 | defer func() { assert.NoError(t, ps.Close()) }() 38 | 39 | invokeDone := make(chan struct{}) 40 | 41 | ctx.Run(func(ctx context.Context) { 42 | wr := drpcwire.NewWriter(ps, 64) 43 | rd := drpcwire.NewReader(ps) 44 | 45 | _, _ = rd.ReadPacket() // Invoke 46 | _, _ = rd.ReadPacket() // Message 47 | pkt, _ := rd.ReadPacket() // CloseSend 48 | 49 | _ = wr.WritePacket(drpcwire.Packet{ 50 | Data: []byte("qux"), 51 | ID: drpcwire.ID{Stream: pkt.ID.Stream, Message: 1}, 52 | Kind: drpcwire.KindMessage, 53 | }) 54 | _ = wr.Flush() 55 | 56 | _, _ = rd.ReadPacket() // Close 57 | <-invokeDone // wait for invoke to return 58 | 59 | // ensure that any later packets are dropped by writing one 60 | // before closing the transport. 61 | for i := 0; i < 5; i++ { 62 | _ = wr.WritePacket(drpcwire.Packet{ 63 | ID: drpcwire.ID{Stream: pkt.ID.Stream, Message: 2}, 64 | Kind: drpcwire.KindCloseSend, 65 | }) 66 | _ = wr.Flush() 67 | } 68 | 69 | _ = ps.Close() 70 | }) 71 | 72 | conn := New(pc) 73 | 74 | in, out := "baz", "" 75 | assert.NoError(t, conn.Invoke(ctx, "/com.example.Foo/Bar", testEncoding{}, &in, &out)) 76 | assert.True(t, out == "qux") 77 | 78 | invokeDone <- struct{}{} // signal invoke has returned 79 | 80 | // we should eventually notice the transport is closed 81 | select { 82 | case <-conn.Closed(): 83 | case <-time.After(1 * time.Second): 84 | t.Fatal("took too long for conn to be closed") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /internal/integration/large_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package integration 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "io" 10 | "math/rand" 11 | "testing" 12 | 13 | "github.com/zeebo/assert" 14 | "github.com/zeebo/errs" 15 | 16 | "storj.io/drpc/drpctest" 17 | ) 18 | 19 | func TestLarge(t *testing.T) { 20 | ctx := drpctest.NewTracker(t) 21 | defer ctx.Close() 22 | 23 | // 24 | // define some test helpers 25 | // 26 | 27 | type any = interface{} 28 | 29 | receiver := func(recv func() (any, error), valid func(any) bool) error { 30 | var got []any 31 | for { 32 | in, err := recv() 33 | if errors.Is(err, io.EOF) { 34 | break 35 | } else if err != nil { 36 | return err 37 | } 38 | got = append(got, in) 39 | } 40 | for _, v := range got { 41 | if !valid(v) { 42 | return errs.New("invalid data") 43 | } 44 | } 45 | return nil 46 | } 47 | 48 | sender := func(send func(n int64) error) error { 49 | for i := 0; i < 100; i++ { 50 | if err := send(int64(rand.Intn(4 << 15))); err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | run := func(send func(n int64) error, recv func() (any, error), valid func(any) bool, close func() error) error { 58 | ch := make(chan error, 2) 59 | go func() { ch <- receiver(recv, valid) }() 60 | go func() { ch <- errs.Combine(sender(send), close()) }() 61 | return errs.Combine(<-ch, <-ch) 62 | } 63 | 64 | // 65 | // execute the actual test 66 | // 67 | 68 | cli, close := createConnection(t, &impl{ 69 | Method4Fn: func(stream DRPCService_Method4Stream) error { 70 | return run( 71 | func(n int64) error { return stream.Send(&Out{Out: n, Data: data(n)}) }, 72 | func() (any, error) { return stream.Recv() }, 73 | func(in any) bool { return bytes.Equal(in.(*In).Data, data(in.(*In).In)) }, 74 | stream.CloseSend, 75 | ) 76 | }, 77 | }) 78 | defer close() 79 | 80 | stream, err := cli.Method4(ctx) 81 | assert.NoError(t, err) 82 | defer func() { assert.NoError(t, stream.Close()) }() 83 | 84 | assert.NoError(t, run( 85 | func(n int64) error { return stream.Send(&In{In: n, Data: data(n)}) }, 86 | func() (any, error) { return stream.Recv() }, 87 | func(out any) bool { return bytes.Equal(out.(*Out).Data, data(out.(*Out).Out)) }, 88 | stream.CloseSend, 89 | )) 90 | } 91 | -------------------------------------------------------------------------------- /internal/backcompat/run_client.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package backcompat 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "io" 10 | "net" 11 | 12 | "github.com/zeebo/errs" 13 | 14 | "storj.io/drpc/drpcconn" 15 | "storj.io/drpc/internal/backcompat/servicedefs" 16 | ) 17 | 18 | func runClient(ctx context.Context, addr string) error { 19 | conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", addr) 20 | if err != nil { 21 | return errs.Wrap(err) 22 | } 23 | defer func() { _ = conn.Close() }() 24 | 25 | cli := servicedefs.NewDRPCServiceClient(drpcconn.New(conn)) 26 | 27 | { // check method 1 28 | out, err := cli.Method1(ctx, &servicedefs.In{In: 10}) 29 | if err != nil { 30 | return errs.Wrap(err) 31 | } else if out.Out != 10 { 32 | return errs.New("invalid out value") 33 | } 34 | } 35 | 36 | { // check method 2 37 | stream, err := cli.Method2(ctx) 38 | if err != nil { 39 | return errs.Wrap(err) 40 | } 41 | for i := 0; i < 5; i++ { 42 | if err := stream.Send(&servicedefs.In{In: 0}); err != nil { 43 | return errs.Wrap(err) 44 | } 45 | } 46 | out, err := stream.CloseAndRecv() 47 | if err != nil { 48 | return errs.Wrap(err) 49 | } else if out.Out != 5 { 50 | return errs.New("invalid out value") 51 | } 52 | } 53 | 54 | { // check method 3 55 | stream, err := cli.Method3(ctx, &servicedefs.In{In: 7}) 56 | if err != nil { 57 | return errs.Wrap(err) 58 | } 59 | for i := 0; i < 7; i++ { 60 | _, err := stream.Recv() 61 | if err != nil { 62 | return errs.Wrap(err) 63 | } 64 | } 65 | _, err = stream.Recv() 66 | if !errors.Is(err, io.EOF) { 67 | return errs.New("invalid last receive (method3): %w", err) 68 | } 69 | } 70 | 71 | { // check method 4 72 | stream, err := cli.Method4(ctx) 73 | if err != nil { 74 | return errs.Wrap(err) 75 | } 76 | for i := 0; i < 15; i++ { 77 | if err := stream.Send(&servicedefs.In{In: 0}); err != nil { 78 | return errs.Wrap(err) 79 | } 80 | } 81 | if err := stream.CloseSend(); err != nil { 82 | return errs.Wrap(err) 83 | } 84 | for i := 0; i < 15; i++ { 85 | _, err := stream.Recv() 86 | if err != nil { 87 | return errs.Wrap(err) 88 | } 89 | } 90 | _, err = stream.Recv() 91 | if !errors.Is(err, io.EOF) { 92 | return errs.New("invalid last receive (method4): %w", err) 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /drpcmux/mux.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcmux 5 | 6 | import ( 7 | "reflect" 8 | 9 | "github.com/zeebo/errs" 10 | 11 | "storj.io/drpc" 12 | ) 13 | 14 | // Mux is an implementation of Handler to serve drpc connections to the 15 | // appropriate Receivers registered by Descriptions. 16 | type Mux struct { 17 | rpcs map[string]rpcData 18 | } 19 | 20 | // New constructs a new Mux. 21 | func New() *Mux { 22 | return &Mux{ 23 | rpcs: make(map[string]rpcData), 24 | } 25 | } 26 | 27 | var ( 28 | streamType = reflect.TypeOf((*drpc.Stream)(nil)).Elem() 29 | messageType = reflect.TypeOf((*drpc.Message)(nil)).Elem() 30 | ) 31 | 32 | type rpcData struct { 33 | srv interface{} 34 | enc drpc.Encoding 35 | receiver drpc.Receiver 36 | in1 reflect.Type 37 | in2 reflect.Type 38 | unitary bool 39 | } 40 | 41 | // Register associates the RPCs described by the description in the server. 42 | // It returns an error if there was a problem registering it. 43 | func (m *Mux) Register(srv interface{}, desc drpc.Description) error { 44 | n := desc.NumMethods() 45 | for i := 0; i < n; i++ { 46 | rpc, enc, receiver, method, ok := desc.Method(i) 47 | if !ok { 48 | return errs.New("Description returned invalid method for index %d", i) 49 | } 50 | if err := m.registerOne(srv, rpc, enc, receiver, method); err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | // registerOne does the work to register a single rpc. 58 | func (m *Mux) registerOne(srv interface{}, rpc string, enc drpc.Encoding, receiver drpc.Receiver, method interface{}) error { 59 | data := rpcData{srv: srv, enc: enc, receiver: receiver} 60 | 61 | switch mt := reflect.TypeOf(method); { 62 | // unitary input, unitary output 63 | case mt.NumOut() == 2: 64 | data.unitary = true 65 | data.in1 = mt.In(2) 66 | if !data.in1.Implements(messageType) { 67 | return errs.New("input argument not a drpc message: %v", data.in1) 68 | } 69 | 70 | // unitary input, stream output 71 | case mt.NumIn() == 3: 72 | data.in1 = mt.In(1) 73 | if !data.in1.Implements(messageType) { 74 | return errs.New("input argument not a drpc message: %v", data.in1) 75 | } 76 | data.in2 = streamType 77 | 78 | // stream input 79 | case mt.NumIn() == 2: 80 | data.in1 = streamType 81 | 82 | // code gen bug? 83 | default: 84 | return errs.New("unknown method type: %v", mt) 85 | } 86 | 87 | m.rpcs[rpc] = data 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /examples/drpc_and_http/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "net/http" 10 | 11 | "golang.org/x/sync/errgroup" 12 | 13 | "storj.io/drpc/drpchttp" 14 | "storj.io/drpc/drpcmigrate" 15 | "storj.io/drpc/drpcmux" 16 | "storj.io/drpc/drpcserver" 17 | 18 | "storj.io/drpc/examples/drpc_and_http/pb" 19 | ) 20 | 21 | type CookieMonsterServer struct { 22 | pb.DRPCCookieMonsterUnimplementedServer 23 | // struct fields 24 | } 25 | 26 | // EatCookie turns a cookie into crumbs. 27 | func (s *CookieMonsterServer) EatCookie(ctx context.Context, cookie *pb.Cookie) (*pb.Crumbs, error) { 28 | return &pb.Crumbs{ 29 | Cookie: cookie, 30 | }, nil 31 | } 32 | 33 | func main() { 34 | err := Main(context.Background()) 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | 40 | func Main(ctx context.Context) error { 41 | // create an RPC server 42 | cookieMonster := &CookieMonsterServer{} 43 | 44 | // create a drpc RPC mux 45 | m := drpcmux.New() 46 | 47 | // register the proto-specific methods on the mux 48 | err := pb.DRPCRegisterCookieMonster(m, cookieMonster) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | // listen on a tcp socket 54 | lis, err := net.Listen("tcp", ":8080") 55 | if err != nil { 56 | return err 57 | } 58 | 59 | // create a listen mux that evalutes enough bytes to recognize the DRPC header 60 | lisMux := drpcmigrate.NewListenMux(lis, len(drpcmigrate.DRPCHeader)) 61 | 62 | // grap the listen mux route for the DRPC Header and default handler 63 | drpcLis := lisMux.Route(drpcmigrate.DRPCHeader) 64 | httpLis := lisMux.Default() 65 | 66 | // we're going to run the different protocol servers in parallel, so 67 | // make an errgroup 68 | var group errgroup.Group 69 | 70 | // drpc handling 71 | group.Go(func() error { 72 | // create a drpc server 73 | s := drpcserver.New(m) 74 | 75 | // run the server 76 | // N.B.: if you want TLS, you need to wrap the drpcLis net.Listener 77 | // with TLS before passing to Serve here. 78 | return s.Serve(ctx, drpcLis) 79 | }) 80 | 81 | // http handling 82 | group.Go(func() error { 83 | // create an http server using the drpc mux wrapped in a handler 84 | s := http.Server{Handler: drpchttp.New(m)} 85 | 86 | // run the server 87 | return s.Serve(httpLis) 88 | }) 89 | 90 | // run the listen mux 91 | group.Go(func() error { 92 | return lisMux.Run(ctx) 93 | }) 94 | 95 | // wait 96 | return group.Wait() 97 | } 98 | -------------------------------------------------------------------------------- /examples/grpc_and_drpc/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "net" 9 | 10 | "golang.org/x/sync/errgroup" 11 | "google.golang.org/grpc" 12 | 13 | "storj.io/drpc/drpcmigrate" 14 | "storj.io/drpc/drpcmux" 15 | "storj.io/drpc/drpcserver" 16 | 17 | "storj.io/drpc/examples/grpc_and_drpc/pb" 18 | ) 19 | 20 | type CookieMonsterServer struct { 21 | pb.UnimplementedCookieMonsterServer 22 | // struct fields 23 | } 24 | 25 | // EatCookie turns a cookie into crumbs. 26 | func (s *CookieMonsterServer) EatCookie(ctx context.Context, cookie *pb.Cookie) (*pb.Crumbs, error) { 27 | return &pb.Crumbs{ 28 | Cookie: cookie, 29 | }, nil 30 | } 31 | 32 | func main() { 33 | err := Main(context.Background()) 34 | if err != nil { 35 | panic(err) 36 | } 37 | } 38 | 39 | func Main(ctx context.Context) error { 40 | // create an RPC server 41 | cookieMonster := &CookieMonsterServer{} 42 | 43 | // listen on a tcp socket 44 | lis, err := net.Listen("tcp", ":8080") 45 | if err != nil { 46 | return err 47 | } 48 | 49 | // create a listen mux that evalutes enough bytes to recognize the DRPC header 50 | lisMux := drpcmigrate.NewListenMux(lis, len(drpcmigrate.DRPCHeader)) 51 | 52 | // grap the listen mux route for the DRPC Header and default listener 53 | drpcLis := lisMux.Route(drpcmigrate.DRPCHeader) 54 | grpcLis := lisMux.Default() 55 | 56 | // we're going to run the different protocol servers in parallel, so 57 | // make an errgroup 58 | var group errgroup.Group 59 | 60 | // grpc handling 61 | group.Go(func() error { 62 | // create a grpc server (without TLS) 63 | s := grpc.NewServer() 64 | 65 | // register the proto-specific methods on the server 66 | pb.RegisterCookieMonsterServer(s, cookieMonster) 67 | 68 | // run the server 69 | return s.Serve(grpcLis) 70 | }) 71 | 72 | // drpc handling 73 | group.Go(func() error { 74 | // create a drpc RPC mux 75 | m := drpcmux.New() 76 | 77 | // register the proto-specific methods on the mux 78 | err := pb.DRPCRegisterCookieMonster(m, cookieMonster) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | // create a drpc server 84 | s := drpcserver.New(m) 85 | 86 | // run the server 87 | // N.B.: if you want TLS, you need to wrap the drpcLis net.Listener 88 | // with TLS before passing to Serve here. 89 | return s.Serve(ctx, drpcLis) 90 | }) 91 | 92 | // run the listen mux 93 | group.Go(func() error { 94 | return lisMux.Run(ctx) 95 | }) 96 | 97 | // wait 98 | return group.Wait() 99 | } 100 | -------------------------------------------------------------------------------- /examples/opentelemetry/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 3 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 4 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 5 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 6 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 7 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 12 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 13 | github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g= 14 | github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 15 | go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= 16 | go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= 17 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.10.0 h1:c9UtMu/qnbLlVwTwt+ABrURrioEruapIslTDYZHJe2w= 18 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.10.0/go.mod h1:h3Lrh9t3Dnqp3NPwAZx7i37UFX7xrfnO1D+fuClREOA= 19 | go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= 20 | go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= 21 | go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= 22 | go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= 23 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= 24 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 27 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 28 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 29 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 30 | -------------------------------------------------------------------------------- /internal/twirpcompat/compat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package twirpcompat 5 | 6 | import ( 7 | "context" 8 | errors "errors" 9 | "net/http" 10 | "testing" 11 | 12 | "github.com/twitchtv/twirp" 13 | "github.com/zeebo/assert" 14 | "google.golang.org/protobuf/proto" 15 | ) 16 | 17 | func TestCompat_Noop(t *testing.T) { 18 | for _, code := range []twirp.ErrorCode{ 19 | "", 20 | twirp.Canceled, 21 | twirp.Unknown, 22 | twirp.InvalidArgument, 23 | twirp.DeadlineExceeded, 24 | twirp.NotFound, 25 | twirp.BadRoute, 26 | twirp.AlreadyExists, 27 | twirp.PermissionDenied, 28 | twirp.Unauthenticated, 29 | twirp.ResourceExhausted, 30 | twirp.FailedPrecondition, 31 | twirp.Aborted, 32 | twirp.OutOfRange, 33 | twirp.Unimplemented, 34 | twirp.Internal, 35 | twirp.Unavailable, 36 | twirp.DataLoss, 37 | } { 38 | t.Run(string(code), func(t *testing.T) { 39 | cs, srv := newServer() 40 | defer srv.Close() 41 | 42 | cs.noop = func(context.Context, *Empty) (*Empty, error) { 43 | if code != "" { 44 | return nil, twirp.NewError(code, "oopsie") 45 | } 46 | return new(Empty), nil 47 | } 48 | 49 | client := NewCompatServiceProtobufClient(srv.URL, http.DefaultClient) 50 | _, err := client.NoopMethod(context.Background(), new(Empty)) 51 | 52 | if code == "" { 53 | assert.NoError(t, err) 54 | } else { 55 | var te twirp.Error 56 | assert.That(t, errors.As(err, &te)) 57 | assert.Equal(t, code, te.Code()) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | func TestCompat_Method(t *testing.T) { 64 | cs, srv := newServer() 65 | defer srv.Close() 66 | 67 | cs.method = func(ctx context.Context, req *Req) (*Resp, error) { 68 | switch req.V { 69 | case "": 70 | return &Resp{}, nil 71 | case "error": 72 | return nil, twirp.InvalidArgumentError("V", "some error") 73 | default: 74 | return &Resp{V: int32(len(req.V))}, nil 75 | } 76 | } 77 | client := NewCompatServiceProtobufClient(srv.URL, http.DefaultClient) 78 | 79 | t.Run("empty", func(t *testing.T) { 80 | resp, err := client.Method(context.Background(), new(Req)) 81 | assert.NoError(t, err) 82 | assert.That(t, proto.Equal(resp, new(Resp))) 83 | }) 84 | 85 | t.Run("data", func(t *testing.T) { 86 | resp, err := client.Method(context.Background(), &Req{V: "hello world"}) 87 | assert.NoError(t, err) 88 | assert.That(t, proto.Equal(resp, &Resp{V: 11})) 89 | }) 90 | 91 | t.Run("error", func(t *testing.T) { 92 | resp, err := client.Method(context.Background(), &Req{V: "error"}) 93 | var te twirp.Error 94 | assert.That(t, errors.As(err, &te)) 95 | assert.Equal(t, twirp.InvalidArgument, te.Code()) 96 | assert.Nil(t, resp) 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /drpcconn/README.md: -------------------------------------------------------------------------------- 1 | # package drpcconn 2 | 3 | `import "storj.io/drpc/drpcconn"` 4 | 5 | Package drpcconn creates a drpc client connection from a transport. 6 | 7 | ## Usage 8 | 9 | #### type Conn 10 | 11 | ```go 12 | type Conn struct { 13 | } 14 | ``` 15 | 16 | Conn is a drpc client connection. 17 | 18 | #### func New 19 | 20 | ```go 21 | func New(tr drpc.Transport) *Conn 22 | ``` 23 | New returns a conn that uses the transport for reads and writes. 24 | 25 | #### func NewWithOptions 26 | 27 | ```go 28 | func NewWithOptions(tr drpc.Transport, opts Options) *Conn 29 | ``` 30 | NewWithOptions returns a conn that uses the transport for reads and writes. The 31 | Options control details of how the conn operates. 32 | 33 | #### func (*Conn) Close 34 | 35 | ```go 36 | func (c *Conn) Close() (err error) 37 | ``` 38 | Close closes the connection. 39 | 40 | #### func (*Conn) Closed 41 | 42 | ```go 43 | func (c *Conn) Closed() <-chan struct{} 44 | ``` 45 | Closed returns a channel that is closed once the connection is closed. 46 | 47 | #### func (*Conn) Invoke 48 | 49 | ```go 50 | func (c *Conn) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) (err error) 51 | ``` 52 | Invoke issues the rpc on the transport serializing in, waits for a response, and 53 | deserializes it into out. Only one Invoke or Stream may be open at a time. 54 | 55 | #### func (*Conn) NewStream 56 | 57 | ```go 58 | func (c *Conn) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (_ drpc.Stream, err error) 59 | ``` 60 | NewStream begins a streaming rpc on the connection. Only one Invoke or Stream 61 | may be open at a time. 62 | 63 | #### func (*Conn) Stats 64 | 65 | ```go 66 | func (c *Conn) Stats() map[string]drpcstats.Stats 67 | ``` 68 | Stats returns the collected stats grouped by rpc. 69 | 70 | #### func (*Conn) Transport 71 | 72 | ```go 73 | func (c *Conn) Transport() drpc.Transport 74 | ``` 75 | Transport returns the transport the conn is using. 76 | 77 | #### func (*Conn) Unblocked 78 | 79 | ```go 80 | func (c *Conn) Unblocked() <-chan struct{} 81 | ``` 82 | Unblocked returns a channel that is closed once the connection is no longer 83 | blocked by a previously canceled Invoke or NewStream call. It should not be 84 | called concurrently with Invoke or NewStream. 85 | 86 | #### type Options 87 | 88 | ```go 89 | type Options struct { 90 | // Manager controls the options we pass to the manager of this conn. 91 | Manager drpcmanager.Options 92 | 93 | // CollectStats controls whether the server should collect stats on the 94 | // rpcs it creates. 95 | CollectStats bool 96 | } 97 | ``` 98 | 99 | Options controls configuration settings for a conn. 100 | -------------------------------------------------------------------------------- /drpcpool/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcpool 5 | 6 | import ( 7 | "context" 8 | 9 | "storj.io/drpc" 10 | ) 11 | 12 | type callbackConn struct { 13 | CloseFn func() error 14 | ClosedFn func() <-chan struct{} 15 | UnblockedFn func() <-chan struct{} 16 | InvokeFn func(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error 17 | NewStreamFn func(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) 18 | } 19 | 20 | func (cb *callbackConn) Close() error { 21 | if cb.CloseFn != nil { 22 | return cb.CloseFn() 23 | } 24 | return nil 25 | } 26 | 27 | func (cb *callbackConn) Closed() <-chan struct{} { 28 | if cb.ClosedFn != nil { 29 | return cb.ClosedFn() 30 | } 31 | return nil 32 | } 33 | 34 | func (cb *callbackConn) Unblocked() <-chan struct{} { 35 | if cb.UnblockedFn != nil { 36 | return cb.UnblockedFn() 37 | } 38 | return closedCh 39 | } 40 | 41 | func (cb *callbackConn) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error { 42 | if cb.InvokeFn != nil { 43 | return cb.InvokeFn(ctx, rpc, enc, in, out) 44 | } 45 | return nil 46 | } 47 | 48 | func (cb *callbackConn) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) { 49 | if cb.NewStreamFn != nil { 50 | return cb.NewStreamFn(ctx, rpc, enc) 51 | } 52 | return newCallbackStream(ctx), nil 53 | } 54 | 55 | type callbackStream struct { 56 | drpc.Stream 57 | ctx context.Context 58 | cancel func() 59 | } 60 | 61 | func newCallbackStream(ctx context.Context) *callbackStream { 62 | ctx, cancel := context.WithCancel(ctx) 63 | return &callbackStream{ 64 | ctx: ctx, 65 | cancel: cancel, 66 | } 67 | } 68 | 69 | func (cb *callbackStream) Close() error { cb.cancel(); return nil } 70 | func (cb *callbackStream) Context() context.Context { return cb.ctx } 71 | 72 | // getConn is a helper to get a new conn from the pool that will send its key over 73 | // the closed channel when it is closed. 74 | func getConn(ctx context.Context, pool *Pool[string, Conn], closed chan string, key string) Conn { 75 | return pool.Get(ctx, key, func(ctx context.Context, _ string) (Conn, error) { 76 | return &callbackConn{CloseFn: func() error { closed <- key; return nil }}, nil 77 | }) 78 | } 79 | 80 | // useConn is a helper to get a new conn from the pool and use it, sending its 81 | // key over the closed channel when it is closed. 82 | func useConn(ctx context.Context, pool *Pool[string, Conn], closed chan string, key string) { 83 | conn := getConn(ctx, pool, closed, key) 84 | invoke(ctx, conn) 85 | } 86 | 87 | // invoke is a helper to invoke a dummy rpc on the conn. 88 | func invoke(ctx context.Context, conn Conn) { 89 | _ = conn.Invoke(ctx, "", nil, nil, nil) 90 | } 91 | -------------------------------------------------------------------------------- /drpcwire/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcwire 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "sync" 10 | "sync/atomic" 11 | 12 | "storj.io/drpc/drpcdebug" 13 | ) 14 | 15 | // 16 | // Writer 17 | // 18 | 19 | // Writer is a helper to buffer and write packets and frames to an io.Writer. 20 | type Writer struct { 21 | empty uint32 22 | w io.Writer 23 | size int 24 | mu sync.Mutex 25 | buf []byte 26 | } 27 | 28 | // NewWriter returns a Writer that will attempt to buffer size data before 29 | // sending it to the io.Writer. 30 | func NewWriter(w io.Writer, size int) *Writer { 31 | if size == 0 { 32 | size = 4 * 1024 33 | } 34 | 35 | return &Writer{ 36 | w: w, 37 | size: size, 38 | buf: make([]byte, 0, size), 39 | } 40 | } 41 | 42 | func (b *Writer) log(what string, cb func() string) { 43 | if drpcdebug.Enabled { 44 | drpcdebug.Log(func() (_, _, _ string) { return fmt.Sprintf("", b), what, cb() }) 45 | } 46 | } 47 | 48 | // WritePacket writes the packet as a single frame, ignoring any size 49 | // constraints. 50 | func (b *Writer) WritePacket(pkt Packet) (err error) { 51 | return b.WriteFrame(Frame{ 52 | Data: pkt.Data, 53 | ID: pkt.ID, 54 | Kind: pkt.Kind, 55 | Control: pkt.Control, 56 | Done: true, 57 | }) 58 | } 59 | 60 | // Empty returns true if there are no bytes buffered in the writer. 61 | func (b *Writer) Empty() bool { 62 | return atomic.LoadUint32(&b.empty) == 0 63 | } 64 | 65 | // Reset clears any pending data in the buffer. 66 | func (b *Writer) Reset() *Writer { 67 | b.mu.Lock() 68 | defer b.mu.Unlock() 69 | 70 | b.buf = b.buf[:0] 71 | atomic.StoreUint32(&b.empty, 0) 72 | return b 73 | } 74 | 75 | // WriteFrame appends the frame into the buffer, and if the buffer is larger 76 | // than the configured size, flushes it. 77 | func (b *Writer) WriteFrame(fr Frame) (err error) { 78 | b.mu.Lock() 79 | defer b.mu.Unlock() 80 | 81 | if len(b.buf) == 0 { 82 | atomic.StoreUint32(&b.empty, 1) 83 | } 84 | b.buf = AppendFrame(b.buf, fr) 85 | if len(b.buf) >= b.size { 86 | b.log("FLUSH", func() string { return fmt.Sprintf("buffer: %d > %d", len(b.buf), b.size) }) 87 | _, err = b.w.Write(b.buf) 88 | b.buf = b.buf[:0] 89 | atomic.StoreUint32(&b.empty, 0) 90 | } 91 | return err 92 | } 93 | 94 | // Flush forces a flush of any buffered data to the io.Writer. It is a no-op if 95 | // there is no data in the buffer. 96 | func (b *Writer) Flush() (err error) { 97 | b.mu.Lock() 98 | defer b.mu.Unlock() 99 | 100 | if len(b.buf) > 0 { 101 | _, err = b.w.Write(b.buf) 102 | b.log("FLUSH", func() string { return fmt.Sprintf("explicit: %d", len(b.buf)) }) 103 | b.buf = b.buf[:0] 104 | atomic.StoreUint32(&b.empty, 0) 105 | } 106 | return err 107 | } 108 | -------------------------------------------------------------------------------- /internal/drpcopts/README.md: -------------------------------------------------------------------------------- 1 | # package drpcopts 2 | 3 | `import "storj.io/drpc/internal/drpcopts"` 4 | 5 | Package drpcopts contains internal options. 6 | 7 | This package allows options to exist that are too sharp to provide to typical 8 | users of the library that are not required to be backward compatible. 9 | 10 | ## Usage 11 | 12 | #### func GetManagerStatsCB 13 | 14 | ```go 15 | func GetManagerStatsCB(opts *Manager) func(string) *drpcstats.Stats 16 | ``` 17 | GetManagerStatsCB returns the stats callback stored in the options. 18 | 19 | #### func GetStreamFin 20 | 21 | ```go 22 | func GetStreamFin(opts *Stream) chan<- struct{} 23 | ``` 24 | GetStreamFin returns the chan<- struct{} stored in the options. 25 | 26 | #### func GetStreamKind 27 | 28 | ```go 29 | func GetStreamKind(opts *Stream) string 30 | ``` 31 | GetStreamKind returns the kind debug string stored in the options. 32 | 33 | #### func GetStreamRPC 34 | 35 | ```go 36 | func GetStreamRPC(opts *Stream) string 37 | ``` 38 | GetStreamRPC returns the RPC debug string stored in the options. 39 | 40 | #### func GetStreamStats 41 | 42 | ```go 43 | func GetStreamStats(opts *Stream) *drpcstats.Stats 44 | ``` 45 | GetStreamStats returns the Stats stored in the options. 46 | 47 | #### func GetStreamTransport 48 | 49 | ```go 50 | func GetStreamTransport(opts *Stream) drpc.Transport 51 | ``` 52 | GetStreamTransport returns the drpc.Transport stored in the options. 53 | 54 | #### func SetManagerStatsCB 55 | 56 | ```go 57 | func SetManagerStatsCB(opts *Manager, statsCB func(string) *drpcstats.Stats) 58 | ``` 59 | SetManagerStatsCB sets the stats callback stored in the options. 60 | 61 | #### func SetStreamFin 62 | 63 | ```go 64 | func SetStreamFin(opts *Stream, fin chan<- struct{}) 65 | ``` 66 | SetStreamFin sets the chan<- struct{} stored in the options. 67 | 68 | #### func SetStreamKind 69 | 70 | ```go 71 | func SetStreamKind(opts *Stream, kind string) 72 | ``` 73 | SetStreamKind sets the kind debug string stored in the options. 74 | 75 | #### func SetStreamRPC 76 | 77 | ```go 78 | func SetStreamRPC(opts *Stream, rpc string) 79 | ``` 80 | SetStreamRPC sets the RPC debug string stored in the options. 81 | 82 | #### func SetStreamStats 83 | 84 | ```go 85 | func SetStreamStats(opts *Stream, stats *drpcstats.Stats) 86 | ``` 87 | SetStreamStats sets the Stats stored in the options. 88 | 89 | #### func SetStreamTransport 90 | 91 | ```go 92 | func SetStreamTransport(opts *Stream, tr drpc.Transport) 93 | ``` 94 | SetStreamTransport sets the drpc.Transport stored in the options. 95 | 96 | #### type Manager 97 | 98 | ```go 99 | type Manager struct { 100 | } 101 | ``` 102 | 103 | Manager contains internal options for the drpcmanager package. 104 | 105 | #### type Stream 106 | 107 | ```go 108 | type Stream struct { 109 | } 110 | ``` 111 | 112 | Stream contains internal options for the drpcstream package. 113 | -------------------------------------------------------------------------------- /drpchttp/context.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpchttp 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "strings" 10 | 11 | "github.com/zeebo/errs" 12 | 13 | "storj.io/drpc/drpcmetadata" 14 | ) 15 | 16 | // 17 | // code to unescape and build the request context metadata 18 | // 19 | 20 | // Context returns the context.Context from the http.Request with any metadata 21 | // sent using the X-Drpc-Metadata header set as values. 22 | func Context(req *http.Request) (context.Context, error) { 23 | // header string we look up must already be canonicalized 24 | return buildContext(req.Context(), req.Header["X-Drpc-Metadata"]) 25 | } 26 | 27 | // buildContext adds key/value pairs in entries that are of the form 28 | // `urlencode(key)=urlencode(value)` to the passed in context. 29 | func buildContext(ctx context.Context, entries []string) (context.Context, error) { 30 | for _, entry := range entries { 31 | var key, value string 32 | var err error 33 | 34 | index := strings.IndexByte(entry, '=') 35 | if index >= 0 { 36 | value, err = unescape(entry[index+1:]) 37 | if err != nil { 38 | return nil, err 39 | } 40 | entry = entry[:index] 41 | } 42 | 43 | key, err = unescape(entry) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | ctx = drpcmetadata.Add(ctx, key, value) 49 | } 50 | 51 | return ctx, nil 52 | } 53 | 54 | // unhex adds to the accumulator c the numeric value of the hex digit v 55 | // multiplied by the multiplier m and a boolean indicating if the hex 56 | // digit was valid. it compiles to like 3 compares and can be inlined. 57 | func unhex(c, v, m byte) (d byte, ok bool) { 58 | switch { 59 | case '0' <= v && v <= '9': 60 | d = (v - '0') 61 | case 'a' <= v && v <= 'f': 62 | d = (v - 'a' + 10) 63 | case 'A' <= v && v <= 'F': 64 | d = (v - 'A' + 10) 65 | default: 66 | return 0, false 67 | } 68 | return c + d*m, true 69 | } 70 | 71 | // unescape is an optimized form of url.QueryUnescape that is less general. 72 | func unescape(s string) (string, error) { 73 | count := strings.Count(s, "%") 74 | if count == 0 { 75 | return s, nil 76 | } 77 | 78 | var t strings.Builder 79 | t.Grow(len(s) - 2*count) 80 | 81 | for i := uint(0); i < uint(len(s)); i++ { 82 | switch s[i] { 83 | case '%': 84 | if i+2 >= uint(len(s)) { 85 | return "", errs.New("error unescaping %q: sequence ends", s) 86 | } 87 | 88 | c, ok := unhex(0, s[i+1], 16) 89 | if !ok { 90 | return "", errs.New("error unescaping %q: invalid hex digit", s) 91 | } 92 | 93 | c, ok = unhex(c, s[i+2], 1) 94 | if !ok { 95 | return "", errs.New("error unescaping %q: invalid hex digit", s) 96 | } 97 | 98 | _ = t.WriteByte(c) 99 | i += 2 100 | 101 | default: 102 | _ = t.WriteByte(s[i]) 103 | } 104 | } 105 | 106 | return t.String(), nil 107 | } 108 | -------------------------------------------------------------------------------- /drpcsignal/README.md: -------------------------------------------------------------------------------- 1 | # package drpcsignal 2 | 3 | `import "storj.io/drpc/drpcsignal"` 4 | 5 | Package drpcsignal holds a helper type to signal errors. 6 | 7 | ## Usage 8 | 9 | #### type Chan 10 | 11 | ```go 12 | type Chan struct { 13 | } 14 | ``` 15 | 16 | Chan is a lazily allocated chan struct{} that avoids allocating if it is closed 17 | before being used for anything. 18 | 19 | #### func (*Chan) Close 20 | 21 | ```go 22 | func (c *Chan) Close() 23 | ``` 24 | Close tries to set the channel to an already closed one if a fresh one has not 25 | already been set, and closes the fresh one otherwise. 26 | 27 | #### func (*Chan) Full 28 | 29 | ```go 30 | func (c *Chan) Full() bool 31 | ``` 32 | Full returns true if the channel is currently full. The information is 33 | immediately invalid in the sense that a send could always block. 34 | 35 | #### func (*Chan) Get 36 | 37 | ```go 38 | func (c *Chan) Get() chan struct{} 39 | ``` 40 | Get returns the channel, allocating if necessary. 41 | 42 | #### func (*Chan) Make 43 | 44 | ```go 45 | func (c *Chan) Make(cap uint) 46 | ``` 47 | Make sets the channel to a freshly allocated channel with the provided capacity. 48 | It is a no-op if called after any other methods. 49 | 50 | #### func (*Chan) Recv 51 | 52 | ```go 53 | func (c *Chan) Recv() 54 | ``` 55 | Recv receives a value on the channel, allocating if necessary. 56 | 57 | #### func (*Chan) Send 58 | 59 | ```go 60 | func (c *Chan) Send() 61 | ``` 62 | Send sends a value on the channel, allocating if necessary. 63 | 64 | #### type Signal 65 | 66 | ```go 67 | type Signal struct { 68 | } 69 | ``` 70 | 71 | Signal contains an error value that can be set one and exports a number of ways 72 | to inspect it. 73 | 74 | #### func (*Signal) Err 75 | 76 | ```go 77 | func (s *Signal) Err() error 78 | ``` 79 | Err returns the error stored in the signal. Since one can store a nil error care 80 | must be taken. A non-nil error returned from this method means that the Signal 81 | has been set, but the inverse is not true. 82 | 83 | #### func (*Signal) Get 84 | 85 | ```go 86 | func (s *Signal) Get() (error, bool) 87 | ``` 88 | Get returns the error set with the signal and a boolean indicating if the result 89 | is valid. 90 | 91 | #### func (*Signal) IsSet 92 | 93 | ```go 94 | func (s *Signal) IsSet() bool 95 | ``` 96 | IsSet returns true if the Signal is set. 97 | 98 | #### func (*Signal) Set 99 | 100 | ```go 101 | func (s *Signal) Set(err error) (ok bool) 102 | ``` 103 | Set stores the error in the signal. It only keeps track of the first error set, 104 | and returns true if it was the first error set. 105 | 106 | #### func (*Signal) Signal 107 | 108 | ```go 109 | func (s *Signal) Signal() chan struct{} 110 | ``` 111 | Signal returns a channel that will be closed when the signal is set. 112 | 113 | #### func (*Signal) Wait 114 | 115 | ```go 116 | func (s *Signal) Wait() 117 | ``` 118 | Wait blocks until the signal has been Set. 119 | -------------------------------------------------------------------------------- /internal/integration/http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package integration 5 | 6 | import ( 7 | "encoding/json" 8 | "io" 9 | "net/http" 10 | "net/http/httptest" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/zeebo/assert" 15 | 16 | "storj.io/drpc/drpchttp" 17 | "storj.io/drpc/drpcmux" 18 | "storj.io/drpc/drpctest" 19 | ) 20 | 21 | func TestHTTP(t *testing.T) { 22 | ctx := drpctest.NewTracker(t) 23 | defer ctx.Close() 24 | 25 | mux := drpcmux.New() 26 | assert.NoError(t, DRPCRegisterService(mux, standardImpl)) 27 | 28 | server := httptest.NewServer(drpchttp.New(mux)) 29 | defer server.Close() 30 | 31 | type response struct { 32 | StatusCode int 33 | Code string 34 | Msg string 35 | Response *jsonOut 36 | } 37 | 38 | request := func(method, body string, metadata ...string) (r response) { 39 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, server.URL+method, strings.NewReader(body)) 40 | assert.NoError(t, err) 41 | req.Header.Set("Content-Type", "application/json") 42 | req.Header["X-Drpc-Metadata"] = metadata 43 | 44 | resp, err := http.DefaultClient.Do(req) 45 | assert.NoError(t, err) 46 | 47 | data, err := io.ReadAll(resp.Body) 48 | assert.NoError(t, resp.Body.Close()) 49 | assert.NoError(t, err) 50 | 51 | if resp.StatusCode == http.StatusOK { 52 | assert.NoError(t, json.Unmarshal(data, &r.Response)) 53 | } else { 54 | assert.NoError(t, json.Unmarshal(data, &r)) 55 | } 56 | r.StatusCode = resp.StatusCode 57 | 58 | return r 59 | } 60 | 61 | assertEqual := func(t *testing.T, a, b response) { 62 | t.Helper() 63 | assert.True(t, Equal((*Out)(a.Response), (*Out)(b.Response))) 64 | a.Response, b.Response = nil, nil 65 | assert.DeepEqual(t, a, b) 66 | } 67 | 68 | // basic successful request 69 | assertEqual(t, request("/service.Service/Method1", `{"in": 1}`), response{ 70 | StatusCode: http.StatusOK, 71 | Response: &jsonOut{Out: 1}, 72 | }) 73 | 74 | // basic erroring request 75 | assertEqual(t, request("/service.Service/Method1", `{"in": 5}`), response{ 76 | StatusCode: http.StatusInternalServerError, 77 | Code: "drpcerr(5)", 78 | Msg: "test", 79 | }) 80 | 81 | // metadata gets passed through 82 | assertEqual(t, request("/service.Service/Method1", `{"in": 1}`, "inc=10"), response{ 83 | StatusCode: http.StatusOK, 84 | Response: &jsonOut{Out: 11}, 85 | }) 86 | 87 | // non-existing method 88 | assertEqual(t, request("/service.Service/DoesNotExist", `{}`), response{ 89 | StatusCode: http.StatusInternalServerError, 90 | Code: "unknown", 91 | Msg: `protocol error: unknown rpc: "/service.Service/DoesNotExist"`, 92 | }) 93 | } 94 | 95 | // 96 | // super hacky hack to make it so that you can use encoding/json with the protobuf 97 | // 98 | 99 | type jsonOut Out 100 | 101 | func (o *jsonOut) UnmarshalJSON(v []byte) error { 102 | return Encoding.JSONUnmarshal(v, (*Out)(o)) 103 | } 104 | -------------------------------------------------------------------------------- /drpchttp/protocol_grpc_web.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpchttp 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "encoding/binary" 10 | "io" 11 | "net/http" 12 | "net/textproto" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/zeebo/errs" 17 | 18 | "storj.io/drpc" 19 | "storj.io/drpc/drpcerr" 20 | ) 21 | 22 | // 23 | // protocol handler 24 | // 25 | 26 | type grpcWebProtocol struct { 27 | ct string 28 | read readFunc 29 | write writeFunc 30 | marshal marshalFunc 31 | unmarshal unmarshalFunc 32 | } 33 | 34 | func (gwp grpcWebProtocol) NewStream(rw http.ResponseWriter, req *http.Request) Stream { 35 | rw.Header().Set("Content-Type", gwp.ct) 36 | return &grpcWebStream{ 37 | ctx: req.Context(), 38 | gwp: gwp, 39 | in: req.Body, 40 | rw: rw, 41 | } 42 | } 43 | 44 | func (gwp grpcWebProtocol) framedWrite(rw http.ResponseWriter, hdr byte, buf []byte) error { 45 | tmp := [5]byte{0: hdr} 46 | binary.BigEndian.PutUint32(tmp[1:5], uint32(len(buf))) 47 | return gwp.write(rw, append(tmp[:], buf...)) 48 | } 49 | 50 | // 51 | // stream type 52 | // 53 | 54 | type grpcWebStream struct { 55 | ctx context.Context 56 | gwp grpcWebProtocol 57 | in io.ReadCloser 58 | rw http.ResponseWriter 59 | } 60 | 61 | func (gws *grpcWebStream) Context() context.Context { return gws.ctx } 62 | func (gws *grpcWebStream) CloseSend() error { return nil } 63 | func (gws *grpcWebStream) Close() error { return gws.in.Close() } 64 | 65 | func (gws *grpcWebStream) MsgSend(msg drpc.Message, enc drpc.Encoding) (err error) { 66 | data, err := gws.gwp.marshal(msg, enc) 67 | if err != nil { 68 | return err 69 | } else if len(data) >= maxSize { 70 | return errs.New("message too large") 71 | } else if err := gws.gwp.framedWrite(gws.rw, 0, data); err != nil { 72 | return err 73 | } else if fl, ok := gws.rw.(http.Flusher); ok { 74 | fl.Flush() 75 | } 76 | return nil 77 | } 78 | 79 | func (gws *grpcWebStream) MsgRecv(msg drpc.Message, enc drpc.Encoding) (err error) { 80 | buf, err := gws.gwp.read(gws.in) 81 | if err != nil { 82 | return err 83 | } 84 | return gws.gwp.unmarshal(buf, msg, enc) 85 | } 86 | 87 | var nlSpace = strings.NewReplacer("\n", " ", "\r", " ") 88 | 89 | func (gws *grpcWebStream) Finish(err error) { 90 | // if there is an error and the code is "0" (Ok) either 91 | // because it is unset or explicitly set to 0, then set it 92 | // to "2" (Unknown) so that an error status is sent instead. 93 | status := strconv.FormatUint(drpcerr.Code(err), 10) 94 | if err != nil && status == "0" { 95 | status = "2" 96 | } 97 | 98 | var buf bytes.Buffer 99 | write := func(k, v string) { 100 | buf.WriteString(k) 101 | buf.WriteString(": ") 102 | buf.WriteString(textproto.TrimString(nlSpace.Replace(v))) 103 | buf.WriteString("\r\n") 104 | } 105 | 106 | write("grpc-status", status) 107 | if err != nil { 108 | write("grpc-code", getCode(err)) 109 | write("grpc-message", err.Error()) 110 | } 111 | 112 | _ = gws.gwp.framedWrite(gws.rw, 128, buf.Bytes()) 113 | } 114 | -------------------------------------------------------------------------------- /drpchttp/options.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpchttp 5 | 6 | import ( 7 | "net/http" 8 | 9 | "storj.io/drpc" 10 | ) 11 | 12 | // Option configures some aspect of the handler. 13 | type Option struct{ apply func(*options) } 14 | 15 | type options struct { 16 | protocols map[string]Protocol 17 | } 18 | 19 | // Protocol is used by the handler to create drpc.Streams from incoming 20 | // requests and format responses. 21 | type Protocol interface { 22 | // NewStream takes an incoming request and response writer and returns 23 | // a drpc.Stream that should be used for the RPC. 24 | NewStream(rw http.ResponseWriter, req *http.Request) Stream 25 | } 26 | 27 | // Stream wraps a drpc.Stream type with a Finish method that knows how to 28 | // send and format the error/response to an http request. 29 | type Stream interface { 30 | drpc.Stream 31 | 32 | // Finish is passed the possibly-nil error that was generated handling 33 | // the RPC and is expected to write any error reporting or otherwise 34 | // finalize the request. 35 | Finish(err error) 36 | } 37 | 38 | // WithProtocol associates the given Protocol with some content type. The 39 | // match is exact, with the special case that the content type "*" is the 40 | // fallback Protocol used when nothing matches. 41 | func WithProtocol(contentType string, pr Protocol) Option { 42 | return Option{apply: func(opts *options) { 43 | opts.protocols[contentType] = pr 44 | }} 45 | } 46 | 47 | func defaultProtocols() map[string]Protocol { 48 | return map[string]Protocol{ 49 | "*": twirpProtocol{ 50 | ct: "application/proto", 51 | marshal: protoMarshal, 52 | unmarshal: protoUnmarshal, 53 | }, 54 | 55 | "application/proto": twirpProtocol{ 56 | ct: "application/proto", 57 | marshal: protoMarshal, 58 | unmarshal: protoUnmarshal, 59 | }, 60 | 61 | "application/json": twirpProtocol{ 62 | ct: "application/json", 63 | marshal: JSONMarshal, 64 | unmarshal: JSONUnmarshal, 65 | }, 66 | 67 | "application/grpc-web+proto": grpcWebProtocol{ 68 | ct: "application/grpc-web+proto", 69 | read: grpcRead, 70 | write: normalWrite, 71 | marshal: protoMarshal, 72 | unmarshal: protoUnmarshal, 73 | }, 74 | 75 | "application/grpc-web+json": grpcWebProtocol{ 76 | ct: "application/grpc-web+json", 77 | read: grpcRead, 78 | write: normalWrite, 79 | marshal: JSONMarshal, 80 | unmarshal: JSONUnmarshal, 81 | }, 82 | 83 | "application/grpc-web-text+proto": grpcWebProtocol{ 84 | ct: "application/grpc-web-text+proto", 85 | read: base64Read(grpcRead), 86 | write: base64Write(normalWrite), 87 | marshal: protoMarshal, 88 | unmarshal: protoUnmarshal, 89 | }, 90 | 91 | "application/grpc-web-text+json": grpcWebProtocol{ 92 | ct: "application/grpc-web-text+json", 93 | read: base64Read(grpcRead), 94 | write: base64Write(normalWrite), 95 | marshal: JSONMarshal, 96 | unmarshal: JSONUnmarshal, 97 | }, 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /drpcsignal/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpcsignal 5 | 6 | import ( 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | type signalStatus = uint32 12 | 13 | const ( 14 | statusErrorSet = 0b10 15 | statusChannelCreated = 0b01 16 | ) 17 | 18 | // Signal contains an error value that can be set one and exports 19 | // a number of ways to inspect it. 20 | type Signal struct { 21 | status signalStatus 22 | mu sync.Mutex 23 | ch chan struct{} 24 | err error 25 | } 26 | 27 | // Wait blocks until the signal has been Set. 28 | func (s *Signal) Wait() { 29 | <-s.Signal() 30 | } 31 | 32 | // Signal returns a channel that will be closed when the signal is set. 33 | func (s *Signal) Signal() chan struct{} { 34 | if atomic.LoadUint32(&s.status)&statusChannelCreated != 0 { 35 | return s.ch 36 | } 37 | return s.signalSlow() 38 | } 39 | 40 | // signalSlow is the slow path for Signal, so that the fast path is inlined into 41 | // callers. 42 | func (s *Signal) signalSlow() chan struct{} { 43 | s.mu.Lock() 44 | if set := s.status; set&statusChannelCreated == 0 { 45 | s.ch = make(chan struct{}) 46 | atomic.StoreUint32(&s.status, set|statusChannelCreated) 47 | } 48 | s.mu.Unlock() 49 | return s.ch 50 | } 51 | 52 | // Set stores the error in the signal. It only keeps track of the first 53 | // error set, and returns true if it was the first error set. 54 | func (s *Signal) Set(err error) (ok bool) { 55 | if atomic.LoadUint32(&s.status)&statusErrorSet != 0 { 56 | return false 57 | } 58 | return s.setSlow(err) 59 | } 60 | 61 | // setSlow is the slow path for Set, so that the fast path is inlined into 62 | // callers. 63 | func (s *Signal) setSlow(err error) (ok bool) { 64 | s.mu.Lock() 65 | if status := s.status; status&statusErrorSet == 0 { 66 | ok = true 67 | 68 | s.err = err 69 | if status&statusChannelCreated == 0 { 70 | s.ch = closed 71 | } 72 | 73 | // we have to store the flags after we set the channel but before we 74 | // close it, otherwise there are races where a caller can hit the 75 | // atomic fast path and observe invalid values. 76 | atomic.StoreUint32(&s.status, statusErrorSet|statusChannelCreated) 77 | 78 | if status&statusChannelCreated != 0 { 79 | close(s.ch) 80 | } 81 | } 82 | s.mu.Unlock() 83 | return ok 84 | } 85 | 86 | // Get returns the error set with the signal and a boolean indicating if 87 | // the result is valid. 88 | func (s *Signal) Get() (error, bool) { 89 | if atomic.LoadUint32(&s.status)&statusErrorSet != 0 { 90 | return s.err, true 91 | } 92 | return nil, false 93 | } 94 | 95 | // IsSet returns true if the Signal is set. 96 | func (s *Signal) IsSet() bool { 97 | return atomic.LoadUint32(&s.status)&statusErrorSet != 0 98 | } 99 | 100 | // Err returns the error stored in the signal. Since one can store a nil error 101 | // care must be taken. A non-nil error returned from this method means that 102 | // the Signal has been set, but the inverse is not true. 103 | func (s *Signal) Err() error { 104 | if atomic.LoadUint32(&s.status)&statusErrorSet != 0 { 105 | return s.err 106 | } 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /drpchttp/protocol_twirp.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpchttp 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "io" 10 | "net/http" 11 | 12 | "storj.io/drpc" 13 | ) 14 | 15 | // 16 | // protocol handler 17 | // 18 | 19 | type twirpProtocol struct { 20 | ct string 21 | marshal marshalFunc 22 | unmarshal unmarshalFunc 23 | } 24 | 25 | func (tp twirpProtocol) NewStream(rw http.ResponseWriter, req *http.Request) Stream { 26 | rw.Header().Set("Content-Type", tp.ct) 27 | return &twirpStream{ 28 | ctx: req.Context(), 29 | tp: tp, 30 | body: req.Body, 31 | rw: rw, 32 | } 33 | } 34 | 35 | // 36 | // stream type 37 | // 38 | 39 | type twirpStream struct { 40 | ctx context.Context 41 | tp twirpProtocol 42 | body io.ReadCloser 43 | rw http.ResponseWriter 44 | 45 | response []byte 46 | recvErr error 47 | sendErr error 48 | } 49 | 50 | func setErrorOrEOF(errp *error, err error) { 51 | if err == nil { 52 | err = io.EOF 53 | } 54 | *errp = err 55 | } 56 | 57 | func (ts *twirpStream) Context() context.Context { return ts.ctx } 58 | func (ts *twirpStream) CloseSend() error { return nil } 59 | func (ts *twirpStream) Close() error { return nil } 60 | 61 | func (ts *twirpStream) MsgSend(msg drpc.Message, enc drpc.Encoding) (err error) { 62 | if ts.sendErr != nil { 63 | return ts.sendErr 64 | } 65 | ts.response, err = ts.tp.marshal(msg, enc) 66 | setErrorOrEOF(&ts.sendErr, err) 67 | return err 68 | } 69 | 70 | func (ts *twirpStream) MsgRecv(msg drpc.Message, enc drpc.Encoding) (err error) { 71 | if ts.recvErr != nil { 72 | return ts.recvErr 73 | } 74 | buf, err := twirpRead(ts.body) 75 | setErrorOrEOF(&ts.recvErr, err) 76 | if err != nil { 77 | return err 78 | } 79 | return ts.tp.unmarshal(buf, msg, enc) 80 | } 81 | 82 | func (ts *twirpStream) Finish(err error) { 83 | if err == nil { 84 | ts.rw.WriteHeader(http.StatusOK) 85 | _, _ = ts.rw.Write(ts.response) 86 | return 87 | } 88 | 89 | code := getCode(err) 90 | status := twirpStatus[code] 91 | if status == 0 { 92 | status = 500 93 | } 94 | 95 | data, err := json.MarshalIndent(map[string]interface{}{ 96 | "code": code, 97 | "msg": err.Error(), 98 | }, "", " ") 99 | if err != nil { 100 | http.Error(ts.rw, "", http.StatusInternalServerError) 101 | return 102 | } 103 | 104 | ts.rw.Header().Set("Content-Type", "application/json") 105 | ts.rw.WriteHeader(status) 106 | _, _ = ts.rw.Write(data) 107 | } 108 | 109 | var twirpStatus = map[string]int{ 110 | "canceled": 408, 111 | "unknown": 500, 112 | "invalid_argument": 400, 113 | "malformed": 400, 114 | "deadline_exceeded": 408, 115 | "not_found": 404, 116 | "bad_route": 404, 117 | "already_exists": 409, 118 | "permission_denied": 403, 119 | "unauthenticated": 401, 120 | "resource_exhausted": 429, 121 | "failed_precondition": 412, 122 | "aborted": 409, 123 | "out_of_range": 400, 124 | "unimplemented": 501, 125 | "internal": 500, 126 | "unavailable": 503, 127 | "dataloss": 500, 128 | } 129 | -------------------------------------------------------------------------------- /drpcpool/README.md: -------------------------------------------------------------------------------- 1 | # package drpcpool 2 | 3 | `import "storj.io/drpc/drpcpool"` 4 | 5 | Package drpcpool is a simple connection pool for clients. 6 | 7 | It has the ability to maintain a cache of connections with a maximum size on 8 | both the total and per key basis. It also can expire cached connections if they 9 | have been inactive in the pool for long enough. 10 | 11 | ## Usage 12 | 13 | #### type Conn 14 | 15 | ```go 16 | type Conn interface { 17 | drpc.Conn 18 | 19 | // Unblocked returns a channel that is closed when the conn is available for an Invoke or 20 | // NewStream call. 21 | Unblocked() <-chan struct{} 22 | } 23 | ``` 24 | 25 | Conn is the type of connections that can be managed by the pool. Connections 26 | need to provide an Unblocked function that can be used by the pool to skip 27 | connections that are still blocked on canceling the last RPC. 28 | 29 | #### type Options 30 | 31 | ```go 32 | type Options struct { 33 | // Expiration will remove any values from the Pool after the 34 | // value passes. Zero means no expiration. 35 | Expiration time.Duration 36 | 37 | // Capacity is the maximum number of values the Pool can store. 38 | // Zero means unlimited. Negative means no values. 39 | Capacity int 40 | 41 | // KeyCapacity is like Capacity except it is per key. Zero means 42 | // the Pool holds unlimited for any single key. Negative means 43 | // no values for any single key. 44 | KeyCapacity int 45 | } 46 | ``` 47 | 48 | Options contains the options to configure a pool. 49 | 50 | #### type Pool 51 | 52 | ```go 53 | type Pool[K comparable, V Conn] struct { 54 | } 55 | ``` 56 | 57 | Pool is a connection pool with key type K. It maintains a cache of connections 58 | per key and ensures the total number of connections in the cache is bounded by 59 | configurable values. It does not limit the maximum concurrency of the number of 60 | connections either in total or per key. 61 | 62 | #### func New 63 | 64 | ```go 65 | func New[K comparable, V Conn](opts Options) *Pool[K, V] 66 | ``` 67 | New constructs a new Pool with the provided Options. 68 | 69 | #### func (*Pool[K, V]) Close 70 | 71 | ```go 72 | func (p *Pool[K, V]) Close() (err error) 73 | ``` 74 | Close evicts all entries from the Pool's cache, closing them and returning all 75 | of the combined errors from closing. 76 | 77 | #### func (*Pool[K, V]) Get 78 | 79 | ```go 80 | func (p *Pool[K, V]) Get(ctx context.Context, key K, 81 | dial func(ctx context.Context, key K) (V, error)) Conn 82 | ``` 83 | Get returns a new Conn that will use the provided dial function to create an 84 | underlying conn to be cached by the Pool when Conn methods are invoked. It will 85 | share any cached connections with other conns that use the same key. 86 | 87 | #### func (*Pool[K, V]) Put 88 | 89 | ```go 90 | func (p *Pool[K, V]) Put(key K, val V) 91 | ``` 92 | Put places the connection in to the cache with the provided key, ensuring that 93 | the size limits the Pool is configured with are respected. 94 | 95 | #### func (*Pool[K, V]) Take 96 | 97 | ```go 98 | func (p *Pool[K, V]) Take(key K) (V, bool) 99 | ``` 100 | Take acquires a value from the cache if one exists. It returns the zero value 101 | for V and false if one does not. 102 | -------------------------------------------------------------------------------- /examples/opentelemetry/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "io" 10 | "net" 11 | "os" 12 | "time" 13 | 14 | "go.opentelemetry.io/otel" 15 | "go.opentelemetry.io/otel/attribute" 16 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 17 | "go.opentelemetry.io/otel/propagation" 18 | "go.opentelemetry.io/otel/sdk/resource" 19 | "go.opentelemetry.io/otel/sdk/trace" 20 | semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 21 | "storj.io/drpc/drpcconn" 22 | 23 | "storj.io/drpc/examples/opentelemetry/pb" 24 | ) 25 | 26 | func main() { 27 | err := Main(context.Background()) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | func Main(ctx context.Context) error { 34 | // start outputting spans to standard output 35 | defer startTelemetryToStdout()() 36 | 37 | // dial the drpc server 38 | rawconn, err := net.Dial("tcp", "localhost:8080") 39 | if err != nil { 40 | return err 41 | } 42 | // N.B.: If you want TLS, you need to wrap the net.Conn with TLS before 43 | // making a DRPC conn. 44 | 45 | // convert the net.Conn to a drpc.Conn 46 | conn := drpcconn.New(rawconn) 47 | defer conn.Close() 48 | 49 | // wrap the drpc.Conn with otel 50 | oconn := &otelConn{Conn: conn} 51 | 52 | // make a drpc proto-specific client 53 | client := pb.NewDRPCCookieMonsterClient(oconn) 54 | 55 | // set a deadline for the operation 56 | ctx, cancel := context.WithTimeout(ctx, time.Second) 57 | defer cancel() 58 | 59 | // run the RPC 60 | crumbs, err := client.EatCookie(ctx, &pb.Cookie{ 61 | Type: pb.Cookie_Oatmeal, 62 | }) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | // check the results 68 | _, err = fmt.Println(crumbs.Cookie.Type.String()) 69 | return err 70 | } 71 | 72 | // 73 | // otel things 74 | // 75 | 76 | var tracer = otel.Tracer("storj.io/drpc/examples/opentelemetry/server") 77 | 78 | // newExporter returns a console exporter. 79 | func newExporter(w io.Writer) (trace.SpanExporter, error) { 80 | return stdouttrace.New( 81 | stdouttrace.WithWriter(w), 82 | // Use human-readable output. 83 | stdouttrace.WithPrettyPrint(), 84 | // Do not print timestamps for the demo. 85 | stdouttrace.WithoutTimestamps(), 86 | ) 87 | } 88 | 89 | // newResource returns a resource describing this application. 90 | func newResource() *resource.Resource { 91 | r, _ := resource.Merge( 92 | resource.Default(), 93 | resource.NewWithAttributes( 94 | semconv.SchemaURL, 95 | semconv.ServiceNameKey.String("opentelemetry-client"), 96 | semconv.ServiceVersionKey.String("v0.1.0"), 97 | attribute.String("environment", "demo"), 98 | ), 99 | ) 100 | return r 101 | } 102 | 103 | func startTelemetryToStdout() func() { 104 | otel.SetTextMapPropagator(propagation.TraceContext{}) 105 | 106 | exp, err := newExporter(os.Stdout) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | tp := trace.NewTracerProvider( 112 | trace.WithBatcher(exp), 113 | trace.WithResource(newResource()), 114 | ) 115 | 116 | otel.SetTracerProvider(tp) 117 | 118 | return func() { 119 | if err := tp.Shutdown(context.Background()); err != nil { 120 | panic(err) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /drpchttp/encoding.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 Storj Labs, Inc. 2 | // See LICENSE for copying information. 3 | 4 | package drpchttp 5 | 6 | import ( 7 | "encoding/base64" 8 | "encoding/binary" 9 | "encoding/json" 10 | "errors" 11 | "io" 12 | 13 | "github.com/zeebo/errs" 14 | 15 | "storj.io/drpc" 16 | ) 17 | 18 | const maxSize = 4 << 20 19 | 20 | type ( 21 | marshalFunc = func(msg drpc.Message, enc drpc.Encoding) ([]byte, error) 22 | unmarshalFunc = func(buf []byte, msg drpc.Message, enc drpc.Encoding) error 23 | writeFunc = func(w io.Writer, buf []byte) error 24 | readFunc = func(r io.Reader) ([]byte, error) 25 | ) 26 | 27 | // JSONMarshal looks for a JSONMarshal method on the encoding and calls that if it 28 | // exists. Otherwise, it does a normal message marshal before doing a JSON marshal. 29 | func JSONMarshal(msg drpc.Message, enc drpc.Encoding) ([]byte, error) { 30 | if enc, ok := enc.(interface { 31 | JSONMarshal(msg drpc.Message) ([]byte, error) 32 | }); ok { 33 | return enc.JSONMarshal(msg) 34 | } 35 | 36 | // fallback to normal Marshal + JSON Marshal 37 | buf, err := enc.Marshal(msg) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return json.Marshal(buf) 42 | } 43 | 44 | // JSONUnmarshal looks for a JSONUnmarshal method on the encoding and calls that 45 | // if it exists. Otherwise, it JSON unmarshals the buf before doing a normal 46 | // message unmarshal. 47 | func JSONUnmarshal(buf []byte, msg drpc.Message, enc drpc.Encoding) error { 48 | if enc, ok := enc.(interface { 49 | JSONUnmarshal(buf []byte, msg drpc.Message) error 50 | }); ok { 51 | return enc.JSONUnmarshal(buf, msg) 52 | } 53 | 54 | // fallback to JSON Unmarshal + normal Unmarshal 55 | var data []byte 56 | if err := json.Unmarshal(buf, &data); err != nil { 57 | return err 58 | } 59 | return enc.Unmarshal(data, msg) 60 | } 61 | 62 | func protoMarshal(msg drpc.Message, enc drpc.Encoding) ([]byte, error) { 63 | return enc.Marshal(msg) 64 | } 65 | 66 | func protoUnmarshal(buf []byte, msg drpc.Message, enc drpc.Encoding) error { 67 | return enc.Unmarshal(buf, msg) 68 | } 69 | 70 | func normalWrite(w io.Writer, buf []byte) error { 71 | _, err := w.Write(buf) 72 | return err 73 | } 74 | 75 | func base64Write(wf writeFunc) writeFunc { 76 | return func(w io.Writer, buf []byte) error { 77 | tmp := make([]byte, base64.StdEncoding.EncodedLen(len(buf))) 78 | base64.StdEncoding.Encode(tmp, buf) 79 | return wf(w, tmp) 80 | } 81 | } 82 | 83 | func readExactly(r io.Reader, n uint64) ([]byte, error) { 84 | buf := make([]byte, n) 85 | _, err := io.ReadFull(r, buf) 86 | return buf, err 87 | } 88 | 89 | func grpcRead(r io.Reader) ([]byte, error) { 90 | if tmp, err := readExactly(r, 5); err != nil { 91 | return nil, err 92 | } else if size := binary.BigEndian.Uint32(tmp[1:5]); size > maxSize { 93 | return nil, errs.New("message too large") 94 | } else if data, err := readExactly(r, uint64(size)); errors.Is(err, io.EOF) { 95 | return nil, io.ErrUnexpectedEOF 96 | } else if err != nil { 97 | return nil, err 98 | } else { 99 | return data, nil 100 | } 101 | } 102 | 103 | func twirpRead(r io.Reader) ([]byte, error) { 104 | if data, err := io.ReadAll(io.LimitReader(r, maxSize)); err != nil { 105 | return nil, err 106 | } else if len(data) > maxSize { 107 | return nil, errs.New("message too large") 108 | } else { 109 | return data, nil 110 | } 111 | } 112 | 113 | func base64Read(rf readFunc) readFunc { 114 | return func(r io.Reader) ([]byte, error) { 115 | return rf(base64.NewDecoder(base64.StdEncoding, r)) 116 | } 117 | } 118 | --------------------------------------------------------------------------------