syntax = "proto3";

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

option go_package = "/proto";

package management;

service ManagementService {

  // Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey
  // Returns encrypted LoginResponse in EncryptedMessage.Body
  rpc Login(EncryptedMessage) returns (EncryptedMessage) {}

  // Sync enables peer synchronization. Each peer that is connected to this stream will receive updates from the server.
  // For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
  // The initial SyncResponse contains all of the available peers so the local state can be refreshed
  // Returns encrypted SyncResponse in EncryptedMessage.Body
  rpc Sync(EncryptedMessage) returns (stream EncryptedMessage) {}

  // Exposes a Wireguard public key of the Management service.
  // This key is used to support message encryption between client and server
  rpc GetServerKey(Empty) returns (ServerKeyResponse) {}

  // health check endpoint
  rpc isHealthy(Empty) returns (Empty) {}

  // Exposes a device authorization flow information
  // This is used for initiating a Oauth 2 device authorization grant flow
  // which will be used by our clients to Login.
  // EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest.
  // EncryptedMessage of the response has a body of DeviceAuthorizationFlow.
  rpc GetDeviceAuthorizationFlow(EncryptedMessage) returns (EncryptedMessage) {}

  // Exposes a PKCE authorization code flow information
  // This is used for initiating a Oauth 2 authorization grant flow
  // with Proof Key for Code Exchange (PKCE) which will be used by our clients to Login.
  // EncryptedMessage of the request has a body of PKCEAuthorizationFlowRequest.
  // EncryptedMessage of the response has a body of PKCEAuthorizationFlow.
  rpc GetPKCEAuthorizationFlow(EncryptedMessage) returns (EncryptedMessage) {}

  // SyncMeta is used to sync metadata of the peer.
  // After sync the peer if there is a change in peer posture check which  needs to be evaluated by the client,
  // sync meta will evaluate the checks and update the peer meta with the result.
  // EncryptedMessage of the request has a body of Empty.
  rpc  SyncMeta(EncryptedMessage) returns (Empty) {}

  // Logout logs out the peer and removes it from the management server
  rpc Logout(EncryptedMessage) returns (Empty) {}

  // Executes a job on a target peer (e.g., debug bundle)
  rpc Job(stream EncryptedMessage) returns (stream EncryptedMessage) {}

  // ExtendAuthSession refreshes the peer's session expiry deadline using a fresh JWT.
  // Same JWT validation pipeline as Login (including jwt.UserID == peer.UserID check),
  // but does not redo the network-map sync. Only valid for SSO-registered peers where
  // login expiration is enabled. The tunnel remains up.
  // EncryptedMessage of the request has a body of ExtendAuthSessionRequest.
  // EncryptedMessage of the response has a body of ExtendAuthSessionResponse.
  rpc ExtendAuthSession(EncryptedMessage) returns (EncryptedMessage) {}

  // CreateExpose creates a temporary reverse proxy service for a peer
  rpc CreateExpose(EncryptedMessage) returns (EncryptedMessage) {}

  // RenewExpose extends the TTL of an active expose session
  rpc RenewExpose(EncryptedMessage) returns (EncryptedMessage) {}

  // StopExpose terminates an active expose session
  rpc StopExpose(EncryptedMessage) returns (EncryptedMessage) {}
}

message EncryptedMessage {
  // Wireguard public key
  string wgPubKey = 1;

  // encrypted message Body
  bytes body = 2;
  // Version of the Netbird Management Service protocol
  int32 version = 3;
}

message JobRequest {
  bytes ID = 1;

  oneof workload_parameters {
    BundleParameters bundle = 10;
    //OtherParameters other = 11;
  }
}

enum JobStatus {
  unknown_status = 0; //placeholder
  succeeded = 1;
  failed = 2;
}

message JobResponse{
  bytes ID = 1;
  JobStatus status=2;
  bytes Reason=3;
  oneof workload_results {
    BundleResult bundle = 10;
    //OtherResult other = 11;
  }
}

message BundleParameters {
  bool   bundle_for       = 1;
  int64  bundle_for_time  = 2;
  int32  log_file_count   = 3;
  bool   anonymize        = 4;
}

message BundleResult {
  string upload_key = 1;
}

message SyncRequest {
  // Meta data of the peer
  PeerSystemMeta meta = 1;
}

// SyncResponse represents a state that should be applied to the local peer (e.g. Netbird servers config as well as local peer and remote peers configs)
message SyncResponse {

  // Global config
  NetbirdConfig netbirdConfig = 1;

  // Deprecated. Use NetworkMap.PeerConfig
  PeerConfig peerConfig = 2;

  // Deprecated. Use NetworkMap.RemotePeerConfig
  repeated RemotePeerConfig remotePeers = 3;

  // Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
  // Deprecated. Use NetworkMap.remotePeersIsEmpty
  bool remotePeersIsEmpty = 4;

  NetworkMap NetworkMap = 5;

  // Posture checks to be evaluated by client
  repeated Checks Checks = 6;

  // 3-state session deadline. Carried on every Sync snapshot so admin-side
  // changes propagate live without a client reconnect.
  //   field unset (nil)        → snapshot carries no info; client keeps the
  //                              deadline it already had
  //   set, seconds=0 nanos=0   → explicit "expiry disabled" or peer is not
  //                              SSO-registered; client clears its anchor
  //   set, valid timestamp     → new absolute UTC deadline
  google.protobuf.Timestamp sessionExpiresAt = 7;

  // NetworkMapEnvelope carries the component-based wire format for peers that
  // advertise PeerCapabilityComponentNetworkMap. When set, NetworkMap (field 5)
  // is left empty: management ships components and the client runs Calculate()
  // locally instead of receiving an expanded NetworkMap.
  NetworkMapEnvelope NetworkMapEnvelope = 8;

  int32 Version = 9;
}

