TUN-3019: Remove declarative tunnel entry code

This commit is contained in:
cthuang 2020-05-29 17:21:03 +08:00
parent be0514c5c9
commit fb82b2ced5
36 changed files with 643 additions and 3955 deletions

View File

@ -21,7 +21,6 @@ import (
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/dbconnect"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/hello"
@ -32,10 +31,8 @@ import (
"github.com/cloudflare/cloudflared/socks"
"github.com/cloudflare/cloudflared/sshlog"
"github.com/cloudflare/cloudflared/sshserver"
"github.com/cloudflare/cloudflared/supervisor"
"github.com/cloudflare/cloudflared/tlsconfig"
"github.com/cloudflare/cloudflared/tunneldns"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/websocket"
"github.com/coreos/go-systemd/daemon"
@ -93,8 +90,6 @@ const (
// bastionFlag is to enable bastion, or jump host, operation
bastionFlag = "bastion"
noIntentMsg = "The --intent argument is required. Cloudflared looks up an Intent to determine what configuration to use (i.e. which tunnels to start). If you don't have any Intents yet, you can use a placeholder Intent Label for now. Then, when you make an Intent with that label, cloudflared will get notified and open the tunnels you specified in that Intent."
debugLevelWarning = "At debug level, request URL, method, protocol, content legnth and header will be logged. " +
"Response status, content length and header will also be logged in debug level."
)
@ -322,10 +317,6 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
cancel()
}()
if c.IsSet("use-declarative-tunnels") {
return startDeclarativeTunnel(ctx, c, cloudflaredID, buildInfo, &listeners, logger)
}
// update needs to be after DNS proxy is up to resolve equinox server address
if updater.IsAutoupdateEnabled(c, logger) {
logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
@ -500,124 +491,6 @@ func isProxyDestinationConfigured(staticHost string, c *cli.Context) bool {
return staticHost != "" || c.IsSet(bastionFlag)
}
func startDeclarativeTunnel(ctx context.Context,
c *cli.Context,
cloudflaredID uuid.UUID,
buildInfo *buildinfo.BuildInfo,
listeners *gracenet.Net,
logger logger.Service,
) error {
reverseProxyOrigin, err := defaultOriginConfig(c)
if err != nil {
logger.Errorf("%s", err)
return err
}
reverseProxyConfig, err := pogs.NewReverseProxyConfig(
c.String("hostname"),
reverseProxyOrigin,
c.Uint64("retries"),
c.Duration("proxy-connection-timeout"),
c.Uint64("compression-quality"),
)
if err != nil {
logger.Errorf("Cannot initialize default client config because reverse proxy config is invalid: %s", err)
return err
}
defaultClientConfig := &pogs.ClientConfig{
Version: pogs.InitVersion(),
SupervisorConfig: &pogs.SupervisorConfig{
AutoUpdateFrequency: c.Duration("autoupdate-freq"),
MetricsUpdateFrequency: c.Duration("metrics-update-freq"),
GracePeriod: c.Duration("grace-period"),
},
EdgeConnectionConfig: &pogs.EdgeConnectionConfig{
NumHAConnections: uint8(c.Int("ha-connections")),
HeartbeatInterval: c.Duration("heartbeat-interval"),
Timeout: c.Duration("dial-edge-timeout"),
MaxFailedHeartbeats: c.Uint64("heartbeat-count"),
},
DoHProxyConfigs: []*pogs.DoHProxyConfig{},
ReverseProxyConfigs: []*pogs.ReverseProxyConfig{reverseProxyConfig},
}
autoupdater := updater.NewAutoUpdater(defaultClientConfig.SupervisorConfig.AutoUpdateFrequency, listeners, logger)
originCert, err := getOriginCert(c, logger)
if err != nil {
logger.Errorf("error getting origin cert: %s", err)
return err
}
toEdgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c)
if err != nil {
logger.Errorf("unable to create TLS config to connect with edge: %s", err)
return err
}
tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
if err != nil {
logger.Errorf("unable to parse tag: %s", err)
return err
}
intentLabel := c.String("intent")
if intentLabel == "" {
logger.Error("--intent was empty")
return fmt.Errorf(noIntentMsg)
}
cloudflaredConfig := &connection.CloudflaredConfig{
BuildInfo: buildInfo,
CloudflaredID: cloudflaredID,
IntentLabel: intentLabel,
Tags: tags,
}
serviceDiscoverer, err := serviceDiscoverer(c, logger)
if err != nil {
logger.Errorf("unable to create service discoverer: %s", err)
return err
}
supervisor, err := supervisor.NewSupervisor(defaultClientConfig, originCert, toEdgeTLSConfig,
serviceDiscoverer, cloudflaredConfig, autoupdater, updater.SupportAutoUpdate(logger), logger)
if err != nil {
logger.Errorf("unable to create Supervisor: %s", err)
return err
}
return supervisor.Run(ctx)
}
func defaultOriginConfig(c *cli.Context) (pogs.OriginConfig, error) {
if c.IsSet("hello-world") {
return &pogs.HelloWorldOriginConfig{}, nil
}
originConfig := &pogs.HTTPOriginConfig{
TCPKeepAlive: c.Duration("proxy-tcp-keepalive"),
DialDualStack: !c.Bool("proxy-no-happy-eyeballs"),
TLSHandshakeTimeout: c.Duration("proxy-tls-timeout"),
TLSVerify: !c.Bool("no-tls-verify"),
OriginCAPool: c.String("origin-ca-pool"),
OriginServerName: c.String("origin-server-name"),
MaxIdleConnections: c.Uint64("proxy-keepalive-connections"),
IdleConnectionTimeout: c.Duration("proxy-keepalive-timeout"),
ProxyConnectionTimeout: c.Duration("proxy-connection-timeout"),
ExpectContinueTimeout: c.Duration("proxy-expect-continue-timeout"),
ChunkedEncoding: c.Bool("no-chunked-encoding"),
}
if c.IsSet("unix-socket") {
unixSocket, err := config.ValidateUnixSocket(c)
if err != nil {
return nil, errors.Wrap(err, "error validating --unix-socket")
}
originConfig.URLString = unixSocket
}
originAddr, err := config.ValidateUrl(c)
if err != nil {
return nil, errors.Wrap(err, "error validating origin URL")
}
originConfig.URLString = originAddr
return originConfig, nil
}
func waitToShutdown(wg *sync.WaitGroup,
errC chan error,
shutdownC, graceShutdownC chan struct{},
@ -1064,18 +937,6 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
EnvVars: []string{"TUNNEL_TRACE_OUTPUT"},
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "use-declarative-tunnels",
Usage: "Test establishing connections with declarative tunnel methods.",
EnvVars: []string{"TUNNEL_USE_DECLARATIVE"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "intent",
Usage: "The label of an Intent from which `cloudflared` should gets its tunnels from. Intents can be created in the Origin Registry UI.",
EnvVars: []string{"TUNNEL_INTENT"},
Hidden: true,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "use-reconnect-token",
Usage: "Test reestablishing connections with the new 'reconnect token' flow.",

View File

@ -14,7 +14,6 @@ import (
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tlsconfig"
@ -290,15 +289,6 @@ func prepareTunnelConfig(
}, nil
}
func serviceDiscoverer(c *cli.Context, logger logger.Service) (*edgediscovery.Edge, error) {
// If --edge is specfied, resolve edge server addresses
if len(c.StringSlice("edge")) > 0 {
return edgediscovery.StaticEdge(logger, c.StringSlice("edge"))
}
// Otherwise lookup edge server addresses through service discovery
return edgediscovery.ResolveEdge(logger)
}
func isRunningFromTerminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}

View File

@ -1,57 +0,0 @@
package connection
import (
"context"
"net"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
const (
openStreamTimeout = 30 * time.Second
)
type Connection struct {
id uuid.UUID
muxer *h2mux.Muxer
addr *net.TCPAddr
isLongLived bool
longLivedID int
}
func newConnection(muxer *h2mux.Muxer, addr *net.TCPAddr) (*Connection, error) {
id, err := uuid.NewRandom()
if err != nil {
return nil, err
}
return &Connection{
id: id,
muxer: muxer,
addr: addr,
}, nil
}
func (c *Connection) Serve(ctx context.Context) error {
// Serve doesn't return until h2mux is shutdown
return c.muxer.Serve(ctx)
}
// Connect is used to establish connections with cloudflare's edge network
func (c *Connection) Connect(ctx context.Context, parameters *tunnelpogs.ConnectParameters, logger logger.Service) (tunnelpogs.ConnectResult, error) {
tsClient, err := NewRPCClient(ctx, c.muxer, logger, openStreamTimeout)
if err != nil {
return nil, errors.Wrap(err, "cannot create new RPC connection")
}
defer tsClient.Close()
return tsClient.Connect(ctx, parameters)
}
func (c *Connection) Shutdown() {
c.muxer.Shutdown()
}

View File

@ -1,302 +0,0 @@
package connection
import (
"context"
"crypto/tls"
"fmt"
"sync"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/streamhandler"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
const (
quickStartLink = "https://developers.cloudflare.com/argo-tunnel/quickstart/"
faqLink = "https://developers.cloudflare.com/argo-tunnel/faq/"
defaultRetryAfter = time.Second * 5
packageNamespace = "connection"
edgeManagerSubsystem = "edgemanager"
)
// EdgeManager manages connections with the edge
type EdgeManager struct {
// streamHandler handles stream opened by the edge
streamHandler *streamhandler.StreamHandler
// TLSConfig is the TLS configuration to connect with edge
tlsConfig *tls.Config
// cloudflaredConfig is the cloudflared configuration that is determined when the process first starts
cloudflaredConfig *CloudflaredConfig
// serviceDiscoverer returns the next edge addr to connect to
serviceDiscoverer *edgediscovery.Edge
// state is attributes of ConnectionManager that can change during runtime.
state *edgeManagerState
logger logger.Service
metrics *metrics
}
type metrics struct {
// activeStreams is a gauge shared by all muxers of this process to expose the total number of active streams
activeStreams prometheus.Gauge
}
func newMetrics(namespace, subsystem string) *metrics {
return &metrics{
activeStreams: h2mux.NewActiveStreamsMetrics(namespace, subsystem),
}
}
// EdgeManagerConfigurable is the configurable attributes of a EdgeConnectionManager
type EdgeManagerConfigurable struct {
TunnelHostnames []h2mux.TunnelHostname
*tunnelpogs.EdgeConnectionConfig
}
type CloudflaredConfig struct {
CloudflaredID uuid.UUID
Tags []tunnelpogs.Tag
BuildInfo *buildinfo.BuildInfo
IntentLabel string
}
func NewEdgeManager(
streamHandler *streamhandler.StreamHandler,
edgeConnMgrConfigurable *EdgeManagerConfigurable,
userCredential []byte,
tlsConfig *tls.Config,
serviceDiscoverer *edgediscovery.Edge,
cloudflaredConfig *CloudflaredConfig,
logger logger.Service,
) *EdgeManager {
return &EdgeManager{
streamHandler: streamHandler,
tlsConfig: tlsConfig,
cloudflaredConfig: cloudflaredConfig,
serviceDiscoverer: serviceDiscoverer,
state: newEdgeConnectionManagerState(edgeConnMgrConfigurable, userCredential),
logger: logger,
metrics: newMetrics(packageNamespace, edgeManagerSubsystem),
}
}
func (em *EdgeManager) Run(ctx context.Context) error {
defer em.shutdown()
// Currently, declarative tunnels don't have any concept of a stable connection
// Each edge connection is transient and when it dies, it is replaced by a different one,
// not restarted.
// So in the future we should really change this so that n connections are stored individually
connIndex := 0
for {
select {
case <-ctx.Done():
return errors.Wrap(ctx.Err(), "EdgeConnectionManager terminated")
default:
time.Sleep(1 * time.Second)
}
// Create/delete connection one at a time, so we don't need to adjust for connections that are being created/deleted
// in shouldCreateConnection or shouldReduceConnection calculation
if em.state.shouldCreateConnection(em.serviceDiscoverer.AvailableAddrs()) {
if connErr := em.newConnection(ctx, connIndex); connErr != nil {
if !connErr.ShouldRetry {
em.logger.Errorf("connectionManager: %s with error: %s", em.noRetryMessage(), connErr)
return connErr
}
em.logger.Errorf("connectionManager: cannot create new connection: %s", connErr)
} else {
connIndex++
}
} else if em.state.shouldReduceConnection() {
if err := em.closeConnection(ctx); err != nil {
em.logger.Errorf("connectionManager: cannot close connection: %s", err)
}
}
}
}
func (em *EdgeManager) UpdateConfigurable(newConfigurable *EdgeManagerConfigurable) {
em.logger.Infof("New edge connection manager configuration %+v", newConfigurable)
em.state.updateConfigurable(newConfigurable)
}
func (em *EdgeManager) newConnection(ctx context.Context, index int) *tunnelpogs.ConnectError {
edgeTCPAddr, err := em.serviceDiscoverer.GetAddr(index)
if err != nil {
return retryConnection(fmt.Sprintf("edge address discovery error: %v", err))
}
configurable := em.state.getConfigurable()
edgeConn, err := DialEdge(ctx, configurable.Timeout, em.tlsConfig, edgeTCPAddr)
if err != nil {
return retryConnection(fmt.Sprintf("dial edge error: %v", err))
}
// Establish a muxed connection with the edge
// Client mux handshake with agent server
muxer, err := h2mux.Handshake(edgeConn, edgeConn, h2mux.MuxerConfig{
Timeout: configurable.Timeout,
Handler: em.streamHandler,
IsClient: true,
HeartbeatInterval: configurable.HeartbeatInterval,
MaxHeartbeats: configurable.MaxFailedHeartbeats,
Logger: em.logger,
}, em.metrics.activeStreams)
if err != nil {
retryConnection(fmt.Sprintf("couldn't perform handshake with edge: %v", err))
}
h2muxConn, err := newConnection(muxer, edgeTCPAddr)
if err != nil {
return retryConnection(fmt.Sprintf("couldn't create h2mux connection: %v", err))
}
go em.serveConn(ctx, h2muxConn)
connResult, err := h2muxConn.Connect(ctx, &tunnelpogs.ConnectParameters{
CloudflaredID: em.cloudflaredConfig.CloudflaredID,
CloudflaredVersion: em.cloudflaredConfig.BuildInfo.CloudflaredVersion,
NumPreviousAttempts: 0,
OriginCert: em.state.getUserCredential(),
IntentLabel: em.cloudflaredConfig.IntentLabel,
Tags: em.cloudflaredConfig.Tags,
}, em.logger)
if err != nil {
h2muxConn.Shutdown()
return retryConnection(fmt.Sprintf("couldn't connect to edge: %v", err))
}
if connErr := connResult.ConnectError(); connErr != nil {
return connErr
}
em.state.newConnection(h2muxConn)
em.logger.Infof("connectionManager: connected to %s", connResult.ConnectedTo())
if connResult.ClientConfig() != nil {
em.streamHandler.UseConfiguration(ctx, connResult.ClientConfig())
}
return nil
}
func (em *EdgeManager) closeConnection(ctx context.Context) error {
conn := em.state.getFirstConnection()
if conn == nil {
return fmt.Errorf("no connection to close")
}
conn.Shutdown()
// teardown will be handled by EdgeManager.serveConn in another goroutine
return nil
}
func (em *EdgeManager) serveConn(ctx context.Context, conn *Connection) {
err := conn.Serve(ctx)
em.logger.Errorf("connectionManager: Connection closed: %s", err)
em.state.closeConnection(conn)
em.serviceDiscoverer.GiveBack(conn.addr)
}
func (em *EdgeManager) noRetryMessage() string {
messageTemplate := "cloudflared could not register an Argo Tunnel on your account. Please confirm the following before trying again:" +
"1. You have Argo Smart Routing enabled in your account, See Enable Argo section of %s." +
"2. Your credential at %s is still valid. See %s."
return fmt.Sprintf(messageTemplate, quickStartLink, em.state.getConfigurable().UserCredentialPath, faqLink)
}
func (em *EdgeManager) shutdown() {
em.state.shutdown()
}
type edgeManagerState struct {
sync.RWMutex
configurable *EdgeManagerConfigurable
userCredential []byte
conns map[uuid.UUID]*Connection
}
func newEdgeConnectionManagerState(configurable *EdgeManagerConfigurable, userCredential []byte) *edgeManagerState {
return &edgeManagerState{
configurable: configurable,
userCredential: userCredential,
conns: make(map[uuid.UUID]*Connection),
}
}
func (ems *edgeManagerState) shouldCreateConnection(availableEdgeAddrs int) bool {
ems.RLock()
defer ems.RUnlock()
expectedHAConns := int(ems.configurable.NumHAConnections)
if availableEdgeAddrs < expectedHAConns {
expectedHAConns = availableEdgeAddrs
}
return len(ems.conns) < expectedHAConns
}
func (ems *edgeManagerState) shouldReduceConnection() bool {
ems.RLock()
defer ems.RUnlock()
return uint8(len(ems.conns)) > ems.configurable.NumHAConnections
}
func (ems *edgeManagerState) newConnection(conn *Connection) {
ems.Lock()
defer ems.Unlock()
ems.conns[conn.id] = conn
}
func (ems *edgeManagerState) closeConnection(conn *Connection) {
ems.Lock()
defer ems.Unlock()
delete(ems.conns, conn.id)
}
func (ems *edgeManagerState) getFirstConnection() *Connection {
ems.RLock()
defer ems.RUnlock()
for _, conn := range ems.conns {
return conn
}
return nil
}
func (ems *edgeManagerState) shutdown() {
ems.Lock()
defer ems.Unlock()
for _, conn := range ems.conns {
conn.Shutdown()
}
}
func (ems *edgeManagerState) getConfigurable() *EdgeManagerConfigurable {
ems.Lock()
defer ems.Unlock()
return ems.configurable
}
func (ems *edgeManagerState) updateConfigurable(newConfigurable *EdgeManagerConfigurable) {
ems.Lock()
defer ems.Unlock()
ems.configurable = newConfigurable
}
func (ems *edgeManagerState) getUserCredential() []byte {
ems.RLock()
defer ems.RUnlock()
return ems.userCredential
}
func retryConnection(cause string) *tunnelpogs.ConnectError {
return &tunnelpogs.ConnectError{
Cause: cause,
RetryAfter: defaultRetryAfter,
ShouldRetry: true,
}
}

View File

@ -1,77 +0,0 @@
package connection
import (
"net"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/streamhandler"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
var (
configurable = &EdgeManagerConfigurable{
[]h2mux.TunnelHostname{
"http.example.com",
"ws.example.com",
"hello.example.com",
},
&pogs.EdgeConnectionConfig{
NumHAConnections: 1,
HeartbeatInterval: 1 * time.Second,
Timeout: 5 * time.Second,
MaxFailedHeartbeats: 3,
UserCredentialPath: "/etc/cloudflared/cert.pem",
},
}
cloudflaredConfig = &CloudflaredConfig{
CloudflaredID: uuid.New(),
Tags: []pogs.Tag{
{Name: "pool", Value: "east-6"},
},
BuildInfo: &buildinfo.BuildInfo{
GoOS: "linux",
GoVersion: "1.12",
GoArch: "amd64",
CloudflaredVersion: "2019.6.0",
},
}
)
func mockEdgeManager() *EdgeManager {
newConfigChan := make(chan<- *pogs.ClientConfig)
useConfigResultChan := make(<-chan *pogs.UseConfigurationResult)
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
edge := edgediscovery.MockEdge(logger, []*net.TCPAddr{})
return NewEdgeManager(
streamhandler.NewStreamHandler(newConfigChan, useConfigResultChan, logger),
configurable,
[]byte{},
nil,
edge,
cloudflaredConfig,
logger,
)
}
func TestUpdateConfigurable(t *testing.T) {
m := mockEdgeManager()
newConfigurable := &EdgeManagerConfigurable{
[]h2mux.TunnelHostname{
"second.example.com",
},
&pogs.EdgeConnectionConfig{
NumHAConnections: 2,
},
}
m.UpdateConfigurable(newConfigurable)
assert.Equal(t, newConfigurable, m.state.getConfigurable())
}

2
go.mod
View File

@ -70,7 +70,7 @@ require (
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8
gopkg.in/yaml.v2 v2.2.4
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7
zombiezen.com/go/capnproto2 v2.18.0+incompatible
)
// ../../go/pkg/mod/github.com/coredns/coredns@v1.2.0/plugin/metrics/metrics.go:40:49: too many arguments in call to prometheus.NewProcessCollector

4
go.sum
View File

@ -9,9 +9,9 @@ github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFD
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1 h1:RKnVV4C7qoN/sToLX2y1dqH7T6kKLMHcwRJlgwb9Ggk=
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1/go.mod h1:gI5CyA/CEnS6eqNV22rqs4dG3aGfaSbXgPORIlwr2r0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -285,3 +285,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7 h1:CZoOFlTPbKfAShKYrMuUfYbnXexFT1rYRUX1SPnrdE4=
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7/go.mod h1:TMGa8HWGJkXiq4nHe9Zu/JgRF5oUtg4XizFC+Vexbec=
zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess=
zombiezen.com/go/capnproto2 v2.18.0+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=

View File

@ -25,7 +25,6 @@ import (
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/signal"
"github.com/cloudflare/cloudflared/streamhandler"
"github.com/cloudflare/cloudflared/tunnelrpc"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/validation"
@ -618,8 +617,8 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
return reqErr
}
cfRay := streamhandler.FindCfRayHeader(req)
lbProbe := streamhandler.IsLBProbeRequest(req)
cfRay := findCfRayHeader(req)
lbProbe := isLBProbeRequest(req)
h.logRequest(req, cfRay, lbProbe)
var resp *http.Response
@ -833,3 +832,11 @@ func activeIncidentsMsg(incidents []Incident) string {
return preamble + " " + strings.Join(incidentStrings, "; ")
}
func findCfRayHeader(h1 *http.Request) string {
return h1.Header.Get("Cf-Ray")
}
func isLBProbeRequest(req *http.Request) bool {
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
}

View File

@ -1,247 +0,0 @@
// Package client defines and implements interface to proxy to HTTP, websocket and hello world origins
package originservice
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/cloudflare/cloudflared/buffer"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/websocket"
"github.com/pkg/errors"
)
// OriginService is an interface to proxy requests to different type of origins
type OriginService interface {
Proxy(stream *h2mux.MuxedStream, req *http.Request) (resp *http.Response, err error)
URL() *url.URL
Summary() string
Shutdown()
}
// HTTPService talks to origin using HTTP/HTTPS
type HTTPService struct {
client http.RoundTripper
originURL *url.URL
chunkedEncoding bool
bufferPool *buffer.Pool
}
func NewHTTPService(transport http.RoundTripper, url *url.URL, chunkedEncoding bool) OriginService {
return &HTTPService{
client: transport,
originURL: url,
chunkedEncoding: chunkedEncoding,
bufferPool: buffer.NewPool(512 * 1024),
}
}
func (hc *HTTPService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
const responseSourceOrigin = "origin"
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
if !hc.chunkedEncoding {
req.TransferEncoding = []string{"gzip", "deflate"}
cLength, err := strconv.Atoi(req.Header.Get("Content-Length"))
if err == nil {
req.ContentLength = int64(cLength)
}
}
// Request origin to keep connection alive to improve performance
req.Header.Set("Connection", "keep-alive")
resp, err := hc.client.RoundTrip(req)
if err != nil {
return nil, errors.Wrap(err, "error proxying request to HTTP origin")
}
defer resp.Body.Close()
responseHeaders := h1ResponseToH2Response(resp)
responseHeaders = append(responseHeaders, h2mux.CreateResponseMetaHeader(h2mux.ResponseMetaHeaderField, responseSourceOrigin))
err = stream.WriteHeaders(responseHeaders)
if err != nil {
return nil, errors.Wrap(err, "error writing response header to HTTP origin")
}
if isEventStream(resp) {
writeEventStream(stream, resp.Body)
} else {
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
// compression generates dictionary on first write
buf := hc.bufferPool.Get()
defer hc.bufferPool.Put(buf)
io.CopyBuffer(stream, resp.Body, buf)
}
return resp, nil
}
func (hc *HTTPService) URL() *url.URL {
return hc.originURL
}
func (hc *HTTPService) Summary() string {
return fmt.Sprintf("HTTP service listening on %s", hc.originURL)
}
func (hc *HTTPService) Shutdown() {}
// WebsocketService talks to origin using WS/WSS
type WebsocketService struct {
tlsConfig *tls.Config
originURL *url.URL
shutdownC chan struct{}
}
func NewWebSocketService(tlsConfig *tls.Config, url *url.URL, logger logger.Service) (OriginService, error) {
listener, err := net.Listen("tcp", "127.0.0.1:")
if err != nil {
return nil, errors.Wrap(err, "cannot start Websocket Proxy Server")
}
shutdownC := make(chan struct{})
go func() {
websocket.StartProxyServer(logger, listener, url.String(), shutdownC, websocket.DefaultStreamHandler)
}()
return &WebsocketService{
tlsConfig: tlsConfig,
originURL: url,
shutdownC: shutdownC,
}, nil
}
func (wsc *WebsocketService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
if !websocket.IsWebSocketUpgrade(req) {
return nil, fmt.Errorf("request is not a websocket connection")
}
conn, response, err := websocket.ClientConnect(req, wsc.tlsConfig)
if err != nil {
return nil, err
}
defer conn.Close()
err = stream.WriteHeaders(h1ResponseToH2Response(response))
if err != nil {
return nil, errors.Wrap(err, "error writing response header to websocket origin")
}
// Copy to/from stream to the undelying connection. Use the underlying
// connection because cloudflared doesn't operate on the message themselves
websocket.Stream(conn.UnderlyingConn(), stream)
return response, nil
}
func (wsc *WebsocketService) URL() *url.URL {
return wsc.originURL
}
func (wsc *WebsocketService) Summary() string {
return fmt.Sprintf("Websocket listening on %s", wsc.originURL)
}
func (wsc *WebsocketService) Shutdown() {
close(wsc.shutdownC)
}
// HelloWorldService talks to the hello world example origin
type HelloWorldService struct {
client http.RoundTripper
listener net.Listener
originURL *url.URL
shutdownC chan struct{}
bufferPool *buffer.Pool
}
func NewHelloWorldService(transport http.RoundTripper, logger logger.Service) (OriginService, error) {
listener, err := hello.CreateTLSListener("127.0.0.1:")
if err != nil {
return nil, errors.Wrap(err, "cannot start Hello World Server")
}
shutdownC := make(chan struct{})
go func() {
hello.StartHelloWorldServer(logger, listener, shutdownC)
}()
return &HelloWorldService{
client: transport,
listener: listener,
originURL: &url.URL{
Scheme: "https",
Host: listener.Addr().String(),
},
shutdownC: shutdownC,
bufferPool: buffer.NewPool(512 * 1024),
}, nil
}
func (hwc *HelloWorldService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
// Request origin to keep connection alive to improve performance
req.Header.Set("Connection", "keep-alive")
resp, err := hwc.client.RoundTrip(req)
if err != nil {
return nil, errors.Wrap(err, "error proxying request to Hello World origin")
}
defer resp.Body.Close()
err = stream.WriteHeaders(h1ResponseToH2Response(resp))
if err != nil {
return nil, errors.Wrap(err, "error writing response header to Hello World origin")
}
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
// compression generates dictionary on first write
buf := hwc.bufferPool.Get()
defer hwc.bufferPool.Put(buf)
io.CopyBuffer(stream, resp.Body, buf)
return resp, nil
}
func (hwc *HelloWorldService) URL() *url.URL {
return hwc.originURL
}
func (hwc *HelloWorldService) Summary() string {
return fmt.Sprintf("Hello World service listening on %s", hwc.originURL)
}
func (hwc *HelloWorldService) Shutdown() {
hwc.listener.Close()
}
func isEventStream(resp *http.Response) bool {
// Check if content-type is text/event-stream. We need to check if the header value starts with text/event-stream
// because text/event-stream; charset=UTF-8 is also valid
// Ref: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
for _, contentType := range resp.Header["content-type"] {
if strings.HasPrefix(strings.ToLower(contentType), "text/event-stream") {
return true
}
}
return false
}
func writeEventStream(stream *h2mux.MuxedStream, respBody io.ReadCloser) {
reader := bufio.NewReader(respBody)
for {
line, err := reader.ReadBytes('\n')
if err != nil {
break
}
stream.Write(line)
}
}
func h1ResponseToH2Response(h1 *http.Response) (h2 []h2mux.Header) {
h2 = []h2mux.Header{{Name: ":status", Value: fmt.Sprintf("%d", h1.StatusCode)}}
for headerName, headerValues := range h1.Header {
for _, headerValue := range headerValues {
h2 = append(h2, h2mux.Header{Name: strings.ToLower(headerName), Value: headerValue})
}
}
return
}

View File

@ -1,60 +0,0 @@
package originservice
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsEventStream(t *testing.T) {
tests := []struct {
resp *http.Response
isEventStream bool
}{
{
resp: &http.Response{},
isEventStream: false,
},
{
// isEventStream checks all headers
resp: &http.Response{
Header: http.Header{
"accept": []string{"text/html"},
"content-type": []string{"text/event-stream"},
},
},
isEventStream: true,
},
{
// Content-Type and text/event-stream are case-insensitive. text/event-stream can be followed by OWS parameter
resp: &http.Response{
Header: http.Header{
"content-type": []string{"Text/event-stream;charset=utf-8"},
},
},
isEventStream: true,
},
{
// Content-Type and text/event-stream are case-insensitive. text/event-stream can be followed by OWS parameter
resp: &http.Response{
Header: http.Header{
"content-type": []string{"appication/json", "text/html", "Text/event-stream;charset=utf-8"},
},
},
isEventStream: true,
},
{
// Not an event stream because the content-type value doesn't start with text/event-stream
resp: &http.Response{
Header: http.Header{
"content-type": []string{" text/event-stream"},
},
},
isEventStream: false,
},
}
for _, test := range tests {
assert.Equal(t, test.isEventStream, isEventStream(test.resp), "Header: %v", test.resp.Header)
}
}

View File

@ -1,34 +0,0 @@
package streamhandler
import (
"net/http"
"net/url"
"strings"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/pkg/errors"
)
const (
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
)
func FindCfRayHeader(h1 *http.Request) string {
return h1.Header.Get("Cf-Ray")
}
func IsLBProbeRequest(req *http.Request) bool {
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
}
func createRequest(stream *h2mux.MuxedStream, url *url.URL) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, url.String(), h2mux.MuxedStreamReader{MuxedStream: stream})
if err != nil {
return nil, errors.Wrap(err, "unexpected error from http.NewRequest")
}
err = h2mux.H2RequestHeadersToH1Request(stream.Headers, req)
if err != nil {
return nil, errors.Wrap(err, "invalid request received")
}
return req, nil
}

View File

@ -1,189 +0,0 @@
package streamhandler
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/tunnelhostnamemapper"
"github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/pkg/errors"
"zombiezen.com/go/capnproto2/rpc"
)
const (
statusPseudoHeader = ":status"
)
type httpErrorStatus struct {
status string
text []byte
}
var (
statusBadRequest = newHTTPErrorStatus(http.StatusBadRequest)
statusNotFound = newHTTPErrorStatus(http.StatusNotFound)
statusBadGateway = newHTTPErrorStatus(http.StatusBadGateway)
)
func newHTTPErrorStatus(status int) *httpErrorStatus {
return &httpErrorStatus{
status: strconv.Itoa(status),
text: []byte(http.StatusText(status)),
}
}
// StreamHandler handles new stream opened by the edge. The streams can be used to proxy requests or make RPC.
type StreamHandler struct {
// newConfigChan is a send-only channel to notify Supervisor of a new ClientConfig
newConfigChan chan<- *pogs.ClientConfig
// useConfigResultChan is a receive-only channel for Supervisor to communicate the result of applying a new ClientConfig
useConfigResultChan <-chan *pogs.UseConfigurationResult
// originMapper maps tunnel hostname to origin service
tunnelHostnameMapper *tunnelhostnamemapper.TunnelHostnameMapper
logger logger.Service
}
// NewStreamHandler creates a new StreamHandler
func NewStreamHandler(newConfigChan chan<- *pogs.ClientConfig,
useConfigResultChan <-chan *pogs.UseConfigurationResult,
logger logger.Service,
) *StreamHandler {
return &StreamHandler{
newConfigChan: newConfigChan,
useConfigResultChan: useConfigResultChan,
tunnelHostnameMapper: tunnelhostnamemapper.NewTunnelHostnameMapper(),
logger: logger,
}
}
// UseConfiguration implements ClientService
func (s *StreamHandler) UseConfiguration(ctx context.Context, config *pogs.ClientConfig) (*pogs.UseConfigurationResult, error) {
select {
case <-ctx.Done():
err := fmt.Errorf("Timeout while sending new config to Supervisor")
s.logger.Errorf("streamHandler: %s", err)
return nil, err
case s.newConfigChan <- config:
}
select {
case <-ctx.Done():
err := fmt.Errorf("Timeout applying new configuration")
s.logger.Errorf("streamHandler: %s", err)
return nil, err
case result := <-s.useConfigResultChan:
return result, nil
}
}
// UpdateConfig replaces current originmapper mapping with mappings from newConfig
func (s *StreamHandler) UpdateConfig(newConfig []*pogs.ReverseProxyConfig) (failedConfigs []*pogs.FailedConfig) {
// Delete old configs that aren't in the `newConfig`
toRemove := s.tunnelHostnameMapper.ToRemove(newConfig)
for _, hostnameToRemove := range toRemove {
s.tunnelHostnameMapper.Delete(hostnameToRemove)
}
// Add new configs that weren't in the old mapper
toAdd := s.tunnelHostnameMapper.ToAdd(newConfig)
for _, tunnelConfig := range toAdd {
tunnelHostname := tunnelConfig.TunnelHostname
originSerice, err := tunnelConfig.OriginConfig.Service(s.logger)
if err != nil {
s.logger.Errorf("streamHandler: tunnelHostname: %s Invalid origin service config: %s", tunnelHostname, err)
failedConfigs = append(failedConfigs, &pogs.FailedConfig{
Config: tunnelConfig,
Reason: tunnelConfig.FailReason(err),
})
continue
}
s.tunnelHostnameMapper.Add(tunnelConfig.TunnelHostname, originSerice)
s.logger.Infof("streamHandler: tunnelHostname: %s New origin service config: %v", tunnelHostname, originSerice.Summary())
}
return
}
// ServeStream implements MuxedStreamHandler interface
func (s *StreamHandler) ServeStream(stream *h2mux.MuxedStream) error {
if stream.IsRPCStream() {
return s.serveRPC(stream)
}
if err := s.serveRequest(stream); err != nil {
s.logger.Errorf("streamHandler: %s", err)
return err
}
return nil
}
func (s *StreamHandler) serveRPC(stream *h2mux.MuxedStream) error {
stream.WriteHeaders([]h2mux.Header{{Name: ":status", Value: "200"}})
main := pogs.ClientService_ServerToClient(s)
rpcConn := rpc.NewConn(
tunnelrpc.NewTransportLogger(s.logger, rpc.StreamTransport(stream)),
rpc.MainInterface(main.Client),
tunnelrpc.ConnLog(s.logger),
)
return rpcConn.Wait()
}
func (s *StreamHandler) serveRequest(stream *h2mux.MuxedStream) error {
tunnelHostname := stream.TunnelHostname()
if !tunnelHostname.IsSet() {
s.writeErrorStatus(stream, statusBadRequest)
return fmt.Errorf("stream doesn't have tunnelHostname")
}
originService, ok := s.tunnelHostnameMapper.Get(tunnelHostname)
if !ok {
s.writeErrorStatus(stream, statusNotFound)
return fmt.Errorf("cannot map tunnel hostname %s to origin", tunnelHostname)
}
req, err := createRequest(stream, originService.URL())
if err != nil {
s.writeErrorStatus(stream, statusBadRequest)
return errors.Wrap(err, "cannot create request")
}
cfRay := s.logRequest(req, tunnelHostname)
s.logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s Request Headers %+v", tunnelHostname, cfRay, req.Header)
resp, err := originService.Proxy(stream, req)
if err != nil {
s.writeErrorStatus(stream, statusBadGateway)
return errors.Wrap(err, "cannot proxy request")
}
s.logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s status: %s Response Headers %+v", tunnelHostname, cfRay, resp.Status, resp.Header)
return nil
}
func (s *StreamHandler) logRequest(req *http.Request, tunnelHostname h2mux.TunnelHostname) string {
cfRay := FindCfRayHeader(req)
lbProbe := IsLBProbeRequest(req)
logger := s.logger
if cfRay != "" {
logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s %s %s %s", tunnelHostname, cfRay, req.Method, req.URL, req.Proto)
} else if lbProbe {
logger.Debugf("streamHandler: tunnelHostname: %s CF-RAY: %s Load Balancer health check %s %s %s", tunnelHostname, cfRay, req.Method, req.URL, req.Proto)
} else {
logger.Infof("streamHandler: tunnelHostname: %s CF-RAY: %s Requests %v does not have CF-RAY header. Please open a support ticket with Cloudflare.", tunnelHostname, cfRay, req)
}
return cfRay
}
func (s *StreamHandler) writeErrorStatus(stream *h2mux.MuxedStream, status *httpErrorStatus) {
_ = stream.WriteHeaders([]h2mux.Header{
{
Name: statusPseudoHeader,
Value: status.status,
},
h2mux.CreateResponseMetaHeader(h2mux.ResponseMetaHeaderField, h2mux.ResponseSourceCloudflared),
})
_, _ = stream.Write(status.text)
}

View File

@ -1,261 +0,0 @@
package streamhandler
import (
"context"
"io"
"net"
"net/http"
"net/http/httptest"
"strconv"
"sync"
"testing"
"time"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/sync/errgroup"
)
const (
testOpenStreamTimeout = time.Millisecond * 5000
testHandshakeTimeout = time.Millisecond * 1000
)
var (
testTunnelHostname = h2mux.TunnelHostname("123.cftunnel.com")
baseHeaders = []h2mux.Header{
{Name: ":method", Value: "GET"},
{Name: ":scheme", Value: "http"},
{Name: ":authority", Value: "example.com"},
{Name: ":path", Value: "/"},
// Regular headers must always come after the pseudoheaders
{Name: h2mux.RequestUserHeadersField, Value: ""},
}
tunnelHostnameHeader = h2mux.Header{Name: h2mux.CloudflaredProxyTunnelHostnameHeader, Value: testTunnelHostname.String()}
)
func TestServeRequest(t *testing.T) {
l := logger.NewOutputWriter(logger.NewMockWriteManager())
configChan := make(chan *pogs.ClientConfig)
useConfigResultChan := make(chan *pogs.UseConfigurationResult)
streamHandler := NewStreamHandler(configChan, useConfigResultChan, l)
message := []byte("Hello cloudflared")
httpServer := httptest.NewServer(&mockHTTPHandler{message})
reverseProxyConfigs := []*pogs.ReverseProxyConfig{
{
TunnelHostname: testTunnelHostname,
OriginConfig: &pogs.HTTPOriginConfig{
URLString: httpServer.URL,
},
},
}
streamHandler.UpdateConfig(reverseProxyConfigs)
muxPair := NewDefaultMuxerPair(t, streamHandler)
muxPair.Serve(t)
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
defer cancel()
headers := append(baseHeaders, tunnelHostnameHeader)
stream, err := muxPair.EdgeMux.OpenStream(ctx, headers, nil)
assert.NoError(t, err)
assertStatusHeader(t, http.StatusOK, stream.Headers)
assertRespBody(t, message, stream)
}
func createStreamHandler() *StreamHandler {
configChan := make(chan *pogs.ClientConfig)
useConfigResultChan := make(chan *pogs.UseConfigurationResult)
l := logger.NewOutputWriter(logger.NewMockWriteManager())
return NewStreamHandler(configChan, useConfigResultChan, l)
}
func createRequestMuxPair(t *testing.T, streamHandler *StreamHandler) *DefaultMuxerPair {
muxPair := NewDefaultMuxerPair(t, streamHandler)
muxPair.Serve(t)
return muxPair
}
func TestServeStatusBadRequest(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
defer cancel()
// No tunnel hostname header, expect to get 400 Bad Request
stream, err := createRequestMuxPair(t, createStreamHandler()).EdgeMux.OpenStream(ctx, baseHeaders, nil)
assert.NoError(t, err)
assertStatusHeader(t, http.StatusBadRequest, stream.Headers)
assertRespBody(t, statusBadRequest.text, stream)
}
func TestServeInvalidContentLength(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
defer cancel()
// Invalid content-length, wouldn't be able to create a request
// Expect to get 400 Bad Request
headers := append(baseHeaders, tunnelHostnameHeader)
headers = append(headers, h2mux.Header{
Name: "content-length",
Value: "x",
})
streamHandler := createStreamHandler()
streamHandler.UpdateConfig([]*pogs.ReverseProxyConfig{
{
TunnelHostname: testTunnelHostname,
OriginConfig: &pogs.HTTPOriginConfig{
URLString: "",
},
},
})
mux := createRequestMuxPair(t, streamHandler).EdgeMux
stream, err := mux.OpenStream(ctx, headers, nil)
assert.NoError(t, err)
assertStatusHeader(t, http.StatusBadRequest, stream.Headers)
assertRespBody(t, statusBadRequest.text, stream)
}
func TestServeStatusNotFound(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
defer cancel()
// No mapping for the tunnel hostname, expect to get 404 Not Found
headers := append(baseHeaders, tunnelHostnameHeader)
stream, err := createRequestMuxPair(t, createStreamHandler()).EdgeMux.OpenStream(ctx, headers, nil)
assert.NoError(t, err)
assertStatusHeader(t, http.StatusNotFound, stream.Headers)
assertRespBody(t, statusNotFound.text, stream)
}
func TestServeStatusBadGateway(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testOpenStreamTimeout)
defer cancel()
// Nothing listening on empty url, so proxy would fail. Expect to get 502 Bad Gateway
reverseProxyConfigs := []*pogs.ReverseProxyConfig{
{
TunnelHostname: testTunnelHostname,
OriginConfig: &pogs.HTTPOriginConfig{
URLString: "",
},
},
}
streamHandler := createStreamHandler()
streamHandler.UpdateConfig(reverseProxyConfigs)
headers := append(baseHeaders, tunnelHostnameHeader)
stream, err := createRequestMuxPair(t, streamHandler).EdgeMux.OpenStream(ctx, headers, nil)
assert.NoError(t, err)
assertStatusHeader(t, http.StatusBadGateway, stream.Headers)
assertRespBody(t, statusBadGateway.text, stream)
}
func assertStatusHeader(t *testing.T, expectedStatus int, headers []h2mux.Header) {
assert.Equal(t, statusPseudoHeader, headers[0].Name)
assert.Equal(t, strconv.Itoa(expectedStatus), headers[0].Value)
}
func assertRespBody(t *testing.T, expectedRespBody []byte, stream *h2mux.MuxedStream) {
respBody := make([]byte, len(expectedRespBody))
_, err := stream.Read(respBody)
assert.NoError(t, err)
assert.Equal(t, expectedRespBody, respBody)
}
type DefaultMuxerPair struct {
OriginMuxConfig h2mux.MuxerConfig
OriginMux *h2mux.Muxer
OriginConn net.Conn
EdgeMuxConfig h2mux.MuxerConfig
EdgeMux *h2mux.Muxer
EdgeConn net.Conn
doneC chan struct{}
}
func NewDefaultMuxerPair(t *testing.T, h h2mux.MuxedStreamHandler) *DefaultMuxerPair {
origin, edge := net.Pipe()
p := &DefaultMuxerPair{
OriginMuxConfig: h2mux.MuxerConfig{
Timeout: testHandshakeTimeout,
Handler: h,
IsClient: true,
Name: "origin",
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
DefaultWindowSize: (1 << 8) - 1,
MaxWindowSize: (1 << 15) - 1,
StreamWriteBufferMaxLen: 1024,
},
OriginConn: origin,
EdgeMuxConfig: h2mux.MuxerConfig{
Timeout: testHandshakeTimeout,
IsClient: false,
Name: "edge",
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
DefaultWindowSize: (1 << 8) - 1,
MaxWindowSize: (1 << 15) - 1,
StreamWriteBufferMaxLen: 1024,
},
EdgeConn: edge,
doneC: make(chan struct{}),
}
assert.NoError(t, p.Handshake(t.Name()))
return p
}
func (p *DefaultMuxerPair) Handshake(testName string) error {
ctx, cancel := context.WithTimeout(context.Background(), testHandshakeTimeout)
defer cancel()
errGroup, _ := errgroup.WithContext(ctx)
errGroup.Go(func() (err error) {
p.EdgeMux, err = h2mux.Handshake(p.EdgeConn, p.EdgeConn, p.EdgeMuxConfig, h2mux.NewActiveStreamsMetrics(testName, "edge"))
return errors.Wrap(err, "edge handshake failure")
})
errGroup.Go(func() (err error) {
p.OriginMux, err = h2mux.Handshake(p.OriginConn, p.OriginConn, p.OriginMuxConfig, h2mux.NewActiveStreamsMetrics(testName, "origin"))
return errors.Wrap(err, "origin handshake failure")
})
return errGroup.Wait()
}
func (p *DefaultMuxerPair) Serve(t assert.TestingT) {
ctx := context.Background()
var wg sync.WaitGroup
wg.Add(2)
go func() {
err := p.EdgeMux.Serve(ctx)
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
t.Errorf("error in edge muxer Serve(): %s", err)
}
p.OriginMux.Shutdown()
wg.Done()
}()
go func() {
err := p.OriginMux.Serve(ctx)
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
t.Errorf("error in origin muxer Serve(): %s", err)
}
p.EdgeMux.Shutdown()
wg.Done()
}()
go func() {
// notify when both muxes have stopped serving
wg.Wait()
close(p.doneC)
}()
}
type mockHTTPHandler struct {
message []byte
}
func (mth *mockHTTPHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write(mth.message)
}

