diff --git a/CHANGELOG.md b/CHANGELOG.md index ede205ffa5..b567384622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,13 @@ For details about compatibility between different releases, see the **Commitment ### Deprecated +- Experimental RPCs `AuthorizeGateway` and `UnauthorizeGateway` of the Gateway Claiming Server (GCLS). +- `CUPSRedirection` field of `ClaimGatewayRequest`. + ### Removed +- `authorize` and `unauthorize` sub-commands of the gateway `claim` command. + ### Fixed ### Security diff --git a/api/ttn/lorawan/v3/api.md b/api/ttn/lorawan/v3/api.md index 2dac035f83..76a3ee07db 100644 --- a/api/ttn/lorawan/v3/api.md +++ b/api/ttn/lorawan/v3/api.md @@ -2924,6 +2924,8 @@ in a future version of The Things Stack. ### Message `CUPSRedirection` +DEPRECATED: This message is deprecated and will be removed in a future version of The Things Stack. + | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `target_cups_uri` | [`string`](#string) | | CUPS URI for LoRa Basics Station CUPS redirection. | @@ -2996,7 +2998,7 @@ in a future version of The Things Stack. | `collaborator` | [`OrganizationOrUserIdentifiers`](#ttn.lorawan.v3.OrganizationOrUserIdentifiers) | | Collaborator to grant all rights on the target gateway. | | `target_gateway_id` | [`string`](#string) | | Gateway ID for the target gateway. This must be a unique value. If this is not set, the target ID for the target gateway will be set to ``. | | `target_gateway_server_address` | [`string`](#string) | | Target Gateway Server Address for the target gateway. | -| `cups_redirection` | [`CUPSRedirection`](#ttn.lorawan.v3.CUPSRedirection) | | Parameters to set CUPS redirection for the gateway. | +| `cups_redirection` | [`CUPSRedirection`](#ttn.lorawan.v3.CUPSRedirection) | | Parameters to set CUPS redirection for the gateway. DEPRECATED: This field is deprecated and will be removed in a future version of The Things Stack. | | `target_frequency_plan_id` | [`string`](#string) | | Frequency plan ID of the target gateway. TODO: Remove this field (https://github.com/TheThingsIndustries/lorawan-stack/issues/4024) DEPRECATED: Use target_frequency_plan_ids instead. | | `target_frequency_plan_ids` | [`string`](#string) | repeated | Frequency plan IDs of the target gateway. | @@ -3164,18 +3166,20 @@ The GatewayClaimingServer service support claiming and managing gateway claims. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | `Claim` | [`ClaimGatewayRequest`](#ttn.lorawan.v3.ClaimGatewayRequest) | [`GatewayIdentifiers`](#ttn.lorawan.v3.GatewayIdentifiers) | Claims a gateway by claim authentication code or QR code and transfers the gateway to the target user. | -| `AuthorizeGateway` | [`AuthorizeGatewayRequest`](#ttn.lorawan.v3.AuthorizeGatewayRequest) | [`.google.protobuf.Empty`](#google.protobuf.Empty) | AuthorizeGateway allows a gateway to be claimed. | -| `UnauthorizeGateway` | [`GatewayIdentifiers`](#ttn.lorawan.v3.GatewayIdentifiers) | [`.google.protobuf.Empty`](#google.protobuf.Empty) | UnauthorizeGateway prevents a gateway from being claimed. | +| `Unclaim` | [`GatewayIdentifiers`](#ttn.lorawan.v3.GatewayIdentifiers) | [`.google.protobuf.Empty`](#google.protobuf.Empty) | Unclaims the gateway. EUI provided in the request are ignored and the end device is looked up by the gateway ID. | | `GetInfoByGatewayEUI` | [`GetInfoByGatewayEUIRequest`](#ttn.lorawan.v3.GetInfoByGatewayEUIRequest) | [`GetInfoByGatewayEUIResponse`](#ttn.lorawan.v3.GetInfoByGatewayEUIResponse) | Return whether claiming is available for a given gateway EUI. | +| `AuthorizeGateway` | [`AuthorizeGatewayRequest`](#ttn.lorawan.v3.AuthorizeGatewayRequest) | [`.google.protobuf.Empty`](#google.protobuf.Empty) | AuthorizeGateway allows a gateway to be claimed. DEPRECATED: Authorizing gateways for claiming is no longer supported and will be removed in a future version of The Things Stack. | +| `UnauthorizeGateway` | [`GatewayIdentifiers`](#ttn.lorawan.v3.GatewayIdentifiers) | [`.google.protobuf.Empty`](#google.protobuf.Empty) | UnauthorizeGateway prevents a gateway from being claimed. DEPRECATED: Unauthorizing (locking) gateways for claiming is no longer supported and will be removed in a future version of The Things Stack. | #### HTTP bindings | Method Name | Method | Pattern | Body | | ----------- | ------ | ------- | ---- | | `Claim` | `POST` | `/api/v3/gcls/claim` | `*` | +| `Unclaim` | `DELETE` | `/api/v3/gcls/claim/{gateway_id}` | | +| `GetInfoByGatewayEUI` | `POST` | `/api/v3/gcls/claim/info` | `*` | | `AuthorizeGateway` | `POST` | `/api/v3/gcls/gateways/{gateway_ids.gateway_id}/authorize` | `*` | | `UnauthorizeGateway` | `DELETE` | `/api/v3/gcls/gateways/{gateway_id}/authorize` | | -| `GetInfoByGatewayEUI` | `POST` | `/api/v3/gcls/claim/info` | `*` | ## File `ttn/lorawan/v3/devicerepository.proto` diff --git a/api/ttn/lorawan/v3/api.swagger.json b/api/ttn/lorawan/v3/api.swagger.json index 7251068181..ff81c19713 100644 --- a/api/ttn/lorawan/v3/api.swagger.json +++ b/api/ttn/lorawan/v3/api.swagger.json @@ -7862,9 +7862,49 @@ ] } }, + "/gcls/claim/{gateway_id}": { + "delete": { + "summary": "Unclaims the gateway.\nEUI provided in the request are ignored and the end device is looked up by the gateway ID.", + "operationId": "GatewayClaimingServer_Unclaim", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": {} + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "gateway_id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "eui", + "description": "Secondary identifier, which can only be used in specific requests.", + "in": "query", + "required": false, + "type": "string", + "format": "string" + } + ], + "tags": [ + "GatewayClaimingServer" + ] + } + }, "/gcls/gateways/{gateway_ids.gateway_id}/authorize": { "post": { - "summary": "AuthorizeGateway allows a gateway to be claimed.", + "summary": "AuthorizeGateway allows a gateway to be claimed.\nDEPRECATED: Authorizing gateways for claiming is no longer supported and will be removed in a future version of The\nThings Stack.", "operationId": "GatewayClaimingServer_AuthorizeGateway", "responses": { "200": { @@ -7904,7 +7944,7 @@ }, "/gcls/gateways/{gateway_id}/authorize": { "delete": { - "summary": "UnauthorizeGateway prevents a gateway from being claimed.", + "summary": "UnauthorizeGateway prevents a gateway from being claimed.\nDEPRECATED: Unauthorizing (locking) gateways for claiming is no longer supported and will be removed in a future\nversion of The Things Stack.", "operationId": "GatewayClaimingServer_UnauthorizeGateway", "responses": { "200": { @@ -20451,7 +20491,8 @@ "type": "string", "description": "The Device Claiming Server will fill this field with a The Things Stack API Key." } - } + }, + "description": "DEPRECATED: This message is deprecated and will be removed in a future version of The Things Stack." }, "v3ClaimEndDeviceRequest": { "type": "object", @@ -20520,7 +20561,7 @@ }, "cups_redirection": { "$ref": "#/definitions/v3CUPSRedirection", - "description": "Parameters to set CUPS redirection for the gateway." + "description": "Parameters to set CUPS redirection for the gateway.\nDEPRECATED: This field is deprecated and will be removed in a future version of The Things Stack." }, "target_frequency_plan_id": { "type": "string", diff --git a/api/ttn/lorawan/v3/deviceclaimingserver.proto b/api/ttn/lorawan/v3/deviceclaimingserver.proto index 46e8833a1b..e57a207541 100644 --- a/api/ttn/lorawan/v3/deviceclaimingserver.proto +++ b/api/ttn/lorawan/v3/deviceclaimingserver.proto @@ -286,7 +286,9 @@ service EndDeviceBatchClaimingServer { } } +// DEPRECATED: This message is deprecated and will be removed in a future version of The Things Stack. message CUPSRedirection { + option deprecated = true; // CUPS URI for LoRa Basics Station CUPS redirection. string target_cups_uri = 1 [(validate.rules).string = { uri: true, @@ -356,7 +358,8 @@ message ClaimGatewayRequest { string target_gateway_server_address = 5 [(validate.rules).string.pattern = "^(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])(?::[0-9]{1,5})?$|^$"]; // Parameters to set CUPS redirection for the gateway. - CUPSRedirection cups_redirection = 6; + // DEPRECATED: This field is deprecated and will be removed in a future version of The Things Stack. + CUPSRedirection cups_redirection = 6 [deprecated = true]; // Frequency plan ID of the target gateway. // TODO: Remove this field (https://github.com/TheThingsIndustries/lorawan-stack/issues/4024) @@ -429,8 +432,25 @@ service GatewayClaimingServer { }; } + // Unclaims the gateway. + // EUI provided in the request are ignored and the end device is looked up by the gateway ID. + rpc Unclaim(GatewayIdentifiers) returns (google.protobuf.Empty) { + option (google.api.http) = {delete: "/gcls/claim/{gateway_id}"}; + } + + // Return whether claiming is available for a given gateway EUI. + rpc GetInfoByGatewayEUI(GetInfoByGatewayEUIRequest) returns (GetInfoByGatewayEUIResponse) { + option (google.api.http) = { + post: "/gcls/claim/info", + body: "*" + }; + } + // AuthorizeGateway allows a gateway to be claimed. + // DEPRECATED: Authorizing gateways for claiming is no longer supported and will be removed in a future version of The + // Things Stack. rpc AuthorizeGateway(AuthorizeGatewayRequest) returns (google.protobuf.Empty) { + option deprecated = true; option (google.api.http) = { post: "/gcls/gateways/{gateway_ids.gateway_id}/authorize", body: "*" @@ -438,15 +458,10 @@ service GatewayClaimingServer { } // UnauthorizeGateway prevents a gateway from being claimed. + // DEPRECATED: Unauthorizing (locking) gateways for claiming is no longer supported and will be removed in a future + // version of The Things Stack. rpc UnauthorizeGateway(GatewayIdentifiers) returns (google.protobuf.Empty) { + option deprecated = true; option (google.api.http) = {delete: "/gcls/gateways/{gateway_id}/authorize"}; } - - // Return whether claiming is available for a given gateway EUI. - rpc GetInfoByGatewayEUI(GetInfoByGatewayEUIRequest) returns (GetInfoByGatewayEUIResponse) { - option (google.api.http) = { - post: "/gcls/claim/info", - body: "*" - }; - } } diff --git a/cmd/ttn-lw-cli/commands/gateways_claim.go b/cmd/ttn-lw-cli/commands/gateways_claim.go index a44a01da95..982e910cc7 100644 --- a/cmd/ttn-lw-cli/commands/gateways_claim.go +++ b/cmd/ttn-lw-cli/commands/gateways_claim.go @@ -15,16 +15,14 @@ package commands import ( - "encoding/pem" "os" "github.com/spf13/cobra" "go.thethings.network/lorawan-stack/v3/cmd/internal/io" "go.thethings.network/lorawan-stack/v3/cmd/ttn-lw-cli/internal/api" + "go.thethings.network/lorawan-stack/v3/cmd/ttn-lw-cli/internal/util" "go.thethings.network/lorawan-stack/v3/pkg/errors" - "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" - "google.golang.org/grpc" ) var ( @@ -32,11 +30,10 @@ var ( errInvalidTargetCUPSTrust = errors.DefineInvalidArgument("invalid_target_cups_trust", "invalid target CUPS trust") ) -var ( - gatewayClaimCommand = &cobra.Command{ - Use: "claim [gateway-eui]", - Short: "Claim a gateway (EXPERIMENTAL)", - Long: `Claim an gateway (EXPERIMENTAL) +var gatewayClaimCommand = &cobra.Command{ + Use: "claim [gateway-eui]", + Short: "Claim a gateway (EXPERIMENTAL)", + Long: `Claim an gateway (EXPERIMENTAL) The claiming procedure transfers ownership of gateways using the Device Claiming Server. @@ -53,169 +50,71 @@ Gateway Server address and a frequency plan ID. For LoRa Basic Station gateways, the Target CUPS URI must be specified. A PEM encoded CUPS trust may be included in the claim request. `, - RunE: func(cmd *cobra.Command, args []string) error { - gtwIDs, err := getGatewayEUI(cmd.Flags(), args, true) - if err != nil { - return err - } - collaborator := getCollaborator(cmd.Flags()) - if collaborator == nil { - return errNoCollaborator.New() - } - dcs, err := api.Dial(ctx, config.DeviceClaimingServerGRPCAddress) - if err != nil { - return err - } - authenticationCode, _ := cmd.Flags().GetString("authentication-code") - targetGatewayServerAddress, _ := cmd.Flags().GetString("target-gateway-server-address") - targetGatewayID, _ := cmd.Flags().GetString("target-gateway-id") - targetCUPSURI, _ := cmd.Flags().GetString("target-cups-uri") - targetCUPSTrustLocalFile, _ := cmd.Flags().GetString("target-cups-trust-local-file") - targetFrequencyPlanId, _ := cmd.Flags().GetString("target-frequency-plan-id") - - var targetCUPSTrust []byte - if targetCUPSTrustLocalFile != "" { - raw, err := getDataBytes("target-cups-trust", cmd.Flags()) - if err != nil { - return err - } - block, _ := pem.Decode(raw) - if block == nil || block.Type != "CERTIFICATE" { - return errInvalidTargetCUPSTrust.New() - } - targetCUPSTrust = block.Bytes - } - req := &ttnpb.ClaimGatewayRequest{ - SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ - AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ - GatewayEui: gtwIDs.Eui, - AuthenticationCode: []byte(authenticationCode), - }, + RunE: func(cmd *cobra.Command, args []string) error { + gtwIDs, err := getGatewayEUI(cmd.Flags(), args, true) + if err != nil { + return err + } + collaborator := getCollaborator(cmd.Flags()) + if collaborator == nil { + return errNoCollaborator.New() + } + dcs, err := api.Dial(ctx, config.DeviceClaimingServerGRPCAddress) + if err != nil { + return err + } + authenticationCode, _ := cmd.Flags().GetString("authentication-code") + targetGatewayServerAddress, _ := cmd.Flags().GetString("target-gateway-server-address") + targetGatewayID, _ := cmd.Flags().GetString("target-gateway-id") + targetFrequencyPlanID, _ := cmd.Flags().GetString("target-frequency-plan-id") + targetFrequencyPlanIDs, _ := cmd.Flags().GetStringSlice("target-frequency-plan-ids") + + if len(targetFrequencyPlanIDs) == 0 && targetFrequencyPlanID != "" { + targetFrequencyPlanIDs = []string{targetFrequencyPlanID} + } + req := &ttnpb.ClaimGatewayRequest{ + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: gtwIDs.Eui, + AuthenticationCode: []byte(authenticationCode), }, - Collaborator: collaborator, - TargetGatewayServerAddress: targetGatewayServerAddress, - TargetGatewayId: targetGatewayID, - TargetFrequencyPlanId: targetFrequencyPlanId, - } - if targetCUPSURI != "" { - req.CupsRedirection = &ttnpb.CUPSRedirection{ - TargetCupsTrust: targetCUPSTrust, - TargetCupsUri: targetCUPSURI, - } - } - ids, err := ttnpb.NewGatewayClaimingServerClient(dcs).Claim(ctx, req) - if err != nil { - return err - } - - return io.Write(os.Stdout, config.OutputFormat, ids) - }, - } - gatewayClaimAuthorize = &cobra.Command{ - Use: "authorize [gateway-id]", - Short: "Authorize an gateway for claiming (EXPERIMENTAL)", - Long: `Authorize an gateway for claiming (EXPERIMENTAL) - -The given API key must have the right to -- read gateway information -- read secrets -- delete the gateway. -If no API key is provided, a new one will be created.`, - RunE: func(cmd *cobra.Command, args []string) error { - gtwID, err := getGatewayID(cmd.Flags(), args, true) - if err != nil { - return errNoGatewayID.New() - } - - expiryDate, err := getAPIKeyExpiry(cmd.Flags()) - if err != nil { - return err - } - - requiredRights := []ttnpb.Right{ - ttnpb.Right_RIGHT_GATEWAY_READ_SECRETS, - ttnpb.Right_RIGHT_GATEWAY_DELETE, - ttnpb.Right_RIGHT_GATEWAY_INFO, - } - - is, err := api.Dial(ctx, config.IdentityServerGRPCAddress) - if err != nil { - return err - } - - key, _ := cmd.Flags().GetString("api-key") - if key != "" { - retrievedRights, err := ttnpb.NewGatewayAccessClient(is).ListRights(ctx, gtwID, grpc.PerRPCCredentials(rpcmetadata.MD{ - AuthType: "Bearer", - AuthValue: key, - })) - if err != nil { - return err - } - if !retrievedRights.IncludesAll(requiredRights...) { - return errInsufficientSourceGatewayRights.New() - } - } else { - logger.Info("No API Key provided. Creating one") - res, err := ttnpb.NewGatewayAccessClient(is).CreateAPIKey(ctx, &ttnpb.CreateGatewayAPIKeyRequest{ - GatewayIds: gtwID, - Name: "Gateway Claim Authorization Key", // This field can only have 50 chars. - Rights: requiredRights, - ExpiresAt: ttnpb.ProtoTime(expiryDate), - }) - if err != nil { - return err - } - - logger.Infof("Created API Key with ID: %s", res.Id) - key = res.Key - } - - dcs, err := api.Dial(ctx, config.DeviceClaimingServerGRPCAddress) - if err != nil { - return err - } - _, err = ttnpb.NewGatewayClaimingServerClient(dcs).AuthorizeGateway(ctx, &ttnpb.AuthorizeGatewayRequest{ - GatewayIds: gtwID, - ApiKey: key, - }) + }, + Collaborator: collaborator, + TargetGatewayServerAddress: targetGatewayServerAddress, + TargetGatewayId: targetGatewayID, + TargetFrequencyPlanId: targetFrequencyPlanID, + TargetFrequencyPlanIds: targetFrequencyPlanIDs, + } + ids, err := ttnpb.NewGatewayClaimingServerClient(dcs).Claim(ctx, req) + if err != nil { return err - }, - } - gatewayClaimUnauthorize = &cobra.Command{ - Use: "unauthorize [gateway-id]", - Short: "Unauthorize an gateway for claiming (EXPERIMENTAL)", - RunE: func(cmd *cobra.Command, args []string) error { - gtwID, err := getGatewayID(cmd.Flags(), args, true) - if err != nil { - return errNoGatewayID.New() - } - - dcs, err := api.Dial(ctx, config.DeviceClaimingServerGRPCAddress) - if err != nil { - return err - } - - logger.Warn("Make sure to delete the API Key used for authorizing claiming as this is not done automatically") + } - _, err = ttnpb.NewGatewayClaimingServerClient(dcs).UnauthorizeGateway(ctx, gtwID) - return err - }, - } -) + return io.Write(os.Stdout, config.OutputFormat, ids) + }, +} func init() { - gatewayClaimAuthorize.Flags().String("api-key", "", "") - gatewayClaimAuthorize.Flags().AddFlagSet(apiKeyExpiryFlag) - gatewayClaimCommand.AddCommand(gatewayClaimAuthorize) gatewayClaimCommand.Flags().AddFlagSet(collaboratorFlags()) - gatewayClaimCommand.AddCommand(gatewayClaimUnauthorize) gatewayClaimCommand.PersistentFlags().AddFlagSet(gatewayIDFlags()) gatewayClaimCommand.Flags().String("authentication-code", "", "(hex)") gatewayClaimCommand.Flags().String("target-cups-uri", "", "") gatewayClaimCommand.Flags().String("target-frequency-plan-id", "", "") + gatewayClaimCommand.Flags().String("target-frequency-plan-ids", "", "") gatewayClaimCommand.Flags().AddFlagSet(dataFlags("target-cups-trust", "(optional) Target CUPS trust in PEM format")) gatewayClaimCommand.Flags().String("target-gateway-server-address", "", "") gatewayClaimCommand.Flags().String("target-gateway-id", "", "gateway ID for the claimed gateway") gatewaysCommand.AddCommand(gatewayClaimCommand) + + // Deprecate unsupported flags. + util.DeprecateFlagWithoutForwarding( + gatewayClaimCommand.Flags(), + "target-cups-uri", + "this functionality is no longer supported", + ) + util.DeprecateFlagWithoutForwarding( + gatewayClaimCommand.Flags(), + "target-cups-trust-local-file", + "this functionality is no longer supported", + ) } diff --git a/config/messages.json b/config/messages.json index 5b49a831df..0fa82d27f7 100644 --- a/config/messages.json +++ b/config/messages.json @@ -3923,6 +3923,33 @@ "file": "ttjs.go" } }, + "error:pkg/deviceclaimingserver/gateways/ttgc:not_implemented": { + "translations": { + "en": "not implemented" + }, + "description": { + "package": "pkg/deviceclaimingserver/gateways/ttgc", + "file": "ttgc.go" + } + }, + "error:pkg/deviceclaimingserver/gateways:invalid_upstream": { + "translations": { + "en": "upstream `{name}` is invalid" + }, + "description": { + "package": "pkg/deviceclaimingserver/gateways", + "file": "gateways.go" + } + }, + "error:pkg/deviceclaimingserver:claim gateway": { + "translations": { + "en": "claim gateway" + }, + "description": { + "package": "pkg/deviceclaimingserver", + "file": "grpc_gateways.go" + } + }, "error:pkg/deviceclaimingserver:claiming_not_supported": { "translations": { "en": "claiming not supported for JoinEUI `{eui}`" @@ -3932,6 +3959,15 @@ "file": "grpc_end_devices.go" } }, + "error:pkg/deviceclaimingserver:create_gateway": { + "translations": { + "en": "create gateway" + }, + "description": { + "package": "pkg/deviceclaimingserver", + "file": "grpc_gateways.go" + } + }, "error:pkg/deviceclaimingserver:device_not_found": { "translations": { "en": "device not found" @@ -3941,13 +3977,31 @@ "file": "grpc_end_devices.go" } }, - "error:pkg/deviceclaimingserver:method_unavailable": { + "error:pkg/deviceclaimingserver:gateway_already_exists": { "translations": { - "en": "method unavailable" + "en": "gateway with EUI `{eui}` already exists" }, "description": { "package": "pkg/deviceclaimingserver", - "file": "grpc_end_devices.go" + "file": "grpc_gateways.go" + } + }, + "error:pkg/deviceclaimingserver:gateway_claiming_not_supported": { + "translations": { + "en": "claiming not supported for gateway with EUI `{eui}`" + }, + "description": { + "package": "pkg/deviceclaimingserver", + "file": "grpc_gateways.go" + } + }, + "error:pkg/deviceclaimingserver:gateway_claiming_with_qrcodes_not_implemented": { + "translations": { + "en": "gateway claiming with QR codes not implemented" + }, + "description": { + "package": "pkg/deviceclaimingserver", + "file": "grpc_gateways.go" } }, "error:pkg/deviceclaimingserver:no_devices_found": { @@ -3959,6 +4013,15 @@ "file": "grpc_end_devices.go" } }, + "error:pkg/deviceclaimingserver:no_eui": { + "translations": { + "en": "no EUI found for gateway" + }, + "description": { + "package": "pkg/deviceclaimingserver", + "file": "grpc_gateways.go" + } + }, "error:pkg/deviceclaimingserver:no_euis": { "translations": { "en": "DevEUI/JoinEUI not set for device" @@ -3968,6 +4031,15 @@ "file": "grpc_end_devices.go" } }, + "error:pkg/deviceclaimingserver:no_gateway_server_address": { + "translations": { + "en": "no gateway server address set for gateway" + }, + "description": { + "package": "pkg/deviceclaimingserver", + "file": "grpc_gateways.go" + } + }, "error:pkg/deviceclaimingserver:no_join_eui": { "translations": { "en": "extract JoinEUI from request" @@ -10151,6 +10223,96 @@ "file": "client_registry.go" } }, + "event:dcs.end_device.claim.abort": { + "translations": { + "en": "abort end device claim" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.end_device.claim.fail": { + "translations": { + "en": "claim end device failure" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.end_device.claim.success": { + "translations": { + "en": "claim end device successful" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.end_device.unclaim.fail": { + "translations": { + "en": "unclaim end device failure" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.end_device.unclaim.success": { + "translations": { + "en": "unclaim end device successful" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.gateway.claim.abort": { + "translations": { + "en": "abort gateway claim" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.gateway.claim.fail": { + "translations": { + "en": "claim gateway failure" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.gateway.claim.success": { + "translations": { + "en": "claim gateway successful" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.gateway.unclaim.fail": { + "translations": { + "en": "unclaim gateway failure" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, + "event:dcs.gateway.unclaim.success": { + "translations": { + "en": "unclaim gateway successful" + }, + "description": { + "package": "pkg/deviceclaimingserver/observability", + "file": "observability.go" + } + }, "event:end_device.batch.delete": { "translations": { "en": "batch delete end devices" diff --git a/pkg/deviceclaimingserver/config.go b/pkg/deviceclaimingserver/config.go index a48c14705f..78611dc6d7 100644 --- a/pkg/deviceclaimingserver/config.go +++ b/pkg/deviceclaimingserver/config.go @@ -16,9 +16,11 @@ package deviceclaimingserver import ( "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/enddevices" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways" ) // Config is the configuration for the Device Claiming Server. type Config struct { EndDeviceClaimingServerConfig enddevices.Config `name:"edcs"` + GatewayClaimingServerConfig gateways.Config `name:"gcls"` } diff --git a/pkg/deviceclaimingserver/deviceclaimingserver.go b/pkg/deviceclaimingserver/deviceclaimingserver.go index 782b572942..e56087de99 100644 --- a/pkg/deviceclaimingserver/deviceclaimingserver.go +++ b/pkg/deviceclaimingserver/deviceclaimingserver.go @@ -21,6 +21,8 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "go.thethings.network/lorawan-stack/v3/pkg/component" "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/enddevices" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways" + gtwregistry "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/registry/gateways" "go.thethings.network/lorawan-stack/v3/pkg/log" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "google.golang.org/grpc" @@ -35,8 +37,6 @@ type DeviceClaimingServer struct { endDeviceClaimingUpstream *enddevices.Upstream - gatewayClaimingServerUpstream ttnpb.GatewayClaimingServerServer - grpc struct { endDeviceClaimingServer *endDeviceClaimingServer endDeviceBatchClaimingServer *endDeviceBatchClaimingServer @@ -64,6 +64,7 @@ func New(c *component.Component, conf *Config, opts ...Option) (*DeviceClaimingS } dcs.endDeviceClaimingUpstream = upstream } + dcs.grpc.endDeviceClaimingServer = &endDeviceClaimingServer{ DCS: dcs, } @@ -72,9 +73,18 @@ func New(c *component.Component, conf *Config, opts ...Option) (*DeviceClaimingS DCS: dcs, } - dcs.gatewayClaimingServerUpstream = noopGCLS{} - dcs.grpc.gatewayClaimingServer = &gatewayClaimingServer{ - DCS: dcs, + if dcs.grpc.gatewayClaimingServer == nil { + upstream, err := gateways.NewUpstream(ctx, conf.GatewayClaimingServerConfig) + if err != nil { + return nil, err + } + dcs.grpc.gatewayClaimingServer = &gatewayClaimingServer{ + upstream: upstream, + registry: gtwregistry.Registry{ + Cluster: c, + }, + peerAccess: c, + } } c.RegisterGRPC(dcs) @@ -91,6 +101,21 @@ func WithEndDeviceClaimingUpstream(upstream *enddevices.Upstream) Option { } } +// WithGatewayClaimingServer configures the gateway claiming server. +func WithGatewayClaimingServer( + upstream *gateways.Upstream, + registry gtwregistry.GatewayRegistry, + access peerAccess, +) Option { + return func(dcs *DeviceClaimingServer) { + dcs.grpc.gatewayClaimingServer = &gatewayClaimingServer{ + upstream: upstream, + registry: registry, + peerAccess: access, + } + } +} + // Context returns the context of the Device Claiming Server. func (dcs *DeviceClaimingServer) Context() context.Context { return dcs.ctx diff --git a/pkg/deviceclaimingserver/gateways/gateways.go b/pkg/deviceclaimingserver/gateways/gateways.go new file mode 100644 index 0000000000..ec3b18f73d --- /dev/null +++ b/pkg/deviceclaimingserver/gateways/gateways.go @@ -0,0 +1,156 @@ +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gateways provides functions to claim gateways. +package gateways + +import ( + "context" + "strings" + + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways/ttgc" + dcstypes "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/types" + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/types" +) + +// Config is the configuration for the Gateway Claiming Server. +type Config struct { + CreateOnNotFound bool `name:"create-on-not-found" description:"DEPRECATED"` // nolint:lll + DefaultGatewayServerAddress string `name:"default-gateway-server-address" description:"The default Gateway Server Address"` // nolint:lll + Upstreams map[string][]string `name:"upstreams" description:"Map of upstream type and the supported Gateway EUI ranges"` // nolint:lll + TTGC ttgc.Config `name:"ttgc"` +} + +var errInvalidUpstream = errors.DefineInvalidArgument("invalid_upstream", "upstream `{name}` is invalid") + +// ParseGatewayEUIRanges parses the configured upstream map and returns map of ranges. +func ParseGatewayEUIRanges(config map[string][]string) (map[string][]dcstypes.EUI64Range, error) { + res := make(map[string][]dcstypes.EUI64Range, len(config)) + for host, ranges := range config { + res[host] = make([]dcstypes.EUI64Range, 0, len(ranges)) + for _, val := range ranges { + var r dcstypes.EUI64Range + switch { + case strings.Contains(val, "/"): + var prefix types.EUI64Prefix + if err := prefix.UnmarshalText([]byte(val)); err != nil { + return nil, errInvalidUpstream.WithAttributes("name", host).WithCause(err) + } + r = dcstypes.RangeFromEUI64Prefix(prefix) + case strings.Contains(val, "-"): + parts := strings.Split(val, "-") + if len(parts) != 2 { + return nil, errInvalidUpstream.WithAttributes("name", host) + } + var start, end types.EUI64 + if err := start.UnmarshalText([]byte(parts[0])); err != nil { + return nil, errInvalidUpstream.WithAttributes("name", host).WithCause(err) + } + if err := end.UnmarshalText([]byte(parts[1])); err != nil { + return nil, errInvalidUpstream.WithAttributes("name", host).WithCause(err) + } + r = dcstypes.RangeFromEUI64Range(start, end) + default: + return nil, errInvalidUpstream.WithAttributes("name", host) + } + res[host] = append(res[host], r) + } + } + return res, nil +} + +// Claimer provides methods for claiming Gateways. +type Claimer interface { + // Claim claims a gateway. + Claim(ctx context.Context, eui types.EUI64, ownerToken string, clusterAddress string) error + // Unclaim unclaims a gateway. + Unclaim(context.Context, types.EUI64, string) error +} + +// rangeClaimer supports claiming a range of EUIs. +type rangeClaimer struct { + ranges []dcstypes.EUI64Range + Claimer +} + +// Upstream is a gateway claiming upstream. +type Upstream struct { + claimers map[string]rangeClaimer +} + +// NewUpstream returns a new upstream based on the provided configuration. +func NewUpstream( + ctx context.Context, + conf Config, + opts ...Option, +) (*Upstream, error) { + upstream := &Upstream{ + claimers: make(map[string]rangeClaimer), + } + for _, opt := range opts { + opt(upstream) + } + + hosts, err := ParseGatewayEUIRanges(conf.Upstreams) + if err != nil { + return nil, err + } + // Setup upstream table. + for name, ranges := range hosts { + if len(ranges) == 0 || name == "" { + continue + } + var claimer Claimer + switch name { + case "ttgc": + claimer, err = conf.TTGC.NewClient(ctx) + if err != nil { + return nil, err + } + default: + return nil, errInvalidUpstream.WithAttributes("name", name) + } + upstream.claimers[name] = rangeClaimer{ + Claimer: claimer, + ranges: ranges, + } + } + return upstream, nil +} + +// Option configures Upstream. +type Option func(*Upstream) + +// WithClaimer adds a claimer to Upstream. +func WithClaimer(name string, ranges []dcstypes.EUI64Range, claimer Claimer) Option { + return func(upstream *Upstream) { + upstream.claimers[name] = rangeClaimer{ + Claimer: claimer, + ranges: ranges, + } + } +} + +// Claimer returns the Claimer for the given Gateway EUI. +func (upstream *Upstream) Claimer(gatewayEUI types.EUI64) Claimer { + for _, claimer := range upstream.claimers { + for _, r := range claimer.ranges { + if r.Contains(gatewayEUI) { + return claimer.Claimer + } + } + } + return nil +} diff --git a/pkg/deviceclaimingserver/gateways/gateways_test.go b/pkg/deviceclaimingserver/gateways/gateways_test.go new file mode 100644 index 0000000000..6ee5192f2a --- /dev/null +++ b/pkg/deviceclaimingserver/gateways/gateways_test.go @@ -0,0 +1,120 @@ +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gateways_test + +import ( + "testing" + + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways/ttgc" + dcstypes "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/types" + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/types" + "go.thethings.network/lorawan-stack/v3/pkg/util/test" + "go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should" +) + +func TestUpstream(t *testing.T) { + t.Parallel() + + a, ctx := test.New(t) + + // Invalid ranges. + ranges := map[string][]string{"ttgc": {"&S(FU*)"}} + euiPrefixes, err := gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.NotBeNil) + a.So(euiPrefixes, should.BeEmpty) + + ranges = map[string][]string{"ttgc": {"58A0CBFFFE800000"}} + euiPrefixes, err = gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.NotBeNil) + a.So(euiPrefixes, should.BeEmpty) + + ranges = map[string][]string{"ttgc": {"58A0CBFFFE800000/123456"}} + euiPrefixes, err = gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.NotBeNil) + a.So(euiPrefixes, should.BeEmpty) + + ranges = map[string][]string{"ttgc": {"58A0CBFFFE800000-58A0CBFFFE800000-58A0CBFFFE800000"}} + euiPrefixes, err = gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.NotBeNil) + a.So(euiPrefixes, should.BeEmpty) + + ranges = map[string][]string{"ttgc": {"001616FFFEWXUSD-001616FFFETGENDE"}} + euiPrefixes, err = gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.NotBeNil) + a.So(euiPrefixes, should.BeEmpty) + + ranges = map[string][]string{"ttgc": {"001616FFFE42DFAD-001616FFFETGENDE"}} + euiPrefixes, err = gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.NotBeNil) + a.So(euiPrefixes, should.BeEmpty) + + // Valid Configuration + ranges = map[string][]string{ + "ttgc": { + "58A0CBFFFE800000/48", + "001616FFFE42DFAD-001616FFFE42E395", + }, + } + euiPrefixes, err = gateways.ParseGatewayEUIRanges(ranges) + a.So(err, should.BeNil) + a.So(euiPrefixes, should.Resemble, map[string][]dcstypes.EUI64Range{ + "ttgc": { + dcstypes.RangeFromEUI64Prefix(types.EUI64Prefix{ + EUI64: types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x80, 0x00, 0x00}, + Length: 48, + }), + dcstypes.RangeFromEUI64Range( + types.EUI64{0x00, 0x16, 0x16, 0xff, 0xfe, 0x42, 0xdf, 0xad}, + types.EUI64{0x00, 0x16, 0x16, 0xff, 0xfe, 0x42, 0xe3, 0x95}, + ), + }, + }) + + // Invalid configurations + config := gateways.Config{ + Upstreams: map[string][]string{"ttgc": {"&S(FU*)"}}, + TTGC: ttgc.Config{}, + } + upstream, err := gateways.NewUpstream(ctx, config) + a.So(errors.IsInvalidArgument(err), should.BeTrue) + a.So(upstream, should.BeNil) + + config = gateways.Config{ + Upstreams: map[string][]string{"unsupported": {"58A0CBFFFE800000/48"}}, + TTGC: ttgc.Config{}, + } + upstream, err = gateways.NewUpstream(ctx, config) + a.So(errors.IsInvalidArgument(err), should.BeTrue) + a.So(upstream, should.BeNil) + + // Valid Configuration + config = gateways.Config{ + Upstreams: map[string][]string{"ttgc": {"58A0CBFFFE800000/48"}}, + TTGC: ttgc.Config{}, + } + upstream, err = gateways.NewUpstream(ctx, config) + a.So(err, should.BeNil) + a.So(upstream, should.NotBeNil) + + // Invalid EUI + claimer := upstream.Claimer(types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x81, 0x00, 0x00}) + a.So(claimer, should.BeNil) + + // Valid EUI + claimer = upstream.Claimer(types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x80, 0x00, 0x1B}) + a.So(claimer, should.NotBeNil) +} diff --git a/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go b/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go new file mode 100644 index 0000000000..efcc204cd1 --- /dev/null +++ b/pkg/deviceclaimingserver/gateways/ttgc/ttgc.go @@ -0,0 +1,50 @@ +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package ttgc provides functions to use The Things Gateway Controller. +package ttgc + +import ( + "context" + + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/types" +) + +// Config is the configuration for the client. +type Config struct{} + +// TTGC is the client for The Things Gateway Controller. +type TTGC struct { + config Config +} + +// NewClient returns a new TTGC client. +func (c Config) NewClient(context.Context) (*TTGC, error) { + return &TTGC{ + config: c, + }, nil +} + +var errUnimplemented = errors.DefineUnimplemented("not_implemented", "not implemented") + +// Claim implements gateways.GatewayClaimer. +func (TTGC) Claim(context.Context, types.EUI64, string, string) error { + return errUnimplemented.New() +} + +// Unclaim implements gateways.GatewayClaimer. +func (TTGC) Unclaim(context.Context, types.EUI64, string) error { + return errUnimplemented.New() +} diff --git a/pkg/deviceclaimingserver/grpc_end_devices.go b/pkg/deviceclaimingserver/grpc_end_devices.go index a1fac529f8..68f6ce21d3 100644 --- a/pkg/deviceclaimingserver/grpc_end_devices.go +++ b/pkg/deviceclaimingserver/grpc_end_devices.go @@ -36,7 +36,6 @@ var ( "DevEUI/JoinEUI not set for device", ) errDeviceNotFound = errors.DefineNotFound("device_not_found", "device not found") - errMethodUnavailable = errors.DefineUnimplemented("method_unavailable", "method unavailable") errClaimingNotSupported = errors.DefineAborted( "claiming_not_supported", "claiming not supported for JoinEUI `{eui}`", @@ -215,10 +214,8 @@ func (dcs *DeviceClaimingServer) getEndDevices( if err != nil { return nil, err } - client, err := ttnpb.NewEndDeviceBatchRegistryClient(conn), nil - if err != nil { - return nil, err - } + client := ttnpb.NewEndDeviceBatchRegistryClient(conn) + callOpt, err := rpcmetadata.WithForwardedAuth(ctx, dcs.AllowInsecureForCredentials()) if err != nil { return nil, err diff --git a/pkg/deviceclaimingserver/grpc_end_devices_test.go b/pkg/deviceclaimingserver/grpc_end_devices_test.go index 6b8a10dbb8..87c211d4f0 100644 --- a/pkg/deviceclaimingserver/grpc_end_devices_test.go +++ b/pkg/deviceclaimingserver/grpc_end_devices_test.go @@ -25,7 +25,7 @@ import ( "go.thethings.network/lorawan-stack/v3/pkg/component" componenttest "go.thethings.network/lorawan-stack/v3/pkg/component/test" "go.thethings.network/lorawan-stack/v3/pkg/config" - . "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver" "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/enddevices" claimerrors "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/enddevices/errors" "go.thethings.network/lorawan-stack/v3/pkg/errors" @@ -91,10 +91,10 @@ func TestEndDeviceClaimingServer(t *testing.T) { ctx, c, enddevices.Config{}, - enddevices.WithClaimer("test", &MockClaimer{ + enddevices.WithClaimer("test", &MockEndDeviceClaimer{ JoinEUIs: []types.EUI64{registeredJoinEUI}, ClaimFunc: func( - ctx context.Context, joinEUI, devEUI types.EUI64, claimAuthenticationCode string, + _ context.Context, joinEUI, devEUI types.EUI64, claimAuthenticationCode string, ) error { a.So(joinEUI, should.Equal, registeredJoinEUI) a.So(devEUI, should.Resemble, registeredDevEUI) @@ -104,7 +104,11 @@ func TestEndDeviceClaimingServer(t *testing.T) { }), ) a.So(err, should.BeNil) - dcs, err := New(c, &Config{}, WithEndDeviceClaimingUpstream(mockUpstream)) + dcs, err := deviceclaimingserver.New( + c, + &deviceclaimingserver.Config{}, + deviceclaimingserver.WithEndDeviceClaimingUpstream(mockUpstream), + ) test.Must(dcs, err) componenttest.StartComponent(t, c) @@ -398,7 +402,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all }) registeredJoinEUI2 := types.EUI64{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E} - mockClaimer := &MockClaimer{ + mockEndDeviceClaimer := &MockEndDeviceClaimer{ JoinEUIs: []types.EUI64{ registeredJoinEUI, registeredJoinEUI2, @@ -408,10 +412,14 @@ func TestBatchOperations(t *testing.T) { // nolint:all ctx, c, enddevices.Config{}, - enddevices.WithClaimer("test", mockClaimer), + enddevices.WithClaimer("test", mockEndDeviceClaimer), ) a.So(err, should.BeNil) - dcs, err := New(c, &Config{}, WithEndDeviceClaimingUpstream(mockUpstream)) + dcs, err := deviceclaimingserver.New( + c, + &deviceclaimingserver.Config{}, + deviceclaimingserver.WithEndDeviceClaimingUpstream(mockUpstream), + ) test.Must(dcs, err) componenttest.StartComponent(t, c) @@ -582,8 +590,8 @@ func TestBatchOperations(t *testing.T) { // nolint:all }, CallOpts: authorizedCallOpt, BatchUnclaimFunc: func( - ctx context.Context, - ids []*ttnpb.EndDeviceIdentifiers, + _ context.Context, + _ []*ttnpb.EndDeviceIdentifiers, ) error { return errors.DefineCanceled("batch level error", "batch level error") }, @@ -600,7 +608,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all }, CallOpts: authorizedCallOpt, BatchUnclaimFunc: func( - ctx context.Context, + _ context.Context, ids []*ttnpb.EndDeviceIdentifiers, ) error { a.So(ids, should.HaveLength, 2) @@ -637,7 +645,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all }, CallOpts: authorizedCallOpt, BatchUnclaimFunc: func( - ctx context.Context, + _ context.Context, ids []*ttnpb.EndDeviceIdentifiers, ) error { a.So(ids, should.HaveLength, 2) @@ -673,7 +681,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all }, CallOpts: authorizedCallOpt, BatchUnclaimFunc: func( - ctx context.Context, + _ context.Context, ids []*ttnpb.EndDeviceIdentifiers, ) error { a.So(ids, should.HaveLength, 2) @@ -711,7 +719,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all }, CallOpts: authorizedCallOpt, BatchUnclaimFunc: func( - ctx context.Context, + _ context.Context, ids []*ttnpb.EndDeviceIdentifiers, ) error { a.So(ids, should.HaveLength, 2) @@ -753,7 +761,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all }, CallOpts: authorizedCallOpt, BatchUnclaimFunc: func( - ctx context.Context, + _ context.Context, ids []*ttnpb.EndDeviceIdentifiers, ) error { a.So(ids, should.HaveLength, 2) @@ -784,7 +792,7 @@ func TestBatchOperations(t *testing.T) { // nolint:all } { tc := tc t.Run(tc.Name, func(t *testing.T) { - mockClaimer.BatchUnclaimFunc = tc.BatchUnclaimFunc + mockEndDeviceClaimer.BatchUnclaimFunc = tc.BatchUnclaimFunc ret, err := edcsClient.Unclaim(ctx, tc.Req, tc.CallOpts) if err != nil { if tc.ErrorAssertion == nil || !a.So(tc.ErrorAssertion(err), should.BeTrue) { diff --git a/pkg/deviceclaimingserver/grpc_gateways.go b/pkg/deviceclaimingserver/grpc_gateways.go index 7ecae5c0ae..1db18606b4 100644 --- a/pkg/deviceclaimingserver/grpc_gateways.go +++ b/pkg/deviceclaimingserver/grpc_gateways.go @@ -16,67 +16,205 @@ package deviceclaimingserver import ( "context" + "fmt" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/observability" + gtwregistry "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/registry/gateways" + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/log" + "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "go.thethings.network/lorawan-stack/v3/pkg/types" "google.golang.org/protobuf/types/known/emptypb" ) -// noopGCLS is a no-op GCLS. -type noopGCLS struct { - ttnpb.UnimplementedGatewayClaimingServerServer -} - -// Claim implements GatewayClaimingServer. -func (noopGCLS) Claim( - _ context.Context, - _ *ttnpb.ClaimGatewayRequest, -) (ids *ttnpb.GatewayIdentifiers, retErr error) { - return nil, errMethodUnavailable.New() -} - -// AuthorizeGateway implements GatewayClaimingServer. -func (noopGCLS) AuthorizeGateway( - _ context.Context, - _ *ttnpb.AuthorizeGatewayRequest, -) (*emptypb.Empty, error) { - return nil, errMethodUnavailable.New() -} - -// UnauthorizeGateway implements GatewayClaimingServer. -func (noopGCLS) UnauthorizeGateway( - _ context.Context, - _ *ttnpb.GatewayIdentifiers, -) (*emptypb.Empty, error) { - return nil, errMethodUnavailable.New() +type peerAccess interface { + AllowInsecureForCredentials() bool } // gatewayClaimingServer is the front facing entity for gRPC requests. type gatewayClaimingServer struct { ttnpb.UnimplementedGatewayClaimingServerServer - DCS *DeviceClaimingServer + peerAccess + + upstream *gateways.Upstream + registry gtwregistry.GatewayRegistry } +var ( + errGatewayClaimingWithQRCode = errors.DefineUnimplemented( + "gateway_claiming_with_qrcodes_not_implemented", + "gateway claiming with QR codes not implemented", + ) + errGatewayAlreadyExists = errors.DefineAlreadyExists( + "gateway_already_exists", + "gateway with EUI `{eui}` already exists", + ) + errGatewayClaimingNotSupported = errors.DefineAborted( + "gateway_claiming_not_supported", + "claiming not supported for gateway with EUI `{eui}`", + ) + errClaim = errors.DefineAborted( + "claim gateway", + "claim gateway", + ) + errCreateGateway = errors.DefineAborted( + "create_gateway", + "create gateway", + ) + errNoEUI = errors.DefineInvalidArgument( + "no_eui", + "no EUI found for gateway", + ) + errNoGatewayServerAddress = errors.DefineInvalidArgument( + "no_gateway_server_address", + "no gateway server address set for gateway", + ) +) + // Claim implements GatewayClaimingServer. -func (gcls gatewayClaimingServer) Claim( +func (gcls *gatewayClaimingServer) Claim( ctx context.Context, req *ttnpb.ClaimGatewayRequest, ) (ids *ttnpb.GatewayIdentifiers, retErr error) { - return gcls.DCS.gatewayClaimingServerUpstream.Claim(ctx, req) + logger := log.FromContext(ctx) + + // Extract the EUI and the owner token (claim authentication code) from the request. + var ( + authCode []byte + gatewayEUI types.EUI64 + ) + switch claim := req.SourceGateway.(type) { + case *ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_: + authIDs := claim.AuthenticatedIdentifiers + gatewayEUI, authCode = types.MustEUI64(authIDs.GatewayEui).OrZero(), authIDs.AuthenticationCode + case *ttnpb.ClaimGatewayRequest_QrCode: + return nil, errGatewayClaimingWithQRCode.New() + default: + panic(fmt.Sprintf("proto: unexpected type %T", claim)) + } + logger = logger.WithFields(log.Fields( + "gateway_eui", gatewayEUI, + )) + ids = &ttnpb.GatewayIdentifiers{ + Eui: gatewayEUI.Bytes(), + GatewayId: req.TargetGatewayId, + } + + // Check if the gateway already exists. + _, err := gcls.registry.GetIdentifiersForEUI(ctx, gatewayEUI) + if err == nil { + return nil, errGatewayAlreadyExists.WithAttributes("eui", gatewayEUI) + } else if !errors.IsNotFound(err) { + return nil, err + } + + // Support clients that only set a single frequency plan. + if len(req.TargetFrequencyPlanIds) == 0 && req.TargetFrequencyPlanId != "" { // nolint:staticcheck + req.TargetFrequencyPlanIds = []string{req.TargetFrequencyPlanId} // nolint:staticcheck + } + + // Check if the gateway is configured for claiming. + claimer := gcls.upstream.Claimer(gatewayEUI) + if claimer == nil { + return nil, errGatewayClaimingNotSupported.WithAttributes("eui", gatewayEUI) + } + + // Claim the gateway on the upstream. + if err := claimer.Claim(ctx, gatewayEUI, string(authCode), req.TargetGatewayServerAddress); err != nil { + observability.RegisterFailClaim(ctx, ids.GetEntityIdentifiers(), err) + return nil, errClaim.WithCause(err) + } + + // Unclaim if creation fails. + defer func(ids *ttnpb.GatewayIdentifiers) { + if retErr != nil { + observability.RegisterAbortClaim(ctx, ids.GetEntityIdentifiers(), retErr) + if err := claimer.Unclaim(ctx, gatewayEUI, string(authCode)); err != nil { + logger.WithError(err).Warn("Failed to unclaim gateway") + } + return + } + observability.RegisterSuccessClaim(ctx, ids.GetEntityIdentifiers()) + }(ids) + + // Create the gateway in the IS. + gateway := &ttnpb.Gateway{ + Ids: ids, + GatewayServerAddress: req.TargetGatewayServerAddress, + EnforceDutyCycle: true, + RequireAuthenticatedConnection: true, + FrequencyPlanIds: req.TargetFrequencyPlanIds, + } + + _, err = gcls.registry.Create(ctx, &ttnpb.CreateGatewayRequest{ + Gateway: gateway, + Collaborator: req.GetCollaborator(), + }) + if err != nil { + return nil, errCreateGateway.WithCause(err) + } + + return ids, nil } -// AuthorizeGateway implements GatewayClaimingServer. -func (gcls gatewayClaimingServer) AuthorizeGateway( - ctx context.Context, - req *ttnpb.AuthorizeGatewayRequest, -) (*emptypb.Empty, error) { - return gcls.DCS.gatewayClaimingServerUpstream.AuthorizeGateway(ctx, req) +// GetInfoByGatewayEUI implements GatewayClaimingServer. +func (gcls gatewayClaimingServer) GetInfoByGatewayEUI( + ctx context.Context, in *ttnpb.GetInfoByGatewayEUIRequest, +) (*ttnpb.GetInfoByGatewayEUIResponse, error) { + // Check that there's any auth token on the request context. + _, err := rpcmetadata.WithForwardedAuth(ctx, gcls.AllowInsecureForCredentials()) + if err != nil { + return nil, err + } + eui := types.MustEUI64(in.Eui).OrZero() + + return &ttnpb.GetInfoByGatewayEUIResponse{ + Eui: in.Eui, + SupportsClaiming: gcls.upstream.Claimer(eui) != nil, + }, nil } -// UnauthorizeGateway implements GatewayClaimingServer. -func (gcls gatewayClaimingServer) UnauthorizeGateway( - ctx context.Context, - gtwIDs *ttnpb.GatewayIdentifiers, -) (*emptypb.Empty, error) { - return gcls.DCS.gatewayClaimingServerUpstream.UnauthorizeGateway(ctx, gtwIDs) +// Unclaim implements GatewayClaimingServer. +func (gcls gatewayClaimingServer) Unclaim(ctx context.Context, req *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) { + // Check for the necessary rights. + if err := gcls.registry.AssertGatewayRights( + ctx, + &ttnpb.GatewayIdentifiers{ + GatewayId: req.GatewayId, + }, + ttnpb.Right_RIGHT_GATEWAY_INFO, + ttnpb.Right_RIGHT_GATEWAY_DELETE, + ); err != nil { + return nil, err + } + + // Get the gateway. + gtw, err := gcls.registry.Get(ctx, &ttnpb.GetGatewayRequest{ + GatewayIds: req, + }) + if err != nil { + return nil, err + } + gatewayEUI := types.MustEUI64(gtw.Ids.Eui).OrZero() + if gatewayEUI.IsZero() { + return nil, errNoEUI.New() + } + if gtw.GatewayServerAddress == "" { + return nil, errNoGatewayServerAddress.New() + } + claimer := gcls.upstream.Claimer(gatewayEUI) + if claimer == nil { + return nil, errGatewayClaimingNotSupported.WithAttributes("eui", gatewayEUI) + } + + if err := claimer.Unclaim(ctx, gatewayEUI, gtw.GatewayServerAddress); err != nil { + observability.RegisterFailUnclaim(ctx, gtw.GetEntityIdentifiers(), err) + return nil, err + } + observability.RegisterSuccessUnclaim(ctx, gtw.GetEntityIdentifiers()) + + return ttnpb.Empty, nil } diff --git a/pkg/deviceclaimingserver/grpc_gateways_test.go b/pkg/deviceclaimingserver/grpc_gateways_test.go index 6701e5903d..2f19ea485e 100644 --- a/pkg/deviceclaimingserver/grpc_gateways_test.go +++ b/pkg/deviceclaimingserver/grpc_gateways_test.go @@ -23,7 +23,9 @@ import ( "go.thethings.network/lorawan-stack/v3/pkg/component" componenttest "go.thethings.network/lorawan-stack/v3/pkg/component/test" "go.thethings.network/lorawan-stack/v3/pkg/config" - . "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver" + "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/gateways" + dcstypes "go.thethings.network/lorawan-stack/v3/pkg/deviceclaimingserver/types" "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/log" "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" @@ -39,14 +41,14 @@ var ( userID = ttnpb.UserIdentifiers{ UserId: "test-user", } - authorizedCallOpt = grpc.PerRPCCredentials(rpcmetadata.MD{ + authorizedMD = rpcmetadata.MD{ AuthType: "Bearer", AuthValue: "foo", - }) + } + authorizedCallOpt = grpc.PerRPCCredentials(authorizedMD) ) -func TestGatewayClaimingServer(t *testing.T) { - t.Parallel() +func TestGatewayClaimingServer(t *testing.T) { // nolint:paralleltest a := assertions.New(t) ctx := log.NewContext(test.Context(), test.GetLogger(t)) ctx, cancelCtx := context.WithCancel(ctx) @@ -54,6 +56,13 @@ func TestGatewayClaimingServer(t *testing.T) { cancelCtx() }) + supportedEUI := types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x80, 0x00, 0x01} + + unAuthorizedCallOpt := grpc.PerRPCCredentials(rpcmetadata.MD{ + AuthType: "Bearer", + AuthValue: "invalid-key", + }) + c := componenttest.NewComponent(t, &component.Config{ ServiceBase: config.ServiceBase{ GRPC: config.GRPC{ @@ -61,7 +70,47 @@ func TestGatewayClaimingServer(t *testing.T) { }, }, }) - test.Must(New(c, &Config{})) + + mockGatewayclaimer := &MockGatewayClaimer{} + mockUpstream, err := gateways.NewUpstream( + ctx, + gateways.Config{}, + gateways.WithClaimer( + "mock", + []dcstypes.EUI64Range{ + dcstypes.RangeFromEUI64Prefix(types.EUI64Prefix{ + EUI64: types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x80, 0x00, 0x00}, + Length: 48, + }), + }, + mockGatewayclaimer, + ), + ) + if err != nil { + t.FailNow() + } + + existingEUI := types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x80, 0x00, 0xFF} + mockGatewayRegistry := &mockGatewayRegistry{ + authorizedMD: authorizedMD, + gateways: []*ttnpb.Gateway{ + { + Ids: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + Eui: existingEUI.Bytes(), + }, + }, + }, + } + + test.Must(deviceclaimingserver.New(c, + &deviceclaimingserver.Config{}, + deviceclaimingserver.WithGatewayClaimingServer( + mockUpstream, + mockGatewayRegistry, + c, + ), + )) componenttest.StartComponent(t, c) t.Cleanup(func() { c.Close() @@ -73,77 +122,379 @@ func TestGatewayClaimingServer(t *testing.T) { mustHavePeer(ctx, c, ttnpb.ClusterRole_DEVICE_CLAIMING_SERVER) gclsClient := ttnpb.NewGatewayClaimingServerClient(c.LoopbackConn()) - // Test API Validation here. Functionality is tested in the implementations. + // Check that AuthorizeGateway and UnauthorizeGateway are not implemented. + _, err = gclsClient.AuthorizeGateway(ctx, &ttnpb.AuthorizeGatewayRequest{ // nolint:staticcheck + GatewayIds: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + }, + ApiKey: "foo", + }, authorizedCallOpt) + a.So(errors.IsUnimplemented(err), should.BeTrue) + + _, err = gclsClient.UnauthorizeGateway(ctx, &ttnpb.GatewayIdentifiers{ // nolint:staticcheck + GatewayId: "test-gateway", + }, authorizedCallOpt) + a.So(errors.IsUnimplemented(err), should.BeTrue) + + // Test GetInfoByGatewayEUI + _, err = gclsClient.GetInfoByGatewayEUI( + ctx, + &ttnpb.GetInfoByGatewayEUIRequest{ + Eui: types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x80, 0x00, 0x00}.Bytes(), + }, + ) + a.So(errors.IsUnauthenticated(err), should.BeTrue) + + unsupportedEUI := types.EUI64{0x58, 0xa0, 0xcb, 0xff, 0xfe, 0x90, 0x00, 0x00} + resp, err := gclsClient.GetInfoByGatewayEUI( + ctx, + &ttnpb.GetInfoByGatewayEUIRequest{ + Eui: unsupportedEUI.Bytes(), + }, + authorizedCallOpt, + ) + a.So(err, should.BeNil) + a.So(resp.Eui, should.Resemble, unsupportedEUI.Bytes()) + a.So(resp.SupportsClaiming, should.BeFalse) + + resp, err = gclsClient.GetInfoByGatewayEUI( + ctx, + &ttnpb.GetInfoByGatewayEUIRequest{ + Eui: supportedEUI.Bytes(), + }, + authorizedCallOpt, + ) + a.So(err, should.BeNil) + a.So(resp.Eui, should.Resemble, supportedEUI.Bytes()) + a.So(resp.SupportsClaiming, should.BeTrue) + + // Test claiming for _, tc := range []struct { Name string - Req any - ErrorAssertion func(err error) bool + Req *ttnpb.ClaimGatewayRequest + CallOpt grpc.CallOption + ClaimFunc func(context.Context, types.EUI64, string, string) error + CreateFunc func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) + UnclaimFunc func(context.Context, types.EUI64, string) error + ErrorAssertion func(error) bool }{ { - Name: "Authorize/NilIDs", - Req: &ttnpb.AuthorizeGatewayRequest{ - GatewayIds: nil, - ApiKey: "test", + Name: "Claim/EmptyRequest", + Req: &ttnpb.ClaimGatewayRequest{ + Collaborator: userID.GetOrganizationOrUserIdentifiers(), }, + CallOpt: authorizedCallOpt, ErrorAssertion: errors.IsInvalidArgument, }, { - Name: "Unauthorize/EmptyIDs", - Req: &ttnpb.GatewayIdentifiers{}, + Name: "Claim/NilCollaborator", + Req: &ttnpb.ClaimGatewayRequest{ + Collaborator: nil, + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: supportedEUI.Bytes(), + AuthenticationCode: claimAuthCode, + }, + }, + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", + }, + CallOpt: authorizedCallOpt, ErrorAssertion: errors.IsInvalidArgument, }, { - Name: "Claim/EmptyRequest", + Name: "Claim/InvalidGatewayID", Req: &ttnpb.ClaimGatewayRequest{ Collaborator: userID.GetOrganizationOrUserIdentifiers(), + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: supportedEUI.Bytes(), + AuthenticationCode: claimAuthCode, + }, + }, + TargetGatewayId: "&-gateway", + TargetGatewayServerAddress: "things.example.com", }, + CallOpt: authorizedCallOpt, ErrorAssertion: errors.IsInvalidArgument, }, { - Name: "Claim/NilCollaborator", + Name: "Claim/GatewayEUIAlreadyExists", Req: &ttnpb.ClaimGatewayRequest{ - Collaborator: nil, + Collaborator: userID.GetOrganizationOrUserIdentifiers(), SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ - GatewayEui: types.EUI64{0x58, 0xA0, 0xCB, 0xFF, 0xFE, 0x80, 0x00, 0x20}.Bytes(), + GatewayEui: existingEUI.Bytes(), AuthenticationCode: claimAuthCode, }, }, - TargetGatewayId: "my-new-gateway", - TargetGatewayServerAddress: "target-tenant.things.example.com", + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", }, - ErrorAssertion: errors.IsInvalidArgument, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsAlreadyExists, }, { - Name: "Claim/NilCollaborator", + Name: "Claim/EUINotRegisteredForClaiming", Req: &ttnpb.ClaimGatewayRequest{ - Collaborator: nil, + Collaborator: userID.GetOrganizationOrUserIdentifiers(), + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: unsupportedEUI.Bytes(), + AuthenticationCode: claimAuthCode, + }, + }, + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", + }, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsAborted, + }, + { + Name: "Claim/ClaimFailed", + Req: &ttnpb.ClaimGatewayRequest{ + Collaborator: userID.GetOrganizationOrUserIdentifiers(), SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ - GatewayEui: types.EUI64{0x58, 0xA0, 0xCB, 0xFF, 0xFE, 0x80, 0x00, 0x20}.Bytes(), + GatewayEui: supportedEUI.Bytes(), AuthenticationCode: claimAuthCode, }, }, - TargetGatewayId: "my-new-gateway", - TargetGatewayServerAddress: "target-tenant.things.example.com", + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", }, + CallOpt: authorizedCallOpt, + ClaimFunc: func(_ context.Context, _ types.EUI64, _, _ string) error { + return errClaim.New() + }, + ErrorAssertion: errors.IsAborted, + }, + { + Name: "Claim/CreateFailed", + Req: &ttnpb.ClaimGatewayRequest{ + Collaborator: userID.GetOrganizationOrUserIdentifiers(), + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: supportedEUI.Bytes(), + AuthenticationCode: claimAuthCode, + }, + }, + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", + }, + CallOpt: authorizedCallOpt, + ClaimFunc: func(context.Context, types.EUI64, string, string) error { + return nil + }, + CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { + return nil, errCreate.New() + }, + UnclaimFunc: func(_ context.Context, eui types.EUI64, _ string) error { + if eui.Equal(supportedEUI) { + return nil + } + return errUnclaim.New() + }, + ErrorAssertion: errors.IsAborted, + }, + { + Name: "Claim/CreateFailedWithUnclaimFailed", + Req: &ttnpb.ClaimGatewayRequest{ + Collaborator: userID.GetOrganizationOrUserIdentifiers(), + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: supportedEUI.Bytes(), + AuthenticationCode: claimAuthCode, + }, + }, + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", + }, + CallOpt: authorizedCallOpt, + ClaimFunc: func(context.Context, types.EUI64, string, string) error { + return nil + }, + CreateFunc: func(context.Context, *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { + return nil, errCreate.New() + }, + UnclaimFunc: func(context.Context, types.EUI64, string) error { + return errUnclaim.New() + }, + ErrorAssertion: errors.IsAborted, + }, + { + Name: "Claim/SuccessfullyClaimedAndCreated", + Req: &ttnpb.ClaimGatewayRequest{ + Collaborator: userID.GetOrganizationOrUserIdentifiers(), + SourceGateway: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers_{ + AuthenticatedIdentifiers: &ttnpb.ClaimGatewayRequest_AuthenticatedIdentifiers{ + GatewayEui: supportedEUI.Bytes(), + AuthenticationCode: claimAuthCode, + }, + }, + TargetGatewayId: "test-gateway", + TargetGatewayServerAddress: "things.example.com", + }, + ClaimFunc: func(context.Context, types.EUI64, string, string) error { + return nil + }, + CreateFunc: func(_ context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { + return in.Gateway, nil + }, + CallOpt: authorizedCallOpt, + }, + } { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + if tc.ClaimFunc != nil { + mockGatewayclaimer.ClaimFunc = tc.ClaimFunc + } + if tc.UnclaimFunc != nil { + mockGatewayclaimer.UnclaimFunc = tc.UnclaimFunc + } + if tc.CreateFunc != nil { + mockGatewayRegistry.createFunc = tc.CreateFunc + } + + _, err := gclsClient.Claim(ctx, tc.Req, tc.CallOpt) + if err != nil { + if tc.ErrorAssertion == nil || !a.So(tc.ErrorAssertion(err), should.BeTrue) { + t.Fatalf("Unexpected error: %v", err) + } + } else if tc.ErrorAssertion != nil { + t.Fatalf("Expected error") + } + }) + } + + // Test unclaiming. + for _, tc := range []struct { //nolint:paralleltest + Name string + Req *ttnpb.GatewayIdentifiers + CallOpt grpc.CallOption + GetFunc func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) + UnclaimFunc func(context.Context, types.EUI64, string) error + ErrorAssertion func(error) bool + }{ + { + Name: "Unclaim/EmptyRequest", + Req: &ttnpb.GatewayIdentifiers{}, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "Unclaim/NoGatewayRights", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + }, + CallOpt: unAuthorizedCallOpt, + ErrorAssertion: errors.IsUnauthenticated, + }, + { + Name: "Unclaim/InvalidGatewayID", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway*W(&$@#)", + }, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "Unclaim/NoGatewayEUI", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + }, + GetFunc: func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + return &ttnpb.Gateway{ + Ids: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + }, + }, nil + }, + CallOpt: authorizedCallOpt, ErrorAssertion: errors.IsInvalidArgument, }, + { + Name: "Unclaim/NoGatewayServerAddress", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "invalid-gateway-server-address", + }, + GetFunc: func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + return &ttnpb.Gateway{ + Ids: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + Eui: supportedEUI.Bytes(), + }, + }, nil + }, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsInvalidArgument, + }, + { + Name: "Unclaim/EUINotRegisteredForClaiming", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "unsupported-eui", + }, + GetFunc: func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + return &ttnpb.Gateway{ + Ids: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + Eui: unsupportedEUI.Bytes(), + }, + GatewayServerAddress: "test.example.com", + }, nil + }, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsAborted, + }, + { + Name: "Unclaim/Failed", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + }, + GetFunc: func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + return &ttnpb.Gateway{ + Ids: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + Eui: supportedEUI.Bytes(), + }, + GatewayServerAddress: "test.example.com", + }, nil + }, + UnclaimFunc: func(context.Context, types.EUI64, string) error { + return errUnclaim.New() + }, + CallOpt: authorizedCallOpt, + ErrorAssertion: errors.IsAborted, + }, + { + Name: "Unclaim/Success", + Req: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + }, + GetFunc: func(context.Context, *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + return &ttnpb.Gateway{ + Ids: &ttnpb.GatewayIdentifiers{ + GatewayId: "test-gateway", + Eui: supportedEUI.Bytes(), + }, + GatewayServerAddress: "test.example.com", + }, nil + }, + UnclaimFunc: func(context.Context, types.EUI64, string) error { + return nil + }, + CallOpt: authorizedCallOpt, + }, } { tc := tc t.Run(tc.Name, func(t *testing.T) { - t.Parallel() - var err error - switch req := tc.Req.(type) { - case *ttnpb.AuthorizeGatewayRequest: - _, err = gclsClient.AuthorizeGateway(ctx, req, authorizedCallOpt) - case *ttnpb.GatewayIdentifiers: - _, err = gclsClient.UnauthorizeGateway(ctx, req, authorizedCallOpt) - case *ttnpb.ClaimGatewayRequest: - _, err = gclsClient.Claim(ctx, req, authorizedCallOpt) - default: - panic("invalid request type") + if tc.UnclaimFunc != nil { + mockGatewayclaimer.UnclaimFunc = tc.UnclaimFunc + } + if tc.GetFunc != nil { + mockGatewayRegistry.getFunc = tc.GetFunc } + _, err := gclsClient.Unclaim(ctx, tc.Req, tc.CallOpt) if err != nil { if tc.ErrorAssertion == nil || !a.So(tc.ErrorAssertion(err), should.BeTrue) { t.Fatalf("Unexpected error: %v", err) diff --git a/pkg/deviceclaimingserver/observability/observability.go b/pkg/deviceclaimingserver/observability/observability.go new file mode 100644 index 0000000000..eec831364e --- /dev/null +++ b/pkg/deviceclaimingserver/observability/observability.go @@ -0,0 +1,264 @@ +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package observability provides events and metrics for device claiming. +package observability + +import ( + "context" + "fmt" + + "github.com/prometheus/client_golang/prometheus" + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/events" + "go.thethings.network/lorawan-stack/v3/pkg/identityserver/store" + "go.thethings.network/lorawan-stack/v3/pkg/metrics" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" +) + +var ( + evtClaimEndDeviceSuccess = events.Define( + "dcs.end_device.claim.success", "claim end device successful", + events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ), + ) + evtClaimEndDeviceAbort = events.Define( + "dcs.end_device.claim.abort", "abort end device claim", + events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ), + ) + evtClaimEndDeviceFail = events.Define( + "dcs.end_device.claim.fail", "claim end device failure", + events.WithVisibility( + ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ, + ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ_KEYS, + ), + ) + evtClaimGatewaySuccess = events.Define( + "dcs.gateway.claim.success", "claim gateway successful", + events.WithVisibility(ttnpb.Right_RIGHT_GATEWAY_STATUS_READ), + ) + evtClaimGatewayAbort = events.Define( + "dcs.gateway.claim.abort", "abort gateway claim", + events.WithVisibility(ttnpb.Right_RIGHT_GATEWAY_STATUS_READ), + ) + evtClaimGatewayFail = events.Define( + "dcs.gateway.claim.fail", "claim gateway failure", + events.WithVisibility( + ttnpb.Right_RIGHT_GATEWAY_STATUS_READ, + ), + ) + + evtUnclaimEndDeviceSuccess = events.Define( + "dcs.end_device.unclaim.success", "unclaim end device successful", + events.WithVisibility(ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ), + ) + evtUnclaimEndDeviceFail = events.Define( + "dcs.end_device.unclaim.fail", "unclaim end device failure", + events.WithVisibility( + ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ, + ttnpb.Right_RIGHT_APPLICATION_DEVICES_READ_KEYS, + ), + ) + evtUnclaimGatewaySuccess = events.Define( + "dcs.gateway.unclaim.success", "unclaim gateway successful", + events.WithVisibility(ttnpb.Right_RIGHT_GATEWAY_STATUS_READ), + ) + evtUnclaimGatewayFail = events.Define( + "dcs.gateway.unclaim.fail", "unclaim gateway failure", + events.WithVisibility(ttnpb.Right_RIGHT_GATEWAY_STATUS_READ), + ) +) + +const ( + subsystem = "dcs" + unknown = "unknown" + entityType = "entity_type" + id = "id" +) + +var dcsMetrics = &claimMetrics{ + claimSucceeded: metrics.NewContextualCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "claim_success_total", + Help: "Total number of successfully claimed entities", + }, + []string{entityType, id}, + ), + claimAborted: metrics.NewContextualCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "claim_aborted_total", + Help: "Total number of claim entity abortions", + }, + []string{entityType, id, "error"}, + ), + claimFailed: metrics.NewContextualCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "claim_failed_total", + Help: "Total number of claim entity failures", + }, + []string{entityType, id, "error"}, + ), + + unclaimSucceeded: metrics.NewContextualCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "unclaim_success_total", + Help: "Total number of successfully unclaimed entities", + }, + []string{entityType, id}, + ), + unclaimFailed: metrics.NewContextualCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: "unclaim_failed_total", + Help: "Total number of unclaim entity failures", + }, + []string{entityType, id, "error"}, + ), +} + +func init() { + metrics.MustRegister(dcsMetrics) +} + +type claimMetrics struct { + claimSucceeded *metrics.ContextualCounterVec + claimAborted *metrics.ContextualCounterVec + claimFailed *metrics.ContextualCounterVec + + unclaimSucceeded *metrics.ContextualCounterVec + unclaimFailed *metrics.ContextualCounterVec +} + +func (m claimMetrics) Describe(ch chan<- *prometheus.Desc) { + m.claimSucceeded.Describe(ch) + m.claimAborted.Describe(ch) + m.claimFailed.Describe(ch) + + m.unclaimSucceeded.Describe(ch) + m.unclaimFailed.Describe(ch) +} + +func (m claimMetrics) Collect(ch chan<- prometheus.Metric) { + m.claimSucceeded.Collect(ch) + m.claimAborted.Collect(ch) + m.claimFailed.Collect(ch) + + m.unclaimSucceeded.Collect(ch) + m.unclaimFailed.Collect(ch) +} + +// RegisterSuccessClaim registers a successful claim. +func RegisterSuccessClaim(ctx context.Context, entityIDs *ttnpb.EntityIdentifiers) { + var id, entityType string + switch ids := entityIDs.Ids.(type) { + case *ttnpb.EntityIdentifiers_DeviceIds: + id = ids.DeviceIds.GetApplicationIds().GetApplicationId() + entityType = store.EntityEndDevice + events.Publish(evtClaimEndDeviceSuccess.NewWithIdentifiersAndData(ctx, entityIDs, nil)) + case *ttnpb.EntityIdentifiers_GatewayIds: + id = ids.GatewayIds.GatewayId + entityType = store.EntityGateway + events.Publish(evtClaimGatewaySuccess.NewWithIdentifiersAndData(ctx, entityIDs, nil)) + default: + panic(fmt.Sprintf("proto: unexpected type %T", entityIDs.Ids)) + } + dcsMetrics.claimSucceeded.WithLabelValues(ctx, entityType, id).Inc() +} + +// RegisterAbortClaim registers an aborted claim. +func RegisterAbortClaim(ctx context.Context, entityIDs *ttnpb.EntityIdentifiers, err error) { + var id, entityType string + switch ids := entityIDs.Ids.(type) { + case *ttnpb.EntityIdentifiers_DeviceIds: + id = ids.DeviceIds.GetApplicationIds().GetApplicationId() + entityType = store.EntityEndDevice + events.Publish(evtClaimEndDeviceAbort.NewWithIdentifiersAndData(ctx, entityIDs, err)) + case *ttnpb.EntityIdentifiers_GatewayIds: + id = ids.GatewayIds.GatewayId + entityType = store.EntityGateway + events.Publish(evtClaimGatewayAbort.NewWithIdentifiersAndData(ctx, entityIDs, err)) + default: + panic(fmt.Sprintf("proto: unexpected type %T", entityIDs.Ids)) + } + if ttnErr, ok := errors.From(err); ok { + dcsMetrics.claimAborted.WithLabelValues(ctx, entityType, id, ttnErr.FullName()).Inc() + } else { + dcsMetrics.claimAborted.WithLabelValues(ctx, entityType, id, unknown).Inc() + } +} + +// RegisterFailClaim registers an failed claim. +func RegisterFailClaim(ctx context.Context, entityIDs *ttnpb.EntityIdentifiers, err error) { + var id, entityType string + switch ids := entityIDs.Ids.(type) { + case *ttnpb.EntityIdentifiers_DeviceIds: + id = ids.DeviceIds.GetApplicationIds().GetApplicationId() + entityType = "end_device" + events.Publish(evtClaimEndDeviceFail.NewWithIdentifiersAndData(ctx, entityIDs, err)) + case *ttnpb.EntityIdentifiers_GatewayIds: + id = ids.GatewayIds.GatewayId + entityType = "gateway" + events.Publish(evtClaimGatewayFail.NewWithIdentifiersAndData(ctx, entityIDs, err)) + default: + panic(fmt.Sprintf("proto: unexpected type %T", entityIDs.Ids)) + } + if ttnErr, ok := errors.From(err); ok { + dcsMetrics.claimFailed.WithLabelValues(ctx, entityType, id, ttnErr.FullName()).Inc() + } else { + dcsMetrics.claimFailed.WithLabelValues(ctx, entityType, id, unknown).Inc() + } +} + +// RegisterSuccessUnclaim registers a successful unclaim. +func RegisterSuccessUnclaim(ctx context.Context, entityIDs *ttnpb.EntityIdentifiers) { + var id, entityType string + switch ids := entityIDs.Ids.(type) { + case *ttnpb.EntityIdentifiers_DeviceIds: + id = ids.DeviceIds.GetApplicationIds().GetApplicationId() + entityType = "end_device" + events.Publish(evtUnclaimEndDeviceSuccess.NewWithIdentifiersAndData(ctx, entityIDs, nil)) + case *ttnpb.EntityIdentifiers_GatewayIds: + id = ids.GatewayIds.GatewayId + entityType = "gateway" + events.Publish(evtUnclaimGatewaySuccess.NewWithIdentifiersAndData(ctx, entityIDs, nil)) + default: + panic(fmt.Sprintf("proto: unexpected type %T", entityIDs.Ids)) + } + dcsMetrics.unclaimSucceeded.WithLabelValues(ctx, entityType, id).Inc() +} + +// RegisterFailUnclaim registers an failed unclaim. +func RegisterFailUnclaim(ctx context.Context, entityIDs *ttnpb.EntityIdentifiers, err error) { + var id, entityType string + switch ids := entityIDs.Ids.(type) { + case *ttnpb.EntityIdentifiers_DeviceIds: + id = ids.DeviceIds.GetApplicationIds().GetApplicationId() + entityType = "end_device" + events.Publish(evtUnclaimEndDeviceFail.NewWithIdentifiersAndData(ctx, entityIDs, err)) + case *ttnpb.EntityIdentifiers_GatewayIds: + id = ids.GatewayIds.GatewayId + entityType = "gateway" + events.Publish(evtUnclaimGatewayFail.NewWithIdentifiersAndData(ctx, entityIDs, err)) + default: + panic(fmt.Sprintf("proto: unexpected type %T", entityIDs.Ids)) + } + if ttnErr, ok := errors.From(err); ok { + dcsMetrics.unclaimFailed.WithLabelValues(ctx, entityType, id, ttnErr.FullName()).Inc() + } else { + dcsMetrics.unclaimFailed.WithLabelValues(ctx, entityType, id, unknown).Inc() + } +} diff --git a/pkg/deviceclaimingserver/registry/gateways/gateways.go b/pkg/deviceclaimingserver/registry/gateways/gateways.go new file mode 100644 index 0000000000..419bb44409 --- /dev/null +++ b/pkg/deviceclaimingserver/registry/gateways/gateways.go @@ -0,0 +1,131 @@ +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gateways provide gateway registry functions. +package gateways + +import ( + "context" + + "go.thethings.network/lorawan-stack/v3/pkg/auth/rights" + "go.thethings.network/lorawan-stack/v3/pkg/cluster" + "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "go.thethings.network/lorawan-stack/v3/pkg/types" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" +) + +// Cluster provides cluster operations. +type Cluster interface { + GetPeerConn(ctx context.Context, role ttnpb.ClusterRole, ids cluster.EntityIdentifiers) (*grpc.ClientConn, error) + AllowInsecureForCredentials() bool + WithClusterAuth() grpc.CallOption +} + +// GatewayRegistry abstracts the entity registry's gateway functions. +type GatewayRegistry interface { + // AssertGatewayRights checks whether the gateway authentication + // (provided in the context) contains the required rights. + AssertGatewayRights(ctx context.Context, ids *ttnpb.GatewayIdentifiers, required ...ttnpb.Right) error + // GetIdentifiersForEUI returns the gateway identifiers for the EUI. + GetIdentifiersForEUI(ctx context.Context, eui types.EUI64) (*ttnpb.GatewayIdentifiers, error) + // Create creates a gateway. + Create(ctx context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) + // Delete the gateway. This may not release the gateway ID for reuse, but it does release the EUI. + Delete(ctx context.Context, in *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) + // Get the gateway. This may not release the gateway ID for reuse, but it does release the EUI. + Get(ctx context.Context, req *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) +} + +// Registry implements GatewayRegistry. +type Registry struct { + Cluster +} + +func (reg Registry) newEntityRegistryClient(ctx context.Context) (ttnpb.GatewayRegistryClient, error) { + cc, err := reg.GetPeerConn(ctx, ttnpb.ClusterRole_ENTITY_REGISTRY, nil) + if err != nil { + return nil, err + } + return ttnpb.NewGatewayRegistryClient(cc), nil +} + +// callOptFromContext returns a gRPC call option from the provided context. +func (reg Registry) callOptFromContext(ctx context.Context) (grpc.CallOption, error) { + return rpcmetadata.WithForwardedAuth(ctx, reg.AllowInsecureForCredentials()) +} + +// GetIdentifiersForEUI implements GatewayRegistry. +func (reg Registry) GetIdentifiersForEUI( + ctx context.Context, + gatewayEUI types.EUI64, +) (*ttnpb.GatewayIdentifiers, error) { + // Check if the gateway is registered. + gatewayRegistry, err := reg.newEntityRegistryClient(ctx) + if err != nil { + return nil, err + } + return gatewayRegistry.GetIdentifiersForEUI(ctx, &ttnpb.GetGatewayIdentifiersForEUIRequest{ + Eui: gatewayEUI.Bytes(), + }, reg.WithClusterAuth()) +} + +// AssertGatewayRights implements GatewayRegistry. +func (Registry) AssertGatewayRights( + ctx context.Context, + ids *ttnpb.GatewayIdentifiers, + required ...ttnpb.Right, +) error { + return rights.RequireGateway(ctx, ids, required...) +} + +// Create implements GatewayRegistry. +func (reg Registry) Create(ctx context.Context, req *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { + callOpt, err := reg.callOptFromContext(ctx) + if err != nil { + return nil, err + } + gatewayRegistry, err := reg.newEntityRegistryClient(ctx) + if err != nil { + return nil, err + } + return gatewayRegistry.Create(ctx, req, callOpt) +} + +// Delete implements GatewayRegistry. +func (reg Registry) Delete(ctx context.Context, req *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) { + callOpt, err := reg.callOptFromContext(ctx) + if err != nil { + return nil, err + } + gatewayRegistry, err := reg.newEntityRegistryClient(ctx) + if err != nil { + return nil, err + } + return gatewayRegistry.Delete(ctx, req, callOpt) +} + +// Get implements GatewayRegistry. +func (reg Registry) Get(ctx context.Context, req *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + callOpt, err := reg.callOptFromContext(ctx) + if err != nil { + return nil, err + } + gatewayRegistry, err := reg.newEntityRegistryClient(ctx) + if err != nil { + return nil, err + } + return gatewayRegistry.Get(ctx, req, callOpt) +} diff --git a/pkg/deviceclaimingserver/types/types.go b/pkg/deviceclaimingserver/types/types.go new file mode 100644 index 0000000000..2e17c59454 --- /dev/null +++ b/pkg/deviceclaimingserver/types/types.go @@ -0,0 +1,58 @@ +// Copyright © 2024 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package types provides types for the Device Claiming Server. +package types + +import "go.thethings.network/lorawan-stack/v3/pkg/types" + +// EUI64Range is a range of EUI64s. +type EUI64Range interface { + // Contains returns true if the EUI64 is in the range. + Contains(types.EUI64) bool +} + +type eui64PrefixRange types.EUI64Prefix + +var _ EUI64Range = eui64PrefixRange{} + +// Contains implements EUI64Range. +func (r eui64PrefixRange) Contains(eui types.EUI64) bool { + return eui.HasPrefix(types.EUI64Prefix(r)) +} + +// RangeFromEUI64Prefix returns a range that contains all EUI64s with the given prefix. +func RangeFromEUI64Prefix(prefix types.EUI64Prefix) EUI64Range { + return eui64PrefixRange(prefix) +} + +type eui64Range struct { + start, end uint64 +} + +var _ EUI64Range = eui64Range{} + +// Contains implements EUI64Range. +func (r eui64Range) Contains(eui types.EUI64) bool { + n := eui.MarshalNumber() + return n >= r.start && n <= r.end +} + +// RangeFromEUI64Range returns a range that contains all EUI64s between start and end. +func RangeFromEUI64Range(start, end types.EUI64) EUI64Range { + return eui64Range{ + start: start.MarshalNumber(), + end: end.MarshalNumber(), + } +} diff --git a/pkg/deviceclaimingserver/util_test.go b/pkg/deviceclaimingserver/util_test.go index 57f94f1779..db7f875088 100644 --- a/pkg/deviceclaimingserver/util_test.go +++ b/pkg/deviceclaimingserver/util_test.go @@ -17,12 +17,15 @@ package deviceclaimingserver_test import ( "context" + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/rpcmetadata" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/types" + "google.golang.org/protobuf/types/known/emptypb" ) -// MockClaimer is a mock Claimer. -type MockClaimer struct { +// MockEndDeviceClaimer is a mock Claimer. +type MockEndDeviceClaimer struct { JoinEUIs []types.EUI64 ClaimFunc func(context.Context, types.EUI64, types.EUI64, string) error @@ -33,7 +36,7 @@ type MockClaimer struct { } // SupportsJoinEUI returns whether the Join Server supports this JoinEUI. -func (m MockClaimer) SupportsJoinEUI(joinEUI types.EUI64) bool { +func (m MockEndDeviceClaimer) SupportsJoinEUI(joinEUI types.EUI64) bool { for _, eui := range m.JoinEUIs { if eui.Equal(joinEUI) { return true @@ -43,14 +46,14 @@ func (m MockClaimer) SupportsJoinEUI(joinEUI types.EUI64) bool { } // Claim claims an End Device. -func (m MockClaimer) Claim( +func (m MockEndDeviceClaimer) Claim( ctx context.Context, joinEUI, devEUI types.EUI64, claimAuthenticationCode string, ) error { return m.ClaimFunc(ctx, joinEUI, devEUI, claimAuthenticationCode) } // GetClaimStatus returns the claim status an End Device. -func (MockClaimer) GetClaimStatus(_ context.Context, +func (MockEndDeviceClaimer) GetClaimStatus(_ context.Context, ids *ttnpb.EndDeviceIdentifiers, ) (*ttnpb.GetClaimStatusResponse, error) { return &ttnpb.GetClaimStatusResponse{ @@ -59,16 +62,97 @@ func (MockClaimer) GetClaimStatus(_ context.Context, } // Unclaim releases the claim on an End Device. -func (MockClaimer) Unclaim(_ context.Context, +func (MockEndDeviceClaimer) Unclaim(_ context.Context, _ *ttnpb.EndDeviceIdentifiers, ) (err error) { return nil } // Unclaim releases the claim on an End Device. -func (m MockClaimer) BatchUnclaim( +func (m MockEndDeviceClaimer) BatchUnclaim( ctx context.Context, ids []*ttnpb.EndDeviceIdentifiers, ) error { return m.BatchUnclaimFunc(ctx, ids) } + +// MockGatewayClaimer is a mock gateway Claimer. +type MockGatewayClaimer struct { + EUIs []types.EUI64 + + ClaimFunc func(context.Context, types.EUI64, string, string) error + UnclaimFunc func(context.Context, types.EUI64, string) error +} + +// Claim implements gateways.Claimer. +func (claimer MockGatewayClaimer) Claim( + ctx context.Context, + eui types.EUI64, + ownerToken string, + clusterAddress string, +) error { + return claimer.ClaimFunc(ctx, eui, ownerToken, clusterAddress) +} + +// Unclaim implements gateways.Claimer. +func (claimer MockGatewayClaimer) Unclaim(ctx context.Context, eui types.EUI64, clusterAddress string) error { + return claimer.UnclaimFunc(ctx, eui, clusterAddress) +} + +type mockGatewayRegistry struct { + gateways []*ttnpb.Gateway + authorizedMD rpcmetadata.MD + + createFunc func(ctx context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) + deleteFunc func(ctx context.Context, in *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) + getFunc func(ctx context.Context, req *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) +} + +var ( + errInvalidCredentials = errors.DefineUnauthenticated("invalid_credentials", "invalid credentials") + errGatewayNotFound = errors.DefineNotFound("gateway_not_found", "gateway not found") + errClaim = errors.DefineAborted("claim", "claim") + errCreate = errors.DefineAborted("create_gateway", "create gateway") + errUnclaim = errors.DefineAborted("unclaim", "unclaim gateway") +) + +// AssertGatewayRights implements GatewayRegistry. +func (mock mockGatewayRegistry) AssertGatewayRights( + ctx context.Context, + _ *ttnpb.GatewayIdentifiers, + _ ...ttnpb.Right, +) error { + md := rpcmetadata.FromIncomingContext(ctx) + if md.AuthType == mock.authorizedMD.AuthType && md.AuthValue == mock.authorizedMD.AuthValue { + return nil + } + return errInvalidCredentials.New() +} + +// GetIdentifiersForEUI implements GatewayRegistry. +func (mock mockGatewayRegistry) GetIdentifiersForEUI( + _ context.Context, + eui types.EUI64, +) (*ttnpb.GatewayIdentifiers, error) { + for _, gateway := range mock.gateways { + if types.MustEUI64(gateway.GetIds().Eui).Equal(eui) { + return gateway.Ids, nil + } + } + return nil, errGatewayNotFound.New() +} + +// Create implements GatewayRegistry. +func (mock mockGatewayRegistry) Create(ctx context.Context, in *ttnpb.CreateGatewayRequest) (*ttnpb.Gateway, error) { + return mock.createFunc(ctx, in) +} + +// Delete implements GatewayRegistry. +func (mock mockGatewayRegistry) Delete(ctx context.Context, in *ttnpb.GatewayIdentifiers) (*emptypb.Empty, error) { + return mock.deleteFunc(ctx, in) +} + +// Get implements GatewayRegistry. +func (mock mockGatewayRegistry) Get(ctx context.Context, in *ttnpb.GetGatewayRequest) (*ttnpb.Gateway, error) { + return mock.getFunc(ctx, in) +} diff --git a/pkg/ttnpb/deviceclaimingserver.pb.go b/pkg/ttnpb/deviceclaimingserver.pb.go index bc2f605688..c1d7f7552c 100644 --- a/pkg/ttnpb/deviceclaimingserver.pb.go +++ b/pkg/ttnpb/deviceclaimingserver.pb.go @@ -577,6 +577,9 @@ func (x *BatchUnclaimEndDevicesResponse) GetFailed() map[string]*ErrorDetails { return nil } +// DEPRECATED: This message is deprecated and will be removed in a future version of The Things Stack. +// +// Deprecated: Marked as deprecated in ttn/lorawan/v3/deviceclaimingserver.proto. type CUPSRedirection struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -705,6 +708,9 @@ type ClaimGatewayRequest struct { // Target Gateway Server Address for the target gateway. TargetGatewayServerAddress string `protobuf:"bytes,5,opt,name=target_gateway_server_address,json=targetGatewayServerAddress,proto3" json:"target_gateway_server_address,omitempty"` // Parameters to set CUPS redirection for the gateway. + // DEPRECATED: This field is deprecated and will be removed in a future version of The Things Stack. + // + // Deprecated: Marked as deprecated in ttn/lorawan/v3/deviceclaimingserver.proto. CupsRedirection *CUPSRedirection `protobuf:"bytes,6,opt,name=cups_redirection,json=cupsRedirection,proto3" json:"cups_redirection,omitempty"` // Frequency plan ID of the target gateway. // TODO: Remove this field (https://github.com/TheThingsIndustries/lorawan-stack/issues/4024) @@ -790,6 +796,7 @@ func (x *ClaimGatewayRequest) GetTargetGatewayServerAddress() string { return "" } +// Deprecated: Marked as deprecated in ttn/lorawan/v3/deviceclaimingserver.proto. func (x *ClaimGatewayRequest) GetCupsRedirection() *CUPSRedirection { if x != nil { return x.CupsRedirection @@ -1438,7 +1445,7 @@ var file_ttn_lorawan_v3_deviceclaimingserver_proto_rawDesc = []byte{ 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x89, 0x03, 0x0a, 0x0f, 0x43, 0x55, 0x50, 0x53, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x22, 0x8d, 0x03, 0x0a, 0x0f, 0x43, 0x55, 0x50, 0x53, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x75, 0x70, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x13, 0xfa, 0x42, 0x10, 0x72, 0x0e, 0x18, 0x80, 0x02, 0x32, 0x06, 0x5e, 0x68, 0x74, 0x74, 0x70, 0x73, 0x88, @@ -1461,247 +1468,255 @@ var file_ttn_lorawan_v3_deviceclaimingserver_proto_rawDesc = []byte{ 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x7a, 0x03, 0x18, 0x80, 0x40, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x7a, 0x03, 0x18, 0x80, 0x40, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x15, 0x0a, 0x13, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0xe8, 0x08, 0x0a, - 0x13, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x7b, 0x0a, 0x19, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, - 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x47, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x75, 0x74, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x3a, 0x02, 0x18, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, + 0x22, 0xec, 0x08, 0x0a, 0x13, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x7b, 0x0a, 0x19, 0x61, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x74, 0x74, + 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6c, 0x61, + 0x69, 0x6d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x18, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x18, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x73, 0x12, 0x25, 0x0a, 0x07, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x7a, 0x05, 0x10, 0x00, 0x18, 0x80, 0x08, 0x48, 0x00, - 0x52, 0x06, 0x71, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x5b, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, - 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, - 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, - 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x72, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, - 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x56, 0x0a, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x2a, 0xfa, 0x42, 0x27, 0x72, 0x25, 0x18, 0x24, 0x32, 0x21, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, - 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, - 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x7c, 0x5e, 0x24, 0x52, 0x0f, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x12, 0xd2, 0x01, - 0x0a, 0x1d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8e, 0x01, 0xfa, 0x42, 0x8a, 0x01, 0x72, 0x87, 0x01, 0x32, - 0x84, 0x01, 0x5e, 0x28, 0x3f, 0x3a, 0x28, 0x3f, 0x3a, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, - 0x30, 0x2d, 0x39, 0x5d, 0x7c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, - 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5c, 0x2d, 0x5d, 0x2a, 0x5b, 0x61, - 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x5c, 0x2e, 0x29, 0x2a, 0x28, 0x3f, - 0x3a, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x7c, 0x5b, 0x41, 0x2d, - 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, - 0x2d, 0x39, 0x5c, 0x2d, 0x5d, 0x2a, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, - 0x5d, 0x29, 0x28, 0x3f, 0x3a, 0x3a, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x35, 0x7d, - 0x29, 0x3f, 0x24, 0x7c, 0x5e, 0x24, 0x52, 0x1a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x4a, 0x0a, 0x10, 0x63, 0x75, 0x70, 0x73, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, - 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x55, - 0x50, 0x53, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x63, - 0x75, 0x70, 0x73, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x42, - 0x0a, 0x18, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, - 0x63, 0x79, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x09, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, 0x40, 0x18, 0x01, 0x52, 0x15, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6c, 0x61, 0x6e, - 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x19, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x66, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x09, 0x42, 0x10, 0xfa, 0x42, 0x0d, 0x92, 0x01, 0x0a, 0x08, 0x00, 0x10, - 0x08, 0x22, 0x04, 0x72, 0x02, 0x18, 0x40, 0x52, 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x46, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x73, 0x1a, - 0xae, 0x02, 0x0a, 0x18, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0xd6, 0x01, 0x0a, - 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, - 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, - 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, - 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, - 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, - 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, - 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, - 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, - 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, - 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, - 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, - 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x45, 0x75, 0x69, 0x12, 0x39, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x7a, 0x03, 0x18, 0x80, 0x10, 0x52, 0x12, 0x61, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, - 0x42, 0x15, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x12, 0x03, 0xf8, 0x42, 0x01, 0x22, 0x8a, 0x01, 0x0a, 0x17, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x4d, 0x0a, 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, - 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, - 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, - 0x64, 0x73, 0x12, 0x20, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x61, 0x70, - 0x69, 0x4b, 0x65, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x55, 0x49, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0xc7, 0x01, 0x0a, 0x03, 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, - 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, - 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, - 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, - 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, - 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, - 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, - 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, - 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, - 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, - 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, - 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x03, 0x65, 0x75, 0x69, 0x22, 0x94, 0x02, - 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x45, 0x55, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xc7, 0x01, - 0x0a, 0x03, 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, - 0x21, 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, - 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, - 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, + 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x25, 0x0a, 0x07, 0x71, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x7a, 0x05, 0x10, 0x00, 0x18, + 0x80, 0x08, 0x48, 0x00, 0x52, 0x06, 0x71, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x5b, 0x0a, 0x0c, + 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x2e, 0x76, 0x33, 0x2e, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x63, 0x6f, 0x6c, + 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x56, 0x0a, 0x11, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x2a, 0xfa, 0x42, 0x27, 0x72, 0x25, 0x18, 0x24, 0x32, 0x21, 0x5e, + 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, + 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x7c, 0x5e, 0x24, + 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, + 0x64, 0x12, 0xd2, 0x01, 0x0a, 0x1d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x8e, 0x01, 0xfa, 0x42, 0x8a, 0x01, + 0x72, 0x87, 0x01, 0x32, 0x84, 0x01, 0x5e, 0x28, 0x3f, 0x3a, 0x28, 0x3f, 0x3a, 0x5b, 0x61, 0x2d, + 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x7c, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, + 0x30, 0x2d, 0x39, 0x5d, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5c, 0x2d, + 0x5d, 0x2a, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x5c, 0x2e, + 0x29, 0x2a, 0x28, 0x3f, 0x3a, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, + 0x7c, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x5b, 0x41, 0x2d, 0x5a, + 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5c, 0x2d, 0x5d, 0x2a, 0x5b, 0x41, 0x2d, 0x5a, 0x61, 0x2d, + 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x28, 0x3f, 0x3a, 0x3a, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x7b, + 0x31, 0x2c, 0x35, 0x7d, 0x29, 0x3f, 0x24, 0x7c, 0x5e, 0x24, 0x52, 0x1a, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x4e, 0x0a, 0x10, 0x63, 0x75, 0x70, 0x73, 0x5f, 0x72, + 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x43, 0x55, 0x50, 0x53, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0f, 0x63, 0x75, 0x70, 0x73, 0x52, 0x65, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x18, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, + 0x40, 0x18, 0x01, 0x52, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x46, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x63, 0x79, 0x50, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x19, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x70, + 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x42, 0x10, 0xfa, + 0x42, 0x0d, 0x92, 0x01, 0x0a, 0x08, 0x00, 0x10, 0x08, 0x22, 0x04, 0x72, 0x02, 0x18, 0x40, 0x52, + 0x16, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, + 0x50, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x73, 0x1a, 0xae, 0x02, 0x0a, 0x18, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x73, 0x12, 0xd6, 0x01, 0x0a, 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, + 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, + 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, + 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, - 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x52, 0x03, 0x65, 0x75, 0x69, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x10, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x43, 0x6c, 0x61, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x32, 0xc2, 0x07, 0x0a, 0x17, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x12, 0x6c, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x25, 0x2e, 0x74, 0x74, 0x6e, 0x2e, - 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, - 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, - 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, - 0x2a, 0x22, 0x0b, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x91, - 0x01, 0x0a, 0x07, 0x55, 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, - 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, + 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x52, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x75, 0x69, 0x12, 0x39, 0x0a, + 0x13, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x7a, + 0x03, 0x18, 0x80, 0x10, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x42, 0x15, 0x0a, 0x0e, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x03, 0xf8, 0x42, 0x01, 0x22, + 0x8a, 0x01, 0x0a, 0x17, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4d, 0x0a, 0x0b, 0x67, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0a, + 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x73, 0x12, 0x20, 0x0a, 0x07, 0x61, 0x70, + 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x22, 0xe6, 0x01, 0x0a, + 0x1a, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x45, 0x55, 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xc7, 0x01, 0x0a, 0x03, + 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, + 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, + 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, + 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, + 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x52, 0x03, 0x65, 0x75, 0x69, 0x22, 0x94, 0x02, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x55, 0x49, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xc7, 0x01, 0x0a, 0x03, 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, + 0x44, 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, + 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, + 0x08, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, + 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, + 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, + 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, + 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, + 0x73, 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x03, 0x65, 0x75, 0x69, 0x12, + 0x2b, 0x0a, 0x11, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5f, 0x63, 0x6c, 0x61, 0x69, + 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x73, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x32, 0xc2, 0x07, 0x0a, + 0x17, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x6c, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x12, 0x25, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, + 0x76, 0x33, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x22, 0x16, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x65, 0x64, 0x63, 0x73, + 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x91, 0x01, 0x0a, 0x07, 0x55, 0x6e, 0x63, 0x6c, 0x61, + 0x69, 0x6d, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x48, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x42, 0x2a, 0x40, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, + 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x7b, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x82, 0x01, 0x0a, 0x10, 0x47, + 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x12, + 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, + 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, + 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, + 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, + 0xa8, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x61, + 0x69, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x48, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x42, 0x12, 0x40, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, + 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x7b, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0xa5, 0x01, 0x0a, 0x14, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, + 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x41, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x48, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x42, - 0x2a, 0x40, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x7b, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x7d, 0x12, 0x82, 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, - 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x12, 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, + 0x3a, 0x01, 0x2a, 0x22, 0x3d, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, + 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x35, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x2a, 0x2d, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x1a, 0x3b, 0x92, 0x41, 0x38, 0x12, 0x36, 0x43, 0x6c, 0x61, 0x69, 0x6d, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x65, 0x6e, 0x64, 0x20, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x20, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, + 0x2e, 0x32, 0xa3, 0x03, 0x0a, 0x1c, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x12, 0xac, 0x01, 0x0a, 0x07, 0x55, 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x2d, + 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x45, 0x6e, 0x64, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, + 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x55, 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x45, 0x6e, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x3c, 0x2a, 0x3a, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, + 0x69, 0x6d, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x73, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x12, 0x8b, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, + 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x73, 0x12, 0x28, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x28, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, - 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, - 0x55, 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, - 0x69, 0x6d, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0xa8, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, - 0x6c, 0x61, 0x69, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24, 0x2e, 0x74, 0x74, 0x6e, - 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, - 0x1a, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, - 0x33, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x48, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x42, - 0x12, 0x40, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x7b, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x61, - 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x7d, 0x12, 0xa5, 0x01, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x2e, 0x74, 0x74, - 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x48, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x42, 0x3a, 0x01, 0x2a, 0x22, 0x3d, 0x2f, 0x65, 0x64, - 0x63, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, - 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, - 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, - 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x8f, 0x01, 0x0a, 0x16, 0x55, - 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, - 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x2a, 0x2d, 0x2f, - 0x65, 0x64, 0x63, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x7d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x1a, 0x3b, 0x92, 0x41, - 0x38, 0x12, 0x36, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, - 0x6f, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x4a, 0x6f, 0x69, 0x6e, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x32, 0xa3, 0x03, 0x0a, 0x1c, 0x45, 0x6e, - 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6c, 0x61, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0xac, 0x01, 0x0a, 0x07, 0x55, - 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x2d, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, - 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x6e, 0x63, - 0x6c, 0x61, 0x69, 0x6d, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, - 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x6e, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3c, 0x2a, 0x3a, 0x2f, - 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x7b, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x2e, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x12, 0x8b, 0x01, 0x0a, 0x11, 0x47, 0x65, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x73, 0x12, - 0x28, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, - 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, - 0x49, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x74, 0x74, 0x6e, 0x2e, - 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, - 0x16, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x69, 0x6e, 0x66, - 0x6f, 0x2f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x1a, 0x46, 0x92, 0x41, 0x43, 0x12, 0x41, 0x43, 0x6c, - 0x61, 0x69, 0x6d, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x62, - 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x20, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x32, - 0xcc, 0x04, 0x0a, 0x15, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x61, 0x69, 0x6d, - 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x68, 0x0a, 0x05, 0x43, 0x6c, 0x61, - 0x69, 0x6d, 0x12, 0x23, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, - 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, - 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x22, 0x16, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x12, 0x91, 0x01, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, - 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3c, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x36, 0x3a, 0x01, 0x2a, 0x22, 0x31, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x67, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, - 0x73, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x7f, 0x0a, 0x12, 0x55, 0x6e, 0x61, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x22, 0x2e, - 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x27, 0x2a, 0x25, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x12, 0x8b, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, - 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x55, 0x49, - 0x12, 0x2a, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, - 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x45, 0x55, 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x74, + 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, 0x45, 0x55, 0x49, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x29, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, + 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x4a, 0x6f, 0x69, 0x6e, + 0x45, 0x55, 0x49, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, 0x16, 0x2f, 0x65, 0x64, 0x63, 0x73, 0x2f, 0x63, + 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x1a, + 0x46, 0x92, 0x41, 0x43, 0x12, 0x41, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x62, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x6f, 0x6e, + 0x20, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2e, 0x32, 0xbc, 0x05, 0x0a, 0x15, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x12, 0x68, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x23, 0x2e, 0x74, 0x74, 0x6e, + 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, + 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x73, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, + 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x67, 0x0a, 0x07, 0x55, + 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, + 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x67, 0x63, 0x6c, + 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x8b, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x55, 0x49, 0x12, 0x2a, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x55, - 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, - 0x6d, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x1a, 0x26, 0x92, 0x41, 0x23, 0x12, 0x21, 0x43, 0x6c, 0x61, - 0x69, 0x6d, 0x20, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x2e, 0x42, 0x31, - 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, 0x70, - 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x42, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x45, 0x55, 0x49, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, + 0x22, 0x10, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x2f, 0x69, 0x6e, + 0x66, 0x6f, 0x12, 0x94, 0x01, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, + 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x36, + 0x3a, 0x01, 0x2a, 0x22, 0x31, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x73, + 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x88, 0x02, 0x01, 0x12, 0x82, 0x01, 0x0a, 0x12, 0x55, 0x6e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x12, 0x22, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x30, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x27, 0x2a, 0x25, 0x2f, 0x67, 0x63, 0x6c, 0x73, 0x2f, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, + 0x7d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x88, 0x02, 0x01, 0x1a, 0x26, + 0x92, 0x41, 0x23, 0x12, 0x21, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x20, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x63, + 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x2e, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, + 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, + 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -1772,23 +1787,25 @@ var file_ttn_lorawan_v3_deviceclaimingserver_proto_depIdxs = []int32{ 7, // 23: ttn.lorawan.v3.EndDeviceBatchClaimingServer.Unclaim:input_type -> ttn.lorawan.v3.BatchUnclaimEndDevicesRequest 4, // 24: ttn.lorawan.v3.EndDeviceBatchClaimingServer.GetInfoByJoinEUIs:input_type -> ttn.lorawan.v3.GetInfoByJoinEUIsRequest 10, // 25: ttn.lorawan.v3.GatewayClaimingServer.Claim:input_type -> ttn.lorawan.v3.ClaimGatewayRequest - 11, // 26: ttn.lorawan.v3.GatewayClaimingServer.AuthorizeGateway:input_type -> ttn.lorawan.v3.AuthorizeGatewayRequest - 22, // 27: ttn.lorawan.v3.GatewayClaimingServer.UnauthorizeGateway:input_type -> ttn.lorawan.v3.GatewayIdentifiers - 12, // 28: ttn.lorawan.v3.GatewayClaimingServer.GetInfoByGatewayEUI:input_type -> ttn.lorawan.v3.GetInfoByGatewayEUIRequest - 20, // 29: ttn.lorawan.v3.EndDeviceClaimingServer.Claim:output_type -> ttn.lorawan.v3.EndDeviceIdentifiers - 25, // 30: ttn.lorawan.v3.EndDeviceClaimingServer.Unclaim:output_type -> google.protobuf.Empty - 3, // 31: ttn.lorawan.v3.EndDeviceClaimingServer.GetInfoByJoinEUI:output_type -> ttn.lorawan.v3.GetInfoByJoinEUIResponse - 6, // 32: ttn.lorawan.v3.EndDeviceClaimingServer.GetClaimStatus:output_type -> ttn.lorawan.v3.GetClaimStatusResponse - 25, // 33: ttn.lorawan.v3.EndDeviceClaimingServer.AuthorizeApplication:output_type -> google.protobuf.Empty - 25, // 34: ttn.lorawan.v3.EndDeviceClaimingServer.UnauthorizeApplication:output_type -> google.protobuf.Empty - 8, // 35: ttn.lorawan.v3.EndDeviceBatchClaimingServer.Unclaim:output_type -> ttn.lorawan.v3.BatchUnclaimEndDevicesResponse - 5, // 36: ttn.lorawan.v3.EndDeviceBatchClaimingServer.GetInfoByJoinEUIs:output_type -> ttn.lorawan.v3.GetInfoByJoinEUIsResponse - 22, // 37: ttn.lorawan.v3.GatewayClaimingServer.Claim:output_type -> ttn.lorawan.v3.GatewayIdentifiers - 25, // 38: ttn.lorawan.v3.GatewayClaimingServer.AuthorizeGateway:output_type -> google.protobuf.Empty - 25, // 39: ttn.lorawan.v3.GatewayClaimingServer.UnauthorizeGateway:output_type -> google.protobuf.Empty + 22, // 26: ttn.lorawan.v3.GatewayClaimingServer.Unclaim:input_type -> ttn.lorawan.v3.GatewayIdentifiers + 12, // 27: ttn.lorawan.v3.GatewayClaimingServer.GetInfoByGatewayEUI:input_type -> ttn.lorawan.v3.GetInfoByGatewayEUIRequest + 11, // 28: ttn.lorawan.v3.GatewayClaimingServer.AuthorizeGateway:input_type -> ttn.lorawan.v3.AuthorizeGatewayRequest + 22, // 29: ttn.lorawan.v3.GatewayClaimingServer.UnauthorizeGateway:input_type -> ttn.lorawan.v3.GatewayIdentifiers + 20, // 30: ttn.lorawan.v3.EndDeviceClaimingServer.Claim:output_type -> ttn.lorawan.v3.EndDeviceIdentifiers + 25, // 31: ttn.lorawan.v3.EndDeviceClaimingServer.Unclaim:output_type -> google.protobuf.Empty + 3, // 32: ttn.lorawan.v3.EndDeviceClaimingServer.GetInfoByJoinEUI:output_type -> ttn.lorawan.v3.GetInfoByJoinEUIResponse + 6, // 33: ttn.lorawan.v3.EndDeviceClaimingServer.GetClaimStatus:output_type -> ttn.lorawan.v3.GetClaimStatusResponse + 25, // 34: ttn.lorawan.v3.EndDeviceClaimingServer.AuthorizeApplication:output_type -> google.protobuf.Empty + 25, // 35: ttn.lorawan.v3.EndDeviceClaimingServer.UnauthorizeApplication:output_type -> google.protobuf.Empty + 8, // 36: ttn.lorawan.v3.EndDeviceBatchClaimingServer.Unclaim:output_type -> ttn.lorawan.v3.BatchUnclaimEndDevicesResponse + 5, // 37: ttn.lorawan.v3.EndDeviceBatchClaimingServer.GetInfoByJoinEUIs:output_type -> ttn.lorawan.v3.GetInfoByJoinEUIsResponse + 22, // 38: ttn.lorawan.v3.GatewayClaimingServer.Claim:output_type -> ttn.lorawan.v3.GatewayIdentifiers + 25, // 39: ttn.lorawan.v3.GatewayClaimingServer.Unclaim:output_type -> google.protobuf.Empty 13, // 40: ttn.lorawan.v3.GatewayClaimingServer.GetInfoByGatewayEUI:output_type -> ttn.lorawan.v3.GetInfoByGatewayEUIResponse - 29, // [29:41] is the sub-list for method output_type - 17, // [17:29] is the sub-list for method input_type + 25, // 41: ttn.lorawan.v3.GatewayClaimingServer.AuthorizeGateway:output_type -> google.protobuf.Empty + 25, // 42: ttn.lorawan.v3.GatewayClaimingServer.UnauthorizeGateway:output_type -> google.protobuf.Empty + 30, // [30:43] is the sub-list for method output_type + 17, // [17:30] 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 diff --git a/pkg/ttnpb/deviceclaimingserver.pb.gw.go b/pkg/ttnpb/deviceclaimingserver.pb.gw.go index ef61197718..de96966474 100644 --- a/pkg/ttnpb/deviceclaimingserver.pb.gw.go +++ b/pkg/ttnpb/deviceclaimingserver.pb.gw.go @@ -497,6 +497,102 @@ func local_request_GatewayClaimingServer_Claim_0(ctx context.Context, marshaler } +var ( + filter_GatewayClaimingServer_Unclaim_0 = &utilities.DoubleArray{Encoding: map[string]int{"gateway_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_GatewayClaimingServer_Unclaim_0(ctx context.Context, marshaler runtime.Marshaler, client GatewayClaimingServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GatewayIdentifiers + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["gateway_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "gateway_id") + } + + protoReq.GatewayId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "gateway_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GatewayClaimingServer_Unclaim_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Unclaim(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GatewayClaimingServer_Unclaim_0(ctx context.Context, marshaler runtime.Marshaler, server GatewayClaimingServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GatewayIdentifiers + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["gateway_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "gateway_id") + } + + protoReq.GatewayId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "gateway_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GatewayClaimingServer_Unclaim_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Unclaim(ctx, &protoReq) + return msg, metadata, err + +} + +func request_GatewayClaimingServer_GetInfoByGatewayEUI_0(ctx context.Context, marshaler runtime.Marshaler, client GatewayClaimingServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoByGatewayEUIRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.GetInfoByGatewayEUI(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_GatewayClaimingServer_GetInfoByGatewayEUI_0(ctx context.Context, marshaler runtime.Marshaler, server GatewayClaimingServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoByGatewayEUIRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.GetInfoByGatewayEUI(ctx, &protoReq) + return msg, metadata, err + +} + func request_GatewayClaimingServer_AuthorizeGateway_0(ctx context.Context, marshaler runtime.Marshaler, client GatewayClaimingServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq AuthorizeGatewayRequest var metadata runtime.ServerMetadata @@ -627,32 +723,6 @@ func local_request_GatewayClaimingServer_UnauthorizeGateway_0(ctx context.Contex } -func request_GatewayClaimingServer_GetInfoByGatewayEUI_0(ctx context.Context, marshaler runtime.Marshaler, client GatewayClaimingServerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetInfoByGatewayEUIRequest - var metadata runtime.ServerMetadata - - if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := client.GetInfoByGatewayEUI(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_GatewayClaimingServer_GetInfoByGatewayEUI_0(ctx context.Context, marshaler runtime.Marshaler, server GatewayClaimingServerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetInfoByGatewayEUIRequest - var metadata runtime.ServerMetadata - - if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - msg, err := server.GetInfoByGatewayEUI(ctx, &protoReq) - return msg, metadata, err - -} - // RegisterEndDeviceClaimingServerHandlerServer registers the http handlers for service EndDeviceClaimingServer to "mux". // UnaryRPC :call EndDeviceClaimingServerServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -902,7 +972,7 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim }) - mux.Handle("POST", pattern_GatewayClaimingServer_AuthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("DELETE", pattern_GatewayClaimingServer_Unclaim_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -910,12 +980,12 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/AuthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_ids.gateway_id}/authorize")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/Unclaim", runtime.WithHTTPPathPattern("/gcls/claim/{gateway_id}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GatewayClaimingServer_Unclaim_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -923,11 +993,11 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim return } - forward_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GatewayClaimingServer_Unclaim_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("DELETE", pattern_GatewayClaimingServer_UnauthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_GatewayClaimingServer_GetInfoByGatewayEUI_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -935,12 +1005,12 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/UnauthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_id}/authorize")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/GetInfoByGatewayEUI", runtime.WithHTTPPathPattern("/gcls/claim/info")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -948,11 +1018,11 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim return } - forward_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_GatewayClaimingServer_GetInfoByGatewayEUI_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_GatewayClaimingServer_AuthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -960,12 +1030,12 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/GetInfoByGatewayEUI", runtime.WithHTTPPathPattern("/gcls/claim/info")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/AuthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_ids.gateway_id}/authorize")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -973,7 +1043,32 @@ func RegisterGatewayClaimingServerHandlerServer(ctx context.Context, mux *runtim return } - forward_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_GatewayClaimingServer_UnauthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/UnauthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_id}/authorize")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1338,69 +1433,91 @@ func RegisterGatewayClaimingServerHandlerClient(ctx context.Context, mux *runtim }) - mux.Handle("POST", pattern_GatewayClaimingServer_AuthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("DELETE", pattern_GatewayClaimingServer_Unclaim_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/AuthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_ids.gateway_id}/authorize")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/Unclaim", runtime.WithHTTPPathPattern("/gcls/claim/{gateway_id}")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_GatewayClaimingServer_Unclaim_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GatewayClaimingServer_Unclaim_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("DELETE", pattern_GatewayClaimingServer_UnauthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_GatewayClaimingServer_GetInfoByGatewayEUI_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/UnauthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_id}/authorize")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/GetInfoByGatewayEUI", runtime.WithHTTPPathPattern("/gcls/claim/info")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_GatewayClaimingServer_GetInfoByGatewayEUI_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_GatewayClaimingServer_AuthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/GetInfoByGatewayEUI", runtime.WithHTTPPathPattern("/gcls/claim/info")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/AuthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_ids.gateway_id}/authorize")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GatewayClaimingServer_GetInfoByGatewayEUI_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GatewayClaimingServer_AuthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_GatewayClaimingServer_UnauthorizeGateway_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ttn.lorawan.v3.GatewayClaimingServer/UnauthorizeGateway", runtime.WithHTTPPathPattern("/gcls/gateways/{gateway_id}/authorize")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_GatewayClaimingServer_UnauthorizeGateway_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1410,19 +1527,23 @@ func RegisterGatewayClaimingServerHandlerClient(ctx context.Context, mux *runtim var ( pattern_GatewayClaimingServer_Claim_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"gcls", "claim"}, "")) + pattern_GatewayClaimingServer_Unclaim_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"gcls", "claim", "gateway_id"}, "")) + + pattern_GatewayClaimingServer_GetInfoByGatewayEUI_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"gcls", "claim", "info"}, "")) + pattern_GatewayClaimingServer_AuthorizeGateway_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"gcls", "gateways", "gateway_ids.gateway_id", "authorize"}, "")) pattern_GatewayClaimingServer_UnauthorizeGateway_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"gcls", "gateways", "gateway_id", "authorize"}, "")) - - pattern_GatewayClaimingServer_GetInfoByGatewayEUI_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"gcls", "claim", "info"}, "")) ) var ( forward_GatewayClaimingServer_Claim_0 = runtime.ForwardResponseMessage + forward_GatewayClaimingServer_Unclaim_0 = runtime.ForwardResponseMessage + + forward_GatewayClaimingServer_GetInfoByGatewayEUI_0 = runtime.ForwardResponseMessage + forward_GatewayClaimingServer_AuthorizeGateway_0 = runtime.ForwardResponseMessage forward_GatewayClaimingServer_UnauthorizeGateway_0 = runtime.ForwardResponseMessage - - forward_GatewayClaimingServer_GetInfoByGatewayEUI_0 = runtime.ForwardResponseMessage ) diff --git a/pkg/ttnpb/deviceclaimingserver_grpc.pb.go b/pkg/ttnpb/deviceclaimingserver_grpc.pb.go index 90f8f37d85..145a5f750b 100644 --- a/pkg/ttnpb/deviceclaimingserver_grpc.pb.go +++ b/pkg/ttnpb/deviceclaimingserver_grpc.pb.go @@ -479,9 +479,10 @@ var EndDeviceBatchClaimingServer_ServiceDesc = grpc.ServiceDesc{ const ( GatewayClaimingServer_Claim_FullMethodName = "/ttn.lorawan.v3.GatewayClaimingServer/Claim" + GatewayClaimingServer_Unclaim_FullMethodName = "/ttn.lorawan.v3.GatewayClaimingServer/Unclaim" + GatewayClaimingServer_GetInfoByGatewayEUI_FullMethodName = "/ttn.lorawan.v3.GatewayClaimingServer/GetInfoByGatewayEUI" GatewayClaimingServer_AuthorizeGateway_FullMethodName = "/ttn.lorawan.v3.GatewayClaimingServer/AuthorizeGateway" GatewayClaimingServer_UnauthorizeGateway_FullMethodName = "/ttn.lorawan.v3.GatewayClaimingServer/UnauthorizeGateway" - GatewayClaimingServer_GetInfoByGatewayEUI_FullMethodName = "/ttn.lorawan.v3.GatewayClaimingServer/GetInfoByGatewayEUI" ) // GatewayClaimingServerClient is the client API for GatewayClaimingServer service. @@ -490,12 +491,21 @@ const ( type GatewayClaimingServerClient interface { // Claims a gateway by claim authentication code or QR code and transfers the gateway to the target user. Claim(ctx context.Context, in *ClaimGatewayRequest, opts ...grpc.CallOption) (*GatewayIdentifiers, error) + // Unclaims the gateway. + // EUI provided in the request are ignored and the end device is looked up by the gateway ID. + Unclaim(ctx context.Context, in *GatewayIdentifiers, opts ...grpc.CallOption) (*emptypb.Empty, error) + // Return whether claiming is available for a given gateway EUI. + GetInfoByGatewayEUI(ctx context.Context, in *GetInfoByGatewayEUIRequest, opts ...grpc.CallOption) (*GetInfoByGatewayEUIResponse, error) + // Deprecated: Do not use. // AuthorizeGateway allows a gateway to be claimed. + // DEPRECATED: Authorizing gateways for claiming is no longer supported and will be removed in a future version of The + // Things Stack. AuthorizeGateway(ctx context.Context, in *AuthorizeGatewayRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // Deprecated: Do not use. // UnauthorizeGateway prevents a gateway from being claimed. + // DEPRECATED: Unauthorizing (locking) gateways for claiming is no longer supported and will be removed in a future + // version of The Things Stack. UnauthorizeGateway(ctx context.Context, in *GatewayIdentifiers, opts ...grpc.CallOption) (*emptypb.Empty, error) - // Return whether claiming is available for a given gateway EUI. - GetInfoByGatewayEUI(ctx context.Context, in *GetInfoByGatewayEUIRequest, opts ...grpc.CallOption) (*GetInfoByGatewayEUIResponse, error) } type gatewayClaimingServerClient struct { @@ -515,27 +525,38 @@ func (c *gatewayClaimingServerClient) Claim(ctx context.Context, in *ClaimGatewa return out, nil } -func (c *gatewayClaimingServerClient) AuthorizeGateway(ctx context.Context, in *AuthorizeGatewayRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *gatewayClaimingServerClient) Unclaim(ctx context.Context, in *GatewayIdentifiers, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, GatewayClaimingServer_AuthorizeGateway_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, GatewayClaimingServer_Unclaim_FullMethodName, in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *gatewayClaimingServerClient) UnauthorizeGateway(ctx context.Context, in *GatewayIdentifiers, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *gatewayClaimingServerClient) GetInfoByGatewayEUI(ctx context.Context, in *GetInfoByGatewayEUIRequest, opts ...grpc.CallOption) (*GetInfoByGatewayEUIResponse, error) { + out := new(GetInfoByGatewayEUIResponse) + err := c.cc.Invoke(ctx, GatewayClaimingServer_GetInfoByGatewayEUI_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Deprecated: Do not use. +func (c *gatewayClaimingServerClient) AuthorizeGateway(ctx context.Context, in *AuthorizeGatewayRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, GatewayClaimingServer_UnauthorizeGateway_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, GatewayClaimingServer_AuthorizeGateway_FullMethodName, in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *gatewayClaimingServerClient) GetInfoByGatewayEUI(ctx context.Context, in *GetInfoByGatewayEUIRequest, opts ...grpc.CallOption) (*GetInfoByGatewayEUIResponse, error) { - out := new(GetInfoByGatewayEUIResponse) - err := c.cc.Invoke(ctx, GatewayClaimingServer_GetInfoByGatewayEUI_FullMethodName, in, out, opts...) +// Deprecated: Do not use. +func (c *gatewayClaimingServerClient) UnauthorizeGateway(ctx context.Context, in *GatewayIdentifiers, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, GatewayClaimingServer_UnauthorizeGateway_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -548,12 +569,21 @@ func (c *gatewayClaimingServerClient) GetInfoByGatewayEUI(ctx context.Context, i type GatewayClaimingServerServer interface { // Claims a gateway by claim authentication code or QR code and transfers the gateway to the target user. Claim(context.Context, *ClaimGatewayRequest) (*GatewayIdentifiers, error) + // Unclaims the gateway. + // EUI provided in the request are ignored and the end device is looked up by the gateway ID. + Unclaim(context.Context, *GatewayIdentifiers) (*emptypb.Empty, error) + // Return whether claiming is available for a given gateway EUI. + GetInfoByGatewayEUI(context.Context, *GetInfoByGatewayEUIRequest) (*GetInfoByGatewayEUIResponse, error) + // Deprecated: Do not use. // AuthorizeGateway allows a gateway to be claimed. + // DEPRECATED: Authorizing gateways for claiming is no longer supported and will be removed in a future version of The + // Things Stack. AuthorizeGateway(context.Context, *AuthorizeGatewayRequest) (*emptypb.Empty, error) + // Deprecated: Do not use. // UnauthorizeGateway prevents a gateway from being claimed. + // DEPRECATED: Unauthorizing (locking) gateways for claiming is no longer supported and will be removed in a future + // version of The Things Stack. UnauthorizeGateway(context.Context, *GatewayIdentifiers) (*emptypb.Empty, error) - // Return whether claiming is available for a given gateway EUI. - GetInfoByGatewayEUI(context.Context, *GetInfoByGatewayEUIRequest) (*GetInfoByGatewayEUIResponse, error) mustEmbedUnimplementedGatewayClaimingServerServer() } @@ -564,15 +594,18 @@ type UnimplementedGatewayClaimingServerServer struct { func (UnimplementedGatewayClaimingServerServer) Claim(context.Context, *ClaimGatewayRequest) (*GatewayIdentifiers, error) { return nil, status.Errorf(codes.Unimplemented, "method Claim not implemented") } +func (UnimplementedGatewayClaimingServerServer) Unclaim(context.Context, *GatewayIdentifiers) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Unclaim not implemented") +} +func (UnimplementedGatewayClaimingServerServer) GetInfoByGatewayEUI(context.Context, *GetInfoByGatewayEUIRequest) (*GetInfoByGatewayEUIResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInfoByGatewayEUI not implemented") +} func (UnimplementedGatewayClaimingServerServer) AuthorizeGateway(context.Context, *AuthorizeGatewayRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method AuthorizeGateway not implemented") } func (UnimplementedGatewayClaimingServerServer) UnauthorizeGateway(context.Context, *GatewayIdentifiers) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UnauthorizeGateway not implemented") } -func (UnimplementedGatewayClaimingServerServer) GetInfoByGatewayEUI(context.Context, *GetInfoByGatewayEUIRequest) (*GetInfoByGatewayEUIResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetInfoByGatewayEUI not implemented") -} func (UnimplementedGatewayClaimingServerServer) mustEmbedUnimplementedGatewayClaimingServerServer() {} // UnsafeGatewayClaimingServerServer may be embedded to opt out of forward compatibility for this service. @@ -604,56 +637,74 @@ func _GatewayClaimingServer_Claim_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _GatewayClaimingServer_AuthorizeGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AuthorizeGatewayRequest) +func _GatewayClaimingServer_Unclaim_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GatewayIdentifiers) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(GatewayClaimingServerServer).AuthorizeGateway(ctx, in) + return srv.(GatewayClaimingServerServer).Unclaim(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: GatewayClaimingServer_AuthorizeGateway_FullMethodName, + FullMethod: GatewayClaimingServer_Unclaim_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GatewayClaimingServerServer).AuthorizeGateway(ctx, req.(*AuthorizeGatewayRequest)) + return srv.(GatewayClaimingServerServer).Unclaim(ctx, req.(*GatewayIdentifiers)) } return interceptor(ctx, in, info, handler) } -func _GatewayClaimingServer_UnauthorizeGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GatewayIdentifiers) +func _GatewayClaimingServer_GetInfoByGatewayEUI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInfoByGatewayEUIRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(GatewayClaimingServerServer).UnauthorizeGateway(ctx, in) + return srv.(GatewayClaimingServerServer).GetInfoByGatewayEUI(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: GatewayClaimingServer_UnauthorizeGateway_FullMethodName, + FullMethod: GatewayClaimingServer_GetInfoByGatewayEUI_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GatewayClaimingServerServer).UnauthorizeGateway(ctx, req.(*GatewayIdentifiers)) + return srv.(GatewayClaimingServerServer).GetInfoByGatewayEUI(ctx, req.(*GetInfoByGatewayEUIRequest)) } return interceptor(ctx, in, info, handler) } -func _GatewayClaimingServer_GetInfoByGatewayEUI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetInfoByGatewayEUIRequest) +func _GatewayClaimingServer_AuthorizeGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthorizeGatewayRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(GatewayClaimingServerServer).GetInfoByGatewayEUI(ctx, in) + return srv.(GatewayClaimingServerServer).AuthorizeGateway(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: GatewayClaimingServer_GetInfoByGatewayEUI_FullMethodName, + FullMethod: GatewayClaimingServer_AuthorizeGateway_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GatewayClaimingServerServer).GetInfoByGatewayEUI(ctx, req.(*GetInfoByGatewayEUIRequest)) + return srv.(GatewayClaimingServerServer).AuthorizeGateway(ctx, req.(*AuthorizeGatewayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GatewayClaimingServer_UnauthorizeGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GatewayIdentifiers) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GatewayClaimingServerServer).UnauthorizeGateway(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GatewayClaimingServer_UnauthorizeGateway_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GatewayClaimingServerServer).UnauthorizeGateway(ctx, req.(*GatewayIdentifiers)) } return interceptor(ctx, in, info, handler) } @@ -669,6 +720,14 @@ var GatewayClaimingServer_ServiceDesc = grpc.ServiceDesc{ MethodName: "Claim", Handler: _GatewayClaimingServer_Claim_Handler, }, + { + MethodName: "Unclaim", + Handler: _GatewayClaimingServer_Unclaim_Handler, + }, + { + MethodName: "GetInfoByGatewayEUI", + Handler: _GatewayClaimingServer_GetInfoByGatewayEUI_Handler, + }, { MethodName: "AuthorizeGateway", Handler: _GatewayClaimingServer_AuthorizeGateway_Handler, @@ -677,10 +736,6 @@ var GatewayClaimingServer_ServiceDesc = grpc.ServiceDesc{ MethodName: "UnauthorizeGateway", Handler: _GatewayClaimingServer_UnauthorizeGateway_Handler, }, - { - MethodName: "GetInfoByGatewayEUI", - Handler: _GatewayClaimingServer_GetInfoByGatewayEUI_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "ttn/lorawan/v3/deviceclaimingserver.proto", diff --git a/pkg/webui/locales/ja.json b/pkg/webui/locales/ja.json index fe17949bc5..5864dc5c8e 100644 --- a/pkg/webui/locales/ja.json +++ b/pkg/webui/locales/ja.json @@ -1944,11 +1944,19 @@ "error:pkg/deviceclaimingserver/enddevices/ttjsv2:internal_error": "内部エラー", "error:pkg/deviceclaimingserver/enddevices/ttjsv2:unclaim_device": "EUI `{dev_eui}`のデバイスの主張を取り消す", "error:pkg/deviceclaimingserver/enddevices/ttjsv2:unclaim_devices": "デバイスの主張を取り消す", + "error:pkg/deviceclaimingserver/gateways/ttgc:not_implemented": "", + "error:pkg/deviceclaimingserver/gateways:invalid_upstream": "", + "error:pkg/deviceclaimingserver:claim gateway": "", "error:pkg/deviceclaimingserver:claiming_not_supported": "JoinEUI `{eui}` に対してクレームはサポートされていません", + "error:pkg/deviceclaimingserver:create_gateway": "", "error:pkg/deviceclaimingserver:device_not_found": "デバイスが見つかりません", - "error:pkg/deviceclaimingserver:method_unavailable": "メソッドが利用できません", + "error:pkg/deviceclaimingserver:gateway_already_exists": "", + "error:pkg/deviceclaimingserver:gateway_claiming_not_supported": "", + "error:pkg/deviceclaimingserver:gateway_claiming_with_qrcodes_not_implemented": "", "error:pkg/deviceclaimingserver:no_devices_found": "デバイスレジストリにバッチ内のデバイスが見つかりません", + "error:pkg/deviceclaimingserver:no_eui": "", "error:pkg/deviceclaimingserver:no_euis": "デバイスにDevEUI/JoinEUIが設定されていません", + "error:pkg/deviceclaimingserver:no_gateway_server_address": "", "error:pkg/deviceclaimingserver:no_join_eui": "リクエストからJoinEUIを抽出", "error:pkg/deviceclaimingserver:parse_qr_code": "QRコードの解析に失敗しました", "error:pkg/deviceclaimingserver:qr_code_data": "無効なQRコードデータ", @@ -2636,6 +2644,16 @@ "event:client.purge": "クライアントをパージする", "event:client.restore": "OAuthクライアントを復元する", "event:client.update": "OAuthクライアントの更新", + "event:dcs.end_device.claim.abort": "", + "event:dcs.end_device.claim.fail": "", + "event:dcs.end_device.claim.success": "", + "event:dcs.end_device.unclaim.fail": "", + "event:dcs.end_device.unclaim.success": "", + "event:dcs.gateway.claim.abort": "", + "event:dcs.gateway.claim.fail": "", + "event:dcs.gateway.claim.success": "", + "event:dcs.gateway.unclaim.fail": "", + "event:dcs.gateway.unclaim.success": "", "event:end_device.batch.delete": "エンドデバイスを一括削除する", "event:end_device.create": "エンドデバイスの作成", "event:end_device.delete": "エンドデバイスの削除", diff --git a/sdk/js/generated/api-definition.json b/sdk/js/generated/api-definition.json index ee96d3416d..1377e6e514 100644 --- a/sdk/js/generated/api-definition.json +++ b/sdk/js/generated/api-definition.json @@ -2280,6 +2280,29 @@ } ] }, + "Unclaim": { + "file": "ttn/lorawan/v3/deviceclaimingserver.proto", + "http": [ + { + "method": "delete", + "pattern": "/gcls/claim/{gateway_id}", + "parameters": [ + "gateway_id" + ] + } + ] + }, + "GetInfoByGatewayEUI": { + "file": "ttn/lorawan/v3/deviceclaimingserver.proto", + "http": [ + { + "method": "post", + "pattern": "/gcls/claim/info", + "body": "*", + "parameters": [] + } + ] + }, "AuthorizeGateway": { "file": "ttn/lorawan/v3/deviceclaimingserver.proto", "http": [ @@ -2304,17 +2327,6 @@ ] } ] - }, - "GetInfoByGatewayEUI": { - "file": "ttn/lorawan/v3/deviceclaimingserver.proto", - "http": [ - { - "method": "post", - "pattern": "/gcls/claim/info", - "body": "*", - "parameters": [] - } - ] } }, "DeviceRepository": { diff --git a/sdk/js/generated/api.json b/sdk/js/generated/api.json index 5b47e34eb0..1292adf71f 100644 --- a/sdk/js/generated/api.json +++ b/sdk/js/generated/api.json @@ -10886,7 +10886,7 @@ "name": "CUPSRedirection", "longName": "CUPSRedirection", "fullName": "ttn.lorawan.v3.CUPSRedirection", - "description": "", + "description": "DEPRECATED: This message is deprecated and will be removed in a future version of The Things Stack.", "hasExtensions": false, "hasFields": true, "hasOneofs": true, @@ -10984,7 +10984,10 @@ ] } } - ] + ], + "options": { + "deprecated": true + } }, { "name": "ClientTLS", @@ -11314,7 +11317,7 @@ }, { "name": "cups_redirection", - "description": "Parameters to set CUPS redirection for the gateway.", + "description": "Parameters to set CUPS redirection for the gateway.\nDEPRECATED: This field is deprecated and will be removed in a future version of The Things Stack.", "label": "", "type": "CUPSRedirection", "longType": "CUPSRedirection", @@ -11322,7 +11325,10 @@ "ismap": false, "isoneof": false, "oneofdecl": "", - "defaultValue": "" + "defaultValue": "", + "options": { + "deprecated": true + } }, { "name": "target_frequency_plan_id", @@ -11983,22 +11989,44 @@ } }, { - "name": "AuthorizeGateway", - "description": "AuthorizeGateway allows a gateway to be claimed.", - "requestType": "AuthorizeGatewayRequest", - "requestLongType": "AuthorizeGatewayRequest", - "requestFullType": "ttn.lorawan.v3.AuthorizeGatewayRequest", + "name": "Unclaim", + "description": "Unclaims the gateway.\nEUI provided in the request are ignored and the end device is looked up by the gateway ID.", + "requestType": "GatewayIdentifiers", + "requestLongType": "GatewayIdentifiers", + "requestFullType": "ttn.lorawan.v3.GatewayIdentifiers", "requestStreaming": false, "responseType": "Empty", "responseLongType": ".google.protobuf.Empty", "responseFullType": "google.protobuf.Empty", "responseStreaming": false, + "options": { + "google.api.http": { + "rules": [ + { + "method": "DELETE", + "pattern": "/gcls/claim/{gateway_id}" + } + ] + } + } + }, + { + "name": "GetInfoByGatewayEUI", + "description": "Return whether claiming is available for a given gateway EUI.", + "requestType": "GetInfoByGatewayEUIRequest", + "requestLongType": "GetInfoByGatewayEUIRequest", + "requestFullType": "ttn.lorawan.v3.GetInfoByGatewayEUIRequest", + "requestStreaming": false, + "responseType": "GetInfoByGatewayEUIResponse", + "responseLongType": "GetInfoByGatewayEUIResponse", + "responseFullType": "ttn.lorawan.v3.GetInfoByGatewayEUIResponse", + "responseStreaming": false, "options": { "google.api.http": { "rules": [ { "method": "POST", - "pattern": "/gcls/gateways/{gateway_ids.gateway_id}/authorize", + "pattern": "/gcls/claim/info", "body": "*" } ] @@ -12006,45 +12034,47 @@ } }, { - "name": "UnauthorizeGateway", - "description": "UnauthorizeGateway prevents a gateway from being claimed.", - "requestType": "GatewayIdentifiers", - "requestLongType": "GatewayIdentifiers", - "requestFullType": "ttn.lorawan.v3.GatewayIdentifiers", + "name": "AuthorizeGateway", + "description": "AuthorizeGateway allows a gateway to be claimed.\nDEPRECATED: Authorizing gateways for claiming is no longer supported and will be removed in a future version of The\nThings Stack.", + "requestType": "AuthorizeGatewayRequest", + "requestLongType": "AuthorizeGatewayRequest", + "requestFullType": "ttn.lorawan.v3.AuthorizeGatewayRequest", "requestStreaming": false, "responseType": "Empty", "responseLongType": ".google.protobuf.Empty", "responseFullType": "google.protobuf.Empty", "responseStreaming": false, "options": { + "deprecated": true, "google.api.http": { "rules": [ { - "method": "DELETE", - "pattern": "/gcls/gateways/{gateway_id}/authorize" + "method": "POST", + "pattern": "/gcls/gateways/{gateway_ids.gateway_id}/authorize", + "body": "*" } ] } } }, { - "name": "GetInfoByGatewayEUI", - "description": "Return whether claiming is available for a given gateway EUI.", - "requestType": "GetInfoByGatewayEUIRequest", - "requestLongType": "GetInfoByGatewayEUIRequest", - "requestFullType": "ttn.lorawan.v3.GetInfoByGatewayEUIRequest", + "name": "UnauthorizeGateway", + "description": "UnauthorizeGateway prevents a gateway from being claimed.\nDEPRECATED: Unauthorizing (locking) gateways for claiming is no longer supported and will be removed in a future\nversion of The Things Stack.", + "requestType": "GatewayIdentifiers", + "requestLongType": "GatewayIdentifiers", + "requestFullType": "ttn.lorawan.v3.GatewayIdentifiers", "requestStreaming": false, - "responseType": "GetInfoByGatewayEUIResponse", - "responseLongType": "GetInfoByGatewayEUIResponse", - "responseFullType": "ttn.lorawan.v3.GetInfoByGatewayEUIResponse", + "responseType": "Empty", + "responseLongType": ".google.protobuf.Empty", + "responseFullType": "google.protobuf.Empty", "responseStreaming": false, "options": { + "deprecated": true, "google.api.http": { "rules": [ { - "method": "POST", - "pattern": "/gcls/claim/info", - "body": "*" + "method": "DELETE", + "pattern": "/gcls/gateways/{gateway_id}/authorize" } ] }