message  SyncMetaRequest {
  // Meta data of the peer
  PeerSystemMeta meta = 1;
}

message LoginRequest {
  // Pre-authorized setup key (can be empty)
  string setupKey = 1;
  // Meta data of the peer (e.g. name, os_name, os_version,
  PeerSystemMeta meta = 2;
  // SSO token (can be empty)
  string jwtToken = 3;
  // Can be absent for now.
  PeerKeys peerKeys = 4;

  repeated string dnsLabels = 5;
}

// PeerKeys is additional peer info like SSH pub key and WireGuard public key.
// This message is sent on Login or register requests, or when a key rotation has to happen.
message PeerKeys {

  // sshPubKey represents a public SSH key of the peer. Can be absent.
  bytes sshPubKey = 1;
  // wgPubKey represents a public WireGuard key of the peer. Can be absent.
  bytes wgPubKey = 2;
}

// Environment is part of the PeerSystemMeta and describes the environment the agent is running in.
message Environment {
  // cloud is the cloud provider the agent is running in if applicable.
  string cloud = 1;
  // platform is the platform the agent is running on if applicable.
  string platform = 2;
}

// File represents a file on the system.
message File {
  // path is the path to the file.
  string path = 1;
  // exist indicate whether the file exists.
  bool exist = 2;
  // processIsRunning indicates whether the file is a running process or not.
  bool processIsRunning = 3;
}

message Flags {
  bool rosenpassEnabled = 1;
  bool rosenpassPermissive = 2;
  bool serverSSHAllowed = 3;

  bool disableClientRoutes = 4;
  bool disableServerRoutes = 5;
  bool disableDNS = 6;
  bool disableFirewall = 7;
  bool blockLANAccess = 8;
  bool blockInbound = 9;

  bool lazyConnectionEnabled = 10;

  bool enableSSHRoot = 11;
  bool enableSSHSFTP = 12;
  bool enableSSHLocalPortForwarding = 13;
  bool enableSSHRemotePortForwarding = 14;
  bool disableSSHAuth = 15;

  bool disableIPv6 = 16;
}

// PeerCapability represents a feature the client binary supports.
// Reported in PeerSystemMeta.capabilities on every login/sync.
enum PeerCapability {
  PeerCapabilityUnknown = 0;
  // Client reads SourcePrefixes instead of the deprecated PeerIP string.
  PeerCapabilitySourcePrefixes = 1;
  // Client handles IPv6 overlay addresses and firewall rules.
  PeerCapabilityIPv6Overlay = 2;
  // Client receives NetworkMap as components and assembles it locally.
  PeerCapabilityComponentNetworkMap = 3;
}

// PeerSystemMeta is machine meta data like OS and version.
message PeerSystemMeta {
  string hostname = 1;
  string goOS = 2;
  string kernel = 3;
  string core = 4;
  string platform = 5;
  string OS = 6;
  string netbirdVersion = 7;
  string uiVersion = 8;
  string kernelVersion = 9;
  string OSVersion = 10;
  repeated NetworkAddress networkAddresses = 11;
  string sysSerialNumber = 12;
  string sysProductName = 13;
  string sysManufacturer = 14;
  Environment environment = 15;
  repeated File files = 16;
  Flags flags = 17;

  repeated PeerCapability capabilities = 18;
  int32 syncMessageVersion = 19;
}

message LoginResponse {
  // Global config
  NetbirdConfig netbirdConfig = 1;
  // Peer local config
  PeerConfig peerConfig = 2;
  // Posture checks to be evaluated by client
  repeated Checks Checks = 3;

  // 3-state session deadline; same encoding as SyncResponse.sessionExpiresAt.
  //   field unset (nil)        → no info; client keeps any deadline it had
  //   set, seconds=0 nanos=0   → explicit "expiry disabled" / non-SSO peer
  //   set, valid timestamp     → new absolute UTC deadline
  google.protobuf.Timestamp sessionExpiresAt = 4;
}

// ExtendAuthSessionRequest carries a fresh JWT to refresh the peer's session deadline.
// The encrypted body of an EncryptedMessage with this payload is sent to the
// ExtendAuthSession RPC.
message ExtendAuthSessionRequest {
  // SSO token (must be a fresh, valid JWT for the peer's owning user)
  string jwtToken = 1;
  // Meta data of the peer (used for IdP user info refresh consistent with Login)
  PeerSystemMeta meta = 2;
}

// ExtendAuthSessionResponse contains the refreshed session deadline.
message ExtendAuthSessionResponse {
  // 3-state session deadline; same encoding as SyncResponse.sessionExpiresAt.
  // In practice ExtendAuthSession only succeeds for SSO peers with expiry
  // enabled, so this carries a valid timestamp on the success path. The
  // 3-state encoding is documented here for symmetry with Login/Sync.
  google.protobuf.Timestamp sessionExpiresAt = 1;
}