View File

@ -1,205 +0,0 @@
// Package supervisor is used by declarative tunnels to get/apply new config from the edge.
package supervisor
import (
"context"
"crypto/tls"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/edgediscovery"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/streamhandler"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/prometheus/client_golang/prometheus"
)
type Supervisor struct {
connManager *connection.EdgeManager
streamHandler *streamhandler.StreamHandler
autoupdater *updater.AutoUpdater
supportAutoupdate bool
newConfigChan <-chan *pogs.ClientConfig
useConfigResultChan chan<- *pogs.UseConfigurationResult
state *state
logger logger.Service
metrics metrics
}
type metrics struct {
configVersion prometheus.Gauge
}
func newMetrics() metrics {
configVersion := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "supervisor",
Subsystem: "supervisor",
Name: "config_version",
Help: "Latest configuration version received from Cloudflare",
},
)
prometheus.MustRegister(
configVersion,
)
return metrics{
configVersion: configVersion,
}
}
func NewSupervisor(
defaultClientConfig *pogs.ClientConfig,
userCredential []byte,
tlsConfig *tls.Config,
serviceDiscoverer *edgediscovery.Edge,
cloudflaredConfig *connection.CloudflaredConfig,
autoupdater *updater.AutoUpdater,
supportAutoupdate bool,
logger logger.Service,
) (*Supervisor, error) {
newConfigChan := make(chan *pogs.ClientConfig)
useConfigResultChan := make(chan *pogs.UseConfigurationResult)
streamHandler := streamhandler.NewStreamHandler(newConfigChan, useConfigResultChan, logger)
invalidConfigs := streamHandler.UpdateConfig(defaultClientConfig.ReverseProxyConfigs)
if len(invalidConfigs) > 0 {
for _, invalidConfig := range invalidConfigs {
logger.Errorf("Tunnel %+v is invalid, reason: %s", invalidConfig.Config, invalidConfig.Reason)
}
return nil, fmt.Errorf("At least 1 Tunnel config is invalid")
}
tunnelHostnames := make([]h2mux.TunnelHostname, len(defaultClientConfig.ReverseProxyConfigs))
for i, reverseProxyConfig := range defaultClientConfig.ReverseProxyConfigs {
tunnelHostnames[i] = reverseProxyConfig.TunnelHostname
}
defaultEdgeMgrConfigurable := &connection.EdgeManagerConfigurable{
TunnelHostnames: tunnelHostnames,
EdgeConnectionConfig: defaultClientConfig.EdgeConnectionConfig,
}
return &Supervisor{
connManager: connection.NewEdgeManager(streamHandler, defaultEdgeMgrConfigurable, userCredential, tlsConfig,
serviceDiscoverer, cloudflaredConfig, logger),
streamHandler: streamHandler,
autoupdater: autoupdater,
supportAutoupdate: supportAutoupdate,
newConfigChan: newConfigChan,
useConfigResultChan: useConfigResultChan,
state: newState(defaultClientConfig),
logger: logger,
metrics: newMetrics(),
}, nil
}
func (s *Supervisor) Run(ctx context.Context) error {
errGroup, groupCtx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
return s.connManager.Run(groupCtx)
})
errGroup.Go(func() error {
return s.listenToNewConfig(groupCtx)
})
errGroup.Go(func() error {
return s.listenToShutdownSignal(groupCtx)
})
if s.supportAutoupdate {
errGroup.Go(func() error {
return s.autoupdater.Run(groupCtx)
})
}
err := errGroup.Wait()
s.logger.Errorf("Supervisor terminated, reason: %v", err)
return err
}
func (s *Supervisor) listenToShutdownSignal(serveCtx context.Context) error {
signals := make(chan os.Signal, 10)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
defer signal.Stop(signals)
select {
case <-serveCtx.Done():
return serveCtx.Err()
case sig := <-signals:
return fmt.Errorf("received %v signal", sig)
}
}
func (s *Supervisor) listenToNewConfig(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case newConfig := <-s.newConfigChan:
s.useConfigResultChan <- s.notifySubsystemsNewConfig(newConfig)
}
}
}
func (s *Supervisor) notifySubsystemsNewConfig(newConfig *pogs.ClientConfig) *pogs.UseConfigurationResult {
s.logger.Infof("Received configuration %v", newConfig.Version)
if s.state.hasAppliedVersion(newConfig.Version) {
s.logger.Infof("%v has been applied", newConfig.Version)
return &pogs.UseConfigurationResult{
Success: true,
}
}
s.metrics.configVersion.Set(float64(newConfig.Version))
s.state.updateConfig(newConfig)
var tunnelHostnames []h2mux.TunnelHostname
for _, tunnelConfig := range newConfig.ReverseProxyConfigs {
tunnelHostnames = append(tunnelHostnames, tunnelConfig.TunnelHostname)
}
// Update connManager configurable
s.connManager.UpdateConfigurable(&connection.EdgeManagerConfigurable{
TunnelHostnames: tunnelHostnames,
EdgeConnectionConfig: newConfig.EdgeConnectionConfig,
})
// Update streamHandler tunnelHostnameMapper mapping
failedConfigs := s.streamHandler.UpdateConfig(newConfig.ReverseProxyConfigs)
if s.supportAutoupdate {
s.autoupdater.Update(newConfig.SupervisorConfig.AutoUpdateFrequency)
}
return &pogs.UseConfigurationResult{
Success: len(failedConfigs) == 0,
FailedConfigs: failedConfigs,
}
}
type state struct {
sync.RWMutex
currentConfig *pogs.ClientConfig
}
func newState(currentConfig *pogs.ClientConfig) *state {
return &state{
currentConfig: currentConfig,
}
}
func (s *state) hasAppliedVersion(incomingVersion pogs.Version) bool {
s.RLock()
defer s.RUnlock()
return s.currentConfig.Version.IsNewerOrEqual(incomingVersion)
}
func (s *state) updateConfig(newConfig *pogs.ClientConfig) {
s.Lock()
defer s.Unlock()
s.currentConfig = newConfig
}

