diff --git a/implants/imixv2/src/portal/run.rs b/implants/imixv2/src/portal/run.rs index e409b24ab..4d11aedd7 100644 --- a/implants/imixv2/src/portal/run.rs +++ b/implants/imixv2/src/portal/run.rs @@ -218,6 +218,11 @@ async fn stream_handler( Payload::Tcp(_) => tcp::handle_tcp(first_mote, rx, out_tx, sequencer).await, Payload::Udp(_) => udp::handle_udp(first_mote, rx, out_tx, sequencer).await, Payload::Bytes(_) => bytes::handle_bytes(first_mote, rx, out_tx, sequencer).await, + Payload::Repl(_) => { + #[cfg(debug_assertions)] + log::warn!("Received REPL message in generic portal handler, ignoring."); + Ok(()) + } } } else { Ok(()) diff --git a/implants/lib/pb/src/generated/c2.rs b/implants/lib/pb/src/generated/c2.rs index 7a37b59c1..214757dbd 100644 --- a/implants/lib/pb/src/generated/c2.rs +++ b/implants/lib/pb/src/generated/c2.rs @@ -244,24 +244,6 @@ pub struct ReportTaskOutputRequest { pub struct ReportTaskOutputResponse {} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ReverseShellRequest { - #[prost(enumeration = "ReverseShellMessageKind", tag = "1")] - pub kind: i32, - #[prost(bytes = "vec", tag = "2")] - pub data: ::prost::alloc::vec::Vec, - #[prost(int64, tag = "3")] - pub task_id: i64, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ReverseShellResponse { - #[prost(enumeration = "ReverseShellMessageKind", tag = "1")] - pub kind: i32, - #[prost(bytes = "vec", tag = "2")] - pub data: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] pub struct CreatePortalRequest { #[prost(int64, tag = "1")] pub task_id: i64, @@ -274,37 +256,6 @@ pub struct CreatePortalResponse { #[prost(message, optional, tag = "2")] pub mote: ::core::option::Option, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum ReverseShellMessageKind { - Unspecified = 0, - Data = 1, - Ping = 2, -} -impl ReverseShellMessageKind { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - ReverseShellMessageKind::Unspecified => { - "REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED" - } - ReverseShellMessageKind::Data => "REVERSE_SHELL_MESSAGE_KIND_DATA", - ReverseShellMessageKind::Ping => "REVERSE_SHELL_MESSAGE_KIND_PING", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED" => Some(Self::Unspecified), - "REVERSE_SHELL_MESSAGE_KIND_DATA" => Some(Self::Data), - "REVERSE_SHELL_MESSAGE_KIND_PING" => Some(Self::Ping), - _ => None, - } - } -} /// Generated client implementations. pub mod c2_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] @@ -548,32 +499,6 @@ pub mod c2_client { self.inner.unary(req, path, codec).await } /// - /// Open a reverse shell bi-directional stream. - pub async fn reverse_shell( - &mut self, - request: impl tonic::IntoStreamingRequest< - Message = super::ReverseShellRequest, - >, - ) -> std::result::Result< - tonic::Response>, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = crate::xchacha::ChachaCodec::default(); - let path = http::uri::PathAndQuery::from_static("/c2.C2/ReverseShell"); - let mut req = request.into_streaming_request(); - req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReverseShell")); - self.inner.streaming(req, path, codec).await - } - /// /// Open a portal bi-directional stream. pub async fn create_portal( &mut self, diff --git a/implants/lib/pb/src/generated/portal.rs b/implants/lib/pb/src/generated/portal.rs index bda60711e..fc1d7b2d8 100644 --- a/implants/lib/pb/src/generated/portal.rs +++ b/implants/lib/pb/src/generated/portal.rs @@ -29,6 +29,12 @@ pub struct UdpPayload { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReplMessage { + #[prost(bytes = "vec", tag = "1")] + pub data: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Mote { /// Unique identifier used to route reply traffic back to original port #[prost(string, tag = "1")] @@ -37,7 +43,7 @@ pub struct Mote { #[prost(uint64, tag = "2")] pub seq_id: u64, /// A payload - #[prost(oneof = "mote::Payload", tags = "3, 4, 5")] + #[prost(oneof = "mote::Payload", tags = "3, 4, 5, 6")] pub payload: ::core::option::Option, } /// Nested message and enum types in `Mote`. @@ -52,6 +58,8 @@ pub mod mote { Tcp(super::TcpPayload), #[prost(message, tag = "5")] Bytes(super::BytesPayload), + #[prost(message, tag = "6")] + Repl(super::ReplMessage), } } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/implants/lib/transport/src/dns.rs b/implants/lib/transport/src/dns.rs index 462e1292e..e1dfc9e96 100644 --- a/implants/lib/transport/src/dns.rs +++ b/implants/lib/transport/src/dns.rs @@ -1061,16 +1061,6 @@ impl Transport for DNS { self.dns_exchange(request, "/c2.C2/ReportTaskOutput").await } - async fn reverse_shell( - &mut self, - _rx: tokio::sync::mpsc::Receiver, - _tx: tokio::sync::mpsc::Sender, - ) -> Result<()> { - Err(anyhow::anyhow!( - "reverse_shell not supported over DNS transport" - )) - } - async fn create_portal( &mut self, _rx: tokio::sync::mpsc::Receiver, diff --git a/implants/lib/transport/src/grpc.rs b/implants/lib/transport/src/grpc.rs index 45c2a7482..1ca37934e 100644 --- a/implants/lib/transport/src/grpc.rs +++ b/implants/lib/transport/src/grpc.rs @@ -24,7 +24,6 @@ static REPORT_CREDENTIAL_PATH: &str = "/c2.C2/ReportCredential"; static REPORT_FILE_PATH: &str = "/c2.C2/ReportFile"; static REPORT_PROCESS_LIST_PATH: &str = "/c2.C2/ReportProcessList"; static REPORT_TASK_OUTPUT_PATH: &str = "/c2.C2/ReportTaskOutput"; -static REVERSE_SHELL_PATH: &str = "/c2.C2/ReverseShell"; static CREATE_PORTAL_PATH: &str = "/c2.C2/CreatePortal"; #[allow(clippy::upper_case_acronyms)] @@ -179,44 +178,6 @@ impl Transport for GRPC { Ok(resp.into_inner()) } - async fn reverse_shell( - &mut self, - rx: tokio::sync::mpsc::Receiver, - tx: tokio::sync::mpsc::Sender, - ) -> Result<()> { - // Wrap PTY output receiver in stream - let req_stream = tokio_stream::wrappers::ReceiverStream::new(rx); - - // Open gRPC Bi-Directional Stream - let resp = self.reverse_shell_impl(req_stream).await?; - let mut resp_stream = resp.into_inner(); - - // Spawn task to deliver PTY input - tokio::spawn(async move { - while let Some(msg) = match resp_stream.message().await { - Ok(m) => m, - Err(_err) => { - #[cfg(debug_assertions)] - log::error!("failed to receive gRPC stream response: {}", _err); - - None - } - } { - match tx.send(msg).await { - Ok(_) => {} - Err(_err) => { - #[cfg(debug_assertions)] - log::error!("failed to queue pty input: {}", _err); - - return; - } - } - } - }); - - Ok(()) - } - async fn create_portal( &mut self, rx: tokio::sync::mpsc::Receiver, @@ -453,37 +414,6 @@ impl GRPC { self.grpc.as_mut().unwrap().unary(req, path, codec).await } - async fn reverse_shell_impl( - &mut self, - request: impl tonic::IntoStreamingRequest, - ) -> std::result::Result< - tonic::Response>, - tonic::Status, - > { - if self.grpc.is_none() { - return Err(tonic::Status::new( - tonic::Code::FailedPrecondition, - "grpc client not created".to_string(), - )); - } - self.grpc.as_mut().unwrap().ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e), - ) - })?; - let codec = pb::xchacha::ChachaCodec::default(); - let path = tonic::codegen::http::uri::PathAndQuery::from_static(REVERSE_SHELL_PATH); - let mut req = request.into_streaming_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "ReverseShell")); - self.grpc - .as_mut() - .unwrap() - .streaming(req, path, codec) - .await - } - async fn create_portal_impl( &mut self, request: impl tonic::IntoStreamingRequest, diff --git a/implants/lib/transport/src/http.rs b/implants/lib/transport/src/http.rs index 176a32ce0..f09b8e74d 100644 --- a/implants/lib/transport/src/http.rs +++ b/implants/lib/transport/src/http.rs @@ -80,7 +80,6 @@ static REPORT_CREDENTIAL_PATH: &str = "/c2.C2/ReportCredential"; static REPORT_FILE_PATH: &str = "/c2.C2/ReportFile"; static REPORT_PROCESS_LIST_PATH: &str = "/c2.C2/ReportProcessList"; static REPORT_TASK_OUTPUT_PATH: &str = "/c2.C2/ReportTaskOutput"; -static _REVERSE_SHELL_PATH: &str = "/c2.C2/ReverseShell"; // Marshal: Encode and encrypt a message using the ChachaCodec // Uses the helper functions exported from pb::xchacha @@ -412,16 +411,6 @@ impl Transport for HTTP { self.unary_rpc(request, REPORT_TASK_OUTPUT_PATH).await } - async fn reverse_shell( - &mut self, - _rx: tokio::sync::mpsc::Receiver, - _tx: tokio::sync::mpsc::Sender, - ) -> Result<()> { - Err(anyhow::anyhow!( - "http/1.1 transport does not support reverse shell" - )) - } - async fn create_portal( &mut self, _rx: tokio::sync::mpsc::Receiver, diff --git a/implants/lib/transport/src/lib.rs b/implants/lib/transport/src/lib.rs index c74c0d990..e5b9ef91d 100644 --- a/implants/lib/transport/src/lib.rs +++ b/implants/lib/transport/src/lib.rs @@ -190,24 +190,6 @@ impl Transport for ActiveTransport { } } - async fn reverse_shell( - &mut self, - rx: tokio::sync::mpsc::Receiver, - tx: tokio::sync::mpsc::Sender, - ) -> Result<()> { - match self { - #[cfg(feature = "grpc")] - Self::Grpc(t) => t.reverse_shell(rx, tx).await, - #[cfg(feature = "http1")] - Self::Http(t) => t.reverse_shell(rx, tx).await, - #[cfg(feature = "dns")] - Self::Dns(t) => t.reverse_shell(rx, tx).await, - #[cfg(feature = "mock")] - Self::Mock(t) => t.reverse_shell(rx, tx).await, - Self::Empty => Err(anyhow!("Transport not initialized")), - } - } - async fn create_portal( &mut self, rx: tokio::sync::mpsc::Receiver, @@ -313,13 +295,13 @@ mod tests { } #[tokio::test] - #[cfg(not(feature = "http1"))] + #[cfg(feature = "http1")] async fn test_routes_to_http1_transport() { // All these prefixes should result in the Http1 variant let inputs = vec!["http1://127.0.0.1:8080", "https1://127.0.0.1:8080"]; for uri in inputs { - let result = ActiveTransport::new(uri.to_string(), None); + let result = ActiveTransport::new(uri.to_string(), Config::default()); assert!( matches!(result, Ok(ActiveTransport::Http(_))), @@ -356,7 +338,7 @@ mod tests { // If the feature is off, these should error out let inputs = vec!["grpc://foo", "grpcs://foo", "http://foo"]; for uri in inputs { - let result = ActiveTransport::new(uri.to_string(), None); + let result = ActiveTransport::new(uri.to_string(), Config::default()); assert!( result.is_err(), "Expected error for '{}' when gRPC feature is disabled", diff --git a/implants/lib/transport/src/mock.rs b/implants/lib/transport/src/mock.rs index da26ebeab..f8e4d4c1a 100644 --- a/implants/lib/transport/src/mock.rs +++ b/implants/lib/transport/src/mock.rs @@ -42,12 +42,6 @@ mock! { request: ReportTaskOutputRequest, ) -> Result; - async fn reverse_shell( - &mut self, - rx: tokio::sync::mpsc::Receiver, - tx: tokio::sync::mpsc::Sender, - ) -> Result<()>; - async fn create_portal( &mut self, rx: tokio::sync::mpsc::Receiver, diff --git a/implants/lib/transport/src/transport.rs b/implants/lib/transport/src/transport.rs index 13f4ca23d..132cd74ca 100644 --- a/implants/lib/transport/src/transport.rs +++ b/implants/lib/transport/src/transport.rs @@ -70,15 +70,6 @@ pub trait UnsafeTransport: Clone + Send { request: ReportTaskOutputRequest, ) -> Result; - /// - /// Open a shell via the transport. - #[allow(dead_code)] - async fn reverse_shell( - &mut self, - rx: tokio::sync::mpsc::Receiver, - tx: tokio::sync::mpsc::Sender, - ) -> Result<()>; - /// /// Create a portal via the transport. #[allow(dead_code)] diff --git a/tavern/app.go b/tavern/app.go index 07888941c..05d246382 100644 --- a/tavern/app.go +++ b/tavern/app.go @@ -228,19 +228,6 @@ func NewServer(ctx context.Context, options ...func(*Config)) (*Server, error) { // Configure Request Logging httpLogger := log.New(os.Stderr, "[HTTP] ", log.Flags()) - // Configure Shell Muxes - wsShellMux, grpcShellMux := cfg.NewShellMuxes(ctx) - go func() { - if err := wsShellMux.Start(ctx); err != nil { - slog.ErrorContext(ctx, "websocket shell mux stopped", "err", err) - } - }() - go func() { - if err := grpcShellMux.Start(ctx); err != nil { - slog.ErrorContext(ctx, "grpc shell mux stopped", "err", err) - } - }() - // Configure Portal Mux portalMux := cfg.NewPortalMux(ctx) @@ -275,7 +262,7 @@ func NewServer(ctx context.Context, options ...func(*Config)) (*Server, error) { AllowUnactivated: true, }, "/c2.C2/": tavernhttp.Endpoint{ - Handler: newGRPCHandler(client, grpcShellMux, portalMux), + Handler: newGRPCHandler(client, portalMux), AllowUnauthenticated: true, AllowUnactivated: true, }, @@ -294,10 +281,10 @@ func NewServer(ctx context.Context, options ...func(*Config)) (*Server, error) { Handler: cdn.NewUploadHandler(client), }, "/shell/ws": tavernhttp.Endpoint{ - Handler: stream.NewShellHandler(client, wsShellMux), + Handler: stream.NewShellHandler(client, portalMux), }, "/shell/ping": tavernhttp.Endpoint{ - Handler: stream.NewPingHandler(client, wsShellMux), + Handler: stream.NewPingHandler(client, portalMux), }, "/": tavernhttp.Endpoint{ Handler: www.NewHandler(httpLogger), @@ -523,14 +510,14 @@ func newPortalGRPCHandler(graph *ent.Client, portalMux *mux.Mux) http.Handler { }) } -func newGRPCHandler(client *ent.Client, grpcShellMux *stream.Mux, portalMux *mux.Mux) http.Handler { +func newGRPCHandler(client *ent.Client, portalMux *mux.Mux) http.Handler { pub, priv, err := getKeyPair() if err != nil { panic(err) } slog.Info(fmt.Sprintf("public key: %s", base64.StdEncoding.EncodeToString(pub.Bytes()))) - c2srv := c2.New(client, grpcShellMux, portalMux) + c2srv := c2.New(client, portalMux) xchacha := cryptocodec.StreamDecryptCodec{ Csvc: cryptocodec.NewCryptoSvc(priv), } diff --git a/tavern/config.go b/tavern/config.go index 27b60c614..823c30bb6 100644 --- a/tavern/config.go +++ b/tavern/config.go @@ -12,13 +12,11 @@ import ( gcppubsub "cloud.google.com/go/pubsub" "entgo.io/ent/dialect/sql" "github.com/go-sql-driver/mysql" - "gocloud.dev/pubsub" _ "gocloud.dev/pubsub/gcppubsub" _ "gocloud.dev/pubsub/mempubsub" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "realm.pub/tavern/internal/ent" - "realm.pub/tavern/internal/http/stream" "realm.pub/tavern/internal/namegen" "realm.pub/tavern/internal/portals/mux" "realm.pub/tavern/tomes" @@ -163,109 +161,6 @@ func (cfg *Config) NewPortalMux(ctx context.Context) *mux.Mux { return mux.New(mux.WithGCPDriver(projectID, gcpClient), mux.WithSubscriberBufferSize(1024)) } -// NewShellMuxes configures two stream.Mux instances for shell i/o. -// The wsMux will be used by websockets to subscribe to shell output and publish new input. -// The grpcMux will be used by gRPC to subscribe to shell input and publish new output. -func (cfg *Config) NewShellMuxes(ctx context.Context) (wsMux *stream.Mux, grpcMux *stream.Mux) { - var ( - projectID = EnvGCPProjectID.String() - gcpTopicPrefix = fmt.Sprintf("gcppubsub://projects/%s/topics/", projectID) - topicShellInput = EnvPubSubTopicShellInput.String() - topicShellOutput = EnvPubSubTopicShellOutput.String() - subShellInput = EnvPubSubSubscriptionShellInput.String() - subShellOutput = EnvPubSubSubscriptionShellOutput.String() - ) - - // For GCP, messages for a "Subscription" are load-balanced across all of the "Subscribers" to that same "Subscription" - // This means we must make a new "Subscription" in GCP for each instance of tavern to ensure they all receive the - // appropriate input/output from shells. For more information, see the information here: - // https://cloud.google.com/pubsub/docs/pubsub-basics#choose_a_publish_and_subscribe_pattern - if strings.HasPrefix(subShellInput, "gcppubsub://") && strings.HasPrefix(subShellOutput, "gcppubsub://") { - if projectID == "" { - log.Fatalf("[FATAL] must set value for %q when using gcppubsub:// in configuration", EnvGCPProjectID.Key) - } - - client, err := gcppubsub.NewClient(ctx, projectID) - if err != nil { - panic(fmt.Errorf("failed to create gcppubsub client needed to create a new subscription: %v", err)) - } - defer client.Close() - - createGCPSubscription := func(ctx context.Context, topic *gcppubsub.Topic) string { - name := fmt.Sprintf("%s-sub_%s", topic.ID(), GlobalInstanceID) - - sub, err := client.CreateSubscription(ctx, name, gcppubsub.SubscriptionConfig{ - Topic: topic, - AckDeadline: 10 * time.Second, - ExpirationPolicy: 24 * time.Hour, // Automatically delete unused subscriptions after 1 day - }) - if err != nil { - panic(fmt.Errorf( - "failed to create gcppubsub subscription (topic=%q,subscription_name=%q), to disable creation do not use the 'gcppubsub://' prefix for the environment variable %q: %v", - topic.ID(), - name, - EnvPubSubSubscriptionShellInput.Key, - err, - )) - } - exists, err := sub.Exists(ctx) - if err != nil { - panic(fmt.Errorf("failed to check if gcppubsub subscription was successfully created: %w", err)) - } - if !exists { - panic(fmt.Errorf("failed to create gcppubsub subscription, it does not exist! name=%q", name)) - } - return name - } - - shellInputTopic := client.Topic(strings.TrimPrefix(topicShellInput, gcpTopicPrefix)) - shellOutputTopic := client.Topic(strings.TrimPrefix(topicShellOutput, gcpTopicPrefix)) - - // Overwrite env var specification with newly created GCP PubSub Subscriptions - subShellInput = fmt.Sprintf("gcppubsub://projects/%s/subscriptions/%s", projectID, createGCPSubscription(ctx, shellInputTopic)) - slog.DebugContext(ctx, "created GCP PubSub subscription for shell input", "subscription_name", subShellInput) - subShellOutput = fmt.Sprintf("gcppubsub://projects/%s/subscriptions/%s", projectID, createGCPSubscription(ctx, shellOutputTopic)) - slog.DebugContext(ctx, "created GCP PubSub subscription for shell output", "subscription_name", subShellOutput) - - // Start a goroutine to publish noop messages on an interval. - // This reduces cold-start latency for GCP PubSub which can improve shell user experience. - if interval := EnvGCPPubsubKeepAliveIntervalMs.Int(); interval > 0 { - go stream.PreventPubSubColdStarts( - ctx, - time.Duration(interval)*time.Millisecond, - topicShellOutput, - topicShellInput, - ) - } - } - - pubOutput, err := pubsub.OpenTopic(ctx, topicShellOutput) - if err != nil { - log.Fatalf("[FATAL] Failed to connect to pubsub topic (%q): %v", topicShellOutput, err) - } - - slog.DebugContext(ctx, "opening GCP PubSub subscription for shell output", "subscription_name", subShellOutput) - subOutput, err := pubsub.OpenSubscription(ctx, subShellOutput) - if err != nil { - log.Fatalf("[FATAL] Failed to connect to pubsub subscription (%q): %v", subShellOutput, err) - } - - pubInput, err := pubsub.OpenTopic(ctx, topicShellInput) - if err != nil { - log.Fatalf("[FATAL] Failed to connect to pubsub topic (%q): %v", topicShellInput, err) - } - - slog.DebugContext(ctx, "opening GCP PubSub subscription for shell input", "subscription_name", subShellInput) - subInput, err := pubsub.OpenSubscription(ctx, subShellInput) - if err != nil { - log.Fatalf("[FATAL] Failed to connect to pubsub subscription (%q): %v", subShellInput, err) - } - - wsMux = stream.NewMux(pubInput, subOutput) - grpcMux = stream.NewMux(pubOutput, subInput) - return -} - // NewGitImporter configures and returns a new RepoImporter using git. func (cfg *Config) NewGitImporter(client *ent.Client) *tomes.GitImporter { var options []tomes.GitImportOption diff --git a/tavern/internal/c2/api_reverse_shell.go b/tavern/internal/c2/api_reverse_shell.go deleted file mode 100644 index 0e285242e..000000000 --- a/tavern/internal/c2/api_reverse_shell.go +++ /dev/null @@ -1,252 +0,0 @@ -package c2 - -import ( - "context" - "fmt" - "io" - "log/slog" - "sync" - "time" - - "gocloud.dev/pubsub" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "realm.pub/tavern/internal/c2/c2pb" - "realm.pub/tavern/internal/ent" - "realm.pub/tavern/internal/http/stream" -) - -// keepAlivePingInterval defines the frequency to send no-op ping messages to the stream, -// this is useful because imix relies on an input after the "exit" command to perform cleanup. -const ( - keepAlivePingInterval = 5 * time.Second -) - -func (srv *Server) ReverseShell(gstream c2pb.C2_ReverseShellServer) error { - // Setup Context - ctx := gstream.Context() - - // Get initial message for registration - registerMsg, err := gstream.Recv() - if err != nil { - return status.Errorf(codes.Internal, "failed to receive registration message: %v", err) - } - - // Load Relevant Ents - task, err := srv.graph.Task.Get(ctx, int(registerMsg.TaskId)) - if err != nil { - if ent.IsNotFound(err) { - slog.ErrorContext(ctx, "reverse shell failed: associated task does not exist", "task_id", registerMsg.TaskId, "error", err) - return status.Errorf(codes.NotFound, "task does not exist (task_id=%d)", registerMsg.TaskId) - } - slog.ErrorContext(ctx, "reverse shell failed: could not load associated task", "task_id", registerMsg.TaskId, "error", err) - return status.Errorf(codes.Internal, "failed to load task ent (task_id=%d): %v", registerMsg.TaskId, err) - } - beacon, err := task.Beacon(ctx) - if err != nil { - slog.ErrorContext(ctx, "reverse shell failed: could not load associated beacon", "task_id", registerMsg.TaskId, "error", err) - return status.Errorf(codes.Internal, "failed to load beacon ent (task_id=%d): %v", registerMsg.TaskId, err) - } - quest, err := task.Quest(ctx) - if err != nil { - slog.ErrorContext(ctx, "reverse shell failed: could not load associated quest", "task_id", registerMsg.TaskId, "error", err) - return status.Errorf(codes.Internal, "failed to load quest ent (task_id=%d): %v", registerMsg.TaskId, err) - } - creator, err := quest.Creator(ctx) - if err != nil { - slog.ErrorContext(ctx, "reverse shell failed: could not load associated quest creator", "task_id", registerMsg.TaskId, "error", err) - return status.Errorf(codes.Internal, "failed to load quest creator (task_id=%d): %v", registerMsg.TaskId, err) - } - - // Create the Shell Entity - shell, err := srv.graph.Shell.Create(). - SetOwner(creator). - SetBeacon(beacon). - SetTask(task). - SetData([]byte{}). - Save(ctx) - if err != nil { - slog.ErrorContext(ctx, "reverse shell failed: could not create shell entity", "task_id", registerMsg.TaskId, "error", err) - return status.Errorf(codes.Internal, "failed to create shell: %v", err) - } - shellID := shell.ID - - // Log Shell Session - slog.InfoContext(ctx, "started gRPC reverse shell", - "shell_id", shellID, - "task_id", registerMsg.TaskId, - "creator_id", creator.ID, - ) - defer func(start time.Time) { - slog.InfoContext(ctx, "closed gRPC reverse shell", - "started_at", start.String(), - "ended_at", time.Now().String(), - "duration", time.Since(start).String(), - "shell_id", shellID, - "task_id", registerMsg.TaskId, - "creator_id", creator.ID, - ) - }(time.Now()) - - // Create new Stream - pubsubStream := stream.New(fmt.Sprintf("%d", shellID)) - - // Cleanup - defer func() { - closedAt := time.Now() - - // Prepare New Context - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Notify Subscribers that the stream is closed - slog.DebugContext(ctx, "reverse shell closed, sending stream close message", "shell_id", shell.ID) - if err := pubsubStream.SendMessage(ctx, &pubsub.Message{ - Metadata: map[string]string{ - stream.MetadataStreamClose: fmt.Sprintf("%d", shellID), - }, - }, srv.mux); err != nil { - slog.ErrorContext(ctx, "reverse shell closed and failed to notify subscribers", - "shell_id", shell.ID, - "error", err, - ) - } - - // Update Ent - shell, err := srv.graph.Shell.Get(ctx, shellID) - if err != nil { - slog.ErrorContext(ctx, "reverse shell closed and failed to load ent for updates", - "error", err, - "shell_id", shellID, - ) - return - } - if _, err := shell.Update(). - SetClosedAt(closedAt). - Save(ctx); err != nil { - slog.ErrorContext(ctx, "reverse shell closed and failed to update ent", - "error", err, - "shell_id", shell.ID, - ) - } - }() - - // Register stream with Mux - srv.mux.Register(pubsubStream) - defer srv.mux.Unregister(pubsubStream) - - // WaitGroup to manage tasks - var wg sync.WaitGroup - - // Send Keep Alives - wg.Add(1) - go func() { - defer wg.Done() - sendKeepAlives(ctx, gstream) - }() - - // Send Input (to shell) - wg.Add(1) - go func() { - defer wg.Done() - sendShellInput(ctx, shellID, gstream, pubsubStream) - }() - - // Send Output (to pubsub) - err = sendShellOutput(ctx, shellID, gstream, pubsubStream, srv.mux) - - wg.Wait() - - return err -} - -func sendShellInput(ctx context.Context, shellID int, gstream c2pb.C2_ReverseShellServer, pubsubStream *stream.Stream) { - for { - select { - case <-ctx.Done(): - return - case msg := <-pubsubStream.Messages(): - msgLen := len(msg.Body) - // Determine message kind - kind := c2pb.ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_DATA - if msg.Metadata != nil { - metadataKind, ok := msg.Metadata[stream.MetadataMsgKind] - if ok && metadataKind == "ping" { - kind = c2pb.ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_PING - } - } - - if err := gstream.Send(&c2pb.ReverseShellResponse{ - Kind: kind, - Data: msg.Body, - }); err != nil { - slog.ErrorContext(ctx, "failed to send shell input to reverse shell", - "shell_id", shellID, - "msg_len", msgLen, - "error", err, - ) - return - } - slog.DebugContext(ctx, "reverse shell sent input to agent via gRPC", - "shell_id", shellID, - "msg_len", msgLen, - ) - } - } -} - -func sendShellOutput(ctx context.Context, shellID int, gstream c2pb.C2_ReverseShellServer, pubsubStream *stream.Stream, mux *stream.Mux) error { - for { - req, err := gstream.Recv() - if err == io.EOF { - return nil - } - if err != nil { - return status.Errorf(codes.Internal, "failed to receive shell request: %v", err) - } - - // Determine message kind - kind := "data" - if req.Kind == c2pb.ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_PING { - kind = "ping" - } - - // Send Pubsub Message - msgLen := len(req.Data) - if err := pubsubStream.SendMessage(ctx, &pubsub.Message{ - Body: req.Data, - Metadata: map[string]string{ - stream.MetadataMsgKind: kind, - }, - }, mux); err != nil { - slog.ErrorContext(ctx, "reverse shell failed to publish shell output", - "shell_id", shellID, - "msg_len", msgLen, - "error", err, - ) - return status.Errorf(codes.Internal, "failed to publish message: %v", err) - } - slog.DebugContext(ctx, "reverse shell published shell output", - "shell_id", shellID, - "msg_len", msgLen, - ) - } -} - -func sendKeepAlives(ctx context.Context, gstream c2pb.C2_ReverseShellServer) { - ticker := time.NewTicker(keepAlivePingInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if err := gstream.Send(&c2pb.ReverseShellResponse{ - Kind: c2pb.ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_PING, - }); err != nil { - slog.ErrorContext(ctx, "reverse shell failed to send gRPC keep alive ping", "error", err) - } - } - } -} diff --git a/tavern/internal/c2/c2pb/c2.pb.go b/tavern/internal/c2/c2pb/c2.pb.go index 953023979..d3a3807f7 100644 --- a/tavern/internal/c2/c2pb/c2.pb.go +++ b/tavern/internal/c2/c2pb/c2.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.11 // protoc v3.21.12 // source: c2.proto @@ -24,55 +24,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type ReverseShellMessageKind int32 - -const ( - ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED ReverseShellMessageKind = 0 - ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_DATA ReverseShellMessageKind = 1 - ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_PING ReverseShellMessageKind = 2 -) - -// Enum value maps for ReverseShellMessageKind. -var ( - ReverseShellMessageKind_name = map[int32]string{ - 0: "REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED", - 1: "REVERSE_SHELL_MESSAGE_KIND_DATA", - 2: "REVERSE_SHELL_MESSAGE_KIND_PING", - } - ReverseShellMessageKind_value = map[string]int32{ - "REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED": 0, - "REVERSE_SHELL_MESSAGE_KIND_DATA": 1, - "REVERSE_SHELL_MESSAGE_KIND_PING": 2, - } -) - -func (x ReverseShellMessageKind) Enum() *ReverseShellMessageKind { - p := new(ReverseShellMessageKind) - *p = x - return p -} - -func (x ReverseShellMessageKind) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (ReverseShellMessageKind) Descriptor() protoreflect.EnumDescriptor { - return file_c2_proto_enumTypes[0].Descriptor() -} - -func (ReverseShellMessageKind) Type() protoreflect.EnumType { - return &file_c2_proto_enumTypes[0] -} - -func (x ReverseShellMessageKind) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use ReverseShellMessageKind.Descriptor instead. -func (ReverseShellMessageKind) EnumDescriptor() ([]byte, []int) { - return file_c2_proto_rawDescGZIP(), []int{0} -} - type ActiveTransport_Type int32 const ( @@ -109,11 +60,11 @@ func (x ActiveTransport_Type) String() string { } func (ActiveTransport_Type) Descriptor() protoreflect.EnumDescriptor { - return file_c2_proto_enumTypes[1].Descriptor() + return file_c2_proto_enumTypes[0].Descriptor() } func (ActiveTransport_Type) Type() protoreflect.EnumType { - return &file_c2_proto_enumTypes[1] + return &file_c2_proto_enumTypes[0] } func (x ActiveTransport_Type) Number() protoreflect.EnumNumber { @@ -164,11 +115,11 @@ func (x Host_Platform) String() string { } func (Host_Platform) Descriptor() protoreflect.EnumDescriptor { - return file_c2_proto_enumTypes[2].Descriptor() + return file_c2_proto_enumTypes[1].Descriptor() } func (Host_Platform) Type() protoreflect.EnumType { - return &file_c2_proto_enumTypes[2] + return &file_c2_proto_enumTypes[1] } func (x Host_Platform) Number() protoreflect.EnumNumber { @@ -1145,118 +1096,6 @@ func (*ReportTaskOutputResponse) Descriptor() ([]byte, []int) { return file_c2_proto_rawDescGZIP(), []int{18} } -type ReverseShellRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Kind ReverseShellMessageKind `protobuf:"varint,1,opt,name=kind,proto3,enum=c2.ReverseShellMessageKind" json:"kind,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - TaskId int64 `protobuf:"varint,3,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ReverseShellRequest) Reset() { - *x = ReverseShellRequest{} - mi := &file_c2_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ReverseShellRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReverseShellRequest) ProtoMessage() {} - -func (x *ReverseShellRequest) ProtoReflect() protoreflect.Message { - mi := &file_c2_proto_msgTypes[19] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReverseShellRequest.ProtoReflect.Descriptor instead. -func (*ReverseShellRequest) Descriptor() ([]byte, []int) { - return file_c2_proto_rawDescGZIP(), []int{19} -} - -func (x *ReverseShellRequest) GetKind() ReverseShellMessageKind { - if x != nil { - return x.Kind - } - return ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED -} - -func (x *ReverseShellRequest) GetData() []byte { - if x != nil { - return x.Data - } - return nil -} - -func (x *ReverseShellRequest) GetTaskId() int64 { - if x != nil { - return x.TaskId - } - return 0 -} - -type ReverseShellResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Kind ReverseShellMessageKind `protobuf:"varint,1,opt,name=kind,proto3,enum=c2.ReverseShellMessageKind" json:"kind,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ReverseShellResponse) Reset() { - *x = ReverseShellResponse{} - mi := &file_c2_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ReverseShellResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReverseShellResponse) ProtoMessage() {} - -func (x *ReverseShellResponse) ProtoReflect() protoreflect.Message { - mi := &file_c2_proto_msgTypes[20] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReverseShellResponse.ProtoReflect.Descriptor instead. -func (*ReverseShellResponse) Descriptor() ([]byte, []int) { - return file_c2_proto_rawDescGZIP(), []int{20} -} - -func (x *ReverseShellResponse) GetKind() ReverseShellMessageKind { - if x != nil { - return x.Kind - } - return ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED -} - -func (x *ReverseShellResponse) GetData() []byte { - if x != nil { - return x.Data - } - return nil -} - type CreatePortalRequest struct { state protoimpl.MessageState `protogen:"open.v1"` TaskId int64 `protobuf:"varint,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` @@ -1267,7 +1106,7 @@ type CreatePortalRequest struct { func (x *CreatePortalRequest) Reset() { *x = CreatePortalRequest{} - mi := &file_c2_proto_msgTypes[21] + mi := &file_c2_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1279,7 +1118,7 @@ func (x *CreatePortalRequest) String() string { func (*CreatePortalRequest) ProtoMessage() {} func (x *CreatePortalRequest) ProtoReflect() protoreflect.Message { - mi := &file_c2_proto_msgTypes[21] + mi := &file_c2_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1292,7 +1131,7 @@ func (x *CreatePortalRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePortalRequest.ProtoReflect.Descriptor instead. func (*CreatePortalRequest) Descriptor() ([]byte, []int) { - return file_c2_proto_rawDescGZIP(), []int{21} + return file_c2_proto_rawDescGZIP(), []int{19} } func (x *CreatePortalRequest) GetTaskId() int64 { @@ -1318,7 +1157,7 @@ type CreatePortalResponse struct { func (x *CreatePortalResponse) Reset() { *x = CreatePortalResponse{} - mi := &file_c2_proto_msgTypes[22] + mi := &file_c2_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1330,7 +1169,7 @@ func (x *CreatePortalResponse) String() string { func (*CreatePortalResponse) ProtoMessage() {} func (x *CreatePortalResponse) ProtoReflect() protoreflect.Message { - mi := &file_c2_proto_msgTypes[22] + mi := &file_c2_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1343,7 +1182,7 @@ func (x *CreatePortalResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePortalResponse.ProtoReflect.Descriptor instead. func (*CreatePortalResponse) Descriptor() ([]byte, []int) { - return file_c2_proto_rawDescGZIP(), []int{22} + return file_c2_proto_rawDescGZIP(), []int{20} } func (x *CreatePortalResponse) GetMote() *portalpb.Mote { @@ -1355,192 +1194,101 @@ func (x *CreatePortalResponse) GetMote() *portalpb.Mote { var File_c2_proto protoreflect.FileDescriptor -var file_c2_proto_rawDesc = string([]byte{ - 0x0a, 0x08, 0x63, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x63, 0x32, 0x1a, 0x1f, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, - 0x0e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, - 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x27, 0x0a, - 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x22, 0xe2, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1a, 0x0a, 0x08, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x63, 0x32, 0x2e, 0x41, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x78, 0x74, 0x72, 0x61, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x78, 0x74, 0x72, 0x61, 0x22, 0x5d, 0x0a, 0x04, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x50, 0x4f, 0x52, - 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x12, 0x0a, 0x0e, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x47, 0x52, 0x50, - 0x43, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x50, 0x4f, 0x52, 0x54, - 0x5f, 0x48, 0x54, 0x54, 0x50, 0x31, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x52, 0x41, 0x4e, - 0x53, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x44, 0x4e, 0x53, 0x10, 0x03, 0x22, 0xc5, 0x01, 0x0a, 0x06, - 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, - 0x70, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, - 0x69, 0x70, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x63, 0x32, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x04, 0x68, 0x6f, - 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x09, 0x2e, 0x63, 0x32, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x63, 0x32, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, - 0x72, 0x74, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x22, 0xfe, 0x01, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x2d, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x63, 0x32, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x2e, 0x50, 0x6c, 0x61, - 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x70, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x70, 0x22, 0x74, - 0x0a, 0x08, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x4c, - 0x41, 0x54, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x4c, 0x41, 0x54, 0x46, 0x4f, 0x52, 0x4d, - 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4c, - 0x41, 0x54, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x10, 0x02, 0x12, 0x12, - 0x0a, 0x0e, 0x50, 0x4c, 0x41, 0x54, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x4d, 0x41, 0x43, 0x4f, 0x53, - 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x4c, 0x41, 0x54, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x42, - 0x53, 0x44, 0x10, 0x04, 0x22, 0x59, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x04, - 0x74, 0x6f, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6c, 0x64, - 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x54, 0x6f, 0x6d, 0x65, 0x52, 0x04, 0x74, 0x6f, 0x6d, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, - 0x1d, 0x0a, 0x09, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, - 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0xe3, - 0x01, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x63, 0x32, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x0f, 0x65, 0x78, - 0x65, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0d, 0x65, 0x78, 0x65, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x44, - 0x0a, 0x10, 0x65, 0x78, 0x65, 0x63, 0x5f, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x65, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, - 0x65, 0x64, 0x41, 0x74, 0x22, 0x37, 0x0a, 0x11, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, - 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x06, 0x62, 0x65, 0x61, - 0x63, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x63, 0x32, 0x2e, 0x42, - 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x52, 0x06, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x22, 0x34, 0x0a, - 0x12, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x63, 0x32, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x22, 0x27, 0x0a, 0x11, 0x46, 0x65, 0x74, 0x63, 0x68, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x12, - 0x46, 0x65, 0x74, 0x63, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x22, 0x68, 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x0a, - 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x22, 0x1a, 0x0a, 0x18, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, - 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x05, - 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6c, - 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x63, 0x68, 0x75, - 0x6e, 0x6b, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x18, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x29, 0x0a, - 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x65, 0x6c, - 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x0a, 0x17, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, - 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x26, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0e, 0x2e, 0x63, 0x32, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x1a, 0x0a, 0x18, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x73, 0x0a, 0x13, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, - 0x68, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x6b, - 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, 0x32, 0x2e, 0x52, - 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x5b, 0x0a, 0x14, 0x52, 0x65, 0x76, - 0x65, 0x72, 0x73, 0x65, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x1b, 0x2e, 0x63, 0x32, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x68, 0x65, 0x6c, - 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x50, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x4d, 0x6f, - 0x74, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x74, 0x65, 0x22, 0x38, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x4d, 0x6f, 0x74, 0x65, 0x52, 0x04, 0x6d, 0x6f, - 0x74, 0x65, 0x2a, 0x8f, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x68, - 0x65, 0x6c, 0x6c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x2a, - 0x0a, 0x26, 0x52, 0x45, 0x56, 0x45, 0x52, 0x53, 0x45, 0x5f, 0x53, 0x48, 0x45, 0x4c, 0x4c, 0x5f, - 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x52, 0x45, - 0x56, 0x45, 0x52, 0x53, 0x45, 0x5f, 0x53, 0x48, 0x45, 0x4c, 0x4c, 0x5f, 0x4d, 0x45, 0x53, 0x53, - 0x41, 0x47, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x01, 0x12, - 0x23, 0x0a, 0x1f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x53, 0x45, 0x5f, 0x53, 0x48, 0x45, 0x4c, 0x4c, - 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x50, 0x49, - 0x4e, 0x47, 0x10, 0x02, 0x32, 0xc5, 0x04, 0x0a, 0x02, 0x43, 0x32, 0x12, 0x3d, 0x0a, 0x0a, 0x43, - 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x15, 0x2e, 0x63, 0x32, 0x2e, 0x43, - 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x63, 0x32, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x46, 0x65, - 0x74, 0x63, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x63, 0x32, 0x2e, 0x46, 0x65, - 0x74, 0x63, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x63, 0x32, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x10, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x2e, - 0x63, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, 0x32, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x15, 0x2e, 0x63, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x63, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x50, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, - 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x63, - 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x32, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x10, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x2e, - 0x63, 0x32, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, 0x32, 0x2e, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0c, 0x52, 0x65, - 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x12, 0x17, 0x2e, 0x63, 0x32, 0x2e, - 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x32, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, - 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, - 0x01, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, - 0x74, 0x61, 0x6c, 0x12, 0x17, 0x2e, 0x63, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, - 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, - 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x23, 0x5a, 0x21, - 0x72, 0x65, 0x61, 0x6c, 0x6d, 0x2e, 0x70, 0x75, 0x62, 0x2f, 0x74, 0x61, 0x76, 0x65, 0x72, 0x6e, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x32, 0x2f, 0x63, 0x32, 0x70, - 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_c2_proto_rawDesc = "" + + "\n" + + "\bc2.proto\x12\x02c2\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x0eeldritch.proto\x1a\fportal.proto\"'\n" + + "\x05Agent\x12\x1e\n" + + "\n" + + "identifier\x18\x01 \x01(\tR\n" + + "identifier\"\xe2\x01\n" + + "\x0fActiveTransport\x12\x10\n" + + "\x03uri\x18\x01 \x01(\tR\x03uri\x12\x1a\n" + + "\binterval\x18\x02 \x01(\x04R\binterval\x12,\n" + + "\x04type\x18\x03 \x01(\x0e2\x18.c2.ActiveTransport.TypeR\x04type\x12\x14\n" + + "\x05extra\x18\x04 \x01(\tR\x05extra\"]\n" + + "\x04Type\x12\x19\n" + + "\x15TRANSPORT_UNSPECIFIED\x10\x00\x12\x12\n" + + "\x0eTRANSPORT_GRPC\x10\x01\x12\x13\n" + + "\x0fTRANSPORT_HTTP1\x10\x02\x12\x11\n" + + "\rTRANSPORT_DNS\x10\x03\"\xc5\x01\n" + + "\x06Beacon\x12\x1e\n" + + "\n" + + "identifier\x18\x01 \x01(\tR\n" + + "identifier\x12\x1c\n" + + "\tprincipal\x18\x02 \x01(\tR\tprincipal\x12\x1c\n" + + "\x04host\x18\x03 \x01(\v2\b.c2.HostR\x04host\x12\x1f\n" + + "\x05agent\x18\x04 \x01(\v2\t.c2.AgentR\x05agent\x12>\n" + + "\x10active_transport\x18\x05 \x01(\v2\x13.c2.ActiveTransportR\x0factiveTransport\"\xfe\x01\n" + + "\x04Host\x12\x1e\n" + + "\n" + + "identifier\x18\x01 \x01(\tR\n" + + "identifier\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12-\n" + + "\bplatform\x18\x03 \x01(\x0e2\x11.c2.Host.PlatformR\bplatform\x12\x1d\n" + + "\n" + + "primary_ip\x18\x04 \x01(\tR\tprimaryIp\"t\n" + + "\bPlatform\x12\x18\n" + + "\x14PLATFORM_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10PLATFORM_WINDOWS\x10\x01\x12\x12\n" + + "\x0ePLATFORM_LINUX\x10\x02\x12\x12\n" + + "\x0ePLATFORM_MACOS\x10\x03\x12\x10\n" + + "\fPLATFORM_BSD\x10\x04\"Y\n" + + "\x04Task\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\"\n" + + "\x04tome\x18\x02 \x01(\v2\x0e.eldritch.TomeR\x04tome\x12\x1d\n" + + "\n" + + "quest_name\x18\x03 \x01(\tR\tquestName\"\x1d\n" + + "\tTaskError\x12\x10\n" + + "\x03msg\x18\x01 \x01(\tR\x03msg\"\xe3\x01\n" + + "\n" + + "TaskOutput\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + + "\x06output\x18\x02 \x01(\tR\x06output\x12#\n" + + "\x05error\x18\x03 \x01(\v2\r.c2.TaskErrorR\x05error\x12B\n" + + "\x0fexec_started_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\rexecStartedAt\x12D\n" + + "\x10exec_finished_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x0eexecFinishedAt\"7\n" + + "\x11ClaimTasksRequest\x12\"\n" + + "\x06beacon\x18\x01 \x01(\v2\n" + + ".c2.BeaconR\x06beacon\"4\n" + + "\x12ClaimTasksResponse\x12\x1e\n" + + "\x05tasks\x18\x01 \x03(\v2\b.c2.TaskR\x05tasks\"'\n" + + "\x11FetchAssetRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"*\n" + + "\x12FetchAssetResponse\x12\x14\n" + + "\x05chunk\x18\x01 \x01(\fR\x05chunk\"h\n" + + "\x17ReportCredentialRequest\x12\x17\n" + + "\atask_id\x18\x01 \x01(\x03R\x06taskId\x124\n" + + "\n" + + "credential\x18\x02 \x01(\v2\x14.eldritch.CredentialR\n" + + "credential\"\x1a\n" + + "\x18ReportCredentialResponse\"R\n" + + "\x11ReportFileRequest\x12\x17\n" + + "\atask_id\x18\x01 \x01(\x03R\x06taskId\x12$\n" + + "\x05chunk\x18\x02 \x01(\v2\x0e.eldritch.FileR\x05chunk\"\x14\n" + + "\x12ReportFileResponse\"^\n" + + "\x18ReportProcessListRequest\x12\x17\n" + + "\atask_id\x18\x01 \x01(\x03R\x06taskId\x12)\n" + + "\x04list\x18\x02 \x01(\v2\x15.eldritch.ProcessListR\x04list\"\x1b\n" + + "\x19ReportProcessListResponse\"A\n" + + "\x17ReportTaskOutputRequest\x12&\n" + + "\x06output\x18\x01 \x01(\v2\x0e.c2.TaskOutputR\x06output\"\x1a\n" + + "\x18ReportTaskOutputResponse\"P\n" + + "\x13CreatePortalRequest\x12\x17\n" + + "\atask_id\x18\x01 \x01(\x03R\x06taskId\x12 \n" + + "\x04mote\x18\x02 \x01(\v2\f.portal.MoteR\x04mote\"8\n" + + "\x14CreatePortalResponse\x12 \n" + + "\x04mote\x18\x02 \x01(\v2\f.portal.MoteR\x04mote2\xfc\x03\n" + + "\x02C2\x12=\n" + + "\n" + + "ClaimTasks\x12\x15.c2.ClaimTasksRequest\x1a\x16.c2.ClaimTasksResponse\"\x00\x12=\n" + + "\n" + + "FetchAsset\x12\x15.c2.FetchAssetRequest\x1a\x16.c2.FetchAssetResponse0\x01\x12M\n" + + "\x10ReportCredential\x12\x1b.c2.ReportCredentialRequest\x1a\x1c.c2.ReportCredentialResponse\x12=\n" + + "\n" + + "ReportFile\x12\x15.c2.ReportFileRequest\x1a\x16.c2.ReportFileResponse(\x01\x12P\n" + + "\x11ReportProcessList\x12\x1c.c2.ReportProcessListRequest\x1a\x1d.c2.ReportProcessListResponse\x12O\n" + + "\x10ReportTaskOutput\x12\x1b.c2.ReportTaskOutputRequest\x1a\x1c.c2.ReportTaskOutputResponse\"\x00\x12G\n" + + "\fCreatePortal\x12\x17.c2.CreatePortalRequest\x1a\x18.c2.CreatePortalResponse\"\x00(\x010\x01B#Z!realm.pub/tavern/internal/c2/c2pbb\x06proto3" var ( file_c2_proto_rawDescOnce sync.Once @@ -1554,83 +1302,76 @@ func file_c2_proto_rawDescGZIP() []byte { return file_c2_proto_rawDescData } -var file_c2_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_c2_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_c2_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_c2_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_c2_proto_goTypes = []any{ - (ReverseShellMessageKind)(0), // 0: c2.ReverseShellMessageKind - (ActiveTransport_Type)(0), // 1: c2.ActiveTransport.Type - (Host_Platform)(0), // 2: c2.Host.Platform - (*Agent)(nil), // 3: c2.Agent - (*ActiveTransport)(nil), // 4: c2.ActiveTransport - (*Beacon)(nil), // 5: c2.Beacon - (*Host)(nil), // 6: c2.Host - (*Task)(nil), // 7: c2.Task - (*TaskError)(nil), // 8: c2.TaskError - (*TaskOutput)(nil), // 9: c2.TaskOutput - (*ClaimTasksRequest)(nil), // 10: c2.ClaimTasksRequest - (*ClaimTasksResponse)(nil), // 11: c2.ClaimTasksResponse - (*FetchAssetRequest)(nil), // 12: c2.FetchAssetRequest - (*FetchAssetResponse)(nil), // 13: c2.FetchAssetResponse - (*ReportCredentialRequest)(nil), // 14: c2.ReportCredentialRequest - (*ReportCredentialResponse)(nil), // 15: c2.ReportCredentialResponse - (*ReportFileRequest)(nil), // 16: c2.ReportFileRequest - (*ReportFileResponse)(nil), // 17: c2.ReportFileResponse - (*ReportProcessListRequest)(nil), // 18: c2.ReportProcessListRequest - (*ReportProcessListResponse)(nil), // 19: c2.ReportProcessListResponse - (*ReportTaskOutputRequest)(nil), // 20: c2.ReportTaskOutputRequest - (*ReportTaskOutputResponse)(nil), // 21: c2.ReportTaskOutputResponse - (*ReverseShellRequest)(nil), // 22: c2.ReverseShellRequest - (*ReverseShellResponse)(nil), // 23: c2.ReverseShellResponse - (*CreatePortalRequest)(nil), // 24: c2.CreatePortalRequest - (*CreatePortalResponse)(nil), // 25: c2.CreatePortalResponse - (*epb.Tome)(nil), // 26: eldritch.Tome - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*epb.Credential)(nil), // 28: eldritch.Credential - (*epb.File)(nil), // 29: eldritch.File - (*epb.ProcessList)(nil), // 30: eldritch.ProcessList - (*portalpb.Mote)(nil), // 31: portal.Mote + (ActiveTransport_Type)(0), // 0: c2.ActiveTransport.Type + (Host_Platform)(0), // 1: c2.Host.Platform + (*Agent)(nil), // 2: c2.Agent + (*ActiveTransport)(nil), // 3: c2.ActiveTransport + (*Beacon)(nil), // 4: c2.Beacon + (*Host)(nil), // 5: c2.Host + (*Task)(nil), // 6: c2.Task + (*TaskError)(nil), // 7: c2.TaskError + (*TaskOutput)(nil), // 8: c2.TaskOutput + (*ClaimTasksRequest)(nil), // 9: c2.ClaimTasksRequest + (*ClaimTasksResponse)(nil), // 10: c2.ClaimTasksResponse + (*FetchAssetRequest)(nil), // 11: c2.FetchAssetRequest + (*FetchAssetResponse)(nil), // 12: c2.FetchAssetResponse + (*ReportCredentialRequest)(nil), // 13: c2.ReportCredentialRequest + (*ReportCredentialResponse)(nil), // 14: c2.ReportCredentialResponse + (*ReportFileRequest)(nil), // 15: c2.ReportFileRequest + (*ReportFileResponse)(nil), // 16: c2.ReportFileResponse + (*ReportProcessListRequest)(nil), // 17: c2.ReportProcessListRequest + (*ReportProcessListResponse)(nil), // 18: c2.ReportProcessListResponse + (*ReportTaskOutputRequest)(nil), // 19: c2.ReportTaskOutputRequest + (*ReportTaskOutputResponse)(nil), // 20: c2.ReportTaskOutputResponse + (*CreatePortalRequest)(nil), // 21: c2.CreatePortalRequest + (*CreatePortalResponse)(nil), // 22: c2.CreatePortalResponse + (*epb.Tome)(nil), // 23: eldritch.Tome + (*timestamppb.Timestamp)(nil), // 24: google.protobuf.Timestamp + (*epb.Credential)(nil), // 25: eldritch.Credential + (*epb.File)(nil), // 26: eldritch.File + (*epb.ProcessList)(nil), // 27: eldritch.ProcessList + (*portalpb.Mote)(nil), // 28: portal.Mote } var file_c2_proto_depIdxs = []int32{ - 1, // 0: c2.ActiveTransport.type:type_name -> c2.ActiveTransport.Type - 6, // 1: c2.Beacon.host:type_name -> c2.Host - 3, // 2: c2.Beacon.agent:type_name -> c2.Agent - 4, // 3: c2.Beacon.active_transport:type_name -> c2.ActiveTransport - 2, // 4: c2.Host.platform:type_name -> c2.Host.Platform - 26, // 5: c2.Task.tome:type_name -> eldritch.Tome - 8, // 6: c2.TaskOutput.error:type_name -> c2.TaskError - 27, // 7: c2.TaskOutput.exec_started_at:type_name -> google.protobuf.Timestamp - 27, // 8: c2.TaskOutput.exec_finished_at:type_name -> google.protobuf.Timestamp - 5, // 9: c2.ClaimTasksRequest.beacon:type_name -> c2.Beacon - 7, // 10: c2.ClaimTasksResponse.tasks:type_name -> c2.Task - 28, // 11: c2.ReportCredentialRequest.credential:type_name -> eldritch.Credential - 29, // 12: c2.ReportFileRequest.chunk:type_name -> eldritch.File - 30, // 13: c2.ReportProcessListRequest.list:type_name -> eldritch.ProcessList - 9, // 14: c2.ReportTaskOutputRequest.output:type_name -> c2.TaskOutput - 0, // 15: c2.ReverseShellRequest.kind:type_name -> c2.ReverseShellMessageKind - 0, // 16: c2.ReverseShellResponse.kind:type_name -> c2.ReverseShellMessageKind - 31, // 17: c2.CreatePortalRequest.mote:type_name -> portal.Mote - 31, // 18: c2.CreatePortalResponse.mote:type_name -> portal.Mote - 10, // 19: c2.C2.ClaimTasks:input_type -> c2.ClaimTasksRequest - 12, // 20: c2.C2.FetchAsset:input_type -> c2.FetchAssetRequest - 14, // 21: c2.C2.ReportCredential:input_type -> c2.ReportCredentialRequest - 16, // 22: c2.C2.ReportFile:input_type -> c2.ReportFileRequest - 18, // 23: c2.C2.ReportProcessList:input_type -> c2.ReportProcessListRequest - 20, // 24: c2.C2.ReportTaskOutput:input_type -> c2.ReportTaskOutputRequest - 22, // 25: c2.C2.ReverseShell:input_type -> c2.ReverseShellRequest - 24, // 26: c2.C2.CreatePortal:input_type -> c2.CreatePortalRequest - 11, // 27: c2.C2.ClaimTasks:output_type -> c2.ClaimTasksResponse - 13, // 28: c2.C2.FetchAsset:output_type -> c2.FetchAssetResponse - 15, // 29: c2.C2.ReportCredential:output_type -> c2.ReportCredentialResponse - 17, // 30: c2.C2.ReportFile:output_type -> c2.ReportFileResponse - 19, // 31: c2.C2.ReportProcessList:output_type -> c2.ReportProcessListResponse - 21, // 32: c2.C2.ReportTaskOutput:output_type -> c2.ReportTaskOutputResponse - 23, // 33: c2.C2.ReverseShell:output_type -> c2.ReverseShellResponse - 25, // 34: c2.C2.CreatePortal:output_type -> c2.CreatePortalResponse - 27, // [27:35] is the sub-list for method output_type - 19, // [19:27] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 0, // 0: c2.ActiveTransport.type:type_name -> c2.ActiveTransport.Type + 5, // 1: c2.Beacon.host:type_name -> c2.Host + 2, // 2: c2.Beacon.agent:type_name -> c2.Agent + 3, // 3: c2.Beacon.active_transport:type_name -> c2.ActiveTransport + 1, // 4: c2.Host.platform:type_name -> c2.Host.Platform + 23, // 5: c2.Task.tome:type_name -> eldritch.Tome + 7, // 6: c2.TaskOutput.error:type_name -> c2.TaskError + 24, // 7: c2.TaskOutput.exec_started_at:type_name -> google.protobuf.Timestamp + 24, // 8: c2.TaskOutput.exec_finished_at:type_name -> google.protobuf.Timestamp + 4, // 9: c2.ClaimTasksRequest.beacon:type_name -> c2.Beacon + 6, // 10: c2.ClaimTasksResponse.tasks:type_name -> c2.Task + 25, // 11: c2.ReportCredentialRequest.credential:type_name -> eldritch.Credential + 26, // 12: c2.ReportFileRequest.chunk:type_name -> eldritch.File + 27, // 13: c2.ReportProcessListRequest.list:type_name -> eldritch.ProcessList + 8, // 14: c2.ReportTaskOutputRequest.output:type_name -> c2.TaskOutput + 28, // 15: c2.CreatePortalRequest.mote:type_name -> portal.Mote + 28, // 16: c2.CreatePortalResponse.mote:type_name -> portal.Mote + 9, // 17: c2.C2.ClaimTasks:input_type -> c2.ClaimTasksRequest + 11, // 18: c2.C2.FetchAsset:input_type -> c2.FetchAssetRequest + 13, // 19: c2.C2.ReportCredential:input_type -> c2.ReportCredentialRequest + 15, // 20: c2.C2.ReportFile:input_type -> c2.ReportFileRequest + 17, // 21: c2.C2.ReportProcessList:input_type -> c2.ReportProcessListRequest + 19, // 22: c2.C2.ReportTaskOutput:input_type -> c2.ReportTaskOutputRequest + 21, // 23: c2.C2.CreatePortal:input_type -> c2.CreatePortalRequest + 10, // 24: c2.C2.ClaimTasks:output_type -> c2.ClaimTasksResponse + 12, // 25: c2.C2.FetchAsset:output_type -> c2.FetchAssetResponse + 14, // 26: c2.C2.ReportCredential:output_type -> c2.ReportCredentialResponse + 16, // 27: c2.C2.ReportFile:output_type -> c2.ReportFileResponse + 18, // 28: c2.C2.ReportProcessList:output_type -> c2.ReportProcessListResponse + 20, // 29: c2.C2.ReportTaskOutput:output_type -> c2.ReportTaskOutputResponse + 22, // 30: c2.C2.CreatePortal:output_type -> c2.CreatePortalResponse + 24, // [24:31] is the sub-list for method output_type + 17, // [17:24] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_c2_proto_init() } @@ -1643,8 +1384,8 @@ func file_c2_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_c2_proto_rawDesc), len(file_c2_proto_rawDesc)), - NumEnums: 3, - NumMessages: 23, + NumEnums: 2, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/tavern/internal/c2/c2pb/c2_grpc.pb.go b/tavern/internal/c2/c2pb/c2_grpc.pb.go index 3a45ce6fb..103e74dfa 100644 --- a/tavern/internal/c2/c2pb/c2_grpc.pb.go +++ b/tavern/internal/c2/c2pb/c2_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 +// - protoc-gen-go-grpc v1.6.0 // - protoc v3.21.12 // source: c2.proto @@ -15,8 +15,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.62.0 or later. -const _ = grpc.SupportPackageIsVersion8 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( C2_ClaimTasks_FullMethodName = "/c2.C2/ClaimTasks" @@ -25,7 +25,6 @@ const ( C2_ReportFile_FullMethodName = "/c2.C2/ReportFile" C2_ReportProcessList_FullMethodName = "/c2.C2/ReportProcessList" C2_ReportTaskOutput_FullMethodName = "/c2.C2/ReportTaskOutput" - C2_ReverseShell_FullMethodName = "/c2.C2/ReverseShell" C2_CreatePortal_FullMethodName = "/c2.C2/CreatePortal" ) @@ -42,7 +41,7 @@ type C2Client interface { // - "file-size": The number of bytes contained by the file. // // If no associated file can be found, a NotFound status error is returned. - FetchAsset(ctx context.Context, in *FetchAssetRequest, opts ...grpc.CallOption) (C2_FetchAssetClient, error) + FetchAsset(ctx context.Context, in *FetchAssetRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FetchAssetResponse], error) // Report a credential from the host to the server. ReportCredential(ctx context.Context, in *ReportCredentialRequest, opts ...grpc.CallOption) (*ReportCredentialResponse, error) // Report a file from the host to the server. @@ -52,16 +51,14 @@ type C2Client interface { // // Content is provided as chunks, the size of which are up to the agent to define (based on memory constraints). // Any existing files at the provided path for the host are replaced. - ReportFile(ctx context.Context, opts ...grpc.CallOption) (C2_ReportFileClient, error) + ReportFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[ReportFileRequest, ReportFileResponse], error) // Report the active list of running processes. This list will replace any previously reported // lists for the same host. ReportProcessList(ctx context.Context, in *ReportProcessListRequest, opts ...grpc.CallOption) (*ReportProcessListResponse, error) // Report execution output for a task. ReportTaskOutput(ctx context.Context, in *ReportTaskOutputRequest, opts ...grpc.CallOption) (*ReportTaskOutputResponse, error) - // Open a reverse shell bi-directional stream. - ReverseShell(ctx context.Context, opts ...grpc.CallOption) (C2_ReverseShellClient, error) // Open a portal bi-directional stream. - CreatePortal(ctx context.Context, opts ...grpc.CallOption) (C2_CreatePortalClient, error) + CreatePortal(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[CreatePortalRequest, CreatePortalResponse], error) } type c2Client struct { @@ -82,13 +79,13 @@ func (c *c2Client) ClaimTasks(ctx context.Context, in *ClaimTasksRequest, opts . return out, nil } -func (c *c2Client) FetchAsset(ctx context.Context, in *FetchAssetRequest, opts ...grpc.CallOption) (C2_FetchAssetClient, error) { +func (c *c2Client) FetchAsset(ctx context.Context, in *FetchAssetRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FetchAssetResponse], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &C2_ServiceDesc.Streams[0], C2_FetchAsset_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &c2FetchAssetClient{ClientStream: stream} + x := &grpc.GenericClientStream[FetchAssetRequest, FetchAssetResponse]{ClientStream: stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } @@ -98,22 +95,8 @@ func (c *c2Client) FetchAsset(ctx context.Context, in *FetchAssetRequest, opts . return x, nil } -type C2_FetchAssetClient interface { - Recv() (*FetchAssetResponse, error) - grpc.ClientStream -} - -type c2FetchAssetClient struct { - grpc.ClientStream -} - -func (x *c2FetchAssetClient) Recv() (*FetchAssetResponse, error) { - m := new(FetchAssetResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type C2_FetchAssetClient = grpc.ServerStreamingClient[FetchAssetResponse] func (c *c2Client) ReportCredential(ctx context.Context, in *ReportCredentialRequest, opts ...grpc.CallOption) (*ReportCredentialResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) @@ -125,40 +108,18 @@ func (c *c2Client) ReportCredential(ctx context.Context, in *ReportCredentialReq return out, nil } -func (c *c2Client) ReportFile(ctx context.Context, opts ...grpc.CallOption) (C2_ReportFileClient, error) { +func (c *c2Client) ReportFile(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[ReportFileRequest, ReportFileResponse], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &C2_ServiceDesc.Streams[1], C2_ReportFile_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &c2ReportFileClient{ClientStream: stream} + x := &grpc.GenericClientStream[ReportFileRequest, ReportFileResponse]{ClientStream: stream} return x, nil } -type C2_ReportFileClient interface { - Send(*ReportFileRequest) error - CloseAndRecv() (*ReportFileResponse, error) - grpc.ClientStream -} - -type c2ReportFileClient struct { - grpc.ClientStream -} - -func (x *c2ReportFileClient) Send(m *ReportFileRequest) error { - return x.ClientStream.SendMsg(m) -} - -func (x *c2ReportFileClient) CloseAndRecv() (*ReportFileResponse, error) { - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - m := new(ReportFileResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type C2_ReportFileClient = grpc.ClientStreamingClient[ReportFileRequest, ReportFileResponse] func (c *c2Client) ReportProcessList(ctx context.Context, in *ReportProcessListRequest, opts ...grpc.CallOption) (*ReportProcessListResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) @@ -180,73 +141,22 @@ func (c *c2Client) ReportTaskOutput(ctx context.Context, in *ReportTaskOutputReq return out, nil } -func (c *c2Client) ReverseShell(ctx context.Context, opts ...grpc.CallOption) (C2_ReverseShellClient, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &C2_ServiceDesc.Streams[2], C2_ReverseShell_FullMethodName, cOpts...) - if err != nil { - return nil, err - } - x := &c2ReverseShellClient{ClientStream: stream} - return x, nil -} - -type C2_ReverseShellClient interface { - Send(*ReverseShellRequest) error - Recv() (*ReverseShellResponse, error) - grpc.ClientStream -} - -type c2ReverseShellClient struct { - grpc.ClientStream -} - -func (x *c2ReverseShellClient) Send(m *ReverseShellRequest) error { - return x.ClientStream.SendMsg(m) -} - -func (x *c2ReverseShellClient) Recv() (*ReverseShellResponse, error) { - m := new(ReverseShellResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *c2Client) CreatePortal(ctx context.Context, opts ...grpc.CallOption) (C2_CreatePortalClient, error) { +func (c *c2Client) CreatePortal(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[CreatePortalRequest, CreatePortalResponse], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &C2_ServiceDesc.Streams[3], C2_CreatePortal_FullMethodName, cOpts...) + stream, err := c.cc.NewStream(ctx, &C2_ServiceDesc.Streams[2], C2_CreatePortal_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &c2CreatePortalClient{ClientStream: stream} + x := &grpc.GenericClientStream[CreatePortalRequest, CreatePortalResponse]{ClientStream: stream} return x, nil } -type C2_CreatePortalClient interface { - Send(*CreatePortalRequest) error - Recv() (*CreatePortalResponse, error) - grpc.ClientStream -} - -type c2CreatePortalClient struct { - grpc.ClientStream -} - -func (x *c2CreatePortalClient) Send(m *CreatePortalRequest) error { - return x.ClientStream.SendMsg(m) -} - -func (x *c2CreatePortalClient) Recv() (*CreatePortalResponse, error) { - m := new(CreatePortalResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type C2_CreatePortalClient = grpc.BidiStreamingClient[CreatePortalRequest, CreatePortalResponse] // C2Server is the server API for C2 service. // All implementations must embed UnimplementedC2Server -// for forward compatibility +// for forward compatibility. type C2Server interface { // Contact the server for new tasks to execute. ClaimTasks(context.Context, *ClaimTasksRequest) (*ClaimTasksResponse, error) @@ -257,7 +167,7 @@ type C2Server interface { // - "file-size": The number of bytes contained by the file. // // If no associated file can be found, a NotFound status error is returned. - FetchAsset(*FetchAssetRequest, C2_FetchAssetServer) error + FetchAsset(*FetchAssetRequest, grpc.ServerStreamingServer[FetchAssetResponse]) error // Report a credential from the host to the server. ReportCredential(context.Context, *ReportCredentialRequest) (*ReportCredentialResponse, error) // Report a file from the host to the server. @@ -267,48 +177,47 @@ type C2Server interface { // // Content is provided as chunks, the size of which are up to the agent to define (based on memory constraints). // Any existing files at the provided path for the host are replaced. - ReportFile(C2_ReportFileServer) error + ReportFile(grpc.ClientStreamingServer[ReportFileRequest, ReportFileResponse]) error // Report the active list of running processes. This list will replace any previously reported // lists for the same host. ReportProcessList(context.Context, *ReportProcessListRequest) (*ReportProcessListResponse, error) // Report execution output for a task. ReportTaskOutput(context.Context, *ReportTaskOutputRequest) (*ReportTaskOutputResponse, error) - // Open a reverse shell bi-directional stream. - ReverseShell(C2_ReverseShellServer) error // Open a portal bi-directional stream. - CreatePortal(C2_CreatePortalServer) error + CreatePortal(grpc.BidiStreamingServer[CreatePortalRequest, CreatePortalResponse]) error mustEmbedUnimplementedC2Server() } -// UnimplementedC2Server must be embedded to have forward compatible implementations. -type UnimplementedC2Server struct { -} +// UnimplementedC2Server must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedC2Server struct{} func (UnimplementedC2Server) ClaimTasks(context.Context, *ClaimTasksRequest) (*ClaimTasksResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ClaimTasks not implemented") + return nil, status.Error(codes.Unimplemented, "method ClaimTasks not implemented") } -func (UnimplementedC2Server) FetchAsset(*FetchAssetRequest, C2_FetchAssetServer) error { - return status.Errorf(codes.Unimplemented, "method FetchAsset not implemented") +func (UnimplementedC2Server) FetchAsset(*FetchAssetRequest, grpc.ServerStreamingServer[FetchAssetResponse]) error { + return status.Error(codes.Unimplemented, "method FetchAsset not implemented") } func (UnimplementedC2Server) ReportCredential(context.Context, *ReportCredentialRequest) (*ReportCredentialResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReportCredential not implemented") + return nil, status.Error(codes.Unimplemented, "method ReportCredential not implemented") } -func (UnimplementedC2Server) ReportFile(C2_ReportFileServer) error { - return status.Errorf(codes.Unimplemented, "method ReportFile not implemented") +func (UnimplementedC2Server) ReportFile(grpc.ClientStreamingServer[ReportFileRequest, ReportFileResponse]) error { + return status.Error(codes.Unimplemented, "method ReportFile not implemented") } func (UnimplementedC2Server) ReportProcessList(context.Context, *ReportProcessListRequest) (*ReportProcessListResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReportProcessList not implemented") + return nil, status.Error(codes.Unimplemented, "method ReportProcessList not implemented") } func (UnimplementedC2Server) ReportTaskOutput(context.Context, *ReportTaskOutputRequest) (*ReportTaskOutputResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ReportTaskOutput not implemented") + return nil, status.Error(codes.Unimplemented, "method ReportTaskOutput not implemented") } -func (UnimplementedC2Server) ReverseShell(C2_ReverseShellServer) error { - return status.Errorf(codes.Unimplemented, "method ReverseShell not implemented") -} -func (UnimplementedC2Server) CreatePortal(C2_CreatePortalServer) error { - return status.Errorf(codes.Unimplemented, "method CreatePortal not implemented") +func (UnimplementedC2Server) CreatePortal(grpc.BidiStreamingServer[CreatePortalRequest, CreatePortalResponse]) error { + return status.Error(codes.Unimplemented, "method CreatePortal not implemented") } func (UnimplementedC2Server) mustEmbedUnimplementedC2Server() {} +func (UnimplementedC2Server) testEmbeddedByValue() {} // UnsafeC2Server may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to C2Server will @@ -318,6 +227,13 @@ type UnsafeC2Server interface { } func RegisterC2Server(s grpc.ServiceRegistrar, srv C2Server) { + // If the following call panics, it indicates UnimplementedC2Server was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&C2_ServiceDesc, srv) } @@ -344,21 +260,11 @@ func _C2_FetchAsset_Handler(srv interface{}, stream grpc.ServerStream) error { if err := stream.RecvMsg(m); err != nil { return err } - return srv.(C2Server).FetchAsset(m, &c2FetchAssetServer{ServerStream: stream}) + return srv.(C2Server).FetchAsset(m, &grpc.GenericServerStream[FetchAssetRequest, FetchAssetResponse]{ServerStream: stream}) } -type C2_FetchAssetServer interface { - Send(*FetchAssetResponse) error - grpc.ServerStream -} - -type c2FetchAssetServer struct { - grpc.ServerStream -} - -func (x *c2FetchAssetServer) Send(m *FetchAssetResponse) error { - return x.ServerStream.SendMsg(m) -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type C2_FetchAssetServer = grpc.ServerStreamingServer[FetchAssetResponse] func _C2_ReportCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReportCredentialRequest) @@ -379,30 +285,11 @@ func _C2_ReportCredential_Handler(srv interface{}, ctx context.Context, dec func } func _C2_ReportFile_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(C2Server).ReportFile(&c2ReportFileServer{ServerStream: stream}) -} - -type C2_ReportFileServer interface { - SendAndClose(*ReportFileResponse) error - Recv() (*ReportFileRequest, error) - grpc.ServerStream -} - -type c2ReportFileServer struct { - grpc.ServerStream + return srv.(C2Server).ReportFile(&grpc.GenericServerStream[ReportFileRequest, ReportFileResponse]{ServerStream: stream}) } -func (x *c2ReportFileServer) SendAndClose(m *ReportFileResponse) error { - return x.ServerStream.SendMsg(m) -} - -func (x *c2ReportFileServer) Recv() (*ReportFileRequest, error) { - m := new(ReportFileRequest) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type C2_ReportFileServer = grpc.ClientStreamingServer[ReportFileRequest, ReportFileResponse] func _C2_ReportProcessList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReportProcessListRequest) @@ -440,57 +327,12 @@ func _C2_ReportTaskOutput_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } -func _C2_ReverseShell_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(C2Server).ReverseShell(&c2ReverseShellServer{ServerStream: stream}) -} - -type C2_ReverseShellServer interface { - Send(*ReverseShellResponse) error - Recv() (*ReverseShellRequest, error) - grpc.ServerStream -} - -type c2ReverseShellServer struct { - grpc.ServerStream -} - -func (x *c2ReverseShellServer) Send(m *ReverseShellResponse) error { - return x.ServerStream.SendMsg(m) -} - -func (x *c2ReverseShellServer) Recv() (*ReverseShellRequest, error) { - m := new(ReverseShellRequest) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - func _C2_CreatePortal_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(C2Server).CreatePortal(&c2CreatePortalServer{ServerStream: stream}) -} - -type C2_CreatePortalServer interface { - Send(*CreatePortalResponse) error - Recv() (*CreatePortalRequest, error) - grpc.ServerStream + return srv.(C2Server).CreatePortal(&grpc.GenericServerStream[CreatePortalRequest, CreatePortalResponse]{ServerStream: stream}) } -type c2CreatePortalServer struct { - grpc.ServerStream -} - -func (x *c2CreatePortalServer) Send(m *CreatePortalResponse) error { - return x.ServerStream.SendMsg(m) -} - -func (x *c2CreatePortalServer) Recv() (*CreatePortalRequest, error) { - m := new(CreatePortalRequest) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type C2_CreatePortalServer = grpc.BidiStreamingServer[CreatePortalRequest, CreatePortalResponse] // C2_ServiceDesc is the grpc.ServiceDesc for C2 service. // It's only intended for direct use with grpc.RegisterService, @@ -527,12 +369,6 @@ var C2_ServiceDesc = grpc.ServiceDesc{ Handler: _C2_ReportFile_Handler, ClientStreams: true, }, - { - StreamName: "ReverseShell", - Handler: _C2_ReverseShell_Handler, - ServerStreams: true, - ClientStreams: true, - }, { StreamName: "CreatePortal", Handler: _C2_CreatePortal_Handler, diff --git a/tavern/internal/c2/dnspb/dns.pb.go b/tavern/internal/c2/dnspb/dns.pb.go index 12c484b81..3cdeb1435 100644 --- a/tavern/internal/c2/dnspb/dns.pb.go +++ b/tavern/internal/c2/dnspb/dns.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.11 // protoc v3.21.12 // source: dns.proto @@ -412,60 +412,45 @@ func (x *ResponseMetadata) GetChunkSize() uint32 { var File_dns_proto protoreflect.FileDescriptor -var file_dns_proto_rawDesc = string([]byte{ - 0x0a, 0x09, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x64, 0x6e, 0x73, - 0x22, 0xf9, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x23, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x64, - 0x6e, 0x73, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, - 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, - 0x63, 0x72, 0x63, 0x33, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x72, 0x63, - 0x33, 0x32, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0d, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x6b, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x52, 0x04, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x63, 0x6b, 0x73, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x05, 0x6e, 0x61, 0x63, 0x6b, 0x73, 0x22, 0x40, 0x0a, 0x08, - 0x41, 0x63, 0x6b, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x73, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x53, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x71, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x71, 0x22, 0x8d, - 0x01, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x68, 0x75, 0x6e, - 0x6b, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x72, 0x63, 0x33, 0x32, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x43, 0x72, 0x63, 0x33, - 0x32, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x2f, - 0x0a, 0x0c, 0x46, 0x65, 0x74, 0x63, 0x68, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, - 0x73, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, - 0x72, 0x63, 0x33, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, - 0x43, 0x72, 0x63, 0x33, 0x32, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x68, 0x75, 0x6e, 0x6b, - 0x53, 0x69, 0x7a, 0x65, 0x2a, 0x84, 0x01, 0x0a, 0x0a, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x49, 0x4e, 0x49, 0x54, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, - 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x45, 0x54, 0x43, - 0x48, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x10, 0x04, 0x42, 0x24, 0x5a, 0x22, 0x72, - 0x65, 0x61, 0x6c, 0x6d, 0x2e, 0x70, 0x75, 0x62, 0x2f, 0x74, 0x61, 0x76, 0x65, 0x72, 0x6e, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x32, 0x2f, 0x64, 0x6e, 0x73, 0x70, - 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_dns_proto_rawDesc = "" + + "\n" + + "\tdns.proto\x12\x03dns\"\xf9\x01\n" + + "\tDNSPacket\x12#\n" + + "\x04type\x18\x01 \x01(\x0e2\x0f.dns.PacketTypeR\x04type\x12\x1a\n" + + "\bsequence\x18\x02 \x01(\rR\bsequence\x12'\n" + + "\x0fconversation_id\x18\x03 \x01(\tR\x0econversationId\x12\x12\n" + + "\x04data\x18\x04 \x01(\fR\x04data\x12\x14\n" + + "\x05crc32\x18\x05 \x01(\rR\x05crc32\x12\x1f\n" + + "\vwindow_size\x18\x06 \x01(\rR\n" + + "windowSize\x12!\n" + + "\x04acks\x18\a \x03(\v2\r.dns.AckRangeR\x04acks\x12\x14\n" + + "\x05nacks\x18\b \x03(\rR\x05nacks\"@\n" + + "\bAckRange\x12\x1b\n" + + "\tstart_seq\x18\x01 \x01(\rR\bstartSeq\x12\x17\n" + + "\aend_seq\x18\x02 \x01(\rR\x06endSeq\"\x8d\x01\n" + + "\vInitPayload\x12\x1f\n" + + "\vmethod_code\x18\x01 \x01(\tR\n" + + "methodCode\x12!\n" + + "\ftotal_chunks\x18\x02 \x01(\rR\vtotalChunks\x12\x1d\n" + + "\n" + + "data_crc32\x18\x03 \x01(\rR\tdataCrc32\x12\x1b\n" + + "\tfile_size\x18\x04 \x01(\rR\bfileSize\"/\n" + + "\fFetchPayload\x12\x1f\n" + + "\vchunk_index\x18\x01 \x01(\rR\n" + + "chunkIndex\"s\n" + + "\x10ResponseMetadata\x12!\n" + + "\ftotal_chunks\x18\x01 \x01(\rR\vtotalChunks\x12\x1d\n" + + "\n" + + "data_crc32\x18\x02 \x01(\rR\tdataCrc32\x12\x1d\n" + + "\n" + + "chunk_size\x18\x03 \x01(\rR\tchunkSize*\x84\x01\n" + + "\n" + + "PacketType\x12\x1b\n" + + "\x17PACKET_TYPE_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10PACKET_TYPE_INIT\x10\x01\x12\x14\n" + + "\x10PACKET_TYPE_DATA\x10\x02\x12\x15\n" + + "\x11PACKET_TYPE_FETCH\x10\x03\x12\x16\n" + + "\x12PACKET_TYPE_STATUS\x10\x04B$Z\"realm.pub/tavern/internal/c2/dnspbb\x06proto3" var ( file_dns_proto_rawDescOnce sync.Once diff --git a/tavern/internal/c2/epb/eldritch.pb.go b/tavern/internal/c2/epb/eldritch.pb.go index a44d762f3..08617af76 100644 --- a/tavern/internal/c2/epb/eldritch.pb.go +++ b/tavern/internal/c2/epb/eldritch.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.11 // protoc v3.21.12 // source: eldritch.proto @@ -568,89 +568,67 @@ func (x *File) GetChunk() []byte { var File_eldritch_proto protoreflect.FileDescriptor -var file_eldritch_proto_rawDesc = string([]byte{ - 0x0a, 0x0e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x08, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x22, 0xc0, 0x01, 0x0a, 0x04, 0x54, - 0x6f, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x12, - 0x3e, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x54, - 0x6f, 0x6d, 0x65, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, - 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x3d, - 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb4, 0x01, - 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, - 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, - 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x12, 0x2d, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x19, 0x2e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, - 0x64, 0x22, 0x41, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x10, 0x4b, 0x49, 0x4e, - 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x11, 0x0a, 0x0d, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, - 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x53, 0x48, 0x5f, 0x4b, - 0x45, 0x59, 0x10, 0x02, 0x22, 0x8b, 0x04, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, - 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x70, - 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x04, 0x70, 0x70, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, - 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, - 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, - 0x63, 0x6d, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x6d, 0x64, 0x12, 0x10, - 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x76, - 0x12, 0x10, 0x0a, 0x03, 0x63, 0x77, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, - 0x77, 0x64, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x50, 0x72, - 0x6f, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0xab, 0x02, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, - 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x55, 0x4e, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x4c, 0x45, 0x45, 0x50, 0x10, 0x04, 0x12, 0x0f, - 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x05, 0x12, - 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x5a, 0x4f, 0x4d, 0x42, 0x49, 0x45, - 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x54, 0x52, 0x41, - 0x43, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x44, 0x45, 0x41, 0x44, 0x10, 0x08, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x57, 0x41, 0x4b, 0x45, 0x5f, 0x4b, 0x49, 0x4c, 0x4c, 0x10, 0x09, 0x12, 0x11, 0x0a, - 0x0d, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x57, 0x41, 0x4b, 0x49, 0x4e, 0x47, 0x10, 0x0a, - 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x41, 0x52, 0x4b, 0x45, - 0x44, 0x10, 0x0b, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4c, 0x4f, - 0x43, 0x4b, 0x5f, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x0c, 0x12, 0x24, 0x0a, 0x20, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x55, 0x50, - 0x54, 0x49, 0x42, 0x4c, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x53, 0x4c, 0x45, 0x45, 0x50, - 0x10, 0x0d, 0x22, 0x34, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x0c, 0x46, 0x69, 0x6c, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, - 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, - 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, - 0x22, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x33, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x68, 0x61, 0x33, 0x32, 0x35, 0x36, 0x48, - 0x61, 0x73, 0x68, 0x22, 0x50, 0x0a, 0x04, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x65, 0x6c, 0x64, 0x72, 0x69, 0x74, 0x63, 0x68, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, - 0x14, 0x0a, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x22, 0x5a, 0x20, 0x72, 0x65, 0x61, 0x6c, 0x6d, 0x2e, 0x70, - 0x75, 0x62, 0x2f, 0x74, 0x61, 0x76, 0x65, 0x72, 0x6e, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x63, 0x32, 0x2f, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -}) +const file_eldritch_proto_rawDesc = "" + + "\n" + + "\x0eeldritch.proto\x12\beldritch\"\xc0\x01\n" + + "\x04Tome\x12\x1a\n" + + "\beldritch\x18\x01 \x01(\tR\beldritch\x12>\n" + + "\n" + + "parameters\x18\x02 \x03(\v2\x1e.eldritch.Tome.ParametersEntryR\n" + + "parameters\x12\x1d\n" + + "\n" + + "file_names\x18\x03 \x03(\tR\tfileNames\x1a=\n" + + "\x0fParametersEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb4\x01\n" + + "\n" + + "Credential\x12\x1c\n" + + "\tprincipal\x18\x01 \x01(\tR\tprincipal\x12\x16\n" + + "\x06secret\x18\x02 \x01(\tR\x06secret\x12-\n" + + "\x04kind\x18\x03 \x01(\x0e2\x19.eldritch.Credential.KindR\x04kind\"A\n" + + "\x04Kind\x12\x14\n" + + "\x10KIND_UNSPECIFIED\x10\x00\x12\x11\n" + + "\rKIND_PASSWORD\x10\x01\x12\x10\n" + + "\fKIND_SSH_KEY\x10\x02\"\x8b\x04\n" + + "\aProcess\x12\x10\n" + + "\x03pid\x18\x01 \x01(\x04R\x03pid\x12\x12\n" + + "\x04ppid\x18\x02 \x01(\x04R\x04ppid\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1c\n" + + "\tprincipal\x18\x04 \x01(\tR\tprincipal\x12\x12\n" + + "\x04path\x18\x05 \x01(\tR\x04path\x12\x10\n" + + "\x03cmd\x18\x06 \x01(\tR\x03cmd\x12\x10\n" + + "\x03env\x18\a \x01(\tR\x03env\x12\x10\n" + + "\x03cwd\x18\b \x01(\tR\x03cwd\x120\n" + + "\x06status\x18\t \x01(\x0e2\x18.eldritch.Process.StatusR\x06status\"\xab\x02\n" + + "\x06Status\x12\x16\n" + + "\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n" + + "\x0eSTATUS_UNKNOWN\x10\x01\x12\x0f\n" + + "\vSTATUS_IDLE\x10\x02\x12\x0e\n" + + "\n" + + "STATUS_RUN\x10\x03\x12\x10\n" + + "\fSTATUS_SLEEP\x10\x04\x12\x0f\n" + + "\vSTATUS_STOP\x10\x05\x12\x11\n" + + "\rSTATUS_ZOMBIE\x10\x06\x12\x12\n" + + "\x0eSTATUS_TRACING\x10\a\x12\x0f\n" + + "\vSTATUS_DEAD\x10\b\x12\x14\n" + + "\x10STATUS_WAKE_KILL\x10\t\x12\x11\n" + + "\rSTATUS_WAKING\x10\n" + + "\x12\x11\n" + + "\rSTATUS_PARKED\x10\v\x12\x17\n" + + "\x13STATUS_LOCK_BLOCKED\x10\f\x12$\n" + + " STATUS_UNINTERUPTIBLE_DISK_SLEEP\x10\r\"4\n" + + "\vProcessList\x12%\n" + + "\x04list\x18\x01 \x03(\v2\x11.eldritch.ProcessR\x04list\"\xa8\x01\n" + + "\fFileMetadata\x12\x12\n" + + "\x04path\x18\x01 \x01(\tR\x04path\x12\x14\n" + + "\x05owner\x18\x02 \x01(\tR\x05owner\x12\x14\n" + + "\x05group\x18\x03 \x01(\tR\x05group\x12 \n" + + "\vpermissions\x18\x04 \x01(\tR\vpermissions\x12\x12\n" + + "\x04size\x18\x05 \x01(\x04R\x04size\x12\"\n" + + "\rsha3_256_hash\x18\x06 \x01(\tR\vsha3256Hash\"P\n" + + "\x04File\x122\n" + + "\bmetadata\x18\x01 \x01(\v2\x16.eldritch.FileMetadataR\bmetadata\x12\x14\n" + + "\x05chunk\x18\x02 \x01(\fR\x05chunkB\"Z realm.pub/tavern/internal/c2/epbb\x06proto3" var ( file_eldritch_proto_rawDescOnce sync.Once diff --git a/tavern/internal/c2/proto/c2.proto b/tavern/internal/c2/proto/c2.proto index 6bb8e68b3..ae91d5287 100644 --- a/tavern/internal/c2/proto/c2.proto +++ b/tavern/internal/c2/proto/c2.proto @@ -128,22 +128,6 @@ message ReportTaskOutputRequest { message ReportTaskOutputResponse {} -enum ReverseShellMessageKind { - REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED = 0; - REVERSE_SHELL_MESSAGE_KIND_DATA = 1; - REVERSE_SHELL_MESSAGE_KIND_PING = 2; -} - -message ReverseShellRequest{ - ReverseShellMessageKind kind = 1; - bytes data = 2; - int64 task_id = 3; -} -message ReverseShellResponse{ - ReverseShellMessageKind kind = 1; - bytes data = 2; -} - message CreatePortalRequest { int64 task_id = 1; portal.Mote mote = 2; @@ -200,11 +184,6 @@ service C2 { */ rpc ReportTaskOutput(ReportTaskOutputRequest) returns (ReportTaskOutputResponse) {} - /* - * Open a reverse shell bi-directional stream. - */ - rpc ReverseShell(stream ReverseShellRequest) returns (stream ReverseShellResponse) {} - /* * Open a portal bi-directional stream. */ diff --git a/tavern/internal/c2/reverse_shell_e2e_test.go b/tavern/internal/c2/reverse_shell_e2e_test.go deleted file mode 100644 index 0bfe7ad8e..000000000 --- a/tavern/internal/c2/reverse_shell_e2e_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package c2_test - -import ( - "context" - "net" - "net/http/httptest" - "strconv" - "strings" - "testing" - "time" - - "github.com/gorilla/websocket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gocloud.dev/pubsub" - _ "gocloud.dev/pubsub/mempubsub" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" - "realm.pub/tavern/internal/c2" - "realm.pub/tavern/internal/c2/c2pb" - "realm.pub/tavern/internal/ent/enttest" - "realm.pub/tavern/internal/http/stream" - "realm.pub/tavern/internal/portals/mux" - - _ "github.com/mattn/go-sqlite3" -) - -func TestReverseShell_E2E(t *testing.T) { - // Setup Ent Client - graph := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") - defer graph.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // C2 Server Setup - lis := bufconn.Listen(1024 * 1024) - s := grpc.NewServer() - - // Pub/Sub Topics - // The wsMux will be used by websockets to subscribe to shell output and publish new input. - // The grpcMux will be used by gRPC to subscribe to shell input and publish new output. - - pubInput, err := pubsub.OpenTopic(ctx, "mem://e2e-input") - require.NoError(t, err) - defer pubInput.Shutdown(ctx) - - subInput, err := pubsub.OpenSubscription(ctx, "mem://e2e-input") - require.NoError(t, err) - defer subInput.Shutdown(ctx) - - pubOutput, err := pubsub.OpenTopic(ctx, "mem://e2e-output") - require.NoError(t, err) - defer pubOutput.Shutdown(ctx) - - subOutput, err := pubsub.OpenSubscription(ctx, "mem://e2e-output") - require.NoError(t, err) - defer subOutput.Shutdown(ctx) - - wsMux := stream.NewMux(pubInput, subOutput) - grpcMux := stream.NewMux(pubOutput, subInput) - portalMux := mux.New(mux.WithInMemoryDriver()) - - go wsMux.Start(ctx) - go grpcMux.Start(ctx) - - c2pb.RegisterC2Server(s, c2.New(graph, grpcMux, portalMux)) - go func() { - if err := s.Serve(lis); err != nil { - t.Logf("Server exited with error: %v", err) - } - }() - - // gRPC Client Setup - conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { - return lis.Dial() - }), grpc.WithTransportCredentials(insecure.NewCredentials())) - require.NoError(t, err) - defer conn.Close() - - c2Client := c2pb.NewC2Client(conn) - - // Create test entities - user, err := graph.User.Create().SetName("test-user").SetOauthID("test-oauth-id").SetPhotoURL("http://example.com/photo.jpg").Save(ctx) - require.NoError(t, err) - host, err := graph.Host.Create().SetIdentifier("test-host").SetPlatform(c2pb.Host_PLATFORM_LINUX).Save(ctx) - require.NoError(t, err) - beacon, err := graph.Beacon.Create().SetHost(host).SetTransport(c2pb.ActiveTransport_TRANSPORT_UNSPECIFIED).Save(ctx) - require.NoError(t, err) - tome, err := graph.Tome.Create().SetName("test-tome").SetDescription("test-desc").SetAuthor("test-author").SetEldritch("test-eldritch").SetUploader(user).Save(ctx) - require.NoError(t, err) - quest, err := graph.Quest.Create().SetName("test-quest").SetTome(tome).SetCreator(user).Save(ctx) - require.NoError(t, err) - task, err := graph.Task.Create().SetQuest(quest).SetBeacon(beacon).Save(ctx) - require.NoError(t, err) - - // WebSocket Server Setup - handler := stream.NewShellHandler(graph, wsMux) - httpServer := httptest.NewServer(handler) - defer httpServer.Close() - - // gRPC Stream - gRPCStream, err := c2Client.ReverseShell(ctx) - require.NoError(t, err) - - // Register gRPC stream with task ID - err = gRPCStream.Send(&c2pb.ReverseShellRequest{ - TaskId: int64(task.ID), - }) - require.NoError(t, err) - - // Find the shell created by the gRPC service - var shellID int - require.Eventually(t, func() bool { - shells, err := task.QueryShells().All(ctx) - if err != nil || len(shells) == 0 { - return false - } - shellID = shells[0].ID - return true - }, 5*time.Second, 100*time.Millisecond, "shell was not created in time") - - // WebSocket Client Setup - wsURL := "ws" + strings.TrimPrefix(httpServer.URL, "http") + "?shell_id=" + strconv.Itoa(shellID) - ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil) - require.NoError(t, err) - defer ws.Close() - - // Test message from gRPC to WebSocket - grpcMsg := []byte("hello from grpc") - err = gRPCStream.Send(&c2pb.ReverseShellRequest{ - Kind: c2pb.ReverseShellMessageKind_REVERSE_SHELL_MESSAGE_KIND_DATA, - Data: grpcMsg, - }) - require.NoError(t, err) - - _, wsMsg, err := ws.ReadMessage() - assert.NoError(t, err) - assert.Equal(t, grpcMsg, wsMsg) - - // Test message from WebSocket to gRPC - wsMsgToSend := []byte("hello from websocket") - err = ws.WriteMessage(websocket.BinaryMessage, wsMsgToSend) - require.NoError(t, err) - - grpcResp, err := gRPCStream.Recv() - require.NoError(t, err) - assert.Equal(t, wsMsgToSend, grpcResp.Data) -} diff --git a/tavern/internal/c2/server.go b/tavern/internal/c2/server.go index adf28499c..6cab5d9cc 100644 --- a/tavern/internal/c2/server.go +++ b/tavern/internal/c2/server.go @@ -10,24 +10,21 @@ import ( "google.golang.org/grpc/peer" "realm.pub/tavern/internal/c2/c2pb" "realm.pub/tavern/internal/ent" - "realm.pub/tavern/internal/http/stream" "realm.pub/tavern/internal/portals/mux" ) type Server struct { MaxFileChunkSize uint64 graph *ent.Client - mux *stream.Mux portalMux *mux.Mux c2pb.UnimplementedC2Server } -func New(graph *ent.Client, mux *stream.Mux, portalMux *mux.Mux) *Server { +func New(graph *ent.Client, portalMux *mux.Mux) *Server { return &Server{ MaxFileChunkSize: 1024 * 1024, // 1 MB graph: graph, - mux: mux, portalMux: portalMux, } } diff --git a/tavern/internal/http/stream/circular_buffer.go b/tavern/internal/http/stream/circular_buffer.go deleted file mode 100644 index ac6f3a931..000000000 --- a/tavern/internal/http/stream/circular_buffer.go +++ /dev/null @@ -1,92 +0,0 @@ -package stream - -import ( - "sync" -) - -// CircularBuffer is a fixed-size byte buffer that overwrites old data when full. -// It is safe for concurrent use. -type CircularBuffer struct { - mu sync.Mutex - data []byte - size int - start int - length int -} - -// NewCircularBuffer creates a new circular buffer with the given size. -func NewCircularBuffer(size int) *CircularBuffer { - return &CircularBuffer{ - data: make([]byte, size), - size: size, - start: 0, - length: 0, - } -} - -// Write appends data to the buffer. -func (cb *CircularBuffer) Write(p []byte) { - cb.mu.Lock() - defer cb.mu.Unlock() - - n := len(p) - if n == 0 { - return - } - - // If the data being written is larger than the buffer size, - // we only care about the last `size` bytes. - if n >= cb.size { - copy(cb.data, p[n-cb.size:]) - cb.start = 0 - cb.length = cb.size - return - } - - // We are writing n bytes. - // We write starting at (start + length) % size. - writeStart := (cb.start + cb.length) % cb.size - - // Check if the write wraps around the end of the buffer - if writeStart+n <= cb.size { - // Contiguous write - copy(cb.data[writeStart:], p) - } else { - // Wrapped write - chunk1 := cb.size - writeStart - copy(cb.data[writeStart:], p[:chunk1]) - copy(cb.data[0:], p[chunk1:]) - } - - // Update length and start - if cb.length+n <= cb.size { - cb.length += n - } else { - // Buffer overflowed - overflow := (cb.length + n) - cb.size - cb.start = (cb.start + overflow) % cb.size - cb.length = cb.size - } -} - -// Bytes returns the current content of the buffer. -func (cb *CircularBuffer) Bytes() []byte { - cb.mu.Lock() - defer cb.mu.Unlock() - - out := make([]byte, cb.length) - if cb.length == 0 { - return out - } - - // If the data is contiguous - if cb.start+cb.length <= cb.size { - copy(out, cb.data[cb.start:cb.start+cb.length]) - } else { - // Data wraps around - chunk1 := cb.size - cb.start - copy(out, cb.data[cb.start:]) - copy(out[chunk1:], cb.data[:cb.length-chunk1]) - } - return out -} diff --git a/tavern/internal/http/stream/circular_buffer_test.go b/tavern/internal/http/stream/circular_buffer_test.go deleted file mode 100644 index e2e6b94f0..000000000 --- a/tavern/internal/http/stream/circular_buffer_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package stream - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCircularBuffer(t *testing.T) { - cb := NewCircularBuffer(10) - - // Test basic write - cb.Write([]byte("hello")) - assert.Equal(t, []byte("hello"), cb.Bytes()) - - // Test append within size - cb2 := NewCircularBuffer(10) - cb2.Write([]byte("hello ")) - cb2.Write([]byte("world")) - assert.Equal(t, []byte("ello world"), cb2.Bytes()) - - // Test write larger than size - cb3 := NewCircularBuffer(5) - cb3.Write([]byte("1234567890")) - assert.Equal(t, []byte("67890"), cb3.Bytes()) - - // Test write exact size - cb4 := NewCircularBuffer(5) - cb4.Write([]byte("12345")) - assert.Equal(t, []byte("12345"), cb4.Bytes()) - cb4.Write([]byte("6")) - assert.Equal(t, []byte("23456"), cb4.Bytes()) -} diff --git a/tavern/internal/http/stream/gcp_coldstart.go b/tavern/internal/http/stream/gcp_coldstart.go deleted file mode 100644 index e1f91f6d0..000000000 --- a/tavern/internal/http/stream/gcp_coldstart.go +++ /dev/null @@ -1,61 +0,0 @@ -package stream - -import ( - "context" - "log/slog" - "time" - - "gocloud.dev/pubsub" - _ "gocloud.dev/pubsub/gcppubsub" -) - -// PreventPubSubColdStarts by publishing noop messages on an interval. -// This reduces cold-start latency for GCP PubSub which can improve shell user experience. -// In other environments, this functionality may not be necessary. -func PreventPubSubColdStarts(ctx context.Context, interval time.Duration, topicShellOutput string, topicShellInput string) { - if interval == 0 { - slog.WarnContext(ctx, "gcppubsub cold-start polling disabled due to 0ms interval, this may impact shell latency") - return - } - if interval < 1*time.Millisecond { - slog.WarnContext(ctx, "gcppubsub cold-start polling interval less than minimum, setting to 1 millisecond") - interval = 1 * time.Millisecond - } - - pubOutput, err := pubsub.OpenTopic(ctx, topicShellOutput) - if err != nil { - slog.ErrorContext(ctx, "warmup failed to connect to pubsub output topic, cold-start latency may impact user experience (%q): %v", topicShellOutput, err) - return - } - pubInput, err := pubsub.OpenTopic(ctx, topicShellInput) - if err != nil { - slog.ErrorContext(ctx, "warmup failed to connect to pubsub input topic, cold-start latency may impact user experience (%q): %v", topicShellOutput, err) - return - } - - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if err := pubOutput.Send(ctx, &pubsub.Message{ - Metadata: map[string]string{ - "id": "noop", - }, - Body: []byte{0}, - }); err != nil { - slog.ErrorContext(ctx, "warmup failed to publish shell output keep-alive no op. GCP coldstart latency may be encountered.") - } - if err := pubInput.Send(ctx, &pubsub.Message{ - Metadata: map[string]string{ - "id": "noop", - }, - Body: []byte{0}, - }); err != nil { - slog.ErrorContext(ctx, "warmup failed to publish shell input keep-alive no op. GCP coldstart latency may be encountered.") - } - } - } -} diff --git a/tavern/internal/http/stream/gcp_coldstart_test.go b/tavern/internal/http/stream/gcp_coldstart_test.go deleted file mode 100644 index 45722466e..000000000 --- a/tavern/internal/http/stream/gcp_coldstart_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package stream_test - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "gocloud.dev/pubsub" - _ "gocloud.dev/pubsub/mempubsub" - "realm.pub/tavern/internal/http/stream" -) - -func TestPreventPubSubColdStarts_ValidInterval(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - // Create a mock topic and subscription. - topic, err := pubsub.OpenTopic(ctx, "mem://valid") - if err != nil { - t.Fatalf("Failed to open topic: %v", err) - } - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, "mem://valid") - if err != nil { - t.Fatalf("Failed to open subscription: %v", err) - } - defer sub.Shutdown(ctx) - - go stream.PreventPubSubColdStarts(ctx, 50*time.Millisecond, "mem://valid", "mem://valid") - - // Expect to receive a message - msg, err := sub.Receive(ctx) - assert.NoError(t, err) - assert.NotNil(t, msg) - if msg != nil { - assert.Equal(t, "noop", msg.Metadata["id"]) - msg.Ack() - } -} - -func TestPreventPubSubColdStarts_ZeroInterval(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - topic, err := pubsub.OpenTopic(ctx, "mem://zero") - if err != nil { - t.Fatalf("Failed to open topic: %v", err) - } - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, "mem://zero") - if err != nil { - t.Fatalf("Failed to open subscription: %v", err) - } - defer sub.Shutdown(ctx) - - go stream.PreventPubSubColdStarts(ctx, 0, "mem://zero", "mem://zero") - - // Expect to not receive a message and for the context to timeout - _, err = sub.Receive(ctx) - assert.Error(t, err) - assert.Equal(t, context.DeadlineExceeded, err) -} - -func TestPreventPubSubColdStarts_SubMillisecondInterval(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - topic, err := pubsub.OpenTopic(ctx, "mem://sub") - if err != nil { - t.Fatalf("Failed to open topic: %v", err) - } - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, "mem://sub") - if err != nil { - t.Fatalf("Failed to open subscription: %v", err) - } - defer sub.Shutdown(ctx) - - go stream.PreventPubSubColdStarts(ctx, 1*time.Microsecond, "mem://sub", "mem://sub") - - // Expect to receive a message - msg, err := sub.Receive(ctx) - assert.NoError(t, err) - assert.NotNil(t, msg) - if msg != nil { - assert.Equal(t, "noop", msg.Metadata["id"]) - msg.Ack() - } -} diff --git a/tavern/internal/http/stream/mux.go b/tavern/internal/http/stream/mux.go deleted file mode 100644 index 8f7df7a60..000000000 --- a/tavern/internal/http/stream/mux.go +++ /dev/null @@ -1,252 +0,0 @@ -package stream - -import ( - "context" - "fmt" - "net/http" - - "github.com/gorilla/websocket" - "gocloud.dev/pubsub" - "golang.org/x/exp/slog" -) - -const ( - // maxRegistrationBufSize defines the maximum receivers that can be buffered in the registration / unregistration channel - // before new calls to `mux.Register()` and `mux.Unregister()` will block. - maxRegistrationBufSize = 256 - // defaultHistorySize is the default size of the circular buffer for stream history. - defaultHistorySize = 1024 -) - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, -} - -type historyState struct { - buffer *CircularBuffer - sessions map[string]*sessionBuffer -} - -// A Mux enables multiplexing subscription messages to multiple Streams. -// Streams will only receive a Message if their configured ID matches the incoming metadata of a Message. -// Additionally, new messages may be published using the Mux. -type Mux struct { - pub *pubsub.Topic - sub *pubsub.Subscription - register chan *Stream - unregister chan *Stream - streams map[*Stream]bool - history map[string]*historyState - historySize int -} - -// A MuxOption is used to provide further configuration to the Mux. -type MuxOption func(*Mux) - -// WithHistorySize sets the size of the circular buffer for stream history. -func WithHistorySize(size int) MuxOption { - return func(m *Mux) { - m.historySize = size - } -} - -// NewMux initializes and returns a new Mux with the provided pubsub info. -func NewMux(pub *pubsub.Topic, sub *pubsub.Subscription, options ...MuxOption) *Mux { - mux := &Mux{ - pub: pub, - sub: sub, - register: make(chan *Stream, maxRegistrationBufSize), - unregister: make(chan *Stream, maxRegistrationBufSize), - streams: make(map[*Stream]bool), - history: make(map[string]*historyState), - historySize: defaultHistorySize, - } - for _, opt := range options { - opt(mux) - } - return mux -} - -// send a new message to the configured publish topic. -// The provided message MUST include an id metadata. -func (mux *Mux) send(ctx context.Context, m *pubsub.Message) error { - if _, ok := m.Metadata[metadataID]; !ok { - return fmt.Errorf("must set 'id' metadata before publishing") - } - return mux.pub.Send(ctx, m) -} - -// Register a new stream with the Mux, which will receive broadcast messages from a pubsub subscription -// if the Message metadata ID matches the stream ID. -func (mux *Mux) Register(s *Stream) { - mux.register <- s -} - -// Unregister a stream when it should no longer receive Messages from the Mux. -// Typically this is done via defer after registering a Stream. -// Unregistering a stream that is not registered will still close the stream channel. -func (mux *Mux) Unregister(s *Stream) { - mux.unregister <- s -} - -// Start the mux, returning an error if polling ever fails. -func (mux *Mux) Start(ctx context.Context) error { - slog.DebugContext(ctx, "mux starting to manage streams and polling") - - // Message channel to receive messages from the poller - type pollResult struct { - msg *pubsub.Message - err error - } - msgChan := make(chan pollResult) - - // Start poller goroutine - go func() { - defer close(msgChan) - for { - msg, err := mux.sub.Receive(ctx) - select { - case <-ctx.Done(): - return - case msgChan <- pollResult{msg: msg, err: err}: - // If context is done, stop. - if ctx.Err() != nil { - return - } - // Otherwise, loop again (retry on error). - } - } - }() - - for { - select { - case <-ctx.Done(): - slog.DebugContext(ctx, "mux context finished, exiting") - return ctx.Err() - - case s := <-mux.register: - // Handle Registration - slog.DebugContext(ctx, "mux registering new stream", "stream_id", s.id) - mux.streams[s] = true - - // Send history to the new stream - if state, ok := mux.history[s.id]; ok && state.buffer != nil { - data := state.buffer.Bytes() - if len(data) > 0 { - slog.DebugContext(ctx, "mux sending history to new stream", "stream_id", s.id, "bytes", len(data)) - msg := &pubsub.Message{ - Body: data, - Metadata: map[string]string{ - metadataID: s.id, - MetadataMsgKind: "data", - // No order key needed for history injection - }, - } - s.processOneMessage(ctx, msg) - } - } - - case s := <-mux.unregister: - // Handle Unregistration - slog.DebugContext(ctx, "mux unregistering stream", "stream_id", s.id) - delete(mux.streams, s) - s.Close() - - case res, ok := <-msgChan: - if !ok { - // Poller exited. If due to context cancel, return that error. - if ctx.Err() != nil { - return ctx.Err() - } - return fmt.Errorf("poller exited unexpectedly") - } - if res.err != nil { - // Log error and continue, matching original behavior (retry loop). - // Unless context is done. - if ctx.Err() != nil { - return ctx.Err() - } - slog.ErrorContext(ctx, "mux failed to poll subscription", "error", res.err) - continue - } - - // Handle Message - mux.handleMessage(ctx, res.msg) - } - } -} - -// handleMessage processes a new message, updating history and broadcasting to streams. -func (mux *Mux) handleMessage(ctx context.Context, msg *pubsub.Message) { - // Always acknowledge the message - defer msg.Ack() - - // Get Message Metadata - msgID, ok := msg.Metadata["id"] - if !ok { - slog.DebugContext(ctx, "mux received message without 'id' for stream, ignoring") - return - } - msgOrderKey, ok := msg.Metadata[metadataOrderKey] - if !ok { - slog.DebugContext(ctx, "mux received message without metadataOrderKey") - } - msgOrderIndex, ok := msg.Metadata[metadataOrderIndex] - if !ok { - slog.DebugContext(ctx, "mux received message without msgOrderIndex") - } - - // Update History - // Only buffer "data" messages (or messages with no kind specified, which default to data) - kind, hasKind := msg.Metadata[MetadataMsgKind] - if !hasKind || kind == "data" { - state, ok := mux.history[msgID] - if !ok { - state = &historyState{ - buffer: NewCircularBuffer(mux.historySize), - sessions: make(map[string]*sessionBuffer), - } - mux.history[msgID] = state - } - - // Use sessionBuffer to reorder messages before writing to circular buffer - key := parseOrderKey(msg) - sessBuf, ok := state.sessions[key] - if !ok { - sessBuf = &sessionBuffer{ - data: make(map[uint64]*pubsub.Message, maxStreamOrderBuf), - } - state.sessions[key] = sessBuf - } - - sessBuf.writeMessage(ctx, msg, func(m *pubsub.Message) { - state.buffer.Write(m.Body) - }) - } - - // Broadcast Message - slog.DebugContext(ctx, "mux broadcasting received message", - "msg_id", msgID, - "msg_order_key", msgOrderKey, - "msg_order_index", msgOrderIndex, - "stream_count", len(mux.streams), - ) - for s := range mux.streams { - if s == nil { - slog.ErrorContext(ctx, "mux found nil stream in map while broadcasting message, skipping stream", "msg_id", msgID) - continue - } - - if s.id == msgID { - slog.DebugContext(ctx, "mux sending message to stream", - "msg_id", msgID, - "stream_id", s.id, - "stream_order_key", s.orderKey, - "stream_index", s.orderIndex.Load(), - ) - s.processOneMessage(ctx, msg) - } - } -} diff --git a/tavern/internal/http/stream/mux_test.go b/tavern/internal/http/stream/mux_test.go deleted file mode 100644 index 42bf72600..000000000 --- a/tavern/internal/http/stream/mux_test.go +++ /dev/null @@ -1,259 +0,0 @@ -package stream_test - -import ( - "context" - "fmt" - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gocloud.dev/pubsub" - _ "gocloud.dev/pubsub/mempubsub" - "realm.pub/tavern/internal/http/stream" -) - -func newTopicName(base string) string { - return fmt.Sprintf("mem://%s-%d", base, rand.Int()) -} - -func TestMux(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - topicName := newTopicName("mux-test") - topic, err := pubsub.OpenTopic(ctx, topicName) - require.NoError(t, err) - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, topicName) - require.NoError(t, err) - defer sub.Shutdown(ctx) - - // Create Mux - mux := stream.NewMux(topic, sub) - go mux.Start(ctx) - - // Create and Register Streams - stream1 := stream.New("stream1") - stream2 := stream.New("stream2") - - mux.Register(stream1) - defer mux.Unregister(stream1) - mux.Register(stream2) - defer mux.Unregister(stream2) - - // Give the mux a moment to register the streams - time.Sleep(50 * time.Millisecond) - - // Send a message for stream1 - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("hello stream 1"), - Metadata: map[string]string{"id": "stream1"}, - }) - require.NoError(t, err) - - // Send a message for stream2 - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("hello stream 2"), - Metadata: map[string]string{"id": "stream2"}, - }) - require.NoError(t, err) - - // Send a message with no id - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("no id"), - }) - require.NoError(t, err) - - // Assert messages are received by the correct stream - select { - case msg1 := <-stream1.Messages(): - assert.Equal(t, "hello stream 1", string(msg1.Body)) - case <-time.After(5 * time.Second): - t.Fatal("stream1 did not receive message in time") - } - - select { - case msg2 := <-stream2.Messages(): - assert.Equal(t, "hello stream 2", string(msg2.Body)) - case <-time.After(5 * time.Second): - t.Fatal("stream2 did not receive message in time") - } -} - -func TestMuxHistory(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - topicName := newTopicName("mux-history-test") - topic, err := pubsub.OpenTopic(ctx, topicName) - require.NoError(t, err) - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, topicName) - require.NoError(t, err) - defer sub.Shutdown(ctx) - - // Create Mux with small history size - mux := stream.NewMux(topic, sub, stream.WithHistorySize(10)) - go mux.Start(ctx) - - // Create monitor stream to ensure messages are processed - monitor := stream.New("history_stream") - mux.Register(monitor) - defer mux.Unregister(monitor) - - // Send some messages - // Total 15 bytes. Buffer size 10. Last 10 bytes should be kept. - messages := []string{"12345", "67890", "ABCDE"} - for _, m := range messages { - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte(m), - Metadata: map[string]string{"id": "history_stream"}, - }) - require.NoError(t, err) - - // Wait for monitor to receive it - select { - case msg := <-monitor.Messages(): - assert.Equal(t, m, string(msg.Body)) - case <-time.After(1 * time.Second): - t.Fatal("monitor did not receive message in time") - } - } - - // Register a new stream - s := stream.New("history_stream") - mux.Register(s) - defer mux.Unregister(s) - - // Send SYNC message to trigger registration loop in Mux - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("SYNC"), - Metadata: map[string]string{"id": "history_stream"}, - }) - require.NoError(t, err) - - // Expect history message immediately - // "12345" (5) + "67890" (5) = 10. - // "ABCDE" (5). Total 15. - // Last 10 bytes: "67890ABCDE" - select { - case msg := <-s.Messages(): - assert.Equal(t, "67890ABCDE", string(msg.Body)) - case <-time.After(1 * time.Second): - t.Fatal("stream did not receive history message in time") - } - - // Expect SYNC message - select { - case msg := <-s.Messages(): - assert.Equal(t, "SYNC", string(msg.Body)) - case <-time.After(1 * time.Second): - t.Fatal("stream did not receive new message in time") - } -} - -func TestMuxHistoryOrdering(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - topicName := newTopicName("mux-history-ordering-test") - topic, err := pubsub.OpenTopic(ctx, topicName) - require.NoError(t, err) - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, topicName) - require.NoError(t, err) - defer sub.Shutdown(ctx) - - // Create Mux - mux := stream.NewMux(topic, sub, stream.WithHistorySize(100)) - go mux.Start(ctx) - - // Create monitor stream to wait for processing - monitor := stream.New("ordering_stream") - mux.Register(monitor) - defer mux.Unregister(monitor) - - // Send messages in an order that respects the new "Late Join" logic. - // We must send the anchor (0) first so the stream knows where it starts. - orderKey := "session1" - - // 1. Send Anchor (0) - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("A"), - Metadata: map[string]string{ - "id": "ordering_stream", - "order-key": orderKey, - "order-index": "0", - }, - }) - require.NoError(t, err) - - // Wait for Anchor to be processed to ensure stream initialization - select { - case msg := <-monitor.Messages(): - assert.Equal(t, "A", string(msg.Body)) - case <-time.After(1 * time.Second): - t.Fatal("monitor did not receive anchor message in time") - } - - // 2. Send C (2) - Out of order - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("C"), - Metadata: map[string]string{ - "id": "ordering_stream", - "order-key": orderKey, - "order-index": "2", - }, - }) - require.NoError(t, err) - - // 3. Send B (1) - Fills gap - // Sleep to ensure C arrives at Mux before B (testing the buffer logic) - time.Sleep(10 * time.Millisecond) - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("B"), - Metadata: map[string]string{ - "id": "ordering_stream", - "order-key": orderKey, - "order-index": "1", - }, - }) - require.NoError(t, err) - - // Wait for monitor to receive remaining messages. - received := "" - for i := 0; i < 2; i++ { - select { - case msg := <-monitor.Messages(): - received += string(msg.Body) - case <-time.After(1 * time.Second): - t.Fatal("monitor did not receive message in time") - } - } - assert.Equal(t, "BC", received) - - // Now register a new stream to check history - s := stream.New("ordering_stream") - mux.Register(s) - defer mux.Unregister(s) - - // Send SYNC - err = topic.Send(ctx, &pubsub.Message{ - Body: []byte("SYNC"), - Metadata: map[string]string{"id": "ordering_stream"}, - }) - require.NoError(t, err) - - // Expect history: "ABC" - select { - case msg := <-s.Messages(): - assert.Equal(t, "ABC", string(msg.Body)) - case <-time.After(1 * time.Second): - t.Fatal("stream did not receive history message in time") - } -} diff --git a/tavern/internal/http/stream/stream.go b/tavern/internal/http/stream/stream.go deleted file mode 100644 index 33551977c..000000000 --- a/tavern/internal/http/stream/stream.go +++ /dev/null @@ -1,224 +0,0 @@ -package stream - -import ( - "context" - "crypto/rand" - "encoding/base64" - "fmt" - "io" - "log/slog" - "strconv" - "sync" - "sync/atomic" - - "gocloud.dev/pubsub" -) - -// metadataID defines the ID of the entity for the stream, enabling multiple ents to be multiplexed over a topic. -// metadataOrderKey defines the key for specifying a unique stream ID, which all published messages should be ordered (per order key). -// metadataOrderIndex defines the index of a published message within the context of an order key. -const ( - metadataID = "id" - metadataOrderKey = "order-key" - metadataOrderIndex = "order-index" - - // MetadataStreamClose can be added by producers when publishing messages to a topic, indicating no more messages will be sent to the stream. - // This should never be sent if there is more than one producer for the stream (e.g. only send from gRPC, not from websockets). - MetadataStreamClose = "stream-close" - - // MetadataMsgKind defines the kind of message (data, ping) - MetadataMsgKind = "msg_kind" - - // maxStreamMsgBufSize defines the maximum number of messages that can be buffered for a stream before causing the Mux to block. - maxStreamMsgBufSize = 1024 - - // maxStreamOrderBuf defines how many messages to wait before dropping an out-of-order message and move on. - maxStreamOrderBuf = 10 -) - -// A Stream is registered with a Mux to receive filtered messages from a pubsub subscription. -type Stream struct { - mu sync.Mutex - - id string // ID of the underlying ent (e.g. Shell) - orderKey string // Unique ID for the session (e.g. Websocket) - orderIndex *atomic.Uint64 - recv chan *pubsub.Message - recvOrdered chan *pubsub.Message - buffers map[string]*sessionBuffer // Receive messages, bucket by sender (order-key), and order messages from same sender -} - -// New initializes a new stream that will only receive messages with the provided ID. -// It must be registered on a Mux to begin receiving messages. -// This method panics if it fails to generate a random string for the order-key. -func New(id string) *Stream { - return &Stream{ - id: id, - orderKey: newOrderKey(), - orderIndex: &atomic.Uint64{}, - recv: make(chan *pubsub.Message, maxStreamMsgBufSize), - recvOrdered: make(chan *pubsub.Message, maxStreamMsgBufSize), - buffers: make(map[string]*sessionBuffer, maxStreamMsgBufSize), - } -} - -// SendMessage -func (s *Stream) SendMessage(ctx context.Context, msg *pubsub.Message, mux *Mux) error { - // Add Metadata - if msg.Metadata == nil { - msg.Metadata = make(map[string]string, 3) - } - msg.Metadata[metadataID] = s.id - msg.Metadata[metadataOrderKey] = s.orderKey - msg.Metadata[metadataOrderIndex] = fmt.Sprintf("%d", s.orderIndex.Load()) - - // Send Message to Mux - err := mux.send(ctx, msg) - if err != nil { - return err - } - - // Track Index for Stream - s.orderIndex.Add(1) - return nil -} - -// Messages returns a channel for receiving new pubsub messages. -func (s *Stream) Messages() <-chan *pubsub.Message { - return s.recvOrdered -} - -// Close the stream, preventing it from receiving any new messages. -// The Mux a stream is registered with will call Close() when it is unregistered. -func (s *Stream) Close() error { - s.mu.Lock() - defer s.mu.Unlock() - - close(s.recv) - close(s.recvOrdered) - return nil -} - -// processOneMessage is called by a Mux to receive a new pubsub message designated for this stream. -// It may be unordered. -func (s *Stream) processOneMessage(ctx context.Context, msg *pubsub.Message) { - s.mu.Lock() - defer s.mu.Unlock() - - // Get Buffer for the given order-key - // We order all messages within the same order key, or create a new one - // if we have not seen this order key yet. - key := parseOrderKey(msg) - buf, ok := s.buffers[key] - if !ok || buf == nil { - buf = &sessionBuffer{ - data: make(map[uint64]*pubsub.Message, maxStreamMsgBufSize), - } - s.buffers[key] = buf - } - - // Write Message (or buffer it) - emit := func(m *pubsub.Message) { - s.recvOrdered <- m - } - buf.writeMessage(ctx, msg, emit) -} - -func parseOrderKey(msg *pubsub.Message) string { - key, ok := msg.Metadata[metadataOrderKey] - if !ok { - return "" - } - return key -} - -func parseOrderIndex(msg *pubsub.Message) (uint64, bool) { - indexStr, ok := msg.Metadata[metadataOrderIndex] - if !ok { - return 0, false - } - index, err := strconv.ParseUint(indexStr, 10, 64) - if err != nil { - return 0, false - } - return index, true -} - -// newOrderKey returns a new base64 encoded random string, intended for use as a unique order key. -// If it fails to generate a key, it panics. -func newOrderKey() string { - buf := make([]byte, 16) - _, err := io.ReadFull(rand.Reader, buf) - if err != nil { - slog.Error("failed to generate order key, defaulting to empty string!", "error", err) - return "" - } - return base64.StdEncoding.EncodeToString(buf) -} - -// A sessionBuffer enables ordering of messages for a given session of the stream. -// For example, if multiple websockets send to the same pubsub topic, we need to ensure -// that their messages can be received in order. To accomplish this, each websocket (a "session") -// is assigned a unique UUID, which is associated with all messages it publishes (as the order-key). -// Then, when receiving messages, streams will order all messages received from the same order-key. -// In this example, websockets could race to add data, but all the data they add will be in order -// with respect to the websocket. -type sessionBuffer struct { - nextToSend uint64 - data map[uint64]*pubsub.Message - initialized bool -} - -func (buf *sessionBuffer) writeMessage(ctx context.Context, msg *pubsub.Message, emit func(*pubsub.Message)) { - index, ok := parseOrderIndex(msg) - if !ok { - slog.DebugContext(ctx, "sessionBuffer received no order index, will write immediately and not buffer") - emit(msg) - return - } - - if !buf.initialized { - buf.nextToSend = index - buf.initialized = true - } - - if index < buf.nextToSend { - slog.ErrorContext(ctx, "dropping message because subsequent message has already been sent", - "msg_log_id", msg.LoggableID, - "next_to_send", buf.nextToSend, - "order_index", index, - ) - return - } - - buf.data[index] = msg - buf.flushBuffer(ctx, emit) -} - -func (buf *sessionBuffer) flushBuffer(ctx context.Context, emit func(*pubsub.Message)) { - for { - msg, ok := buf.data[buf.nextToSend] - if !ok { - if len(buf.data) > maxStreamOrderBuf { - // To prevent getting stuck, find the lowest index in the buffer and jump to it. - lowestIndex := uint64(0) - for k := range buf.data { - if lowestIndex == 0 || k < lowestIndex { - lowestIndex = k - } - } - slog.ErrorContext(ctx, "sessionBuffer overflow, skipping message to catch up", - "skipped_message_index", buf.nextToSend, - "new_index", lowestIndex, - "buffered_msgs_count", len(buf.data), - ) - buf.nextToSend = lowestIndex - continue - } - break - } - emit(msg) - delete(buf.data, buf.nextToSend) - buf.nextToSend++ - } -} diff --git a/tavern/internal/http/stream/stream_test.go b/tavern/internal/http/stream/stream_test.go deleted file mode 100644 index ddebddbaa..000000000 --- a/tavern/internal/http/stream/stream_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package stream - -import ( - "context" - "fmt" - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gocloud.dev/pubsub" - _ "gocloud.dev/pubsub/mempubsub" -) - -func newTopicName(base string) string { - return fmt.Sprintf("mem://%s-%d", base, rand.Int()) -} - -func TestStream_SendMessage(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - topicName := newTopicName("stream-test-send") - topic, err := pubsub.OpenTopic(ctx, topicName) - require.NoError(t, err) - defer topic.Shutdown(ctx) - sub, err := pubsub.OpenSubscription(ctx, topicName) - require.NoError(t, err) - defer sub.Shutdown(ctx) - - mux := NewMux(topic, sub) - stream := New("test-stream") - - // Send a message - err = stream.SendMessage(ctx, &pubsub.Message{Body: []byte("test message")}, mux) - require.NoError(t, err) - - // Receive the message from the subscription to verify - msg, err := sub.Receive(ctx) - require.NoError(t, err) - assert.Equal(t, "test message", string(msg.Body)) - assert.Equal(t, "test-stream", msg.Metadata["id"]) - msg.Ack() -} - -func TestStream_MessageOrdering(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - stream := New("ordering-stream") - go func() { - // Send 0 first to anchor the stream - stream.processOneMessage(ctx, &pubsub.Message{ - Body: []byte("message 0"), - Metadata: map[string]string{ - "id": "ordering-stream", - metadataOrderKey: "test-key", - metadataOrderIndex: "0", - }, - }) - // Then send 2 (buffered) - stream.processOneMessage(ctx, &pubsub.Message{ - Body: []byte("message 2"), - Metadata: map[string]string{ - "id": "ordering-stream", - metadataOrderKey: "test-key", - metadataOrderIndex: "2", - }, - }) - // Then send 1 (fills gap) - stream.processOneMessage(ctx, &pubsub.Message{ - Body: []byte("message 1"), - Metadata: map[string]string{ - "id": "ordering-stream", - metadataOrderKey: "test-key", - metadataOrderIndex: "1", - }, - }) - }() - - // Expect to receive messages in order - for i := 0; i < 3; i++ { - select { - case msg := <-stream.Messages(): - expectedBody := fmt.Sprintf("message %d", i) - assert.Equal(t, expectedBody, string(msg.Body)) - case <-time.After(1 * time.Second): - t.Fatalf("timed out waiting for message %d", i) - } - } -} - -func TestStream_LateJoin(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - stream := New("late-join-stream") - go func() { - // Simulate joining late: receive message 10 first - stream.processOneMessage(ctx, &pubsub.Message{ - Body: []byte("message 10"), - Metadata: map[string]string{ - "id": "late-join-stream", - metadataOrderKey: "test-key", - metadataOrderIndex: "10", - }, - }) - }() - - select { - case msg := <-stream.Messages(): - assert.Equal(t, "message 10", string(msg.Body)) - case <-ctx.Done(): - t.Fatal("timed out waiting for message 10 (late join failed)") - } -} - -func TestStream_Close(t *testing.T) { - t.Parallel() - stream := New("closable-stream") - go func() { - time.Sleep(10 * time.Millisecond) - stream.Close() - }() - - _, ok := <-stream.Messages() - assert.False(t, ok, "channel should be closed") -} diff --git a/tavern/internal/http/stream/websocket.go b/tavern/internal/http/stream/websocket.go index df388f039..d67eaea2f 100644 --- a/tavern/internal/http/stream/websocket.go +++ b/tavern/internal/http/stream/websocket.go @@ -9,11 +9,12 @@ import ( "time" "github.com/gorilla/websocket" - "gocloud.dev/pubsub" "realm.pub/tavern/internal/auth" "realm.pub/tavern/internal/ent" - "realm.pub/tavern/internal/ent/shell" + "realm.pub/tavern/internal/ent/portal" "realm.pub/tavern/internal/ent/user" + "realm.pub/tavern/internal/portals/mux" + "realm.pub/tavern/portals/portalpb" ) const ( @@ -30,22 +31,28 @@ const ( maxMessageSize = 256 * 1024 // 256KB ) +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true // Allow all origins for now (dev/internal) + }, +} + type connector struct { - *Stream - mux *Mux - ws *websocket.Conn - kind string + mux *mux.Mux + portalID int + ws *websocket.Conn } // WriteToWebsocket will read messages from the Mux and write them to the underlying websocket. func (c *connector) WriteToWebsocket(ctx context.Context) { defer c.ws.Close() - // Register with mux to receive messages - c.mux.Register(c.Stream) - defer c.mux.Unregister(c.Stream) + topicOut := c.mux.TopicOut(c.portalID) + recv, cleanup := c.mux.Subscribe(topicOut) + defer cleanup() - // Keep Alive ticker := time.NewTicker(pingPeriod) defer ticker.Stop() @@ -54,77 +61,34 @@ func (c *connector) WriteToWebsocket(ctx context.Context) { case <-ctx.Done(): c.ws.WriteMessage(websocket.CloseMessage, []byte{}) return - case message, ok := <-c.Messages(): + case <-ticker.C: c.ws.SetWriteDeadline(time.Now().Add(writeWait)) - if !ok { - // The mux closed the channel. - c.ws.WriteMessage(websocket.CloseMessage, []byte{}) + if err := c.ws.WriteMessage(websocket.PingMessage, nil); err != nil { return } - - // Check if stream has closed - hasClosed, ok := message.Metadata[MetadataStreamClose] - if ok && hasClosed != "" { - // The producer ended the stream. - slog.DebugContext(ctx, "websocket closed due to producer ending stream", - "stream_id", c.Stream.id, - "stream_order_key", c.Stream.orderKey, - ) + case mote, ok := <-recv: + c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { c.ws.WriteMessage(websocket.CloseMessage, []byte{}) return } - // Filter by kind - kind := message.Metadata[MetadataMsgKind] - if kind == "" { - kind = "data" - } - if kind != c.kind { - continue - } - - w, err := c.ws.NextWriter(websocket.BinaryMessage) - if err != nil { - return - } - if _, err := w.Write(message.Body); err != nil { - slog.ErrorContext(ctx, "failed to write message from producer to websocket", - "stream_id", c.Stream.id, - "stream_order_key", c.Stream.orderKey, - "error", err, - ) - } - - // Flush queued messages to the current websocket message. - n := len(c.Messages()) - for i := 0; i < n; i++ { - additionalMsg := <-c.Messages() - - // Filter additional messages too - kind := additionalMsg.Metadata[MetadataMsgKind] - if kind == "" { - kind = "data" + // Extract Payload + if mote.GetRepl() != nil { + // REPL Data -> Text/Binary Message + w, err := c.ws.NextWriter(websocket.BinaryMessage) + if err != nil { + return } - if kind != c.kind { - continue + if _, err := w.Write(mote.GetRepl().Data); err != nil { + slog.ErrorContext(ctx, "failed to write repl message to websocket", "error", err) } - - if _, err := w.Write(additionalMsg.Body); err != nil { - slog.ErrorContext(ctx, "failed to write additional message from producer to websocket", - "stream_id", c.Stream.id, - "stream_order_key", c.Stream.orderKey, - "error", err, - ) + if err := w.Close(); err != nil { + return } - } - - if err := w.Close(); err != nil { - return - } - case <-ticker.C: - c.ws.SetWriteDeadline(time.Now().Add(writeWait)) - if err := c.ws.WriteMessage(websocket.PingMessage, nil); err != nil { - return + } else if mote.GetBytes() != nil { + // Handle BytesPayload (e.g. Ping/Keepalive) + // Currently we just ignore it as we send our own pings } } } @@ -134,7 +98,6 @@ func (c *connector) WriteToWebsocket(ctx context.Context) { func (c *connector) ReadFromWebsocket(ctx context.Context) { defer c.ws.Close() - // Configure connection info c.ws.SetReadLimit(maxMessageSize) c.ws.SetReadDeadline(time.Now().Add(pongWait)) c.ws.SetPongHandler(func(string) error { @@ -142,6 +105,8 @@ func (c *connector) ReadFromWebsocket(ctx context.Context) { return nil }) + topicIn := c.mux.TopicIn(c.portalID) + for { select { case <-ctx.Done(): @@ -150,56 +115,50 @@ func (c *connector) ReadFromWebsocket(ctx context.Context) { _, message, err := c.ws.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { - slog.ErrorContext(ctx, "websocket closed unexpectedly", - "stream_id", c.Stream.id, - "stream_order_key", c.Stream.orderKey, - "error", err, - ) + slog.ErrorContext(ctx, "websocket closed unexpectedly", "error", err) } return } - msgLen := len(message) - if err := c.Stream.SendMessage(ctx, &pubsub.Message{ - Body: message, - Metadata: map[string]string{ - metadataID: c.id, - MetadataMsgKind: c.kind, + // Wrap in Mote + mote := &portalpb.Mote{ + StreamId: "repl", // Default stream ID for WS + Payload: &portalpb.Mote_Repl{ + Repl: &portalpb.REPLMessage{ + Data: message, + }, }, - }, c.mux); err != nil { - slog.ErrorContext(ctx, "websocket failed to publish message", - "stream_id", c.Stream.id, - "stream_order_key", c.Stream.orderKey, - "msg_len", msgLen, - "error", err, - ) + } + + if err := c.mux.Publish(ctx, topicIn, mote); err != nil { + slog.ErrorContext(ctx, "websocket failed to publish message", "error", err) return } } } } -func manageActiveUser(ctx context.Context, done <-chan struct{}, graph *ent.Client, shellID int, userID int) { +func manageActiveUser(ctx context.Context, done <-chan struct{}, graph *ent.Client, portalID int, userID int) { defer func() { - slog.DebugContext(ctx, "websocket checking user activity for shell before removal", "user_id", userID, "shell_id", shellID) + slog.DebugContext(ctx, "websocket checking user activity for portal before removal", "user_id", userID, "portal_id", portalID) - wasAdded, err := graph.Shell.Query(). - Where(shell.ID(shellID)). + wasAdded, err := graph.Portal.Query(). + Where(portal.ID(portalID)). QueryActiveUsers(). Where(user.ID(userID)). Exist(ctx) if err != nil { - slog.ErrorContext(ctx, "websocket failed to check user activity for shell", "err", err, "user_id", userID, "shell_id", shellID) + slog.ErrorContext(ctx, "websocket failed to check user activity for portal", "err", err, "user_id", userID, "portal_id", portalID) return } if !wasAdded { return } - if _, err := graph.Shell.UpdateOneID(shellID). + if _, err := graph.Portal.UpdateOneID(portalID). RemoveActiveUserIDs(userID). Save(ctx); err != nil { - slog.ErrorContext(ctx, "websocket failed to remove inactive user from shell", "err", err, "user_id", userID, "shell_id", shellID) + slog.ErrorContext(ctx, "websocket failed to remove inactive user from portal", "err", err, "user_id", userID, "portal_id", portalID) return } }() @@ -213,34 +172,32 @@ func manageActiveUser(ctx context.Context, done <-chan struct{}, graph *ent.Clie case <-done: return case <-ticker.C: - // Handle case where user has multiple shells open - alreadyAdded, err := graph.Shell.Query(). - Where(shell.ID(shellID)). + alreadyAdded, err := graph.Portal.Query(). + Where(portal.ID(portalID)). QueryActiveUsers(). Where(user.ID(userID)). Exist(ctx) if err != nil { - slog.ErrorContext(ctx, "websocket failed to check user activity for shell", "err", err, "user_id", userID, "shell_id", shellID) + slog.ErrorContext(ctx, "websocket failed to check user activity for portal", "err", err, "user_id", userID, "portal_id", portalID) continue } if alreadyAdded { continue } - if _, err := graph.Shell.UpdateOneID(shellID). + if _, err := graph.Portal.UpdateOneID(portalID). AddActiveUserIDs(userID). Save(ctx); err != nil { - slog.ErrorContext(ctx, "websocket failed to add active user to shell", "err", err, "user_id", userID, "shell_id", shellID) + slog.ErrorContext(ctx, "websocket failed to add active user to portal", "err", err, "user_id", userID, "portal_id", portalID) } } } - } // NewShellHandler provides an HTTP handler which handles a websocket for shell io. // It requires a query param "shell_id" be specified (must be an integer). -// This ID represents which Shell ent the websocket will connect to. -func NewShellHandler(graph *ent.Client, mux *Mux) http.HandlerFunc { +// This ID represents which Portal ent the websocket will connect to (mapped from shell_id). +func NewShellHandler(graph *ent.Client, mux *mux.Mux) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -255,29 +212,29 @@ func NewShellHandler(graph *ent.Client, mux *Mux) http.HandlerFunc { authUserName = authUser.Name } - // Parse Shell ID + // Parse Shell ID (Portal ID) shellIDStr := r.URL.Query().Get("shell_id") if shellIDStr == "" { http.Error(w, "must provide integer value for 'shell_id'", http.StatusBadRequest) return } - shellID, err := strconv.Atoi(shellIDStr) + portalID, err := strconv.Atoi(shellIDStr) if err != nil { http.Error(w, "invalid 'shell_id' provided, must be integer", http.StatusBadRequest) return } - // Load Shell - revShell, err := graph.Shell.Query(). - Where(shell.ID(shellID)). - Select(shell.FieldClosedAt). + // Load Portal + p, err := graph.Portal.Query(). + Where(portal.ID(portalID)). + Select(portal.FieldClosedAt). Only(ctx) if err != nil { if ent.IsNotFound(err) { - http.Error(w, "shell not found", http.StatusNotFound) + http.Error(w, "portal not found", http.StatusNotFound) } else { - slog.ErrorContext(ctx, "websocket failed to load shell", "err", err, "shell_id", shellID, "user_id", authUserID, "user_name", authUserName) - http.Error(w, "failed to load shell", http.StatusInternalServerError) + slog.ErrorContext(ctx, "websocket failed to load portal", "err", err, "portal_id", portalID, "user_id", authUserID, "user_name", authUserName) + http.Error(w, "failed to load portal", http.StatusInternalServerError) } return } @@ -287,36 +244,32 @@ func NewShellHandler(graph *ent.Client, mux *Mux) http.HandlerFunc { activeUserDoneCh := make(chan struct{}) if authUser != nil { activeUserWG.Add(1) - go func(ctx context.Context, shellID, userID int) { + go func(ctx context.Context, portalID, userID int) { defer activeUserWG.Done() - manageActiveUser(ctx, activeUserDoneCh, graph, shellID, userID) - }(ctx, revShell.ID, authUser.ID) + manageActiveUser(ctx, activeUserDoneCh, graph, portalID, userID) + }(ctx, p.ID, authUser.ID) } - // Prevent opening closed shells - if !revShell.ClosedAt.IsZero() { - http.Error(w, "shell already closed", http.StatusBadRequest) + // Prevent opening closed portals + if !p.ClosedAt.IsZero() { + http.Error(w, "portal already closed", http.StatusBadRequest) return } // Start Websocket - slog.InfoContext(ctx, "new shell websocket connection", "shell_id", shellID, "user_id", authUserID, "user_name", authUserName) + slog.InfoContext(ctx, "new portal websocket connection", "portal_id", portalID, "user_id", authUserID, "user_name", authUserName) ws, err := upgrader.Upgrade(w, r, nil) if err != nil { - slog.ErrorContext(ctx, "websocket failed to upgrade connection", "err", err, "shell_id", shellID, "user_id", authUserID, "user_name", authUserName) + slog.ErrorContext(ctx, "websocket failed to upgrade connection", "err", err, "portal_id", portalID, "user_id", authUserID, "user_name", authUserName) return } - defer slog.InfoContext(ctx, "websocket shell connection closed", "shell_id", shellID, "user_id", authUserID, "user_name", authUserName) - - // Initialize Stream - stream := New(shellIDStr) + defer slog.InfoContext(ctx, "websocket portal connection closed", "portal_id", portalID, "user_id", authUserID, "user_name", authUserName) // Create Connector conn := &connector{ - Stream: stream, - mux: mux, - ws: ws, - kind: "data", + mux: mux, + portalID: portalID, + ws: ws, } // Read & Write @@ -337,60 +290,10 @@ func NewShellHandler(graph *ent.Client, mux *Mux) http.HandlerFunc { }) } -// NewPingHandler provides an HTTP handler which handles a websocket for latency pings. -// It requires a query param "shell_id" be specified (must be an integer). -func NewPingHandler(graph *ent.Client, mux *Mux) http.HandlerFunc { +// NewPingHandler is a no-op handler to satisfy legacy routes if needed, +// or we can remove it. For now, let's keep it but just close. +func NewPingHandler(graph *ent.Client, mux *mux.Mux) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // Parse Shell ID - shellIDStr := r.URL.Query().Get("shell_id") - if shellIDStr == "" { - http.Error(w, "must provide integer value for 'shell_id'", http.StatusBadRequest) - return - } - shellID, err := strconv.Atoi(shellIDStr) - if err != nil { - http.Error(w, "invalid 'shell_id' provided, must be integer", http.StatusBadRequest) - return - } - - // Check if shell exists (optional, but good for consistency) - exists, err := graph.Shell.Query().Where(shell.ID(shellID)).Exist(ctx) - if err != nil || !exists { - http.Error(w, "shell not found", http.StatusNotFound) - return - } - - // Start Websocket - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - - // Initialize Stream - stream := New(shellIDStr) - - // Create Connector - conn := &connector{ - Stream: stream, - mux: mux, - ws: ws, - kind: "ping", - } - - // Read & Write - var wg sync.WaitGroup - wg.Add(2) - go func() { - defer wg.Done() - conn.ReadFromWebsocket(ctx) - }() - go func() { - defer wg.Done() - conn.WriteToWebsocket(ctx) - }() - - wg.Wait() + http.Error(w, "not implemented", http.StatusNotImplemented) }) } diff --git a/tavern/internal/http/stream/websocket_test.go b/tavern/internal/http/stream/websocket_test.go deleted file mode 100644 index 670328052..000000000 --- a/tavern/internal/http/stream/websocket_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package stream_test - -import ( - "context" - "net/http/httptest" - "strconv" - "strings" - "testing" - "time" - - "github.com/gorilla/websocket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gocloud.dev/pubsub" - _ "gocloud.dev/pubsub/mempubsub" - "realm.pub/tavern/internal/c2/c2pb" - "realm.pub/tavern/internal/ent/enttest" - "realm.pub/tavern/internal/http/stream" - - _ "github.com/mattn/go-sqlite3" -) - -func TestNewShellHandler(t *testing.T) { - // Setup Ent Client - graph := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") - defer graph.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // Topic for messages going TO the websocket (server -> shell) - outputTopic, err := pubsub.OpenTopic(ctx, "mem://websocket-output") - require.NoError(t, err) - defer outputTopic.Shutdown(ctx) - outputSub, err := pubsub.OpenSubscription(ctx, "mem://websocket-output") - require.NoError(t, err) - defer outputSub.Shutdown(ctx) - - // Topic for messages coming FROM the websocket (shell -> server) - inputTopic, err := pubsub.OpenTopic(ctx, "mem://websocket-input") - require.NoError(t, err) - defer inputTopic.Shutdown(ctx) - inputSub, err := pubsub.OpenSubscription(ctx, "mem://websocket-input") - require.NoError(t, err) - defer inputSub.Shutdown(ctx) - - // Create a test user - user, err := graph.User.Create().SetName("test-user").SetOauthID("test-oauth-id").SetPhotoURL("http://example.com/photo.jpg").Save(ctx) - require.NoError(t, err) - - // Create a test host - host, err := graph.Host.Create().SetIdentifier("test-host").SetPlatform(c2pb.Host_PLATFORM_LINUX).Save(ctx) - require.NoError(t, err) - - // Create a test beacon - beacon, err := graph.Beacon.Create().SetHost(host).SetTransport(c2pb.ActiveTransport_TRANSPORT_UNSPECIFIED).Save(ctx) - require.NoError(t, err) - - // Create a test tome - tome, err := graph.Tome.Create(). - SetName("test-tome"). - SetDescription("test-description"). - SetAuthor("test-author"). - SetEldritch("test-eldritch"). - SetUploader(user). - Save(ctx) - require.NoError(t, err) - - // Create a test quest - quest, err := graph.Quest.Create().SetName("test-quest").SetTome(tome).SetCreator(user).Save(ctx) - require.NoError(t, err) - - // Create a test task - task, err := graph.Task.Create().SetQuest(quest).SetBeacon(beacon).Save(ctx) - require.NoError(t, err) - - // Create Mux with the correct topics - mux := stream.NewMux(inputTopic, outputSub) - go mux.Start(ctx) - - // Create a test shell - shell, err := graph.Shell.Create().SetData([]byte("test-data")).SetOwner(user).SetTask(task).SetBeacon(beacon).Save(ctx) - require.NoError(t, err) - - // Create a test server - handler := stream.NewShellHandler(graph, mux) - server := httptest.NewServer(handler) - defer server.Close() - - // Create a websocket client - wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "?shell_id=" + strconv.Itoa(shell.ID) - ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil) - require.NoError(t, err) - defer ws.Close() - - // Test writing to the websocket (server -> shell) - testMessage := []byte("hello from server") - err = outputTopic.Send(ctx, &pubsub.Message{ - Body: testMessage, - Metadata: map[string]string{"id": strconv.Itoa(shell.ID)}, - }) - require.NoError(t, err) - - _, p, err := ws.ReadMessage() - assert.NoError(t, err) - - assert.Equal(t, testMessage, p) - - // Test reading from the websocket (shell -> server) - // Client sends raw bytes - readMessage := []byte("hello from shell") - err = ws.WriteMessage(websocket.TextMessage, readMessage) - require.NoError(t, err) - - // Now, we expect the message on the input subscription - msg, err := inputSub.Receive(ctx) - require.NoError(t, err, "timed out waiting for message from websocket") - - // The body sent to pubsub should be the raw bytes - assert.Equal(t, readMessage, msg.Body) - assert.Equal(t, "data", msg.Metadata[stream.MetadataMsgKind]) - msg.Ack() -} diff --git a/tavern/portals/portalpb/portal.pb.go b/tavern/portals/portalpb/portal.pb.go index f646908f9..1c53b09ab 100644 --- a/tavern/portals/portalpb/portal.pb.go +++ b/tavern/portals/portalpb/portal.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.11 // protoc v3.21.12 // source: portal.proto @@ -248,6 +248,50 @@ func (x *UDPPayload) GetDstPort() uint32 { return 0 } +type REPLMessage struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *REPLMessage) Reset() { + *x = REPLMessage{} + mi := &file_portal_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *REPLMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*REPLMessage) ProtoMessage() {} + +func (x *REPLMessage) ProtoReflect() protoreflect.Message { + mi := &file_portal_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use REPLMessage.ProtoReflect.Descriptor instead. +func (*REPLMessage) Descriptor() ([]byte, []int) { + return file_portal_proto_rawDescGZIP(), []int{3} +} + +func (x *REPLMessage) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + type Mote struct { state protoimpl.MessageState `protogen:"open.v1"` // Unique identifier used to route reply traffic back to original port @@ -261,6 +305,7 @@ type Mote struct { // *Mote_Udp // *Mote_Tcp // *Mote_Bytes + // *Mote_Repl Payload isMote_Payload `protobuf_oneof:"payload"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -268,7 +313,7 @@ type Mote struct { func (x *Mote) Reset() { *x = Mote{} - mi := &file_portal_proto_msgTypes[3] + mi := &file_portal_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -280,7 +325,7 @@ func (x *Mote) String() string { func (*Mote) ProtoMessage() {} func (x *Mote) ProtoReflect() protoreflect.Message { - mi := &file_portal_proto_msgTypes[3] + mi := &file_portal_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -293,7 +338,7 @@ func (x *Mote) ProtoReflect() protoreflect.Message { // Deprecated: Use Mote.ProtoReflect.Descriptor instead. func (*Mote) Descriptor() ([]byte, []int) { - return file_portal_proto_rawDescGZIP(), []int{3} + return file_portal_proto_rawDescGZIP(), []int{4} } func (x *Mote) GetStreamId() string { @@ -344,6 +389,15 @@ func (x *Mote) GetBytes() *BytesPayload { return nil } +func (x *Mote) GetRepl() *REPLMessage { + if x != nil { + if x, ok := x.Payload.(*Mote_Repl); ok { + return x.Repl + } + } + return nil +} + type isMote_Payload interface { isMote_Payload() } @@ -360,12 +414,18 @@ type Mote_Bytes struct { Bytes *BytesPayload `protobuf:"bytes,5,opt,name=bytes,proto3,oneof"` } +type Mote_Repl struct { + Repl *REPLMessage `protobuf:"bytes,6,opt,name=repl,proto3,oneof"` +} + func (*Mote_Udp) isMote_Payload() {} func (*Mote_Tcp) isMote_Payload() {} func (*Mote_Bytes) isMote_Payload() {} +func (*Mote_Repl) isMote_Payload() {} + type OpenPortalRequest struct { state protoimpl.MessageState `protogen:"open.v1"` PortalId int64 `protobuf:"varint,1,opt,name=portal_id,json=portalId,proto3" json:"portal_id,omitempty"` @@ -376,7 +436,7 @@ type OpenPortalRequest struct { func (x *OpenPortalRequest) Reset() { *x = OpenPortalRequest{} - mi := &file_portal_proto_msgTypes[4] + mi := &file_portal_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -388,7 +448,7 @@ func (x *OpenPortalRequest) String() string { func (*OpenPortalRequest) ProtoMessage() {} func (x *OpenPortalRequest) ProtoReflect() protoreflect.Message { - mi := &file_portal_proto_msgTypes[4] + mi := &file_portal_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -401,7 +461,7 @@ func (x *OpenPortalRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenPortalRequest.ProtoReflect.Descriptor instead. func (*OpenPortalRequest) Descriptor() ([]byte, []int) { - return file_portal_proto_rawDescGZIP(), []int{4} + return file_portal_proto_rawDescGZIP(), []int{5} } func (x *OpenPortalRequest) GetPortalId() int64 { @@ -427,7 +487,7 @@ type OpenPortalResponse struct { func (x *OpenPortalResponse) Reset() { *x = OpenPortalResponse{} - mi := &file_portal_proto_msgTypes[5] + mi := &file_portal_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -439,7 +499,7 @@ func (x *OpenPortalResponse) String() string { func (*OpenPortalResponse) ProtoMessage() {} func (x *OpenPortalResponse) ProtoReflect() protoreflect.Message { - mi := &file_portal_proto_msgTypes[5] + mi := &file_portal_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -452,7 +512,7 @@ func (x *OpenPortalResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenPortalResponse.ProtoReflect.Descriptor instead. func (*OpenPortalResponse) Descriptor() ([]byte, []int) { - return file_portal_proto_rawDescGZIP(), []int{5} + return file_portal_proto_rawDescGZIP(), []int{6} } func (x *OpenPortalResponse) GetMote() *Mote { @@ -464,66 +524,46 @@ func (x *OpenPortalResponse) GetMote() *Mote { var File_portal_proto protoreflect.FileDescriptor -var file_portal_proto_rawDesc = string([]byte{ - 0x0a, 0x0c, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, - 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x22, 0x50, 0x0a, 0x0c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, - 0x6c, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4b, 0x69, - 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x56, 0x0a, 0x0a, 0x54, 0x43, 0x50, 0x50, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, - 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, - 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x64, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x22, 0x56, 0x0a, 0x0a, 0x55, 0x44, 0x50, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x19, 0x0a, - 0x08, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x07, 0x64, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xc3, 0x01, 0x0a, 0x04, 0x4d, 0x6f, 0x74, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, - 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x73, 0x65, 0x71, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x03, 0x75, 0x64, 0x70, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x55, 0x44, 0x50, 0x50, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x03, 0x75, 0x64, 0x70, 0x12, 0x26, 0x0a, - 0x03, 0x74, 0x63, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x6f, 0x72, - 0x74, 0x61, 0x6c, 0x2e, 0x54, 0x43, 0x50, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, - 0x52, 0x03, 0x74, 0x63, 0x70, 0x12, 0x2c, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x05, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x52, - 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x49, 0x64, - 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x6f, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, - 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x4d, 0x6f, 0x74, 0x65, 0x52, 0x04, 0x6d, 0x6f, - 0x74, 0x65, 0x22, 0x36, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x6f, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, - 0x4d, 0x6f, 0x74, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x74, 0x65, 0x2a, 0xb0, 0x01, 0x0a, 0x10, 0x42, - 0x79, 0x74, 0x65, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x4b, 0x69, 0x6e, 0x64, 0x12, - 0x22, 0x0a, 0x1e, 0x42, 0x59, 0x54, 0x45, 0x53, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, - 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x59, 0x54, 0x45, 0x53, 0x5f, 0x50, 0x41, 0x59, - 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x01, - 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x59, 0x54, 0x45, 0x53, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, - 0x44, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x20, 0x0a, - 0x1c, 0x42, 0x59, 0x54, 0x45, 0x53, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x4b, - 0x49, 0x4e, 0x44, 0x5f, 0x4b, 0x45, 0x45, 0x50, 0x41, 0x4c, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, - 0x1c, 0x0a, 0x18, 0x42, 0x59, 0x54, 0x45, 0x53, 0x5f, 0x50, 0x41, 0x59, 0x4c, 0x4f, 0x41, 0x44, - 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x04, 0x32, 0x53, 0x0a, - 0x06, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x12, 0x49, 0x0a, 0x0a, 0x4f, 0x70, 0x65, 0x6e, 0x50, - 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x12, 0x19, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x4f, - 0x70, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x6f, - 0x72, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, - 0x30, 0x01, 0x42, 0x23, 0x5a, 0x21, 0x72, 0x65, 0x61, 0x6c, 0x6d, 0x2e, 0x70, 0x75, 0x62, 0x2f, - 0x74, 0x61, 0x76, 0x65, 0x72, 0x6e, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x73, 0x2f, 0x70, - 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_portal_proto_rawDesc = "" + + "\n" + + "\fportal.proto\x12\x06portal\"P\n" + + "\fBytesPayload\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\x12,\n" + + "\x04kind\x18\x02 \x01(\x0e2\x18.portal.BytesPayloadKindR\x04kind\"V\n" + + "\n" + + "TCPPayload\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\x12\x19\n" + + "\bdst_addr\x18\x02 \x01(\tR\adstAddr\x12\x19\n" + + "\bdst_port\x18\x03 \x01(\rR\adstPort\"V\n" + + "\n" + + "UDPPayload\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\x12\x19\n" + + "\bdst_addr\x18\x02 \x01(\tR\adstAddr\x12\x19\n" + + "\bdst_port\x18\x03 \x01(\rR\adstPort\"!\n" + + "\vREPLMessage\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"\xee\x01\n" + + "\x04Mote\x12\x1b\n" + + "\tstream_id\x18\x01 \x01(\tR\bstreamId\x12\x15\n" + + "\x06seq_id\x18\x02 \x01(\x04R\x05seqId\x12&\n" + + "\x03udp\x18\x03 \x01(\v2\x12.portal.UDPPayloadH\x00R\x03udp\x12&\n" + + "\x03tcp\x18\x04 \x01(\v2\x12.portal.TCPPayloadH\x00R\x03tcp\x12,\n" + + "\x05bytes\x18\x05 \x01(\v2\x14.portal.BytesPayloadH\x00R\x05bytes\x12)\n" + + "\x04repl\x18\x06 \x01(\v2\x13.portal.REPLMessageH\x00R\x04replB\t\n" + + "\apayload\"R\n" + + "\x11OpenPortalRequest\x12\x1b\n" + + "\tportal_id\x18\x01 \x01(\x03R\bportalId\x12 \n" + + "\x04mote\x18\x02 \x01(\v2\f.portal.MoteR\x04mote\"6\n" + + "\x12OpenPortalResponse\x12 \n" + + "\x04mote\x18\x02 \x01(\v2\f.portal.MoteR\x04mote*\xb0\x01\n" + + "\x10BytesPayloadKind\x12\"\n" + + "\x1eBYTES_PAYLOAD_KIND_UNSPECIFIED\x10\x00\x12\x1b\n" + + "\x17BYTES_PAYLOAD_KIND_DATA\x10\x01\x12\x1b\n" + + "\x17BYTES_PAYLOAD_KIND_PING\x10\x02\x12 \n" + + "\x1cBYTES_PAYLOAD_KIND_KEEPALIVE\x10\x03\x12\x1c\n" + + "\x18BYTES_PAYLOAD_KIND_TRACE\x10\x042S\n" + + "\x06Portal\x12I\n" + + "\n" + + "OpenPortal\x12\x19.portal.OpenPortalRequest\x1a\x1a.portal.OpenPortalResponse\"\x00(\x010\x01B#Z!realm.pub/tavern/portals/portalpbb\x06proto3" var ( file_portal_proto_rawDescOnce sync.Once @@ -538,30 +578,32 @@ func file_portal_proto_rawDescGZIP() []byte { } var file_portal_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_portal_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_portal_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_portal_proto_goTypes = []any{ (BytesPayloadKind)(0), // 0: portal.BytesPayloadKind (*BytesPayload)(nil), // 1: portal.BytesPayload (*TCPPayload)(nil), // 2: portal.TCPPayload (*UDPPayload)(nil), // 3: portal.UDPPayload - (*Mote)(nil), // 4: portal.Mote - (*OpenPortalRequest)(nil), // 5: portal.OpenPortalRequest - (*OpenPortalResponse)(nil), // 6: portal.OpenPortalResponse + (*REPLMessage)(nil), // 4: portal.REPLMessage + (*Mote)(nil), // 5: portal.Mote + (*OpenPortalRequest)(nil), // 6: portal.OpenPortalRequest + (*OpenPortalResponse)(nil), // 7: portal.OpenPortalResponse } var file_portal_proto_depIdxs = []int32{ 0, // 0: portal.BytesPayload.kind:type_name -> portal.BytesPayloadKind 3, // 1: portal.Mote.udp:type_name -> portal.UDPPayload 2, // 2: portal.Mote.tcp:type_name -> portal.TCPPayload 1, // 3: portal.Mote.bytes:type_name -> portal.BytesPayload - 4, // 4: portal.OpenPortalRequest.mote:type_name -> portal.Mote - 4, // 5: portal.OpenPortalResponse.mote:type_name -> portal.Mote - 5, // 6: portal.Portal.OpenPortal:input_type -> portal.OpenPortalRequest - 6, // 7: portal.Portal.OpenPortal:output_type -> portal.OpenPortalResponse - 7, // [7:8] is the sub-list for method output_type - 6, // [6:7] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 4, // 4: portal.Mote.repl:type_name -> portal.REPLMessage + 5, // 5: portal.OpenPortalRequest.mote:type_name -> portal.Mote + 5, // 6: portal.OpenPortalResponse.mote:type_name -> portal.Mote + 6, // 7: portal.Portal.OpenPortal:input_type -> portal.OpenPortalRequest + 7, // 8: portal.Portal.OpenPortal:output_type -> portal.OpenPortalResponse + 8, // [8:9] is the sub-list for method output_type + 7, // [7:8] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_portal_proto_init() } @@ -569,10 +611,11 @@ func file_portal_proto_init() { if File_portal_proto != nil { return } - file_portal_proto_msgTypes[3].OneofWrappers = []any{ + file_portal_proto_msgTypes[4].OneofWrappers = []any{ (*Mote_Udp)(nil), (*Mote_Tcp)(nil), (*Mote_Bytes)(nil), + (*Mote_Repl)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -580,7 +623,7 @@ func file_portal_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_portal_proto_rawDesc), len(file_portal_proto_rawDesc)), NumEnums: 1, - NumMessages: 6, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, diff --git a/tavern/portals/portalpb/portal_grpc.pb.go b/tavern/portals/portalpb/portal_grpc.pb.go index 9b0bd90f3..0890a534a 100644 --- a/tavern/portals/portalpb/portal_grpc.pb.go +++ b/tavern/portals/portalpb/portal_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 +// - protoc-gen-go-grpc v1.6.0 // - protoc v3.21.12 // source: portal.proto @@ -15,8 +15,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.62.0 or later. -const _ = grpc.SupportPackageIsVersion8 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( Portal_OpenPortal_FullMethodName = "/portal.Portal/OpenPortal" @@ -26,7 +26,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PortalClient interface { - OpenPortal(ctx context.Context, opts ...grpc.CallOption) (Portal_OpenPortalClient, error) + OpenPortal(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[OpenPortalRequest, OpenPortalResponse], error) } type portalClient struct { @@ -37,54 +37,39 @@ func NewPortalClient(cc grpc.ClientConnInterface) PortalClient { return &portalClient{cc} } -func (c *portalClient) OpenPortal(ctx context.Context, opts ...grpc.CallOption) (Portal_OpenPortalClient, error) { +func (c *portalClient) OpenPortal(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[OpenPortalRequest, OpenPortalResponse], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &Portal_ServiceDesc.Streams[0], Portal_OpenPortal_FullMethodName, cOpts...) if err != nil { return nil, err } - x := &portalOpenPortalClient{ClientStream: stream} + x := &grpc.GenericClientStream[OpenPortalRequest, OpenPortalResponse]{ClientStream: stream} return x, nil } -type Portal_OpenPortalClient interface { - Send(*OpenPortalRequest) error - Recv() (*OpenPortalResponse, error) - grpc.ClientStream -} - -type portalOpenPortalClient struct { - grpc.ClientStream -} - -func (x *portalOpenPortalClient) Send(m *OpenPortalRequest) error { - return x.ClientStream.SendMsg(m) -} - -func (x *portalOpenPortalClient) Recv() (*OpenPortalResponse, error) { - m := new(OpenPortalResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Portal_OpenPortalClient = grpc.BidiStreamingClient[OpenPortalRequest, OpenPortalResponse] // PortalServer is the server API for Portal service. // All implementations must embed UnimplementedPortalServer -// for forward compatibility +// for forward compatibility. type PortalServer interface { - OpenPortal(Portal_OpenPortalServer) error + OpenPortal(grpc.BidiStreamingServer[OpenPortalRequest, OpenPortalResponse]) error mustEmbedUnimplementedPortalServer() } -// UnimplementedPortalServer must be embedded to have forward compatible implementations. -type UnimplementedPortalServer struct { -} +// UnimplementedPortalServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPortalServer struct{} -func (UnimplementedPortalServer) OpenPortal(Portal_OpenPortalServer) error { - return status.Errorf(codes.Unimplemented, "method OpenPortal not implemented") +func (UnimplementedPortalServer) OpenPortal(grpc.BidiStreamingServer[OpenPortalRequest, OpenPortalResponse]) error { + return status.Error(codes.Unimplemented, "method OpenPortal not implemented") } func (UnimplementedPortalServer) mustEmbedUnimplementedPortalServer() {} +func (UnimplementedPortalServer) testEmbeddedByValue() {} // UnsafePortalServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PortalServer will @@ -94,34 +79,22 @@ type UnsafePortalServer interface { } func RegisterPortalServer(s grpc.ServiceRegistrar, srv PortalServer) { + // If the following call panics, it indicates UnimplementedPortalServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&Portal_ServiceDesc, srv) } func _Portal_OpenPortal_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(PortalServer).OpenPortal(&portalOpenPortalServer{ServerStream: stream}) -} - -type Portal_OpenPortalServer interface { - Send(*OpenPortalResponse) error - Recv() (*OpenPortalRequest, error) - grpc.ServerStream -} - -type portalOpenPortalServer struct { - grpc.ServerStream + return srv.(PortalServer).OpenPortal(&grpc.GenericServerStream[OpenPortalRequest, OpenPortalResponse]{ServerStream: stream}) } -func (x *portalOpenPortalServer) Send(m *OpenPortalResponse) error { - return x.ServerStream.SendMsg(m) -} - -func (x *portalOpenPortalServer) Recv() (*OpenPortalRequest, error) { - m := new(OpenPortalRequest) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Portal_OpenPortalServer = grpc.BidiStreamingServer[OpenPortalRequest, OpenPortalResponse] // Portal_ServiceDesc is the grpc.ServiceDesc for Portal service. // It's only intended for direct use with grpc.RegisterService, diff --git a/tavern/portals/proto/portal.proto b/tavern/portals/proto/portal.proto index 0a5856984..b34db14d4 100644 --- a/tavern/portals/proto/portal.proto +++ b/tavern/portals/proto/portal.proto @@ -26,6 +26,9 @@ message UDPPayload { string dst_addr = 2; uint32 dst_port = 3; } +message REPLMessage { + bytes data = 1; +} message Mote { // Unique identifier used to route reply traffic back to original port @@ -39,6 +42,7 @@ message Mote { UDPPayload udp = 3; TCPPayload tcp = 4; BytesPayload bytes = 5; + REPLMessage repl = 6; } } diff --git a/tavern/portals/tracepb/trace.pb.go b/tavern/portals/tracepb/trace.pb.go index d16c158d5..4a6d657f4 100644 --- a/tavern/portals/tracepb/trace.pb.go +++ b/tavern/portals/tracepb/trace.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.11 // protoc v3.21.12 // source: trace.proto @@ -222,57 +222,32 @@ func (x *TraceData) GetEvents() []*TraceEvent { var File_trace_proto protoreflect.FileDescriptor -var file_trace_proto_rawDesc = string([]byte{ - 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, - 0x72, 0x61, 0x63, 0x65, 0x22, 0x62, 0x0a, 0x0a, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x15, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x29, 0x0a, - 0x10, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x73, 0x0a, 0x09, 0x54, 0x72, 0x61, 0x63, - 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, - 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, - 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, - 0x6e, 0x67, 0x12, 0x29, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2a, 0xec, 0x03, - 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, - 0x12, 0x20, 0x0a, 0x1c, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, - 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, - 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x45, 0x4e, 0x44, - 0x10, 0x01, 0x12, 0x25, 0x0a, 0x21, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, - 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x55, 0x53, - 0x45, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x10, 0x02, 0x12, 0x24, 0x0a, 0x20, 0x54, 0x52, 0x41, - 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, - 0x52, 0x56, 0x45, 0x52, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x50, 0x55, 0x42, 0x10, 0x03, 0x12, - 0x25, 0x0a, 0x21, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, - 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, - 0x5f, 0x53, 0x55, 0x42, 0x10, 0x04, 0x12, 0x26, 0x0a, 0x22, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, - 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x05, 0x12, 0x1f, - 0x0a, 0x1b, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x49, - 0x4e, 0x44, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x10, 0x06, 0x12, - 0x1f, 0x0a, 0x1b, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, - 0x49, 0x4e, 0x44, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x07, - 0x12, 0x26, 0x0a, 0x22, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, - 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, - 0x54, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x10, 0x08, 0x12, 0x25, 0x0a, 0x21, 0x54, 0x52, 0x41, 0x43, - 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, - 0x56, 0x45, 0x52, 0x5f, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x10, 0x09, 0x12, - 0x24, 0x0a, 0x20, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, - 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, - 0x53, 0x55, 0x42, 0x10, 0x0a, 0x12, 0x25, 0x0a, 0x21, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, - 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, - 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x0b, 0x12, 0x1e, 0x0a, 0x1a, - 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4b, 0x49, 0x4e, 0x44, - 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x43, 0x56, 0x10, 0x0c, 0x42, 0x22, 0x5a, 0x20, - 0x72, 0x65, 0x61, 0x6c, 0x6d, 0x2e, 0x70, 0x75, 0x62, 0x2f, 0x74, 0x61, 0x76, 0x65, 0x72, 0x6e, - 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x73, 0x2f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x70, 0x62, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_trace_proto_rawDesc = "" + + "\n" + + "\vtrace.proto\x12\x05trace\"b\n" + + "\n" + + "TraceEvent\x12)\n" + + "\x04kind\x18\x01 \x01(\x0e2\x15.trace.TraceEventKindR\x04kind\x12)\n" + + "\x10timestamp_micros\x18\x02 \x01(\x03R\x0ftimestampMicros\"s\n" + + "\tTraceData\x12!\n" + + "\fstart_micros\x18\x01 \x01(\x03R\vstartMicros\x12\x18\n" + + "\apadding\x18\x02 \x01(\fR\apadding\x12)\n" + + "\x06events\x18\x03 \x03(\v2\x11.trace.TraceEventR\x06events*\xec\x03\n" + + "\x0eTraceEventKind\x12 \n" + + "\x1cTRACE_EVENT_KIND_UNSPECIFIED\x10\x00\x12\x1e\n" + + "\x1aTRACE_EVENT_KIND_USER_SEND\x10\x01\x12%\n" + + "!TRACE_EVENT_KIND_SERVER_USER_RECV\x10\x02\x12$\n" + + " TRACE_EVENT_KIND_SERVER_USER_PUB\x10\x03\x12%\n" + + "!TRACE_EVENT_KIND_SERVER_AGENT_SUB\x10\x04\x12&\n" + + "\"TRACE_EVENT_KIND_SERVER_AGENT_SEND\x10\x05\x12\x1f\n" + + "\x1bTRACE_EVENT_KIND_AGENT_RECV\x10\x06\x12\x1f\n" + + "\x1bTRACE_EVENT_KIND_AGENT_SEND\x10\a\x12&\n" + + "\"TRACE_EVENT_KIND_SERVER_AGENT_RECV\x10\b\x12%\n" + + "!TRACE_EVENT_KIND_SERVER_AGENT_PUB\x10\t\x12$\n" + + " TRACE_EVENT_KIND_SERVER_USER_SUB\x10\n" + + "\x12%\n" + + "!TRACE_EVENT_KIND_SERVER_USER_SEND\x10\v\x12\x1e\n" + + "\x1aTRACE_EVENT_KIND_USER_RECV\x10\fB\"Z realm.pub/tavern/portals/tracepbb\x06proto3" var ( file_trace_proto_rawDescOnce sync.Once