message ServerKeyResponse {
  // Server's Wireguard public key
  string key = 1;
  // Key expiration timestamp after which the key should be fetched again by the client
  google.protobuf.Timestamp expiresAt = 2;
  // Version of the Netbird Management Service protocol
  int32 version = 3;
}

message Empty {}

// NetbirdConfig is a common configuration of any Netbird peer. It contains STUN, TURN, Signal and Management servers configurations
message NetbirdConfig {
  // a list of STUN servers
  repeated HostConfig stuns = 1;
  // a list of TURN servers
  repeated ProtectedHostConfig turns = 2;

  // a Signal server config
  HostConfig signal = 3;

  RelayConfig relay = 4;

  FlowConfig flow = 5;

  MetricsConfig metrics = 6;
}

// HostConfig describes connection properties of some server (e.g. STUN, Signal, Management)
message HostConfig {
  // URI of the resource e.g. turns://stun.netbird.io:4430 or signal.netbird.io:10000
  string uri = 1;
  Protocol protocol = 2;

  enum Protocol {
    UDP = 0;
    TCP = 1;
    HTTP = 2;
    HTTPS = 3;
    DTLS = 4;
  }
}

message RelayConfig {
  repeated string urls = 1;
  string tokenPayload = 2;
  string tokenSignature = 3;
}

message FlowConfig {
  string url = 1;
  string tokenPayload = 2;
  string tokenSignature = 3;
  google.protobuf.Duration interval = 4;
  bool enabled = 5;

  // counters determines if flow packets and bytes counters should be sent
  bool counters = 6;
  // exitNodeCollection determines if event collection on exit nodes should be enabled
  bool exitNodeCollection = 7;
  // dnsCollection determines if DNS event collection should be enabled
  bool dnsCollection = 8;
}

message MetricsConfig {
  bool enabled = 1;
}

// JWTConfig represents JWT authentication configuration for validating tokens.
message JWTConfig {
  string issuer = 1;
  // Deprecated: audience is kept for backwards compatibility only. Use audiences instead in the client code but populate this field.
  string audience = 2;
  string keysLocation = 3;
  int64 maxTokenAge = 4;
  // audiences contains the list of valid audiences for JWT validation.
  // Tokens matching any audience in this list are considered valid.
  repeated string audiences = 5;
}

// ProtectedHostConfig is similar to HostConfig but has additional user and password
// Mostly used for TURN servers
message ProtectedHostConfig {
  HostConfig hostConfig = 1;
  string user = 2;
  string password = 3;
}

// PeerConfig represents a configuration of a "our" peer.
// The properties are used to configure local Wireguard
message PeerConfig {
  // Peer's virtual IP address within the Netbird VPN (a Wireguard address config)
  string  address = 1;
  // Netbird DNS server (a Wireguard DNS config)
  string dns = 2;

  // SSHConfig of the peer.
  SSHConfig sshConfig = 3;
  // Peer fully qualified domain name
  string fqdn = 4;

  bool RoutingPeerDnsResolutionEnabled = 5;

  bool LazyConnectionEnabled = 6;

  int32 mtu = 7;

  // Auto-update config
  AutoUpdateSettings autoUpdate = 8;

  // IPv6 overlay address as compact bytes: 16 bytes IP + 1 byte prefix length.
  bytes address_v6 = 9;
}

message AutoUpdateSettings {
  string version = 1;
  /*
    alwaysUpdate = true → Updates are installed automatically in the background
    alwaysUpdate = false → Updates require user interaction from the UI
   */
  bool alwaysUpdate = 2;
}

// NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections
message NetworkMap {
  // Serial is an ID of the network state to be used by clients to order updates.
  // The larger the Serial the newer the configuration.
  // E.g. the client app should keep track of this id locally and discard all the configurations with a lower value
  uint64 Serial = 1;

  // PeerConfig represents configuration of a peer
  PeerConfig peerConfig = 2;

  // RemotePeerConfig represents a list of remote peers that the receiver can connect to
  repeated RemotePeerConfig remotePeers = 3;

  // Indicates whether remotePeers array is empty or not to bypass protobuf null and empty array equality.
  bool remotePeersIsEmpty = 4;

  // List of routes to be applied
  repeated Route Routes = 5;

  // DNS config to be applied
  DNSConfig DNSConfig = 6;

  // RemotePeerConfig represents a list of remote peers that the receiver can connect to
  repeated RemotePeerConfig offlinePeers = 7;

  // FirewallRule represents a list of firewall rules to be applied to peer
  repeated FirewallRule FirewallRules = 8;

  // firewallRulesIsEmpty indicates whether FirewallRule array is empty or not to bypass protobuf null and empty array equality.
  bool firewallRulesIsEmpty = 9;

  // RoutesFirewallRules represents a list of routes firewall rules to be applied to peer
  repeated RouteFirewallRule routesFirewallRules = 10;

  // RoutesFirewallRulesIsEmpty indicates whether RouteFirewallRule array is empty or not to bypass protobuf null and empty array equality.
  bool routesFirewallRulesIsEmpty = 11;

  repeated ForwardingRule forwardingRules = 12;

  // SSHAuth represents SSH authorization configuration
  SSHAuth sshAuth = 13;
}

message SSHAuth {
  // UserIDClaim is the JWT claim to be used to get the users ID
  string UserIDClaim = 1;

  // AuthorizedUsers is a list of hashed user IDs authorized to access this peer via SSH
  repeated bytes AuthorizedUsers = 2;

  // MachineUsers is a map of machine user names to their corresponding indexes in the AuthorizedUsers list
  map<string, MachineUserIndexes> machine_users = 3;
}