View File

@ -1,93 +0,0 @@
package tunnelhostnamemapper
import (
"sync"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/originservice"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
// TunnelHostnameMapper maps TunnelHostname to an OriginService
type TunnelHostnameMapper struct {
sync.RWMutex
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
}
func NewTunnelHostnameMapper() *TunnelHostnameMapper {
return &TunnelHostnameMapper{
tunnelHostnameToOrigin: make(map[h2mux.TunnelHostname]originservice.OriginService),
}
}
// Get an OriginService given a TunnelHostname
func (om *TunnelHostnameMapper) Get(key h2mux.TunnelHostname) (originservice.OriginService, bool) {
om.RLock()
defer om.RUnlock()
originService, ok := om.tunnelHostnameToOrigin[key]
return originService, ok
}
// Add a mapping. If there is already an OriginService with this key, shutdown the old origin service and replace it
// with the new one
func (om *TunnelHostnameMapper) Add(key h2mux.TunnelHostname, os originservice.OriginService) {
om.Lock()
defer om.Unlock()
if oldOS, ok := om.tunnelHostnameToOrigin[key]; ok {
oldOS.Shutdown()
}
om.tunnelHostnameToOrigin[key] = os
}
// Delete a mapping, and shutdown its OriginService
func (om *TunnelHostnameMapper) Delete(key h2mux.TunnelHostname) (keyFound bool) {
om.Lock()
defer om.Unlock()
if os, ok := om.tunnelHostnameToOrigin[key]; ok {
os.Shutdown()
delete(om.tunnelHostnameToOrigin, key)
return true
}
return false
}
// ToRemove finds all keys that should be removed from the TunnelHostnameMapper.
func (om *TunnelHostnameMapper) ToRemove(newConfigs []*pogs.ReverseProxyConfig) (toRemove []h2mux.TunnelHostname) {
om.Lock()
defer om.Unlock()
// Convert into a set, for O(1) lookups instead of O(n)
newConfigSet := toSet(newConfigs)
// If a config in `om` isn't in `newConfigs`, it must be removed.
for hostname := range om.tunnelHostnameToOrigin {
if _, ok := newConfigSet[hostname]; !ok {
toRemove = append(toRemove, hostname)
}
}
return
}
// ToAdd filters the given configs, keeping those that should be added to the TunnelHostnameMapper.
func (om *TunnelHostnameMapper) ToAdd(newConfigs []*pogs.ReverseProxyConfig) (toAdd []*pogs.ReverseProxyConfig) {
om.Lock()
defer om.Unlock()
// If a config in `newConfigs` isn't in `om`, it must be added.
for _, config := range newConfigs {
if _, ok := om.tunnelHostnameToOrigin[config.TunnelHostname]; !ok {
toAdd = append(toAdd, config)
}
}
return
}
func toSet(configs []*pogs.ReverseProxyConfig) map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig {
m := make(map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig)
for _, config := range configs {
m[config.TunnelHostname] = config
}
return m
}

View File

@ -1,212 +0,0 @@
package tunnelhostnamemapper
import (
"fmt"
"net/http"
"net/url"
"reflect"
"sync"
"testing"
"time"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/originservice"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/stretchr/testify/assert"
)
const (
routines = 1000
)
func TestTunnelHostnameMapperConcurrentAccess(t *testing.T) {
thm := NewTunnelHostnameMapper()
concurrentOps(t, func(i int) {
// om is empty
os, ok := thm.Get(tunnelHostname(i))
assert.False(t, ok)
assert.Nil(t, os)
})
firstURL, err := url.Parse("https://127.0.0.1:8080")
assert.NoError(t, err)
httpOS := originservice.NewHTTPService(http.DefaultTransport, firstURL, false)
concurrentOps(t, func(i int) {
thm.Add(tunnelHostname(i), httpOS)
})
concurrentOps(t, func(i int) {
os, ok := thm.Get(tunnelHostname(i))
assert.True(t, ok)
assert.Equal(t, httpOS, os)
})
secondURL, err := url.Parse("https://127.0.0.1:8080")
assert.NoError(t, err)
secondHTTPOS := originservice.NewHTTPService(http.DefaultTransport, secondURL, true)
concurrentOps(t, func(i int) {
// Add should httpOS with secondHTTPOS
thm.Add(tunnelHostname(i), secondHTTPOS)
})
concurrentOps(t, func(i int) {
os, ok := thm.Get(tunnelHostname(i))
assert.True(t, ok)
assert.Equal(t, secondHTTPOS, os)
})
}
func concurrentOps(t *testing.T, f func(i int)) {
var wg sync.WaitGroup
wg.Add(routines)
for i := 0; i < routines; i++ {
go func(i int) {
f(i)
wg.Done()
}(i)
}
wg.Wait()
}
func tunnelHostname(i int) h2mux.TunnelHostname {
return h2mux.TunnelHostname(fmt.Sprintf("%d.cftunnel.com", i))
}
func Test_toSet(t *testing.T) {
type args struct {
configs []*pogs.ReverseProxyConfig
}
tests := []struct {
name string
args args
want map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig
}{
{
name: "empty slice should yield empty map",
args: args{},
want: map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig{},
},
{
name: "multiple elements",
args: args{[]*pogs.ReverseProxyConfig{sampleConfig1(), sampleConfig2()}},
want: map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig{
"mock.example.com": sampleConfig1(),
"mock2.example.com": sampleConfig2(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := toSet(tt.args.configs); !reflect.DeepEqual(got, tt.want) {
t.Errorf("toSet() = %v, want %v", got, tt.want)
}
})
}
}
func TestTunnelHostnameMapper_ToAdd(t *testing.T) {
type fields struct {
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
}
type args struct {
newConfigs []*pogs.ReverseProxyConfig
}
tests := []struct {
name string
fields fields
args args
wantToAdd []*pogs.ReverseProxyConfig
}{
{
name: "Mapper={}, NewConfig={}, toAdd={}",
},
{
name: "Mapper={}, NewConfig={x}, toAdd={x}",
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig1()}},
wantToAdd: []*pogs.ReverseProxyConfig{sampleConfig1()},
},
{
name: "Mapper={x}, NewConfig={x,y}, toAdd={y}",
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig2()}},
wantToAdd: []*pogs.ReverseProxyConfig{sampleConfig2()},
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
om := &TunnelHostnameMapper{
tunnelHostnameToOrigin: tt.fields.tunnelHostnameToOrigin,
}
if gotToAdd := om.ToAdd(tt.args.newConfigs); !reflect.DeepEqual(gotToAdd, tt.wantToAdd) {
t.Errorf("TunnelHostnameMapper.ToAdd() = %v, want %v", gotToAdd, tt.wantToAdd)
}
})
}
}
func TestTunnelHostnameMapper_ToRemove(t *testing.T) {
type fields struct {
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
}
type args struct {
newConfigs []*pogs.ReverseProxyConfig
}
tests := []struct {
name string
fields fields
args args
wantToRemove []h2mux.TunnelHostname
}{
{
name: "Mapper={}, NewConfig={}, toRemove={}",
},
{
name: "Mapper={x}, NewConfig={}, toRemove={x}",
wantToRemove: []h2mux.TunnelHostname{sampleConfig1().TunnelHostname},
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
}},
},
{
name: "Mapper={x}, NewConfig={x}, toRemove={}",
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig1()}},
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
om := &TunnelHostnameMapper{
tunnelHostnameToOrigin: tt.fields.tunnelHostnameToOrigin,
}
if gotToRemove := om.ToRemove(tt.args.newConfigs); !reflect.DeepEqual(gotToRemove, tt.wantToRemove) {
t.Errorf("TunnelHostnameMapper.ToRemove() = %v, want %v", gotToRemove, tt.wantToRemove)
}
})
}
}
func sampleConfig1() *pogs.ReverseProxyConfig {
return &pogs.ReverseProxyConfig{
TunnelHostname: "mock.example.com",
OriginConfig: &pogs.HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
}
}
func sampleConfig2() *pogs.ReverseProxyConfig {
return &pogs.ReverseProxyConfig{
TunnelHostname: "mock2.example.com",
OriginConfig: &pogs.HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
}
}

View File

@ -1,775 +0,0 @@
package pogs
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/http"
"net/url"
"time"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/originservice"
"github.com/cloudflare/cloudflared/tlsconfig"
"github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/pkg/errors"
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/pogs"
"zombiezen.com/go/capnproto2/rpc"
"zombiezen.com/go/capnproto2/server"
)
///
/// Structs
///
// ClientConfig is a collection of FallibleConfig that determines how cloudflared should function
type ClientConfig struct {
Version Version
SupervisorConfig *SupervisorConfig
EdgeConnectionConfig *EdgeConnectionConfig
DoHProxyConfigs []*DoHProxyConfig `capnp:"dohProxyConfigs"`
ReverseProxyConfigs []*ReverseProxyConfig
}
func (c *ClientConfig) MarshalBytes() ([]byte, error) {
msg, firstSeg, err := capnp.NewMessage(capnp.SingleSegment(nil))
if err != nil {
return nil, err
}
capnpEntity, err := tunnelrpc.NewRootClientConfig(firstSeg)
if err != nil {
return nil, err
}
err = MarshalClientConfig(capnpEntity, c)
if err != nil {
return nil, err
}
return msg.Marshal()
}
func UnmarshalClientConfigFromBytes(clientConfigBytes []byte) (*ClientConfig, error) {
msg, err := capnp.Unmarshal(clientConfigBytes)
if err != nil {
return nil, err
}
capnpClientConfig, err := tunnelrpc.ReadRootClientConfig(msg)
if err != nil {
return nil, err
}
pogsClientConfig, err := UnmarshalClientConfig(capnpClientConfig)
if err != nil {
return nil, err
}
return pogsClientConfig, nil
}
// Version type models the version of a ClientConfig
type Version uint64
func InitVersion() Version {
return Version(0)
}
func (v Version) IsNewerOrEqual(comparedVersion Version) bool {
return v >= comparedVersion
}
func (v Version) String() string {
return fmt.Sprintf("Version: %d", v)
}
// FallibleConfig is an interface implemented by configs that cloudflared might not be able to apply
//go-sumtype:decl FallibleConfig
type FallibleConfig interface {
FailReason(err error) string
isFallibleConfig()
}
// SupervisorConfig specifies config of components managed by Supervisor other than ConnectionManager
type SupervisorConfig struct {
AutoUpdateFrequency time.Duration
MetricsUpdateFrequency time.Duration
GracePeriod time.Duration
}
// FailReason impelents FallibleConfig interface for SupervisorConfig
func (sc *SupervisorConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply SupervisorConfig, err: %v", err)
}
func (_ *SupervisorConfig) isFallibleConfig() {}
// EdgeConnectionConfig specifies what parameters and how may connections should ConnectionManager establish with edge
type EdgeConnectionConfig struct {
NumHAConnections uint8
HeartbeatInterval time.Duration
Timeout time.Duration
MaxFailedHeartbeats uint64
UserCredentialPath string
}
// FailReason impelents FallibleConfig interface for EdgeConnectionConfig
func (cmc *EdgeConnectionConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply EdgeConnectionConfig, err: %v", err)
}
func (_ *EdgeConnectionConfig) isFallibleConfig() {}
// DoHProxyConfig is configuration for DNS over HTTPS service
type DoHProxyConfig struct {
ListenHost string
ListenPort uint16
Upstreams []string
}
// FailReason impelents FallibleConfig interface for DoHProxyConfig
func (dpc *DoHProxyConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply DoHProxyConfig, err: %v", err)
}
func (_ *DoHProxyConfig) isFallibleConfig() {}
// ReverseProxyConfig how and for what hostnames can this cloudflared proxy
type ReverseProxyConfig struct {
TunnelHostname h2mux.TunnelHostname
OriginConfig OriginConfig
Retries uint64
ConnectionTimeout time.Duration
CompressionQuality uint64
}
func NewReverseProxyConfig(
tunnelHostname string,
originConfig OriginConfig,
retries uint64,
connectionTimeout time.Duration,
compressionQuality uint64,
) (*ReverseProxyConfig, error) {
if originConfig == nil {
return nil, fmt.Errorf("NewReverseProxyConfig: originConfigUnmarshaler was null")
}
return &ReverseProxyConfig{
TunnelHostname: h2mux.TunnelHostname(tunnelHostname),
OriginConfig: originConfig,
Retries: retries,
ConnectionTimeout: connectionTimeout,
CompressionQuality: compressionQuality,
}, nil
}
// FailReason impelents FallibleConfig interface for ReverseProxyConfig
func (rpc *ReverseProxyConfig) FailReason(err error) string {
return fmt.Sprintf("Cannot apply ReverseProxyConfig, err: %v", err)
}
func (_ *ReverseProxyConfig) isFallibleConfig() {}
//go-sumtype:decl OriginConfig
type OriginConfig interface {
// Service returns a OriginService used to proxy to the origin
Service(logger.Service) (originservice.OriginService, error)
// go-sumtype requires at least one unexported method, otherwise it will complain that interface is not sealed
isOriginConfig()
}
type HTTPOriginConfig struct {
URLString string `capnp:"urlString"`
TCPKeepAlive time.Duration `capnp:"tcpKeepAlive"`
DialDualStack bool
TLSHandshakeTimeout time.Duration `capnp:"tlsHandshakeTimeout"`
TLSVerify bool `capnp:"tlsVerify"`
OriginCAPool string
OriginServerName string
MaxIdleConnections uint64
IdleConnectionTimeout time.Duration
ProxyConnectionTimeout time.Duration
ExpectContinueTimeout time.Duration
ChunkedEncoding bool
}
func (hc *HTTPOriginConfig) Service(logger logger.Service) (originservice.OriginService, error) {
rootCAs, err := tlsconfig.LoadCustomOriginCA(hc.OriginCAPool)
if err != nil {
return nil, err
}
dialer := &net.Dialer{
Timeout: hc.ProxyConnectionTimeout,
KeepAlive: hc.TCPKeepAlive,
}
if !hc.DialDualStack {
dialer.FallbackDelay = -1
}
dialContext := dialer.DialContext
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialContext,
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
ServerName: hc.OriginServerName,
InsecureSkipVerify: hc.TLSVerify,
},
TLSHandshakeTimeout: hc.TLSHandshakeTimeout,
MaxIdleConns: int(hc.MaxIdleConnections),
IdleConnTimeout: hc.IdleConnectionTimeout,
ExpectContinueTimeout: hc.ExpectContinueTimeout,
}
url, err := url.Parse(hc.URLString)
if err != nil {
return nil, errors.Wrapf(err, "%s is not a valid URL", hc.URLString)
}
if url.Scheme == "unix" {
transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialContext(ctx, "unix", url.Host)
}
}
return originservice.NewHTTPService(transport, url, hc.ChunkedEncoding), nil
}
func (*HTTPOriginConfig) isOriginConfig() {}
type WebSocketOriginConfig struct {
URLString string `capnp:"urlString"`
TLSVerify bool `capnp:"tlsVerify"`
OriginCAPool string
OriginServerName string
}
func (wsc *WebSocketOriginConfig) Service(logger logger.Service) (originservice.OriginService, error) {
rootCAs, err := tlsconfig.LoadCustomOriginCA(wsc.OriginCAPool)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{
RootCAs: rootCAs,
ServerName: wsc.OriginServerName,
InsecureSkipVerify: wsc.TLSVerify,
}
url, err := url.Parse(wsc.URLString)
if err != nil {
return nil, errors.Wrapf(err, "%s is not a valid URL", wsc.URLString)
}
return originservice.NewWebSocketService(tlsConfig, url, logger)
}
func (*WebSocketOriginConfig) isOriginConfig() {}
type HelloWorldOriginConfig struct{}
func (*HelloWorldOriginConfig) Service(logger logger.Service) (originservice.OriginService, error) {
helloCert, err := tlsconfig.GetHelloCertificateX509()
if err != nil {
return nil, errors.Wrap(err, "Cannot get Hello World server certificate")
}
rootCAs := x509.NewCertPool()
rootCAs.AddCert(helloCert)
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
},
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
return originservice.NewHelloWorldService(transport, logger)
}
func (*HelloWorldOriginConfig) isOriginConfig() {}
/*
* Boilerplate to convert between these structs and the primitive structs
* generated by capnp-go.
* Mnemonics for variable names in this section:
* - `p` is for POGS (plain old Go struct)
* - `s` (and `ss`) is for "capnp.Struct", which is the fundamental type
* underlying the capnp-go data structures.
*/
func MarshalClientConfig(s tunnelrpc.ClientConfig, p *ClientConfig) error {
s.SetVersion(uint64(p.Version))
supervisorConfig, err := s.NewSupervisorConfig()
if err != nil {
return errors.Wrap(err, "failed to get SupervisorConfig")
}
if err = MarshalSupervisorConfig(supervisorConfig, p.SupervisorConfig); err != nil {
return errors.Wrap(err, "MarshalSupervisorConfig error")
}
edgeConnectionConfig, err := s.NewEdgeConnectionConfig()
if err != nil {
return errors.Wrap(err, "failed to get EdgeConnectionConfig")
}
if err := MarshalEdgeConnectionConfig(edgeConnectionConfig, p.EdgeConnectionConfig); err != nil {
return errors.Wrap(err, "MarshalEdgeConnectionConfig error")
}
if err := marshalDoHProxyConfigs(s, p.DoHProxyConfigs); err != nil {
return errors.Wrap(err, "marshalDoHProxyConfigs error")
}
if err := marshalReverseProxyConfigs(s, p.ReverseProxyConfigs); err != nil {
return errors.Wrap(err, "marshalReverseProxyConfigs error")
}
return nil
}
func MarshalSupervisorConfig(s tunnelrpc.SupervisorConfig, p *SupervisorConfig) error {
if err := pogs.Insert(tunnelrpc.SupervisorConfig_TypeID, s.Struct, p); err != nil {
return errors.Wrap(err, "failed to insert SupervisorConfig")
}
return nil
}
func MarshalEdgeConnectionConfig(s tunnelrpc.EdgeConnectionConfig, p *EdgeConnectionConfig) error {
if err := pogs.Insert(tunnelrpc.EdgeConnectionConfig_TypeID, s.Struct, p); err != nil {
return errors.Wrap(err, "failed to insert EdgeConnectionConfig")
}
return nil
}
func marshalDoHProxyConfigs(s tunnelrpc.ClientConfig, dohProxyConfigs []*DoHProxyConfig) error {
capnpList, err := s.NewDohProxyConfigs(int32(len(dohProxyConfigs)))
if err != nil {
return err
}
for i, unmarshalledConfig := range dohProxyConfigs {
err := MarshalDoHProxyConfig(capnpList.At(i), unmarshalledConfig)
if err != nil {
return err
}
}
return nil
}
func marshalReverseProxyConfigs(s tunnelrpc.ClientConfig, reverseProxyConfigs []*ReverseProxyConfig) error {
capnpList, err := s.NewReverseProxyConfigs(int32(len(reverseProxyConfigs)))
if err != nil {
return err
}
for i, unmarshalledConfig := range reverseProxyConfigs {
err := MarshalReverseProxyConfig(capnpList.At(i), unmarshalledConfig)
if err != nil {
return err
}
}
return nil
}
func UnmarshalClientConfig(s tunnelrpc.ClientConfig) (*ClientConfig, error) {
p := new(ClientConfig)
p.Version = Version(s.Version())
supervisorConfig, err := s.SupervisorConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to get SupervisorConfig")
}
p.SupervisorConfig, err = UnmarshalSupervisorConfig(supervisorConfig)
if err != nil {
return nil, errors.Wrap(err, "UnmarshalSupervisorConfig error")
}
edgeConnectionConfig, err := s.EdgeConnectionConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to get ConnectionManagerConfig")
}
p.EdgeConnectionConfig, err = UnmarshalEdgeConnectionConfig(edgeConnectionConfig)
if err != nil {
return nil, errors.Wrap(err, "UnmarshalConnectionManagerConfig error")
}
p.DoHProxyConfigs, err = unmarshalDoHProxyConfigs(s)
if err != nil {
return nil, errors.Wrap(err, "unmarshalDoHProxyConfigs error")
}
p.ReverseProxyConfigs, err = unmarshalReverseProxyConfigs(s)
if err != nil {
return nil, errors.Wrap(err, "unmarshalReverseProxyConfigs error")
}
return p, nil
}
func UnmarshalSupervisorConfig(s tunnelrpc.SupervisorConfig) (*SupervisorConfig, error) {
p := new(SupervisorConfig)
err := pogs.Extract(p, tunnelrpc.SupervisorConfig_TypeID, s.Struct)
return p, err
}
func UnmarshalEdgeConnectionConfig(s tunnelrpc.EdgeConnectionConfig) (*EdgeConnectionConfig, error) {
p := new(EdgeConnectionConfig)
err := pogs.Extract(p, tunnelrpc.EdgeConnectionConfig_TypeID, s.Struct)
return p, err
}
func unmarshalDoHProxyConfigs(s tunnelrpc.ClientConfig) ([]*DoHProxyConfig, error) {
var result []*DoHProxyConfig
marshalledDoHProxyConfigs, err := s.DohProxyConfigs()
if err != nil {
return nil, err
}
for i := 0; i < marshalledDoHProxyConfigs.Len(); i++ {
ss := marshalledDoHProxyConfigs.At(i)
dohProxyConfig, err := UnmarshalDoHProxyConfig(ss)
if err != nil {
return nil, err
}
result = append(result, dohProxyConfig)
}
return result, nil
}
func unmarshalReverseProxyConfigs(s tunnelrpc.ClientConfig) ([]*ReverseProxyConfig, error) {
var result []*ReverseProxyConfig
marshalledReverseProxyConfigs, err := s.ReverseProxyConfigs()
if err != nil {
return nil, err
}
for i := 0; i < marshalledReverseProxyConfigs.Len(); i++ {
ss := marshalledReverseProxyConfigs.At(i)
reverseProxyConfig, err := UnmarshalReverseProxyConfig(ss)
if err != nil {
return nil, err
}
result = append(result, reverseProxyConfig)
}
return result, nil
}
func MarshalUseConfigurationResult(s tunnelrpc.UseConfigurationResult, p *UseConfigurationResult) error {
capnpList, err := s.NewFailedConfigs(int32(len(p.FailedConfigs)))
if err != nil {
return errors.Wrap(err, "Cannot create new FailedConfigs")
}
for i, unmarshalledFailedConfig := range p.FailedConfigs {
err := MarshalFailedConfig(capnpList.At(i), unmarshalledFailedConfig)
if err != nil {
return errors.Wrapf(err, "Cannot MarshalFailedConfig at index %d", i)
}
}
s.SetSuccess(p.Success)
return nil
}
func UnmarshalUseConfigurationResult(s tunnelrpc.UseConfigurationResult) (*UseConfigurationResult, error) {
p := new(UseConfigurationResult)
var failedConfigs []*FailedConfig
marshalledFailedConfigs, err := s.FailedConfigs()
if err != nil {
return nil, errors.Wrap(err, "Cannot get FailedConfigs")
}
for i := 0; i < marshalledFailedConfigs.Len(); i++ {
ss := marshalledFailedConfigs.At(i)
failedConfig, err := UnmarshalFailedConfig(ss)
if err != nil {
return nil, errors.Wrapf(err, "Cannot UnmarshalFailedConfig at index %d", i)
}
failedConfigs = append(failedConfigs, failedConfig)
}
p.FailedConfigs = failedConfigs
p.Success = s.Success()
return p, nil
}
func MarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig, p *DoHProxyConfig) error {
return pogs.Insert(tunnelrpc.DoHProxyConfig_TypeID, s.Struct, p)
}
func UnmarshalDoHProxyConfig(s tunnelrpc.DoHProxyConfig) (*DoHProxyConfig, error) {
p := new(DoHProxyConfig)
err := pogs.Extract(p, tunnelrpc.DoHProxyConfig_TypeID, s.Struct)
return p, err
}
func MarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig, p *ReverseProxyConfig) error {
s.SetTunnelHostname(p.TunnelHostname.String())
switch config := p.OriginConfig.(type) {
case *HTTPOriginConfig:
ss, err := s.OriginConfig().NewHttp()
if err != nil {
return err
}
if err := MarshalHTTPOriginConfig(ss, config); err != nil {
return err
}
case *WebSocketOriginConfig:
ss, err := s.OriginConfig().NewWebsocket()
if err != nil {
return err
}
if err := MarshalWebSocketOriginConfig(ss, config); err != nil {
return err
}
case *HelloWorldOriginConfig:
ss, err := s.OriginConfig().NewHelloWorld()
if err != nil {
return err
}
if err := MarshalHelloWorldOriginConfig(ss, config); err != nil {
return err
}
default:
return fmt.Errorf("Unknown type for config: %T", config)
}
s.SetRetries(p.Retries)
s.SetConnectionTimeout(p.ConnectionTimeout.Nanoseconds())
s.SetCompressionQuality(p.CompressionQuality)
return nil
}
func UnmarshalReverseProxyConfig(s tunnelrpc.ReverseProxyConfig) (*ReverseProxyConfig, error) {
p := new(ReverseProxyConfig)
tunnelHostname, err := s.TunnelHostname()
if err != nil {
return nil, err
}
p.TunnelHostname = h2mux.TunnelHostname(tunnelHostname)
switch s.OriginConfig().Which() {
case tunnelrpc.ReverseProxyConfig_originConfig_Which_http:
ss, err := s.OriginConfig().Http()
if err != nil {
return nil, err
}
config, err := UnmarshalHTTPOriginConfig(ss)
if err != nil {
return nil, err
}
p.OriginConfig = config
case tunnelrpc.ReverseProxyConfig_originConfig_Which_websocket:
ss, err := s.OriginConfig().Websocket()
if err != nil {
return nil, err
}
config, err := UnmarshalWebSocketOriginConfig(ss)
if err != nil {
return nil, err
}
p.OriginConfig = config
case tunnelrpc.ReverseProxyConfig_originConfig_Which_helloWorld:
ss, err := s.OriginConfig().HelloWorld()
if err != nil {
return nil, err
}
config, err := UnmarshalHelloWorldOriginConfig(ss)
if err != nil {
return nil, err
}
p.OriginConfig = config
}
p.Retries = s.Retries()
p.ConnectionTimeout = time.Duration(s.ConnectionTimeout())
p.CompressionQuality = s.CompressionQuality()
return p, nil
}
func MarshalHTTPOriginConfig(s tunnelrpc.HTTPOriginConfig, p *HTTPOriginConfig) error {
return pogs.Insert(tunnelrpc.HTTPOriginConfig_TypeID, s.Struct, p)
}
func UnmarshalHTTPOriginConfig(s tunnelrpc.HTTPOriginConfig) (*HTTPOriginConfig, error) {
p := new(HTTPOriginConfig)
err := pogs.Extract(p, tunnelrpc.HTTPOriginConfig_TypeID, s.Struct)
return p, err
}
func MarshalWebSocketOriginConfig(s tunnelrpc.WebSocketOriginConfig, p *WebSocketOriginConfig) error {
return pogs.Insert(tunnelrpc.WebSocketOriginConfig_TypeID, s.Struct, p)
}
func UnmarshalWebSocketOriginConfig(s tunnelrpc.WebSocketOriginConfig) (*WebSocketOriginConfig, error) {
p := new(WebSocketOriginConfig)
err := pogs.Extract(p, tunnelrpc.WebSocketOriginConfig_TypeID, s.Struct)
return p, err
}
func MarshalHelloWorldOriginConfig(s tunnelrpc.HelloWorldOriginConfig, p *HelloWorldOriginConfig) error {
return pogs.Insert(tunnelrpc.HelloWorldOriginConfig_TypeID, s.Struct, p)
}
func UnmarshalHelloWorldOriginConfig(s tunnelrpc.HelloWorldOriginConfig) (*HelloWorldOriginConfig, error) {
p := new(HelloWorldOriginConfig)
err := pogs.Extract(p, tunnelrpc.HelloWorldOriginConfig_TypeID, s.Struct)
return p, err
}
type ClientService interface {
UseConfiguration(ctx context.Context, config *ClientConfig) (*UseConfigurationResult, error)
}
type ClientService_PogsClient struct {
Client capnp.Client
Conn *rpc.Conn
}
func (c *ClientService_PogsClient) Close() error {
return c.Conn.Close()
}
func (c *ClientService_PogsClient) UseConfiguration(
ctx context.Context,
config *ClientConfig,
) (*UseConfigurationResult, error) {
client := tunnelrpc.ClientService{Client: c.Client}
promise := client.UseConfiguration(ctx, func(p tunnelrpc.ClientService_useConfiguration_Params) error {
clientServiceConfig, err := p.NewClientServiceConfig()
if err != nil {
return err
}
return MarshalClientConfig(clientServiceConfig, config)
})
retval, err := promise.Result().Struct()
if err != nil {
return nil, err
}
return UnmarshalUseConfigurationResult(retval)
}
func ClientService_ServerToClient(s ClientService) tunnelrpc.ClientService {
return tunnelrpc.ClientService_ServerToClient(ClientService_PogsImpl{s})
}
type ClientService_PogsImpl struct {
impl ClientService
}
func (i ClientService_PogsImpl) UseConfiguration(p tunnelrpc.ClientService_useConfiguration) error {
config, err := p.Params.ClientServiceConfig()
if err != nil {
return errors.Wrap(err, "Cannot get CloudflaredConfig parameter")
}
pogsConfig, err := UnmarshalClientConfig(config)
if err != nil {
return errors.Wrap(err, "Cannot unmarshal tunnelrpc.CloudflaredConfig to *CloudflaredConfig")
}
server.Ack(p.Options)
userConfigResult, err := i.impl.UseConfiguration(p.Ctx, pogsConfig)
if err != nil {
return err
}
result, err := p.Results.NewResult()
if err != nil {
return err
}
return MarshalUseConfigurationResult(result, userConfigResult)
}
type UseConfigurationResult struct {
Success bool
FailedConfigs []*FailedConfig
}
type FailedConfig struct {
Config FallibleConfig
Reason string
}
func MarshalFailedConfig(s tunnelrpc.FailedConfig, p *FailedConfig) error {
switch config := p.Config.(type) {
case *SupervisorConfig:
ss, err := s.Config().NewSupervisor()
if err != nil {
return err
}
err = MarshalSupervisorConfig(ss, config)
if err != nil {
return err
}
case *EdgeConnectionConfig:
ss, err := s.Config().NewEdgeConnection()
if err != nil {
return err
}
err = MarshalEdgeConnectionConfig(ss, config)
if err != nil {
return err
}
case *DoHProxyConfig:
ss, err := s.Config().NewDoh()
if err != nil {
return err
}
err = MarshalDoHProxyConfig(ss, config)
if err != nil {
return err
}
case *ReverseProxyConfig:
ss, err := s.Config().NewReverseProxy()
if err != nil {
return err
}
err = MarshalReverseProxyConfig(ss, config)
if err != nil {
return err
}
default:
return fmt.Errorf("Unknown type for Config: %T", config)
}
s.SetReason(p.Reason)
return nil
}
func UnmarshalFailedConfig(s tunnelrpc.FailedConfig) (*FailedConfig, error) {
p := new(FailedConfig)
switch s.Config().Which() {
case tunnelrpc.FailedConfig_config_Which_supervisor:
ss, err := s.Config().Supervisor()
if err != nil {
return nil, errors.Wrap(err, "Cannot get SupervisorConfig from Config")
}
config, err := UnmarshalSupervisorConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalSupervisorConfig")
}
p.Config = config
case tunnelrpc.FailedConfig_config_Which_edgeConnection:
ss, err := s.Config().EdgeConnection()
if err != nil {
return nil, errors.Wrap(err, "Cannot get ConnectionManager from Config")
}
config, err := UnmarshalEdgeConnectionConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalConnectionManagerConfig")
}
p.Config = config
case tunnelrpc.FailedConfig_config_Which_doh:
ss, err := s.Config().Doh()
if err != nil {
return nil, errors.Wrap(err, "Cannot get Doh from Config")
}
config, err := UnmarshalDoHProxyConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalDoHProxyConfig")
}
p.Config = config
case tunnelrpc.FailedConfig_config_Which_reverseProxy:
ss, err := s.Config().ReverseProxy()
if err != nil {
return nil, errors.Wrap(err, "Cannot get ReverseProxy from Config")
}
config, err := UnmarshalReverseProxyConfig(ss)
if err != nil {
return nil, errors.Wrap(err, "Cannot UnmarshalReverseProxyConfig")
}
p.Config = config
default:
return nil, fmt.Errorf("Unknown type for FailedConfig: %v", s.Config().Which())
}
reason, err := s.Reason()
if err != nil {
return nil, errors.Wrap(err, "Cannot get Reason")
}
p.Reason = reason
return p, nil
}

