diff --git a/core/core.go b/core/core.go index 39eab621..2058a1cd 100644 --- a/core/core.go +++ b/core/core.go @@ -23,6 +23,7 @@ import ( udptpt "github.com/aperturerobotics/bifrost/transport/udp" "github.com/aperturerobotics/bifrost/transport/webrtc" wtpt "github.com/aperturerobotics/bifrost/transport/websocket" + wtpt_http "github.com/aperturerobotics/bifrost/transport/websocket/http" "github.com/aperturerobotics/controllerbus/bus" "github.com/aperturerobotics/controllerbus/controller/resolver/static" cbc "github.com/aperturerobotics/controllerbus/core" @@ -67,6 +68,7 @@ func AddFactories(b bus.Bus, sr *static.Resolver) { sr.AddFactory(udptpt.NewFactory(b)) // websocket transport sr.AddFactory(wtpt.NewFactory(b)) + sr.AddFactory(wtpt_http.NewFactory(b)) // webrtc transport sr.AddFactory(webrtc.NewFactory(b)) diff --git a/transport/websocket/http/config.go b/transport/websocket/http/config.go new file mode 100644 index 00000000..ec85bd9e --- /dev/null +++ b/transport/websocket/http/config.go @@ -0,0 +1,66 @@ +package websocket_http + +import ( + "github.com/aperturerobotics/bifrost/peer" + "github.com/aperturerobotics/bifrost/transport" + "github.com/aperturerobotics/bifrost/util/confparse" + "github.com/aperturerobotics/controllerbus/config" + "github.com/pkg/errors" +) + +// ConfigID is the string used to identify this config object. +const ConfigID = ControllerID + +// Validate validates the configuration. +// This is a cursory validation to see if the values "look correct." +func (c *Config) Validate() error { + if _, err := c.ParseTransportPeerID(); err != nil { + return errors.Wrap(err, "transport_peer_id") + } + + return nil +} + +// GetConfigID returns the unique string for this configuration type. +// This string is stored with the encoded config. +func (c *Config) GetConfigID() string { + return ConfigID +} + +// EqualsConfig checks if the other config is equal. +func (c *Config) EqualsConfig(c2 config.Config) bool { + return config.EqualsConfig(c, c2) +} + +// ParseTransportPeerID parses the node peer ID if it is not empty. +func (c *Config) ParseTransportPeerID() (peer.ID, error) { + return confparse.ParsePeerID(c.GetTransportPeerId()) +} + +// SetTransportPeerId sets the node peer ID field. +func (c *Config) SetTransportPeerId(peerID string) { + c.TransportPeerId = peerID +} + +// GetDebugVals returns the directive arguments as key/value pairs. +// This should be something like param1="test", param2="test". +// This is not necessarily unique, and is primarily intended for display. +func (c *Config) GetDebugVals() config.DebugValues { + vals := make(config.DebugValues) + if tp := c.GetTransportPeerId(); tp != "" { + vals["peer-id"] = []string{tp} + } + if hp := c.GetHttpPatterns(); len(hp) != 0 { + vals["http-patterns"] = hp + } + if hp := c.GetPeerHttpPatterns(); len(hp) != 0 { + vals["peer-http-patterns"] = hp + } + return vals +} + +// _ is a type assertion +var _ transport.Config = ((*Config)(nil)) + +// _ is a type assertion +var _ config.Debuggable = ((*Config)(nil)) diff --git a/transport/websocket/http/factory.go b/transport/websocket/http/factory.go new file mode 100644 index 00000000..b0f13107 --- /dev/null +++ b/transport/websocket/http/factory.go @@ -0,0 +1,56 @@ +package websocket_http + +import ( + "context" + + "github.com/aperturerobotics/controllerbus/bus" + "github.com/aperturerobotics/controllerbus/config" + "github.com/aperturerobotics/controllerbus/controller" + "github.com/blang/semver" +) + +// Factory constructs a WebSocket transport. +type Factory struct { + // bus is the controller bus + bus bus.Bus +} + +// NewFactory builds a transport factory. +func NewFactory(bus bus.Bus) *Factory { + return &Factory{bus: bus} +} + +// GetConfigID returns the configuration ID for the controller. +func (t *Factory) GetConfigID() string { + return ConfigID +} + +// GetControllerID returns the unique ID for the controller. +func (t *Factory) GetControllerID() string { + return ControllerID +} + +// ConstructConfig constructs an instance of the controller configuration. +func (t *Factory) ConstructConfig() config.Config { + return &Config{} +} + +// Construct constructs the associated controller given configuration. +func (t *Factory) Construct( + ctx context.Context, + conf config.Config, + opts controller.ConstructOpts, +) (controller.Controller, error) { + le := opts.GetLogger() + cc := conf.(*Config) + + return NewWebSocketHttp(le, t.bus, cc) +} + +// GetVersion returns the version of this controller. +func (t *Factory) GetVersion() semver.Version { + return Version +} + +// _ is a type assertion +var _ controller.Factory = ((*Factory)(nil)) diff --git a/transport/websocket/http/http.go b/transport/websocket/http/http.go new file mode 100644 index 00000000..b46a552f --- /dev/null +++ b/transport/websocket/http/http.go @@ -0,0 +1,155 @@ +package websocket_http + +import ( + "context" + "net/http" + + bifrost_http "github.com/aperturerobotics/bifrost/http" + "github.com/aperturerobotics/bifrost/transport" + transport_controller "github.com/aperturerobotics/bifrost/transport/controller" + "github.com/aperturerobotics/bifrost/transport/websocket" + "github.com/aperturerobotics/controllerbus/bus" + "github.com/aperturerobotics/controllerbus/controller" + "github.com/aperturerobotics/controllerbus/directive" + "github.com/blang/semver" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/sirupsen/logrus" +) + +// ControllerID is the WebSocket HTTP handler controller ID. +const ControllerID = "bifrost/websocket/http" + +// Version is the version of the implementation. +var Version = semver.MustParse("0.0.1") + +// NewWebSocketHttp builds a new WebSocket http handler controller. +type WebSocketHttp struct { + // Controller is the transport controller + *transport_controller.Controller + // mux is the serve mux + mux *http.ServeMux +} + +// NewWebSocketHttp builds a new WebSocket http handler controller. +func NewWebSocketHttp(le *logrus.Entry, b bus.Bus, conf *Config) (*WebSocketHttp, error) { + peerID, err := conf.ParseTransportPeerID() + if err != nil { + return nil, err + } + + mux := http.NewServeMux() + ctrl := &WebSocketHttp{mux: mux} + + // Ensure no duplicate patterns (causes a panic in net/http) + seenPatterns := make(map[string]struct{}, len(conf.GetHttpPatterns())+len(conf.GetPeerHttpPatterns())) + checkPattern := func(httpPattern string) bool { + if len(httpPattern) == 0 { + return false + } + + if _, ok := seenPatterns[httpPattern]; ok { + le. + WithField("http-pattern", httpPattern). + Warn("ignoring duplicate http pattern") + return false + } + + seenPatterns[httpPattern] = struct{}{} + return true + } + + for _, httpPattern := range conf.GetHttpPatterns() { + if checkPattern(httpPattern) { + mux.HandleFunc(httpPattern, ctrl.ServeWebSocketHTTP) + } + } + + for _, peerHttpPattern := range conf.GetPeerHttpPatterns() { + if checkPattern(peerHttpPattern) { + mux.HandleFunc(peerHttpPattern, ctrl.ServePeerHTTP) + } + } + + ctrl.Controller = transport_controller.NewController( + le, + b, + controller.NewInfo(ControllerID, Version, "bifrost websocket http handler"), + peerID, + func( + ctx context.Context, + le *logrus.Entry, + pkey crypto.PrivKey, + handler transport.TransportHandler, + ) (transport.Transport, error) { + return websocket.NewWebSocket( + ctx, + le, + &websocket.Config{ + TransportPeerId: peerID.String(), + Quic: conf.GetQuic(), + Dialers: conf.GetDialers(), + }, + pkey, + handler, + ) + }, + ) + + return ctrl, nil +} + +// ServeWebSocketHTTP serves the WebSocket on the HTTP response. +func (t *WebSocketHttp) ServeWebSocketHTTP(rw http.ResponseWriter, req *http.Request) { + // wait for the transport to be ready + tpt, err := t.GetTransport(req.Context()) + if err != nil { + // This must be a context canceled error. + rw.WriteHeader(500) + return + } + + // Call ServeHTTP + tpt.(*websocket.WebSocket).ServeHTTP(rw, req) +} + +// ServePeerHTTP serves the peer ID as a string on the HTTP response. +func (t *WebSocketHttp) ServePeerHTTP(rw http.ResponseWriter, req *http.Request) { + // wait for the transport to be ready + tpt, err := t.GetTransport(req.Context()) + if err != nil { + // This must be a context canceled error. + rw.WriteHeader(500) + return + } + + rw.WriteHeader(200) + _, _ = rw.Write([]byte(tpt.GetPeerID().String())) +} + +// HandleDirective asks if the handler can resolve the directive. +// If it can, it returns resolver(s). If not, returns nil. +// It is safe to add a reference to the directive during this call. +// The passed context is canceled when the directive instance expires. +// NOTE: the passed context is not canceled when the handler is removed. +func (t *WebSocketHttp) HandleDirective(ctx context.Context, di directive.Instance) ([]directive.Resolver, error) { + switch dir := di.GetDirective().(type) { + case bifrost_http.LookupHTTPHandler: + return t.ResolveLookupHTTPHandler(ctx, dir) + } + + return t.Controller.HandleDirective(ctx, di) +} + +// ResolveLookupHTTPHandler resolves the LookupHTTPHandler directive conditionally. +// Returns nil, nil if no handlers matched. +func (t *WebSocketHttp) ResolveLookupHTTPHandler(ctx context.Context, dir bifrost_http.LookupHTTPHandler) ([]directive.Resolver, error) { + handler, _ := bifrost_http.MatchServeMuxPattern(t.mux, dir) + if handler == nil { + return nil, nil + } + + return directive.R(bifrost_http.NewLookupHTTPHandlerResolver(handler), nil) +} + +// _ is a type assertion. +var _ transport.Controller = ((*WebSocketHttp)(nil)) diff --git a/transport/websocket/http/http.pb.go b/transport/websocket/http/http.pb.go new file mode 100644 index 00000000..90ac0072 --- /dev/null +++ b/transport/websocket/http/http.pb.go @@ -0,0 +1,872 @@ +// Code generated by protoc-gen-go-lite. DO NOT EDIT. +// protoc-gen-go-lite version: v0.6.5 +// source: github.com/aperturerobotics/bifrost/transport/websocket/http/http.proto + +package websocket_http + +import ( + fmt "fmt" + dialer "github.com/aperturerobotics/bifrost/transport/common/dialer" + quic "github.com/aperturerobotics/bifrost/transport/common/quic" + protobuf_go_lite "github.com/aperturerobotics/protobuf-go-lite" + json "github.com/aperturerobotics/protobuf-go-lite/json" + io "io" + strconv "strconv" + strings "strings" +) + +// Config is the configuration for the Websocket HTTP handler transport. +// +// Listen for incoming connections with bifrost/http/listener +// This controller resolves LookupHTTPHandler directives filtering by ServeMux patterns. +// Example: ["GET example.com/my/ws", "GET /other/path"] +type Config struct { + unknownFields []byte + // TransportPeerID sets the peer ID to attach the transport to. + // If unset, attaches to any running peer with a private key. + TransportPeerId string `protobuf:"bytes,1,opt,name=transport_peer_id,json=transportPeerId,proto3" json:"transportPeerId,omitempty"` + // HttpPatterns is the list of patterns to listen on. + // Example: ["GET example.com/my/ws", "GET /other/path"] + HttpPatterns []string `protobuf:"bytes,2,rep,name=http_patterns,json=httpPatterns,proto3" json:"httpPatterns,omitempty"` + // PeerHttpPatterns is the list of patterns to serve the peer ID on. + // Example: ["GET example.com/my/ws/peer-id", "GET /other/path/peer-id"] + PeerHttpPatterns []string `protobuf:"bytes,3,rep,name=peer_http_patterns,json=peerHttpPatterns,proto3" json:"peerHttpPatterns,omitempty"` + // Quic contains the quic protocol options. + // + // The WebSocket transport always disables FEC and several other UDP-centric + // features which are unnecessary due to the "reliable" nature of WebSockets. + Quic *quic.Opts `protobuf:"bytes,4,opt,name=quic,proto3" json:"quic,omitempty"` + // Dialers maps peer IDs to dialers. + Dialers map[string]*dialer.DialerOpts `protobuf:"bytes,5,rep,name=dialers,proto3" json:"dialers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Config) Reset() { + *x = Config{} +} + +func (*Config) ProtoMessage() {} + +func (x *Config) GetTransportPeerId() string { + if x != nil { + return x.TransportPeerId + } + return "" +} + +func (x *Config) GetHttpPatterns() []string { + if x != nil { + return x.HttpPatterns + } + return nil +} + +func (x *Config) GetPeerHttpPatterns() []string { + if x != nil { + return x.PeerHttpPatterns + } + return nil +} + +func (x *Config) GetQuic() *quic.Opts { + if x != nil { + return x.Quic + } + return nil +} + +func (x *Config) GetDialers() map[string]*dialer.DialerOpts { + if x != nil { + return x.Dialers + } + return nil +} + +type Config_DialersEntry struct { + unknownFields []byte + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value *dialer.DialerOpts `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Config_DialersEntry) Reset() { + *x = Config_DialersEntry{} +} + +func (*Config_DialersEntry) ProtoMessage() {} + +func (x *Config_DialersEntry) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Config_DialersEntry) GetValue() *dialer.DialerOpts { + if x != nil { + return x.Value + } + return nil +} + +func (m *Config) CloneVT() *Config { + if m == nil { + return (*Config)(nil) + } + r := new(Config) + r.TransportPeerId = m.TransportPeerId + if rhs := m.HttpPatterns; rhs != nil { + tmpContainer := make([]string, len(rhs)) + copy(tmpContainer, rhs) + r.HttpPatterns = tmpContainer + } + if rhs := m.PeerHttpPatterns; rhs != nil { + tmpContainer := make([]string, len(rhs)) + copy(tmpContainer, rhs) + r.PeerHttpPatterns = tmpContainer + } + if rhs := m.Quic; rhs != nil { + r.Quic = rhs.CloneVT() + } + if rhs := m.Dialers; rhs != nil { + tmpContainer := make(map[string]*dialer.DialerOpts, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.Dialers = tmpContainer + } + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *Config) CloneMessageVT() protobuf_go_lite.CloneMessage { + return m.CloneVT() +} + +func (this *Config) EqualVT(that *Config) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.TransportPeerId != that.TransportPeerId { + return false + } + if len(this.HttpPatterns) != len(that.HttpPatterns) { + return false + } + for i, vx := range this.HttpPatterns { + vy := that.HttpPatterns[i] + if vx != vy { + return false + } + } + if len(this.PeerHttpPatterns) != len(that.PeerHttpPatterns) { + return false + } + for i, vx := range this.PeerHttpPatterns { + vy := that.PeerHttpPatterns[i] + if vx != vy { + return false + } + } + if !this.Quic.EqualVT(that.Quic) { + return false + } + if len(this.Dialers) != len(that.Dialers) { + return false + } + for i, vx := range this.Dialers { + vy, ok := that.Dialers[i] + if !ok { + return false + } + if p, q := vx, vy; p != q { + if p == nil { + p = &dialer.DialerOpts{} + } + if q == nil { + q = &dialer.DialerOpts{} + } + if !p.EqualVT(q) { + return false + } + } + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *Config) EqualMessageVT(thatMsg any) bool { + that, ok := thatMsg.(*Config) + if !ok { + return false + } + return this.EqualVT(that) +} + +// MarshalProtoJSON marshals the Config_DialersEntry message to JSON. +func (x *Config_DialersEntry) MarshalProtoJSON(s *json.MarshalState) { + if x == nil { + s.WriteNil() + return + } + s.WriteObjectStart() + var wroteField bool + if x.Key != "" || s.HasField("key") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("key") + s.WriteString(x.Key) + } + if x.Value != nil || s.HasField("value") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("value") + x.Value.MarshalProtoJSON(s.WithField("value")) + } + s.WriteObjectEnd() +} + +// MarshalJSON marshals the Config_DialersEntry to JSON. +func (x *Config_DialersEntry) MarshalJSON() ([]byte, error) { + return json.DefaultMarshalerConfig.Marshal(x) +} + +// UnmarshalProtoJSON unmarshals the Config_DialersEntry message from JSON. +func (x *Config_DialersEntry) UnmarshalProtoJSON(s *json.UnmarshalState) { + if s.ReadNil() { + return + } + s.ReadObject(func(key string) { + switch key { + default: + s.Skip() // ignore unknown field + case "key": + s.AddField("key") + x.Key = s.ReadString() + case "value": + if s.ReadNil() { + x.Value = nil + return + } + x.Value = &dialer.DialerOpts{} + x.Value.UnmarshalProtoJSON(s.WithField("value", true)) + } + }) +} + +// UnmarshalJSON unmarshals the Config_DialersEntry from JSON. +func (x *Config_DialersEntry) UnmarshalJSON(b []byte) error { + return json.DefaultUnmarshalerConfig.Unmarshal(b, x) +} + +// MarshalProtoJSON marshals the Config message to JSON. +func (x *Config) MarshalProtoJSON(s *json.MarshalState) { + if x == nil { + s.WriteNil() + return + } + s.WriteObjectStart() + var wroteField bool + if x.TransportPeerId != "" || s.HasField("transportPeerId") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("transportPeerId") + s.WriteString(x.TransportPeerId) + } + if len(x.HttpPatterns) > 0 || s.HasField("httpPatterns") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("httpPatterns") + s.WriteStringArray(x.HttpPatterns) + } + if len(x.PeerHttpPatterns) > 0 || s.HasField("peerHttpPatterns") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("peerHttpPatterns") + s.WriteStringArray(x.PeerHttpPatterns) + } + if x.Quic != nil || s.HasField("quic") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("quic") + x.Quic.MarshalProtoJSON(s.WithField("quic")) + } + if x.Dialers != nil || s.HasField("dialers") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("dialers") + s.WriteObjectStart() + var wroteElement bool + for k, v := range x.Dialers { + s.WriteMoreIf(&wroteElement) + s.WriteObjectStringField(k) + v.MarshalProtoJSON(s.WithField("dialers")) + } + s.WriteObjectEnd() + } + s.WriteObjectEnd() +} + +// MarshalJSON marshals the Config to JSON. +func (x *Config) MarshalJSON() ([]byte, error) { + return json.DefaultMarshalerConfig.Marshal(x) +} + +// UnmarshalProtoJSON unmarshals the Config message from JSON. +func (x *Config) UnmarshalProtoJSON(s *json.UnmarshalState) { + if s.ReadNil() { + return + } + s.ReadObject(func(key string) { + switch key { + default: + s.Skip() // ignore unknown field + case "transport_peer_id", "transportPeerId": + s.AddField("transport_peer_id") + x.TransportPeerId = s.ReadString() + case "http_patterns", "httpPatterns": + s.AddField("http_patterns") + if s.ReadNil() { + x.HttpPatterns = nil + return + } + x.HttpPatterns = s.ReadStringArray() + case "peer_http_patterns", "peerHttpPatterns": + s.AddField("peer_http_patterns") + if s.ReadNil() { + x.PeerHttpPatterns = nil + return + } + x.PeerHttpPatterns = s.ReadStringArray() + case "quic": + if s.ReadNil() { + x.Quic = nil + return + } + x.Quic = &quic.Opts{} + x.Quic.UnmarshalProtoJSON(s.WithField("quic", true)) + case "dialers": + s.AddField("dialers") + if s.ReadNil() { + x.Dialers = nil + return + } + x.Dialers = make(map[string]*dialer.DialerOpts) + s.ReadStringMap(func(key string) { + var v dialer.DialerOpts + v.UnmarshalProtoJSON(s) + x.Dialers[key] = &v + }) + } + }) +} + +// UnmarshalJSON unmarshals the Config from JSON. +func (x *Config) UnmarshalJSON(b []byte) error { + return json.DefaultUnmarshalerConfig.Unmarshal(b, x) +} + +func (m *Config) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Config) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Config) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Dialers) > 0 { + for k := range m.Dialers { + v := m.Dialers[k] + baseI := i + size, err := v.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + i -= len(k) + copy(dAtA[i:], k) + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x2a + } + } + if m.Quic != nil { + size, err := m.Quic.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if len(m.PeerHttpPatterns) > 0 { + for iNdEx := len(m.PeerHttpPatterns) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.PeerHttpPatterns[iNdEx]) + copy(dAtA[i:], m.PeerHttpPatterns[iNdEx]) + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(len(m.PeerHttpPatterns[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.HttpPatterns) > 0 { + for iNdEx := len(m.HttpPatterns) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.HttpPatterns[iNdEx]) + copy(dAtA[i:], m.HttpPatterns[iNdEx]) + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(len(m.HttpPatterns[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.TransportPeerId) > 0 { + i -= len(m.TransportPeerId) + copy(dAtA[i:], m.TransportPeerId) + i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(len(m.TransportPeerId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Config) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.TransportPeerId) + if l > 0 { + n += 1 + l + protobuf_go_lite.SizeOfVarint(uint64(l)) + } + if len(m.HttpPatterns) > 0 { + for _, s := range m.HttpPatterns { + l = len(s) + n += 1 + l + protobuf_go_lite.SizeOfVarint(uint64(l)) + } + } + if len(m.PeerHttpPatterns) > 0 { + for _, s := range m.PeerHttpPatterns { + l = len(s) + n += 1 + l + protobuf_go_lite.SizeOfVarint(uint64(l)) + } + } + if m.Quic != nil { + l = m.Quic.SizeVT() + n += 1 + l + protobuf_go_lite.SizeOfVarint(uint64(l)) + } + if len(m.Dialers) > 0 { + for k, v := range m.Dialers { + _ = k + _ = v + l = 0 + if v != nil { + l = v.SizeVT() + } + l += 1 + protobuf_go_lite.SizeOfVarint(uint64(l)) + mapEntrySize := 1 + len(k) + protobuf_go_lite.SizeOfVarint(uint64(len(k))) + l + n += mapEntrySize + 1 + protobuf_go_lite.SizeOfVarint(uint64(mapEntrySize)) + } + } + n += len(m.unknownFields) + return n +} + +func (x *Config_DialersEntry) MarshalProtoText() string { + var sb strings.Builder + sb.WriteString("DialersEntry { ") + if x.Key != "" { + sb.WriteString(" key: ") + sb.WriteString(strconv.Quote(x.Key)) + } + if x.Value != nil { + sb.WriteString(" value: ") + sb.WriteString(x.Value.MarshalProtoText()) + } + sb.WriteString("}") + return sb.String() +} +func (x *Config_DialersEntry) String() string { + return x.MarshalProtoText() +} +func (x *Config) MarshalProtoText() string { + var sb strings.Builder + sb.WriteString("Config { ") + if x.TransportPeerId != "" { + sb.WriteString(" transport_peer_id: ") + sb.WriteString(strconv.Quote(x.TransportPeerId)) + } + if len(x.HttpPatterns) > 0 { + sb.WriteString(" http_patterns: [") + for i, v := range x.HttpPatterns { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(strconv.Quote(v)) + } + sb.WriteString("]") + } + if len(x.PeerHttpPatterns) > 0 { + sb.WriteString(" peer_http_patterns: [") + for i, v := range x.PeerHttpPatterns { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(strconv.Quote(v)) + } + sb.WriteString("]") + } + if x.Quic != nil { + sb.WriteString(" quic: ") + sb.WriteString(x.Quic.MarshalProtoText()) + } + if len(x.Dialers) > 0 { + sb.WriteString(" dialers: {") + for k, v := range x.Dialers { + sb.WriteString(" ") + sb.WriteString(strconv.Quote(k)) + sb.WriteString(": ") + sb.WriteString(v.MarshalProtoText()) + } + sb.WriteString(" }") + } + sb.WriteString("}") + return sb.String() +} +func (x *Config) String() string { + return x.MarshalProtoText() +} +func (m *Config) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Config: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Config: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TransportPeerId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TransportPeerId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HttpPatterns", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HttpPatterns = append(m.HttpPatterns, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeerHttpPatterns", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeerHttpPatterns = append(m.PeerHttpPatterns, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Quic", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Quic == nil { + m.Quic = &quic.Opts{} + } + if err := m.Quic.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Dialers", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Dialers == nil { + m.Dialers = make(map[string]*dialer.DialerOpts) + } + var mapkey string + var mapvalue *dialer.DialerOpts + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protobuf_go_lite.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return protobuf_go_lite.ErrInvalidLength + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &dialer.DialerOpts{} + if err := mapvalue.UnmarshalVT(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := protobuf_go_lite.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Dialers[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protobuf_go_lite.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protobuf_go_lite.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/transport/websocket/http/http.pb.ts b/transport/websocket/http/http.pb.ts new file mode 100644 index 00000000..6b5a03e0 --- /dev/null +++ b/transport/websocket/http/http.pb.ts @@ -0,0 +1,73 @@ +// @generated by protoc-gen-es-lite unknown with parameter "target=ts,ts_nocheck=false" +// @generated from file github.com/aperturerobotics/bifrost/transport/websocket/http/http.proto (package websocket.http, syntax proto3) +/* eslint-disable */ + +import { Opts } from "../../common/quic/quic.pb.js"; +import { DialerOpts } from "../../common/dialer/dialer.pb.js"; +import type { MessageType, PartialFieldInfo } from "@aptre/protobuf-es-lite"; +import { createMessageType, ScalarType } from "@aptre/protobuf-es-lite"; + +export const protobufPackage = "websocket.http"; + +/** + * Config is the configuration for the Websocket HTTP handler transport. + * + * Listen for incoming connections with bifrost/http/listener + * This controller resolves LookupHTTPHandler directives filtering by ServeMux patterns. + * Example: ["GET example.com/my/ws", "GET /other/path"] + * + * @generated from message websocket.http.Config + */ +export interface Config { + /** + * TransportPeerID sets the peer ID to attach the transport to. + * If unset, attaches to any running peer with a private key. + * + * @generated from field: string transport_peer_id = 1; + */ + transportPeerId?: string; + /** + * HttpPatterns is the list of patterns to listen on. + * Example: ["GET example.com/my/ws", "GET /other/path"] + * + * @generated from field: repeated string http_patterns = 2; + */ + httpPatterns?: string[]; + /** + * PeerHttpPatterns is the list of patterns to serve the peer ID on. + * Example: ["GET example.com/my/ws/peer-id", "GET /other/path/peer-id"] + * + * @generated from field: repeated string peer_http_patterns = 3; + */ + peerHttpPatterns?: string[]; + /** + * Quic contains the quic protocol options. + * + * The WebSocket transport always disables FEC and several other UDP-centric + * features which are unnecessary due to the "reliable" nature of WebSockets. + * + * @generated from field: transport.quic.Opts quic = 4; + */ + quic?: Opts; + /** + * Dialers maps peer IDs to dialers. + * + * @generated from field: map dialers = 5; + */ + dialers?: { [key: string]: DialerOpts }; + +}; + +// Config contains the message type declaration for Config. +export const Config: MessageType = createMessageType({ + typeName: "websocket.http.Config", + fields: [ + { no: 1, name: "transport_peer_id", kind: "scalar", T: ScalarType.STRING }, + { no: 2, name: "http_patterns", kind: "scalar", T: ScalarType.STRING, repeated: true }, + { no: 3, name: "peer_http_patterns", kind: "scalar", T: ScalarType.STRING, repeated: true }, + { no: 4, name: "quic", kind: "message", T: () => Opts }, + { no: 5, name: "dialers", kind: "map", K: ScalarType.STRING, V: {kind: "message", T: () => DialerOpts} }, + ] as readonly PartialFieldInfo[], + packedByDefault: true, +}); + diff --git a/transport/websocket/http/http.proto b/transport/websocket/http/http.proto new file mode 100644 index 00000000..3de2fc44 --- /dev/null +++ b/transport/websocket/http/http.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; +package websocket.http; + +import "github.com/aperturerobotics/bifrost/transport/common/quic/quic.proto"; +import "github.com/aperturerobotics/bifrost/transport/common/dialer/dialer.proto"; + +// Config is the configuration for the Websocket HTTP handler transport. +// +// Listen for incoming connections with bifrost/http/listener +// This controller resolves LookupHTTPHandler directives filtering by ServeMux patterns. +// Example: ["GET example.com/my/ws", "GET /other/path"] +message Config { + // TransportPeerID sets the peer ID to attach the transport to. + // If unset, attaches to any running peer with a private key. + string transport_peer_id = 1; + // HttpPatterns is the list of patterns to listen on. + // Example: ["GET example.com/my/ws", "GET /other/path"] + repeated string http_patterns = 2; + // PeerHttpPatterns is the list of patterns to serve the peer ID on. + // Example: ["GET example.com/my/ws/peer-id", "GET /other/path/peer-id"] + repeated string peer_http_patterns = 3; + // Quic contains the quic protocol options. + // + // The WebSocket transport always disables FEC and several other UDP-centric + // features which are unnecessary due to the "reliable" nature of WebSockets. + .transport.quic.Opts quic = 4; + // Dialers maps peer IDs to dialers. + map dialers = 5; +}