message MachineUserIndexes {
  repeated uint32 indexes = 1;
}

// RemotePeerConfig represents a configuration of a remote peer.
// The properties are used to configure WireGuard Peers sections
message RemotePeerConfig {

  // A WireGuard public key of a remote peer
  string wgPubKey = 1;

  // WireGuard allowed IPs of a remote peer e.g. [10.30.30.1/32]
  repeated string allowedIps = 2;

  // SSHConfig is a SSH config of the remote peer. SSHConfig.sshPubKey should be ignored because peer knows it's SSH key.
  SSHConfig sshConfig = 3;

  // Peer fully qualified domain name
  string fqdn = 4;

  string agentVersion = 5;
}

// SSHConfig represents SSH configurations of a peer.
message SSHConfig {
  // sshEnabled indicates whether a SSH server is enabled on this peer
  bool sshEnabled = 1;

  // sshPubKey is a SSH public key of a peer to be added to authorized_hosts.
  // This property should be ignore if SSHConfig comes from PeerConfig.
  bytes sshPubKey = 2;

  JWTConfig jwtConfig = 3;
}

// DeviceAuthorizationFlowRequest empty struct for future expansion
message DeviceAuthorizationFlowRequest {}
// DeviceAuthorizationFlow represents Device Authorization Flow information
// that can be used by the client to login initiate a Oauth 2.0 device authorization grant flow
// see https://datatracker.ietf.org/doc/html/rfc8628
message DeviceAuthorizationFlow {
  // An IDP provider , (eg. Auth0)
  provider Provider = 1;
  ProviderConfig ProviderConfig = 2;

  enum provider {
    HOSTED = 0;
  }
}

// PKCEAuthorizationFlowRequest empty struct for future expansion
message PKCEAuthorizationFlowRequest {}

// PKCEAuthorizationFlow represents Authorization Code Flow information
// that can be used by the client to login initiate a Oauth 2.0 authorization code grant flow
// with Proof Key for Code Exchange (PKCE). See https://datatracker.ietf.org/doc/html/rfc7636
message PKCEAuthorizationFlow {
  ProviderConfig ProviderConfig = 1;
}

// ProviderConfig has all attributes needed to initiate a device/pkce authorization flow
message ProviderConfig {
  // An IDP application client id
  string ClientID = 1;
  // Deprecated: use embedded IdP for providers that require a client secret (e.g. Google Workspace).
  string ClientSecret = 2 [deprecated = true];
  // An IDP API domain
  // Deprecated. Use a DeviceAuthEndpoint and TokenEndpoint
  string Domain = 3;
  // An Audience for validation
  string Audience = 4;
  // DeviceAuthEndpoint is an endpoint to request device authentication code.
  string DeviceAuthEndpoint = 5;
  // TokenEndpoint is an endpoint to request auth token.
  string TokenEndpoint = 6;
  // Scopes provides the scopes to be included in the token request
  string Scope = 7;
  // UseIDToken indicates if the id token should be used for authentication
  bool UseIDToken = 8;
  // AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code.
  string AuthorizationEndpoint = 9;
  // RedirectURLs handles authorization code from IDP manager
  repeated string RedirectURLs = 10;
  // DisablePromptLogin makes the PKCE flow to not prompt the user for login
  bool DisablePromptLogin = 11;
  // LoginFlags sets the PKCE flow login details 
  uint32 LoginFlag = 12;
}

// Route represents a route.Route object
message Route {
  string ID = 1;
  string Network = 2;
  int64  NetworkType = 3;
  string Peer = 4;
  int64  Metric = 5;
  bool   Masquerade = 6;
  string NetID = 7;
  repeated string Domains = 8;
  bool keepRoute = 9;
  bool skipAutoApply = 10;
}

// DNSConfig represents a dns.Update
message DNSConfig {
  bool ServiceEnable = 1;
  repeated NameServerGroup NameServerGroups = 2;
  repeated CustomZone CustomZones = 3;
  int64 ForwarderPort = 4 [deprecated = true];
}

// CustomZone represents a dns.CustomZone
message CustomZone {
  string Domain = 1;
  repeated SimpleRecord Records = 2;
  bool SearchDomainDisabled = 3;
  // NonAuthoritative indicates this is a user-created zone (not the built-in peer DNS zone).
  // Non-authoritative zones will fallthrough to lower-priority handlers on NXDOMAIN and skip PTR processing.
  bool NonAuthoritative = 4;
}

// SimpleRecord represents a dns.SimpleRecord
message SimpleRecord {
  string Name = 1;
  int64 Type = 2;
  string Class = 3;
  int64 TTL = 4;
  string RData = 5;
}

// NameServerGroup represents a dns.NameServerGroup
message NameServerGroup {
  repeated NameServer NameServers = 1;
  bool Primary = 2;
  repeated string Domains = 3;
  bool SearchDomainsEnabled = 4;
}

// NameServer represents a dns.NameServer
message NameServer {
  string IP = 1;
  int64  NSType = 2;
  int64  Port = 3;
}