View File

@ -1,455 +0,0 @@
package pogs
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/tunnelrpc"
capnp "zombiezen.com/go/capnproto2"
)
// Assert *HTTPOriginConfig implements OriginConfig
var _ OriginConfig = (*HTTPOriginConfig)(nil)
// Assert *WebSocketOriginConfig implements OriginConfig
var _ OriginConfig = (*WebSocketOriginConfig)(nil)
// Assert *HelloWorldOriginConfig implements OriginConfig
var _ OriginConfig = (*HelloWorldOriginConfig)(nil)
func TestVersion(t *testing.T) {
firstVersion := InitVersion()
secondVersion := Version(1)
assert.False(t, firstVersion.IsNewerOrEqual(secondVersion))
assert.True(t, secondVersion.IsNewerOrEqual(firstVersion))
assert.True(t, secondVersion.IsNewerOrEqual(secondVersion))
}
func TestClientConfigCapnp(t *testing.T) {
for i, testCase := range ClientConfigTestCases() {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewClientConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalClientConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalClientConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func ClientConfigTestCases() []*ClientConfig {
addDoHProxyConfigs := func(c *ClientConfig) {
c.DoHProxyConfigs = []*DoHProxyConfig{
sampleDoHProxyConfig(),
}
}
addReverseProxyConfigs := func(c *ClientConfig) {
c.ReverseProxyConfigs = []*ReverseProxyConfig{
sampleReverseProxyConfig(),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfig = sampleHTTPOriginConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfig = sampleHTTPOriginConfigUnixPath()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfig = sampleWebSocketOriginConfig()
}),
}
}
testCases := []*ClientConfig{
sampleClientConfig(),
sampleClientConfig(addDoHProxyConfigs),
sampleClientConfig(addReverseProxyConfigs),
sampleClientConfig(addDoHProxyConfigs, addReverseProxyConfigs),
}
return testCases
}
func TestClientConfig(t *testing.T) {
for _, testCase := range ClientConfigTestCases() {
b, err := testCase.MarshalBytes()
assert.NoError(t, err)
clientConfig, err := UnmarshalClientConfigFromBytes(b)
assert.NoError(t, err)
assert.Equal(t, testCase, clientConfig)
}
}
func TestUseConfigurationResult(t *testing.T) {
testCases := []*UseConfigurationResult{
&UseConfigurationResult{
Success: true,
},
&UseConfigurationResult{
Success: false,
FailedConfigs: []*FailedConfig{
{
Config: sampleReverseProxyConfig(),
Reason: "Invalid certificate",
},
{
Config: sampleDoHProxyConfig(),
Reason: "Cannot listen on port 53",
},
},
},
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewUseConfigurationResult(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalUseConfigurationResult(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalUseConfigurationResult(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestDoHProxyConfig(t *testing.T) {
testCases := []*DoHProxyConfig{
sampleDoHProxyConfig(),
sampleDoHProxyConfig(func(c *DoHProxyConfig) {
c.Upstreams = nil
}),
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewDoHProxyConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalDoHProxyConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalDoHProxyConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestReverseProxyConfig(t *testing.T) {
testCases := []*ReverseProxyConfig{
sampleReverseProxyConfig(),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfig = sampleHTTPOriginConfig()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfig = sampleHTTPOriginConfigUnixPath()
}),
sampleReverseProxyConfig(func(c *ReverseProxyConfig) {
c.OriginConfig = sampleWebSocketOriginConfig()
}),
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewReverseProxyConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalReverseProxyConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalReverseProxyConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestHTTPOriginConfig(t *testing.T) {
testCases := []*HTTPOriginConfig{
sampleHTTPOriginConfig(),
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewHTTPOriginConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalHTTPOriginConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalHTTPOriginConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestWebSocketOriginConfig(t *testing.T) {
testCases := []*WebSocketOriginConfig{
sampleWebSocketOriginConfig(),
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewWebSocketOriginConfig(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalWebSocketOriginConfig(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalWebSocketOriginConfig(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestOriginConfigInvalidURL(t *testing.T) {
invalidConfigs := []OriginConfig{
&HTTPOriginConfig{
// this url doesn't have a scheme
URLString: "127.0.0.1:36192",
},
&WebSocketOriginConfig{
URLString: "127.0.0.1:36192",
},
}
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
for _, config := range invalidConfigs {
service, err := config.Service(logger)
assert.Error(t, err)
assert.Nil(t, service)
}
}
//////////////////////////////////////////////////////////////////////////////
// Functions to generate sample data for ease of testing
//
// There's one "sample" function per struct type. Each goes like this:
// 1. Initialize an instance of the relevant struct.
// 2. Ensure the instance has no zero-valued fields. (This catches the
// error-case where a field was added, but we forgot to add code to
// marshal/unmarshal this field in CapnProto.)
// 3. Apply one or more "override" functions (which accept a
// pointer-to-struct, so they can mutate the instance).
// sampleClientConfig initializes a new ClientConfig literal,
// applies any number of overrides to it, and returns it.
func sampleClientConfig(overrides ...func(*ClientConfig)) *ClientConfig {
sample := &ClientConfig{
Version: Version(1337),
SupervisorConfig: sampleSupervisorConfig(),
EdgeConnectionConfig: sampleEdgeConnectionConfig(),
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
func sampleSupervisorConfig() *SupervisorConfig {
sample := &SupervisorConfig{
AutoUpdateFrequency: 21 * time.Hour,
MetricsUpdateFrequency: 11 * time.Minute,
GracePeriod: 31 * time.Second,
}
sample.ensureNoZeroFields()
return sample
}
func sampleEdgeConnectionConfig() *EdgeConnectionConfig {
sample := &EdgeConnectionConfig{
NumHAConnections: 49,
HeartbeatInterval: 5 * time.Second,
Timeout: 9 * time.Second,
MaxFailedHeartbeats: 9001,
UserCredentialPath: "/Users/example/.cloudflared/cert.pem",
}
sample.ensureNoZeroFields()
return sample
}
// sampleDoHProxyConfig initializes a new DoHProxyConfig struct,
// applies any number of overrides to it, and returns it.
func sampleDoHProxyConfig(overrides ...func(*DoHProxyConfig)) *DoHProxyConfig {
sample := &DoHProxyConfig{
ListenHost: "127.0.0.1",
ListenPort: 53,
Upstreams: []string{"1.1.1.1", "1.0.0.1"},
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
// sampleReverseProxyConfig initializes a new ReverseProxyConfig struct,
// applies any number of overrides to it, and returns it.
func sampleReverseProxyConfig(overrides ...func(*ReverseProxyConfig)) *ReverseProxyConfig {
sample := &ReverseProxyConfig{
TunnelHostname: "mock-non-lb-tunnel.example.com",
OriginConfig: &HelloWorldOriginConfig{},
Retries: 18,
ConnectionTimeout: 5 * time.Second,
CompressionQuality: 3,
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
func sampleHTTPOriginConfig(overrides ...func(*HTTPOriginConfig)) *HTTPOriginConfig {
sample := &HTTPOriginConfig{
URLString: "https.example.com",
TCPKeepAlive: 7 * time.Second,
DialDualStack: true,
TLSHandshakeTimeout: 11 * time.Second,
TLSVerify: true,
OriginCAPool: "/etc/cert.pem",
OriginServerName: "secure.example.com",
MaxIdleConnections: 19,
IdleConnectionTimeout: 17 * time.Second,
ProxyConnectionTimeout: 15 * time.Second,
ExpectContinueTimeout: 21 * time.Second,
ChunkedEncoding: true,
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
func sampleHTTPOriginConfigUnixPath(overrides ...func(*HTTPOriginConfig)) *HTTPOriginConfig {
sample := &HTTPOriginConfig{
URLString: "unix:/var/lib/file.sock",
TCPKeepAlive: 7 * time.Second,
DialDualStack: true,
TLSHandshakeTimeout: 11 * time.Second,
TLSVerify: true,
OriginCAPool: "/etc/cert.pem",
OriginServerName: "secure.example.com",
MaxIdleConnections: 19,
IdleConnectionTimeout: 17 * time.Second,
ProxyConnectionTimeout: 15 * time.Second,
ExpectContinueTimeout: 21 * time.Second,
ChunkedEncoding: true,
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
func sampleWebSocketOriginConfig(overrides ...func(*WebSocketOriginConfig)) *WebSocketOriginConfig {
sample := &WebSocketOriginConfig{
URLString: "ssh://example.com",
TLSVerify: true,
OriginCAPool: "/etc/cert.pem",
OriginServerName: "secure.example.com",
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
func (c *ClientConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{"DoHProxyConfigs", "ReverseProxyConfigs"})
}
func (c *SupervisorConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *EdgeConnectionConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *DoHProxyConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *ReverseProxyConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *HTTPOriginConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
func (c *WebSocketOriginConfig) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}
// ensureNoZeroFieldsInSample checks that all fields in the sample struct,
// except those listed in `allowedZeroFieldNames`, are initialized to nonzero
// values. Note that the value has to be a pointer for reflection to work
// correctly:
// e := &ExampleStruct{ ... }
// ensureNoZeroFieldsInSample(reflect.ValueOf(e), []string{})
//
// Context:
// Our tests work by taking a sample struct and marshalling/unmarshalling it.
// This makes them easy to write, but introduces some risk: if we don't
// include a field in the sample value, it won't be covered under tests.
// This check reduces that risk by requiring fields to be either initialized
// or explicitly uninitialized.
func ensureNoZeroFieldsInSample(ptrToSampleValue reflect.Value, allowedZeroFieldNames []string) {
sampleValue := ptrToSampleValue.Elem()
structType := ptrToSampleValue.Type().Elem()
allowedZeroFieldSet := make(map[string]bool)
for _, name := range allowedZeroFieldNames {
if _, ok := structType.FieldByName(name); !ok {
panic(fmt.Sprintf("struct %v has no field %v", structType.Name(), name))
}
allowedZeroFieldSet[name] = true
}
for i := 0; i < structType.NumField(); i++ {
if allowedZeroFieldSet[structType.Field(i).Name] {
continue
}
zeroValue := reflect.Zero(structType.Field(i).Type)
if reflect.DeepEqual(zeroValue.Interface(), sampleValue.Field(i).Interface()) {
panic(fmt.Sprintf("In the sample value for struct %v, field %v was not initialized", structType.Name(), structType.Field(i).Name))
}
}
}

View File

@ -3,11 +3,8 @@ package pogs
import (
"context"
"fmt"
"time"
"github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/google/uuid"
"github.com/pkg/errors"
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/pogs"
@ -184,143 +181,6 @@ func UnmarshalRegistrationOptions(s tunnelrpc.RegistrationOptions) (*Registratio
return p, err
}
// ConnectResult models the result of Connect RPC, implemented by ConnectError and ConnectSuccess.
type ConnectResult interface {
ConnectError() *ConnectError
ConnectedTo() string
ClientConfig() *ClientConfig
Marshal(s tunnelrpc.ConnectResult) error
}
func MarshalConnectResult(s tunnelrpc.ConnectResult, p ConnectResult) error {
return p.Marshal(s)
}
func UnmarshalConnectResult(s tunnelrpc.ConnectResult) (ConnectResult, error) {
switch s.Result().Which() {
case tunnelrpc.ConnectResult_result_Which_err:
capnpConnectError, err := s.Result().Err()
if err != nil {
return nil, err
}
return UnmarshalConnectError(capnpConnectError)
case tunnelrpc.ConnectResult_result_Which_success:
capnpConnectSuccess, err := s.Result().Success()
if err != nil {
return nil, err
}
return UnmarshalConnectSuccess(capnpConnectSuccess)
default:
return nil, fmt.Errorf("Unmarshal %v not implemented yet", s.Result().Which().String())
}
}
// ConnectSuccess is the concrete returned type when Connect RPC succeed
type ConnectSuccess struct {
ServerLocationName string
Config *ClientConfig
}
func (*ConnectSuccess) ConnectError() *ConnectError {
return nil
}
func (cs *ConnectSuccess) ConnectedTo() string {
return cs.ServerLocationName
}
func (cs *ConnectSuccess) ClientConfig() *ClientConfig {
return cs.Config
}
func (cs *ConnectSuccess) Marshal(s tunnelrpc.ConnectResult) error {
capnpConnectSuccess, err := s.Result().NewSuccess()
if err != nil {
return err
}
err = capnpConnectSuccess.SetServerLocationName(cs.ServerLocationName)
if err != nil {
return errors.Wrap(err, "failed to set ConnectSuccess.ServerLocationName")
}
if cs.Config != nil {
capnpClientConfig, err := capnpConnectSuccess.NewClientConfig()
if err != nil {
return errors.Wrap(err, "failed to initialize ConnectSuccess.ClientConfig")
}
if err := MarshalClientConfig(capnpClientConfig, cs.Config); err != nil {
return errors.Wrap(err, "failed to marshal ClientConfig")
}
}
return nil
}
func UnmarshalConnectSuccess(s tunnelrpc.ConnectSuccess) (*ConnectSuccess, error) {
p := new(ConnectSuccess)
serverLocationName, err := s.ServerLocationName()
if err != nil {
return nil, errors.Wrap(err, "failed to get tunnelrpc.ConnectSuccess.ServerLocationName")
}
p.ServerLocationName = serverLocationName
if s.HasClientConfig() {
capnpClientConfig, err := s.ClientConfig()
if err != nil {
return nil, errors.Wrap(err, "failed to get tunnelrpc.ConnectSuccess.ClientConfig")
}
p.Config, err = UnmarshalClientConfig(capnpClientConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to get unmarshal ClientConfig")
}
}
return p, nil
}
// ConnectError is the concrete returned type when Connect RPC encounters some error
type ConnectError struct {
Cause string
RetryAfter time.Duration
ShouldRetry bool
}
func (ce *ConnectError) ConnectError() *ConnectError {
return ce
}
func (*ConnectError) ConnectedTo() string {
return ""
}
func (*ConnectError) ClientConfig() *ClientConfig {
return nil
}
func (ce *ConnectError) Marshal(s tunnelrpc.ConnectResult) error {
capnpConnectError, err := s.Result().NewErr()
if err != nil {
return err
}
return MarshalConnectError(capnpConnectError, ce)
}
func MarshalConnectError(s tunnelrpc.ConnectError, p *ConnectError) error {
return pogs.Insert(tunnelrpc.ConnectError_TypeID, s.Struct, p)
}
func UnmarshalConnectError(s tunnelrpc.ConnectError) (*ConnectError, error) {
p := new(ConnectError)
err := pogs.Extract(p, tunnelrpc.ConnectError_TypeID, s.Struct)
return p, err
}
func (e *ConnectError) Error() string {
return e.Cause
}
type Tag struct {
Name string `json:"name"`
Value string `json:"value"`
@ -340,102 +200,10 @@ func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
return p, err
}
type ConnectParameters struct {
OriginCert []byte
CloudflaredID uuid.UUID
NumPreviousAttempts uint8
Tags []Tag
CloudflaredVersion string
IntentLabel string
}
func MarshalConnectParameters(s tunnelrpc.CapnpConnectParameters, p *ConnectParameters) error {
if err := s.SetOriginCert(p.OriginCert); err != nil {
return err
}
cloudflaredIDBytes, err := p.CloudflaredID.MarshalBinary()
if err != nil {
return err
}
if err := s.SetCloudflaredID(cloudflaredIDBytes); err != nil {
return err
}
s.SetNumPreviousAttempts(p.NumPreviousAttempts)
if len(p.Tags) > 0 {
tagsCapnpList, err := s.NewTags(int32(len(p.Tags)))
if err != nil {
return err
}
for i, tag := range p.Tags {
tagCapnp := tagsCapnpList.At(i)
if err := tagCapnp.SetName(tag.Name); err != nil {
return err
}
if err := tagCapnp.SetValue(tag.Value); err != nil {
return err
}
}
}
if err := s.SetCloudflaredVersion(p.CloudflaredVersion); err != nil {
return err
}
return s.SetIntentLabel(p.IntentLabel)
}
func UnmarshalConnectParameters(s tunnelrpc.CapnpConnectParameters) (*ConnectParameters, error) {
originCert, err := s.OriginCert()
if err != nil {
return nil, err
}
cloudflaredIDBytes, err := s.CloudflaredID()
if err != nil {
return nil, err
}
cloudflaredID, err := uuid.FromBytes(cloudflaredIDBytes)
if err != nil {
return nil, err
}
tagsCapnpList, err := s.Tags()
if err != nil {
return nil, err
}
var tags []Tag
for i := 0; i < tagsCapnpList.Len(); i++ {
tagCapnp := tagsCapnpList.At(i)
name, err := tagCapnp.Name()
if err != nil {
return nil, err
}
value, err := tagCapnp.Value()
if err != nil {
return nil, err
}
tags = append(tags, Tag{Name: name, Value: value})
}
cloudflaredVersion, err := s.CloudflaredVersion()
if err != nil {
return nil, err
}
intentLabel, err := s.IntentLabel()
return &ConnectParameters{
OriginCert: originCert,
CloudflaredID: cloudflaredID,
NumPreviousAttempts: s.NumPreviousAttempts(),
Tags: tags,
CloudflaredVersion: cloudflaredVersion,
IntentLabel: intentLabel,
}, nil
}
type TunnelServer interface {
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration
GetServerInfo(ctx context.Context) (*ServerInfo, error)
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
Connect(ctx context.Context, parameters *ConnectParameters) (ConnectResult, error)
Authenticate(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*AuthenticateResponse, error)
ReconnectTunnel(ctx context.Context, jwt, eventDigest, connDigest []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error)
}
@ -494,25 +262,8 @@ func (i TunnelServer_PogsImpl) UnregisterTunnel(p tunnelrpc.TunnelServer_unregis
return i.impl.UnregisterTunnel(p.Ctx, gracePeriodNanoSec)
}
func (i TunnelServer_PogsImpl) Connect(p tunnelrpc.TunnelServer_connect) error {
parameters, err := p.Params.Parameters()
if err != nil {
return err
}
pogsParameters, err := UnmarshalConnectParameters(parameters)
if err != nil {
return err
}
server.Ack(p.Options)
connectResult, err := i.impl.Connect(p.Ctx, pogsParameters)
if err != nil {
return err
}
result, err := p.Results.NewResult()
if err != nil {
return err
}
return connectResult.Marshal(result)
func (i TunnelServer_PogsImpl) ObsoleteDeclarativeTunnelConnect(p tunnelrpc.TunnelServer_obsoleteDeclarativeTunnelConnect) error {
return fmt.Errorf("RPC to create declarative tunnel connection has been deprecated")
}
type TunnelServer_PogsClient struct {
@ -578,25 +329,3 @@ func (c TunnelServer_PogsClient) UnregisterTunnel(ctx context.Context, gracePeri
_, err := promise.Struct()
return err
}
func (c TunnelServer_PogsClient) Connect(ctx context.Context,
parameters *ConnectParameters,
) (ConnectResult, error) {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.Connect(ctx, func(p tunnelrpc.TunnelServer_connect_Params) error {
connectParameters, err := p.NewParameters()
if err != nil {
return err
}
err = MarshalConnectParameters(connectParameters, parameters)
if err != nil {
return err
}
return nil
})
retval, err := promise.Result().Struct()
if err != nil {
return nil, err
}
return UnmarshalConnectResult(retval)
}

View File

@ -2,12 +2,9 @@ package pogs
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
capnp "zombiezen.com/go/capnproto2"
)
@ -56,94 +53,3 @@ func TestTunnelRegistration(t *testing.T) {
}
}
func TestConnectResult(t *testing.T) {
testCases := []ConnectResult{
&ConnectError{
Cause: "it broke",
ShouldRetry: false,
RetryAfter: 2 * time.Second,
},
&ConnectSuccess{
ServerLocationName: "SFO",
Config: sampleClientConfig(),
},
&ConnectSuccess{
ServerLocationName: "",
Config: nil,
},
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewConnectResult(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalConnectResult(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase #%v failed to marshal", i) {
continue
}
result, err := UnmarshalConnectResult(capnpEntity)
if !assert.NoError(t, err, "testCase #%v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func TestConnectParameters(t *testing.T) {
testCases := []*ConnectParameters{
sampleConnectParameters(),
sampleConnectParameters(func(c *ConnectParameters) {
c.IntentLabel = "my_intent"
}),
sampleConnectParameters(func(c *ConnectParameters) {
c.Tags = nil
}),
}
for i, testCase := range testCases {
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
capnpEntity, err := tunnelrpc.NewCapnpConnectParameters(seg)
if !assert.NoError(t, err) {
t.Fatal("Couldn't initialize a new message")
}
err = MarshalConnectParameters(capnpEntity, testCase)
if !assert.NoError(t, err, "testCase index %v failed to marshal", i) {
continue
}
result, err := UnmarshalConnectParameters(capnpEntity)
if !assert.NoError(t, err, "testCase index %v failed to unmarshal", i) {
continue
}
assert.Equal(t, testCase, result, "testCase index %v didn't preserve struct through marshalling and unmarshalling", i)
}
}
func sampleConnectParameters(overrides ...func(*ConnectParameters)) *ConnectParameters {
cloudflaredID, err := uuid.Parse("ED7BA470-8E54-465E-825C-99712043E01C")
if err != nil {
panic(err)
}
sample := &ConnectParameters{
OriginCert: []byte("my-origin-cert"),
CloudflaredID: cloudflaredID,
NumPreviousAttempts: 19,
Tags: []Tag{
Tag{
Name: "provision-method",
Value: "new",
},
},
CloudflaredVersion: "7.0",
IntentLabel: "my_intent",
}
sample.ensureNoZeroFields()
for _, f := range overrides {
f(sample)
}
return sample
}
func (c *ConnectParameters) ensureNoZeroFields() {
ensureNoZeroFieldsInSample(reflect.ValueOf(c), []string{})
}

View File

@ -295,7 +295,8 @@ interface TunnelServer {
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
getServerInfo @1 () -> (result :ServerInfo);
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
connect @3 (parameters :CapnpConnectParameters) -> (result :ConnectResult);
# obsoleteDeclarativeTunnelConnect RPC deprecated in TUN-3019
obsoleteDeclarativeTunnelConnect @3 (parameters :CapnpConnectParameters) -> (result :ConnectResult);
authenticate @4 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :AuthenticateResponse);
reconnectTunnel @5 (jwt :Data, eventDigest :Data, connDigest :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
}

View File

@ -2874,9 +2874,9 @@ func (c TunnelServer) UnregisterTunnel(ctx context.Context, params func(TunnelSe
}
return TunnelServer_unregisterTunnel_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
func (c TunnelServer) Connect(ctx context.Context, params func(TunnelServer_connect_Params) error, opts ...capnp.CallOption) TunnelServer_connect_Results_Promise {
func (c TunnelServer) ObsoleteDeclarativeTunnelConnect(ctx context.Context, params func(TunnelServer_obsoleteDeclarativeTunnelConnect_Params) error, opts ...capnp.CallOption) TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise {
if c.Client == nil {
return TunnelServer_connect_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
@ -2884,15 +2884,17 @@ func (c TunnelServer) Connect(ctx context.Context, params func(TunnelServer_conn
InterfaceID: 0xea58385c65416035,
MethodID: 3,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:TunnelServer",
MethodName: "connect",
MethodName: "obsoleteDeclarativeTunnelConnect",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
call.ParamsFunc = func(s capnp.Struct) error { return params(TunnelServer_connect_Params{Struct: s}) }
call.ParamsFunc = func(s capnp.Struct) error {
return params(TunnelServer_obsoleteDeclarativeTunnelConnect_Params{Struct: s})
}
}
return TunnelServer_connect_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
func (c TunnelServer) Authenticate(ctx context.Context, params func(TunnelServer_authenticate_Params) error, opts ...capnp.CallOption) TunnelServer_authenticate_Results_Promise {
if c.Client == nil {
@ -2942,7 +2944,7 @@ type TunnelServer_Server interface {
UnregisterTunnel(TunnelServer_unregisterTunnel) error
Connect(TunnelServer_connect) error
ObsoleteDeclarativeTunnelConnect(TunnelServer_obsoleteDeclarativeTunnelConnect) error
Authenticate(TunnelServer_authenticate) error
@ -3006,11 +3008,11 @@ func TunnelServer_Methods(methods []server.Method, s TunnelServer_Server) []serv
InterfaceID: 0xea58385c65416035,
MethodID: 3,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:TunnelServer",
MethodName: "connect",
MethodName: "obsoleteDeclarativeTunnelConnect",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := TunnelServer_connect{c, opts, TunnelServer_connect_Params{Struct: p}, TunnelServer_connect_Results{Struct: r}}
return s.Connect(call)
call := TunnelServer_obsoleteDeclarativeTunnelConnect{c, opts, TunnelServer_obsoleteDeclarativeTunnelConnect_Params{Struct: p}, TunnelServer_obsoleteDeclarativeTunnelConnect_Results{Struct: r}}
return s.ObsoleteDeclarativeTunnelConnect(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 1},
})
@ -3070,12 +3072,12 @@ type TunnelServer_unregisterTunnel struct {
Results TunnelServer_unregisterTunnel_Results
}
// TunnelServer_connect holds the arguments for a server call to TunnelServer.connect.
type TunnelServer_connect struct {
// TunnelServer_obsoleteDeclarativeTunnelConnect holds the arguments for a server call to TunnelServer.obsoleteDeclarativeTunnelConnect.
type TunnelServer_obsoleteDeclarativeTunnelConnect struct {
Ctx context.Context
Options capnp.CallOptions
Params TunnelServer_connect_Params
Results TunnelServer_connect_Results
Params TunnelServer_obsoleteDeclarativeTunnelConnect_Params
Results TunnelServer_obsoleteDeclarativeTunnelConnect_Results
}
// TunnelServer_authenticate holds the arguments for a server call to TunnelServer.authenticate.
@ -3552,48 +3554,48 @@ func (p TunnelServer_unregisterTunnel_Results_Promise) Struct() (TunnelServer_un
return TunnelServer_unregisterTunnel_Results{s}, err
}
type TunnelServer_connect_Params struct{ capnp.Struct }
type TunnelServer_obsoleteDeclarativeTunnelConnect_Params struct{ capnp.Struct }
// TunnelServer_connect_Params_TypeID is the unique identifier for the type TunnelServer_connect_Params.
const TunnelServer_connect_Params_TypeID = 0xa766b24d4fe5da35
// TunnelServer_obsoleteDeclarativeTunnelConnect_Params_TypeID is the unique identifier for the type TunnelServer_obsoleteDeclarativeTunnelConnect_Params.
const TunnelServer_obsoleteDeclarativeTunnelConnect_Params_TypeID = 0xa766b24d4fe5da35
func NewTunnelServer_connect_Params(s *capnp.Segment) (TunnelServer_connect_Params, error) {
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Params(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return TunnelServer_connect_Params{st}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{st}, err
}
func NewRootTunnelServer_connect_Params(s *capnp.Segment) (TunnelServer_connect_Params, error) {
func NewRootTunnelServer_obsoleteDeclarativeTunnelConnect_Params(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return TunnelServer_connect_Params{st}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{st}, err
}
func ReadRootTunnelServer_connect_Params(msg *capnp.Message) (TunnelServer_connect_Params, error) {
func ReadRootTunnelServer_obsoleteDeclarativeTunnelConnect_Params(msg *capnp.Message) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
root, err := msg.RootPtr()
return TunnelServer_connect_Params{root.Struct()}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{root.Struct()}, err
}
func (s TunnelServer_connect_Params) String() string {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) String() string {
str, _ := text.Marshal(0xa766b24d4fe5da35, s.Struct)
return str
}
func (s TunnelServer_connect_Params) Parameters() (CapnpConnectParameters, error) {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) Parameters() (CapnpConnectParameters, error) {
p, err := s.Struct.Ptr(0)
return CapnpConnectParameters{Struct: p.Struct()}, err
}
func (s TunnelServer_connect_Params) HasParameters() bool {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) HasParameters() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s TunnelServer_connect_Params) SetParameters(v CapnpConnectParameters) error {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) SetParameters(v CapnpConnectParameters) error {
return s.Struct.SetPtr(0, v.Struct.ToPtr())
}
// NewParameters sets the parameters field to a newly
// allocated CapnpConnectParameters struct, preferring placement in s's segment.
func (s TunnelServer_connect_Params) NewParameters() (CapnpConnectParameters, error) {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params) NewParameters() (CapnpConnectParameters, error) {
ss, err := NewCapnpConnectParameters(s.Struct.Segment())
if err != nil {
return CapnpConnectParameters{}, err
@ -3602,82 +3604,82 @@ func (s TunnelServer_connect_Params) NewParameters() (CapnpConnectParameters, er
return ss, err
}
// TunnelServer_connect_Params_List is a list of TunnelServer_connect_Params.
type TunnelServer_connect_Params_List struct{ capnp.List }
// TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List is a list of TunnelServer_obsoleteDeclarativeTunnelConnect_Params.
type TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List struct{ capnp.List }
// NewTunnelServer_connect_Params creates a new list of TunnelServer_connect_Params.
func NewTunnelServer_connect_Params_List(s *capnp.Segment, sz int32) (TunnelServer_connect_Params_List, error) {
// NewTunnelServer_obsoleteDeclarativeTunnelConnect_Params creates a new list of TunnelServer_obsoleteDeclarativeTunnelConnect_Params.
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Params_List(s *capnp.Segment, sz int32) (TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
return TunnelServer_connect_Params_List{l}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List{l}, err
}
func (s TunnelServer_connect_Params_List) At(i int) TunnelServer_connect_Params {
return TunnelServer_connect_Params{s.List.Struct(i)}
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List) At(i int) TunnelServer_obsoleteDeclarativeTunnelConnect_Params {
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{s.List.Struct(i)}
}
func (s TunnelServer_connect_Params_List) Set(i int, v TunnelServer_connect_Params) error {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List) Set(i int, v TunnelServer_obsoleteDeclarativeTunnelConnect_Params) error {
return s.List.SetStruct(i, v.Struct)
}
func (s TunnelServer_connect_Params_List) String() string {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Params_List) String() string {
str, _ := text.MarshalList(0xa766b24d4fe5da35, s.List)
return str
}
// TunnelServer_connect_Params_Promise is a wrapper for a TunnelServer_connect_Params promised by a client call.
type TunnelServer_connect_Params_Promise struct{ *capnp.Pipeline }
// TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise is a wrapper for a TunnelServer_obsoleteDeclarativeTunnelConnect_Params promised by a client call.
type TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise struct{ *capnp.Pipeline }
func (p TunnelServer_connect_Params_Promise) Struct() (TunnelServer_connect_Params, error) {
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise) Struct() (TunnelServer_obsoleteDeclarativeTunnelConnect_Params, error) {
s, err := p.Pipeline.Struct()
return TunnelServer_connect_Params{s}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Params{s}, err
}
func (p TunnelServer_connect_Params_Promise) Parameters() CapnpConnectParameters_Promise {
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Params_Promise) Parameters() CapnpConnectParameters_Promise {
return CapnpConnectParameters_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
}
type TunnelServer_connect_Results struct{ capnp.Struct }
type TunnelServer_obsoleteDeclarativeTunnelConnect_Results struct{ capnp.Struct }
// TunnelServer_connect_Results_TypeID is the unique identifier for the type TunnelServer_connect_Results.
const TunnelServer_connect_Results_TypeID = 0xfeac5c8f4899ef7c
// TunnelServer_obsoleteDeclarativeTunnelConnect_Results_TypeID is the unique identifier for the type TunnelServer_obsoleteDeclarativeTunnelConnect_Results.
const TunnelServer_obsoleteDeclarativeTunnelConnect_Results_TypeID = 0xfeac5c8f4899ef7c
func NewTunnelServer_connect_Results(s *capnp.Segment) (TunnelServer_connect_Results, error) {
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Results(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return TunnelServer_connect_Results{st}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{st}, err
}
func NewRootTunnelServer_connect_Results(s *capnp.Segment) (TunnelServer_connect_Results, error) {
func NewRootTunnelServer_obsoleteDeclarativeTunnelConnect_Results(s *capnp.Segment) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return TunnelServer_connect_Results{st}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{st}, err
}
func ReadRootTunnelServer_connect_Results(msg *capnp.Message) (TunnelServer_connect_Results, error) {
func ReadRootTunnelServer_obsoleteDeclarativeTunnelConnect_Results(msg *capnp.Message) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
root, err := msg.RootPtr()
return TunnelServer_connect_Results{root.Struct()}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{root.Struct()}, err
}
func (s TunnelServer_connect_Results) String() string {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) String() string {
str, _ := text.Marshal(0xfeac5c8f4899ef7c, s.Struct)
return str
}
func (s TunnelServer_connect_Results) Result() (ConnectResult, error) {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) Result() (ConnectResult, error) {
p, err := s.Struct.Ptr(0)
return ConnectResult{Struct: p.Struct()}, err
}
func (s TunnelServer_connect_Results) HasResult() bool {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) HasResult() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s TunnelServer_connect_Results) SetResult(v ConnectResult) error {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) SetResult(v ConnectResult) error {
return s.Struct.SetPtr(0, v.Struct.ToPtr())
}
// NewResult sets the result field to a newly
// allocated ConnectResult struct, preferring placement in s's segment.
func (s TunnelServer_connect_Results) NewResult() (ConnectResult, error) {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results) NewResult() (ConnectResult, error) {
ss, err := NewConnectResult(s.Struct.Segment())
if err != nil {
return ConnectResult{}, err
@ -3686,37 +3688,37 @@ func (s TunnelServer_connect_Results) NewResult() (ConnectResult, error) {
return ss, err
}
// TunnelServer_connect_Results_List is a list of TunnelServer_connect_Results.
type TunnelServer_connect_Results_List struct{ capnp.List }
// TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List is a list of TunnelServer_obsoleteDeclarativeTunnelConnect_Results.
type TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List struct{ capnp.List }
// NewTunnelServer_connect_Results creates a new list of TunnelServer_connect_Results.
func NewTunnelServer_connect_Results_List(s *capnp.Segment, sz int32) (TunnelServer_connect_Results_List, error) {
// NewTunnelServer_obsoleteDeclarativeTunnelConnect_Results creates a new list of TunnelServer_obsoleteDeclarativeTunnelConnect_Results.
func NewTunnelServer_obsoleteDeclarativeTunnelConnect_Results_List(s *capnp.Segment, sz int32) (TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
return TunnelServer_connect_Results_List{l}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List{l}, err
}
func (s TunnelServer_connect_Results_List) At(i int) TunnelServer_connect_Results {
return TunnelServer_connect_Results{s.List.Struct(i)}
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List) At(i int) TunnelServer_obsoleteDeclarativeTunnelConnect_Results {
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{s.List.Struct(i)}
}
func (s TunnelServer_connect_Results_List) Set(i int, v TunnelServer_connect_Results) error {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List) Set(i int, v TunnelServer_obsoleteDeclarativeTunnelConnect_Results) error {
return s.List.SetStruct(i, v.Struct)
}
func (s TunnelServer_connect_Results_List) String() string {
func (s TunnelServer_obsoleteDeclarativeTunnelConnect_Results_List) String() string {
str, _ := text.MarshalList(0xfeac5c8f4899ef7c, s.List)
return str
}
// TunnelServer_connect_Results_Promise is a wrapper for a TunnelServer_connect_Results promised by a client call.
type TunnelServer_connect_Results_Promise struct{ *capnp.Pipeline }
// TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise is a wrapper for a TunnelServer_obsoleteDeclarativeTunnelConnect_Results promised by a client call.
type TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise struct{ *capnp.Pipeline }
func (p TunnelServer_connect_Results_Promise) Struct() (TunnelServer_connect_Results, error) {
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise) Struct() (TunnelServer_obsoleteDeclarativeTunnelConnect_Results, error) {
s, err := p.Pipeline.Struct()
return TunnelServer_connect_Results{s}, err
return TunnelServer_obsoleteDeclarativeTunnelConnect_Results{s}, err
}
func (p TunnelServer_connect_Results_Promise) Result() ConnectResult_Promise {
func (p TunnelServer_obsoleteDeclarativeTunnelConnect_Results_Promise) Result() ConnectResult_Promise {
return ConnectResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
}
@ -4383,257 +4385,259 @@ func (p ClientService_useConfiguration_Results_Promise) Result() UseConfiguratio
return UseConfigurationResult_Promise{Pipeline: p.Pipeline.GetPipeline(0)}
}
const schema_db8274f9144abc7e = "x\xda\xccz}\x90\x14uz\xff\xf3t\xcf\xd2\xbb\xb0" +
"\xcbL\xdbc\xfdv)\xf67\xc7\x8bQ8!\"G" +
"\xa2\x9b\xe4\xf6\x0d\xb8]\x0ea{g\x17u\xe5R\xf6" +
"\xce|w\xb6a\xa6{\xe8\xee\x01\x96\xe0\xf1R\x10e" +
"\x03'x\x90\x02\x0f\xaf\x00\x8f\xf8\x12\xbd\x13\x0f+\xa7" +
"\x11K\x93\xab\xa89\x13\xe5\x82)\xbd\x98\x8a\x11\xa8T" +
"\xac\xb3<\xd1\x94eJ\xed\xd4\xf3\xed\xd7\x1d\x96\x05L" +
"R\x95\x7f`\xea\xe9\xe7\xfb\xf2\xbc}\x9e\x97\xef\xde\xf4" +
"\xe3\xc9m\xc2\x82\x9a\xeb\x93\x00\xeaS5\x93\\6\xf7" +
"\x97\x9b\x8e\\\xf7\xd7\xdbA\x9d\x86\xe8~\xf7\xf9e\xe9" +
"\xcf\x9c\xed\xff\x045\xa2\x04\xb0pD\xda\x84\xca\x1eI" +
"\x02PvI\xff\x06\xe8\xd6\xfc\xd6\x9b\xef\x96\xdf\x95v" +
"\x80<-\xce,\x10s\xa9v\x19*\xdbj\x89\xf9\x9e" +
"\xda\x0d\x80\xee\xef\x97^?\xf6;\x07~A\xccB\xc4" +
"\x0c\xb8\xf0|\xed&T>\xe3\x9c\xffQ\xbb\x12\xd0\xfd" +
"x\x7f\xe3\x9f\x1f\xfd\xfbWv\x82|=\x82\x7fvC" +
"\xdd\xaf\x10P\x99Q\xf7\x13@\xf7\x1fn\xda|\xee\xee" +
"\x8f\xf7\xdd7\xf6\xdc\x04\xf1\xbdT7\x8a\xca\xdbu\x12" +
"\x88\xeeCw\xa5\xff\x16\x8f|\xba\x0f\xe4\x1bh\x1b\xa4" +
"\xcf\xcf\xd4M\x16\x00\x95\xbf\xabk\x05t_\xbf\xf1\xf9" +
"\xe7\xf6\xfe\xf4\xde\x1f\x80z=\"x\xeb?\xa8\xfbO" +
":\x07'\x13\xc3G?\xfaz\xe2\xc9\xd7\xaf\xf9!g" +
"p\x8f\x9f\xbe\xfd\xe9\xbd?\xfd\xda\xfb\xd0/H\x98\x00" +
"X8g\xb2E\xbc\x8b&\x93.\xf6\xbfujEi" +
"\xdf\x83\xc7\xbcK\xf3\xbd\xae\x9d\"\x08\x90pwt\x7f" +
"Z\xea\x7f8\xfb\xb0/N\x0d}\xaa\x9br\x01\x01\x17" +
"6O\xc9 \xa0\xbb\xe8W\xe7W\xde\xf6\xf4\xd0#>" +
"\x07\xbf\xe9\xad\xf5O\xd3\xe6\xdd\xf5t\x91W6\xa4v" +
"\xb7\xff\xee\xfd\x8fT\x9b\x85\xefU\xaa\x1fEeg=" +
"\xfd\xdcV\x7f;\xed7z\xeb\xa9U\x1f\xff\xb1\xfd8" +
"\xa8\xf30\xe1\xfe|\xd7\xd9\xf5s\x1e\x1bz\x99\xdf[" +
"\x04X\xf8Y\xc3/i\xeb\x86\xa9\xa4\xcb\x86\xbf\x9c\xbb" +
"\xe2\xfes\xcbO\xd0\xd61\xbbx\x97xrj\x0b*" +
"\xa7\xa6\x92i\x9e\xe5\xdco\xdc\xb8\xea\x85\x17\x9e*\x9c" +
"\xa8\xbe\x087\xf9\x9d\xc9e\xa8\x94\x92\xc4\xad'\x89\xfb" +
"\xdan|\xe7\xc5\x05\x89\xbf\x88\x1b\xb29\xf5>\x1d\xbe" +
" E\x0cw}\xfe\xcc_-\xf9\xf0\xcc\xb3q\x13\x9d" +
"N\x09d\xa2\xf3)\x12|`\x14K\xef\xb4\xb4\xbd\x00" +
"\xea\x0d\x88\xee\x9a\x03\x9b\x9d\xae\x83{\\\xe8G\x09\x05" +
"\xf2\x0ay\x13m\xd6$\x93\x835\x7f\xd0\xd1`|\xb8" +
"\xfd\xc5*o\xe4\xa7V\xe4e\xa8\xec\x92\xe9j;\xe5" +
"\x9f\x00~\xfa\xf8\xbd{\xbb\xcf.~Y\x9d\x86\x89j" +
"\xa1g\\\xb3\x09\x95E\xd7\xd0\xcf\x05\xd7p\xfb\x84\x1a" +
"\xacb\xe7Rk\xca\x1aT*\x0a\xfd\\\xa7p\xf6e" +
"w}\xff\x81\x9a\xf3\xdf\x7f\xb9Z\xa5\xe4\xe2\x0b\xefI" +
"[\xa8\xecK\xd3\xcf=\xe9\xff'\x02\xba\xd3\x9e\xfa\xbd" +
"\x1fw\xe4\xdf\xfe\xc58Q\xa444^P\x9a\x1a\xe9" +
"\xd7\xb5\x8d$\xe3\xd9y'\xfe\xe8\xdf\xf7\x9c>\x13\xf7" +
"\x94u\x8d\xdcew6\x92\xc2\xee\xfd\xfa\xc8\xa6\x15\xd7" +
"\x8d\xbeYm \xcey\xbcq\x14\x95S|\xbbg\xf9" +
"v\xc2y\xadi\xeb?~\xf3\x9d\x98\xd3\xceiz\x0f" +
"!\xe1\xaeXu\xd7\x9a\xba{\xce\x9e\x8d\x1f\xd4\xdc\xe4" +
"\x99\xae\x89\x0e:)?\xa0<\x7f\xf4\xcf\xce\xd1AR" +
"\xb5\xba\xd5\xa6\x01TX\x13WO\xd3#\x02\xc4\x82g" +
"<\xc7\xf9\xce\xf4\x16TJ\xd3\xb9\xe3L\xa7{-\xba" +
"\xbb\x9d\xad\xbe\xe5\x8e\xf7A\x9e&\x8e\xc1\x8a\xc7\x88\xf3" +
"\xd9\xe9<\x94\xa7\xdf\x8b\xca\xa9f\x09\xc0\xfd^a\xe0" +
"\xd5\x8f:\x8f\xfe\xa6zs.\xd0\xf1\xe6\x16T\x9e!" +
"\xbe\x85'\x9a\xb9}\x16.\xf8\x93\x0f\x0e<\xdc\xf9\xd1" +
"E\xbb\x7f\xf1\xff;Pi\xc8\xd0=\xea2\xdfRn" +
"\xcd\xf0\xcd\xbf\xbbx\xe5\xad3_\xba\x10\xd7\xc4\x8c\xcc" +
"\x05\x1e\xf9\x19\xd2\xc4\xd0-\xbf\xfe\xd6u\xdf\xfb\x9b\x0b" +
"U\xf6\xe3\x8c\xfd\x99\xb9\xa80\xbe\xa3F\xcc\x1f.\xfd" +
"\xe1\x99i\xc9i\x9fT]t\x12\xf1\xee\xcc\xacA\xe5" +
"\x10\xf1.<\x90y\x99.z\xc7{\x0fnh\xfd\xc1" +
"'\x9f\x92\\b\x15\xd0\xed\x9a1\x80\xcaC3h\xe7" +
"C3(\x96\x96?\xf1\xf67\x87\x0f\xbc\xf2\xd9\xb8\xd0" +
"\xbdd\xe6vT\xee\x9cI\xdc\xfd3\x09\xae\xfeT:" +
"|v\xeb\xbf\xfc\xe1\xe7q\xa9\xfe`\xd6{$\x95:" +
"\x8b\xa4\xda\xfc\xe1\xa1\xae\xfbW?\xf1\xe5\x18O\x9b\xf5" +
"\x1c1l\xe3\x0ca0\x8e\xe7iGgu\xa0rb" +
"\x16\x9d\xf7\xe4\xacV\x98\xe7:\x15\xc3`E\xab\x9c\xc8" +
"\xfdv\xf037?\xa7\x95\x8drK{\xc5\x19f\x86" +
"\xa3\xe74\x87\xf5\xb2V\xbbl\x1a6\xebATSb" +
"\x02 \x81\x00\xb2\xb6\x06@\xbd[D\xb5(\xa0\x8c\x98" +
"&\xb8\x96u\"\x0e\x8b\xa8:\x02\xca\x82\x90&D\x90" +
"\xd7\xcd\x04P\x8b\"\xaa\x1b\x05D1Mx'W\x1e" +
"\x00P7\x8a\xa8\xee\x10\xd0-3\xab\xa4\x19\xcc\x80\xa4" +
"\xb3\xc4\xb2\xb0\x1e\x04\xac\x07t-\xe6X#\xda`\x11" +
"\x92,F\x96\xd6lp\xb0\x01\x04l\x00t\x87\xcd\x8a" +
"e\xf7\x1b\x0e\xea\xc5^6d1\x1b\x87q\x12\x088" +
"\x09p\"\xf1:M\xc3`9'[\xc9\xe5\x98m\x03" +
"\x90d\xb5\xa1ds\x1e\x04Po\x14Q\xbd%&\xd9" +
"\"\x92\xec\x1b\"\xaam\x02\xba6\xb3\xd63k\xb9\x89" +
"9\xcd\xd1Mc\x85&\x96Xx\xed\\Qg\x86\xd3" +
"iB\xd2\x18\xd2\x0b\x98\x8aB\x01\x10S\x13_l\xc9" +
"F\xddvt\xa3\xd0\xc7\xe9\xad=fQ\xcf\x8d\xd0\xed" +
"\xea\xb9&\x9b[h\x0f\xf9\xda\x01\x00\x14d\xb9\x03\xa0" +
"U/\x18\xa6\xc5\xdc\xbcn\xe7H(\x10s\xce\x96A" +
"\xad\xa8\x199\x16\x1e4\xe9\xe2\x83\xbc\x03\xb2\\\x8e\xf9" +
"Z\xcc\xda\xb3{4K\x13K\xb6Z\x1f\xeac\xc9\x00" +
"\x80\xbaXD\xb5'\xa6\x8f\xdb\x96\x01\xa8\xcbET\xef" +
"\x88Y\xba\xbf\x03@\xed\x11Q]-\xa0kZzA" +
"7:\x19\x88V\xdc`\xb6ch%\x06\x00\x81\xc2\xb6" +
"\x98eR\xa2\x8d\xa9\x08\xa5\xab4Us\xb1\x00]\xac" +
"X4o7\xadb~\xa5w\x8eI\xda\xe6\xa6\x0c\x97" +
"I\xe3X\x9e\x1b\x87\xe4\xd6sl~\xc5f\xde\xba\x8a" +
"\xc5\x0d9\xbb\x97\xd9\x95\xa2c\x03\xa8\x89P\xfc\x86\x16" +
"\x00\xb5VD5-`\xab\xc5\x190\x15\x81z\xd5U" +
"/\xa7\xeb\x8aa\xb1\x82n;\xcc\xf2\xc8\xb3[I\xe1" +
"%;~ \xf9_JDu\xba\x80n\xc1\xd2r\xac" +
"\x87Y\xa8\x9b\xf9\x15\x9aafE\x96\xc3\x1a\x10\xb0f" +
"bOZ\xaa\xe9E\x96\xf7\xa4\x9b\x9f\xcb\xf0\xff)z" +
"\xeb]\xd7\x0b\xdf\x81(|\x1b\xf0K\xd7\x8f\xdfMQ" +
"\xfc6\x08_\xb8\x17\x07p\x83\xf8\xb9\xeb\x870E\x84" +
"#\xa2\xba\x95\"\xa2R&\x9d\xda \x9a\x16\xa6\"\x94" +
"\xf4\xb5\xc3\xf2\x05\xd2\xb4\x01\xad,G\x8a\xc6T\x90\xed" +
"=\x06)o\x0ec**e\xfce\x16[\xcf,\x9b" +
"\xf5@\xd227\x8e`*\xca\xfaUZ\x9fz\xb5Z" +
"\x0f\x0c\x1d\xae\x9ax\xbd\xc5r\x1ed\xf8\xcb{2\x9e" +
"\xd1\xd2\xa1\xd1\xee\x99\x19\x01Z\x18$\xdb\x06\x01\xd4\xad" +
"\"\xaa\xbbcA\xb2\x8b4\x7f\x9f\x88\xea~\x01e\xd1" +
"\xc7\xc3}\x14N{ET\x0f\x0b('\x12i*f" +
"\xe5C\x14N\xfbET\x8f\x08ca\x8f\xadg\x86\xb3" +
"X/\x80\xc4\xec\x88JW\\\xac\x17\x18\x88\xf6\xffB" +
"\xc0\x8d\xd1\x87\xaf\x8dP\x0f1\xe7%\xe9\xeaET\x1b" +
"\x09\xd8\xe9+s\x08\x02\xe8\xb4\xb0d\xbe\xfci\x9d\xf4" +
"\xaf\x0f\xd3=\xfe.\x96\x8f\xd4\x8d\xe1a\x87\xe8\xb0\x83" +
"\"\xaa?\x8a)\xfd\xa8\x05\xa0\x1e\x11Q}B@\xf4" +
"u\xfe\xd81\x00\xf5\x09\x11\xd5\x9f\x91\xce\x05O\xe7\xcf" +
"\xcc\xa5\x0eKD\xf55\xd2\xb9\xe8\xe9\xfcU\x0a\xbe\xd7" +
"DT\xdf\x12P\xaeI\xa4\xb1\x06@~\x93\xecxF" +
"D\xf5\xddK\xe1Z\xaehV\xf2CE\x0d2\x16\xcb" +
"w/\x0e\xe9F\xa5\xd4c\xb1\xf5:\x9a\x15\xbb\xddq" +
"XI*;v\x90\xa2\x92\x8eV\xb0q*`\x8f\x88" +
"\x98\x8a\x8aN@\"\x86{\xa2\xc5\xf2\xab\x98e\xeb\xa2" +
"i\x84YF7\x1cf8\xcb5\x90\x06Y1\xa4N" +
"\x80B\xbd~,Q$\xf9\xb0`F\xc8\x89\x05\x02\xfc" +
"\xe9\xae\xeb+q\x09\xe9\xa6MDu\xb9\x80\xcd\xf8%" +
"\x91I\x8f\xdd\xbd\x00j\x97\x88j\x9f\x80\xcd\xc2\x17D" +
"&M\xaa\x03\x11\xee'\x87\x1d\xa7\x8c\xa9\xa8\x18\xf5\x8d" +
"\xbd\x81\x0d\xdafn-\x03$\xf8\x0c+#\xff\xeb\xb0" +
"\x0f\xe7 \x16\xf3\x98\x8a\xba\xc9*O\x11/\x95\xcb[" +
"\xa9r0-\x9e*\xa3\xc4us$D\xe0\x1d\xdd\x03" +
"\x91\x04\xb2\xd0\xe6\x89\xa5\x0eF\xf7\xcf\xe4\xb4\x8a\xcd\xc6" +
"\x16!\xedC\x0e\x88\xcc\x0aq\xd7\x1e6+\xc5|/" +
"\x03\xc9\xb1F\x10A@\x9c\x18\x8d\x17\x9b]1\xc5{" +
"n<~\x82\x0d\xf3\xeb@<\xbf\xfa\xea\xef'\xf5\xf7" +
"\x89\xa8\x96\x05t\x8b\x84gF\x97\xc9\xc3=\xb8\xaeG" +
"\xec1\xb9sJ \xa0\x04\xe8V\xca\xb6c1\xad\x04" +
"\x18z\x1b\xf1O\xbd\x8a\xb4U\x05\x9f=Z\x92\xc7\xfd" +
"\xff\xa5\"\xe1\xea\xb3\xbd\x97y\xc7\xe4\xfac\xb1\xd4\x9b" +
"\xf3W#_\xdei\x1a\xd2U\xd7s>\x82y\xd9f" +
"\xbe_=P\xa9\x19\xa4\xe19\x946f\x8b\xa8\xde\x14" +
"O\xc3\xf3HE7\x88\xa8~C@\x89Y\x94Q\xc3" +
"\x99\x80w\xe8\x16\xdb\xab]1\x15M|.\x7f\x9dX" +
"Y\xaf\x9b\xc6En83\x0a\x97\xd0\x84\xdd7\xc7\xec" +
"\x1a\x98\xf0\xb6\xc1\xc8\xae\xd2Z6\x12X)\xc3J\x9a" +
"\x1e\xa1\x91o\xdcv\x90\xbe\x1d\xf1LX\xfe\xfae\x82" +
"W$\xb4z\xd6\xa2K\xc6\xf2\xech,\xa5\x06\x97\xdc" +
"E\xdd\xc4n\x11\xd5\x83\xb1K\x1e\xe8\x88\xa5\xd4 \xcf" +
"\x1e\"\x03\x1f\x16Q}T@\xf4\xd3\xecq\x82\xfcG" +
"ETO\x0a\x1c\xb0\xbb\xda;M\x03\xfdK\xd8\x00a" +
"G1\xcc4\xcb\x19d\x1a:\xdd\x86\xc3\xac\xf5\x1a\x16" +
"\x03H\xd8\xe2\xe8%fV\x9c\x10\"J\xdaF^\x82" +
"a\xbe\xcb[%i\x8e\x8du `\x1dE\xa4\xcd\xac" +
"N\x8b\xe5\x91\xac\xa1\x15{4\xd1\x19\xbe\x12\x05\x8d\x05" +
"\xf1\xe48\xea\xa1\x02n\xb3\x88\xea}\x04%\x18\x9b;" +
"\xc9;\xd7\x80\xc0\x91\x84d^\xd7\x11\x95t<!\xd6" +
"T5e<!N\xa2\x1a\x86\xb4\xb3CDu\xaf\x10" +
"\\\xad\xcb\x84V/B\xabM\xed7=[\x085u" +
"\x16\xc9\xeb\xd7\x0b:\x9aF\x1fW\x14F\x9a\xca\x99\xa5" +
"\xb2E\xae\xac\x9b\x86Z\xd1\x8a\xba\xe8\x8c\x84\x0b'\xd4" +
"\x05A\x92\x17\xca+\xcb\x19n,R\xc6-\x812\x94" +
"\x11\\\x06\x90\xdd\x88\"fw`\xe4.\xca6\xec\x00" +
"\xc8n&\xfa}\x18y\x8c\xb2\x13\xa7\x01d\xb7\x12}" +
"7\x86\xbd\xaa\xb2\x0b\x1f\x07\xc8\xee&\xf2A\x8cJ\x05" +
"\xe5\x00\xdf~?\xd1\x8f`T-(\x0f\xe1\\\x80\xec" +
"A\xa2\x9f$\xfa$\x81kR9\x81k\x00\xb2O\x11" +
"\xfdy\xa2K5i\xe4s\x1f\xb4\x00\xb2?#\xfa\xcf" +
"\x89^\xdb\x98\xc6Z\x00\xe5%N\x7f\x91\xe8\xaf\x11\xbd" +
"\xae)\x8du\x00\xca\xab\xb8\x1d \xfb\x0a\xd1\xcf\x10}" +
"2\xa6q2\x80r\x1a\x1f\x04\xc8\x9e!\xfa\xbbD\x9f" +
"2)\x8dS\x00\x94\x7f\xe6\xf7y\x8b\xe8\xe7\x88^\x9f" +
"Hc=\x80\xf2\xafx\x0c {\x8e\xe8\xbf!z\x83" +
"\x94\xc6\x06\x00\xe5\x03.\xd7\xaf\x89^+\x848\xd8\x9d" +
"\x8f\xc31\xb9\xa1\x1e\x95#\xa2i\x87\xae\xc0\xfc\x1e\x16" +
"\xbd\\\xd1c&\xa9\x89\xc5d4m\x06\xc4$\xa0[" +
"6\xcd\xe2\x8a\xb10\x7f\xb9\x8a\xc8w#H\x9aFw" +
">\x8cK\xcf\xf9\x96\x9b\x90\xc9i\xc5\xeerT#\xd9" +
"\xed\x15\xc7\xac\x94!\x93\xd7\x1c\x96\x0f\x13\xb5U1\x96" +
"Zf\xa9\x0f\x99U\xd2\x0d\xad\x08\xe1\x97\x89|1Y" +
"\xa9\xe8\xf9p\xef\x09\x0b;w\x88iN\xc5b6\x89" +
"v\x89\x8c+T{t\xa6\xdc\xd2\xa7\x15\xaaF\x11s" +
"\xa3\xf4\x10\xa2\xdd\xbc\x9b\xa3\xec\x90\x8cGaf\xbdV" +
"\xac\xb0+)\x06'lnz[\xbd\xe6\xe8r=p" +
"08\xbb|5\xdf_\x95y\xbd|x\xd1\xdc\xa5#" +
"\x126\x94\xd5\xf2g1]B\x94\xf3\x02k\x0d\xf9=" +
".dh\xef\x98\xdf\x84\x93M\xdfo\xaeT\x13\x05\xe6" +
"x\xbf\xba\x8d!\x93\xca\x03I+\xd9_qu/\xb3" +
"\x93W\xa2\xc5hVy\xf9\xfc\xdd\xd5\xd7\xd7\x13\x8d;" +
"D\x0f\xfco\x0a\xf1\xae\x1d{\x01\xb2m\x14\xb8\xcb1" +
"\xd4\xa1\xd2\xcdq\xa7\x8b\xc8}\x18U\xbd\x8a\xca\xf1\xa5" +
"\x87\xe8\xab1\xea\x8b\x94;9.\xac&\xfa0\xc7\xbb" +
"v\x0f\xef\x18\xdf>O\xf42\xc7;\xf4\xf0\xae\xc4\xf7" +
"/\x12}c\x1c\xef*8:\x06~%\xd1\xc3\xbbm" +
"\x1c\xa7v\x10}/\xc7\xbb\x84\x87w{\xf0i\x80\xec" +
"^\xa2\x1f\xe6xW\xe3\xe1\xdd!|\x0e {\x98\xe8" +
"\x8fr\xbc\x9b\xe4\xe1\xddq\xce\xffh\x88\xb3S:<" +
"\xbc;\xc1\xf11\xc4Y\xb7b\x15\xb3\x8e\xa5\x1b\x80\x85" +
"(6r\xe5o3Vn\x87dQ_\xcf\xc2\\\x94" +
"\xd7\xb5\xe2\xe2\x8aV\x84L\xd6\xd1rk\xa3\xd2\xbeh" +
"wiF\xde\xc6am-\xa3\x0c&\xc5s\xbdS\xb4" +
"W1K\x1f\x02\x8c\x9a\x81\xb0\xf6I\xf6\x98fuI" +
"\xc4kJfy\xe0\x17~+i\x1b\xbb\xf3E\xd6\x89" +
"A\x05$\x1aQ\x06\xd5\xe9\x8bi\x18\xe8\x95%}z" +
"fl\xbdQ\xf6\xdb\x8b\xa0n\xe9k\xad*H\xd8\xc6" +
"2\xcb9\x9d&\x1a\x8enT\xd8E\x1b\xe4\x86+\xc6" +
"Z\x96_\x82F\xce\xcc\xebF\x01.\xeak\xc4KM" +
"\x99b\x85\x1a\x8ff\x8c=\xd3\xc9sZ@\xe0\xd0E" +
"e\x87\xdc\x12M\x07Zs|U\xab\xc54;\xd6\xd8" +
"Np\x9a?\x15\xf5\x82\xcc\x9b\x04\xd4\x00\x84OZ\x18" +
"<\x0b\xc8'6\x81 ?&a\xf4\x9a\x82\xc1\xe3\x89" +
"\xfc\x90\x05\x82|@B!|k\xc4\xe0\x9dP\xde5" +
"\x0a\x82\xbcSB1|\xfe\xc3`\xe6.\x8ft\x80 " +
"\x97$L\x84o\xa1\x18\x0c\xece\x8dJ\xab;%\xac" +
"\x09\x1f\x161x\x15\x92o\xdb\x0e\x82\xbcDr\x83\x0e" +
"\x0aZ=1\xda\xd0\x0d\x00\x032\x1c2\xda\xd0\x0d\xe6" +
"T\x18tZ\x00m\xb8\xc5\x87\xe76t\x83I-$" +
"s\x9a\xc3\xda\xa8=\xf5>\xa2\x0f\xde\xd0\x86\xf1\x09\xa8" +
"x\xa9\x9eh\xfc\xda\xba#\xaa\xff\xc2\x11\xd6hT\xfe" +
"\x85}\xe8\x9e\xc7\xe3\xa5\xb5?N9\xb4\xdd\x1f\xc6\x9c" +
"\x8c\x8dSNP\xbd}RD\xf5\x0d!*\x1a\x02\x9f" +
"\x0e\x86\x86hZAc<\xc1\xec\xd0\xf7|\xbf\xec\xad" +
"\x9e \xbays\x98\x97\xc5\xe8meC\x94\x0e\xe2c" +
"\xc5\xa9\xb1\xb1\"\x06-\xb94&{\xc4\x87\x8cS/" +
"\xd3\xdf\xc5\x1bL\x9e\xce\x12\xdc%\x83GT\x0c\x1e\xbc" +
"e\x99\\\xabAr\x83&\x14\x83\\\x08U&\xbb\xca" +
"N\xbc\x97e\xfe;\xc9z\x1c\x07\xf1\xceI\x92Gz" +
"\x02\x85\xfb\xae\x89\x8d\xf6\x8a\xa6\xdfD&W\xc4\xfb\x80" +
"\x09t\xe5]8\xa8\xda\x93\xb4\x98\xf6\xffZ\xb8\xff\xe9" +
"\x99\xb1\xd1[\xe0\x7fo\x12\xf1\x0d\x11\xd5wb\xad\xdd" +
"\xdb\xcb\x00\xd4\xb7DT?\x89^\x94>\"G\xfdD" +
"\xc4\xdeX\x89.\x7fA\x8c\x9fS!\x1bOX5\xf8" +
"\x00@\xb6\x96\x12D\x9a'\xac\x84\x97\xb0d\x1c\x04\xc8" +
"\xa6\x88>=^\xa07\xe1\x00@\xb6\x91\xe8\xb3\xd1\xef" +
"\xc8\x83\xd7\xa8\x8a\x15\x81{\xd1,,\xd7\x8dq\xab\xbe" +
"\xe0\x89\x0b\x1d\x82\xcc\x8aE\xb8?\x16_\xbb\x17\xc7\xea" +
"\xe0p\xec\x84\xcc\xcaR\x88\xe7\xd1\x0e\xc79W1\xfd" +
"\x9d\xc0\x1cY?\xf8\xbc\xd8\xf3k\x89\xd80\xe0Xl" +
"N\x16\x18C}\xce\x9f?\xdd\x1d3\xc6w\x06\x01\xd4" +
"\xd5\"\xaa\xc3\x02\x07(\xb3\xbf\x9c\xd7\xd0aK-\xb6" +
"\xae\xc2$#7\x125\xc5\xd4\x16\xe6\xec~,SA" +
"\xbe\xd4b\xad\xeb*,\xce\x10\xbcv\x80\xa4\x9b\xf9\x8b" +
"\x9e9\xc6\xa9,og\x83Y3\xb7\x969c^\x81" +
"\xaa^*{\xa3\xa7\x8e\xf0\xa1\xb27\xfeP\xe9\xc3\xda" +
":r\xf0\xb2\x88\xea\xe6\x18\xac\x8d\x8cF\x1d\xf5\xf8\xa5" +
"\xc4\xffL\xf6\xffJ\x8fuTHKWRd\x86\x7f" +
"D\xf4\x15\x07\xfdW\xda\x13DO\xd0W9\x1c\x83\x10" +
"k0\xf6'&t\x88\xe0o\xfe_\x01\x00\x00\xff\xff" +
"\x86\xbe\xf5t"
const schema_db8274f9144abc7e = "x\xda\xccZ{\x90\x15ev?\xa7\xfb\xde\xe9\x19`" +
"\xb8\xb7\xed\xb1\x18X`\x041+\xac\x10u\xd6\xc4\x9d" +
"$;O\xd8\x19\x16az\xee\x0c\xba#\xa6\xec\xb9\xf7" +
"\x9b\x99\x86\xbe\xdd\x97~\x00CpA\x0a\xa2L`\x05" +
"\x17R\xe0\xe2\x16\xe0\x12\x1fq\xb3\xe2be5jI" +
"\xb2\x1b$\xabQ6\x98\x92\x8dVT\xb0Rk\xad\xe5" +
"\xa2\xa6,Sj\xa7N\xbf\xe7\xce0\x03k\xfe\xc8?" +
":u\xee\xe9\xefq\x1e\xbf\xf3;\xe7\xe3\xfa\x9d\x93\x9a" +
"\xb8\x1b\xd2'\xab\x01\xe4C\xe9\x0a\x97-\xf8\xd5\xc6C" +
"\xd7\xfc\xd3V\x90g \xba\xdf}vi\xcd\xa7\xf6\xd6" +
"\xff\x804/\x00\xd4\x9f\xaa\xd8\x88\xd2\x9b\x15\x02\x80t" +
"\xb6\xe2\xbf\x00\xdd\xf4\x1f\xbc\xf6V\xe9-a\x1b\x883" +
"\x92\xca\x1c)?',E\xe9\xb4@\xca/\x0b\xeb\x01" +
"\xdd?-\xber\xe4\x8f\xf6\xfd\x92\x94\xb9X\x19\xb0\xfe" +
"\xa6\xca\x8d(uT\x92\xe6\xe2\xca\x15\x80\xeeG{k" +
"\xff\xf6\xf0\xbf\xbe\xb8\x1d\xc4\xaf\"\x04{\xdfQ\xf9k" +
"\x04\x94\xd6V\xfe\x04\xd0\xfd\xb7\xeb7\x9d\xbf\xf3\xa3=" +
"\xf7\x8e\xdc7Ezb\xd50J\xf3\xab\x04\xe0\xdd\x07" +
"o\xaf\xf9\x17<\xf4\xc9\x1e\x10\xaf\xa5e\x90~NW" +
"M\xe2\x00\xa5YU\x8d\x80\xee+\xd7=\xfb\xcc\xee\x9f" +
"\xde\xf3\x03\x90\xbf\x8a\x08\xfe\xf7\x7fV\xf5?\xb4\x8f\xec" +
")\\\xf8\xd1\xd7R?~\xe5\x8a\x1fz\x0a\xee\xd1\xd3" +
"\xb7>\xb9\xfb\xa7W\xbd\x07=\x9c\x80)\x80\xfa\xa1*" +
"\x93t\xb7W\x91-\xf6\xbe\xfe\xdc\xf2\xe2\x9e\x07\x8e\xf8" +
"\x87\xf6\xd6b\x938\x0eR\xee\xb6\x8eO\x8a=\x0f\xe5" +
"\x1e\x0a\xae\x93\xa6\x9f\xbe3\xe9C\x04\xac/N\xaaC" +
"@\xf7\xa6_\xbf\xbb\xe2\x96'\xfb\x1f\x0e4\xbc\x93\xee" +
"\x98\xbc\x91Nzt2\x1d\xe4\xc5\xf5\xd9\x9d\xcd\x7f|" +
"\xdf\xc3\xe5n\xf1\xd6:5y\x18\xa5\xb7'\xd3\x9fo" +
"N\xbe\x95\xd6\x1b\xfe\xc6s+?\xfaK\xeb1\x90\x17" +
"b\xca\xfd\xf9\x8es\xeb\xe6?\xda\x7f\xd2;7\x0fP" +
"\xdfS\xfd+:\xb7ZM\xb6\xac\xfe\x87\x05\xcb\xef;" +
"\xbf\xec\x18-\x9d\xf0\x8b\x7f\x88\xaa\xa9\x0d(M\x9fJ" +
"\xae\xb9r*i\xbfz\xdd\xca\xe7\x9f\x7fb\xe0X\xf9" +
"A<\x97?5u)J\xa7<\xed_x\xdaWv" +
"\xe0\x1b/\xdc\x90\xfa\xfb\xa4#\x872\xef\xd1\xe6\xbb2" +
"\xa4p\xfbgO\xfd\xe3\xe2\x0f\xce<\x9dt\xd1\xc2," +
"G\x17o\xce\xd2\xc5{\x87\xb1\xf8FC\xd3\xf3 _" +
"\x8b\xe8\xae\xde\xb7\xc9n\xdf\xbf\xcb\x85\x1e\x14\x90\x03\xa8" +
"W\xb3\x1bi1'K\x016\xeb\xfd\x96j\xfd\x83\xad" +
"/\x94E\xa3\xb7\xeb\xe9\xecR\x94\xde\xcd\xd2\xd1\xde\xce" +
"\xfe\x04\xf0\x93\xc7\xee\xd9\xddq\xae\xed\xa4<\x03S\xe5" +
"\x97\xbeK\xdc\x88\xd2\x1e\x91\xfe\xdc%z\xfe\x89,X" +
"\xa6\xee\x07\xfa\x15\xabQ:}\x05\xfd\xf9\xf2\x15\x9e\xfa" +
"\xd2\xdb\xbf\x7f\x7f\xfa\xdd\xef\x9f,7)\x85x\xfdY" +
"\xc9D\xe9}\x89\xfe\xfc\x8d4\x8d\x07tg<\xf1'" +
"\x7f\xd7R8\xfb\xcb1\xb2HR\xa7}(9\xd3\xe8" +
"\xaf\xb5\xd3\xe8\x8e\xe7\x16\x1e\xfb\x8b\xdf\xec:}&\x19" +
")/O\xf3B\xf6\xedid\xb0{\xbe6\xb4q\xf9" +
"5\xc3\xaf\x95;\xc8\xd3\xc4\xdaa\x94\xa6\xd7z\xee\xac" +
"\xa5\xe5\xb8w\x95\xe9[\xfe\xfd\x9bo$\x82v{\xed" +
";\x08)w\xf9\xca\xdbWW\xddu\xee\\r\xa3\xa1" +
"Z\xdfu\xb5\xb4\xd1q\xf1~\xe9\xd9\xc3\x7fs\x9e6" +
"\x12\xca\xcd\xfd\xe3\xda^\x94N\xd4z\xe6\xa9}\x98\x83" +
"D\xf2\x8c\x158O\x7f\xa5\x01\xa5S_\xf1\x02\xe7+" +
"t\xae\x9b\xeelf\xabn\xbe\xed=\x10g\xf0#\xb0" +
"\"=\xb3\x01\xa5+gz\x99>\xf3\x1e\x94\xe6\xcf\x12" +
"\x00\xdc\xef\x0d\xf4\x9e\xba\xd0z\xf8w\xe5\x8b\xfb\x880" +
"\xab\x01\xa59\xa4W?k\x96\xe7\x9f\xfa\x1b\xfe\xea\xfd" +
"}\x0f\xb5^\x18\xb5\xba:\xbb\x05\xa5\xa1\xd9t\x0eg" +
"\xf6\xb7\xa4\xa3\xb3\xbd\xc5\xbf\xdb\xb6\xe2\x1bsO|\x98" +
"\xb4\xc4\xae\xd9\x94\xbe\xd2\xe1\xd9d\x89\xfe\x9b\x7f\xfb\xad" +
"k\xbe\xf7\xcf\x1f\x96\xf9\xcfS<1{\x01J\xa7\xbd" +
"\x15_&\xe5\x0f\x96\xfc\xf0\xcc\x8c\xcc\x8c\x8f\xcb\x0eJ" +
"\x98Z\x7fa\xf6j\x94\xd2u\x9e\xa3\xeaN\xd2Ao" +
"{\xe7\x81\xf5\x8d?\xf8\xf8\x13\xba\x17_\x06t\xff}" +
"U/JUsh\xe5\xf4\x1c\xca\xa5e\x8f\x9f\xfd\xe6" +
"\xe0\xbe\x17?\x1d\x13\xba\x8f\xcd\xd9\x8a\xd2/<\xed\x13" +
"s\x08\xae\xfeZ8xn\xcb\x7f\xfe\xf9g\xc9[=" +
":\xf7\x1d\xba\xd5ss\xe9V\x9b>8\xd0~\xdf\xaa" +
"\xc7\xbfH*\xbc9w+\xa5\xe6\xa7\x9eB\x94\x8cc" +
"E\xda\xf4\xab[P\x9a\x7f5\xedw\xcd\xd5\xa4m;" +
"\xba\xce4\xb3\x94\xca\xffa\xf8g~Q^)\xe9\xa5" +
"\x86f\xc7\x1ed\xba\xad\xe6\x15\x9bu\xb1F\xabd\xe8" +
"\x16\xebD\x94\xb3|\x0a \x85\x00\xa2\xb2\x1a@\xbe\x93" +
"GY\xe3PD\xac!\xb8\x16U\x12\x0e\xf2(\xdb\x1c" +
"\x8a\x1cWC\x88 \xae\x9d\x0b k<\xca\x1b8D" +
"\xbe\x86\xf0Nt\xee\x07\x907\xf0(o\xe3\xd0-1" +
"\xb3\xa8\xe8L\x87\x8c\xbd\xd84q\x0ap8\x05\xd05" +
"\x99m\x0e)}\x1adXB,\xac^oc5p" +
"X\x0d\xe8\x0e\x1a\x8ei\xf5\xe86\xaaZ\x17\xeb7\x99" +
"\x85\x83X\x01\x1cV\x8c\x7f\xbdVC\xd7Y\xde\xce9" +
"\xf9<\xb3,\x00\xbaYet\xb3\xf9\x0f\x00\xc8\xd7\xf1" +
"(\xdf\x9c\xb8\xd9Mt\xb3\xaf\xf3(7q\xe8Z\xcc" +
"\\\xc7\xcce\x06\xe6\x15[5\xf4\xe5\x0a_d\xd1\xb1" +
"\xf3\x9a\xcat\xbb\xd5\x80\x8c\xde\xaf\x0e`6N\x05@" +
"\xcc\x8e\x7f\xb0\xc5\x1bT\xcbV\xf5\x81nO\xde\xd8i" +
"hj~\x88N7\xc5\xb3\xe4\xac\x06ZC\xbc\xb2\x17" +
"\x009Ql\x01hT\x07t\xc3dnA\xb5\xf2t" +
")\xe0\xf3\xf6\xe6>ES\xf4<\x8b6\xaa\x18\xbd\x91" +
"\xbfA\xce\xbb\xc7\"%\xe1\xedy\x9d\x8a\xa9\xf0EK" +
"\x9e\x12\xd9cq/\x80\xdc\xc6\xa3\xdc\x99\xb0\xc7-K" +
"\x01\xe4e<\xca\xb7%<\xdd\xd3\x02 w\xf2(\xaf" +
"\xe2\xd05Lu@\xd5[\x19\xf0f\xd2a\x96\xad+" +
"E\x06\x00\xa1\xc16\x1b%2\xa2\x85\xd9\x18\xa5\xcb," +
"\x95\x1e}\x81v\xa6i\xc6\xad\x86\xa9\x15V\xf8\xfb\x18" +
"dm\xcf\x95\xd1g\xc2\x18\x9e\xf7\x9cC\xf7V\xf3l" +
"\x91c1\xff;\xc7\xf4\x1c9\xaf\x8bY\x8ef[\x00" +
"r*\xba~u\x03\x80\\\xc9\xa3\\\xc3a\xa3\xe9)" +
"`6\x06\xf5\xb2\xa3NdkG7\xd9\x80j\xd9\xcc" +
"\xf4\xc5\xf3\x1a\xc9\xe0E+\xb9!\xc5_\x96Gy&" +
"\x87\xee\x80\xa9\xe4Y'3Q5\x0a\xcb\x15\xdd\xc8\xf1" +
",\x8fi\xe00=~$-QT\x8d\x15\xfc\xdb-" +
"\xca\xd7y\xff\xa7\xec\x9d\xe2\xba~\xfa\xf6\xc6\xe9[\x8d" +
"_\xb8A\xfen\x8c\xf3\xb7\x9a\xfb\xdc\x1d\x9d\xc0\xd5\xfc" +
"gn\x90\xc2\x94\x116\x8f\xf2\x16\xca\x08\xa7D6\xb5" +
"\x807L\xcc\xc6(\x19X\x87\x15\x06\xc8\xd2:4\xb2" +
"<\x19\x1a\xb3a\xb5\xf7\x15\x84\x821\x88\xd9\x98\xca\x04" +
"\x9f\x99l\x1d3-\xd6\x09\x19\xd3\xd80\x84\xd9\xb8\xea" +
"\x97Y}\xea\xe5Z=tt\xf4\xd5\xf8\xdf\x9b,\xef" +
"CF\xf0yg\x9d\xef\xb4\x9a\xc8iw\xcd\x8d\x01-" +
"J\x92\xbb\xfb\x00\xe4-<\xca;\x13I\xb2\x83,\x7f" +
"/\x8f\xf2^\x0eE>\xc0\xc3=\x94N\xbby\x94\x0f" +
"r(\xa6R5Df\xc5\x03\x94N{y\x94\x0fq" +
"#a\x8f\xadc\xba\xdd\xa6\x0e\x80\xc0\xacXJGl" +
"S\x07\x18\xf0\xd6\x97M\xb8\xca\x09\xeca\xf4Y\x86\xc6" +
"l\xd6\xc6\xf2\x9aB\xb9\xb3\x8e\xf9\xbf\x07\xc8:VT" +
"\xd3\xb5\xa7\xf0(\xd7\x12\xe2\xd3\xaf\xcc&l\xa0cD" +
"\\z\xe2\xbco\xa5\xff\x06\xbbt\x06\xab\x98\x01\x84\xd7" +
"F\x9b\x1d\xa0\xcd\xf6\xf3(\xff(\xe1\x8d\xc3&uT" +
"<\xca\x8fs\x88\x813\x1e=\x02 ?\xce\xa3\xfc3" +
"r\x06\xe7;\xe3\xa9\x05\x00\xf2\x13<\xca/\x913x" +
"\xdf\x19\xa7(+_\xe2Q~\x9dC1\x9d\xaa\xc14" +
"\x80\xf8\x1a9\xf8\x0c\x8f\xf2[\x17\x03\xbc\xbcf8\x85" +
"~M\x81:\x93\x15:\xda\"\xb9\xee\x14;M\xb6N" +
"E\xc3\xb1\x9am\x9b\x15\x85\x92m\x85\xb5+c+\x03" +
"\x16N\x05\xec\xe4\x11\xb31\x1b\x05$a\xb4&\x9a\xac" +
"\xb0\x92\x99\x96\xca\x1bzT~T\xddf\xba\xbdL\x01" +
"\xa1\x8fi\x91t\x1cx\xea\x0a\x92\x8cR,\xc0\x0b#" +
"\x86T\x1c\xa0J0\xd3u\x03#.&\xdb4\xf1(" +
"/\xe3p\x16~Ab\xb2cG\x17\x80\xdc\xce\xa3\xdc" +
"\xcd\xe1,\xees\x12\x93%\xe5\xde\xb8 d\x06m\xbb" +
"\x84\xd9\x98\xa5\x06\xce^\xcf\xfa,#\xbf\x86\x01\x12\xae" +
"F\x94)\xf8u0\xc0y\xe0\xb5\x02f\xe36\xb3," +
"R\xf8\x8b\x15\xf9F\xa2\x14\x86\xe9\xd5\xd0\xb8\xa2\xdd\x18" +
"_\"\x8c\x8e\x8e\xde\xf8\x06\"\xd7\xe4_K\xee\x8b\xcf" +
"_\x97W\x1c\x8b\x8dd'\xcd\xfd6\xf0\xcc\x8c\x00\xd9" +
"\x1a4\x1c\xad\xd0\xc5@\xb0\xcd!D\xe0\x10\xc7\x87\xe9" +
"6\xa3=ax?\x8c\xc7\xae\xbcQ\xe1\xedM\x16\xde" +
"\xc0\xfc=d\xfen\x1e\xe5\x12\x87\xaeF@\xa7\xb7\x1b" +
"\x1e\x0e\x84\xc7\xf5\x85\x9d\x86\x17\x9c\x02p(\x00\xbaN" +
"\xc9\xb2M\xa6\x14\x01\xa3h#\xfd\xa9\x97Q\xcf\xcap" +
"\xb5S\xc9xy\xff\xff\x89=\\>\x0d\xf0\xc1k\x04" +
"\x098\x92\xa8\xc9\xf9\xe0k\xf4>o5t\xe1\xb2\x89" +
"^\x80`~\x19Z\x14\xd0\x0a\xe2\xa0a}\x9eO\xf5" +
"d\x1e\x8f\xf2\xf5\xc9\xfa\xbc\x90Lt-\x8f\xf2\xd79" +
"\x14\x98I\xa56\x1a\x16\xf8\x9bn\xb6|R\x8b\xd9x" +
"\x144\xf1q\x12|_5\xf4Qa87N\x97\xc8" +
"\x85\x1d7&\xfc\x1a\xba\xf0\x96\xbe\xd8\xaf\xc2\x1a6\x14" +
"z\xa9\x8e\x15\x155F\xa3\xc0\xb9\xcd |;\xd6\x19" +
"\x97\x17\x07\xfc\xc1g\x0f\x8d\xbe\xb7\xe8\x90\x89\x02<\x9c" +
"\xa8\xb5\xe1!wP\x9b\xb1\x93Gy\x7f\xe2\x90\xfbZ" +
"\x12\xb56,\xc0\x07\xc8\xc1\x07y\x94\x1f\xe1\x10\x83\xfa" +
"{\x94 \xff\x11\x1e\xe5\xe3\x9c\x07\xd8\xed\xcd\xad\x86\x8e" +
"\xc1!,\x80\xa8\xd5\x18d\x8ai\xf71\x05\xed\x0e\xdd" +
"f\xe6:\x05\xb5\x10\x126\xdbj\x91\x19\x8e\x1dAD" +
"Q\xd9\xe0q3,\xb4\xfb_\x09\x8ama\x15pX" +
"E\x19i1\xb3\xd5d\x05$o(Z\xa7\xc2\xdb\x83" +
"\x97b\xa0\x91 \x9e\x19\xc3<\xc4\xec6\xf1(\xdfK" +
"P\x82\x89\x81\x94\xb8}5p\x1e\x92\xd0\x9d\xd7\xb6\xc4" +
"\\\xcf+\x88\xe9\xb2n\xcd+\x88\x15Dn\xc8:\xdb" +
"x\x94ws\xe1\xd1\xda\x0dh\xf43\xb4\xdc\xd5A7" +
"\xb4\x99PSe\xf1}\x03Z\xa5\xa2\xa1w{\x86\xc2" +
"\xd8Ry\xa3X2)\x94UC\x97\x1dESy{" +
"(\xfap\\[\x10$\xf9\xa9\xbc\xa2T\xe79\x8b\x8c" +
"qsh\x0ci\x08\x97\x02\xe46 \x8f\xb9m\x18\x87" +
"\x8bt7\xb6\x00\xe46\x91\xfc^\x8c#F\xda\x8e3" +
"\x00r[H\xbe\x13\xa3&V\xda\x81\x8f\x01\xe4v\x92" +
"x?\xc6TA\xda\xe7-\xbf\x97\xe4\x870f\x0b\xd2" +
"\x83\xb8\x00 \xb7\x9f\xe4\xc7I^\xc1y\x96\x94\x8e\xe1" +
"j\x80\xdc\x13$\x7f\x96\xe4B\xba\x86:v\xe9i4" +
"\x01r?#\xf9\xcfI^Y[\x83\x95\x00\xd2\x09O" +
"\xfe\x02\xc9_\"y\xd5\xf4\x1a\xac\x02\x90N\xe1V\x80" +
"\xdc\x8b$?C\xf2IX\x83\x93\x00\xa4\xd3\xf8\x00@" +
"\xee\x0c\xc9\xdf\"\xf9\xe4\x8a\x1a\x9c\x0c \xbd\xe9\x9d\xe7" +
"u\x92\x9f'\xf9\x94T\x0dN\x01\x90\xde\xc6#\x00\xb9" +
"\xf3$\xff\x1d\xc9\xab\x85\x1a\xac\x06\x90\xde\xf7\xee\xf5[" +
"\x92Wr\x11\x0ev\x14\x92pLa\xa8\xc6t\x847" +
"\xac(\x14X\xd0\xdc\xa2_+:\x8d\x0cu\xb7\x98\x89" +
"\xc7\xd0\x80\x98\x01tK\x86\xa1-\x1f\x09\xf3\x131\xa2" +
" \x8c c\xe8\x1d\x85(/\xfd\xe0[f@]^" +
"\xd1:J1G\xb2\x9a\x1d\xdbpJPWPlV" +
"\x88\x0a\xb5\xe9\xe8KL\xa3\xd8\x8d\xcc,\xaa\xba\xa2A" +
"\xf4\xcbx\xb1\x98q\x1c\xb5\x10\xad=.\xb1s\xfb\x99" +
"b;&\xb3\xe8j\x17\xa9\xb8\\yD\xd7\x95\x1a\xba" +
"\x95\x81\xb2\x19\xc5\x82\xb8<Dh\xb7\xf0\xc6\xb8:d" +
"\x92YX\xb7N\xd1\x1cv)dp\xdc\xae\xa7\xab\xd1" +
"\xef\x9a&j\x8e\xc3\x89\xda\xc4l\xbe\xa7\xac\xf2\xfa\xf5" +
"p\xd4@\xa6%\xbeltW3\x18\xd2\xb4sq\xcd" +
"\x0b\xbd\xd5\x1f4\xbfPGk'\xe2&\x1ay\x06q" +
"s\xa9\x96\x18`\xb6\xffW\x87\xdeo\x10=\x10\x94\xa2" +
"\xf5{~\xdd\xc5\xac\xcc\xa5X1\x1ebN\\\xbf\xdb" +
"\xbb\xbb;\xe39\x08\xef\x83\xff\xf5\x11\xde5c\x17@" +
"\xae\x89\x12w\x19F6\x94:<\xdci'q7\xc6" +
"\xacW\x92=|\xe9$\xf9*\x8c\xfb\"\xe9;\x1e." +
"\xac\"\xf9\xa0\x87w\xcd>\xde1o\xf9\x02\xc9K\x1e" +
"\xde\xa1\x8fwEo}\x8d\xe4\x1b\x92x\xe7\xe0\xf0\x08" +
"\xf8\x15x\x1f\xef\xee\xf6pj\x1b\xc9w{x\x97\xf2" +
"\xf1n\x17>\x09\x90\xdbM\xf2\x83\x1e\xde\xa5}\xbc;" +
"\x80\xcf\x00\xe4\x0e\x92\xfc\x11\x0f\xef*|\xbc;\xea\xe9" +
"?\x12\xe1\xec\xe4\x16\x1f\xef\x8ey\xf8\x18\xe1\xac\xeb\x98" +
"Z\xce6U\x1dp \xce\x8d|\xe9\xdb\x8c\x95\x9a!" +
"\xa3\xa9\xebXT\x8b\x0a\xaa\xa2\xb59\x8a\x06u9[" +
"\xc9\xaf\x89\xa9\xbdf\xb5+z\xc1\xc2Ae\x0d\xa3\x0a" +
"&$k\xbd\xadY+\x99\xa9\xf6\x03\xc6\xcd@\xc4}" +
"2\x9d\x86QN\x89<N\xc9L\x1f\xfc\xa2\xdf\x8a\xca" +
"\x86\x8e\x82\xc6Z1d@\xbc\x1eWP\x95~1t" +
"\x1d}Z\xd2\xad\xd6\x8d\xe4\x1b\xa5\xa0\xbd\x08yKw" +
"c\x19!a\x1bJ,o\xb7\x1a\xa8\xdb\xaa\xee\xb0Q" +
"\x0b\xe4\x07\x1d}\x0d+,F=o\x14T}\x00F" +
"\xf55\xfc\xc5\xc6O\x09\xa2\xe6e3&\xde\xef\xc4\xf9" +
"\x0d\xc0y\xd0E\xb4Cl\x88\xa7\x03\x8dy\xef\xabF" +
"\x93)V\xa2\xb1\x1dg\xb7`\\\xea'\x99?\x09H" +
"\x03Do]\x18\xbe\x17\x88\xc76\x02'>*`\xfc" +
"\xcc\x82\xe1\xab\x8a\xf8\xa0\x09\x9c\xb8O@.z\x84\xc4" +
"\xf0\x01Q\xdc1\x0c\x9c\xb8]@>z\x17\xc4p\x18" +
"\x7f\xc3\xd0$\x04N\xbcK\xc0T\xf4J\x8a\xe1(_" +
"\\K\xdcJ\x150\x1d=9b\xf8^$\xde\xb1\x15" +
"8\xb1Gp\xc3\x16\x0a\x1a\xfd{4\xa1\x1b\"\x06\xd4" +
"y\x98\xd1\x84n8\xc1\xc2\xb0\xd5\x02hB7\x9c\xc3" +
"\xf0\x17\x1b\xc4xZ\xe1|\x172y\xc5fM\xd4\xbb" +
"\xfa\xc0\x8e\x01\xb2C\x13&\xe7\xa6\xfc\xc5\x1a\xa6\xb1\x89" +
"wKL\x0e\xa3\xc1\xd7p\xcc\x0d\xa3&u\xd7cI" +
"\xde\x1d\xccZ\x0el\x0d&5\xc7\x13\xb3\x96cD\xc6" +
"\x8f\xf3(\xbf\xca\xc5\x8c\"\x0c\xf8p\xd4\x88\x86\x19v" +
"\xcd\xe3L\x1c\x83\xb4\x088q\xf9\xdc\xd1-\x18\x83\x1e" +
"gF\x7f)\x0b\xe2Z\x91\x1cFNM\x0c#1\xec" +
"\xd7\x85\x11\xa5%9\x9a\x9c:A\xf3\x97\xec>\xbdZ" +
"\x97\xf2\xe25|z\xc5\xf0\x99\\\x14)\xee\xaa\x057" +
"\xecP1,\x94P\xe6\xb2\xcbl\xd3\xbbX\xdd\x97\xa9" +
"\xe4c\x04\x88\xbfO\x86\xa2\xd5\xbfP\xb4\xee\xea\xc4\xdc" +
"O3\x82\x0e3\xb3<\xd9$\x8cc+\xff\xc0!\xa5" +
"\xcf\xd0\xc7\xb4\xfeU\xd1\xfa\xa7\xe7&\xe6ra\xfc\xbd" +
"F\xc2Wy\x94\xdfH\xf4}g\x97\x02\xc8\xaf\xf3(" +
"\x7f\x1c\xbfC]\xa0@\xfd\x98\xc7\xae\x04\x7f\x17?'" +
"\xc5\xcf\x88\xe5&\xabY\x1a\xef\x07\xc8UR\xf5\xa8\xf1" +
"\xaaY\xca\xaff\"\xf6\x01\xe4\xb2$\x9f\x99d\xef\xd3" +
"\xb1\x17 WK\xf2y\x18\xb4\xeb\xe1\x1b\x96c\xc6\xc8" +
"\xaf\x19\x03\xcbT}LJ\x18>\x8c\xa1Mx\xea\x98" +
"T\x14F\x82oG[\x82$G3)df\x8eR" +
"\xbc\x80V4\xeb\xb9\x8c\x99\xf18\xee\xc8\x05\xc9\xe7\xe7" +
"^@4\x12\x93\x82#\x89!Z\xe8\x0c\xf9\x99`8" +
"ug\xc2\x19w\xf4\x01\xc8\xabx\x94\x079\x0f\xa0\x8c" +
"\x9eRAA\x9b-1\xd9Z\x87\x09z~(\xee\x98" +
"\xa9g\xcc[=X\"\xb6\xbe\xc4d\x8dk\x1d\x96T" +
"\x08\xdfH@P\x8d\xc2\xa8\xc7\x911h\xe7\xad\xac/" +
"g\xe4\xd70{\xc4\xdbQ\xd9\xfbfW\xfc@\x12=" +
"ov%\x9f7\x03X[K\x01^\xe2Q\xde\x94\x80" +
"\xb5\xa1\xe1\xb8\xdd\x1e\x9bg\xfc\xdfP\x83\xdf\xeb\x89\x8f" +
"X\xb6p)\x0c4\xfa\xa7Ge\xf9_\xf5e\x9f\x07" +
"\xc2\xe7\x97\x09O\x10\xbdh_\xe6H\x0d\"\x10\xc2\xc4" +
"\xbfX\xa1M\xb8`\xf1\xff\x0d\x00\x00\xff\xff\xcb\x86\x19" +
"\x8c"
func init() {
schemas.Register(schema_db8274f9144abc7e,

2
vendor/modules.txt vendored
View File

@ -299,7 +299,7 @@ gopkg.in/urfave/cli.v2
gopkg.in/urfave/cli.v2/altsrc
# gopkg.in/yaml.v2 v2.2.4
gopkg.in/yaml.v2
# zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7
# zombiezen.com/go/capnproto2 v2.18.0+incompatible
zombiezen.com/go/capnproto2
zombiezen.com/go/capnproto2/encoding/text
zombiezen.com/go/capnproto2/internal/fulfiller

View File

@ -10,8 +10,10 @@
# Please keep the list sorted.
Anapaya Systems AG
CloudFlare Inc.
Daniel Darabos <darabos.daniel@gmail.com>
Dominik Roos <domi.roos@gmail.com>
Eran Duchan <pavius@gmail.com>
Evan Shaw <edsrzf@gmail.com>
Google Inc.
@ -20,8 +22,11 @@ James McKaskill <james@foobar.co.nz>
Jason E. Aten <j.e.aten@gmail.com>
Johan Hernandez <im@bithavoc.io>
Joonsung Lee <joonsung@devsisters.com>
Kiwi.com s.r.o.
Lev Radomislensky <lev.radomislensky@gmail.com>
Peter Waldschmidt <peterw@gnoso.com>
Tiit Pikma <pikma@hot.ee>
Tom Thorogood <me+github@tomthorogood.co.uk>
TJ Holowaychuk <tj@apex.sh>
William Laffin <william.laffin@gmail.com>
Colin Arnott <colin@urandom.co.uk>

View File

@ -1,5 +1,10 @@
# Go Cap'n Proto Release Notes
## 2.17.3
- Clear read limits for `const` messages in schemas.
([#131](https://github.com/capnproto/go-capnproto2/pull/131))
## 2.17.0
- Add `capnp.Canonicalize` function that implements the

View File

@ -18,6 +18,7 @@
Alan Braithwaite <alan@cloudflare.com>
Albert Strasheim <albert@cloudflare.com>
Daniel Darabos <darabos.daniel@gmail.com>
Dominik Roos <domi.roos@gmail.com>
Eran Duchan <pavius@gmail.com>
Evan Shaw <edsrzf@gmail.com>
Ian Denhardt <ian@zenhack.net>
@ -26,8 +27,13 @@ Jason E. Aten <j.e.aten@gmail.com>
Johan Hernandez <im@bithavoc.io>
Joonsung Lee <joonsung@devsisters.com>
Lev Radomislensky <lev.radomislensky@gmail.com>
Lukas Vogel <vogel@anapaya.net> <lukedirtwalker@gmail.com>
Martin Sucha <martin.sucha@kiwi.com>
Peter Waldschmidt <peterw@gnoso.com>
Ross Light <light@google.com> <ross@zombiezen.com>
Stephen Shirley <kormat@anapaya.net> <kormat@gmail.com>
Tiit Pikma <pikma@hot.ee>
Tom Thorogood <me+github@tomthorogood.co.uk>
TJ Holowaychuk <tj@apex.sh>
William Laffin <william.laffin@gmail.com>
Colin Arnott <colin@urandom.co.uk>

View File

@ -4,17 +4,17 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_go",
sha256 = "8b68d0630d63d95dacc0016c3bb4b76154fe34fca93efd65d1c366de3fcb4294",
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.12.1/rules_go-0.12.1.tar.gz"],
sha256 = "86ae934bd4c43b99893fc64be9d9fc684b81461581df7ea8fc291c816f5ee8c5",
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.18.3/rules_go-0.18.3.tar.gz"],
)
http_archive(
name = "bazel_gazelle",
sha256 = "ddedc7aaeb61f2654d7d7d4fd7940052ea992ccdb031b8f9797ed143ac7e8d43",
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz"],
sha256 = "3c681998538231a2d24d0c07ed5a7658cb72bfb5fd4bf9911157c0e9ac6a2687",
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.17.0/bazel-gazelle-0.17.0.tar.gz"],
)
load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
go_rules_dependencies()

View File

@ -200,6 +200,9 @@ func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
}
sz := hdr.structSize()
n := int32(hdr.offset())
if n < 0 {
return List{}, errListSize
}
// TODO(light): check that this has the same end address
if tsize, ok := sz.totalSize().times(n); !ok {
return List{}, errOverflow
@ -214,11 +217,15 @@ func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
flags: isCompositeList,
}, nil
}
n := val.numListElements()
if n < 0 {
return List{}, errListSize
}
if lt == bit1List {
return List{
seg: s,
off: addr,
length: val.numListElements(),
length: n,
flags: isBitList,
}, nil
}
@ -226,7 +233,7 @@ func (s *Segment) readListPtr(base Address, val rawPointer) (List, error) {
seg: s,
size: val.elementSize(),
off: addr,
length: val.numListElements(),
length: n,
}, nil
}

View File

@ -81,10 +81,10 @@ Structs
For the following schema:
struct Foo @0x8423424e9b01c0af {
num @0 :UInt32;
bar @1 :Foo;
}
struct Foo @0x8423424e9b01c0af {
num @0 :UInt32;
bar @1 :Foo;
}
capnpc-go will generate:
@ -168,9 +168,9 @@ For each group a typedef is created with a different method set for just the
groups fields:
struct Foo {
group :Group {
field @0 :Bool;
}
group :Group {
field @0 :Bool;
}
}
generates the following:
@ -194,10 +194,10 @@ Named unions are treated as a group with an inner unnamed union. Unnamed
unions generate an enum Type_Which and a corresponding Which() function:
struct Foo {
union {
a @0 :Bool;
b @1 :Bool;
}
union {
a @0 :Bool;
b @1 :Bool;
}
}
generates the following:
@ -225,10 +225,10 @@ For voids in unions, there is a void setter that just sets the discriminator.
For example:
struct Foo {
union {
a @0 :Void;
b @1 :Void;
}
union {
a @0 :Void;
b @1 :Void;
}
}
generates the following:
@ -241,14 +241,14 @@ the discriminator. This must be called before the group getter can be
used to set values. For example:
struct Foo {
union {
a :group {
v :Bool
}
b :group {
v :Bool
}
}
union {
a :group {
v :Bool
}
b :group {
v :Bool
}
}
}
and in usage:
@ -293,10 +293,10 @@ but the tags can be customized with a $Go.tag or $Go.notag annotation.
For example:
enum ElementSize {
empty @0 $Go.tag("void");
bit @1 $Go.tag("1 bit");
byte @2 $Go.tag("8 bits");
inlineComposite @7 $Go.notag;
empty @0 $Go.tag("void");
bit @1 $Go.tag("1 bit");
byte @2 $Go.tag("8 bits");
inlineComposite @7 $Go.notag;
}
In the generated go file:

View File

@ -42,14 +42,14 @@ func MarshalList(typeID uint64, l capnp.List) (string, error) {
// An Encoder writes the text format of Cap'n Proto messages to an output stream.
type Encoder struct {
w errWriter
w indentWriter
tmp []byte
nodes nodemap.Map
}
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: errWriter{w: w}}
return &Encoder{w: indentWriter{w: w}}
}
// UseRegistry changes the registry that the encoder consults for
@ -58,6 +58,12 @@ func (enc *Encoder) UseRegistry(reg *schemas.Registry) {
enc.nodes.UseRegistry(reg)
}
// SetIndent sets string to indent each level with.
// An empty string disables indentation.
func (enc *Encoder) SetIndent(indent string) {
enc.w.indentPerLevel = indent
}
// Encode writes the text representation of s to the stream.
func (enc *Encoder) Encode(typeID uint64, s capnp.Struct) error {
if enc.w.err != nil {
@ -133,8 +139,14 @@ func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
if n.StructNode().DiscriminantCount() > 0 {
discriminant = s.Uint16(capnp.DataOffset(n.StructNode().DiscriminantOffset() * 2))
}
enc.w.WriteByte('(')
fields := codeOrderFields(n.StructNode())
if len(fields) == 0 {
enc.w.WriteString("()")
return nil
}
enc.w.WriteByte('(')
enc.w.Indent()
enc.w.NewLine()
first := true
for _, f := range fields {
if !(f.Which() == schema.Field_Which_slot || f.Which() == schema.Field_Which_group) {
@ -144,7 +156,8 @@ func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
continue
}
if !first {
enc.w.WriteString(", ")
enc.w.WriteByte(',')
enc.w.NewLineOrSpace()
}
first = false
name, err := f.NameBytes()
@ -164,6 +177,8 @@ func (enc *Encoder) marshalStruct(typeID uint64, s capnp.Struct) error {
}
}
}
enc.w.NewLine()
enc.w.Unindent()
enc.w.WriteByte(')')
return nil
}
@ -298,101 +313,154 @@ func codeOrderFields(s schema.Node_structNode) []schema.Field {
}
func (enc *Encoder) marshalList(elem schema.Type, l capnp.List) error {
switch elem.Which() {
case schema.Type_Which_void:
enc.w.WriteString(capnp.VoidList{List: l}.String())
case schema.Type_Which_bool:
enc.w.WriteString(capnp.BitList{List: l}.String())
case schema.Type_Which_int8:
enc.w.WriteString(capnp.Int8List{List: l}.String())
case schema.Type_Which_int16:
enc.w.WriteString(capnp.Int16List{List: l}.String())
case schema.Type_Which_int32:
enc.w.WriteString(capnp.Int32List{List: l}.String())
case schema.Type_Which_int64:
enc.w.WriteString(capnp.Int64List{List: l}.String())
case schema.Type_Which_uint8:
enc.w.WriteString(capnp.UInt8List{List: l}.String())
case schema.Type_Which_uint16:
enc.w.WriteString(capnp.UInt16List{List: l}.String())
case schema.Type_Which_uint32:
enc.w.WriteString(capnp.UInt32List{List: l}.String())
case schema.Type_Which_uint64:
enc.w.WriteString(capnp.UInt64List{List: l}.String())
case schema.Type_Which_float32:
enc.w.WriteString(capnp.Float32List{List: l}.String())
case schema.Type_Which_float64:
enc.w.WriteString(capnp.Float64List{List: l}.String())
case schema.Type_Which_data:
enc.w.WriteString(capnp.DataList{List: l}.String())
case schema.Type_Which_text:
enc.w.WriteString(capnp.TextList{List: l}.String())
case schema.Type_Which_structType:
writeListItems := func(writeItem func(i int) error) error {
if l.Len() == 0 {
_, err := enc.w.WriteString("[]")
return err
}
enc.w.WriteByte('[')
enc.w.Indent()
enc.w.NewLine()
for i := 0; i < l.Len(); i++ {
if i > 0 {
enc.w.WriteString(", ")
}
err := enc.marshalStruct(elem.StructType().TypeId(), l.Struct(i))
err := writeItem(i)
if err != nil {
return err
}
if i == l.Len()-1 {
enc.w.NewLine()
} else {
enc.w.WriteByte(',')
enc.w.NewLineOrSpace()
}
}
enc.w.Unindent()
enc.w.WriteByte(']')
return nil
}
writeListItemsN := func(writeItem func(i int) (int, error)) error {
return writeListItems(func(i int) error {
_, err := writeItem(i)
return err
})
}
switch elem.Which() {
case schema.Type_Which_void:
return writeListItemsN(func(_ int) (int, error) {
return enc.w.WriteString("void")
})
case schema.Type_Which_bool:
p := capnp.BitList{List: l}
return writeListItemsN(func(i int) (int, error) {
if p.At(i) {
return enc.w.WriteString("true")
} else {
return enc.w.WriteString("false")
}
})
case schema.Type_Which_int8:
p := capnp.Int8List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
})
case schema.Type_Which_int16:
p := capnp.Int16List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
})
case schema.Type_Which_int32:
p := capnp.Int32List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(int64(p.At(i)), 10))
})
case schema.Type_Which_int64:
p := capnp.Int64List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatInt(p.At(i), 10))
})
case schema.Type_Which_uint8:
p := capnp.UInt8List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
})
case schema.Type_Which_uint16:
p := capnp.UInt16List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
})
case schema.Type_Which_uint32:
p := capnp.UInt32List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(uint64(p.At(i)), 10))
})
case schema.Type_Which_uint64:
p := capnp.UInt64List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatUint(p.At(i), 10))
})
case schema.Type_Which_float32:
p := capnp.Float32List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatFloat(float64(p.At(i)), 'g', -1, 32))
})
case schema.Type_Which_float64:
p := capnp.Float64List{List: l}
return writeListItemsN(func(i int) (int, error) {
return enc.w.WriteString(strconv.FormatFloat(p.At(i), 'g', -1, 64))
})
case schema.Type_Which_data:
p := capnp.DataList{List: l}
return writeListItemsN(func(i int) (int, error) {
s, err := p.At(i)
if err != nil {
return enc.w.WriteString("<error>")
}
buf := strquote.Append(nil, s)
return enc.w.Write(buf)
})
case schema.Type_Which_text:
p := capnp.TextList{List: l}
return writeListItemsN(func(i int) (int, error) {
s, err := p.BytesAt(i)
if err != nil {
return enc.w.WriteString("<error>")
}
buf := strquote.Append(nil, s)
return enc.w.Write(buf)
})
case schema.Type_Which_structType:
return writeListItems(func(i int) error {
return enc.marshalStruct(elem.StructType().TypeId(), l.Struct(i))
})
case schema.Type_Which_list:
enc.w.WriteByte('[')
ee, err := elem.List().ElementType()
if err != nil {
return err
}
for i := 0; i < l.Len(); i++ {
if i > 0 {
enc.w.WriteString(", ")
}
return writeListItems(func(i int) error {
p, err := capnp.PointerList{List: l}.PtrAt(i)
if err != nil {
return err
}
err = enc.marshalList(ee, p.List())
if err != nil {
return err
}
}
enc.w.WriteByte(']')
return enc.marshalList(ee, p.List())
})
case schema.Type_Which_enum:
enc.w.WriteByte('[')
il := capnp.UInt16List{List: l}
typ := elem.Enum().TypeId()
// TODO(light): only search for node once
for i := 0; i < il.Len(); i++ {
if i > 0 {
enc.w.WriteString(", ")
}
enc.marshalEnum(typ, il.At(i))
}
enc.w.WriteByte(']')
return writeListItems(func(i int) error {
return enc.marshalEnum(typ, il.At(i))
})
case schema.Type_Which_interface:
enc.w.WriteByte('[')
for i := 0; i < l.Len(); i++ {
if i > 0 {
enc.w.WriteString(", ")
}
enc.w.WriteString(interfaceMarker)
}
enc.w.WriteByte(']')
return writeListItemsN(func(_ int) (int, error) {
return enc.w.WriteString(interfaceMarker)
})
case schema.Type_Which_anyPointer:
enc.w.WriteByte('[')
for i := 0; i < l.Len(); i++ {
if i > 0 {
enc.w.WriteString(", ")
}
enc.w.WriteString(anyPointerMarker)
}
enc.w.WriteByte(']')
return writeListItemsN(func(_ int) (int, error) {
return enc.w.WriteString(anyPointerMarker)
})
default:
return fmt.Errorf("unknown list type %v", elem.Which())
}
return nil
}
func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
@ -419,37 +487,96 @@ func (enc *Encoder) marshalEnum(typ uint64, val uint16) error {
return nil
}
type errWriter struct {
// indentWriter is helper for writing indented text
type indentWriter struct {
w io.Writer
err error
// indentPerLevel is a string to prepend to a line for every level of indentation.
indentPerLevel string
// current indent level
currentIndent int
// hasLineContent is true when we have written something on the current line.
hasLineContent bool
}
func (ew *errWriter) Write(p []byte) (int, error) {
if ew.err != nil {
return 0, ew.err
func (iw *indentWriter) beforeWrite() {
if iw.err != nil {
return
}
if len(iw.indentPerLevel) > 0 && !iw.hasLineContent {
iw.hasLineContent = true
for i := 0; i < iw.currentIndent; i++ {
_, err := iw.w.Write([]byte(iw.indentPerLevel))
if err != nil {
iw.err = err
return
}
}
}
}
func (iw *indentWriter) Write(p []byte) (int, error) {
iw.beforeWrite()
if iw.err != nil {
return 0, iw.err
}
var n int
n, ew.err = ew.w.Write(p)
return n, ew.err
n, iw.err = iw.w.Write(p)
return n, iw.err
}
func (ew *errWriter) WriteString(s string) (int, error) {
if ew.err != nil {
return 0, ew.err
func (iw *indentWriter) WriteString(s string) (int, error) {
iw.beforeWrite()
if iw.err != nil {
return 0, iw.err
}
var n int
n, ew.err = io.WriteString(ew.w, s)
return n, ew.err
n, iw.err = io.WriteString(iw.w, s)
return n, iw.err
}
func (ew *errWriter) WriteByte(b byte) error {
if ew.err != nil {
return ew.err
func (iw *indentWriter) WriteByte(b byte) error {
iw.beforeWrite()
if iw.err != nil {
return iw.err
}
if bw, ok := ew.w.(io.ByteWriter); ok {
ew.err = bw.WriteByte(b)
if bw, ok := iw.w.(io.ByteWriter); ok {
iw.err = bw.WriteByte(b)
} else {
_, ew.err = ew.w.Write([]byte{b})
_, iw.err = iw.w.Write([]byte{b})
}
return iw.err
}
func (iw *indentWriter) Indent() {
iw.currentIndent++
}
func (iw *indentWriter) Unindent() {
iw.currentIndent--
}
func (iw *indentWriter) NewLine() {
if len(iw.indentPerLevel) > 0 && iw.hasLineContent {
if iw.err != nil {
return
}
if bw, ok := iw.w.(io.ByteWriter); ok {
iw.err = bw.WriteByte('\n')
} else {
_, iw.err = iw.w.Write([]byte{'\n'})
}
iw.hasLineContent = false
}
}
func (iw *indentWriter) NewLineOrSpace() {
if len(iw.indentPerLevel) > 0 {
iw.NewLine()
} else {
iw.WriteByte(' ')
}
return ew.err
}

View File

@ -1,6 +0,0 @@
module "zombiezen.com/go/capnproto2"
require (
"github.com/kylelemons/godebug" v0.0.0-20170820004349-d65d576e9348
"golang.org/x/net" v0.0.0-20180218175443-cbe0f9307d01
)

8
vendor/zombiezen.com/go/capnproto2/go.sum generated vendored Normal file
View File

@ -0,0 +1,8 @@
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01 h1:po1f06KS05FvIQQA2pMuOWZAUXiy1KYdIf0ElUU2Hhc=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@ -142,6 +142,9 @@ func Unpack(dst, src []byte) ([]byte, error) {
dst = allocWords(dst, int(src[0]))
src = src[1:]
n := copy(dst[start:], src)
if n < len(dst)-start {
return dst, io.ErrUnexpectedEOF
}
src = src[n:]
}
}
@ -281,22 +284,22 @@ func (r *Reader) ReadWord(p []byte) error {
switch tag {
case zeroTag:
z, err := r.rd.ReadByte()
if err == io.EOF {
r.err = io.ErrUnexpectedEOF
return nil
} else if err != nil {
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
r.err = err
return nil
return err
}
r.zeroes = int(z)
case unpackedTag:
l, err := r.rd.ReadByte()
if err == io.EOF {
r.err = io.ErrUnexpectedEOF
return nil
} else if err != nil {
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
r.err = err
return nil
return err
}
r.literal = int(l)
}

View File

@ -360,7 +360,7 @@ func (ssa *singleSegmentArena) Allocate(sz Size, segs map[SegmentID]*Segment) (S
if hasCapacity(data, sz) {
return 0, data, nil
}
inc, err := nextAlloc(int64(len(data)), int64(maxSegmentSize()), sz)
inc, err := nextAlloc(int64(cap(data)), int64(maxSegmentSize()), sz)
if err != nil {
return 0, nil, fmt.Errorf("capnp: alloc %d bytes: %v", sz, err)
}

View File

@ -155,7 +155,7 @@ func (p Ptr) text() (b []byte, ok bool) {
// Text must be null-terminated.
return nil, false
}
return b[:len(b)-1 : len(b)], true
return b[: len(b)-1 : len(b)], true
}
// Data attempts to convert p into Data, returning nil if p is not a

View File

@ -112,7 +112,7 @@ func (r *record) read() ([]byte, error) {
}
p := packed.NewReader(bufio.NewReader(z))
r.data, r.err = ioutil.ReadAll(p)
if err != nil {
if r.err != nil {
r.data = nil
return
}