enum RuleProtocol {
  UNKNOWN = 0;
  ALL = 1;
  TCP = 2;
  UDP = 3;
  ICMP = 4;
  CUSTOM = 5;
  // NETBIRD_SSH (types.PolicyRuleProtocolType "netbird-ssh") is the marker
  // policy rule that drives SSH-server activation in Calculate(). The legacy
  // proto.FirewallRule path doesn't ship this value (Calculate already
  // expands SSH rules into TCP/22 before encoding), but the components path
  // ships RAW policies — the client must see this protocol to derive
  // AuthorizedUsers locally.
  NETBIRD_SSH = 6;
}

enum RuleDirection {
  IN = 0;
  OUT = 1;
}

enum RuleAction {
  ACCEPT = 0;
  DROP = 1;
}


// FirewallRule represents a firewall rule
message FirewallRule {
  // Use sourcePrefixes instead.
  string PeerIP = 1 [deprecated = true];
  RuleDirection Direction = 2;
  RuleAction Action = 3;
  RuleProtocol Protocol = 4;
  string Port = 5;
  PortInfo PortInfo = 6;

  // PolicyID is the ID of the policy that this rule belongs to
  bytes PolicyID = 7;

  // CustomProtocol is a custom protocol ID when Protocol is CUSTOM.
  uint32 customProtocol = 8;

  // Compact source IP prefixes for this rule, supersedes PeerIP.
  // Each entry is 5 bytes (v4) or 17 bytes (v6): [IP bytes][1 byte prefix_len].
  repeated bytes sourcePrefixes = 9;
}

message NetworkAddress {
  string netIP = 1;
  string mac = 2;
}

message Checks {
  repeated string Files = 1;
}


message PortInfo {
  oneof portSelection {
    uint32 port = 1;
    Range range = 2;
  }

  message Range {
    uint32 start = 1;
    uint32 end = 2;
  }
}

// RouteFirewallRule signifies a firewall rule applicable for a routed network.
message RouteFirewallRule {
  // sourceRanges IP ranges of the routing peers.
  repeated string sourceRanges = 1;

  // Action to be taken by the firewall when the rule is applicable.
  RuleAction action = 2;

  // Network prefix for the routed network.
  string destination = 3;

  // Protocol of the routed network.
  RuleProtocol protocol = 4;

  // Details about the port.
  PortInfo portInfo = 5;

  // IsDynamic indicates if the route is a DNS route.
  bool isDynamic = 6;

  // Domains is a list of domains for which the rule is applicable.
  repeated string domains = 7;

  // CustomProtocol is a custom protocol ID.
  uint32 customProtocol = 8;

  // PolicyID is the ID of the policy that this rule belongs to
  bytes PolicyID = 9;

  // RouteID is the ID of the route that this rule belongs to
  string RouteID = 10;
}

message ForwardingRule {
  // Protocol of the forwarding rule
  RuleProtocol protocol = 1;

  // portInfo is the ingress destination port information, where the traffic arrives in the gateway node
  PortInfo destinationPort = 2;

  // IP address of the translated address (remote peer) to send traffic to
  bytes translatedAddress = 3;

  // Translated port information, where the traffic should be forwarded to
  PortInfo translatedPort = 4;
}

enum ExposeProtocol {
  EXPOSE_HTTP = 0;
  EXPOSE_HTTPS = 1;
  EXPOSE_TCP = 2;
  EXPOSE_UDP = 3;
  EXPOSE_TLS = 4;
}

message ExposeServiceRequest {
  uint32 port = 1;
  ExposeProtocol protocol = 2;
  string pin = 3;
  string password = 4;
  repeated string user_groups = 5;
  string domain = 6;
  string name_prefix = 7;
  uint32 listen_port = 8;
}

message ExposeServiceResponse {
  string service_name = 1;
  string service_url = 2;
  string domain = 3;
  bool port_auto_assigned = 4;
}

message RenewExposeRequest {
  string domain = 1;
}

message RenewExposeResponse {}

message StopExposeRequest {
  string domain = 1;
}

message StopExposeResponse {}

// =====================================================================
// Component-based NetworkMap wire format (PeerCapabilityComponentNetworkMap).
//
// Peers that advertise this capability receive NetworkMap building blocks
// (peers + groups + policies + routes + dns + ssh + forwarding) and run the
// expansion (Calculate) locally instead of receiving a fully-expanded
// NetworkMap from the server.
// =====================================================================

// NetworkMapEnvelope wraps either a full snapshot or a delta. Only Full is
// emitted today; Delta is reserved for the incremental-update work.
message NetworkMapEnvelope {
  oneof payload {
    NetworkMapComponentsFull full = 1;
    NetworkMapComponentsDelta delta = 2;
  }
}

// NetworkMapComponentsFull is the full per-peer component snapshot. The
// client decodes it into a types.NetworkMapComponents and runs Calculate()
// locally to produce the same NetworkMap the legacy server path would have
// produced. Every field carries RAW component data — no server-side
// expansion (firewall rules, DNS config, SSH auth, route firewall rules,
// forwarding rules) is shipped; the client computes those itself.
message NetworkMapComponentsFull {
  uint64 serial = 1;

  // Peer config for the receiving peer (legacy proto.PeerConfig kept as-is —
  // it carries the receiving peer's own overlay address, FQDN, SSH config).
  PeerConfig peer_config = 2;

  // Account-level network metadata (id, IPv4/IPv6 overlay subnets, DNS,
  // serial). Mirrors types.Network.
  AccountNetwork network = 3;

  // Account-level settings the client needs for its local Calculate().
  AccountSettingsCompact account_settings = 4;

  // Account DNS settings (mirrors types.DNSSettings).
  DNSSettingsCompact dns_settings = 5;

  // Domain shared across all peers in this account, e.g. "netbird.cloud".
  // Each peer's FQDN is dns_label + "." + dns_domain.
  string dns_domain = 6;

  // Custom-zone domain for this peer's view (c.CustomZoneDomain). Empty when
  // the peer has no custom zone records.
  string custom_zone_domain = 7;

  // Deduplicated agent versions; PeerCompact.agent_version_idx indexes here.
  // Empty string at index 0 if any peer has no version.
  repeated string agent_versions = 8;

  // All peers (deduplicated). The client splits peers into online / offline
  // locally using account_settings.peer_login_expiration on receive.
  repeated PeerCompact peers = 9;

  // Indexes into peers for the subset that may act as routers.
  repeated uint32 router_peer_indexes = 10;

  // Policies that affect the receiving peer.
  repeated PolicyCompact policies = 11;

  // Groups in unspecified order — clients key off id (public_id).
  repeated GroupCompact groups = 12;

  // Routes relevant to this peer, raw shape (mirrors []*route.Route).
  repeated RouteRaw routes = 13;

  // Nameserver groups (mirrors []*nbdns.NameServerGroup).
  repeated NameServerGroupRaw nameserver_groups = 14;

  // All DNS records the client needs to assemble its custom zone. Reuses
  // the existing SimpleRecord wire shape.
  repeated SimpleRecord all_dns_records = 15;

  // Custom zones (typically the peer's own zone). Reuses the existing
  // CustomZone wire shape.
  repeated CustomZone account_zones = 16;

  // Network resources (mirrors []*resourceTypes.NetworkResource).
  repeated NetworkResourceRaw network_resources = 17;

  // Routers per network. Outer key: network public_id. Each entry is
  // the set of routers backing that network for this peer's view.
  map<string, NetworkRouterList> routers_map = 18;

  // For each NetworkResource public_id, the indexes into policies[]
  // that apply to it.
  map<string, PolicyIds> resource_policies_map = 19;

  // Group-id (public_id) → user ids authorized for SSH on members.
  map<string, UserIDList> group_id_to_user_ids = 20;

  // Account-level allowed user ids (used by Calculate() when assembling SSH
  // authorized users for the receiving peer).
  repeated string allowed_user_ids = 21;

  // Per posture-check public_id, the set of peer indexes that failed
  // the check. Server-side evaluation result; clients do not re-evaluate.
  map<string, PeerIndexSet> posture_failed_peers = 22;

  // Account-level DNS forwarder port (mirrors the legacy
  // proto.DNSConfig.ForwarderPort). Computed by the controller from peer
  // versions; clients fold it into their Calculate() DNS output.
  int64 dns_forwarder_port = 23;

  // Pre-expanded NetworkMap fragments injected post-Calculate by external
  // controllers (BYOP / port-forwarding proxies). The receiving client
  // merges these into its locally-computed NetworkMap the same way the
  // legacy server does via NetworkMap.Merge — so downstream consumers see
  // a unified merged result regardless of source.
  ProxyPatch proxy_patch = 24;

  // SSH UserIDClaim — server-side HttpServerConfig.AuthUserIDClaim, or
  // "sub" by default. Populated in proto.SSHAuth.UserIDClaim when the
  // client rebuilds the NetworkMap from this envelope. Empty when the
  // account has no AuthorizedUsers (and thus no SshAuth to populate).
  string user_id_claim = 25;

  // Reserved for future component additions (incremental_serial, parent_seq,
  // etc.) without forcing a renumber.
  reserved 26 to 50;
}

// ProxyPatch carries NetworkMap fragments that don't fit the component-graph
// model — they're pre-expanded by external controllers (BYOP /
// port-forwarding proxies) and injected post-Calculate. Fields use the
// legacy wire types because the proxy delivers them pre-formed; there is
// no raw component shape to convert from. Empty when no proxy is active.
message ProxyPatch {
  repeated RemotePeerConfig peers = 1;
  repeated RemotePeerConfig offline_peers = 2;
  repeated FirewallRule firewall_rules = 3;
  repeated Route routes = 4;
  repeated RouteFirewallRule route_firewall_rules = 5;
  repeated ForwardingRule forwarding_rules = 6;
}

// AccountSettingsCompact carries the account-level settings the client needs
// to evaluate locally. Mirrors the subset of types.AccountSettingsInfo that
// Calculate() actually reads — login-expiration (used to filter expired
// peers). Inactivity expiration is purely server-side bookkeeping and is not
// shipped.
message AccountSettingsCompact {
  bool peer_login_expiration_enabled = 1;
  // Login expiration window. Unit is nanoseconds (matches time.Duration).
  int64 peer_login_expiration_ns = 2;
}

// AccountNetwork is the account-level overlay metadata. Mirrors types.Network
// so the client can populate NetworkMap.Network without a server round-trip.
message AccountNetwork {
  string identifier = 1;
  // IPv4 overlay subnet in CIDR form (e.g. "100.64.0.0/16").
  string net_cidr = 2;
  // IPv6 ULA overlay subnet in CIDR form (e.g. "fd00:4e42::/64"). Empty when
  // the account has no IPv6 overlay yet.
  string net_v6_cidr = 3;
  string dns = 4;
  uint64 serial = 5;
}

// NetworkMapComponentsDelta is reserved for the incremental update
// protocol. Field numbers 1–100 are pre-allocated to keep room for the
// planned event types without needing a renumber.
message NetworkMapComponentsDelta {
  reserved 1 to 100;
}

// PeerCompact is the wire-shape of a remote peer used by the component
// format. It carries every field of types.Peer that the client's local
// Calculate() reads — including the trio needed to evaluate
// LoginExpired() (added_with_sso_login + login_expiration_enabled +
// last_login_unix_nano). Fields the client does not consume (Status,
// CreatedAt, etc.) are not shipped.
message PeerCompact {
  // Raw 32-byte WireGuard public key (no base64 wrapping).
  bytes wg_pub_key = 1;

  // Raw 4-byte IPv4 overlay address. Always a /32 host route, so no prefix
  // byte is needed.
  bytes ip = 2;

  // Raw 16-byte IPv6 overlay address; always a /128 host route. Empty when
  // the peer has no IPv6 overlay address.
  bytes ipv6 = 3;

  // Raw SSH public key bytes (or empty).
  bytes ssh_pub_key = 4;

  // DNS label without the account's domain suffix. Full FQDN is
  // dns_label + "." + NetworkMapComponentsFull.dns_domain.
  string dns_label = 5;

  string agent_version = 6;

  // True iff the peer was added via SSO login (i.e., types.Peer.UserID is
  // non-empty). Combined with login_expiration_enabled and
  // last_login_unix_nano this lets the client reproduce
  // (*Peer).LoginExpired() locally.
  bool added_with_sso_login = 7;

  // True when the peer's login can expire — mirrors
  // types.Peer.LoginExpirationEnabled.
  bool login_expiration_enabled = 8;

  // Unix-nanosecond timestamp of the peer's last login. 0 when the peer has
  // never logged in (server stores nil; client treats 0 as "epoch", which
  // makes a fresh peer immediately expired iff login_expiration_enabled is
  // true — the same semantics as types.Peer.GetLastLogin).
  int64 last_login_unix_nano = 9;

  // True when the peer has an SSH server enabled locally. Used by the
  // legacy SSH path in Calculate() (`policyRuleImpliesLegacySSH`): a rule
  // with protocol ALL/TCP-with-SSH-ports activates SSH for the receiving
  // peer when this bit is set, even without an explicit NetbirdSSH rule.
  bool ssh_enabled = 10;

  // Mirror of types.Peer.SupportsIPv6() — !Meta.Flags.DisableIPv6 &&
  // HasCapability(PeerCapabilityIPv6Overlay). Used by the local peer's
  // Calculate() when deciding whether to emit IPv6 firewall rules
  // (appendIPv6FirewallRule) against this peer's IPv6 address.
  bool supports_ipv6 = 11;

  // Mirror of types.Peer.SupportsSourcePrefixes() —
  // HasCapability(PeerCapabilitySourcePrefixes). Determines whether the
  // local peer's Calculate() emits SourcePrefixes alongside legacy PeerIP
  // fields in proto.FirewallRule.
  bool supports_source_prefixes = 12;

  // Mirror of types.Peer.Meta.Flags.ServerSSHAllowed. Read by Calculate()
  // when expanding TCP port-22 firewall rules — the native SSH companion
  // (port 22022) is only added when this flag is set and the peer agent
  // version supports it.
  bool server_ssh_allowed = 13;
}

// PolicyCompact is the compact form of a policy rule. Group references use
// the public_ids; the client resolves
// them against NetworkMapComponentsFull.groups. Direction is derived per-peer
// on the client (ingress when the peer is in destination_group_ids, egress
// when in source_group_ids; both when bidirectional).
message PolicyCompact {
  // public_id. Used as a stable reference for
  // ResourcePoliciesMap.indexes and future delta updates.
  string id = 1;

  RuleAction action = 2;
  RuleProtocol protocol = 3;
  bool bidirectional = 4;

  // Single ports referenced by the rule.
  repeated uint32 ports = 5;

  // Port ranges (start..end) referenced by the rule.
  repeated PortInfo.Range port_ranges = 6;

  // Group ids (public_ids) of source / destination groups.
  repeated string source_group_ids = 7;
  repeated string destination_group_ids = 8;

  // SSH authorization fields. PolicyRule.AuthorizedGroups maps the rule's
  // applicable group ids (public_ids) to a list of local-user names —
  // when a peer in one of those groups is the SSH destination, the named
  // local users gain access. AuthorizedUser is the single-user form
  // (legacy: rule scopes SSH to one specific user id).
  //
  // Both fields are only consumed by Calculate() when the rule's protocol
  // is NetbirdSSH (or the legacy implicit-SSH heuristic).
  map<string, UserNameList> authorized_groups = 9;
  string authorized_user = 10;

  // Resource-typed rule sources/destinations. When a rule targets a specific
  // peer (rather than groups), Calculate() reads SourceResource /
  // DestinationResource — without these the rule's connection resources
  // can't be produced on the client. ResourceCompact's peer_index refers to
  // NetworkMapComponentsFull.peers; type is the raw ResourceType string
  // ("peer", "host", "subnet", "domain"). Only "peer" is meaningful for
  // Calculate's resource-typed rule path today.
  ResourceCompact source_resource = 11;
  ResourceCompact destination_resource = 12;

  // Posture-check ids gating this policy's source peers. Calculate()
  // reads them when filtering rule peers (peers that fail any listed check
  // are dropped from sourcePeers). Match keys in
  // NetworkMapComponentsFull.posture_failed_peers.
  repeated string source_posture_check_ids = 13;
}

// ResourceCompact mirrors types.Resource. Used by PolicyCompact to carry
// rule.SourceResource / rule.DestinationResource when the rule targets a
// specific resource (typically a peer) rather than groups.
// peer_index_set tells whether peer_index is valid (proto3 uint32 cannot
// disambiguate "0" from "unset"); set only when type == "peer".
message ResourceCompact {
  string type = 1;
  bool peer_index_set = 2;
  uint32 peer_index = 3;
  reserved 4; // future: host/subnet/domain references when needed
}

// UserNameList is a list of local-user names — used as the value type in
// PolicyCompact.authorized_groups.
message UserNameList {
  repeated string names = 1;
}

// GroupCompact is the wire-shape of a group: public id, optional
// name, and indexes into NetworkMapComponentsFull.peers identifying members.
message GroupCompact {
  // id comes from PublicID. Used by PolicyCompact.source_group_ids / destination_group_ids.
  string id = 1;

  // Indexes into NetworkMapComponentsFull.peers.
  repeated uint32 peer_indexes = 2;

  // True when the group is named "All" (types.Group.IsGroupAll). The
  // client-side Calculate short-circuits group→peer expansion on such
  // groups exactly like the server does; without this bit the decoded
  // groups lose that property and the two sides expand policy
  // destinations differently.
  bool is_all = 3;
}

// DNSSettingsCompact mirrors types.DNSSettings.
message DNSSettingsCompact {
  // Group ids (public_id) whose DNS management is disabled.
  repeated string disabled_management_group_ids = 1;
}

// RouteRaw mirrors *route.Route (the domain type), trimmed to fields that
// types.NetworkMapComponents.Calculate() reads. Group references are
// public_ids; the routing peer (when set) is referenced by index into
// NetworkMapComponentsFull.peers.
message RouteRaw {
  string id = 1; // public_id
  string net_id = 2;
  string description = 3;

  // Either network_cidr (e.g. "10.0.0.0/16") or domains is set, not both.
  string network_cidr = 4;
  repeated string domains = 5;
  bool keep_route = 6;

  // Routing peer reference: peer_index_set tells whether peer_index is valid
  // (proto3 uint32 cannot disambiguate "0" from "unset"). Mutually exclusive
  // with peer_group_ids.
  //
  // peer_index decodes back to types.Peer.ID (the peer's xid string), NOT
  // to its WireGuard public key. This matches the server-side data flow:
  // c.Routes carry route.Peer = peer.ID, and getRoutingPeerRoutes mutates
  // it to peer.Key only after the route has been admitted to the network
  // map. Decoders MUST set Route.Peer = peer.ID; the legacy Calculate()
  // path will substitute the WG key downstream.
  bool peer_index_set = 7;
  uint32 peer_index = 8;
  repeated string peer_group_ids = 9;

  int32 network_type = 10;
  bool masquerade = 11;
  int32 metric = 12;
  bool enabled = 13;
  repeated string group_ids = 14;
  repeated string access_control_group_ids = 15;
  bool skip_auto_apply = 16;
}

// NameServerGroupRaw mirrors *nbdns.NameServerGroup. Distinct from the
// legacy NameServerGroup (which is the wire-trimmed shape consumed by
// proto.DNSConfig and lacks the Name/Description/Groups/Enabled fields).
message NameServerGroupRaw {
  string id = 1;
  // Reuses the legacy NameServer wire shape (IP as string).
  repeated NameServer nameservers = 2;
  // Group ids the NSG distributes nameservers to.
  repeated string group_ids = 3;
  bool primary = 4;
  repeated string domains = 5;
  bool enabled = 6;
  bool search_domains_enabled = 7;
}

// NetworkResourceRaw mirrors *resourceTypes.NetworkResource.
//
message NetworkResourceRaw {
  string id = 1;
  string network_seq = 2;
  string name = 3;
  string description = 4;
  // Resource type: "host" / "subnet" / "domain".
  string type = 5;
  string address = 6;
  string domain_value = 7;  // resource.Domain
  string prefix_cidr = 8;
  bool enabled = 9;
}

// NetworkRouterList carries the routers backing one network.
message NetworkRouterList {
  // Routers in this network, keyed by peer_index (the routing peer).
  repeated NetworkRouterEntry entries = 1;
}

// NetworkRouterEntry mirrors a single *routerTypes.NetworkRouter; the routing
// peer is referenced by index into NetworkMapComponentsFull.peers.
message NetworkRouterEntry {
  string id = 1;
  uint32 peer_index = 2;
  bool peer_index_set = 3;
  repeated string peer_group_ids = 4;
  bool masquerade = 5;
  int32 metric = 6;
  bool enabled = 7;
}

message PolicyIds {
  repeated string ids = 1;
}

// UserIDList is a list of user ids — used as the value type in
// NetworkMapComponentsFull.group_id_to_user_ids.
message UserIDList {
  repeated string user_ids = 1;
}

// PeerIndexSet is a set of peer indexes — used as the value type in
// NetworkMapComponentsFull.posture_failed_peers.
message PeerIndexSet {
  repeated uint32 peer_indexes = 1;
}
