Compare commits

..

59 Commits

Author SHA1 Message Date
Orne Brocaar
4b77fa441d Bump version to 4.12.0-test.2
Some checks are pending
CI / tests (postgres) (push) Waiting to run
CI / tests (sqlite) (push) Waiting to run
CI / dist (postgres) (push) Blocked by required conditions
CI / dist (sqlite) (push) Blocked by required conditions
2025-03-20 12:25:23 +00:00
Orne Brocaar
c137136d4d Update chirpstack configfile template.
See also chirpstack/chirpstack-docs#25
2025-03-20 11:49:07 +00:00
dependabot[bot]
27689d172f
Bump vite from 5.3.6 to 5.4.12 in /ui (#604)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.6 to 5.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 11:26:39 +00:00
Orne Brocaar
730ed09840 Update Rust toolchain version.
Some checks are pending
CI / tests (postgres) (push) Waiting to run
CI / tests (sqlite) (push) Waiting to run
CI / dist (postgres) (push) Blocked by required conditions
CI / dist (sqlite) (push) Blocked by required conditions
2025-03-20 10:58:30 +00:00
Orne Brocaar
105ea2806a Remove rand_core and import re-export. 2025-03-20 10:53:14 +00:00
dependabot[bot]
8f34ea2ca5
Bump golang.org/x/net from 0.23.0 to 0.33.0 in /api/go (#599)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 10:41:12 +00:00
dependabot[bot]
447df411df
Bump golang.org/x/net from 0.23.0 to 0.36.0 in /examples/request_log/go (#627)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 10:40:58 +00:00
dependabot[bot]
e228125031
Bump @babel/runtime from 7.24.7 to 7.26.10 in /ui (#633)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.24.7 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 10:40:43 +00:00
Orne Brocaar
7c134a549d Update dependencies. 2025-03-20 10:39:52 +00:00
Jeffrey Esquivel S.
f97af991be
Generalize auto-conversion of SEC1 EC keys to PKCS#8. (#618)
Get the key curve from the params of the SEC1 certificate instead of
assuming that it is P256.
2025-03-20 10:10:57 +00:00
Orne Brocaar
293cfe2664 api: Update Go generated code. 2025-03-20 09:03:47 +00:00
Orne Brocaar
5bbd71ab3a Add warnings to fuota deployment job + UI.
Some checks failed
CI / tests (postgres) (push) Has been cancelled
CI / tests (sqlite) (push) Has been cancelled
CI / dist (postgres) (push) Has been cancelled
CI / dist (sqlite) (push) Has been cancelled
In case some devices do not complete a job, this makes it possible
to show a warning in the UI showing the amount of devices that did
not complete the job.
2025-03-19 14:47:47 +00:00
Orne Brocaar
f02256245c lrwn: Align v2 fragmentation fec with LBM stack.
Some checks are pending
CI / tests (postgres) (push) Waiting to run
CI / tests (sqlite) (push) Waiting to run
CI / dist (postgres) (push) Blocked by required conditions
CI / dist (sqlite) (push) Blocked by required conditions
This aligns the forward-error-correction code with the LoRa Basics
Modem stack, which seems to be different from the TS004 MATLAB example
code in that it only calls the matrix_line function for the redundancy
frames and thus the n argument ranges from 1 until (and including) the
number of redundancy frames.

The TS004 MATLAB example calls the matrix_line function for every
fragment, thus the n argument ranges from 1 until (and including) m +
the number of redundancy frames. While n <= m, it returns early.
2025-03-18 13:32:14 +00:00
Orne Brocaar
a0f07b5303 Initial FUOTA v2 implementation.
This implements selecting the v2.0.0 app-layer package in the
device-profile and handling these payloads in the FUOTA flow.
2025-03-18 12:44:15 +00:00
Orne Brocaar
60547ff973 Fix fuota device timeout filter.
We should filter on error_msg = "" instead of != NULL, as we only would
like to update the error_msg for devices that not yet have an error set.
Else we would overwrite an earlier error message.
2025-03-13 15:09:03 +00:00
Orne Brocaar
351406c363 lrwn: Implement v2 app layer fragmentation structs + encoding. 2025-03-13 15:09:03 +00:00
Orne Brocaar
8b59136942 lrwn: Implement v2 app layer multicast setup structs. 2025-03-13 15:09:03 +00:00
Orne Brocaar
b5e562aa64 lrwn: Implement v2 app layer clock sync structs.
These are the same as the v1 struct, buts re-exporting will make the
documentation confusing + will become inconsistent with other app layer
packages that do provide different struct implementations.
2025-03-13 15:09:03 +00:00
Orne Brocaar
5a7694a3a4 Bump version to 4.12.0-test.1 2025-03-13 15:09:03 +00:00
Orne Brocaar
98ba2f3198 Set device tags after FUOTA complete. 2025-03-13 15:09:03 +00:00
Orne Brocaar
bbdf2dd781 Error if there are no fuota devices + cleanup mc group.
In case there are no fuota devices (e.g. all devices failed the previous
step), this will log a warning and the flow will continue with multicast
cleanup and completion steps.
2025-03-13 15:09:03 +00:00
Orne Brocaar
71cc1aca74 Set FUOTA deployment completed_at. 2025-03-13 15:09:03 +00:00
Orne Brocaar
27f6d2cf03 Implement full FUOTA flow + UI components. 2025-03-13 15:09:03 +00:00
Orne Brocaar
b8ab0182de ui: Make app-layer params configurable. 2025-03-13 15:09:03 +00:00
Orne Brocaar
b1e6c97942 Add get_ and update_device to fuota storage + add return_msg.
The return_msg (text) field can be used to capture errors, e.g. when the
end-device failed to setup the multicast-group.
2025-03-13 15:09:03 +00:00
Orne Brocaar
e75b62f335 lrwn: Add function for encrypting McKey. 2025-03-13 15:09:03 +00:00
Orne Brocaar
cac682c245 Implement handling AppTimeReq / AppTimeAns. 2025-03-13 15:09:03 +00:00
Orne Brocaar
b61a684739 Update fuota + device-keys structs / storage.
This add the gen_app_key to the device keys which is needed for FUOTA
and adds a random multicast address + key to the fuota deployment. To
the FUOTA job structure, this adds a return msg such that errors can
be captured in the database.
2025-03-13 15:09:03 +00:00
Orne Brocaar
439a6b0542 lrwn: Fix clocksync time_correction type.
The correct type is i32 instead of u32, as the value can be negative.
2025-03-13 15:09:03 +00:00
Orne Brocaar
f9efed2317 Rename ts00x_port to _f_port.
This is consistent with the naming in the lrwn package.
2025-03-13 15:09:03 +00:00
Orne Brocaar
4984e8556d ui: First part of FUOTA UI implementation.
Currently this allows for creating FUOTA dpeloyments and adding to /
removing from devices and gateways. In its current state, it does not
show the status of the FUOTA deployment.
2025-03-13 15:09:03 +00:00
Orne Brocaar
43753958ef api: List devices by device-profile + expose tags. 2025-03-13 15:09:03 +00:00
Orne Brocaar
1d76fabdb0 Add APIs + functions to get app. device-profiles and tags.
These API methods can be used to given an application id, retrieve
the list of used device-profiles and device tags.
2025-03-13 15:09:03 +00:00
Orne Brocaar
de7e0c619d Update fuota API. Add options for auto-calculation of params.
This adds options to auto-calculate the fragment size (based on max.
payload size available for the given data-rate) and multicast
timeout (based on server settings).
2025-03-13 15:09:03 +00:00
Orne Brocaar
38386b23f2 Add start job + get schedulable jobs functions + API. 2025-03-13 15:09:03 +00:00
Orne Brocaar
a3e27d8b65 Add tests + add fuota jobs functions. 2025-03-13 15:09:03 +00:00
Orne Brocaar
9b735d6521 Add first fuota storage functions / API. 2025-03-13 15:09:03 +00:00
Orne Brocaar
d000cd3385 Add option to filter devices by tags. 2025-03-13 15:09:03 +00:00
Orne Brocaar
ac52cce7ee api: Extend 'limit' field documentation. 2025-03-13 15:09:03 +00:00
Orne Brocaar
bbce25efbf Add app-layer params field to device-profile API. 2025-03-13 15:09:03 +00:00
Orne Brocaar
4e7ab31714 Add app-layer params field to device-profile schema. 2025-03-13 15:09:03 +00:00
Orne Brocaar
3c3c1f125d Refactor device-profile relay fields. 2025-03-13 15:09:03 +00:00
Orne Brocaar
909eaed1ba Refactor device-profile class-c fields. 2025-03-13 15:09:03 +00:00
Orne Brocaar
b8c02b943c Refactor device-profile class-b fields. 2025-03-13 15:09:03 +00:00
Orne Brocaar
82ed66cf09 Refactor device-profile abp fields.
This this puts the ABP parameters into a single JSON(B) field, to reduce
the amount of device-profile fields that currently exist. The same work
will be done for Class-B/C and Relay parameters. Once completed, this
means we can drop the diesel '64-column-tables' feature, which will
reduce compile time.
2025-03-13 15:09:03 +00:00
Orne Brocaar
f3d3262006 lrwn: Implement v1 applayer multicastsetup key functions. 2025-03-13 15:09:03 +00:00
Orne Brocaar
ffe01d387c lrwn: Implement applayer v1 fragmentation encoding func. 2025-03-13 15:09:03 +00:00
Orne Brocaar
d1f4f42a79 lrwn: Implement v1 applayer fragmentation structs. 2025-03-13 15:09:03 +00:00
Orne Brocaar
bf21297a42 lrwn: Replace Duration with u32 in applayer timesync. 2025-03-13 15:09:03 +00:00
Orne Brocaar
bcb8aaad4f lrwn: Implement v1 applayer multicast setup structs. 2025-03-13 15:09:03 +00:00
Orne Brocaar
f43c9154bc lrwn: Implement v1 applayer clock sync structs. 2025-03-13 15:09:03 +00:00
berthrann
3e7f09db62
Add Yandex ID OAuth provider support. (#622)
Some checks failed
CI / tests (postgres) (push) Has been cancelled
CI / tests (sqlite) (push) Has been cancelled
CI / dist (postgres) (push) Has been cancelled
CI / dist (sqlite) (push) Has been cancelled
2025-03-12 13:03:35 +00:00
Tomas Tulka
01246dd124
Add sorting to device and gw table. (#579)
Some checks failed
CI / tests (postgres) (push) Has been cancelled
CI / tests (sqlite) (push) Has been cancelled
CI / dist (postgres) (push) Has been cancelled
CI / dist (sqlite) (push) Has been cancelled
Co-authored-by: Franka Schmid <fra.schmid@rational-online.com>
Co-authored-by: Orne Brocaar <info@brocaar.com>
2025-02-10 12:18:08 +00:00
Orne Brocaar
2fc762d932 Bump version to 4.11.1 2025-02-10 11:41:05 +00:00
Orne Brocaar
24333f8b5d Bump version to 4.11.1-test.2 2025-02-10 11:10:11 +00:00
Orne Brocaar
99239a82d4 Revert from sqlite-interactive to sqlite. 2025-02-10 10:56:48 +00:00
Orne Brocaar
317c1cb14d Update dev-dependencies to shell.nix. 2025-02-10 10:27:22 +00:00
Orne Brocaar
2e0d034a6b Bump version to 4.11.1 2025-02-07 15:56:39 +00:00
Orne Brocaar
fb59f541b1 Fix ns_time + setting of JSON flag.
Some checks failed
CI / tests (postgres) (push) Has been cancelled
CI / tests (sqlite) (push) Has been cancelled
CI / dist (postgres) (push) Has been cancelled
CI / dist (sqlite) (push) Has been cancelled
These lines were accidentally removed by 922a83597f8aaed089f5c6ed610d20c5aaa91102
when removing the metadata keys as part of the region refactor.
2025-02-03 12:46:04 +00:00
175 changed files with 20742 additions and 3402 deletions

1527
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,10 @@ dist:
cd chirpstack && make dist cd chirpstack && make dist
# Install dev dependencies # Install dev dependencies
# TODO: test latest cargo-deb and move it to shell.nix.
dev-dependencies: dev-dependencies:
cargo install cross --version 0.2.5 cargo install cargo-deb --version 1.43.1 --locked
cargo install diesel_cli --version 2.2.1 --no-default-features --features postgres,sqlite cargo install cargo-generate-rpm --version 0.12.1 --locked
cargo install cargo-deb --version 1.43.1
cargo install cargo-generate-rpm --version 0.12.1
# Set the versions # Set the versions
version: version:

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,8 @@ const (
ApplicationService_UpdateIftttIntegration_FullMethodName = "/api.ApplicationService/UpdateIftttIntegration" ApplicationService_UpdateIftttIntegration_FullMethodName = "/api.ApplicationService/UpdateIftttIntegration"
ApplicationService_DeleteIftttIntegration_FullMethodName = "/api.ApplicationService/DeleteIftttIntegration" ApplicationService_DeleteIftttIntegration_FullMethodName = "/api.ApplicationService/DeleteIftttIntegration"
ApplicationService_GenerateMqttIntegrationClientCertificate_FullMethodName = "/api.ApplicationService/GenerateMqttIntegrationClientCertificate" ApplicationService_GenerateMqttIntegrationClientCertificate_FullMethodName = "/api.ApplicationService/GenerateMqttIntegrationClientCertificate"
ApplicationService_ListDeviceProfiles_FullMethodName = "/api.ApplicationService/ListDeviceProfiles"
ApplicationService_ListDeviceTags_FullMethodName = "/api.ApplicationService/ListDeviceTags"
) )
// ApplicationServiceClient is the client API for ApplicationService service. // ApplicationServiceClient is the client API for ApplicationService service.
@ -167,6 +169,10 @@ type ApplicationServiceClient interface {
DeleteIftttIntegration(ctx context.Context, in *DeleteIftttIntegrationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) DeleteIftttIntegration(ctx context.Context, in *DeleteIftttIntegrationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Generates application ID specific client-certificate. // Generates application ID specific client-certificate.
GenerateMqttIntegrationClientCertificate(ctx context.Context, in *GenerateMqttIntegrationClientCertificateRequest, opts ...grpc.CallOption) (*GenerateMqttIntegrationClientCertificateResponse, error) GenerateMqttIntegrationClientCertificate(ctx context.Context, in *GenerateMqttIntegrationClientCertificateRequest, opts ...grpc.CallOption) (*GenerateMqttIntegrationClientCertificateResponse, error)
// List device-profiles used within the given application.
ListDeviceProfiles(ctx context.Context, in *ListApplicationDeviceProfilesRequest, opts ...grpc.CallOption) (*ListApplicationDeviceProfilesResponse, error)
// List device tags used within the given application.
ListDeviceTags(ctx context.Context, in *ListApplicationDeviceTagsRequest, opts ...grpc.CallOption) (*ListApplicationDeviceTagsResponse, error)
} }
type applicationServiceClient struct { type applicationServiceClient struct {
@ -600,6 +606,24 @@ func (c *applicationServiceClient) GenerateMqttIntegrationClientCertificate(ctx
return out, nil return out, nil
} }
func (c *applicationServiceClient) ListDeviceProfiles(ctx context.Context, in *ListApplicationDeviceProfilesRequest, opts ...grpc.CallOption) (*ListApplicationDeviceProfilesResponse, error) {
out := new(ListApplicationDeviceProfilesResponse)
err := c.cc.Invoke(ctx, ApplicationService_ListDeviceProfiles_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *applicationServiceClient) ListDeviceTags(ctx context.Context, in *ListApplicationDeviceTagsRequest, opts ...grpc.CallOption) (*ListApplicationDeviceTagsResponse, error) {
out := new(ListApplicationDeviceTagsResponse)
err := c.cc.Invoke(ctx, ApplicationService_ListDeviceTags_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ApplicationServiceServer is the server API for ApplicationService service. // ApplicationServiceServer is the server API for ApplicationService service.
// All implementations must embed UnimplementedApplicationServiceServer // All implementations must embed UnimplementedApplicationServiceServer
// for forward compatibility // for forward compatibility
@ -698,6 +722,10 @@ type ApplicationServiceServer interface {
DeleteIftttIntegration(context.Context, *DeleteIftttIntegrationRequest) (*emptypb.Empty, error) DeleteIftttIntegration(context.Context, *DeleteIftttIntegrationRequest) (*emptypb.Empty, error)
// Generates application ID specific client-certificate. // Generates application ID specific client-certificate.
GenerateMqttIntegrationClientCertificate(context.Context, *GenerateMqttIntegrationClientCertificateRequest) (*GenerateMqttIntegrationClientCertificateResponse, error) GenerateMqttIntegrationClientCertificate(context.Context, *GenerateMqttIntegrationClientCertificateRequest) (*GenerateMqttIntegrationClientCertificateResponse, error)
// List device-profiles used within the given application.
ListDeviceProfiles(context.Context, *ListApplicationDeviceProfilesRequest) (*ListApplicationDeviceProfilesResponse, error)
// List device tags used within the given application.
ListDeviceTags(context.Context, *ListApplicationDeviceTagsRequest) (*ListApplicationDeviceTagsResponse, error)
mustEmbedUnimplementedApplicationServiceServer() mustEmbedUnimplementedApplicationServiceServer()
} }
@ -846,6 +874,12 @@ func (UnimplementedApplicationServiceServer) DeleteIftttIntegration(context.Cont
func (UnimplementedApplicationServiceServer) GenerateMqttIntegrationClientCertificate(context.Context, *GenerateMqttIntegrationClientCertificateRequest) (*GenerateMqttIntegrationClientCertificateResponse, error) { func (UnimplementedApplicationServiceServer) GenerateMqttIntegrationClientCertificate(context.Context, *GenerateMqttIntegrationClientCertificateRequest) (*GenerateMqttIntegrationClientCertificateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GenerateMqttIntegrationClientCertificate not implemented") return nil, status.Errorf(codes.Unimplemented, "method GenerateMqttIntegrationClientCertificate not implemented")
} }
func (UnimplementedApplicationServiceServer) ListDeviceProfiles(context.Context, *ListApplicationDeviceProfilesRequest) (*ListApplicationDeviceProfilesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListDeviceProfiles not implemented")
}
func (UnimplementedApplicationServiceServer) ListDeviceTags(context.Context, *ListApplicationDeviceTagsRequest) (*ListApplicationDeviceTagsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListDeviceTags not implemented")
}
func (UnimplementedApplicationServiceServer) mustEmbedUnimplementedApplicationServiceServer() {} func (UnimplementedApplicationServiceServer) mustEmbedUnimplementedApplicationServiceServer() {}
// UnsafeApplicationServiceServer may be embedded to opt out of forward compatibility for this service. // UnsafeApplicationServiceServer may be embedded to opt out of forward compatibility for this service.
@ -1705,6 +1739,42 @@ func _ApplicationService_GenerateMqttIntegrationClientCertificate_Handler(srv in
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _ApplicationService_ListDeviceProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListApplicationDeviceProfilesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApplicationServiceServer).ListDeviceProfiles(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ApplicationService_ListDeviceProfiles_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApplicationServiceServer).ListDeviceProfiles(ctx, req.(*ListApplicationDeviceProfilesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ApplicationService_ListDeviceTags_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListApplicationDeviceTagsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApplicationServiceServer).ListDeviceTags(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ApplicationService_ListDeviceTags_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApplicationServiceServer).ListDeviceTags(ctx, req.(*ListApplicationDeviceTagsRequest))
}
return interceptor(ctx, in, info, handler)
}
// ApplicationService_ServiceDesc is the grpc.ServiceDesc for ApplicationService service. // ApplicationService_ServiceDesc is the grpc.ServiceDesc for ApplicationService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@ -1900,6 +1970,14 @@ var ApplicationService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GenerateMqttIntegrationClientCertificate", MethodName: "GenerateMqttIntegrationClientCertificate",
Handler: _ApplicationService_GenerateMqttIntegrationClientCertificate_Handler, Handler: _ApplicationService_GenerateMqttIntegrationClientCertificate_Handler,
}, },
{
MethodName: "ListDeviceProfiles",
Handler: _ApplicationService_ListDeviceProfiles_Handler,
},
{
MethodName: "ListDeviceTags",
Handler: _ApplicationService_ListDeviceTags_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "api/application.proto", Metadata: "api/application.proto",

1218
api/go/api/device.pb.go vendored

File diff suppressed because it is too large Load Diff

View File

@ -321,6 +321,162 @@ func (RelayModeActivation) EnumDescriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{4} return file_api_device_profile_proto_rawDescGZIP(), []int{4}
} }
type Ts003Version int32
const (
// Not implemented.
Ts003Version_TS003_NOT_IMPLEMENTED Ts003Version = 0
// v1.0.0.
Ts003Version_TS003_V100 Ts003Version = 1
// v2.0.0
Ts003Version_TS003_v200 Ts003Version = 2
)
// Enum value maps for Ts003Version.
var (
Ts003Version_name = map[int32]string{
0: "TS003_NOT_IMPLEMENTED",
1: "TS003_V100",
2: "TS003_v200",
}
Ts003Version_value = map[string]int32{
"TS003_NOT_IMPLEMENTED": 0,
"TS003_V100": 1,
"TS003_v200": 2,
}
)
func (x Ts003Version) Enum() *Ts003Version {
p := new(Ts003Version)
*p = x
return p
}
func (x Ts003Version) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Ts003Version) Descriptor() protoreflect.EnumDescriptor {
return file_api_device_profile_proto_enumTypes[5].Descriptor()
}
func (Ts003Version) Type() protoreflect.EnumType {
return &file_api_device_profile_proto_enumTypes[5]
}
func (x Ts003Version) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Ts003Version.Descriptor instead.
func (Ts003Version) EnumDescriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{5}
}
type Ts004Version int32
const (
// Not implemented.
Ts004Version_TS004_NOT_IMPLEMENTED Ts004Version = 0
// v1.0.0.
Ts004Version_TS004_V100 Ts004Version = 1
// v2.0.0
Ts004Version_TS004_V200 Ts004Version = 2
)
// Enum value maps for Ts004Version.
var (
Ts004Version_name = map[int32]string{
0: "TS004_NOT_IMPLEMENTED",
1: "TS004_V100",
2: "TS004_V200",
}
Ts004Version_value = map[string]int32{
"TS004_NOT_IMPLEMENTED": 0,
"TS004_V100": 1,
"TS004_V200": 2,
}
)
func (x Ts004Version) Enum() *Ts004Version {
p := new(Ts004Version)
*p = x
return p
}
func (x Ts004Version) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Ts004Version) Descriptor() protoreflect.EnumDescriptor {
return file_api_device_profile_proto_enumTypes[6].Descriptor()
}
func (Ts004Version) Type() protoreflect.EnumType {
return &file_api_device_profile_proto_enumTypes[6]
}
func (x Ts004Version) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Ts004Version.Descriptor instead.
func (Ts004Version) EnumDescriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{6}
}
type Ts005Version int32
const (
// Not implemented.
Ts005Version_TS005_NOT_IMPLEMENTED Ts005Version = 0
// v1.0.0.
Ts005Version_TS005_V100 Ts005Version = 1
// v2.0.0
Ts005Version_TS005_V200 Ts005Version = 2
)
// Enum value maps for Ts005Version.
var (
Ts005Version_name = map[int32]string{
0: "TS005_NOT_IMPLEMENTED",
1: "TS005_V100",
2: "TS005_V200",
}
Ts005Version_value = map[string]int32{
"TS005_NOT_IMPLEMENTED": 0,
"TS005_V100": 1,
"TS005_V200": 2,
}
)
func (x Ts005Version) Enum() *Ts005Version {
p := new(Ts005Version)
*p = x
return p
}
func (x Ts005Version) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Ts005Version) Descriptor() protoreflect.EnumDescriptor {
return file_api_device_profile_proto_enumTypes[7].Descriptor()
}
func (Ts005Version) Type() protoreflect.EnumType {
return &file_api_device_profile_proto_enumTypes[7]
}
func (x Ts005Version) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Ts005Version.Descriptor instead.
func (Ts005Version) EnumDescriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{7}
}
type DeviceProfile struct { type DeviceProfile struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -552,6 +708,8 @@ type DeviceProfile struct {
// it. // it.
// Valid options are 1 - 15 (0 = always use system RX1 Delay). // Valid options are 1 - 15 (0 = always use system RX1 Delay).
Rx1Delay uint32 `protobuf:"varint,53,opt,name=rx1_delay,json=rx1Delay,proto3" json:"rx1_delay,omitempty"` Rx1Delay uint32 `protobuf:"varint,53,opt,name=rx1_delay,json=rx1Delay,proto3" json:"rx1_delay,omitempty"`
// Application Layer parameters.
AppLayerParams *AppLayerParams `protobuf:"bytes,54,opt,name=app_layer_params,json=appLayerParams,proto3" json:"app_layer_params,omitempty"`
} }
func (x *DeviceProfile) Reset() { func (x *DeviceProfile) Reset() {
@ -955,6 +1113,13 @@ func (x *DeviceProfile) GetRx1Delay() uint32 {
return 0 return 0
} }
func (x *DeviceProfile) GetAppLayerParams() *AppLayerParams {
if x != nil {
return x.AppLayerParams
}
return nil
}
type Measurement struct { type Measurement struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -1010,6 +1175,97 @@ func (x *Measurement) GetKind() MeasurementKind {
return MeasurementKind_UNKNOWN return MeasurementKind_UNKNOWN
} }
type AppLayerParams struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// TS003 version (Application Layer Clock Sync).
Ts003Version Ts003Version `protobuf:"varint,1,opt,name=ts003_version,json=ts003Version,proto3,enum=api.Ts003Version" json:"ts003_version,omitempty"`
// TS003 fPort.
Ts003FPort uint32 `protobuf:"varint,2,opt,name=ts003_f_port,json=ts003FPort,proto3" json:"ts003_f_port,omitempty"`
// TS004 version (Fragmented Data Block Transport).
Ts004Version Ts004Version `protobuf:"varint,3,opt,name=ts004_version,json=ts004Version,proto3,enum=api.Ts004Version" json:"ts004_version,omitempty"`
// TS004 fPort.
Ts004FPort uint32 `protobuf:"varint,4,opt,name=ts004_f_port,json=ts004FPort,proto3" json:"ts004_f_port,omitempty"`
// TS005 version (Remote Multicast Setup).
Ts005Version Ts005Version `protobuf:"varint,5,opt,name=ts005_version,json=ts005Version,proto3,enum=api.Ts005Version" json:"ts005_version,omitempty"`
// TS005 fPort.
Ts005FPort uint32 `protobuf:"varint,6,opt,name=ts005_f_port,json=ts005FPort,proto3" json:"ts005_f_port,omitempty"`
}
func (x *AppLayerParams) Reset() {
*x = AppLayerParams{}
mi := &file_api_device_profile_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AppLayerParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AppLayerParams) ProtoMessage() {}
func (x *AppLayerParams) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AppLayerParams.ProtoReflect.Descriptor instead.
func (*AppLayerParams) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{2}
}
func (x *AppLayerParams) GetTs003Version() Ts003Version {
if x != nil {
return x.Ts003Version
}
return Ts003Version_TS003_NOT_IMPLEMENTED
}
func (x *AppLayerParams) GetTs003FPort() uint32 {
if x != nil {
return x.Ts003FPort
}
return 0
}
func (x *AppLayerParams) GetTs004Version() Ts004Version {
if x != nil {
return x.Ts004Version
}
return Ts004Version_TS004_NOT_IMPLEMENTED
}
func (x *AppLayerParams) GetTs004FPort() uint32 {
if x != nil {
return x.Ts004FPort
}
return 0
}
func (x *AppLayerParams) GetTs005Version() Ts005Version {
if x != nil {
return x.Ts005Version
}
return Ts005Version_TS005_NOT_IMPLEMENTED
}
func (x *AppLayerParams) GetTs005FPort() uint32 {
if x != nil {
return x.Ts005FPort
}
return 0
}
type DeviceProfileListItem struct { type DeviceProfileListItem struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -1039,7 +1295,7 @@ type DeviceProfileListItem struct {
func (x *DeviceProfileListItem) Reset() { func (x *DeviceProfileListItem) Reset() {
*x = DeviceProfileListItem{} *x = DeviceProfileListItem{}
mi := &file_api_device_profile_proto_msgTypes[2] mi := &file_api_device_profile_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1051,7 +1307,7 @@ func (x *DeviceProfileListItem) String() string {
func (*DeviceProfileListItem) ProtoMessage() {} func (*DeviceProfileListItem) ProtoMessage() {}
func (x *DeviceProfileListItem) ProtoReflect() protoreflect.Message { func (x *DeviceProfileListItem) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[2] mi := &file_api_device_profile_proto_msgTypes[3]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1064,7 +1320,7 @@ func (x *DeviceProfileListItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeviceProfileListItem.ProtoReflect.Descriptor instead. // Deprecated: Use DeviceProfileListItem.ProtoReflect.Descriptor instead.
func (*DeviceProfileListItem) Descriptor() ([]byte, []int) { func (*DeviceProfileListItem) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{2} return file_api_device_profile_proto_rawDescGZIP(), []int{3}
} }
func (x *DeviceProfileListItem) GetId() string { func (x *DeviceProfileListItem) GetId() string {
@ -1148,7 +1404,7 @@ type CreateDeviceProfileRequest struct {
func (x *CreateDeviceProfileRequest) Reset() { func (x *CreateDeviceProfileRequest) Reset() {
*x = CreateDeviceProfileRequest{} *x = CreateDeviceProfileRequest{}
mi := &file_api_device_profile_proto_msgTypes[3] mi := &file_api_device_profile_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1160,7 +1416,7 @@ func (x *CreateDeviceProfileRequest) String() string {
func (*CreateDeviceProfileRequest) ProtoMessage() {} func (*CreateDeviceProfileRequest) ProtoMessage() {}
func (x *CreateDeviceProfileRequest) ProtoReflect() protoreflect.Message { func (x *CreateDeviceProfileRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[3] mi := &file_api_device_profile_proto_msgTypes[4]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1173,7 +1429,7 @@ func (x *CreateDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateDeviceProfileRequest.ProtoReflect.Descriptor instead. // Deprecated: Use CreateDeviceProfileRequest.ProtoReflect.Descriptor instead.
func (*CreateDeviceProfileRequest) Descriptor() ([]byte, []int) { func (*CreateDeviceProfileRequest) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{3} return file_api_device_profile_proto_rawDescGZIP(), []int{4}
} }
func (x *CreateDeviceProfileRequest) GetDeviceProfile() *DeviceProfile { func (x *CreateDeviceProfileRequest) GetDeviceProfile() *DeviceProfile {
@ -1194,7 +1450,7 @@ type CreateDeviceProfileResponse struct {
func (x *CreateDeviceProfileResponse) Reset() { func (x *CreateDeviceProfileResponse) Reset() {
*x = CreateDeviceProfileResponse{} *x = CreateDeviceProfileResponse{}
mi := &file_api_device_profile_proto_msgTypes[4] mi := &file_api_device_profile_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1206,7 +1462,7 @@ func (x *CreateDeviceProfileResponse) String() string {
func (*CreateDeviceProfileResponse) ProtoMessage() {} func (*CreateDeviceProfileResponse) ProtoMessage() {}
func (x *CreateDeviceProfileResponse) ProtoReflect() protoreflect.Message { func (x *CreateDeviceProfileResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[4] mi := &file_api_device_profile_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1219,7 +1475,7 @@ func (x *CreateDeviceProfileResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateDeviceProfileResponse.ProtoReflect.Descriptor instead. // Deprecated: Use CreateDeviceProfileResponse.ProtoReflect.Descriptor instead.
func (*CreateDeviceProfileResponse) Descriptor() ([]byte, []int) { func (*CreateDeviceProfileResponse) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{4} return file_api_device_profile_proto_rawDescGZIP(), []int{5}
} }
func (x *CreateDeviceProfileResponse) GetId() string { func (x *CreateDeviceProfileResponse) GetId() string {
@ -1240,7 +1496,7 @@ type GetDeviceProfileRequest struct {
func (x *GetDeviceProfileRequest) Reset() { func (x *GetDeviceProfileRequest) Reset() {
*x = GetDeviceProfileRequest{} *x = GetDeviceProfileRequest{}
mi := &file_api_device_profile_proto_msgTypes[5] mi := &file_api_device_profile_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1252,7 +1508,7 @@ func (x *GetDeviceProfileRequest) String() string {
func (*GetDeviceProfileRequest) ProtoMessage() {} func (*GetDeviceProfileRequest) ProtoMessage() {}
func (x *GetDeviceProfileRequest) ProtoReflect() protoreflect.Message { func (x *GetDeviceProfileRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[5] mi := &file_api_device_profile_proto_msgTypes[6]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1265,7 +1521,7 @@ func (x *GetDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetDeviceProfileRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetDeviceProfileRequest.ProtoReflect.Descriptor instead.
func (*GetDeviceProfileRequest) Descriptor() ([]byte, []int) { func (*GetDeviceProfileRequest) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{5} return file_api_device_profile_proto_rawDescGZIP(), []int{6}
} }
func (x *GetDeviceProfileRequest) GetId() string { func (x *GetDeviceProfileRequest) GetId() string {
@ -1290,7 +1546,7 @@ type GetDeviceProfileResponse struct {
func (x *GetDeviceProfileResponse) Reset() { func (x *GetDeviceProfileResponse) Reset() {
*x = GetDeviceProfileResponse{} *x = GetDeviceProfileResponse{}
mi := &file_api_device_profile_proto_msgTypes[6] mi := &file_api_device_profile_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1302,7 +1558,7 @@ func (x *GetDeviceProfileResponse) String() string {
func (*GetDeviceProfileResponse) ProtoMessage() {} func (*GetDeviceProfileResponse) ProtoMessage() {}
func (x *GetDeviceProfileResponse) ProtoReflect() protoreflect.Message { func (x *GetDeviceProfileResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[6] mi := &file_api_device_profile_proto_msgTypes[7]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1315,7 +1571,7 @@ func (x *GetDeviceProfileResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetDeviceProfileResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetDeviceProfileResponse.ProtoReflect.Descriptor instead.
func (*GetDeviceProfileResponse) Descriptor() ([]byte, []int) { func (*GetDeviceProfileResponse) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{6} return file_api_device_profile_proto_rawDescGZIP(), []int{7}
} }
func (x *GetDeviceProfileResponse) GetDeviceProfile() *DeviceProfile { func (x *GetDeviceProfileResponse) GetDeviceProfile() *DeviceProfile {
@ -1350,7 +1606,7 @@ type UpdateDeviceProfileRequest struct {
func (x *UpdateDeviceProfileRequest) Reset() { func (x *UpdateDeviceProfileRequest) Reset() {
*x = UpdateDeviceProfileRequest{} *x = UpdateDeviceProfileRequest{}
mi := &file_api_device_profile_proto_msgTypes[7] mi := &file_api_device_profile_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1362,7 +1618,7 @@ func (x *UpdateDeviceProfileRequest) String() string {
func (*UpdateDeviceProfileRequest) ProtoMessage() {} func (*UpdateDeviceProfileRequest) ProtoMessage() {}
func (x *UpdateDeviceProfileRequest) ProtoReflect() protoreflect.Message { func (x *UpdateDeviceProfileRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[7] mi := &file_api_device_profile_proto_msgTypes[8]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1375,7 +1631,7 @@ func (x *UpdateDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateDeviceProfileRequest.ProtoReflect.Descriptor instead. // Deprecated: Use UpdateDeviceProfileRequest.ProtoReflect.Descriptor instead.
func (*UpdateDeviceProfileRequest) Descriptor() ([]byte, []int) { func (*UpdateDeviceProfileRequest) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{7} return file_api_device_profile_proto_rawDescGZIP(), []int{8}
} }
func (x *UpdateDeviceProfileRequest) GetDeviceProfile() *DeviceProfile { func (x *UpdateDeviceProfileRequest) GetDeviceProfile() *DeviceProfile {
@ -1396,7 +1652,7 @@ type DeleteDeviceProfileRequest struct {
func (x *DeleteDeviceProfileRequest) Reset() { func (x *DeleteDeviceProfileRequest) Reset() {
*x = DeleteDeviceProfileRequest{} *x = DeleteDeviceProfileRequest{}
mi := &file_api_device_profile_proto_msgTypes[8] mi := &file_api_device_profile_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1408,7 +1664,7 @@ func (x *DeleteDeviceProfileRequest) String() string {
func (*DeleteDeviceProfileRequest) ProtoMessage() {} func (*DeleteDeviceProfileRequest) ProtoMessage() {}
func (x *DeleteDeviceProfileRequest) ProtoReflect() protoreflect.Message { func (x *DeleteDeviceProfileRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[8] mi := &file_api_device_profile_proto_msgTypes[9]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1421,7 +1677,7 @@ func (x *DeleteDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteDeviceProfileRequest.ProtoReflect.Descriptor instead. // Deprecated: Use DeleteDeviceProfileRequest.ProtoReflect.Descriptor instead.
func (*DeleteDeviceProfileRequest) Descriptor() ([]byte, []int) { func (*DeleteDeviceProfileRequest) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{8} return file_api_device_profile_proto_rawDescGZIP(), []int{9}
} }
func (x *DeleteDeviceProfileRequest) GetId() string { func (x *DeleteDeviceProfileRequest) GetId() string {
@ -1437,6 +1693,7 @@ type ListDeviceProfilesRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of device-profiles to return in the result-set. // Max number of device-profiles to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -1448,7 +1705,7 @@ type ListDeviceProfilesRequest struct {
func (x *ListDeviceProfilesRequest) Reset() { func (x *ListDeviceProfilesRequest) Reset() {
*x = ListDeviceProfilesRequest{} *x = ListDeviceProfilesRequest{}
mi := &file_api_device_profile_proto_msgTypes[9] mi := &file_api_device_profile_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1460,7 +1717,7 @@ func (x *ListDeviceProfilesRequest) String() string {
func (*ListDeviceProfilesRequest) ProtoMessage() {} func (*ListDeviceProfilesRequest) ProtoMessage() {}
func (x *ListDeviceProfilesRequest) ProtoReflect() protoreflect.Message { func (x *ListDeviceProfilesRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[9] mi := &file_api_device_profile_proto_msgTypes[10]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1473,7 +1730,7 @@ func (x *ListDeviceProfilesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListDeviceProfilesRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListDeviceProfilesRequest.ProtoReflect.Descriptor instead.
func (*ListDeviceProfilesRequest) Descriptor() ([]byte, []int) { func (*ListDeviceProfilesRequest) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{9} return file_api_device_profile_proto_rawDescGZIP(), []int{10}
} }
func (x *ListDeviceProfilesRequest) GetLimit() uint32 { func (x *ListDeviceProfilesRequest) GetLimit() uint32 {
@ -1517,7 +1774,7 @@ type ListDeviceProfilesResponse struct {
func (x *ListDeviceProfilesResponse) Reset() { func (x *ListDeviceProfilesResponse) Reset() {
*x = ListDeviceProfilesResponse{} *x = ListDeviceProfilesResponse{}
mi := &file_api_device_profile_proto_msgTypes[10] mi := &file_api_device_profile_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1529,7 +1786,7 @@ func (x *ListDeviceProfilesResponse) String() string {
func (*ListDeviceProfilesResponse) ProtoMessage() {} func (*ListDeviceProfilesResponse) ProtoMessage() {}
func (x *ListDeviceProfilesResponse) ProtoReflect() protoreflect.Message { func (x *ListDeviceProfilesResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[10] mi := &file_api_device_profile_proto_msgTypes[11]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1542,7 +1799,7 @@ func (x *ListDeviceProfilesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListDeviceProfilesResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListDeviceProfilesResponse.ProtoReflect.Descriptor instead.
func (*ListDeviceProfilesResponse) Descriptor() ([]byte, []int) { func (*ListDeviceProfilesResponse) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{10} return file_api_device_profile_proto_rawDescGZIP(), []int{11}
} }
func (x *ListDeviceProfilesResponse) GetTotalCount() uint32 { func (x *ListDeviceProfilesResponse) GetTotalCount() uint32 {
@ -1572,7 +1829,7 @@ type ListDeviceProfileAdrAlgorithmsResponse struct {
func (x *ListDeviceProfileAdrAlgorithmsResponse) Reset() { func (x *ListDeviceProfileAdrAlgorithmsResponse) Reset() {
*x = ListDeviceProfileAdrAlgorithmsResponse{} *x = ListDeviceProfileAdrAlgorithmsResponse{}
mi := &file_api_device_profile_proto_msgTypes[11] mi := &file_api_device_profile_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1584,7 +1841,7 @@ func (x *ListDeviceProfileAdrAlgorithmsResponse) String() string {
func (*ListDeviceProfileAdrAlgorithmsResponse) ProtoMessage() {} func (*ListDeviceProfileAdrAlgorithmsResponse) ProtoMessage() {}
func (x *ListDeviceProfileAdrAlgorithmsResponse) ProtoReflect() protoreflect.Message { func (x *ListDeviceProfileAdrAlgorithmsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[11] mi := &file_api_device_profile_proto_msgTypes[12]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1597,7 +1854,7 @@ func (x *ListDeviceProfileAdrAlgorithmsResponse) ProtoReflect() protoreflect.Mes
// Deprecated: Use ListDeviceProfileAdrAlgorithmsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListDeviceProfileAdrAlgorithmsResponse.ProtoReflect.Descriptor instead.
func (*ListDeviceProfileAdrAlgorithmsResponse) Descriptor() ([]byte, []int) { func (*ListDeviceProfileAdrAlgorithmsResponse) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{11} return file_api_device_profile_proto_rawDescGZIP(), []int{12}
} }
func (x *ListDeviceProfileAdrAlgorithmsResponse) GetTotalCount() uint32 { func (x *ListDeviceProfileAdrAlgorithmsResponse) GetTotalCount() uint32 {
@ -1627,7 +1884,7 @@ type AdrAlgorithmListItem struct {
func (x *AdrAlgorithmListItem) Reset() { func (x *AdrAlgorithmListItem) Reset() {
*x = AdrAlgorithmListItem{} *x = AdrAlgorithmListItem{}
mi := &file_api_device_profile_proto_msgTypes[12] mi := &file_api_device_profile_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1639,7 +1896,7 @@ func (x *AdrAlgorithmListItem) String() string {
func (*AdrAlgorithmListItem) ProtoMessage() {} func (*AdrAlgorithmListItem) ProtoMessage() {}
func (x *AdrAlgorithmListItem) ProtoReflect() protoreflect.Message { func (x *AdrAlgorithmListItem) ProtoReflect() protoreflect.Message {
mi := &file_api_device_profile_proto_msgTypes[12] mi := &file_api_device_profile_proto_msgTypes[13]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1652,7 +1909,7 @@ func (x *AdrAlgorithmListItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdrAlgorithmListItem.ProtoReflect.Descriptor instead. // Deprecated: Use AdrAlgorithmListItem.ProtoReflect.Descriptor instead.
func (*AdrAlgorithmListItem) Descriptor() ([]byte, []int) { func (*AdrAlgorithmListItem) Descriptor() ([]byte, []int) {
return file_api_device_profile_proto_rawDescGZIP(), []int{12} return file_api_device_profile_proto_rawDescGZIP(), []int{13}
} }
func (x *AdrAlgorithmListItem) GetId() string { func (x *AdrAlgorithmListItem) GetId() string {
@ -1681,7 +1938,7 @@ var file_api_device_profile_proto_rawDesc = []byte{
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x63, 0x6f, 0x6d, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x22, 0xf0, 0x16, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x22, 0xaf, 0x17, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12,
@ -1855,20 +2112,42 @@ var file_api_device_profile_proto_rawDesc = []byte{
0x6f, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x34, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x6c, 0x6f, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x34, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x61, 0x6c,
0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x78, 0x6c, 0x6f, 0x77, 0x52, 0x6f, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x78,
0x31, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x35, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x72, 0x31, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x35, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x72,
0x78, 0x31, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x78, 0x31, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x3d, 0x0a, 0x10, 0x61, 0x70, 0x70, 0x5f, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x36, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x4c, 0x61, 0x79, 0x65, 0x72,
0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x0e, 0x61, 0x70, 0x70, 0x4c, 0x61, 0x79, 0x65, 0x72,
0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a,
0x51, 0x0a, 0x11, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73,
0x1a, 0x51, 0x0a, 0x11, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x38, 0x01, 0x22, 0x4b, 0x0a, 0x0b, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20,
0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72,
0x02, 0x38, 0x01, 0x22, 0x4b, 0x0a, 0x0b, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22,
0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x9e, 0x02, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x61, 0x72, 0x61,
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x6d, 0x73, 0x12, 0x36, 0x0a, 0x0d, 0x74, 0x73, 0x30, 0x30, 0x33, 0x5f, 0x76, 0x65, 0x72, 0x73,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x54, 0x73, 0x30, 0x30, 0x33, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x73,
0x30, 0x30, 0x33, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x73,
0x30, 0x30, 0x33, 0x5f, 0x66, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0a, 0x74, 0x73, 0x30, 0x30, 0x33, 0x46, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x0d,
0x74, 0x73, 0x30, 0x30, 0x34, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x73, 0x30, 0x30, 0x34, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x74, 0x73, 0x30, 0x30, 0x34, 0x56, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x73, 0x30, 0x30, 0x34, 0x5f, 0x66, 0x5f,
0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x73, 0x30, 0x30,
0x34, 0x46, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x74, 0x73, 0x30, 0x30, 0x35, 0x5f,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x54, 0x73, 0x30, 0x30, 0x35, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x52, 0x0c, 0x74, 0x73, 0x30, 0x30, 0x35, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20,
0x0a, 0x0c, 0x74, 0x73, 0x30, 0x30, 0x35, 0x5f, 0x66, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x73, 0x30, 0x30, 0x35, 0x46, 0x50, 0x6f, 0x72, 0x74,
0x22, 0xd2, 0x03, 0x0a, 0x15, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x22, 0xd2, 0x03, 0x0a, 0x15, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72,
@ -1984,61 +2263,75 @@ var file_api_device_profile_proto_rawDesc = []byte{
0x4c, 0x45, 0x5f, 0x52, 0x45, 0x4c, 0x41, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, 0x4c, 0x45, 0x5f, 0x52, 0x45, 0x4c, 0x41, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12,
0x0b, 0x0a, 0x07, 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x0b, 0x0a, 0x07, 0x44, 0x59, 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15,
0x45, 0x4e, 0x44, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x45, 0x4e, 0x44, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52,
0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x32, 0xb8, 0x05, 0x0a, 0x14, 0x44, 0x65, 0x76, 0x69, 0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x49, 0x0a, 0x0c, 0x54, 0x73, 0x30, 0x30, 0x33,
0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x53, 0x30, 0x30, 0x33,
0x12, 0x6c, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44,
0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x53, 0x30, 0x30, 0x33, 0x5f, 0x56, 0x31, 0x30, 0x30,
0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x53, 0x30, 0x30, 0x33, 0x5f, 0x76, 0x32, 0x30, 0x30,
0x10, 0x02, 0x2a, 0x49, 0x0a, 0x0c, 0x54, 0x73, 0x30, 0x30, 0x34, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x53, 0x30, 0x30, 0x34, 0x5f, 0x4e, 0x4f, 0x54, 0x5f,
0x49, 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a,
0x0a, 0x54, 0x53, 0x30, 0x30, 0x34, 0x5f, 0x56, 0x31, 0x30, 0x30, 0x10, 0x01, 0x12, 0x0e, 0x0a,
0x0a, 0x54, 0x53, 0x30, 0x30, 0x34, 0x5f, 0x56, 0x32, 0x30, 0x30, 0x10, 0x02, 0x2a, 0x49, 0x0a,
0x0c, 0x54, 0x73, 0x30, 0x30, 0x35, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a,
0x15, 0x54, 0x53, 0x30, 0x30, 0x35, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x45,
0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x53, 0x30, 0x30,
0x35, 0x5f, 0x56, 0x31, 0x30, 0x30, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x53, 0x30, 0x30,
0x35, 0x5f, 0x56, 0x32, 0x30, 0x30, 0x10, 0x02, 0x32, 0xb8, 0x05, 0x0a, 0x14, 0x44, 0x65, 0x76,
0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x6c, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72,
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61,
0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50,
0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x65, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f,
0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12,
0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x65, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74,
0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71,
0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65,
0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x76, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x61, 0x70,
0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x76, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x3a, 0x01, 0x2a, 0x1a, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x2d, 0x3a, 0x01, 0x2a, 0x1a, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63,
0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69,
0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x63, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0x64,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69,
0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f,
0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x61,
0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f,
0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61,
0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f,
0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x11, 0x4c, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82,
0x69, 0x73, 0x74, 0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69,
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x11,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d,
0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x65, 0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x52, 0x65,
0x6c, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x72, 0x2d, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23,
0x6d, 0x73, 0x42, 0x98, 0x01, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66,
0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x72, 0x2d, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74,
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x68, 0x6d, 0x73, 0x42, 0x98, 0x01, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63,
0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x69, 0xaa, 0x02, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72,
0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x41, 0x70, 0x69, 0xca, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63,
0x02, 0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70, 0x69, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x69, 0xaa,
0xe2, 0x02, 0x1a, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5c, 0x43, 0x02, 0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x41, 0x70, 0x69,
0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70, 0x69, 0x62, 0x06, 0x70, 0xca, 0x02, 0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33, 0x69, 0xe2, 0x02, 0x1a, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5c,
0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70, 0x69, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -2053,76 +2346,84 @@ func file_api_device_profile_proto_rawDescGZIP() []byte {
return file_api_device_profile_proto_rawDescData return file_api_device_profile_proto_rawDescData
} }
var file_api_device_profile_proto_enumTypes = make([]protoimpl.EnumInfo, 5) var file_api_device_profile_proto_enumTypes = make([]protoimpl.EnumInfo, 8)
var file_api_device_profile_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_api_device_profile_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_api_device_profile_proto_goTypes = []any{ var file_api_device_profile_proto_goTypes = []any{
(CodecRuntime)(0), // 0: api.CodecRuntime (CodecRuntime)(0), // 0: api.CodecRuntime
(MeasurementKind)(0), // 1: api.MeasurementKind (MeasurementKind)(0), // 1: api.MeasurementKind
(CadPeriodicity)(0), // 2: api.CadPeriodicity (CadPeriodicity)(0), // 2: api.CadPeriodicity
(SecondChAckOffset)(0), // 3: api.SecondChAckOffset (SecondChAckOffset)(0), // 3: api.SecondChAckOffset
(RelayModeActivation)(0), // 4: api.RelayModeActivation (RelayModeActivation)(0), // 4: api.RelayModeActivation
(*DeviceProfile)(nil), // 5: api.DeviceProfile (Ts003Version)(0), // 5: api.Ts003Version
(*Measurement)(nil), // 6: api.Measurement (Ts004Version)(0), // 6: api.Ts004Version
(*DeviceProfileListItem)(nil), // 7: api.DeviceProfileListItem (Ts005Version)(0), // 7: api.Ts005Version
(*CreateDeviceProfileRequest)(nil), // 8: api.CreateDeviceProfileRequest (*DeviceProfile)(nil), // 8: api.DeviceProfile
(*CreateDeviceProfileResponse)(nil), // 9: api.CreateDeviceProfileResponse (*Measurement)(nil), // 9: api.Measurement
(*GetDeviceProfileRequest)(nil), // 10: api.GetDeviceProfileRequest (*AppLayerParams)(nil), // 10: api.AppLayerParams
(*GetDeviceProfileResponse)(nil), // 11: api.GetDeviceProfileResponse (*DeviceProfileListItem)(nil), // 11: api.DeviceProfileListItem
(*UpdateDeviceProfileRequest)(nil), // 12: api.UpdateDeviceProfileRequest (*CreateDeviceProfileRequest)(nil), // 12: api.CreateDeviceProfileRequest
(*DeleteDeviceProfileRequest)(nil), // 13: api.DeleteDeviceProfileRequest (*CreateDeviceProfileResponse)(nil), // 13: api.CreateDeviceProfileResponse
(*ListDeviceProfilesRequest)(nil), // 14: api.ListDeviceProfilesRequest (*GetDeviceProfileRequest)(nil), // 14: api.GetDeviceProfileRequest
(*ListDeviceProfilesResponse)(nil), // 15: api.ListDeviceProfilesResponse (*GetDeviceProfileResponse)(nil), // 15: api.GetDeviceProfileResponse
(*ListDeviceProfileAdrAlgorithmsResponse)(nil), // 16: api.ListDeviceProfileAdrAlgorithmsResponse (*UpdateDeviceProfileRequest)(nil), // 16: api.UpdateDeviceProfileRequest
(*AdrAlgorithmListItem)(nil), // 17: api.AdrAlgorithmListItem (*DeleteDeviceProfileRequest)(nil), // 17: api.DeleteDeviceProfileRequest
nil, // 18: api.DeviceProfile.TagsEntry (*ListDeviceProfilesRequest)(nil), // 18: api.ListDeviceProfilesRequest
nil, // 19: api.DeviceProfile.MeasurementsEntry (*ListDeviceProfilesResponse)(nil), // 19: api.ListDeviceProfilesResponse
(common.Region)(0), // 20: common.Region (*ListDeviceProfileAdrAlgorithmsResponse)(nil), // 20: api.ListDeviceProfileAdrAlgorithmsResponse
(common.MacVersion)(0), // 21: common.MacVersion (*AdrAlgorithmListItem)(nil), // 21: api.AdrAlgorithmListItem
(common.RegParamsRevision)(0), // 22: common.RegParamsRevision nil, // 22: api.DeviceProfile.TagsEntry
(*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp nil, // 23: api.DeviceProfile.MeasurementsEntry
(*emptypb.Empty)(nil), // 24: google.protobuf.Empty (common.Region)(0), // 24: common.Region
(common.MacVersion)(0), // 25: common.MacVersion
(common.RegParamsRevision)(0), // 26: common.RegParamsRevision
(*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 28: google.protobuf.Empty
} }
var file_api_device_profile_proto_depIdxs = []int32{ var file_api_device_profile_proto_depIdxs = []int32{
20, // 0: api.DeviceProfile.region:type_name -> common.Region 24, // 0: api.DeviceProfile.region:type_name -> common.Region
21, // 1: api.DeviceProfile.mac_version:type_name -> common.MacVersion 25, // 1: api.DeviceProfile.mac_version:type_name -> common.MacVersion
22, // 2: api.DeviceProfile.reg_params_revision:type_name -> common.RegParamsRevision 26, // 2: api.DeviceProfile.reg_params_revision:type_name -> common.RegParamsRevision
0, // 3: api.DeviceProfile.payload_codec_runtime:type_name -> api.CodecRuntime 0, // 3: api.DeviceProfile.payload_codec_runtime:type_name -> api.CodecRuntime
18, // 4: api.DeviceProfile.tags:type_name -> api.DeviceProfile.TagsEntry 22, // 4: api.DeviceProfile.tags:type_name -> api.DeviceProfile.TagsEntry
19, // 5: api.DeviceProfile.measurements:type_name -> api.DeviceProfile.MeasurementsEntry 23, // 5: api.DeviceProfile.measurements:type_name -> api.DeviceProfile.MeasurementsEntry
2, // 6: api.DeviceProfile.relay_cad_periodicity:type_name -> api.CadPeriodicity 2, // 6: api.DeviceProfile.relay_cad_periodicity:type_name -> api.CadPeriodicity
3, // 7: api.DeviceProfile.relay_second_channel_ack_offset:type_name -> api.SecondChAckOffset 3, // 7: api.DeviceProfile.relay_second_channel_ack_offset:type_name -> api.SecondChAckOffset
4, // 8: api.DeviceProfile.relay_ed_activation_mode:type_name -> api.RelayModeActivation 4, // 8: api.DeviceProfile.relay_ed_activation_mode:type_name -> api.RelayModeActivation
1, // 9: api.Measurement.kind:type_name -> api.MeasurementKind 10, // 9: api.DeviceProfile.app_layer_params:type_name -> api.AppLayerParams
23, // 10: api.DeviceProfileListItem.created_at:type_name -> google.protobuf.Timestamp 1, // 10: api.Measurement.kind:type_name -> api.MeasurementKind
23, // 11: api.DeviceProfileListItem.updated_at:type_name -> google.protobuf.Timestamp 5, // 11: api.AppLayerParams.ts003_version:type_name -> api.Ts003Version
20, // 12: api.DeviceProfileListItem.region:type_name -> common.Region 6, // 12: api.AppLayerParams.ts004_version:type_name -> api.Ts004Version
21, // 13: api.DeviceProfileListItem.mac_version:type_name -> common.MacVersion 7, // 13: api.AppLayerParams.ts005_version:type_name -> api.Ts005Version
22, // 14: api.DeviceProfileListItem.reg_params_revision:type_name -> common.RegParamsRevision 27, // 14: api.DeviceProfileListItem.created_at:type_name -> google.protobuf.Timestamp
5, // 15: api.CreateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile 27, // 15: api.DeviceProfileListItem.updated_at:type_name -> google.protobuf.Timestamp
5, // 16: api.GetDeviceProfileResponse.device_profile:type_name -> api.DeviceProfile 24, // 16: api.DeviceProfileListItem.region:type_name -> common.Region
23, // 17: api.GetDeviceProfileResponse.created_at:type_name -> google.protobuf.Timestamp 25, // 17: api.DeviceProfileListItem.mac_version:type_name -> common.MacVersion
23, // 18: api.GetDeviceProfileResponse.updated_at:type_name -> google.protobuf.Timestamp 26, // 18: api.DeviceProfileListItem.reg_params_revision:type_name -> common.RegParamsRevision
5, // 19: api.UpdateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile 8, // 19: api.CreateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile
7, // 20: api.ListDeviceProfilesResponse.result:type_name -> api.DeviceProfileListItem 8, // 20: api.GetDeviceProfileResponse.device_profile:type_name -> api.DeviceProfile
17, // 21: api.ListDeviceProfileAdrAlgorithmsResponse.result:type_name -> api.AdrAlgorithmListItem 27, // 21: api.GetDeviceProfileResponse.created_at:type_name -> google.protobuf.Timestamp
6, // 22: api.DeviceProfile.MeasurementsEntry.value:type_name -> api.Measurement 27, // 22: api.GetDeviceProfileResponse.updated_at:type_name -> google.protobuf.Timestamp
8, // 23: api.DeviceProfileService.Create:input_type -> api.CreateDeviceProfileRequest 8, // 23: api.UpdateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile
10, // 24: api.DeviceProfileService.Get:input_type -> api.GetDeviceProfileRequest 11, // 24: api.ListDeviceProfilesResponse.result:type_name -> api.DeviceProfileListItem
12, // 25: api.DeviceProfileService.Update:input_type -> api.UpdateDeviceProfileRequest 21, // 25: api.ListDeviceProfileAdrAlgorithmsResponse.result:type_name -> api.AdrAlgorithmListItem
13, // 26: api.DeviceProfileService.Delete:input_type -> api.DeleteDeviceProfileRequest 9, // 26: api.DeviceProfile.MeasurementsEntry.value:type_name -> api.Measurement
14, // 27: api.DeviceProfileService.List:input_type -> api.ListDeviceProfilesRequest 12, // 27: api.DeviceProfileService.Create:input_type -> api.CreateDeviceProfileRequest
24, // 28: api.DeviceProfileService.ListAdrAlgorithms:input_type -> google.protobuf.Empty 14, // 28: api.DeviceProfileService.Get:input_type -> api.GetDeviceProfileRequest
9, // 29: api.DeviceProfileService.Create:output_type -> api.CreateDeviceProfileResponse 16, // 29: api.DeviceProfileService.Update:input_type -> api.UpdateDeviceProfileRequest
11, // 30: api.DeviceProfileService.Get:output_type -> api.GetDeviceProfileResponse 17, // 30: api.DeviceProfileService.Delete:input_type -> api.DeleteDeviceProfileRequest
24, // 31: api.DeviceProfileService.Update:output_type -> google.protobuf.Empty 18, // 31: api.DeviceProfileService.List:input_type -> api.ListDeviceProfilesRequest
24, // 32: api.DeviceProfileService.Delete:output_type -> google.protobuf.Empty 28, // 32: api.DeviceProfileService.ListAdrAlgorithms:input_type -> google.protobuf.Empty
15, // 33: api.DeviceProfileService.List:output_type -> api.ListDeviceProfilesResponse 13, // 33: api.DeviceProfileService.Create:output_type -> api.CreateDeviceProfileResponse
16, // 34: api.DeviceProfileService.ListAdrAlgorithms:output_type -> api.ListDeviceProfileAdrAlgorithmsResponse 15, // 34: api.DeviceProfileService.Get:output_type -> api.GetDeviceProfileResponse
29, // [29:35] is the sub-list for method output_type 28, // 35: api.DeviceProfileService.Update:output_type -> google.protobuf.Empty
23, // [23:29] is the sub-list for method input_type 28, // 36: api.DeviceProfileService.Delete:output_type -> google.protobuf.Empty
23, // [23:23] is the sub-list for extension type_name 19, // 37: api.DeviceProfileService.List:output_type -> api.ListDeviceProfilesResponse
23, // [23:23] is the sub-list for extension extendee 20, // 38: api.DeviceProfileService.ListAdrAlgorithms:output_type -> api.ListDeviceProfileAdrAlgorithmsResponse
0, // [0:23] is the sub-list for field type_name 33, // [33:39] is the sub-list for method output_type
27, // [27:33] is the sub-list for method input_type
27, // [27:27] is the sub-list for extension type_name
27, // [27:27] is the sub-list for extension extendee
0, // [0:27] is the sub-list for field type_name
} }
func init() { file_api_device_profile_proto_init() } func init() { file_api_device_profile_proto_init() }
@ -2135,8 +2436,8 @@ func file_api_device_profile_proto_init() {
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_device_profile_proto_rawDesc, RawDescriptor: file_api_device_profile_proto_rawDesc,
NumEnums: 5, NumEnums: 8,
NumMessages: 15, NumMessages: 16,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -735,6 +735,7 @@ type ListDeviceProfileTemplatesRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of device-profile templates to return in the result-set. // Max number of device-profile templates to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -76,6 +76,55 @@ func (GatewayState) EnumDescriptor() ([]byte, []int) {
return file_api_gateway_proto_rawDescGZIP(), []int{0} return file_api_gateway_proto_rawDescGZIP(), []int{0}
} }
type ListGatewaysRequest_OrderBy int32
const (
ListGatewaysRequest_NAME ListGatewaysRequest_OrderBy = 0
ListGatewaysRequest_GATEWAY_ID ListGatewaysRequest_OrderBy = 1
ListGatewaysRequest_LAST_SEEN_AT ListGatewaysRequest_OrderBy = 2
)
// Enum value maps for ListGatewaysRequest_OrderBy.
var (
ListGatewaysRequest_OrderBy_name = map[int32]string{
0: "NAME",
1: "GATEWAY_ID",
2: "LAST_SEEN_AT",
}
ListGatewaysRequest_OrderBy_value = map[string]int32{
"NAME": 0,
"GATEWAY_ID": 1,
"LAST_SEEN_AT": 2,
}
)
func (x ListGatewaysRequest_OrderBy) Enum() *ListGatewaysRequest_OrderBy {
p := new(ListGatewaysRequest_OrderBy)
*p = x
return p
}
func (x ListGatewaysRequest_OrderBy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ListGatewaysRequest_OrderBy) Descriptor() protoreflect.EnumDescriptor {
return file_api_gateway_proto_enumTypes[1].Descriptor()
}
func (ListGatewaysRequest_OrderBy) Type() protoreflect.EnumType {
return &file_api_gateway_proto_enumTypes[1]
}
func (x ListGatewaysRequest_OrderBy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ListGatewaysRequest_OrderBy.Descriptor instead.
func (ListGatewaysRequest_OrderBy) EnumDescriptor() ([]byte, []int) {
return file_api_gateway_proto_rawDescGZIP(), []int{7, 0}
}
type Gateway struct { type Gateway struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -579,6 +628,7 @@ type ListGatewaysRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of gateways to return in the result-set. // Max number of gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -589,6 +639,10 @@ type ListGatewaysRequest struct {
TenantId string `protobuf:"bytes,4,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` TenantId string `protobuf:"bytes,4,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
// Multicast-group ID (UUID) to filter gateways on. // Multicast-group ID (UUID) to filter gateways on.
MulticastGroupId string `protobuf:"bytes,5,opt,name=multicast_group_id,json=multicastGroupId,proto3" json:"multicast_group_id,omitempty"` MulticastGroupId string `protobuf:"bytes,5,opt,name=multicast_group_id,json=multicastGroupId,proto3" json:"multicast_group_id,omitempty"`
// If set, the given value will be used to sort by (optional).
OrderBy ListGatewaysRequest_OrderBy `protobuf:"varint,6,opt,name=order_by,json=orderBy,proto3,enum=api.ListGatewaysRequest_OrderBy" json:"order_by,omitempty"`
// If set, the sorting direction will be decending (default = ascending) (optional).
OrderByDesc bool `protobuf:"varint,7,opt,name=order_by_desc,json=orderByDesc,proto3" json:"order_by_desc,omitempty"`
} }
func (x *ListGatewaysRequest) Reset() { func (x *ListGatewaysRequest) Reset() {
@ -656,6 +710,20 @@ func (x *ListGatewaysRequest) GetMulticastGroupId() string {
return "" return ""
} }
func (x *ListGatewaysRequest) GetOrderBy() ListGatewaysRequest_OrderBy {
if x != nil {
return x.OrderBy
}
return ListGatewaysRequest_NAME
}
func (x *ListGatewaysRequest) GetOrderByDesc() bool {
if x != nil {
return x.OrderByDesc
}
return false
}
type ListGatewaysResponse struct { type ListGatewaysResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -1256,6 +1324,7 @@ type ListRelayGatewaysRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of relay-gateways to return in the result-set. // Max number of relay-gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -1785,7 +1854,7 @@ var file_api_gateway_proto_rawDesc = []byte{
0x77, 0x61, 0x79, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x74, 0x77, 0x61, 0x79, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x74,
0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x22, 0xa6, 0x01, 0x0a, 0x13, 0x4c, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x22, 0xbe, 0x02, 0x0a, 0x13, 0x4c,
0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73,
@ -1796,282 +1865,292 @@ var file_api_gateway_proto_rawDesc = []byte{
0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61,
0x73, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x73, 0x74, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
0x09, 0x52, 0x10, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x09, 0x52, 0x10, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x49, 0x64, 0x22, 0x65, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x70, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18,
0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79,
0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x5f, 0x64, 0x65, 0x73,
0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79,
0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x48, 0x0a, 0x27, 0x47, 0x65, 0x44, 0x65, 0x73, 0x63, 0x22, 0x35, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x12,
0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x08, 0x0a, 0x04, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x41, 0x54,
0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x45, 0x57, 0x41, 0x59, 0x5f, 0x49, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x41, 0x53,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x5f, 0x41, 0x54, 0x10, 0x02, 0x22, 0x65, 0x0a, 0x14, 0x4c,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x61, 0x79, 0x49, 0x64, 0x22, 0xb2, 0x01, 0x0a, 0x28, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75,
0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43,
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02,
0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77,
0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6c, 0x73, 0x43, 0x65, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75,
0x74, 0x6c, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6c, 0x74, 0x22, 0x48, 0x0a, 0x27, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61,
0x6c, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x43, 0x65, 0x72, 0x74, 0x12, 0x39, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x22, 0xb2, 0x01, 0x0a,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x28, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0xd0, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6c, 0x73,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6c, 0x73,
0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x43, 0x65, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6c, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18,
0x77, 0x61, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6c, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x63, 0x61, 0x43, 0x65, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x35, 0x0a, 0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x03, 0x0a,
0x19, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x0a, 0x72, 0x78,
0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x09,
0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x0a, 0x74, 0x78, 0x5f,
0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x09, 0x74,
0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x13, 0x74, 0x78, 0x5f, 0x70,
0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73,
0x50, 0x65, 0x72, 0x46, 0x72, 0x65, 0x71, 0x12, 0x3d, 0x0a, 0x13, 0x72, 0x78, 0x5f, 0x70, 0x61,
0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65,
0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50,
0x65, 0x72, 0x46, 0x72, 0x65, 0x71, 0x12, 0x39, 0x0a, 0x11, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63,
0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69,
0x63, 0x52, 0x0e, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44,
0x72, 0x12, 0x39, 0x0a, 0x11, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f,
0x70, 0x65, 0x72, 0x5f, 0x64, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0e, 0x72, 0x78,
0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44, 0x72, 0x12, 0x41, 0x0a, 0x15,
0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x12, 0x74, 0x78, 0x50,
0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
0xa2, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75,
0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x03, 0x65, 0x6e, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72,
0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x13, 0x6d,
0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61,
0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x61,
0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x11, 0x77,
0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x50, 0x65,
0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x22, 0x50, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52,
0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12,
0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x49, 0x64, 0x22, 0x85, 0x02, 0x0a, 0x17, 0x47,
0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f,
0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x39,
0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64,
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x64, 0x41, 0x74, 0x12, 0x3c, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65,
0x6e, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41,
0x41, 0x74, 0x22, 0x65, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x74, 0x22, 0xd0, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d,
0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x0a, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x28, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12,
0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x35, 0x0a,
0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01,
0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x41, 0x67, 0x67, 0x72,
0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61,
0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x8b, 0x03, 0x0a, 0x14, 0x52, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x03, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65,
0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x74, 0x65, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x09, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18,
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x12, 0x3d, 0x0a, 0x13, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70,
0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x65, 0x72, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x74,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x46, 0x72, 0x65, 0x71, 0x12,
0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x3d, 0x0a, 0x13, 0x72, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x72, 0x5f, 0x66, 0x72, 0x65, 0x71, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10, 0x72, 0x78,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x46, 0x72, 0x65, 0x71, 0x12, 0x39,
0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3c, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x0a, 0x11, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72,
0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x5f, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0e, 0x74, 0x78, 0x50, 0x61, 0x63,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65, 0x72, 0x44, 0x72, 0x12, 0x39, 0x0a, 0x11, 0x72, 0x78, 0x5f,
0x65, 0x65, 0x6e, 0x41, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x64, 0x72, 0x18, 0x06,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65,
0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x74, 0x72, 0x69, 0x63, 0x52, 0x0e, 0x72, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50,
0x0a, 0x10, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x65, 0x72, 0x44, 0x72, 0x12, 0x41, 0x0a, 0x15, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65,
0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74,
0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x72, 0x69, 0x63, 0x52, 0x12, 0x74, 0x78, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x50, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa2, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x47,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d,
0x70, 0x69, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
0x0c, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x53, 0x0a, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x09, 0x52, 0x09, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05,
0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c,
0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xa1, 0x01, 0x0a,
0x22, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43,
0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x13, 0x6d, 0x61, 0x78, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x5f,
0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
0x52, 0x11, 0x6d, 0x61, 0x78, 0x4c, 0x6f, 0x61, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74,
0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x11, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x70, 0x65,
0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x52, 0x10,
0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65,
0x22, 0x50, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65,
0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74,
0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x79,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79,
0x49, 0x64, 0x22, 0xcd, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x49, 0x64, 0x22, 0x85, 0x02, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47,
0x77, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18,
0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6c, 0x61,
0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x47,
0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18,
0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x67, 0x69, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3c, 0x0a, 0x0c,
0x49, 0x64, 0x2a, 0x37, 0x0a, 0x0c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x74, 0x61, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01,
0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x45, 0x56, 0x45, 0x52, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a,
0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x32, 0xee, 0x0b, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x22, 0x65, 0x0a, 0x18, 0x4c, 0x69,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x55, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52,
0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18,
0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69,
0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49,
0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x5a, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x61, 0x64, 0x22, 0x6f, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61,
0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20,
0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x31, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77,
0x7d, 0x12, 0x6a, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75,
0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x6c, 0x74, 0x22, 0x8b, 0x03, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x74,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01, 0x2a, 0x1a, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61,
0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61,
0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5f, 0x0a, 0x79, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x6c, 0x65, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x02, 0x1c, 0x2a, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x52, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f,
0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12,
0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x3c, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x18,
0x79, 0x73, 0x12, 0xb1, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x12, 0x2c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x70, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x12, 0x27, 0x0a,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61,
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e,
0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09,
0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x82, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64,
0xd3, 0xe4, 0x93, 0x02, 0x31, 0x22, 0x2f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x22, 0x53, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47,
0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x36, 0x0a,
0x7d, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x18, 0x01,
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x77, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79,
0x72, 0x69, 0x63, 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x53, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52,
0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12,
0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x49, 0x64, 0x22, 0xcd, 0x01, 0x0a, 0x0c, 0x52,
0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74,
0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x26, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61,
0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x61,
0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x79, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61,
0x74, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69,
0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x2a, 0x37, 0x0a, 0x0c, 0x47, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x45,
0x56, 0x45, 0x52, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x4e,
0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, 0x4e,
0x45, 0x10, 0x02, 0x32, 0xee, 0x0b, 0x0a, 0x0e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x55, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 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, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x5a, 0x0a,
0x03, 0x47, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6a, 0x0a, 0x06, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 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, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x3a, 0x01,
0x2a, 0x1a, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73,
0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61,
0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5f, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 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, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x2a, 0x1a, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x52, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0xb1, 0x01, 0x0a, 0x19, 0x47,
0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72,
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47,
0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x6e,
0x65, 0x72, 0x61, 0x74, 0x65, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x22, 0x2f, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x74, 0x65, 0x2d, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x77,
0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x1d, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74,
0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4d, 0x65, 0x74, 0x72,
0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61,
0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44,
0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12,
0x26, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65,
0x12, 0x2d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x44, 0x75, 0x74, 0x79, 0x43, 0x79, 0x63, 0x6c,
0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x75, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x79, 0x2d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x12, 0x2d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67,
0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x77, 0x61, 0x79, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x64, 0x75, 0x74, 0x79, 0x2d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2d,
0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52,
0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x1b, 0x2e, 0x61, 0x70,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x12, 0x33, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47,
0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65,
0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x12, 0x33,
0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x78, 0x0a, 0x11, 0x4c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x65,
0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x74, 0x65,
0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f,
0x69, 0x64, 0x7d, 0x12, 0x78, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73,
0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52,
0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72,
0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0xa8, 0x01,
0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74,
0x65, 0x77, 0x61, 0x79, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 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, 0x5a, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x54, 0x3a, 0x01, 0x2a, 0x1a, 0x4f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74,
0x65, 0x77, 0x61, 0x79, 0x73, 0x12, 0xa8, 0x01, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74,
0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x1e, 0x2e, 0x61, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x72,
0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x6c,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12,
0x6d, 0x70, 0x74, 0x79, 0x22, 0x5a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x54, 0x3a, 0x01, 0x2a, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61,
0x4f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x72, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x74, 0x65, 0x6e, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x2a,
0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x33, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x74,
0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0x92, 0x01, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72,
0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0c, 0x47, 0x61, 0x74, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x77, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63,
0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x2a, 0x33, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x70, 0x69,
0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x2d, 0x67, 0x61, 0x74, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x69, 0xaa, 0x02, 0x0e, 0x43, 0x68, 0x69,
0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x41, 0x70, 0x69, 0xca, 0x02, 0x0e, 0x43, 0x68,
0x7d, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0x92, 0x01, 0x0a, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70, 0x69, 0xe2, 0x02, 0x1a, 0x47,
0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5c, 0x43, 0x68, 0x69, 0x72, 0x70,
0x70, 0x69, 0x42, 0x0c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x33,
0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73,
0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61,
0x70, 0x69, 0xaa, 0x02, 0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e,
0x41, 0x70, 0x69, 0xca, 0x02, 0x0e, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b,
0x5c, 0x41, 0x70, 0x69, 0xe2, 0x02, 0x1a, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x5c, 0x43, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5c, 0x41, 0x70,
0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -2086,113 +2165,115 @@ func file_api_gateway_proto_rawDescGZIP() []byte {
return file_api_gateway_proto_rawDescData return file_api_gateway_proto_rawDescData
} }
var file_api_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_api_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_api_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_api_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
var file_api_gateway_proto_goTypes = []any{ var file_api_gateway_proto_goTypes = []any{
(GatewayState)(0), // 0: api.GatewayState (GatewayState)(0), // 0: api.GatewayState
(*Gateway)(nil), // 1: api.Gateway (ListGatewaysRequest_OrderBy)(0), // 1: api.ListGatewaysRequest.OrderBy
(*GatewayListItem)(nil), // 2: api.GatewayListItem (*Gateway)(nil), // 2: api.Gateway
(*CreateGatewayRequest)(nil), // 3: api.CreateGatewayRequest (*GatewayListItem)(nil), // 3: api.GatewayListItem
(*GetGatewayRequest)(nil), // 4: api.GetGatewayRequest (*CreateGatewayRequest)(nil), // 4: api.CreateGatewayRequest
(*GetGatewayResponse)(nil), // 5: api.GetGatewayResponse (*GetGatewayRequest)(nil), // 5: api.GetGatewayRequest
(*UpdateGatewayRequest)(nil), // 6: api.UpdateGatewayRequest (*GetGatewayResponse)(nil), // 6: api.GetGatewayResponse
(*DeleteGatewayRequest)(nil), // 7: api.DeleteGatewayRequest (*UpdateGatewayRequest)(nil), // 7: api.UpdateGatewayRequest
(*ListGatewaysRequest)(nil), // 8: api.ListGatewaysRequest (*DeleteGatewayRequest)(nil), // 8: api.DeleteGatewayRequest
(*ListGatewaysResponse)(nil), // 9: api.ListGatewaysResponse (*ListGatewaysRequest)(nil), // 9: api.ListGatewaysRequest
(*GenerateGatewayClientCertificateRequest)(nil), // 10: api.GenerateGatewayClientCertificateRequest (*ListGatewaysResponse)(nil), // 10: api.ListGatewaysResponse
(*GenerateGatewayClientCertificateResponse)(nil), // 11: api.GenerateGatewayClientCertificateResponse (*GenerateGatewayClientCertificateRequest)(nil), // 11: api.GenerateGatewayClientCertificateRequest
(*GetGatewayMetricsRequest)(nil), // 12: api.GetGatewayMetricsRequest (*GenerateGatewayClientCertificateResponse)(nil), // 12: api.GenerateGatewayClientCertificateResponse
(*GetGatewayMetricsResponse)(nil), // 13: api.GetGatewayMetricsResponse (*GetGatewayMetricsRequest)(nil), // 13: api.GetGatewayMetricsRequest
(*GetGatewayDutyCycleMetricsRequest)(nil), // 14: api.GetGatewayDutyCycleMetricsRequest (*GetGatewayMetricsResponse)(nil), // 14: api.GetGatewayMetricsResponse
(*GetGatewayDutyCycleMetricsResponse)(nil), // 15: api.GetGatewayDutyCycleMetricsResponse (*GetGatewayDutyCycleMetricsRequest)(nil), // 15: api.GetGatewayDutyCycleMetricsRequest
(*GetRelayGatewayRequest)(nil), // 16: api.GetRelayGatewayRequest (*GetGatewayDutyCycleMetricsResponse)(nil), // 16: api.GetGatewayDutyCycleMetricsResponse
(*GetRelayGatewayResponse)(nil), // 17: api.GetRelayGatewayResponse (*GetRelayGatewayRequest)(nil), // 17: api.GetRelayGatewayRequest
(*ListRelayGatewaysRequest)(nil), // 18: api.ListRelayGatewaysRequest (*GetRelayGatewayResponse)(nil), // 18: api.GetRelayGatewayResponse
(*ListRelayGatewaysResponse)(nil), // 19: api.ListRelayGatewaysResponse (*ListRelayGatewaysRequest)(nil), // 19: api.ListRelayGatewaysRequest
(*RelayGatewayListItem)(nil), // 20: api.RelayGatewayListItem (*ListRelayGatewaysResponse)(nil), // 20: api.ListRelayGatewaysResponse
(*UpdateRelayGatewayRequest)(nil), // 21: api.UpdateRelayGatewayRequest (*RelayGatewayListItem)(nil), // 21: api.RelayGatewayListItem
(*DeleteRelayGatewayRequest)(nil), // 22: api.DeleteRelayGatewayRequest (*UpdateRelayGatewayRequest)(nil), // 22: api.UpdateRelayGatewayRequest
(*RelayGateway)(nil), // 23: api.RelayGateway (*DeleteRelayGatewayRequest)(nil), // 23: api.DeleteRelayGatewayRequest
nil, // 24: api.Gateway.TagsEntry (*RelayGateway)(nil), // 24: api.RelayGateway
nil, // 25: api.Gateway.MetadataEntry nil, // 25: api.Gateway.TagsEntry
nil, // 26: api.GatewayListItem.PropertiesEntry nil, // 26: api.Gateway.MetadataEntry
(*common.Location)(nil), // 27: common.Location nil, // 27: api.GatewayListItem.PropertiesEntry
(*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp (*common.Location)(nil), // 28: common.Location
(common.Aggregation)(0), // 29: common.Aggregation (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp
(*common.Metric)(nil), // 30: common.Metric (common.Aggregation)(0), // 30: common.Aggregation
(*emptypb.Empty)(nil), // 31: google.protobuf.Empty (*common.Metric)(nil), // 31: common.Metric
(*emptypb.Empty)(nil), // 32: google.protobuf.Empty
} }
var file_api_gateway_proto_depIdxs = []int32{ var file_api_gateway_proto_depIdxs = []int32{
27, // 0: api.Gateway.location:type_name -> common.Location 28, // 0: api.Gateway.location:type_name -> common.Location
24, // 1: api.Gateway.tags:type_name -> api.Gateway.TagsEntry 25, // 1: api.Gateway.tags:type_name -> api.Gateway.TagsEntry
25, // 2: api.Gateway.metadata:type_name -> api.Gateway.MetadataEntry 26, // 2: api.Gateway.metadata:type_name -> api.Gateway.MetadataEntry
27, // 3: api.GatewayListItem.location:type_name -> common.Location 28, // 3: api.GatewayListItem.location:type_name -> common.Location
26, // 4: api.GatewayListItem.properties:type_name -> api.GatewayListItem.PropertiesEntry 27, // 4: api.GatewayListItem.properties:type_name -> api.GatewayListItem.PropertiesEntry
28, // 5: api.GatewayListItem.created_at:type_name -> google.protobuf.Timestamp 29, // 5: api.GatewayListItem.created_at:type_name -> google.protobuf.Timestamp
28, // 6: api.GatewayListItem.updated_at:type_name -> google.protobuf.Timestamp 29, // 6: api.GatewayListItem.updated_at:type_name -> google.protobuf.Timestamp
28, // 7: api.GatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp 29, // 7: api.GatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp
0, // 8: api.GatewayListItem.state:type_name -> api.GatewayState 0, // 8: api.GatewayListItem.state:type_name -> api.GatewayState
1, // 9: api.CreateGatewayRequest.gateway:type_name -> api.Gateway 2, // 9: api.CreateGatewayRequest.gateway:type_name -> api.Gateway
1, // 10: api.GetGatewayResponse.gateway:type_name -> api.Gateway 2, // 10: api.GetGatewayResponse.gateway:type_name -> api.Gateway
28, // 11: api.GetGatewayResponse.created_at:type_name -> google.protobuf.Timestamp 29, // 11: api.GetGatewayResponse.created_at:type_name -> google.protobuf.Timestamp
28, // 12: api.GetGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp 29, // 12: api.GetGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp
28, // 13: api.GetGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp 29, // 13: api.GetGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp
1, // 14: api.UpdateGatewayRequest.gateway:type_name -> api.Gateway 2, // 14: api.UpdateGatewayRequest.gateway:type_name -> api.Gateway
2, // 15: api.ListGatewaysResponse.result:type_name -> api.GatewayListItem 1, // 15: api.ListGatewaysRequest.order_by:type_name -> api.ListGatewaysRequest.OrderBy
28, // 16: api.GenerateGatewayClientCertificateResponse.expires_at:type_name -> google.protobuf.Timestamp 3, // 16: api.ListGatewaysResponse.result:type_name -> api.GatewayListItem
28, // 17: api.GetGatewayMetricsRequest.start:type_name -> google.protobuf.Timestamp 29, // 17: api.GenerateGatewayClientCertificateResponse.expires_at:type_name -> google.protobuf.Timestamp
28, // 18: api.GetGatewayMetricsRequest.end:type_name -> google.protobuf.Timestamp 29, // 18: api.GetGatewayMetricsRequest.start:type_name -> google.protobuf.Timestamp
29, // 19: api.GetGatewayMetricsRequest.aggregation:type_name -> common.Aggregation 29, // 19: api.GetGatewayMetricsRequest.end:type_name -> google.protobuf.Timestamp
30, // 20: api.GetGatewayMetricsResponse.rx_packets:type_name -> common.Metric 30, // 20: api.GetGatewayMetricsRequest.aggregation:type_name -> common.Aggregation
30, // 21: api.GetGatewayMetricsResponse.tx_packets:type_name -> common.Metric 31, // 21: api.GetGatewayMetricsResponse.rx_packets:type_name -> common.Metric
30, // 22: api.GetGatewayMetricsResponse.tx_packets_per_freq:type_name -> common.Metric 31, // 22: api.GetGatewayMetricsResponse.tx_packets:type_name -> common.Metric
30, // 23: api.GetGatewayMetricsResponse.rx_packets_per_freq:type_name -> common.Metric 31, // 23: api.GetGatewayMetricsResponse.tx_packets_per_freq:type_name -> common.Metric
30, // 24: api.GetGatewayMetricsResponse.tx_packets_per_dr:type_name -> common.Metric 31, // 24: api.GetGatewayMetricsResponse.rx_packets_per_freq:type_name -> common.Metric
30, // 25: api.GetGatewayMetricsResponse.rx_packets_per_dr:type_name -> common.Metric 31, // 25: api.GetGatewayMetricsResponse.tx_packets_per_dr:type_name -> common.Metric
30, // 26: api.GetGatewayMetricsResponse.tx_packets_per_status:type_name -> common.Metric 31, // 26: api.GetGatewayMetricsResponse.rx_packets_per_dr:type_name -> common.Metric
28, // 27: api.GetGatewayDutyCycleMetricsRequest.start:type_name -> google.protobuf.Timestamp 31, // 27: api.GetGatewayMetricsResponse.tx_packets_per_status:type_name -> common.Metric
28, // 28: api.GetGatewayDutyCycleMetricsRequest.end:type_name -> google.protobuf.Timestamp 29, // 28: api.GetGatewayDutyCycleMetricsRequest.start:type_name -> google.protobuf.Timestamp
30, // 29: api.GetGatewayDutyCycleMetricsResponse.max_load_percentage:type_name -> common.Metric 29, // 29: api.GetGatewayDutyCycleMetricsRequest.end:type_name -> google.protobuf.Timestamp
30, // 30: api.GetGatewayDutyCycleMetricsResponse.window_percentage:type_name -> common.Metric 31, // 30: api.GetGatewayDutyCycleMetricsResponse.max_load_percentage:type_name -> common.Metric
23, // 31: api.GetRelayGatewayResponse.relay_gateway:type_name -> api.RelayGateway 31, // 31: api.GetGatewayDutyCycleMetricsResponse.window_percentage:type_name -> common.Metric
28, // 32: api.GetRelayGatewayResponse.created_at:type_name -> google.protobuf.Timestamp 24, // 32: api.GetRelayGatewayResponse.relay_gateway:type_name -> api.RelayGateway
28, // 33: api.GetRelayGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp 29, // 33: api.GetRelayGatewayResponse.created_at:type_name -> google.protobuf.Timestamp
28, // 34: api.GetRelayGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp 29, // 34: api.GetRelayGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp
20, // 35: api.ListRelayGatewaysResponse.result:type_name -> api.RelayGatewayListItem 29, // 35: api.GetRelayGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp
28, // 36: api.RelayGatewayListItem.created_at:type_name -> google.protobuf.Timestamp 21, // 36: api.ListRelayGatewaysResponse.result:type_name -> api.RelayGatewayListItem
28, // 37: api.RelayGatewayListItem.updated_at:type_name -> google.protobuf.Timestamp 29, // 37: api.RelayGatewayListItem.created_at:type_name -> google.protobuf.Timestamp
28, // 38: api.RelayGatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp 29, // 38: api.RelayGatewayListItem.updated_at:type_name -> google.protobuf.Timestamp
0, // 39: api.RelayGatewayListItem.state:type_name -> api.GatewayState 29, // 39: api.RelayGatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp
23, // 40: api.UpdateRelayGatewayRequest.relay_gateway:type_name -> api.RelayGateway 0, // 40: api.RelayGatewayListItem.state:type_name -> api.GatewayState
3, // 41: api.GatewayService.Create:input_type -> api.CreateGatewayRequest 24, // 41: api.UpdateRelayGatewayRequest.relay_gateway:type_name -> api.RelayGateway
4, // 42: api.GatewayService.Get:input_type -> api.GetGatewayRequest 4, // 42: api.GatewayService.Create:input_type -> api.CreateGatewayRequest
6, // 43: api.GatewayService.Update:input_type -> api.UpdateGatewayRequest 5, // 43: api.GatewayService.Get:input_type -> api.GetGatewayRequest
7, // 44: api.GatewayService.Delete:input_type -> api.DeleteGatewayRequest 7, // 44: api.GatewayService.Update:input_type -> api.UpdateGatewayRequest
8, // 45: api.GatewayService.List:input_type -> api.ListGatewaysRequest 8, // 45: api.GatewayService.Delete:input_type -> api.DeleteGatewayRequest
10, // 46: api.GatewayService.GenerateClientCertificate:input_type -> api.GenerateGatewayClientCertificateRequest 9, // 46: api.GatewayService.List:input_type -> api.ListGatewaysRequest
12, // 47: api.GatewayService.GetMetrics:input_type -> api.GetGatewayMetricsRequest 11, // 47: api.GatewayService.GenerateClientCertificate:input_type -> api.GenerateGatewayClientCertificateRequest
14, // 48: api.GatewayService.GetDutyCycleMetrics:input_type -> api.GetGatewayDutyCycleMetricsRequest 13, // 48: api.GatewayService.GetMetrics:input_type -> api.GetGatewayMetricsRequest
16, // 49: api.GatewayService.GetRelayGateway:input_type -> api.GetRelayGatewayRequest 15, // 49: api.GatewayService.GetDutyCycleMetrics:input_type -> api.GetGatewayDutyCycleMetricsRequest
18, // 50: api.GatewayService.ListRelayGateways:input_type -> api.ListRelayGatewaysRequest 17, // 50: api.GatewayService.GetRelayGateway:input_type -> api.GetRelayGatewayRequest
21, // 51: api.GatewayService.UpdateRelayGateway:input_type -> api.UpdateRelayGatewayRequest 19, // 51: api.GatewayService.ListRelayGateways:input_type -> api.ListRelayGatewaysRequest
22, // 52: api.GatewayService.DeleteRelayGateway:input_type -> api.DeleteRelayGatewayRequest 22, // 52: api.GatewayService.UpdateRelayGateway:input_type -> api.UpdateRelayGatewayRequest
31, // 53: api.GatewayService.Create:output_type -> google.protobuf.Empty 23, // 53: api.GatewayService.DeleteRelayGateway:input_type -> api.DeleteRelayGatewayRequest
5, // 54: api.GatewayService.Get:output_type -> api.GetGatewayResponse 32, // 54: api.GatewayService.Create:output_type -> google.protobuf.Empty
31, // 55: api.GatewayService.Update:output_type -> google.protobuf.Empty 6, // 55: api.GatewayService.Get:output_type -> api.GetGatewayResponse
31, // 56: api.GatewayService.Delete:output_type -> google.protobuf.Empty 32, // 56: api.GatewayService.Update:output_type -> google.protobuf.Empty
9, // 57: api.GatewayService.List:output_type -> api.ListGatewaysResponse 32, // 57: api.GatewayService.Delete:output_type -> google.protobuf.Empty
11, // 58: api.GatewayService.GenerateClientCertificate:output_type -> api.GenerateGatewayClientCertificateResponse 10, // 58: api.GatewayService.List:output_type -> api.ListGatewaysResponse
13, // 59: api.GatewayService.GetMetrics:output_type -> api.GetGatewayMetricsResponse 12, // 59: api.GatewayService.GenerateClientCertificate:output_type -> api.GenerateGatewayClientCertificateResponse
15, // 60: api.GatewayService.GetDutyCycleMetrics:output_type -> api.GetGatewayDutyCycleMetricsResponse 14, // 60: api.GatewayService.GetMetrics:output_type -> api.GetGatewayMetricsResponse
17, // 61: api.GatewayService.GetRelayGateway:output_type -> api.GetRelayGatewayResponse 16, // 61: api.GatewayService.GetDutyCycleMetrics:output_type -> api.GetGatewayDutyCycleMetricsResponse
19, // 62: api.GatewayService.ListRelayGateways:output_type -> api.ListRelayGatewaysResponse 18, // 62: api.GatewayService.GetRelayGateway:output_type -> api.GetRelayGatewayResponse
31, // 63: api.GatewayService.UpdateRelayGateway:output_type -> google.protobuf.Empty 20, // 63: api.GatewayService.ListRelayGateways:output_type -> api.ListRelayGatewaysResponse
31, // 64: api.GatewayService.DeleteRelayGateway:output_type -> google.protobuf.Empty 32, // 64: api.GatewayService.UpdateRelayGateway:output_type -> google.protobuf.Empty
53, // [53:65] is the sub-list for method output_type 32, // 65: api.GatewayService.DeleteRelayGateway:output_type -> google.protobuf.Empty
41, // [41:53] is the sub-list for method input_type 54, // [54:66] is the sub-list for method output_type
41, // [41:41] is the sub-list for extension type_name 42, // [42:54] is the sub-list for method input_type
41, // [41:41] is the sub-list for extension extendee 42, // [42:42] is the sub-list for extension type_name
0, // [0:41] is the sub-list for field type_name 42, // [42:42] is the sub-list for extension extendee
0, // [0:42] is the sub-list for field type_name
} }
func init() { file_api_gateway_proto_init() } func init() { file_api_gateway_proto_init() }
@ -2205,7 +2286,7 @@ func file_api_gateway_proto_init() {
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_gateway_proto_rawDesc, RawDescriptor: file_api_gateway_proto_rawDesc,
NumEnums: 1, NumEnums: 2,
NumMessages: 26, NumMessages: 26,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,

View File

@ -251,6 +251,7 @@ type ListApiKeysRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of items to return. // Max number of items to return.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -630,6 +631,7 @@ type GlobalSearchRequest struct {
// Search query. // Search query.
Search string `protobuf:"bytes,1,opt,name=search,proto3" json:"search,omitempty"` Search string `protobuf:"bytes,1,opt,name=search,proto3" json:"search,omitempty"`
// Max number of results to return. // Max number of results to return.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset offset of the result-set (for pagination). // Offset offset of the result-set (for pagination).
Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -685,6 +685,7 @@ type ListMulticastGroupsRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of multicast groups to return in the result-set. // Max number of multicast groups to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -84,6 +84,7 @@ type ListRelaysRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of devices to return in the result-set. // Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -313,6 +314,7 @@ type ListRelayDevicesRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of multicast groups to return in the result-set. // Max number of multicast groups to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -571,6 +571,7 @@ type ListTenantsRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -1172,6 +1173,7 @@ type ListTenantUsersRequest struct {
// Tenant ID (UUID). // Tenant ID (UUID).
TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` TenantId string `protobuf:"bytes,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -590,6 +590,7 @@ type ListUsersRequest struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"`
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`

6
api/go/go.mod vendored
View File

@ -10,8 +10,8 @@ require (
require ( require (
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa // indirect
) )

12
api/go/go.sum vendored
View File

@ -2,12 +2,12 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa h1:Jt1XW5PaLXF1/ePZrznsh/aAUvI7Adfc3LY1dAKlzRs= google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa h1:Jt1XW5PaLXF1/ePZrznsh/aAUvI7Adfc3LY1dAKlzRs=
google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:K4kfzHtI0kqWA79gecJarFtDn/Mls+GxQcg3Zox91Ac= google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:K4kfzHtI0kqWA79gecJarFtDn/Mls+GxQcg3Zox91Ac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa h1:RBgMaUMP+6soRkik4VoN8ojR2nex2TqZwjSSogic+eo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa h1:RBgMaUMP+6soRkik4VoN8ojR2nex2TqZwjSSogic+eo=

View File

@ -29,6 +29,7 @@ api:
$(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/gateway.proto $(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/gateway.proto
$(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/multicast_group.proto $(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/multicast_group.proto
$(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/relay.proto $(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/relay.proto
$(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/fuota.proto
integration: integration:
mkdir -p integration mkdir -p integration

View File

@ -1,6 +1,6 @@
{ {
"name": "@chirpstack/chirpstack-api-grpc-web", "name": "@chirpstack/chirpstack-api-grpc-web",
"version": "4.11.1-test.1", "version": "4.12.0-test.2",
"description": "Chirpstack gRPC-web API", "description": "Chirpstack gRPC-web API",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {

View File

@ -8,7 +8,7 @@ plugins {
} }
group = "io.chirpstack" group = "io.chirpstack"
version = "4.11.1-test.1" version = "4.12.0-test.2"
repositories { repositories {
mavenCentral() mavenCentral()

1
api/js/Makefile vendored
View File

@ -28,6 +28,7 @@ api:
$(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/gateway.proto $(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/gateway.proto
$(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/multicast_group.proto $(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/multicast_group.proto
$(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/relay.proto $(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/relay.proto
$(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/fuota.proto
integration: integration:
$(PROTOC_PATH) ${PROTOC_ARGS} ../proto/integration/integration.proto $(PROTOC_PATH) ${PROTOC_ARGS} ../proto/integration/integration.proto

2
api/js/package.json vendored
View File

@ -1,6 +1,6 @@
{ {
"name": "@chirpstack/chirpstack-api", "name": "@chirpstack/chirpstack-api",
"version": "4.11.1-test.1", "version": "4.12.0-test.2",
"description": "Chirpstack JS and TS API", "description": "Chirpstack JS and TS API",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {

View File

@ -9,7 +9,7 @@ plugins {
} }
group = "io.chirpstack" group = "io.chirpstack"
version = "4.11.1-test.1" version = "4.12.0-test.2"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -3,7 +3,7 @@
"description": "Chirpstack PHP API", "description": "Chirpstack PHP API",
"license": "MIT", "license": "MIT",
"type": "library", "type": "library",
"version": "4.11.1-test.1", "version": "4.12.0-test.2",
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.0.0",
"grpc/grpc": "^v1.57.0", "grpc/grpc": "^v1.57.0",

View File

@ -427,6 +427,20 @@ service ApplicationService {
post : "/api/applications/{application_id}/integrations/mqtt/certificate" post : "/api/applications/{application_id}/integrations/mqtt/certificate"
}; };
} }
// List device-profiles used within the given application.
rpc ListDeviceProfiles(ListApplicationDeviceProfilesRequest) returns (ListApplicationDeviceProfilesResponse) {
option (google.api.http) = {
get: "/api/applications/{application_id}/device-profiles"
};
}
// List device tags used within the given application.
rpc ListDeviceTags(ListApplicationDeviceTagsRequest) returns (ListApplicationDeviceTagsResponse) {
option (google.api.http) = {
get: "/api/applications/{application_id}/device-tags"
};
}
} }
enum Encoding { enum Encoding {
@ -529,6 +543,7 @@ message DeleteApplicationRequest {
message ListApplicationsRequest { message ListApplicationsRequest {
// Max number of applications to return in the result-set. // Max number of applications to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -1098,3 +1113,39 @@ message GenerateMqttIntegrationClientCertificateResponse {
// Expires at defines the expiration date of the certificate. // Expires at defines the expiration date of the certificate.
google.protobuf.Timestamp expires_at = 4; google.protobuf.Timestamp expires_at = 4;
} }
message ApplicationDeviceProfileListItem {
// Device-profile ID (UUID).
string id = 1;
// Name.
string name = 2;
}
message ListApplicationDeviceProfilesRequest {
// Application ID (UUID).
string application_id = 1;
};
message ListApplicationDeviceProfilesResponse {
// Device-profiles.
repeated ApplicationDeviceProfileListItem result = 1;
}
message ApplicationDeviceTagListItem {
// Tag key.
string key = 1;
// Used values.
repeated string values = 2;
}
message ListApplicationDeviceTagsRequest {
// Application ID (UUID).
string application_id = 1;
}
message ListApplicationDeviceTagsResponse {
// Device tags.
repeated ApplicationDeviceTagListItem result = 1;
}

View File

@ -262,6 +262,9 @@ message DeviceListItem {
// Device status. // Device status.
DeviceStatus device_status = 9; DeviceStatus device_status = 9;
// Device tags.
map<string, string> tags = 10;
} }
message DeviceKeys { message DeviceKeys {
@ -275,6 +278,11 @@ message DeviceKeys {
// Application root key (128 bit). // Application root key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.1.x devices! // Note: This field only needs to be set for LoRaWAN 1.1.x devices!
string app_key = 3; string app_key = 3;
// Gen App Key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.0.x devices that
// implement TS005 (remote multicast setup).
string gen_app_key = 4;
} }
message CreateDeviceRequest { message CreateDeviceRequest {
@ -319,6 +327,7 @@ message DeleteDeviceRequest {
message ListDevicesRequest { message ListDevicesRequest {
// Max number of devices to return in the result-set. // Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -332,6 +341,25 @@ message ListDevicesRequest {
// Multicst-group ID (UUID) to filter devices on. // Multicst-group ID (UUID) to filter devices on.
string multicast_group_id = 5; string multicast_group_id = 5;
enum OrderBy {
NAME = 0;
DEV_EUI = 1;
LAST_SEEN_AT = 2;
DEVICE_PROFILE_NAME = 3;
}
// If set, the given value will be used to sort by (optional).
OrderBy order_by = 6;
// If set, the sorting direction will be decending (default = ascending) (optional).
bool order_by_desc = 7;
// Tags to filter devices on.
map<string, string> tags = 8;
// Device-profile ID (UUID) to filter devices on.
string device_profile_id = 9;
} }
message ListDevicesResponse { message ListDevicesResponse {

View File

@ -98,6 +98,39 @@ enum RelayModeActivation {
END_DEVICE_CONTROLLED = 3; END_DEVICE_CONTROLLED = 3;
} }
enum Ts003Version {
// Not implemented.
TS003_NOT_IMPLEMENTED = 0;
// v1.0.0.
TS003_V100 = 1;
// v2.0.0
TS003_v200 = 2;
}
enum Ts004Version {
// Not implemented.
TS004_NOT_IMPLEMENTED = 0;
// v1.0.0.
TS004_V100 = 1;
// v2.0.0
TS004_V200 = 2;
}
enum Ts005Version {
// Not implemented.
TS005_NOT_IMPLEMENTED = 0;
// v1.0.0.
TS005_V100 = 1;
// v2.0.0
TS005_V200 = 2;
}
// DeviceProfileService is the service providing API methods for managing // DeviceProfileService is the service providing API methods for managing
// device-profiles. // device-profiles.
service DeviceProfileService { service DeviceProfileService {
@ -421,6 +454,9 @@ message DeviceProfile {
// it. // it.
// Valid options are 1 - 15 (0 = always use system RX1 Delay). // Valid options are 1 - 15 (0 = always use system RX1 Delay).
uint32 rx1_delay = 53; uint32 rx1_delay = 53;
// Application Layer parameters.
AppLayerParams app_layer_params = 54;
} }
message Measurement { message Measurement {
@ -431,6 +467,26 @@ message Measurement {
MeasurementKind kind = 3; MeasurementKind kind = 3;
} }
message AppLayerParams {
// TS003 version (Application Layer Clock Sync).
Ts003Version ts003_version = 1;
// TS003 fPort.
uint32 ts003_f_port = 2;
// TS004 version (Fragmented Data Block Transport).
Ts004Version ts004_version = 3;
// TS004 fPort.
uint32 ts004_f_port = 4;
// TS005 version (Remote Multicast Setup).
Ts005Version ts005_version = 5;
// TS005 fPort.
uint32 ts005_f_port = 6;
}
message DeviceProfileListItem { message DeviceProfileListItem {
// Device-profile ID (UUID). // Device-profile ID (UUID).
string id = 1; string id = 1;
@ -501,6 +557,7 @@ message DeleteDeviceProfileRequest {
message ListDeviceProfilesRequest { message ListDeviceProfilesRequest {
// Max number of device-profiles to return in the result-set. // Max number of device-profiles to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -231,6 +231,7 @@ message DeleteDeviceProfileTemplateRequest {
message ListDeviceProfileTemplatesRequest { message ListDeviceProfileTemplatesRequest {
// Max number of device-profile templates to return in the result-set. // Max number of device-profile templates to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

401
api/proto/api/fuota.proto vendored Normal file
View File

@ -0,0 +1,401 @@
syntax = "proto3";
package api;
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
option java_package = "io.chirpstack.api";
option java_multiple_files = true;
option java_outer_classname = "FuotaProto";
option csharp_namespace = "Chirpstack.Api";
option php_namespace = "Chirpstack\\Api";
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "common/common.proto";
import "api/multicast_group.proto";
// FuotaService is the service providing API methods for FUOTA deployments.
service FuotaService {
// Create the given FUOTA deployment.
rpc CreateDeployment(CreateFuotaDeploymentRequest) returns (CreateFuotaDeploymentResponse) {}
// Get the FUOTA deployment for the given ID.
rpc GetDeployment(GetFuotaDeploymentRequest) returns (GetFuotaDeploymentResponse) {}
// Update the given FUOTA deployment.
rpc UpdateDeployment(UpdateFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// Delete the FUOTA deployment for the given ID.
rpc DeleteDeployment(DeleteFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// Start the FUOTA deployment.
rpc StartDeployment(StartFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// List the FUOTA deployments.
rpc ListDeployments(ListFuotaDeploymentsRequest) returns (ListFuotaDeploymentsResponse) {}
// Add the given DevEUIs to the FUOTA deployment.
rpc AddDevices(AddDevicesToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// Remove the given DevEUIs from the FUOTA deployment.
rpc RemoveDevices(RemoveDevicesFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// List FUOTA Deployment devices.
rpc ListDevices(ListFuotaDeploymentDevicesRequest) returns (ListFuotaDeploymentDevicesResponse) {}
// Add the given Gateway IDs to the FUOTA deployment.
// By default, ChirpStack will automatically select the minimum amount of
// gateways needed to cover all devices within the multicast-group. Setting
// the gateways manually overrides this behaviour.
rpc AddGateways(AddGatewaysToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// List the gateways added to the FUOTA deployment.
rpc ListGateways(ListFuotaDeploymentGatewaysRequest) returns (ListFuotaDeploymentGatewaysResponse) {}
// Remove the given Gateway IDs from the FUOTA deployment.
rpc RemoveGateways(RemoveGatewaysFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// GetLogs returns the logs for the FUOTA deployment.
// List jobs for the given FUOTA deployment.
rpc ListJobs(ListFuotaDeploymentJobsRequest) returns (ListFuotaDeploymentJobsResponse) {}
}
enum RequestFragmentationSessionStatus {
// Do not request the fragmentation-session status.
NO_REQUEST = 0;
// Enqueue the fragmentation-session status request command directly after
// enqueueing the fragmentation-session fragments. This is the recommended
// option for Class-A devices as the status request will stay in the
// downlink queue until the device sends its next uplink.
AFTER_FRAGMENT_ENQUEUE = 1;
// Enqueue the fragmentation-session status request after the multicast
// session-timeout. This is the recommended option for Class-B and -C
// devices as selecting AFTER_FRAGMENT_ENQUEUE will likely cause the NS
// to schedule the downlink frame during the FUOTA multicast-session.
AFTER_SESSION_TIMEOUT = 2;
}
message FuotaDeployment {
// Deployment ID.
// This value is automatically set on create.
string id = 1;
// Application ID.
string application_id = 2;
// Device-profile ID.
string device_profile_id = 3;
// Deployment name.
string name = 4;
// Multicast-group type.
MulticastGroupType multicast_group_type = 5;
// Multicast-group scheduling type (Class-C only).
MulticastGroupSchedulingType multicast_class_c_scheduling_type = 6;
// Multicast data-rate.
uint32 multicast_dr = 7;
// Multicast ping-slot period (Class-B only).
uint32 multicast_class_b_ping_slot_nb_k = 8;
// Multicast frequency (Hz).
uint32 multicast_frequency = 9;
// Multicast timeout.
// This defines the timeout of the multicast-session.
// Please refer to the Remote Multicast Setup specification as this field
// has a different meaning for Class-B and Class-C groups.
uint32 multicast_timeout = 10;
// Calculate multicast timeout.
// If set to true, ChirpStack will calculate the multicast-timeout.
bool calculate_multicast_timeout = 11;
// The number of times ChirpStack will retry an unicast command
// before it considers it to be failed.
uint32 unicast_max_retry_count = 12;
// Fragmentation size.
// This defines the size of each payload fragment. Please refer to the
// Regional Parameters specification for the maximum payload sizes
// per data-rate and region.
uint32 fragmentation_fragment_size = 13;
// Calculate fragmentation size.
// If set to true, ChirpStack will calculate the fragmentation size.
bool calculate_fragmentation_fragment_size = 14;
// Fragmentation redundancy percentage.
// The number represents the percentage (0 - 100) of redundant messages
// to send.
uint32 fragmentation_redundancy_percentage = 15;
// Fragmentation session index.
uint32 fragmentation_session_index = 16;
// Fragmentation matrix.
uint32 fragmentation_matrix = 17;
// Block ack delay.
uint32 fragmentation_block_ack_delay = 18;
// Descriptor (4 bytes).
bytes fragmentation_descriptor = 19;
// Request fragmentation session status.
RequestFragmentationSessionStatus request_fragmentation_session_status = 20;
// Payload.
// The FUOTA payload to send.
bytes payload = 21;
// Set device tags on complete.
map<string, string> on_complete_set_device_tags = 22;
}
message FuotaDeploymentListItem {
// ID.
string id = 1;
// Created at timestamp.
google.protobuf.Timestamp created_at = 2;
// Updated at timestamp.
google.protobuf.Timestamp updated_at = 3;
// Started at timestamp.
google.protobuf.Timestamp started_at = 4;
// Completed at timestamp.
google.protobuf.Timestamp completed_at = 5;
// Name.
string name = 6;
}
message FuotaDeploymentDeviceListItem {
// ID.
string fuota_deployment_id = 1;
// DevEUI.
string dev_eui = 2;
// Created at timestamp.
google.protobuf.Timestamp created_at = 3;
// Completed at timestamp.
google.protobuf.Timestamp completed_at = 4;
// McGroupSetup completed at timestamp.
google.protobuf.Timestamp mc_group_setup_completed_at = 5;
// McSession completed at timestamp.
google.protobuf.Timestamp mc_session_completed_at = 6;
// FragSessionSetup completed at timestamp.
google.protobuf.Timestamp frag_session_setup_completed_at = 7;
// FragStatus completed at timestamp.
google.protobuf.Timestamp frag_status_completed_at = 8;
// Error message.
string error_msg = 9;
}
message FuotaDeploymentGatewayListItem {
// ID.
string fuota_deployment_id = 1;
// Gateway ID.
string gateway_id = 2;
// Created at timestamp.
google.protobuf.Timestamp created_at = 3;
}
message CreateFuotaDeploymentRequest {
// Deployment.
FuotaDeployment deployment = 1;
}
message CreateFuotaDeploymentResponse {
// ID of the created deployment.
string id = 1;
}
message GetFuotaDeploymentRequest {
// FUOTA Deployment ID.
string id = 1;
}
message GetFuotaDeploymentResponse {
// FUOTA Deployment.
FuotaDeployment deployment = 1;
// Created at timestamp.
google.protobuf.Timestamp created_at = 2;
// Updated at timestamp.
google.protobuf.Timestamp updated_at = 3;
// Started at timestamp.
google.protobuf.Timestamp started_at = 4;
// Completed at timestamp.
google.protobuf.Timestamp completed_at = 5;
}
message UpdateFuotaDeploymentRequest {
// Deployment.
FuotaDeployment deployment = 1;
}
message DeleteFuotaDeploymentRequest {
// FUOTA deployment ID.
string id = 1;
}
message StartFuotaDeploymentRequest {
// FUOTA deployment ID.
string id = 1;
}
message ListFuotaDeploymentsRequest {
// Max number of FUOTA deployments to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1;
// Offset in the result-set (for pagination).
uint32 offset = 2;
// Application ID to list the FUOTA Deployments for.
// This filter is mandatory.
string application_id = 3;
}
message ListFuotaDeploymentsResponse {
// Total number of FUOTA Deployments.
uint32 total_count = 1;
// Result-test.
repeated FuotaDeploymentListItem result = 2;
}
message AddDevicesToFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// DevEUIs.
// Note that the DevEUIs must share the same device-profile as assigned to
// the FUOTA Deployment.
repeated string dev_euis = 2;
}
message RemoveDevicesFromFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// DevEUIs.
repeated string dev_euis = 2;
}
message ListFuotaDeploymentDevicesRequest {
// Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1;
// Offset in the result-set (for pagination).
uint32 offset = 2;
// FUOTA Deployment ID.
string fuota_deployment_id = 3;
}
message ListFuotaDeploymentDevicesResponse {
// Total number of devices.
uint32 total_count = 1;
// Result-set.
repeated FuotaDeploymentDeviceListItem result = 2;
}
message AddGatewaysToFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// Gateway IDs.
// Note that the Gateways must be under the same tenant as the FUOTA Deployment.
repeated string gateway_ids = 2;
}
message RemoveGatewaysFromFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// Gateway IDs.
repeated string gateway_ids = 2;
}
message ListFuotaDeploymentGatewaysRequest {
// Max number of gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1;
// Offset in the result-set (for pagination).
uint32 offset = 2;
// FUOTA Deployment ID.
string fuota_deployment_id = 3;
}
message ListFuotaDeploymentGatewaysResponse {
// Total number of gateways.
uint32 total_count = 1;
// Result-set.
repeated FuotaDeploymentGatewayListItem result = 2;
}
message ListFuotaDeploymentJobsRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
}
message ListFuotaDeploymentJobsResponse {
// Jobs.
repeated FuotaDeploymentJob jobs = 1;
}
message FuotaDeploymentJob {
// Job identifier.
string job = 1;
// Created at.
google.protobuf.Timestamp created_at = 2;
// Completed at.
google.protobuf.Timestamp completed_at = 3;
// Max. retry count.
uint32 max_retry_count = 4;
// Attempt count.
uint32 attempt_count = 5;
// Scheduler run after.
google.protobuf.Timestamp scheduler_run_after = 6;
// Warning message.
string warning_msg = 7;
// Error message.
string error_msg = 8;
}

View File

@ -215,6 +215,7 @@ message DeleteGatewayRequest {
message ListGatewaysRequest { message ListGatewaysRequest {
// Max number of gateways to return in the result-set. // Max number of gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -229,6 +230,18 @@ message ListGatewaysRequest {
// Multicast-group ID (UUID) to filter gateways on. // Multicast-group ID (UUID) to filter gateways on.
string multicast_group_id = 5; string multicast_group_id = 5;
enum OrderBy {
NAME = 0;
GATEWAY_ID = 1;
LAST_SEEN_AT = 2;
}
// If set, the given value will be used to sort by (optional).
OrderBy order_by = 6;
// If set, the sorting direction will be decending (default = ascending) (optional).
bool order_by_desc = 7;
} }
message ListGatewaysResponse { message ListGatewaysResponse {
@ -338,6 +351,7 @@ message GetRelayGatewayResponse {
message ListRelayGatewaysRequest { message ListRelayGatewaysRequest {
// Max number of relay-gateways to return in the result-set. // Max number of relay-gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -109,6 +109,7 @@ message DeleteApiKeyRequest {
message ListApiKeysRequest { message ListApiKeysRequest {
// Max number of items to return. // Max number of items to return.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -177,6 +178,7 @@ message GlobalSearchRequest {
string search = 1; string search = 1;
// Max number of results to return. // Max number of results to return.
// If not set, it will be treated as 0, and the response will only return the total_count.
int64 limit = 2; int64 limit = 2;
// Offset offset of the result-set (for pagination). // Offset offset of the result-set (for pagination).

View File

@ -237,6 +237,7 @@ message DeleteMulticastGroupRequest {
message ListMulticastGroupsRequest { message ListMulticastGroupsRequest {
// Max number of multicast groups to return in the result-set. // Max number of multicast groups to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -56,6 +56,7 @@ message RelayListItem {
message ListRelaysRequest { message ListRelaysRequest {
// Max number of devices to return in the result-set. // Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -92,6 +93,7 @@ message RemoveRelayDeviceRequest {
message ListRelayDevicesRequest { message ListRelayDevicesRequest {
// Max number of multicast groups to return in the result-set. // Max number of multicast groups to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -200,6 +200,7 @@ message DeleteTenantRequest {
message ListTenantsRequest { message ListTenantsRequest {
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -313,6 +314,7 @@ message ListTenantUsersRequest {
string tenant_id = 1; string tenant_id = 1;
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 2; uint32 limit = 2;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -161,6 +161,7 @@ message DeleteUserRequest {
message ListUsersRequest { message ListUsersRequest {
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -18,7 +18,7 @@ CLASSIFIERS = [
setup( setup(
name='chirpstack-api', name='chirpstack-api',
version = "4.11.1-test.1", version = "4.12.0-test.2",
url='https://github.com/brocaar/chirpstack-api', url='https://github.com/brocaar/chirpstack-api',
author='Orne Brocaar', author='Orne Brocaar',
author_email='info@brocaar.com', author_email='info@brocaar.com',

7
api/rust/Cargo.toml vendored
View File

@ -1,7 +1,7 @@
[package] [package]
name = "chirpstack_api" name = "chirpstack_api"
description = "ChirpStack Protobuf / gRPC API definitions." description = "ChirpStack Protobuf / gRPC API definitions."
version = "4.11.1-test.1" version = "4.12.0-test.2"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
license = "MIT" license = "MIT"
homepage = "https://www.chirpstack.io" homepage = "https://www.chirpstack.io"
@ -18,13 +18,12 @@
prost = "0.13" prost = "0.13"
prost-types = "0.13" prost-types = "0.13"
hex = "0.4" hex = "0.4"
rand = "0.8" rand = "0.9"
tonic = { version = "0.12", features = [ tonic = { version = "0.12", features = [
"codegen", "codegen",
"prost", "prost",
], default-features = false, optional = true } ], default-features = false, optional = true }
tokio = { version = "1.41", features = ["macros"], optional = true } tokio = { version = "1.44", features = ["macros"], optional = true }
pbjson = { version = "0.7", optional = true } pbjson = { version = "0.7", optional = true }
pbjson-types = { version = "0.7", optional = true } pbjson-types = { version = "0.7", optional = true }
serde = { version = "1.0", optional = true } serde = { version = "1.0", optional = true }

1
api/rust/build.rs vendored
View File

@ -215,6 +215,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.to_str() .to_str()
.unwrap(), .unwrap(),
cs_dir.join("api").join("relay.proto").to_str().unwrap(), cs_dir.join("api").join("relay.proto").to_str().unwrap(),
cs_dir.join("api").join("fuota.proto").to_str().unwrap(),
], ],
&[ &[
proto_dir.join("chirpstack").to_str().unwrap(), proto_dir.join("chirpstack").to_str().unwrap(),

View File

@ -427,6 +427,20 @@ service ApplicationService {
post : "/api/applications/{application_id}/integrations/mqtt/certificate" post : "/api/applications/{application_id}/integrations/mqtt/certificate"
}; };
} }
// List device-profiles used within the given application.
rpc ListDeviceProfiles(ListApplicationDeviceProfilesRequest) returns (ListApplicationDeviceProfilesResponse) {
option (google.api.http) = {
get: "/api/applications/{application_id}/device-profiles"
};
}
// List device tags used within the given application.
rpc ListDeviceTags(ListApplicationDeviceTagsRequest) returns (ListApplicationDeviceTagsResponse) {
option (google.api.http) = {
get: "/api/applications/{application_id}/device-tags"
};
}
} }
enum Encoding { enum Encoding {
@ -529,6 +543,7 @@ message DeleteApplicationRequest {
message ListApplicationsRequest { message ListApplicationsRequest {
// Max number of applications to return in the result-set. // Max number of applications to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -1098,3 +1113,39 @@ message GenerateMqttIntegrationClientCertificateResponse {
// Expires at defines the expiration date of the certificate. // Expires at defines the expiration date of the certificate.
google.protobuf.Timestamp expires_at = 4; google.protobuf.Timestamp expires_at = 4;
} }
message ApplicationDeviceProfileListItem {
// Device-profile ID (UUID).
string id = 1;
// Name.
string name = 2;
}
message ListApplicationDeviceProfilesRequest {
// Application ID (UUID).
string application_id = 1;
};
message ListApplicationDeviceProfilesResponse {
// Device-profiles.
repeated ApplicationDeviceProfileListItem result = 1;
}
message ApplicationDeviceTagListItem {
// Tag key.
string key = 1;
// Used values.
repeated string values = 2;
}
message ListApplicationDeviceTagsRequest {
// Application ID (UUID).
string application_id = 1;
}
message ListApplicationDeviceTagsResponse {
// Device tags.
repeated ApplicationDeviceTagListItem result = 1;
}

View File

@ -262,6 +262,9 @@ message DeviceListItem {
// Device status. // Device status.
DeviceStatus device_status = 9; DeviceStatus device_status = 9;
// Device tags.
map<string, string> tags = 10;
} }
message DeviceKeys { message DeviceKeys {
@ -275,6 +278,11 @@ message DeviceKeys {
// Application root key (128 bit). // Application root key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.1.x devices! // Note: This field only needs to be set for LoRaWAN 1.1.x devices!
string app_key = 3; string app_key = 3;
// Gen App Key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.0.x devices that
// implement TS005 (remote multicast setup).
string gen_app_key = 4;
} }
message CreateDeviceRequest { message CreateDeviceRequest {
@ -319,6 +327,7 @@ message DeleteDeviceRequest {
message ListDevicesRequest { message ListDevicesRequest {
// Max number of devices to return in the result-set. // Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -332,6 +341,25 @@ message ListDevicesRequest {
// Multicst-group ID (UUID) to filter devices on. // Multicst-group ID (UUID) to filter devices on.
string multicast_group_id = 5; string multicast_group_id = 5;
enum OrderBy {
NAME = 0;
DEV_EUI = 1;
LAST_SEEN_AT = 2;
DEVICE_PROFILE_NAME = 3;
}
// If set, the given value will be used to sort by (optional).
OrderBy order_by = 6;
// If set, the sorting direction will be decending (default = ascending) (optional).
bool order_by_desc = 7;
// Tags to filter devices on.
map<string, string> tags = 8;
// Device-profile ID (UUID) to filter devices on.
string device_profile_id = 9;
} }
message ListDevicesResponse { message ListDevicesResponse {

View File

@ -98,6 +98,39 @@ enum RelayModeActivation {
END_DEVICE_CONTROLLED = 3; END_DEVICE_CONTROLLED = 3;
} }
enum Ts003Version {
// Not implemented.
TS003_NOT_IMPLEMENTED = 0;
// v1.0.0.
TS003_V100 = 1;
// v2.0.0
TS003_v200 = 2;
}
enum Ts004Version {
// Not implemented.
TS004_NOT_IMPLEMENTED = 0;
// v1.0.0.
TS004_V100 = 1;
// v2.0.0
TS004_V200 = 2;
}
enum Ts005Version {
// Not implemented.
TS005_NOT_IMPLEMENTED = 0;
// v1.0.0.
TS005_V100 = 1;
// v2.0.0
TS005_V200 = 2;
}
// DeviceProfileService is the service providing API methods for managing // DeviceProfileService is the service providing API methods for managing
// device-profiles. // device-profiles.
service DeviceProfileService { service DeviceProfileService {
@ -421,6 +454,9 @@ message DeviceProfile {
// it. // it.
// Valid options are 1 - 15 (0 = always use system RX1 Delay). // Valid options are 1 - 15 (0 = always use system RX1 Delay).
uint32 rx1_delay = 53; uint32 rx1_delay = 53;
// Application Layer parameters.
AppLayerParams app_layer_params = 54;
} }
message Measurement { message Measurement {
@ -431,6 +467,26 @@ message Measurement {
MeasurementKind kind = 3; MeasurementKind kind = 3;
} }
message AppLayerParams {
// TS003 version (Application Layer Clock Sync).
Ts003Version ts003_version = 1;
// TS003 fPort.
uint32 ts003_f_port = 2;
// TS004 version (Fragmented Data Block Transport).
Ts004Version ts004_version = 3;
// TS004 fPort.
uint32 ts004_f_port = 4;
// TS005 version (Remote Multicast Setup).
Ts005Version ts005_version = 5;
// TS005 fPort.
uint32 ts005_f_port = 6;
}
message DeviceProfileListItem { message DeviceProfileListItem {
// Device-profile ID (UUID). // Device-profile ID (UUID).
string id = 1; string id = 1;
@ -501,6 +557,7 @@ message DeleteDeviceProfileRequest {
message ListDeviceProfilesRequest { message ListDeviceProfilesRequest {
// Max number of device-profiles to return in the result-set. // Max number of device-profiles to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -231,6 +231,7 @@ message DeleteDeviceProfileTemplateRequest {
message ListDeviceProfileTemplatesRequest { message ListDeviceProfileTemplatesRequest {
// Max number of device-profile templates to return in the result-set. // Max number of device-profile templates to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -0,0 +1,401 @@
syntax = "proto3";
package api;
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
option java_package = "io.chirpstack.api";
option java_multiple_files = true;
option java_outer_classname = "FuotaProto";
option csharp_namespace = "Chirpstack.Api";
option php_namespace = "Chirpstack\\Api";
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "common/common.proto";
import "api/multicast_group.proto";
// FuotaService is the service providing API methods for FUOTA deployments.
service FuotaService {
// Create the given FUOTA deployment.
rpc CreateDeployment(CreateFuotaDeploymentRequest) returns (CreateFuotaDeploymentResponse) {}
// Get the FUOTA deployment for the given ID.
rpc GetDeployment(GetFuotaDeploymentRequest) returns (GetFuotaDeploymentResponse) {}
// Update the given FUOTA deployment.
rpc UpdateDeployment(UpdateFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// Delete the FUOTA deployment for the given ID.
rpc DeleteDeployment(DeleteFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// Start the FUOTA deployment.
rpc StartDeployment(StartFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// List the FUOTA deployments.
rpc ListDeployments(ListFuotaDeploymentsRequest) returns (ListFuotaDeploymentsResponse) {}
// Add the given DevEUIs to the FUOTA deployment.
rpc AddDevices(AddDevicesToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// Remove the given DevEUIs from the FUOTA deployment.
rpc RemoveDevices(RemoveDevicesFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// List FUOTA Deployment devices.
rpc ListDevices(ListFuotaDeploymentDevicesRequest) returns (ListFuotaDeploymentDevicesResponse) {}
// Add the given Gateway IDs to the FUOTA deployment.
// By default, ChirpStack will automatically select the minimum amount of
// gateways needed to cover all devices within the multicast-group. Setting
// the gateways manually overrides this behaviour.
rpc AddGateways(AddGatewaysToFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// List the gateways added to the FUOTA deployment.
rpc ListGateways(ListFuotaDeploymentGatewaysRequest) returns (ListFuotaDeploymentGatewaysResponse) {}
// Remove the given Gateway IDs from the FUOTA deployment.
rpc RemoveGateways(RemoveGatewaysFromFuotaDeploymentRequest) returns (google.protobuf.Empty) {}
// GetLogs returns the logs for the FUOTA deployment.
// List jobs for the given FUOTA deployment.
rpc ListJobs(ListFuotaDeploymentJobsRequest) returns (ListFuotaDeploymentJobsResponse) {}
}
enum RequestFragmentationSessionStatus {
// Do not request the fragmentation-session status.
NO_REQUEST = 0;
// Enqueue the fragmentation-session status request command directly after
// enqueueing the fragmentation-session fragments. This is the recommended
// option for Class-A devices as the status request will stay in the
// downlink queue until the device sends its next uplink.
AFTER_FRAGMENT_ENQUEUE = 1;
// Enqueue the fragmentation-session status request after the multicast
// session-timeout. This is the recommended option for Class-B and -C
// devices as selecting AFTER_FRAGMENT_ENQUEUE will likely cause the NS
// to schedule the downlink frame during the FUOTA multicast-session.
AFTER_SESSION_TIMEOUT = 2;
}
message FuotaDeployment {
// Deployment ID.
// This value is automatically set on create.
string id = 1;
// Application ID.
string application_id = 2;
// Device-profile ID.
string device_profile_id = 3;
// Deployment name.
string name = 4;
// Multicast-group type.
MulticastGroupType multicast_group_type = 5;
// Multicast-group scheduling type (Class-C only).
MulticastGroupSchedulingType multicast_class_c_scheduling_type = 6;
// Multicast data-rate.
uint32 multicast_dr = 7;
// Multicast ping-slot period (Class-B only).
uint32 multicast_class_b_ping_slot_nb_k = 8;
// Multicast frequency (Hz).
uint32 multicast_frequency = 9;
// Multicast timeout.
// This defines the timeout of the multicast-session.
// Please refer to the Remote Multicast Setup specification as this field
// has a different meaning for Class-B and Class-C groups.
uint32 multicast_timeout = 10;
// Calculate multicast timeout.
// If set to true, ChirpStack will calculate the multicast-timeout.
bool calculate_multicast_timeout = 11;
// The number of times ChirpStack will retry an unicast command
// before it considers it to be failed.
uint32 unicast_max_retry_count = 12;
// Fragmentation size.
// This defines the size of each payload fragment. Please refer to the
// Regional Parameters specification for the maximum payload sizes
// per data-rate and region.
uint32 fragmentation_fragment_size = 13;
// Calculate fragmentation size.
// If set to true, ChirpStack will calculate the fragmentation size.
bool calculate_fragmentation_fragment_size = 14;
// Fragmentation redundancy percentage.
// The number represents the percentage (0 - 100) of redundant messages
// to send.
uint32 fragmentation_redundancy_percentage = 15;
// Fragmentation session index.
uint32 fragmentation_session_index = 16;
// Fragmentation matrix.
uint32 fragmentation_matrix = 17;
// Block ack delay.
uint32 fragmentation_block_ack_delay = 18;
// Descriptor (4 bytes).
bytes fragmentation_descriptor = 19;
// Request fragmentation session status.
RequestFragmentationSessionStatus request_fragmentation_session_status = 20;
// Payload.
// The FUOTA payload to send.
bytes payload = 21;
// Set device tags on complete.
map<string, string> on_complete_set_device_tags = 22;
}
message FuotaDeploymentListItem {
// ID.
string id = 1;
// Created at timestamp.
google.protobuf.Timestamp created_at = 2;
// Updated at timestamp.
google.protobuf.Timestamp updated_at = 3;
// Started at timestamp.
google.protobuf.Timestamp started_at = 4;
// Completed at timestamp.
google.protobuf.Timestamp completed_at = 5;
// Name.
string name = 6;
}
message FuotaDeploymentDeviceListItem {
// ID.
string fuota_deployment_id = 1;
// DevEUI.
string dev_eui = 2;
// Created at timestamp.
google.protobuf.Timestamp created_at = 3;
// Completed at timestamp.
google.protobuf.Timestamp completed_at = 4;
// McGroupSetup completed at timestamp.
google.protobuf.Timestamp mc_group_setup_completed_at = 5;
// McSession completed at timestamp.
google.protobuf.Timestamp mc_session_completed_at = 6;
// FragSessionSetup completed at timestamp.
google.protobuf.Timestamp frag_session_setup_completed_at = 7;
// FragStatus completed at timestamp.
google.protobuf.Timestamp frag_status_completed_at = 8;
// Error message.
string error_msg = 9;
}
message FuotaDeploymentGatewayListItem {
// ID.
string fuota_deployment_id = 1;
// Gateway ID.
string gateway_id = 2;
// Created at timestamp.
google.protobuf.Timestamp created_at = 3;
}
message CreateFuotaDeploymentRequest {
// Deployment.
FuotaDeployment deployment = 1;
}
message CreateFuotaDeploymentResponse {
// ID of the created deployment.
string id = 1;
}
message GetFuotaDeploymentRequest {
// FUOTA Deployment ID.
string id = 1;
}
message GetFuotaDeploymentResponse {
// FUOTA Deployment.
FuotaDeployment deployment = 1;
// Created at timestamp.
google.protobuf.Timestamp created_at = 2;
// Updated at timestamp.
google.protobuf.Timestamp updated_at = 3;
// Started at timestamp.
google.protobuf.Timestamp started_at = 4;
// Completed at timestamp.
google.protobuf.Timestamp completed_at = 5;
}
message UpdateFuotaDeploymentRequest {
// Deployment.
FuotaDeployment deployment = 1;
}
message DeleteFuotaDeploymentRequest {
// FUOTA deployment ID.
string id = 1;
}
message StartFuotaDeploymentRequest {
// FUOTA deployment ID.
string id = 1;
}
message ListFuotaDeploymentsRequest {
// Max number of FUOTA deployments to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1;
// Offset in the result-set (for pagination).
uint32 offset = 2;
// Application ID to list the FUOTA Deployments for.
// This filter is mandatory.
string application_id = 3;
}
message ListFuotaDeploymentsResponse {
// Total number of FUOTA Deployments.
uint32 total_count = 1;
// Result-test.
repeated FuotaDeploymentListItem result = 2;
}
message AddDevicesToFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// DevEUIs.
// Note that the DevEUIs must share the same device-profile as assigned to
// the FUOTA Deployment.
repeated string dev_euis = 2;
}
message RemoveDevicesFromFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// DevEUIs.
repeated string dev_euis = 2;
}
message ListFuotaDeploymentDevicesRequest {
// Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1;
// Offset in the result-set (for pagination).
uint32 offset = 2;
// FUOTA Deployment ID.
string fuota_deployment_id = 3;
}
message ListFuotaDeploymentDevicesResponse {
// Total number of devices.
uint32 total_count = 1;
// Result-set.
repeated FuotaDeploymentDeviceListItem result = 2;
}
message AddGatewaysToFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// Gateway IDs.
// Note that the Gateways must be under the same tenant as the FUOTA Deployment.
repeated string gateway_ids = 2;
}
message RemoveGatewaysFromFuotaDeploymentRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
// Gateway IDs.
repeated string gateway_ids = 2;
}
message ListFuotaDeploymentGatewaysRequest {
// Max number of gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1;
// Offset in the result-set (for pagination).
uint32 offset = 2;
// FUOTA Deployment ID.
string fuota_deployment_id = 3;
}
message ListFuotaDeploymentGatewaysResponse {
// Total number of gateways.
uint32 total_count = 1;
// Result-set.
repeated FuotaDeploymentGatewayListItem result = 2;
}
message ListFuotaDeploymentJobsRequest {
// FUOTA Deployment ID.
string fuota_deployment_id = 1;
}
message ListFuotaDeploymentJobsResponse {
// Jobs.
repeated FuotaDeploymentJob jobs = 1;
}
message FuotaDeploymentJob {
// Job identifier.
string job = 1;
// Created at.
google.protobuf.Timestamp created_at = 2;
// Completed at.
google.protobuf.Timestamp completed_at = 3;
// Max. retry count.
uint32 max_retry_count = 4;
// Attempt count.
uint32 attempt_count = 5;
// Scheduler run after.
google.protobuf.Timestamp scheduler_run_after = 6;
// Warning message.
string warning_msg = 7;
// Error message.
string error_msg = 8;
}

View File

@ -215,6 +215,7 @@ message DeleteGatewayRequest {
message ListGatewaysRequest { message ListGatewaysRequest {
// Max number of gateways to return in the result-set. // Max number of gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -229,6 +230,18 @@ message ListGatewaysRequest {
// Multicast-group ID (UUID) to filter gateways on. // Multicast-group ID (UUID) to filter gateways on.
string multicast_group_id = 5; string multicast_group_id = 5;
enum OrderBy {
NAME = 0;
GATEWAY_ID = 1;
LAST_SEEN_AT = 2;
}
// If set, the given value will be used to sort by (optional).
OrderBy order_by = 6;
// If set, the sorting direction will be decending (default = ascending) (optional).
bool order_by_desc = 7;
} }
message ListGatewaysResponse { message ListGatewaysResponse {
@ -338,6 +351,7 @@ message GetRelayGatewayResponse {
message ListRelayGatewaysRequest { message ListRelayGatewaysRequest {
// Max number of relay-gateways to return in the result-set. // Max number of relay-gateways to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -109,6 +109,7 @@ message DeleteApiKeyRequest {
message ListApiKeysRequest { message ListApiKeysRequest {
// Max number of items to return. // Max number of items to return.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -177,6 +178,7 @@ message GlobalSearchRequest {
string search = 1; string search = 1;
// Max number of results to return. // Max number of results to return.
// If not set, it will be treated as 0, and the response will only return the total_count.
int64 limit = 2; int64 limit = 2;
// Offset offset of the result-set (for pagination). // Offset offset of the result-set (for pagination).

View File

@ -237,6 +237,7 @@ message DeleteMulticastGroupRequest {
message ListMulticastGroupsRequest { message ListMulticastGroupsRequest {
// Max number of multicast groups to return in the result-set. // Max number of multicast groups to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -56,6 +56,7 @@ message RelayListItem {
message ListRelaysRequest { message ListRelaysRequest {
// Max number of devices to return in the result-set. // Max number of devices to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -92,6 +93,7 @@ message RemoveRelayDeviceRequest {
message ListRelayDevicesRequest { message ListRelayDevicesRequest {
// Max number of multicast groups to return in the result-set. // Max number of multicast groups to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -200,6 +200,7 @@ message DeleteTenantRequest {
message ListTenantsRequest { message ListTenantsRequest {
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
@ -313,6 +314,7 @@ message ListTenantUsersRequest {
string tenant_id = 1; string tenant_id = 1;
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 2; uint32 limit = 2;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

View File

@ -161,6 +161,7 @@ message DeleteUserRequest {
message ListUsersRequest { message ListUsersRequest {
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
// If not set, it will be treated as 0, and the response will only return the total_count.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).

4
api/rust/src/gw.rs vendored
View File

@ -115,11 +115,11 @@ impl UplinkFrame {
if let Some(rx_info) = &self.rx_info_legacy { if let Some(rx_info) = &self.rx_info_legacy {
if self.rx_info.is_none() { if self.rx_info.is_none() {
let mut rng = rand::thread_rng(); let mut rng = rand::rng();
self.rx_info = Some(UplinkRxInfo { self.rx_info = Some(UplinkRxInfo {
gateway_id: hex::encode(&rx_info.gateway_id), gateway_id: hex::encode(&rx_info.gateway_id),
uplink_id: rng.gen::<u32>(), uplink_id: rng.random::<u32>(),
gw_time: rx_info.time, gw_time: rx_info.time,
ns_time: None, ns_time: None,
time_since_gps_epoch: rx_info.time_since_gps_epoch, time_since_gps_epoch: rx_info.time_since_gps_epoch,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "backend" name = "backend"
version = "4.11.1-test.1" version = "4.12.0-test.2"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018" edition = "2018"
publish = false publish = false
@ -12,14 +12,14 @@
anyhow = "1.0" anyhow = "1.0"
tracing = "0.1" tracing = "0.1"
hex = "0.4" hex = "0.4"
rand = "0.8" rand = "0.9"
aes-kw = "0.2" aes-kw = "0.2"
reqwest = { version = "0.12", features = [ reqwest = { version = "0.12", features = [
"json", "json",
"rustls-tls", "rustls-tls",
], default-features = false } ], default-features = false }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1.42", features = ["macros"] } tokio = { version = "1.44", features = ["macros"] }
chirpstack_api = { path = "../api/rust", default-features = false, features = [ chirpstack_api = { path = "../api/rust", default-features = false, features = [
"json", "json",
] } ] }

View File

@ -3,14 +3,14 @@
description = "Library for building external ChirpStack integrations" description = "Library for building external ChirpStack integrations"
homepage = "https://www.chirpstack.io/" homepage = "https://www.chirpstack.io/"
license = "MIT" license = "MIT"
version = "4.11.1-test.1" version = "4.12.0-test.2"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021" edition = "2021"
repository = "https://github.com/chirpstack/chirpstack" repository = "https://github.com/chirpstack/chirpstack"
[dependencies] [dependencies]
chirpstack_api = { path = "../api/rust", version = "4.11.1-test.1" } chirpstack_api = { path = "../api/rust", version = "4.12.0-test.2" }
redis = { version = "0.27", features = [ redis = { version = "0.29", features = [
"cluster-async", "cluster-async",
"tokio-rustls-comp", "tokio-rustls-comp",
] } ] }
@ -23,7 +23,7 @@
], default-features = true } ], default-features = true }
async-trait = "0.1" async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
lazy_static = "1.5" lazy_static = "1.5"
serde_json = "1.0" serde_json = "1.0"
toml = "0.8" toml = "0.8"

View File

@ -3,7 +3,7 @@
description = "ChirpStack is an open-source LoRaWAN(TM) Network Server" description = "ChirpStack is an open-source LoRaWAN(TM) Network Server"
repository = "https://github.com/chirpstack/chirpstack" repository = "https://github.com/chirpstack/chirpstack"
homepage = "https://www.chirpstack.io/" homepage = "https://www.chirpstack.io/"
version = "4.11.1-test.1" version = "4.12.0-test.2"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021" edition = "2021"
publish = false publish = false
@ -20,15 +20,12 @@
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
humantime-serde = "1.1" humantime-serde = "1.1"
toml = "0.8" toml = "0.8"
handlebars = "6.2" handlebars = "6.3"
validator = { version = "0.20", features = ["derive"] }
# Database # Database
email_address = "0.2" email_address = "0.2"
diesel = { version = "2.2", features = [ diesel = { version = "2.2", features = ["chrono", "numeric"] }
"chrono",
"numeric",
"64-column-tables",
] }
diesel_migrations = { version = "2.2" } diesel_migrations = { version = "2.2" }
diesel-async = { version = "0.5", features = [ diesel-async = { version = "0.5", features = [
"deadpool", "deadpool",
@ -37,8 +34,8 @@
tokio-postgres = { version = "0.7", optional = true } tokio-postgres = { version = "0.7", optional = true }
tokio-postgres-rustls = { version = "0.13", optional = true } tokio-postgres-rustls = { version = "0.13", optional = true }
bigdecimal = "0.4" bigdecimal = "0.4"
redis = { version = "0.27", features = ["tls-rustls", "tokio-rustls-comp"] } redis = { version = "0.29", features = ["tls-rustls", "tokio-rustls-comp"] }
deadpool-redis = { version = "0.18", features = ["cluster", "serde"] } deadpool-redis = { version = "0.20", features = ["cluster", "serde"] }
# Logging # Logging
tracing = "0.1" tracing = "0.1"
@ -55,6 +52,7 @@
"diesel", "diesel",
"regions", "regions",
"crypto", "crypto",
"applayer",
] } ] }
backend = { path = "../backend" } backend = { path = "../backend" }
@ -83,21 +81,21 @@
tonic = "0.12" tonic = "0.12"
tonic-web = "0.12" tonic-web = "0.12"
tonic-reflection = "0.12" tonic-reflection = "0.12"
tokio = { version = "1.42", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
tokio-stream = "0.1" tokio-stream = "0.1"
prost-types = "0.13" prost-types = "0.13"
prost = "0.13" prost = "0.13"
pbjson-types = "0.7" pbjson-types = "0.7"
# gRPC and HTTP multiplexing # gRPC and HTTP multiplexing
axum = "0.7" axum = "0.8"
axum-server = { version = "0.7.1", features = ["tls-rustls-no-provider"] } axum-server = { version = "0.7", features = ["tls-rustls-no-provider"] }
tower = { version = "0.5", features = ["util"] } tower = { version = "0.5", features = ["util"] }
futures = "0.3" futures = "0.3"
futures-util = "0.3" futures-util = "0.3"
http = "1.1" http = "1.3"
http-body = "1.0" http-body = "1.0"
rust-embed = "8.5" rust-embed = "8.6"
mime_guess = "2.0" mime_guess = "2.0"
tower-http = { version = "0.6", features = ["trace", "auth"] } tower-http = { version = "0.6", features = ["trace", "auth"] }
@ -107,7 +105,6 @@
# Authentication # Authentication
pbkdf2 = { version = "0.12", features = ["simple"] } pbkdf2 = { version = "0.12", features = ["simple"] }
rand_core = { version = "0.6", features = ["std"] }
jsonwebtoken = "9.3" jsonwebtoken = "9.3"
rustls = { version = "0.23", default-features = false, features = [ rustls = { version = "0.23", default-features = false, features = [
"logging", "logging",
@ -118,13 +115,12 @@
rustls-native-certs = "0.8" rustls-native-certs = "0.8"
rustls-pemfile = "2.2" rustls-pemfile = "2.2"
pem = "3.0" pem = "3.0"
x509-parser = "0.16" x509-parser = "0.17"
rsa = "0.9" rsa = "0.9"
elliptic-curve = { version = "0.13", features = ["pem"] } sec1 = { version = "0.7.3", features = ["alloc", "pem", "pkcs8"] }
p256 = "0.13"
rcgen = { version = "0.13.1", features = ["x509-parser"] } rcgen = { version = "0.13.1", features = ["x509-parser"] }
oauth2 = "5.0.0-alpha.4" oauth2 = "5.0.0"
openidconnect = { version = "4.0.0-alpha.2", features = [ openidconnect = { version = "4.0.0", features = [
"accept-rfc3339-timestamps", "accept-rfc3339-timestamps",
] } ] }
@ -133,7 +129,7 @@
hex = "0.4" hex = "0.4"
# Codecs # Codecs
rquickjs = { version = "0.8", features = [ rquickjs = { version = "0.9", features = [
"bindgen", "bindgen",
"loader", "loader",
"array-buffer", "array-buffer",
@ -142,15 +138,15 @@
# Misc # Misc
lazy_static = "1.5" lazy_static = "1.5"
uuid = { version = "1.11", features = ["v4", "serde"] } uuid = { version = "1.16", features = ["v4", "serde"] }
chrono = "0.4" chrono = "0.4"
async-trait = "0.1" async-trait = "0.1"
aes = "0.8" aes = "0.8"
rand = "0.8" rand = "0.9"
base64 = "0.22" base64 = "0.22"
async-recursion = "1.1" async-recursion = "1.1"
regex = "1.11" regex = "1.11"
petgraph = "0.6" petgraph = "0.7"
prometheus-client = "0.22" prometheus-client = "0.22"
pin-project = "1.1" pin-project = "1.1"
scoped-futures = { version = "0.1", features = ["std"] } scoped-futures = { version = "0.1", features = ["std"] }
@ -160,7 +156,7 @@
# Development and testing # Development and testing
[dev-dependencies] [dev-dependencies]
httpmock = "0.7.0" httpmock = "0.7.0"
bytes = "1.8" bytes = "1.10"
dotenv = "0.15" dotenv = "0.15"
[features] [features]

View File

@ -47,12 +47,12 @@ dist:
test: test:
cargo fmt --check cargo fmt --check
cargo clippy --no-deps --no-default-features --features="$(DATABASE)" cargo clippy --no-deps --no-default-features --features="$(DATABASE)"
TZ=UTC cargo test --no-default-features --features="$(DATABASE)" RUST_MIN_STACK=8388608 TZ=UTC cargo test --no-default-features --features="$(DATABASE)"
test-all: test-all:
cargo fmt --check cargo fmt --check
cargo clippy --no-deps --no-default-features --features="$(DATABASE)" cargo clippy --no-deps --no-default-features --features="$(DATABASE)"
TZ=UTC cargo test --no-default-features --features="$(DATABASE),test-all-integrations" RUST_MIN_STACK=8388608 TZ=UTC cargo test --no-default-features --features="$(DATABASE),test-all-integrations"
migration-generate: migration-generate:
ifeq ($(NAME),) ifeq ($(NAME),)

View File

@ -0,0 +1,90 @@
alter table device_profile
add column abp_rx1_delay smallint not null default 0,
add column abp_rx1_dr_offset smallint not null default 0,
add column abp_rx2_dr smallint not null default 0,
add column abp_rx2_freq bigint not null default 0,
add column class_b_timeout integer not null default 0,
add column class_b_ping_slot_nb_k integer not null default 0,
add column class_b_ping_slot_dr smallint not null default 0,
add column class_b_ping_slot_freq bigint not null default 0,
add column class_c_timeout integer not null default 0,
add column is_relay boolean not null default false,
add column is_relay_ed boolean not null default false,
add column relay_ed_relay_only boolean not null default false,
add column relay_enabled boolean not null default false,
add column relay_cad_periodicity smallint not null default 0,
add column relay_default_channel_index smallint not null default 0,
add column relay_second_channel_freq bigint not null default 0,
add column relay_second_channel_dr smallint not null default 0,
add column relay_second_channel_ack_offset smallint not null default 0,
add column relay_ed_activation_mode smallint not null default 0,
add column relay_ed_smart_enable_level smallint not null default 0,
add column relay_ed_back_off smallint not null default 0,
add column relay_ed_uplink_limit_bucket_size smallint not null default 0,
add column relay_ed_uplink_limit_reload_rate smallint not null default 0,
add column relay_join_req_limit_reload_rate smallint not null default 0,
add column relay_notify_limit_reload_rate smallint not null default 0,
add column relay_global_uplink_limit_reload_rate smallint not null default 0,
add column relay_overall_limit_reload_rate smallint not null default 0,
add column relay_join_req_limit_bucket_size smallint not null default 0,
add column relay_notify_limit_bucket_size smallint not null default 0,
add column relay_global_uplink_limit_bucket_size smallint not null default 0,
add column relay_overall_limit_bucket_size smallint not null default 0;
update device_profile
set
abp_rx1_delay = (abp_params->'rx1_delay')::smallint,
abp_rx1_dr_offset = (abp_params->'rx1_dr_offset')::smallint,
abp_rx2_dr = (abp_params->'rx2_dr')::smallint,
abp_rx2_freq = (abp_params->'rx2_freq')::bigint
where
abp_params is not null;
update device_profile
set
class_b_timeout = (class_b_params->'timeout')::integer,
class_b_ping_slot_nb_k = (class_b_params->'ping_slot_nb_k')::integer,
class_b_ping_slot_dr = (class_b_params->'ping_slot_dr')::smallint,
class_b_ping_slot_freq = (class_b_params->'ping_slot_freq')::bigint
where
class_b_params is not null;
update device_profile
set
class_c_timeout = (class_c_params->'timeout')::integer
where
class_c_params is not null;
update device_profile
set
is_relay = (relay_params->'is_relay')::boolean,
is_relay_ed = (relay_params->'is_relay_ed')::boolean,
relay_ed_relay_only = (relay_params->'ed_relay_only')::boolean,
relay_enabled = (relay_params->'relay_enabled')::boolean,
relay_cad_periodicity = (relay_params->'relay_cad_periodicity')::smallint,
relay_default_channel_index = (relay_params->'default_channel_index')::smallint,
relay_second_channel_freq = (relay_params->'second_channel_freq')::bigint,
relay_second_channel_dr = (relay_params->'second_channel_dr')::smallint,
relay_second_channel_ack_offset = (relay_params->'second_channel_ack_offset')::smallint,
relay_ed_activation_mode = (relay_params->'ed_activation_mode')::smallint,
relay_ed_smart_enable_level = (relay_params->'ed_smart_enable_level')::smallint,
relay_ed_back_off = (relay_params->'ed_back_off')::smallint,
relay_ed_uplink_limit_bucket_size = (relay_params->'ed_uplink_limit_bucket_size')::smallint,
relay_ed_uplink_limit_reload_rate = (relay_params->'ed_uplink_limit_reload_rate')::smallint,
relay_join_req_limit_reload_rate = (relay_params->'relay_join_req_limit_reload_rate')::smallint,
relay_notify_limit_reload_rate = (relay_params->'relay_notify_limit_reload_rate')::smallint,
relay_global_uplink_limit_reload_rate = (relay_params->'relay_global_uplink_limit_reload_rate')::smallint,
relay_overall_limit_reload_rate = (relay_params->'relay_overall_limit_reload_rate')::smallint,
relay_join_req_limit_bucket_size = (relay_params->'relay_join_req_limit_bucket_size')::smallint,
relay_notify_limit_bucket_size = (relay_params->'relay_notify_limit_bucket_size')::smallint,
relay_global_uplink_limit_bucket_size = (relay_params->'relay_global_uplink_limit_bucket_size')::smallint,
relay_overall_limit_bucket_size = (relay_params->'relay_overall_limit_bucket_size')::smallint
where
relay_params is not null;
alter table device_profile
drop column abp_params,
drop column class_b_params,
drop column class_c_params,
drop column relay_params,
drop column app_layer_params;

View File

@ -0,0 +1,92 @@
alter table device_profile
add column abp_params jsonb null,
add column class_b_params jsonb null,
add column class_c_params jsonb null,
add column relay_params jsonb null,
add column app_layer_params jsonb not null default '{}';
alter table device_profile
alter column app_layer_params drop default;
update device_profile
set abp_params = json_build_object(
'rx1_delay', abp_rx1_delay,
'rx1_dr_offset', abp_rx1_dr_offset,
'rx2_dr', abp_rx2_dr,
'rx2_freq', abp_rx2_freq)
where supports_otaa = false;
update device_profile
set class_b_params = json_build_object(
'timeout', class_b_timeout,
'ping_slot_nb_k', class_b_ping_slot_nb_k,
'ping_slot_dr', class_b_ping_slot_dr,
'ping_slot_freq', class_b_ping_slot_freq)
where supports_class_b = true;
update device_profile
set class_c_params = json_build_object(
'timeout', class_c_timeout)
where
supports_class_c = true;
update device_profile
set relay_params = json_build_object(
'is_relay', is_relay,
'is_relay_ed', is_relay_ed,
'ed_relay_only', relay_ed_relay_only,
'relay_enabled', relay_enabled,
'relay_cad_periodicity', relay_cad_periodicity,
'default_channel_index', relay_default_channel_index,
'second_channel_freq', relay_second_channel_freq,
'second_channel_dr', relay_second_channel_dr,
'second_channel_ack_offset', relay_second_channel_ack_offset,
'ed_activation_mode', relay_ed_activation_mode,
'ed_smart_enable_level', relay_ed_smart_enable_level,
'ed_back_off', relay_ed_back_off,
'ed_uplink_limit_bucket_size', relay_ed_uplink_limit_bucket_size,
'ed_uplink_limit_reload_rate', relay_ed_uplink_limit_reload_rate,
'relay_join_req_limit_reload_rate', relay_join_req_limit_reload_rate,
'relay_notify_limit_reload_rate', relay_notify_limit_reload_rate,
'relay_global_uplink_limit_reload_rate', relay_global_uplink_limit_reload_rate,
'relay_overall_limit_reload_rate', relay_overall_limit_reload_rate,
'relay_join_req_limit_bucket_size', relay_join_req_limit_bucket_size,
'relay_notify_limit_bucket_size', relay_notify_limit_bucket_size,
'relay_global_uplink_limit_bucket_size', relay_global_uplink_limit_bucket_size,
'relay_overall_limit_bucket_size', relay_overall_limit_bucket_size)
where
is_relay = true or is_relay_ed = true;
alter table device_profile
drop column abp_rx1_delay,
drop column abp_rx1_dr_offset,
drop column abp_rx2_dr,
drop column abp_rx2_freq,
drop column class_b_timeout,
drop column class_b_ping_slot_nb_k,
drop column class_b_ping_slot_dr,
drop column class_b_ping_slot_freq,
drop column class_c_timeout,
drop column is_relay,
drop column is_relay_ed,
drop column relay_ed_relay_only,
drop column relay_enabled,
drop column relay_cad_periodicity,
drop column relay_default_channel_index,
drop column relay_second_channel_freq,
drop column relay_second_channel_dr,
drop column relay_second_channel_ack_offset,
drop column relay_ed_activation_mode,
drop column relay_ed_smart_enable_level,
drop column relay_ed_back_off,
drop column relay_ed_uplink_limit_bucket_size,
drop column relay_ed_uplink_limit_reload_rate,
drop column relay_join_req_limit_reload_rate,
drop column relay_notify_limit_reload_rate,
drop column relay_global_uplink_limit_reload_rate,
drop column relay_overall_limit_reload_rate,
drop column relay_join_req_limit_bucket_size,
drop column relay_notify_limit_bucket_size,
drop column relay_global_uplink_limit_bucket_size,
drop column relay_overall_limit_bucket_size;

View File

@ -0,0 +1,7 @@
alter table device_keys
drop column gen_app_key;
drop table fuota_deployment_job;
drop table fuota_deployment_gateway;
drop table fuota_deployment_device;
drop table fuota_deployment;

View File

@ -0,0 +1,75 @@
create table fuota_deployment (
id uuid primary key,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
started_at timestamp with time zone null,
completed_at timestamp with time zone null,
name varchar(100) not null,
application_id uuid not null references application on delete cascade,
device_profile_id uuid not null references device_profile on delete cascade,
multicast_addr bytea not null,
multicast_key bytea not null,
multicast_group_type char(1) not null,
multicast_class_c_scheduling_type varchar(20) not null,
multicast_dr smallint not null,
multicast_class_b_ping_slot_nb_k smallint not null,
multicast_frequency bigint not null,
multicast_timeout smallint not null,
multicast_session_start timestamp with time zone null,
multicast_session_end timestamp with time zone null,
unicast_max_retry_count smallint not null,
fragmentation_fragment_size smallint not null,
fragmentation_redundancy_percentage smallint not null,
fragmentation_session_index smallint not null,
fragmentation_matrix smallint not null,
fragmentation_block_ack_delay smallint not null,
fragmentation_descriptor bytea not null,
request_fragmentation_session_status varchar(20) not null,
payload bytea not null,
on_complete_set_device_tags jsonb not null
);
create table fuota_deployment_device (
fuota_deployment_id uuid not null references fuota_deployment on delete cascade,
dev_eui bytea not null references device on delete cascade,
created_at timestamp with time zone not null,
completed_at timestamp with time zone null,
mc_group_setup_completed_at timestamp with time zone null,
mc_session_completed_at timestamp with time zone null,
frag_session_setup_completed_at timestamp with time zone null,
frag_status_completed_at timestamp with time zone null,
error_msg text not null,
primary key (fuota_deployment_id, dev_eui)
);
create table fuota_deployment_gateway (
fuota_deployment_id uuid not null references fuota_deployment on delete cascade,
gateway_id bytea not null references gateway on delete cascade,
created_at timestamp with time zone not null,
primary key (fuota_deployment_id, gateway_id)
);
create table fuota_deployment_job (
fuota_deployment_id uuid not null references fuota_deployment on delete cascade,
job varchar(20) not null,
created_at timestamp with time zone not null,
completed_at timestamp with time zone null,
max_retry_count smallint not null,
attempt_count smallint not null,
scheduler_run_after timestamp with time zone not null,
warning_msg text not null,
error_msg text not null,
primary key (fuota_deployment_id, job)
);
create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at);
create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after);
alter table device_keys
add column gen_app_key bytea not null default decode('00000000000000000000000000000000', 'hex');
alter table device_keys
alter column gen_app_key drop default;

View File

@ -0,0 +1,89 @@
alter table device_profile add column abp_rx1_delay smallint not null default 0;
alter table device_profile add column abp_rx1_dr_offset smallint not null default 0;
alter table device_profile add column abp_rx2_dr smallint not null default 0;
alter table device_profile add column abp_rx2_freq bigint not null default 0;
alter table device_profile add column class_b_timeout integer not null default 0;
alter table device_profile add column class_b_ping_slot_nb_k integer not null default 0;
alter table device_profile add column class_b_ping_slot_dr smallint not null default 0;
alter table device_profile add column class_b_ping_slot_freq bigint not null default 0;
alter table device_profile add column class_c_timeout integer not null default 0;
alter table device_profile add column is_relay boolean not null default false;
alter table device_profile add column is_relay_ed boolean not null default false;
alter table device_profile add column relay_ed_relay_only boolean not null default false;
alter table device_profile add column relay_enabled boolean not null default false;
alter table device_profile add column relay_cad_periodicity smallint not null default 0;
alter table device_profile add column relay_default_channel_index smallint not null default 0;
alter table device_profile add column relay_second_channel_freq bigint not null default 0;
alter table device_profile add column relay_second_channel_dr smallint not null default 0;
alter table device_profile add column relay_second_channel_ack_offset smallint not null default 0;
alter table device_profile add column relay_ed_activation_mode smallint not null default 0;
alter table device_profile add column relay_ed_smart_enable_level smallint not null default 0;
alter table device_profile add column relay_ed_back_off smallint not null default 0;
alter table device_profile add column relay_ed_uplink_limit_bucket_size smallint not null default 0;
alter table device_profile add column relay_ed_uplink_limit_reload_rate smallint not null default 0;
alter table device_profile add column relay_join_req_limit_reload_rate smallint not null default 0;
alter table device_profile add column relay_notify_limit_reload_rate smallint not null default 0;
alter table device_profile add column relay_global_uplink_limit_reload_rate smallint not null default 0;
alter table device_profile add column relay_overall_limit_reload_rate smallint not null default 0;
alter table device_profile add column relay_join_req_limit_bucket_size smallint not null default 0;
alter table device_profile add column relay_notify_limit_bucket_size smallint not null default 0;
alter table device_profile add column relay_global_uplink_limit_bucket_size smallint not null default 0;
alter table device_profile add column relay_overall_limit_bucket_size smallint not null default 0;
update device_profile
set
abp_rx1_delay = abp_params->'rx1_delay',
abp_rx1_dr_offset = abp_params->'rx1_dr_offset',
abp_rx2_dr = abp_params->'rx2_dr',
abp_rx2_freq = abp_params->'rx2_freq'
where
abp_params is not null;
update device_profile
set
class_b_timeout = class_b_params->'timeout',
class_b_ping_slot_nb_k = class_b_params->'ping_slot_nb_k',
class_b_ping_slot_dr = class_b_params->'ping_slot_dr',
class_b_ping_slot_freq = class_b_params->'ping_slot_freq'
where
class_b_params is not null;
update device_profile
set
class_c_timeout = class_c_params->'timeout'
where
class_c_params is not null;
update device_profile
set
is_relay = relay_params->'is_relay',
is_relay_ed = relay_params->'is_relay_ed',
relay_ed_relay_only = relay_params->'ed_relay_only',
relay_enabled = relay_params->'relay_enabled',
relay_cad_periodicity = relay_params->'relay_cad_periodicity',
relay_default_channel_index = relay_params->'default_channel_index',
relay_second_channel_freq = relay_params->'second_channel_freq',
relay_second_channel_dr = relay_params->'second_channel_dr',
relay_second_channel_ack_offset = relay_params->'second_channel_ack_offset',
relay_ed_activation_mode = relay_params->'ed_activation_mode',
relay_ed_smart_enable_level = relay_params->'ed_smart_enable_level',
relay_ed_back_off = relay_params->'ed_back_off',
relay_ed_uplink_limit_bucket_size = relay_params->'ed_uplink_limit_bucket_size',
relay_ed_uplink_limit_reload_rate = relay_params->'ed_uplink_limit_reload_rate',
relay_join_req_limit_reload_rate = relay_params->'relay_join_req_limit_reload_rate',
relay_notify_limit_reload_rate = relay_params->'relay_notify_limit_reload_rate',
relay_global_uplink_limit_reload_rate = relay_params->'relay_global_uplink_limit_reload_rate',
relay_overall_limit_reload_rate = relay_params->'relay_overall_limit_reload_rate',
relay_join_req_limit_bucket_size = relay_params->'relay_join_req_limit_bucket_size',
relay_notify_limit_bucket_size = relay_params->'relay_notify_limit_bucket_size',
relay_global_uplink_limit_bucket_size = relay_params->'relay_global_uplink_limit_bucket_size',
relay_overall_limit_bucket_size = relay_params->'relay_overall_limit_bucket_size'
where
relay_params is not null;
alter table device_profile drop column abp_params;
alter table device_profile drop column class_b_params;
alter table device_profile drop column class_c_params;
alter table device_profile drop column relay_params;
alter table device_profile drop column app_layer_params;

View File

@ -0,0 +1,88 @@
alter table device_profile add column abp_params text null;
alter table device_profile add column class_b_params text null;
alter table device_profile add column class_c_params text null;
alter table device_profile add column relay_params text null;
alter table device_profile add column app_layer_params text not null default '{}';
update device_profile
set abp_params = json_object(
'rx1_delay', abp_rx1_delay,
'rx1_dr_offset', abp_rx1_dr_offset,
'rx2_dr', abp_rx2_dr,
'rx2_freq', abp_rx2_freq)
where supports_otaa = false;
update device_profile
set class_b_params = json_object(
'timeout', class_b_timeout,
'ping_slot_nb_k', class_b_ping_slot_nb_k,
'ping_slot_dr', class_b_ping_slot_dr,
'ping_slot_freq', class_b_ping_slot_freq)
where supports_class_b = true;
update device_profile
set class_c_params = json_object(
'timeout', class_c_timeout)
where supports_class_c = true;
update device_profile
set relay_params = json_object(
'is_relay', is_relay,
'is_relay_ed', is_relay_ed,
'ed_relay_only', relay_ed_relay_only,
'relay_enabled', relay_enabled,
'relay_cad_periodicity', relay_cad_periodicity,
'default_channel_index', relay_default_channel_index,
'second_channel_freq', relay_second_channel_freq,
'second_channel_dr', relay_second_channel_dr,
'second_channel_ack_offset', relay_second_channel_ack_offset,
'ed_activation_mode', relay_ed_activation_mode,
'ed_smart_enable_level', relay_ed_smart_enable_level,
'ed_back_off', relay_ed_back_off,
'ed_uplink_limit_bucket_size', relay_ed_uplink_limit_bucket_size,
'ed_uplink_limit_reload_rate', relay_ed_uplink_limit_reload_rate,
'relay_join_req_limit_reload_rate', relay_join_req_limit_reload_rate,
'relay_notify_limit_reload_rate', relay_notify_limit_reload_rate,
'relay_global_uplink_limit_reload_rate', relay_global_uplink_limit_reload_rate,
'relay_overall_limit_reload_rate', relay_overall_limit_reload_rate,
'relay_join_req_limit_bucket_size', relay_join_req_limit_bucket_size,
'relay_notify_limit_bucket_size', relay_notify_limit_bucket_size,
'relay_global_uplink_limit_bucket_size', relay_global_uplink_limit_bucket_size,
'relay_overall_limit_bucket_size', relay_overall_limit_bucket_size)
where is_relay = true or is_relay_ed is true;
alter table device_profile drop column abp_rx1_delay;
alter table device_profile drop column abp_rx1_dr_offset;
alter table device_profile drop column abp_rx2_dr;
alter table device_profile drop column abp_rx2_freq;
alter table device_profile drop column class_b_timeout;
alter table device_profile drop column class_b_ping_slot_nb_k;
alter table device_profile drop column class_b_ping_slot_dr;
alter table device_profile drop column class_b_ping_slot_freq;
alter table device_profile drop column class_c_timeout;
alter table device_profile drop column is_relay;
alter table device_profile drop column is_relay_ed;
alter table device_profile drop column relay_ed_relay_only;
alter table device_profile drop column relay_enabled;
alter table device_profile drop column relay_cad_periodicity;
alter table device_profile drop column relay_default_channel_index;
alter table device_profile drop column relay_second_channel_freq;
alter table device_profile drop column relay_second_channel_dr;
alter table device_profile drop column relay_second_channel_ack_offset;
alter table device_profile drop column relay_ed_activation_mode;
alter table device_profile drop column relay_ed_smart_enable_level;
alter table device_profile drop column relay_ed_back_off;
alter table device_profile drop column relay_ed_uplink_limit_bucket_size;
alter table device_profile drop column relay_ed_uplink_limit_reload_rate;
alter table device_profile drop column relay_join_req_limit_reload_rate;
alter table device_profile drop column relay_notify_limit_reload_rate;
alter table device_profile drop column relay_global_uplink_limit_reload_rate;
alter table device_profile drop column relay_overall_limit_reload_rate;
alter table device_profile drop column relay_join_req_limit_bucket_size;
alter table device_profile drop column relay_notify_limit_bucket_size;
alter table device_profile drop column relay_global_uplink_limit_bucket_size;
alter table device_profile drop column relay_overall_limit_bucket_size;

View File

@ -0,0 +1,7 @@
alter table device_keys
drop column gen_app_key;
drop table fuota_deployment_job;
drop table fuota_deployment_gateway;
drop table fuota_deployment_device;
drop table fuota_deployment;

View File

@ -0,0 +1,72 @@
create table fuota_deployment (
id text not null primary key,
created_at datetime not null,
updated_at datetime not null,
started_at datetime null,
completed_at datetime null,
name varchar(100) not null,
application_id text not null references application on delete cascade,
device_profile_id text not null references device_profile on delete cascade,
multicast_addr blob not null,
multicast_key blob not null,
multicast_group_type char(1) not null,
multicast_class_c_scheduling_type varchar(20) not null,
multicast_dr smallint not null,
multicast_class_b_ping_slot_nb_k smallint not null,
multicast_frequency bigint not null,
multicast_timeout smallint not null,
multicast_session_start datetime null,
multicast_session_end datetime null,
unicast_max_retry_count smallint not null,
fragmentation_fragment_size smallint not null,
fragmentation_redundancy_percentage smallint not null,
fragmentation_session_index smallint not null,
fragmentation_matrix smallint not null,
fragmentation_block_ack_delay smallint not null,
fragmentation_descriptor blob not null,
request_fragmentation_session_status varchar(20) not null,
payload blob not null,
on_complete_set_device_tags text not null
);
create table fuota_deployment_device (
fuota_deployment_id text not null references fuota_deployment on delete cascade,
dev_eui blob not null references device on delete cascade,
created_at datetime not null,
completed_at datetime null,
mc_group_setup_completed_at datetime null,
mc_session_completed_at datetime null,
frag_session_setup_completed_at datetime null,
frag_status_completed_at datetime null,
error_msg text not null,
primary key (fuota_deployment_id, dev_eui)
);
create table fuota_deployment_gateway (
fuota_deployment_id text not null references fuota_deployment on delete cascade,
gateway_id blob not null references gateway on delete cascade,
created_at datetime not null,
primary key (fuota_deployment_id, gateway_id)
);
create table fuota_deployment_job (
fuota_deployment_id text not null references fuota_deployment on delete cascade,
job varchar(20) not null,
created_at datetime not null,
completed_at datetime null,
max_retry_count smallint not null,
attempt_count smallint not null,
scheduler_run_after datetime not null,
warning_msg text not null,
error_msg text not null,
primary key (fuota_deployment_id, job)
);
create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at);
create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after);
alter table device_keys
add column gen_app_key blob not null default x'00000000000000000000000000000000';

View File

@ -1,6 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use rand::seq::SliceRandom; use rand::seq::IndexedRandom;
use super::{Handler, Request, Response}; use super::{Handler, Request, Response};
use crate::region; use crate::region;
@ -134,7 +134,7 @@ impl Handler for Algorithm {
// In case there are multiple with the same coding-rate, we take // In case there are multiple with the same coding-rate, we take
// a random one. // a random one.
resp.dr = drs resp.dr = drs
.choose(&mut rand::thread_rng()) .choose(&mut rand::rng())
.cloned() .cloned()
.ok_or_else(|| anyhow!("Random returned None"))?; .ok_or_else(|| anyhow!("Random returned None"))?;
resp.nb_trans = 1; // 1 is the recommeded value resp.nb_trans = 1; // 1 is the recommeded value

10
chirpstack/src/aeskey.rs Normal file
View File

@ -0,0 +1,10 @@
use rand::RngCore;
use lrwn::AES128Key;
pub fn get_random_aes_key() -> AES128Key {
let mut rng = rand::rng();
let mut key: [u8; 16] = [0; 16];
rng.fill_bytes(&mut key);
AES128Key::from_bytes(key)
}

View File

@ -1899,6 +1899,69 @@ impl ApplicationService for Application {
Ok(resp) Ok(resp)
} }
async fn list_device_profiles(
&self,
request: Request<api::ListApplicationDeviceProfilesRequest>,
) -> Result<Response<api::ListApplicationDeviceProfilesResponse>, Status> {
let req = request.get_ref();
let app_id = Uuid::from_str(&req.application_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateApplicationAccess::new(validator::Flag::Read, app_id),
)
.await?;
let dp_items = application::get_device_profiles(app_id)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListApplicationDeviceProfilesResponse {
result: dp_items
.iter()
.map(|v| api::ApplicationDeviceProfileListItem {
id: v.0.to_string(),
name: v.1.clone(),
})
.collect(),
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn list_device_tags(
&self,
request: Request<api::ListApplicationDeviceTagsRequest>,
) -> Result<Response<api::ListApplicationDeviceTagsResponse>, Status> {
let req = request.get_ref();
let app_id = Uuid::from_str(&req.application_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateApplicationAccess::new(validator::Flag::Read, app_id),
)
.await?;
let tags = application::get_device_tags(app_id)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListApplicationDeviceTagsResponse {
result: tags
.into_iter()
.map(|(k, v)| api::ApplicationDeviceTagListItem { key: k, values: v })
.collect(),
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -12,7 +12,8 @@ use super::error::Error;
use crate::api::auth::AuthID; use crate::api::auth::AuthID;
use crate::helpers::errors::PrintFullError; use crate::helpers::errors::PrintFullError;
use crate::storage::schema::{ use crate::storage::schema::{
api_key, application, device, device_profile, gateway, multicast_group, tenant_user, user, api_key, application, device, device_profile, fuota_deployment, gateway, multicast_group,
tenant_user, user,
}; };
use crate::storage::{fields, get_async_db_conn}; use crate::storage::{fields, get_async_db_conn};
@ -2032,11 +2033,227 @@ impl Validator for ValidateMulticastGroupQueueAccess {
} }
} }
pub struct ValidateFuotaDeploymentsAccess {
flag: Flag,
application_id: Uuid,
}
impl ValidateFuotaDeploymentsAccess {
pub fn new(flag: Flag, application_id: Uuid) -> Self {
ValidateFuotaDeploymentsAccess {
flag,
application_id,
}
}
}
#[async_trait]
impl Validator for ValidateFuotaDeploymentsAccess {
async fn validate_user(&self, id: &Uuid) -> Result<i64, Error> {
let mut q = user::dsl::user
.select(dsl::count_star())
.filter(
user::dsl::id
.eq(fields::Uuid::from(id))
.and(user::dsl::is_active.eq(true)),
)
.into_boxed();
match self.flag {
// admin user
// tenant admin
// tenant device admin
Flag::Create => {
q =
q.filter(
user::dsl::is_admin.eq(true).or(dsl::exists(
application::dsl::application
.inner_join(tenant_user::table.on(
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
))
.filter(
application::dsl::id
.eq(fields::Uuid::from(self.application_id))
.and(tenant_user::dsl::user_id.eq(user::dsl::id))
.and(
tenant_user::dsl::is_admin
.eq(true)
.or(tenant_user::dsl::is_device_admin.eq(true)),
),
),
)),
);
}
// admin user
// tenant user
Flag::List => {
q =
q.filter(
user::dsl::is_admin.eq(true).or(dsl::exists(
application::dsl::application
.inner_join(tenant_user::table.on(
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
))
.filter(
application::dsl::id
.eq(fields::Uuid::from(self.application_id))
.and(tenant_user::dsl::user_id.eq(user::dsl::id)),
),
)),
);
}
_ => return Ok(0),
}
Ok(q.first(&mut get_async_db_conn().await?).await?)
}
async fn validate_key(&self, id: &Uuid) -> Result<i64, Error> {
let mut q = api_key::dsl::api_key
.select(dsl::count_star())
.filter(api_key::dsl::id.eq(fields::Uuid::from(id)))
.into_boxed();
match self.flag {
// admin api key
// tenant api key
Flag::Create | Flag::List => {
q = q.filter(
api_key::dsl::is_admin.eq(true).or(dsl::exists(
application::dsl::application.filter(
application::dsl::id
.eq(fields::Uuid::from(self.application_id))
.and(
api_key::dsl::tenant_id
.eq(application::dsl::tenant_id.nullable()),
),
),
)),
);
}
_ => {
return Ok(0);
}
}
Ok(q.first(&mut get_async_db_conn().await?).await?)
}
}
pub struct ValidateFuotaDeploymentAccess {
flag: Flag,
fuota_deployment_id: Uuid,
}
impl ValidateFuotaDeploymentAccess {
pub fn new(flag: Flag, fuota_deployment_id: Uuid) -> Self {
ValidateFuotaDeploymentAccess {
flag,
fuota_deployment_id,
}
}
}
#[async_trait]
impl Validator for ValidateFuotaDeploymentAccess {
async fn validate_user(&self, id: &Uuid) -> Result<i64, Error> {
let mut q = user::dsl::user
.select(dsl::count_star())
.filter(
user::dsl::id
.eq(fields::Uuid::from(id))
.and(user::dsl::is_active.eq(true)),
)
.into_boxed();
match self.flag {
// admin user
// tenant user
Flag::Read => {
q =
q.filter(
user::dsl::is_admin.eq(true).or(dsl::exists(
fuota_deployment::dsl::fuota_deployment
.inner_join(application::table)
.inner_join(tenant_user::table.on(
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
))
.filter(
fuota_deployment::dsl::id
.eq(fields::Uuid::from(self.fuota_deployment_id))
.and(tenant_user::dsl::user_id.eq(user::dsl::id)),
),
)),
);
}
// admin user
// tenant admin
// tenant device admin
Flag::Update | Flag::Delete => {
q =
q.filter(
user::dsl::is_admin.eq(true).or(dsl::exists(
fuota_deployment::dsl::fuota_deployment
.inner_join(application::table)
.inner_join(tenant_user::table.on(
tenant_user::dsl::tenant_id.eq(application::dsl::tenant_id),
))
.filter(
fuota_deployment::dsl::id
.eq(fields::Uuid::from(self.fuota_deployment_id))
.and(tenant_user::dsl::user_id.eq(user::dsl::id))
.and(
tenant_user::dsl::is_admin
.eq(true)
.or(tenant_user::dsl::is_device_admin.eq(true)),
),
),
)),
);
}
_ => return Ok(0),
}
Ok(q.first(&mut get_async_db_conn().await?).await?)
}
async fn validate_key(&self, id: &Uuid) -> Result<i64, Error> {
let mut q = api_key::dsl::api_key
.select(dsl::count_star())
.filter(api_key::dsl::id.eq(fields::Uuid::from(id)))
.into_boxed();
match self.flag {
// admin api key
// tenant api key
Flag::Read | Flag::Update | Flag::Delete => {
q = q.filter(
api_key::dsl::is_admin.eq(true).or(dsl::exists(
fuota_deployment::dsl::fuota_deployment
.inner_join(application::table)
.filter(
fuota_deployment::dsl::id
.eq(fields::Uuid::from(self.fuota_deployment_id))
.and(
api_key::dsl::tenant_id
.eq(application::dsl::tenant_id.nullable()),
),
),
)),
);
}
_ => return Ok(0),
}
Ok(q.first(&mut get_async_db_conn().await?).await?)
}
}
#[cfg(test)] #[cfg(test)]
pub mod test { pub mod test {
use super::*; use super::*;
use crate::storage::{ use crate::storage::{
api_key, application, device, device_profile, gateway, multicast, tenant, user, api_key, application, device, device_profile, fuota, gateway, multicast, tenant, user,
}; };
use crate::test; use crate::test;
use std::str::FromStr; use std::str::FromStr;
@ -4619,4 +4836,298 @@ pub mod test {
]; ];
run_tests(tests).await; run_tests(tests).await;
} }
#[tokio::test]
async fn fuota_deployment() {
let _guard = test::prepare().await;
let user_active = user::User {
email: "user@user".into(),
is_active: true,
..Default::default()
};
let user_admin = user::User {
email: "admin@user".into(),
is_active: true,
is_admin: true,
..Default::default()
};
let tenant_admin = user::User {
email: "tenant-admin@user".into(),
is_active: true,
..Default::default()
};
let tenant_device_admin = user::User {
email: "tenant-device-admin@user".into(),
is_active: true,
..Default::default()
};
let tenant_gateway_admin = user::User {
email: "tenant-gateway-admin@user".into(),
is_active: true,
..Default::default()
};
let tenant_user = user::User {
email: "tenant-user@user".into(),
is_active: true,
..Default::default()
};
for u in [
&user_active,
&user_admin,
&tenant_admin,
&tenant_gateway_admin,
&tenant_device_admin,
&tenant_user,
] {
user::create(u.clone()).await.unwrap();
}
let api_key_admin = api_key::test::create_api_key(true, false).await;
let api_key_tenant = api_key::test::create_api_key(false, true).await;
let api_key_other_tenant = api_key::test::create_api_key(false, true).await;
let app =
application::test::create_application(Some(api_key_tenant.tenant_id.unwrap().into()))
.await;
tenant::add_user(tenant::TenantUser {
tenant_id: api_key_tenant.tenant_id.unwrap(),
user_id: tenant_admin.id,
is_admin: true,
..Default::default()
})
.await
.unwrap();
tenant::add_user(tenant::TenantUser {
tenant_id: api_key_tenant.tenant_id.unwrap().into(),
user_id: tenant_device_admin.id,
is_device_admin: true,
..Default::default()
})
.await
.unwrap();
tenant::add_user(tenant::TenantUser {
tenant_id: api_key_tenant.tenant_id.unwrap(),
user_id: tenant_gateway_admin.id,
is_gateway_admin: true,
..Default::default()
})
.await
.unwrap();
tenant::add_user(tenant::TenantUser {
tenant_id: api_key_tenant.tenant_id.unwrap(),
user_id: tenant_user.id,
..Default::default()
})
.await
.unwrap();
// fuota deployments with user
let tests = vec![
// admin user can create and list
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::User(user_admin.id.into()),
ok: true,
},
// tenant admin can create and list
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::User(tenant_admin.id.into()),
ok: true,
},
// tenant device admin can create and list
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::User(tenant_device_admin.id.into()),
ok: true,
},
// tenant user can list
ValidatorTest {
validators: vec![ValidateFuotaDeploymentsAccess::new(
Flag::List,
app.id.into(),
)],
id: AuthID::User(tenant_user.id.into()),
ok: true,
},
// tenant user can not create
ValidatorTest {
validators: vec![ValidateFuotaDeploymentsAccess::new(
Flag::Create,
app.id.into(),
)],
id: AuthID::User(tenant_user.id.into()),
ok: false,
},
// other user can not create or list
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::User(user_active.id.into()),
ok: false,
},
];
run_tests(tests).await;
// fuota deployments with api key
let tests = vec![
// admin api key can create and list
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::Key(api_key_admin.id.into()),
ok: true,
},
// tenant api key can create and list
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::Key(api_key_tenant.id.into()),
ok: true,
},
// tenant api key can not create or list for other tenant
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentsAccess::new(Flag::Create, app.id.into()),
ValidateFuotaDeploymentsAccess::new(Flag::List, app.id.into()),
],
id: AuthID::Key(api_key_other_tenant.id.into()),
ok: false,
},
];
run_tests(tests).await;
let dp = device_profile::create(device_profile::DeviceProfile {
tenant_id: app.tenant_id,
name: "test-dp".into(),
..Default::default()
})
.await
.unwrap();
let fuota = fuota::create_deployment(fuota::FuotaDeployment {
name: "test-fuota".into(),
application_id: app.id,
device_profile_id: dp.id,
..Default::default()
})
.await
.unwrap();
// fuota deployment with user
let tests = vec![
// admin user can read, update and delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::User(user_admin.id.into()),
ok: true,
},
// tenant admin can read, update and delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::User(tenant_admin.id.into()),
ok: true,
},
// tenant device admin can read, update and delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::User(tenant_device_admin.id.into()),
ok: true,
},
// tenant user can read
ValidatorTest {
validators: vec![ValidateFuotaDeploymentAccess::new(
Flag::Read,
fuota.id.into(),
)],
id: AuthID::User(tenant_user.id.into()),
ok: true,
},
// tenant user can not update or delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::User(tenant_user.id.into()),
ok: false,
},
// other user can not read, update or delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::User(user_active.id.into()),
ok: false,
},
];
run_tests(tests).await;
// fuota deployment with api key
let tests = vec![
// admin api key can read, update and delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::Key(api_key_admin.id.into()),
ok: true,
},
// tenant api key can read, update and delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::Key(api_key_admin.id.into()),
ok: true,
},
// other api key can not read, update or delete
ValidatorTest {
validators: vec![
ValidateFuotaDeploymentAccess::new(Flag::Read, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Update, fuota.id.into()),
ValidateFuotaDeploymentAccess::new(Flag::Delete, fuota.id.into()),
],
id: AuthID::Key(api_key_other_tenant.id.into()),
ok: false,
},
];
run_tests(tests).await;
}
} }

View File

@ -250,6 +250,11 @@ impl DeviceService for Device {
} else { } else {
Some(Uuid::from_str(&req.multicast_group_id).map_err(|e| e.status())?) Some(Uuid::from_str(&req.multicast_group_id).map_err(|e| e.status())?)
}; };
let dp_id: Option<Uuid> = if req.device_profile_id.is_empty() {
None
} else {
Some(Uuid::from_str(&req.device_profile_id).map_err(|e| e.status())?)
};
self.validator self.validator
.validate( .validate(
@ -270,15 +275,23 @@ impl DeviceService for Device {
let filters = device::Filters { let filters = device::Filters {
application_id: Some(app_id), application_id: Some(app_id),
multicast_group_id: mg_id, multicast_group_id: mg_id,
device_profile_id: dp_id,
search: if req.search.is_empty() { search: if req.search.is_empty() {
None None
} else { } else {
Some(req.search.to_string()) Some(req.search.to_string())
}, },
tags: req.tags.clone(),
}; };
let count = device::get_count(&filters).await.map_err(|e| e.status())?; let count = device::get_count(&filters).await.map_err(|e| e.status())?;
let items = device::list(req.limit as i64, req.offset as i64, &filters) let items = device::list(
req.limit as i64,
req.offset as i64,
&filters,
req.order_by().from_proto(),
req.order_by_desc,
)
.await .await
.map_err(|e| e.status())?; .map_err(|e| e.status())?;
@ -309,6 +322,7 @@ impl DeviceService for Device {
}), }),
false => None, false => None,
}, },
tags: d.tags.into_hashmap(),
}) })
.collect(), .collect(),
}); });
@ -340,11 +354,8 @@ impl DeviceService for Device {
let dk = device_keys::DeviceKeys { let dk = device_keys::DeviceKeys {
dev_eui, dev_eui,
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?, nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
app_key: if !req_dk.app_key.is_empty() { app_key: AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?,
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())? gen_app_key: AES128Key::from_str(&req_dk.gen_app_key).map_err(|e| e.status())?,
} else {
AES128Key::null()
},
..Default::default() ..Default::default()
}; };
@ -378,6 +389,7 @@ impl DeviceService for Device {
dev_eui: dk.dev_eui.to_string(), dev_eui: dk.dev_eui.to_string(),
nwk_key: dk.nwk_key.to_string(), nwk_key: dk.nwk_key.to_string(),
app_key: dk.app_key.to_string(), app_key: dk.app_key.to_string(),
gen_app_key: dk.gen_app_key.to_string(),
}), }),
created_at: Some(helpers::datetime_to_prost_timestamp(&dk.created_at)), created_at: Some(helpers::datetime_to_prost_timestamp(&dk.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)), updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)),
@ -414,11 +426,8 @@ impl DeviceService for Device {
dev_nonces: dk.dev_nonces, dev_nonces: dk.dev_nonces,
join_nonce: dk.join_nonce, join_nonce: dk.join_nonce,
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?, nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
app_key: if !req_dk.app_key.is_empty() { app_key: AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?,
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())? gen_app_key: AES128Key::from_str(&req_dk.gen_app_key).map_err(|e| e.status())?,
} else {
AES128Key::null()
},
..Default::default() ..Default::default()
}; };
let _ = device_keys::update(dk).await.map_err(|e| e.status())?; let _ = device_keys::update(dk).await.map_err(|e| e.status())?;
@ -1362,6 +1371,9 @@ pub mod test {
multicast_group_id: "".into(), multicast_group_id: "".into(),
limit: 10, limit: 10,
offset: 0, offset: 0,
order_by: api::list_devices_request::OrderBy::Name.into(),
order_by_desc: true,
..Default::default()
}, },
); );
let list_resp = service.list(list_req).await.unwrap(); let list_resp = service.list(list_req).await.unwrap();
@ -1376,6 +1388,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "02020304050607080202030405060708".into(), app_key: "02020304050607080202030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
}, },
); );
@ -1394,6 +1407,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "02020304050607080202030405060708".into(), app_key: "02020304050607080202030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
get_keys_resp.get_ref().device_keys get_keys_resp.get_ref().device_keys
); );
@ -1406,6 +1420,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "03020304050607080302030405060708".into(), app_key: "03020304050607080302030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
}, },
); );
@ -1424,6 +1439,7 @@ pub mod test {
dev_eui: "0102030405060708".into(), dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(), nwk_key: "01020304050607080102030405060708".into(),
app_key: "03020304050607080302030405060708".into(), app_key: "03020304050607080302030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}), }),
get_keys_resp.get_ref().device_keys get_keys_resp.get_ref().device_keys
); );

View File

@ -60,15 +60,6 @@ impl DeviceProfileService for DeviceProfile {
supports_otaa: req_dp.supports_otaa, supports_otaa: req_dp.supports_otaa,
supports_class_b: req_dp.supports_class_b, supports_class_b: req_dp.supports_class_b,
supports_class_c: req_dp.supports_class_c, supports_class_c: req_dp.supports_class_c,
class_b_timeout: req_dp.class_b_timeout as i32,
class_b_ping_slot_nb_k: req_dp.class_b_ping_slot_nb_k as i32,
class_b_ping_slot_dr: req_dp.class_b_ping_slot_dr as i16,
class_b_ping_slot_freq: req_dp.class_b_ping_slot_freq as i64,
class_c_timeout: req_dp.class_c_timeout as i32,
abp_rx1_delay: req_dp.abp_rx1_delay as i16,
abp_rx1_dr_offset: req_dp.abp_rx1_dr_offset as i16,
abp_rx2_dr: req_dp.abp_rx2_dr as i16,
abp_rx2_freq: req_dp.abp_rx2_freq as i64,
tags: fields::KeyValue::new(req_dp.tags.clone()), tags: fields::KeyValue::new(req_dp.tags.clone()),
measurements: fields::Measurements::new( measurements: fields::Measurements::new(
req_dp req_dp
@ -88,32 +79,77 @@ impl DeviceProfileService for DeviceProfile {
auto_detect_measurements: req_dp.auto_detect_measurements, auto_detect_measurements: req_dp.auto_detect_measurements,
region_config_id: (!req_dp.region_config_id.is_empty()) region_config_id: (!req_dp.region_config_id.is_empty())
.then(|| req_dp.region_config_id.clone()), .then(|| req_dp.region_config_id.clone()),
is_relay: req_dp.is_relay,
is_relay_ed: req_dp.is_relay_ed,
relay_ed_relay_only: req_dp.relay_ed_relay_only,
relay_enabled: req_dp.relay_enabled,
relay_cad_periodicity: req_dp.relay_cad_periodicity as i16,
relay_default_channel_index: req_dp.relay_default_channel_index as i16,
relay_second_channel_freq: req_dp.relay_second_channel_freq as i64,
relay_second_channel_dr: req_dp.relay_second_channel_dr as i16,
relay_second_channel_ack_offset: req_dp.relay_second_channel_ack_offset as i16,
relay_ed_activation_mode: req_dp.relay_ed_activation_mode().from_proto(),
relay_ed_smart_enable_level: req_dp.relay_ed_smart_enable_level as i16,
relay_ed_back_off: req_dp.relay_ed_back_off as i16,
relay_ed_uplink_limit_bucket_size: req_dp.relay_ed_uplink_limit_bucket_size as i16,
relay_ed_uplink_limit_reload_rate: req_dp.relay_ed_uplink_limit_reload_rate as i16,
relay_join_req_limit_reload_rate: req_dp.relay_join_req_limit_reload_rate as i16,
relay_notify_limit_reload_rate: req_dp.relay_notify_limit_reload_rate as i16,
relay_global_uplink_limit_reload_rate: req_dp.relay_global_uplink_limit_reload_rate
as i16,
relay_overall_limit_reload_rate: req_dp.relay_overall_limit_reload_rate as i16,
relay_join_req_limit_bucket_size: req_dp.relay_join_req_limit_bucket_size as i16,
relay_notify_limit_bucket_size: req_dp.relay_notify_limit_bucket_size as i16,
relay_global_uplink_limit_bucket_size: req_dp.relay_global_uplink_limit_bucket_size
as i16,
relay_overall_limit_bucket_size: req_dp.relay_overall_limit_bucket_size as i16,
allow_roaming: req_dp.allow_roaming, allow_roaming: req_dp.allow_roaming,
rx1_delay: req_dp.rx1_delay as i16, rx1_delay: req_dp.rx1_delay as i16,
abp_params: if req_dp.supports_otaa {
None
} else {
Some(fields::AbpParams {
rx1_delay: req_dp.abp_rx1_delay as u8,
rx1_dr_offset: req_dp.abp_rx1_dr_offset as u8,
rx2_dr: req_dp.abp_rx2_dr as u8,
rx2_freq: req_dp.abp_rx2_freq as u32,
})
},
class_b_params: if req_dp.supports_class_b {
Some(fields::ClassBParams {
timeout: req_dp.class_b_timeout as u16,
ping_slot_nb_k: req_dp.class_b_ping_slot_nb_k as u8,
ping_slot_dr: req_dp.class_b_ping_slot_dr as u8,
ping_slot_freq: req_dp.class_b_ping_slot_freq as u32,
})
} else {
None
},
class_c_params: if req_dp.supports_class_c {
Some(fields::ClassCParams {
timeout: req_dp.class_c_timeout as u16,
})
} else {
None
},
relay_params: if req_dp.is_relay || req_dp.is_relay_ed {
Some(fields::RelayParams {
is_relay: req_dp.is_relay,
is_relay_ed: req_dp.is_relay_ed,
ed_relay_only: req_dp.relay_ed_relay_only,
relay_enabled: req_dp.relay_enabled,
relay_cad_periodicity: req_dp.relay_cad_periodicity as u8,
default_channel_index: req_dp.relay_default_channel_index as u8,
second_channel_freq: req_dp.relay_second_channel_freq as u32,
second_channel_dr: req_dp.relay_second_channel_dr as u8,
second_channel_ack_offset: req_dp.relay_second_channel_ack_offset as u8,
ed_activation_mode: req_dp.relay_ed_activation_mode().from_proto(),
ed_smart_enable_level: req_dp.relay_ed_smart_enable_level as u8,
ed_back_off: req_dp.relay_ed_back_off as u8,
ed_uplink_limit_bucket_size: req_dp.relay_ed_uplink_limit_bucket_size as u8,
ed_uplink_limit_reload_rate: req_dp.relay_ed_uplink_limit_reload_rate as u8,
relay_join_req_limit_reload_rate: req_dp.relay_join_req_limit_reload_rate as u8,
relay_notify_limit_reload_rate: req_dp.relay_notify_limit_reload_rate as u8,
relay_global_uplink_limit_reload_rate: req_dp
.relay_global_uplink_limit_reload_rate
as u8,
relay_overall_limit_reload_rate: req_dp.relay_overall_limit_reload_rate as u8,
relay_join_req_limit_bucket_size: req_dp.relay_join_req_limit_bucket_size as u8,
relay_notify_limit_bucket_size: req_dp.relay_notify_limit_bucket_size as u8,
relay_global_uplink_limit_bucket_size: req_dp
.relay_global_uplink_limit_bucket_size
as u8,
relay_overall_limit_bucket_size: req_dp.relay_overall_limit_bucket_size as u8,
})
} else {
None
},
app_layer_params: {
let app_layer_params = req_dp.app_layer_params.unwrap_or_default();
fields::AppLayerParams {
ts003_version: app_layer_params.ts003_version().from_proto(),
ts004_version: app_layer_params.ts004_version().from_proto(),
ts005_version: app_layer_params.ts005_version().from_proto(),
..Default::default()
}
},
..Default::default() ..Default::default()
}; };
@ -145,6 +181,10 @@ impl DeviceProfileService for DeviceProfile {
.await?; .await?;
let dp = device_profile::get(&dp_id).await.map_err(|e| e.status())?; let dp = device_profile::get(&dp_id).await.map_err(|e| e.status())?;
let abp_params = dp.abp_params.clone().unwrap_or_default();
let class_b_params = dp.class_b_params.clone().unwrap_or_default();
let class_c_params = dp.class_c_params.clone().unwrap_or_default();
let relay_params = dp.relay_params.clone().unwrap_or_default();
let mut resp = Response::new(api::GetDeviceProfileResponse { let mut resp = Response::new(api::GetDeviceProfileResponse {
device_profile: Some(api::DeviceProfile { device_profile: Some(api::DeviceProfile {
@ -164,15 +204,15 @@ impl DeviceProfileService for DeviceProfile {
supports_otaa: dp.supports_otaa, supports_otaa: dp.supports_otaa,
supports_class_b: dp.supports_class_b, supports_class_b: dp.supports_class_b,
supports_class_c: dp.supports_class_c, supports_class_c: dp.supports_class_c,
class_b_timeout: dp.class_b_timeout as u32, class_b_timeout: class_b_params.timeout as u32,
class_b_ping_slot_nb_k: dp.class_b_ping_slot_nb_k as u32, class_b_ping_slot_nb_k: class_b_params.ping_slot_nb_k as u32,
class_b_ping_slot_dr: dp.class_b_ping_slot_dr as u32, class_b_ping_slot_dr: class_b_params.ping_slot_dr as u32,
class_b_ping_slot_freq: dp.class_b_ping_slot_freq as u32, class_b_ping_slot_freq: class_b_params.ping_slot_freq as u32,
class_c_timeout: dp.class_c_timeout as u32, class_c_timeout: class_c_params.timeout as u32,
abp_rx1_delay: dp.abp_rx1_delay as u32, abp_rx1_delay: abp_params.rx1_delay as u32,
abp_rx1_dr_offset: dp.abp_rx1_dr_offset as u32, abp_rx1_dr_offset: abp_params.rx1_dr_offset as u32,
abp_rx2_dr: dp.abp_rx2_dr as u32, abp_rx2_dr: abp_params.rx2_dr as u32,
abp_rx2_freq: dp.abp_rx2_freq as u32, abp_rx2_freq: abp_params.rx2_freq as u32,
tags: dp.tags.into_hashmap(), tags: dp.tags.into_hashmap(),
measurements: dp measurements: dp
.measurements .measurements
@ -190,32 +230,46 @@ impl DeviceProfileService for DeviceProfile {
.collect(), .collect(),
auto_detect_measurements: dp.auto_detect_measurements, auto_detect_measurements: dp.auto_detect_measurements,
region_config_id: dp.region_config_id.clone().unwrap_or_default(), region_config_id: dp.region_config_id.clone().unwrap_or_default(),
is_relay: dp.is_relay, is_relay: relay_params.is_relay,
is_relay_ed: dp.is_relay_ed, is_relay_ed: relay_params.is_relay_ed,
relay_ed_relay_only: dp.relay_ed_relay_only, relay_ed_relay_only: relay_params.ed_relay_only,
relay_enabled: dp.relay_enabled, relay_enabled: relay_params.relay_enabled,
relay_cad_periodicity: dp.relay_cad_periodicity as i32, relay_cad_periodicity: relay_params.relay_cad_periodicity as i32,
relay_default_channel_index: dp.relay_default_channel_index as u32, relay_default_channel_index: relay_params.default_channel_index as u32,
relay_second_channel_freq: dp.relay_second_channel_freq as u32, relay_second_channel_freq: relay_params.second_channel_freq as u32,
relay_second_channel_dr: dp.relay_second_channel_dr as u32, relay_second_channel_dr: relay_params.second_channel_dr as u32,
relay_second_channel_ack_offset: dp.relay_second_channel_ack_offset as i32, relay_second_channel_ack_offset: relay_params.second_channel_ack_offset as i32,
relay_ed_activation_mode: dp.relay_ed_activation_mode.to_proto().into(), relay_ed_activation_mode: relay_params.ed_activation_mode.to_proto().into(),
relay_ed_smart_enable_level: dp.relay_ed_smart_enable_level as u32, relay_ed_smart_enable_level: relay_params.ed_smart_enable_level as u32,
relay_ed_back_off: dp.relay_ed_back_off as u32, relay_ed_back_off: relay_params.ed_back_off as u32,
relay_ed_uplink_limit_bucket_size: dp.relay_ed_uplink_limit_bucket_size as u32, relay_ed_uplink_limit_bucket_size: relay_params.ed_uplink_limit_bucket_size as u32,
relay_ed_uplink_limit_reload_rate: dp.relay_ed_uplink_limit_reload_rate as u32, relay_ed_uplink_limit_reload_rate: relay_params.ed_uplink_limit_reload_rate as u32,
relay_join_req_limit_reload_rate: dp.relay_join_req_limit_reload_rate as u32, relay_join_req_limit_reload_rate: relay_params.relay_join_req_limit_reload_rate
relay_notify_limit_reload_rate: dp.relay_notify_limit_reload_rate as u32,
relay_global_uplink_limit_reload_rate: dp.relay_global_uplink_limit_reload_rate
as u32, as u32,
relay_overall_limit_reload_rate: dp.relay_overall_limit_reload_rate as u32, relay_notify_limit_reload_rate: relay_params.relay_notify_limit_reload_rate as u32,
relay_join_req_limit_bucket_size: dp.relay_join_req_limit_bucket_size as u32, relay_global_uplink_limit_reload_rate: relay_params
relay_notify_limit_bucket_size: dp.relay_notify_limit_bucket_size as u32, .relay_global_uplink_limit_reload_rate
relay_global_uplink_limit_bucket_size: dp.relay_global_uplink_limit_bucket_size as u32,
relay_overall_limit_reload_rate: relay_params.relay_overall_limit_reload_rate
as u32,
relay_join_req_limit_bucket_size: relay_params.relay_join_req_limit_bucket_size
as u32,
relay_notify_limit_bucket_size: relay_params.relay_notify_limit_bucket_size as u32,
relay_global_uplink_limit_bucket_size: relay_params
.relay_global_uplink_limit_bucket_size
as u32,
relay_overall_limit_bucket_size: relay_params.relay_overall_limit_bucket_size
as u32, as u32,
relay_overall_limit_bucket_size: dp.relay_overall_limit_bucket_size as u32,
allow_roaming: dp.allow_roaming, allow_roaming: dp.allow_roaming,
rx1_delay: dp.rx1_delay as u32, rx1_delay: dp.rx1_delay as u32,
app_layer_params: Some(api::AppLayerParams {
ts003_version: dp.app_layer_params.ts003_version.to_proto().into(),
ts003_f_port: dp.app_layer_params.ts003_f_port as u32,
ts004_version: dp.app_layer_params.ts004_version.to_proto().into(),
ts004_f_port: dp.app_layer_params.ts004_f_port as u32,
ts005_version: dp.app_layer_params.ts005_version.to_proto().into(),
ts005_f_port: dp.app_layer_params.ts005_f_port as u32,
}),
}), }),
created_at: Some(helpers::datetime_to_prost_timestamp(&dp.created_at)), created_at: Some(helpers::datetime_to_prost_timestamp(&dp.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dp.updated_at)), updated_at: Some(helpers::datetime_to_prost_timestamp(&dp.updated_at)),
@ -262,15 +316,6 @@ impl DeviceProfileService for DeviceProfile {
supports_otaa: req_dp.supports_otaa, supports_otaa: req_dp.supports_otaa,
supports_class_b: req_dp.supports_class_b, supports_class_b: req_dp.supports_class_b,
supports_class_c: req_dp.supports_class_c, supports_class_c: req_dp.supports_class_c,
class_b_timeout: req_dp.class_b_timeout as i32,
class_b_ping_slot_nb_k: req_dp.class_b_ping_slot_nb_k as i32,
class_b_ping_slot_dr: req_dp.class_b_ping_slot_dr as i16,
class_b_ping_slot_freq: req_dp.class_b_ping_slot_freq as i64,
class_c_timeout: req_dp.class_c_timeout as i32,
abp_rx1_delay: req_dp.abp_rx1_delay as i16,
abp_rx1_dr_offset: req_dp.abp_rx1_dr_offset as i16,
abp_rx2_dr: req_dp.abp_rx2_dr as i16,
abp_rx2_freq: req_dp.abp_rx2_freq as i64,
tags: fields::KeyValue::new(req_dp.tags.clone()), tags: fields::KeyValue::new(req_dp.tags.clone()),
measurements: fields::Measurements::new( measurements: fields::Measurements::new(
req_dp req_dp
@ -290,32 +335,80 @@ impl DeviceProfileService for DeviceProfile {
auto_detect_measurements: req_dp.auto_detect_measurements, auto_detect_measurements: req_dp.auto_detect_measurements,
region_config_id: (!req_dp.region_config_id.is_empty()) region_config_id: (!req_dp.region_config_id.is_empty())
.then(|| req_dp.region_config_id.clone()), .then(|| req_dp.region_config_id.clone()),
is_relay: req_dp.is_relay,
is_relay_ed: req_dp.is_relay_ed,
relay_ed_relay_only: req_dp.relay_ed_relay_only,
relay_enabled: req_dp.relay_enabled,
relay_cad_periodicity: req_dp.relay_cad_periodicity as i16,
relay_default_channel_index: req_dp.relay_default_channel_index as i16,
relay_second_channel_freq: req_dp.relay_second_channel_freq as i64,
relay_second_channel_dr: req_dp.relay_second_channel_dr as i16,
relay_second_channel_ack_offset: req_dp.relay_second_channel_ack_offset as i16,
relay_ed_activation_mode: req_dp.relay_ed_activation_mode().from_proto(),
relay_ed_smart_enable_level: req_dp.relay_ed_smart_enable_level as i16,
relay_ed_back_off: req_dp.relay_ed_back_off as i16,
relay_ed_uplink_limit_bucket_size: req_dp.relay_ed_uplink_limit_bucket_size as i16,
relay_ed_uplink_limit_reload_rate: req_dp.relay_ed_uplink_limit_reload_rate as i16,
relay_join_req_limit_reload_rate: req_dp.relay_join_req_limit_reload_rate as i16,
relay_notify_limit_reload_rate: req_dp.relay_notify_limit_reload_rate as i16,
relay_global_uplink_limit_reload_rate: req_dp.relay_global_uplink_limit_reload_rate
as i16,
relay_overall_limit_reload_rate: req_dp.relay_overall_limit_reload_rate as i16,
relay_join_req_limit_bucket_size: req_dp.relay_join_req_limit_bucket_size as i16,
relay_notify_limit_bucket_size: req_dp.relay_notify_limit_bucket_size as i16,
relay_global_uplink_limit_bucket_size: req_dp.relay_global_uplink_limit_bucket_size
as i16,
relay_overall_limit_bucket_size: req_dp.relay_overall_limit_bucket_size as i16,
allow_roaming: req_dp.allow_roaming, allow_roaming: req_dp.allow_roaming,
rx1_delay: req_dp.rx1_delay as i16, rx1_delay: req_dp.rx1_delay as i16,
abp_params: if req_dp.supports_otaa {
None
} else {
Some(fields::AbpParams {
rx1_delay: req_dp.abp_rx1_delay as u8,
rx1_dr_offset: req_dp.abp_rx1_dr_offset as u8,
rx2_dr: req_dp.abp_rx2_dr as u8,
rx2_freq: req_dp.abp_rx2_freq as u32,
})
},
class_b_params: if req_dp.supports_class_b {
Some(fields::ClassBParams {
timeout: req_dp.class_b_timeout as u16,
ping_slot_nb_k: req_dp.class_b_ping_slot_nb_k as u8,
ping_slot_dr: req_dp.class_b_ping_slot_dr as u8,
ping_slot_freq: req_dp.class_b_ping_slot_freq as u32,
})
} else {
None
},
class_c_params: if req_dp.supports_class_c {
Some(fields::ClassCParams {
timeout: req_dp.class_c_timeout as u16,
})
} else {
None
},
relay_params: if req_dp.is_relay || req_dp.is_relay_ed {
Some(fields::RelayParams {
is_relay: req_dp.is_relay,
is_relay_ed: req_dp.is_relay_ed,
ed_relay_only: req_dp.relay_ed_relay_only,
relay_enabled: req_dp.relay_enabled,
relay_cad_periodicity: req_dp.relay_cad_periodicity as u8,
default_channel_index: req_dp.relay_default_channel_index as u8,
second_channel_freq: req_dp.relay_second_channel_freq as u32,
second_channel_dr: req_dp.relay_second_channel_dr as u8,
second_channel_ack_offset: req_dp.relay_second_channel_ack_offset as u8,
ed_activation_mode: req_dp.relay_ed_activation_mode().from_proto(),
ed_smart_enable_level: req_dp.relay_ed_smart_enable_level as u8,
ed_back_off: req_dp.relay_ed_back_off as u8,
ed_uplink_limit_bucket_size: req_dp.relay_ed_uplink_limit_bucket_size as u8,
ed_uplink_limit_reload_rate: req_dp.relay_ed_uplink_limit_reload_rate as u8,
relay_join_req_limit_reload_rate: req_dp.relay_join_req_limit_reload_rate as u8,
relay_notify_limit_reload_rate: req_dp.relay_notify_limit_reload_rate as u8,
relay_global_uplink_limit_reload_rate: req_dp
.relay_global_uplink_limit_reload_rate
as u8,
relay_overall_limit_reload_rate: req_dp.relay_overall_limit_reload_rate as u8,
relay_join_req_limit_bucket_size: req_dp.relay_join_req_limit_bucket_size as u8,
relay_notify_limit_bucket_size: req_dp.relay_notify_limit_bucket_size as u8,
relay_global_uplink_limit_bucket_size: req_dp
.relay_global_uplink_limit_bucket_size
as u8,
relay_overall_limit_bucket_size: req_dp.relay_overall_limit_bucket_size as u8,
})
} else {
None
},
app_layer_params: {
let app_layer_params = req_dp.app_layer_params.unwrap_or_default();
fields::AppLayerParams {
ts003_version: app_layer_params.ts003_version().from_proto(),
ts003_f_port: app_layer_params.ts003_f_port as u8,
ts004_version: app_layer_params.ts004_version().from_proto(),
ts004_f_port: app_layer_params.ts004_f_port as u8,
ts005_version: app_layer_params.ts005_version().from_proto(),
ts005_f_port: app_layer_params.ts005_f_port as u8,
..Default::default()
}
},
..Default::default() ..Default::default()
}) })
.await .await
@ -506,6 +599,14 @@ pub mod test {
mac_version: common::MacVersion::Lorawan103.into(), mac_version: common::MacVersion::Lorawan103.into(),
reg_params_revision: common::RegParamsRevision::A.into(), reg_params_revision: common::RegParamsRevision::A.into(),
adr_algorithm_id: "default".into(), adr_algorithm_id: "default".into(),
app_layer_params: Some(api::AppLayerParams {
ts003_version: api::Ts003Version::Ts003NotImplemented.into(),
ts003_f_port: 202,
ts004_version: api::Ts004Version::Ts004NotImplemented.into(),
ts004_f_port: 201,
ts005_version: api::Ts005Version::Ts005NotImplemented.into(),
ts005_f_port: 200,
}),
..Default::default() ..Default::default()
}), }),
get_resp.get_ref().device_profile get_resp.get_ref().device_profile
@ -546,6 +647,7 @@ pub mod test {
mac_version: common::MacVersion::Lorawan103.into(), mac_version: common::MacVersion::Lorawan103.into(),
reg_params_revision: common::RegParamsRevision::A.into(), reg_params_revision: common::RegParamsRevision::A.into(),
adr_algorithm_id: "default".into(), adr_algorithm_id: "default".into(),
app_layer_params: Some(api::AppLayerParams::default()),
..Default::default() ..Default::default()
}), }),
get_resp.get_ref().device_profile get_resp.get_ref().device_profile

View File

@ -47,6 +47,17 @@ impl ToStatus for storage::error::Error {
storage::error::Error::ProstDecode(_) => { storage::error::Error::ProstDecode(_) => {
Status::new(Code::Internal, format!("{:#}", self)) Status::new(Code::Internal, format!("{:#}", self))
} }
storage::error::Error::ValidatorValidate(_) => {
Status::new(Code::InvalidArgument, format!("{:#}", self))
}
storage::error::Error::MultiError(errors) => {
let errors = errors
.into_iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join(", ");
Status::new(Code::InvalidArgument, errors)
}
} }
} }
} }

935
chirpstack/src/api/fuota.rs Normal file
View File

@ -0,0 +1,935 @@
use std::str::FromStr;
use chrono::Utc;
use tonic::{Request, Response, Status};
use uuid::Uuid;
use chirpstack_api::api;
use chirpstack_api::api::fuota_service_server::FuotaService;
use lrwn::EUI64;
use crate::aeskey::get_random_aes_key;
use crate::api::auth::validator;
use crate::api::error::ToStatus;
use crate::api::helpers::{self, FromProto, ToProto};
use crate::devaddr::get_random_dev_addr;
use crate::storage::{fields, fuota};
pub struct Fuota {
validator: validator::RequestValidator,
}
impl Fuota {
pub fn new(validator: validator::RequestValidator) -> Self {
Fuota { validator }
}
}
#[tonic::async_trait]
impl FuotaService for Fuota {
async fn create_deployment(
&self,
request: Request<api::CreateFuotaDeploymentRequest>,
) -> Result<Response<api::CreateFuotaDeploymentResponse>, Status> {
let req_dp = match &request.get_ref().deployment {
Some(v) => v,
None => {
return Err(Status::invalid_argument("deployment is missing"));
}
};
let app_id = Uuid::from_str(&req_dp.application_id).map_err(|e| e.status())?;
let dp_id = Uuid::from_str(&req_dp.device_profile_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentsAccess::new(validator::Flag::Create, app_id),
)
.await?;
let mut dp = fuota::FuotaDeployment {
name: req_dp.name.clone(),
application_id: app_id.into(),
device_profile_id: dp_id.into(),
multicast_addr: get_random_dev_addr(),
multicast_key: get_random_aes_key(),
multicast_group_type: match req_dp.multicast_group_type() {
api::MulticastGroupType::ClassB => "B",
api::MulticastGroupType::ClassC => "C",
}
.to_string(),
multicast_class_c_scheduling_type: req_dp
.multicast_class_c_scheduling_type()
.from_proto(),
multicast_dr: req_dp.multicast_dr as i16,
multicast_class_b_ping_slot_nb_k: req_dp.multicast_class_b_ping_slot_nb_k as i16,
multicast_frequency: req_dp.multicast_frequency as i64,
multicast_timeout: req_dp.multicast_timeout as i16,
unicast_max_retry_count: req_dp.unicast_max_retry_count as i16,
fragmentation_fragment_size: req_dp.fragmentation_fragment_size as i16,
fragmentation_redundancy_percentage: req_dp.fragmentation_redundancy_percentage as i16,
fragmentation_session_index: req_dp.fragmentation_session_index as i16,
fragmentation_matrix: req_dp.fragmentation_matrix as i16,
fragmentation_block_ack_delay: req_dp.fragmentation_block_ack_delay as i16,
fragmentation_descriptor: req_dp.fragmentation_descriptor.clone(),
request_fragmentation_session_status: req_dp
.request_fragmentation_session_status()
.from_proto(),
payload: req_dp.payload.clone(),
on_complete_set_device_tags: fields::KeyValue::new(
req_dp.on_complete_set_device_tags.clone(),
),
..Default::default()
};
if req_dp.calculate_fragmentation_fragment_size {
dp.fragmentation_fragment_size = fuota::get_max_fragment_size(&dp)
.await
.map_err(|e| e.status())? as i16;
}
if req_dp.calculate_multicast_timeout {
dp.multicast_timeout =
fuota::get_multicast_timeout(&dp).map_err(|e| e.status())? as i16;
}
let dp = fuota::create_deployment(dp).await.map_err(|e| e.status())?;
let mut resp = Response::new(api::CreateFuotaDeploymentResponse {
id: dp.id.to_string(),
});
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
dp.id.to_string().parse().unwrap(),
);
Ok(resp)
}
async fn get_deployment(
&self,
request: Request<api::GetFuotaDeploymentRequest>,
) -> Result<Response<api::GetFuotaDeploymentResponse>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
)
.await?;
let dp = fuota::get_deployment(dp_id).await.map_err(|e| e.status())?;
let mut resp = Response::new(api::GetFuotaDeploymentResponse {
deployment: Some(api::FuotaDeployment {
id: dp.id.to_string(),
application_id: dp.application_id.to_string(),
device_profile_id: dp.device_profile_id.to_string(),
name: dp.name.clone(),
multicast_group_type: match dp.multicast_group_type.as_ref() {
"B" => api::MulticastGroupType::ClassB,
"C" => api::MulticastGroupType::ClassC,
_ => return Err(Status::invalid_argument("Invalid multicast_group_type")),
}
.into(),
multicast_class_c_scheduling_type: dp
.multicast_class_c_scheduling_type
.to_proto()
.into(),
multicast_dr: dp.multicast_dr as u32,
multicast_class_b_ping_slot_nb_k: dp.multicast_class_b_ping_slot_nb_k as u32,
multicast_frequency: dp.multicast_frequency as u32,
multicast_timeout: dp.multicast_timeout as u32,
unicast_max_retry_count: dp.unicast_max_retry_count as u32,
fragmentation_fragment_size: dp.fragmentation_fragment_size as u32,
fragmentation_redundancy_percentage: dp.fragmentation_redundancy_percentage as u32,
fragmentation_session_index: dp.fragmentation_session_index as u32,
fragmentation_matrix: dp.fragmentation_matrix as u32,
fragmentation_block_ack_delay: dp.fragmentation_block_ack_delay as u32,
fragmentation_descriptor: dp.fragmentation_descriptor.clone(),
request_fragmentation_session_status: dp
.request_fragmentation_session_status
.to_proto()
.into(),
payload: dp.payload.clone(),
calculate_multicast_timeout: false,
calculate_fragmentation_fragment_size: false,
on_complete_set_device_tags: dp.on_complete_set_device_tags.into_hashmap(),
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&dp.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dp.updated_at)),
started_at: dp
.started_at
.as_ref()
.map(helpers::datetime_to_prost_timestamp),
completed_at: dp
.completed_at
.as_ref()
.map(helpers::datetime_to_prost_timestamp),
});
resp.metadata_mut()
.insert("x-log-fuota_deployment_id", req.id.parse().unwrap());
Ok(resp)
}
async fn update_deployment(
&self,
request: Request<api::UpdateFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req_dp = match &request.get_ref().deployment {
Some(v) => v,
None => {
return Err(Status::invalid_argument("deployment is missing"));
}
};
let id = Uuid::from_str(&req_dp.id).map_err(|e| e.status())?;
let app_id = Uuid::from_str(&req_dp.application_id).map_err(|e| e.status())?;
let dp_id = Uuid::from_str(&req_dp.device_profile_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
)
.await?;
let d = fuota::get_deployment(id).await.map_err(|e| e.status())?;
if d.started_at.is_some() {
return Err(Status::failed_precondition(
"FUOTA deployment has already started",
));
}
let mut dp = fuota::FuotaDeployment {
id: id.into(),
name: req_dp.name.clone(),
application_id: app_id.into(),
device_profile_id: dp_id.into(),
multicast_group_type: match req_dp.multicast_group_type() {
api::MulticastGroupType::ClassB => "B",
api::MulticastGroupType::ClassC => "C",
}
.to_string(),
multicast_class_c_scheduling_type: req_dp
.multicast_class_c_scheduling_type()
.from_proto(),
multicast_dr: req_dp.multicast_dr as i16,
multicast_class_b_ping_slot_nb_k: req_dp.multicast_class_b_ping_slot_nb_k as i16,
multicast_frequency: req_dp.multicast_frequency as i64,
multicast_timeout: req_dp.multicast_timeout as i16,
unicast_max_retry_count: req_dp.unicast_max_retry_count as i16,
fragmentation_fragment_size: req_dp.fragmentation_fragment_size as i16,
fragmentation_redundancy_percentage: req_dp.fragmentation_redundancy_percentage as i16,
fragmentation_session_index: req_dp.fragmentation_session_index as i16,
fragmentation_matrix: req_dp.fragmentation_matrix as i16,
fragmentation_block_ack_delay: req_dp.fragmentation_block_ack_delay as i16,
fragmentation_descriptor: req_dp.fragmentation_descriptor.clone(),
request_fragmentation_session_status: req_dp
.request_fragmentation_session_status()
.from_proto(),
payload: req_dp.payload.clone(),
on_complete_set_device_tags: fields::KeyValue::new(
req_dp.on_complete_set_device_tags.clone(),
),
..Default::default()
};
if req_dp.calculate_fragmentation_fragment_size {
dp.fragmentation_fragment_size = fuota::get_max_fragment_size(&dp)
.await
.map_err(|e| e.status())? as i16;
}
if req_dp.calculate_multicast_timeout {
dp.multicast_timeout =
fuota::get_multicast_timeout(&dp).map_err(|e| e.status())? as i16;
}
let _ = fuota::update_deployment(dp).await.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-fuota_deployment_id", req_dp.id.parse().unwrap());
Ok(resp)
}
async fn delete_deployment(
&self,
request: Request<api::DeleteFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req = request.get_ref();
let id = Uuid::from_str(&req.id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Delete, id),
)
.await?;
let _ = fuota::delete_deployment(id).await.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-fuota_deployment_id", req.id.parse().unwrap());
Ok(resp)
}
async fn start_deployment(
&self,
request: Request<api::StartFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req = request.get_ref();
let id = Uuid::from_str(&req.id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, id),
)
.await?;
let mut d = fuota::get_deployment(id).await.map_err(|e| e.status())?;
if d.started_at.is_some() {
return Err(Status::failed_precondition(
"FUOTA deployment has already started",
));
}
d.started_at = Some(Utc::now());
let d = fuota::update_deployment(d).await.map_err(|e| e.status())?;
fuota::create_job(fuota::FuotaDeploymentJob {
fuota_deployment_id: d.id,
job: fields::FuotaJob::CreateMcGroup,
..Default::default()
})
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-fuota_deployment_id", req.id.parse().unwrap());
Ok(resp)
}
async fn list_deployments(
&self,
request: Request<api::ListFuotaDeploymentsRequest>,
) -> Result<Response<api::ListFuotaDeploymentsResponse>, Status> {
let req = request.get_ref();
let app_id = Uuid::from_str(&req.application_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentsAccess::new(validator::Flag::List, app_id),
)
.await?;
let count = fuota::get_deployment_count(app_id)
.await
.map_err(|e| e.status())?;
let items = fuota::list_deployments(app_id, req.limit as i64, req.offset as i64)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListFuotaDeploymentsResponse {
total_count: count as u32,
result: items
.iter()
.map(|d| api::FuotaDeploymentListItem {
id: d.id.to_string(),
created_at: Some(helpers::datetime_to_prost_timestamp(&d.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&d.created_at)),
started_at: d
.started_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
completed_at: d
.completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
name: d.name.clone(),
})
.collect(),
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn add_devices(
&self,
request: Request<api::AddDevicesToFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
)
.await?;
let d = fuota::get_deployment(dp_id).await.map_err(|e| e.status())?;
if d.started_at.is_some() {
return Err(Status::failed_precondition(
"FUOTA deployment has already started",
));
}
let mut dev_euis = Vec::with_capacity(req.dev_euis.len());
for dev_eui in &req.dev_euis {
dev_euis.push(EUI64::from_str(dev_eui).map_err(|e| e.status())?);
}
fuota::add_devices(dp_id, dev_euis)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
async fn remove_devices(
&self,
request: Request<api::RemoveDevicesFromFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
)
.await?;
let mut dev_euis = Vec::with_capacity(req.dev_euis.len());
for dev_eui in &req.dev_euis {
dev_euis.push(EUI64::from_str(dev_eui).map_err(|e| e.status())?);
}
fuota::remove_devices(dp_id, dev_euis)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
async fn list_devices(
&self,
request: Request<api::ListFuotaDeploymentDevicesRequest>,
) -> Result<Response<api::ListFuotaDeploymentDevicesResponse>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
)
.await?;
let count = fuota::get_device_count(dp_id)
.await
.map_err(|e| e.status())?;
let items = fuota::get_devices(dp_id, req.limit as i64, req.offset as i64)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListFuotaDeploymentDevicesResponse {
total_count: count as u32,
result: items
.iter()
.map(|d| api::FuotaDeploymentDeviceListItem {
fuota_deployment_id: d.fuota_deployment_id.to_string(),
dev_eui: d.dev_eui.to_string(),
created_at: Some(helpers::datetime_to_prost_timestamp(&d.created_at)),
completed_at: d
.completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
mc_group_setup_completed_at: d
.mc_group_setup_completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
mc_session_completed_at: d
.mc_session_completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
frag_session_setup_completed_at: d
.frag_session_setup_completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
frag_status_completed_at: d
.frag_status_completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
error_msg: d.error_msg.clone(),
})
.collect(),
});
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
async fn add_gateways(
&self,
request: Request<api::AddGatewaysToFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
)
.await?;
let d = fuota::get_deployment(dp_id).await.map_err(|e| e.status())?;
if d.started_at.is_some() {
return Err(Status::failed_precondition(
"FUOTA deployment has already started",
));
}
let mut gateway_ids = Vec::with_capacity(req.gateway_ids.len());
for gateway_id in &req.gateway_ids {
gateway_ids.push(EUI64::from_str(gateway_id).map_err(|e| e.status())?);
}
fuota::add_gateways(dp_id, gateway_ids)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
async fn remove_gateways(
&self,
request: Request<api::RemoveGatewaysFromFuotaDeploymentRequest>,
) -> Result<Response<()>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Update, dp_id),
)
.await?;
let mut gateway_ids = Vec::with_capacity(req.gateway_ids.len());
for gateway_id in &req.gateway_ids {
gateway_ids.push(EUI64::from_str(gateway_id).map_err(|e| e.status())?);
}
fuota::remove_gateways(dp_id, gateway_ids)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
async fn list_gateways(
&self,
request: Request<api::ListFuotaDeploymentGatewaysRequest>,
) -> Result<Response<api::ListFuotaDeploymentGatewaysResponse>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
)
.await?;
let count = fuota::get_gateway_count(dp_id)
.await
.map_err(|e| e.status())?;
let items = fuota::get_gateways(dp_id, req.limit as i64, req.offset as i64)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListFuotaDeploymentGatewaysResponse {
total_count: count as u32,
result: items
.iter()
.map(|gw| api::FuotaDeploymentGatewayListItem {
fuota_deployment_id: gw.fuota_deployment_id.to_string(),
gateway_id: gw.gateway_id.to_string(),
created_at: Some(helpers::datetime_to_prost_timestamp(&gw.created_at)),
})
.collect(),
});
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
async fn list_jobs(
&self,
request: Request<api::ListFuotaDeploymentJobsRequest>,
) -> Result<Response<api::ListFuotaDeploymentJobsResponse>, Status> {
let req = request.get_ref();
let dp_id = Uuid::from_str(&req.fuota_deployment_id).map_err(|e| e.status())?;
self.validator
.validate(
request.extensions(),
validator::ValidateFuotaDeploymentAccess::new(validator::Flag::Read, dp_id),
)
.await?;
let jobs = fuota::list_jobs(dp_id).await.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListFuotaDeploymentJobsResponse {
jobs: jobs
.iter()
.map(|j| api::FuotaDeploymentJob {
job: j.job.to_string(),
created_at: Some(helpers::datetime_to_prost_timestamp(&j.created_at)),
completed_at: j
.completed_at
.as_ref()
.map(|ts| helpers::datetime_to_prost_timestamp(ts)),
max_retry_count: j.max_retry_count as u32,
attempt_count: j.attempt_count as u32,
scheduler_run_after: Some(helpers::datetime_to_prost_timestamp(
&j.scheduler_run_after,
)),
warning_msg: j.warning_msg.clone(),
error_msg: j.error_msg.clone(),
})
.collect(),
});
resp.metadata_mut().insert(
"x-log-fuota_deployment_id",
req.fuota_deployment_id.parse().unwrap(),
);
Ok(resp)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::api::auth::validator::RequestValidator;
use crate::api::auth::AuthID;
use crate::storage::{application, device, device_profile, gateway, tenant, user};
use crate::test;
#[tokio::test]
async fn test_fuota() {
let _guard = test::prepare().await;
// setup admin user
let u = user::User {
is_admin: true,
is_active: true,
email: "admin@admin".into(),
email_verified: true,
..Default::default()
};
let u = user::create(u).await.unwrap();
// create tenant
let t = tenant::create(tenant::Tenant {
name: "test-tenant".into(),
can_have_gateways: true,
..Default::default()
})
.await
.unwrap();
// create app
let app = application::create(application::Application {
tenant_id: t.id,
name: "test-app".into(),
..Default::default()
})
.await
.unwrap();
// create dp
let dp = device_profile::create(device_profile::DeviceProfile {
tenant_id: t.id,
name: "test-dp".into(),
..Default::default()
})
.await
.unwrap();
// create device
let dev = device::create(device::Device {
dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
application_id: app.id,
device_profile_id: dp.id,
..Default::default()
})
.await
.unwrap();
// create gateway
let gw = gateway::create(gateway::Gateway {
gateway_id: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
tenant_id: t.id,
name: "test-gw".into(),
..Default::default()
})
.await
.unwrap();
// setup api
let service = Fuota::new(RequestValidator::new());
// create deployment
let create_req = get_request(
&u.id,
api::CreateFuotaDeploymentRequest {
deployment: Some(api::FuotaDeployment {
application_id: app.id.to_string(),
device_profile_id: dp.id.to_string(),
name: "test-fuota".into(),
..Default::default()
}),
},
);
let create_resp = service.create_deployment(create_req).await.unwrap();
let create_resp = create_resp.get_ref();
// get deployment
let get_req = get_request(
&u.id,
api::GetFuotaDeploymentRequest {
id: create_resp.id.clone(),
},
);
let get_resp = service.get_deployment(get_req).await.unwrap();
let get_resp = get_resp.get_ref();
assert_eq!(
Some(api::FuotaDeployment {
id: create_resp.id.clone(),
application_id: app.id.to_string(),
device_profile_id: dp.id.to_string(),
name: "test-fuota".into(),
..Default::default()
}),
get_resp.deployment
);
// update deployment
let update_req = get_request(
&u.id,
api::UpdateFuotaDeploymentRequest {
deployment: Some(api::FuotaDeployment {
id: create_resp.id.clone(),
application_id: app.id.to_string(),
device_profile_id: dp.id.to_string(),
name: "updated-test-fuota".into(),
..Default::default()
}),
},
);
service.update_deployment(update_req).await.unwrap();
let get_req = get_request(
&u.id,
api::GetFuotaDeploymentRequest {
id: create_resp.id.clone(),
},
);
let get_resp = service.get_deployment(get_req).await.unwrap();
let get_resp = get_resp.get_ref();
assert_eq!(
Some(api::FuotaDeployment {
id: create_resp.id.clone(),
application_id: app.id.to_string(),
device_profile_id: dp.id.to_string(),
name: "updated-test-fuota".into(),
..Default::default()
}),
get_resp.deployment
);
// list deployments
let list_req = get_request(
&u.id,
api::ListFuotaDeploymentsRequest {
application_id: app.id.to_string(),
limit: 10,
offset: 0,
},
);
let list_resp = service.list_deployments(list_req).await.unwrap();
let list_resp = list_resp.get_ref();
assert_eq!(1, list_resp.total_count);
assert_eq!(1, list_resp.result.len());
assert_eq!(create_resp.id, list_resp.result[0].id);
// add device
let add_dev_req = get_request(
&u.id,
api::AddDevicesToFuotaDeploymentRequest {
fuota_deployment_id: create_resp.id.clone(),
dev_euis: vec![dev.dev_eui.to_string()],
},
);
service.add_devices(add_dev_req).await.unwrap();
// list devices
let list_devs_req = get_request(
&u.id,
api::ListFuotaDeploymentDevicesRequest {
fuota_deployment_id: create_resp.id.clone(),
limit: 10,
offset: 0,
},
);
let list_devs_resp = service.list_devices(list_devs_req).await.unwrap();
let list_devs_resp = list_devs_resp.get_ref();
assert_eq!(1, list_devs_resp.total_count);
assert_eq!(1, list_devs_resp.result.len());
assert_eq!(dev.dev_eui.to_string(), list_devs_resp.result[0].dev_eui);
// remove devices
let remove_devs_req = get_request(
&u.id,
api::RemoveDevicesFromFuotaDeploymentRequest {
fuota_deployment_id: create_resp.id.clone(),
dev_euis: vec![dev.dev_eui.to_string()],
},
);
service.remove_devices(remove_devs_req).await.unwrap();
let list_devs_req = get_request(
&u.id,
api::ListFuotaDeploymentDevicesRequest {
fuota_deployment_id: create_resp.id.clone(),
limit: 10,
offset: 0,
},
);
let list_devs_resp = service.list_devices(list_devs_req).await.unwrap();
let list_devs_resp = list_devs_resp.get_ref();
assert_eq!(0, list_devs_resp.total_count);
assert_eq!(0, list_devs_resp.result.len());
// add gateway
let add_gws_req = get_request(
&u.id,
api::AddGatewaysToFuotaDeploymentRequest {
fuota_deployment_id: create_resp.id.clone(),
gateway_ids: vec![gw.gateway_id.to_string()],
},
);
service.add_gateways(add_gws_req).await.unwrap();
// list gateways
let list_gws_req = get_request(
&u.id,
api::ListFuotaDeploymentGatewaysRequest {
fuota_deployment_id: create_resp.id.clone(),
limit: 10,
offset: 0,
},
);
let list_gws_resp = service.list_gateways(list_gws_req).await.unwrap();
let list_gws_resp = list_gws_resp.get_ref();
assert_eq!(1, list_gws_resp.total_count);
assert_eq!(1, list_gws_resp.result.len());
assert_eq!(
gw.gateway_id.to_string(),
list_gws_resp.result[0].gateway_id
);
// remove gateways
let remove_gws_req = get_request(
&u.id,
api::RemoveGatewaysFromFuotaDeploymentRequest {
fuota_deployment_id: create_resp.id.clone(),
gateway_ids: vec![gw.gateway_id.to_string()],
},
);
service.remove_gateways(remove_gws_req).await.unwrap();
let list_gws_req = get_request(
&u.id,
api::ListFuotaDeploymentGatewaysRequest {
fuota_deployment_id: create_resp.id.clone(),
limit: 10,
offset: 0,
},
);
let list_gws_resp = service.list_gateways(list_gws_req).await.unwrap();
let list_gws_resp = list_gws_resp.get_ref();
assert_eq!(0, list_gws_resp.total_count);
assert_eq!(0, list_gws_resp.result.len());
// start deployment
let start_req = get_request(
&u.id,
api::StartFuotaDeploymentRequest {
id: create_resp.id.clone(),
},
);
service.start_deployment(start_req).await.unwrap();
let jobs = fuota::list_jobs(Uuid::from_str(&create_resp.id).unwrap())
.await
.unwrap();
assert_eq!(1, jobs.len());
assert_eq!(create_resp.id, jobs[0].fuota_deployment_id.to_string());
assert_eq!(fields::FuotaJob::CreateMcGroup, jobs[0].job);
// delete deployment
let delete_req = get_request(
&u.id,
api::DeleteFuotaDeploymentRequest {
id: create_resp.id.clone(),
},
);
service.delete_deployment(delete_req).await.unwrap();
let delete_req = get_request(
&u.id,
api::DeleteFuotaDeploymentRequest {
id: create_resp.id.clone(),
},
);
assert!(service.delete_deployment(delete_req).await.is_err());
}
fn get_request<T>(user_id: &Uuid, req: T) -> Request<T> {
let mut req = Request::new(req);
req.extensions_mut().insert(AuthID::User(*user_id));
req
}
}

View File

@ -238,7 +238,13 @@ impl GatewayService for Gateway {
}; };
let count = gateway::get_count(&filters).await.map_err(|e| e.status())?; let count = gateway::get_count(&filters).await.map_err(|e| e.status())?;
let result = gateway::list(req.limit as i64, req.offset as i64, &filters) let result = gateway::list(
req.limit as i64,
req.offset as i64,
&filters,
req.order_by().from_proto(),
req.order_by_desc,
)
.await .await
.map_err(|e| e.status())?; .map_err(|e| e.status())?;

View File

@ -1,11 +1,14 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crate::codec::Codec;
use crate::storage::fields::{MeasurementKind, MulticastGroupSchedulingType};
use crate::storage::{device::DeviceClass, metrics::Aggregation};
use chirpstack_api::{api, common}; use chirpstack_api::{api, common};
use lrwn::region::{CommonName, MacVersion, Revision}; use lrwn::region::{CommonName, MacVersion, Revision};
use crate::codec::Codec;
use crate::storage::fields::{
self, MeasurementKind, MulticastGroupSchedulingType, RequestFragmentationSessionStatus,
};
use crate::storage::{device, device::DeviceClass, gateway, metrics::Aggregation};
pub trait FromProto<T> { pub trait FromProto<T> {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
fn from_proto(self) -> T; fn from_proto(self) -> T;
@ -263,6 +266,107 @@ impl ToProto<common::DeviceClass> for DeviceClass {
} }
} }
impl FromProto<device::OrderBy> for api::list_devices_request::OrderBy {
fn from_proto(self) -> device::OrderBy {
match self {
Self::Name => device::OrderBy::Name,
Self::DevEui => device::OrderBy::DevEui,
Self::LastSeenAt => device::OrderBy::LastSeenAt,
Self::DeviceProfileName => device::OrderBy::DeviceProfileName,
}
}
}
impl FromProto<gateway::OrderBy> for api::list_gateways_request::OrderBy {
fn from_proto(self) -> gateway::OrderBy {
match self {
Self::Name => gateway::OrderBy::Name,
Self::GatewayId => gateway::OrderBy::GatewayId,
Self::LastSeenAt => gateway::OrderBy::LastSeenAt,
}
}
}
impl ToProto<api::Ts003Version> for Option<fields::device_profile::Ts003Version> {
fn to_proto(self) -> api::Ts003Version {
match self {
None => api::Ts003Version::Ts003NotImplemented,
Some(fields::device_profile::Ts003Version::V100) => api::Ts003Version::Ts003V100,
Some(fields::device_profile::Ts003Version::V200) => api::Ts003Version::Ts003V200,
}
}
}
impl FromProto<Option<fields::device_profile::Ts003Version>> for api::Ts003Version {
fn from_proto(self) -> Option<fields::device_profile::Ts003Version> {
match self {
api::Ts003Version::Ts003NotImplemented => None,
api::Ts003Version::Ts003V100 => Some(fields::device_profile::Ts003Version::V100),
api::Ts003Version::Ts003V200 => Some(fields::device_profile::Ts003Version::V200),
}
}
}
impl ToProto<api::Ts004Version> for Option<fields::device_profile::Ts004Version> {
fn to_proto(self) -> api::Ts004Version {
match self {
None => api::Ts004Version::Ts004NotImplemented,
Some(fields::device_profile::Ts004Version::V100) => api::Ts004Version::Ts004V100,
Some(fields::device_profile::Ts004Version::V200) => api::Ts004Version::Ts004V200,
}
}
}
impl FromProto<Option<fields::device_profile::Ts004Version>> for api::Ts004Version {
fn from_proto(self) -> Option<fields::device_profile::Ts004Version> {
match self {
api::Ts004Version::Ts004NotImplemented => None,
api::Ts004Version::Ts004V100 => Some(fields::device_profile::Ts004Version::V100),
api::Ts004Version::Ts004V200 => Some(fields::device_profile::Ts004Version::V200),
}
}
}
impl ToProto<api::Ts005Version> for Option<fields::device_profile::Ts005Version> {
fn to_proto(self) -> api::Ts005Version {
match self {
None => api::Ts005Version::Ts005NotImplemented,
Some(fields::device_profile::Ts005Version::V100) => api::Ts005Version::Ts005V100,
Some(fields::device_profile::Ts005Version::V200) => api::Ts005Version::Ts005V200,
}
}
}
impl FromProto<Option<fields::device_profile::Ts005Version>> for api::Ts005Version {
fn from_proto(self) -> Option<fields::device_profile::Ts005Version> {
match self {
api::Ts005Version::Ts005NotImplemented => None,
api::Ts005Version::Ts005V100 => Some(fields::device_profile::Ts005Version::V100),
api::Ts005Version::Ts005V200 => Some(fields::device_profile::Ts005Version::V200),
}
}
}
impl ToProto<api::RequestFragmentationSessionStatus> for RequestFragmentationSessionStatus {
fn to_proto(self) -> api::RequestFragmentationSessionStatus {
match self {
Self::NoRequest => api::RequestFragmentationSessionStatus::NoRequest,
Self::AfterFragEnqueue => api::RequestFragmentationSessionStatus::AfterFragmentEnqueue,
Self::AfterSessTimeout => api::RequestFragmentationSessionStatus::AfterSessionTimeout,
}
}
}
impl FromProto<RequestFragmentationSessionStatus> for api::RequestFragmentationSessionStatus {
fn from_proto(self) -> RequestFragmentationSessionStatus {
match self {
Self::NoRequest => RequestFragmentationSessionStatus::NoRequest,
Self::AfterFragmentEnqueue => RequestFragmentationSessionStatus::AfterFragEnqueue,
Self::AfterSessionTimeout => RequestFragmentationSessionStatus::AfterSessTimeout,
}
}
}
pub fn datetime_to_prost_timestamp(dt: &DateTime<Utc>) -> prost_types::Timestamp { pub fn datetime_to_prost_timestamp(dt: &DateTime<Utc>) -> prost_types::Timestamp {
let ts = dt.timestamp_nanos_opt().unwrap_or_default(); let ts = dt.timestamp_nanos_opt().unwrap_or_default();

View File

@ -32,6 +32,7 @@ use chirpstack_api::api::application_service_server::ApplicationServiceServer;
use chirpstack_api::api::device_profile_service_server::DeviceProfileServiceServer; use chirpstack_api::api::device_profile_service_server::DeviceProfileServiceServer;
use chirpstack_api::api::device_profile_template_service_server::DeviceProfileTemplateServiceServer; use chirpstack_api::api::device_profile_template_service_server::DeviceProfileTemplateServiceServer;
use chirpstack_api::api::device_service_server::DeviceServiceServer; use chirpstack_api::api::device_service_server::DeviceServiceServer;
use chirpstack_api::api::fuota_service_server::FuotaServiceServer;
use chirpstack_api::api::gateway_service_server::GatewayServiceServer; use chirpstack_api::api::gateway_service_server::GatewayServiceServer;
use chirpstack_api::api::internal_service_server::InternalServiceServer; use chirpstack_api::api::internal_service_server::InternalServiceServer;
use chirpstack_api::api::multicast_group_service_server::MulticastGroupServiceServer; use chirpstack_api::api::multicast_group_service_server::MulticastGroupServiceServer;
@ -53,6 +54,7 @@ pub mod device;
pub mod device_profile; pub mod device_profile;
pub mod device_profile_template; pub mod device_profile_template;
pub mod error; pub mod error;
pub mod fuota;
pub mod gateway; pub mod gateway;
mod grpc_multiplex; mod grpc_multiplex;
pub mod helpers; pub mod helpers;
@ -175,6 +177,10 @@ pub async fn setup() -> Result<()> {
.add_service(RelayServiceServer::with_interceptor( .add_service(RelayServiceServer::with_interceptor(
relay::Relay::new(validator::RequestValidator::new()), relay::Relay::new(validator::RequestValidator::new()),
auth::auth_interceptor, auth::auth_interceptor,
))
.add_service(FuotaServiceServer::with_interceptor(
fuota::Fuota::new(validator::RequestValidator::new()),
auth::auth_interceptor,
)); ));
let backend_handle = tokio::spawn(backend::setup()); let backend_handle = tokio::spawn(backend::setup());

View File

@ -28,6 +28,12 @@ struct ClerkUserinfo {
pub user_id: String, pub user_id: String,
} }
#[derive(Deserialize)]
struct YandexUserinfo {
pub default_email: String,
pub id: String,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CallbackArgs { pub struct CallbackArgs {
pub code: String, pub code: String,
@ -129,9 +135,11 @@ pub async fn get_user(code: &str, state: &str) -> Result<User> {
let conf = config::get(); let conf = config::get();
let provider = conf.user_authentication.oauth2.provider.clone(); let provider = conf.user_authentication.oauth2.provider.clone();
let userinfo_url = conf.user_authentication.oauth2.userinfo_url.clone(); let userinfo_url = conf.user_authentication.oauth2.userinfo_url.clone();
let assume_email_verified = conf.user_authentication.oauth2.assume_email_verified;
match provider.as_ref() { match provider.as_ref() {
"clerk" => get_clerk_user(access_token, &userinfo_url).await, "clerk" => get_clerk_user(access_token, &userinfo_url).await,
"yandex" => get_yandex_user(access_token, &userinfo_url, assume_email_verified).await,
_ => Err(anyhow!("Unsupported OAuth2 provider: {}", provider)), _ => Err(anyhow!("Unsupported OAuth2 provider: {}", provider)),
} }
} }
@ -155,6 +163,25 @@ async fn get_clerk_user(token: &str, url: &str) -> Result<User> {
}) })
} }
async fn get_yandex_user(token: &str, url: &str, assume_email_verified: bool) -> Result<User> {
let client = reqwest::Client::new();
let auth_header = format!("Bearer {}", token);
let resp: YandexUserinfo = client
.get(url)
.header(AUTHORIZATION, auth_header)
.send()
.await?
.json()
.await?;
Ok(User {
email: resp.default_email,
email_verified: assume_email_verified,
external_id: resp.id,
})
}
async fn store_verifier( async fn store_verifier(
token: &oauth2::CsrfToken, token: &oauth2::CsrfToken,
verifier: &oauth2::PkceCodeVerifier, verifier: &oauth2::PkceCodeVerifier,

View File

@ -182,7 +182,7 @@ pub mod test {
use super::*; use super::*;
use crate::api::auth::validator::RequestValidator; use crate::api::auth::validator::RequestValidator;
use crate::api::auth::AuthID; use crate::api::auth::AuthID;
use crate::storage::{application, device, device_profile, tenant, user}; use crate::storage::{application, device, device_profile, fields, tenant, user};
use crate::test; use crate::test;
#[tokio::test] #[tokio::test]
@ -229,8 +229,11 @@ pub mod test {
let dp_relay = device_profile::create(device_profile::DeviceProfile { let dp_relay = device_profile::create(device_profile::DeviceProfile {
name: "test-dp".into(), name: "test-dp".into(),
tenant_id: t.id, tenant_id: t.id,
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
..Default::default() ..Default::default()
}),
..Default::default()
}) })
.await .await
.unwrap(); .unwrap();

View File

@ -0,0 +1,468 @@
use anyhow::Result;
use tracing::info;
use crate::gpstime::ToGpsTime;
use crate::storage::fields::device_profile::Ts003Version;
use crate::storage::{device, device_profile, device_queue};
use crate::uplink::helpers;
use chirpstack_api::gw;
use lrwn::applayer::clocksync;
pub async fn handle_uplink(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
data: &[u8],
) -> Result<()> {
let version = dp
.app_layer_params
.ts003_version
.ok_or_else(|| anyhow!("Device does not support TS003"))?;
match version {
Ts003Version::V100 => handle_uplink_v100(dev, dp, rx_info, data).await,
Ts003Version::V200 => handle_uplink_v200(dev, dp, rx_info, data).await,
}
}
async fn handle_uplink_v100(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
data: &[u8],
) -> Result<()> {
let pl = clocksync::v1::Payload::from_slice(true, data)?;
match pl {
clocksync::v1::Payload::AppTimeReq(pl) => {
handle_v1_app_time_req(dev, dp, rx_info, pl).await?
}
_ => {}
}
Ok(())
}
async fn handle_uplink_v200(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
data: &[u8],
) -> Result<()> {
let pl = clocksync::v2::Payload::from_slice(true, data)?;
match pl {
clocksync::v2::Payload::AppTimeReq(pl) => {
handle_v2_app_time_req(dev, dp, rx_info, pl).await?
}
_ => {}
}
Ok(())
}
async fn handle_v1_app_time_req(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
pl: clocksync::v1::AppTimeReqPayload,
) -> Result<()> {
info!("Handling AppTimeReq");
let now_time_since_gps = if let Some(t) = helpers::get_time_since_gps_epoch(rx_info) {
chrono::Duration::from_std(t)?
} else {
helpers::get_rx_timestamp_chrono(rx_info).to_gps_time()
};
let dev_time_since_gps = chrono::Duration::seconds(pl.device_time.into());
let time_diff = (now_time_since_gps - dev_time_since_gps).num_seconds();
let time_correction: i32 = if time_diff < 0 {
time_diff.try_into().unwrap_or(i32::MIN)
} else {
time_diff.try_into().unwrap_or(i32::MAX)
};
if time_diff == 0 && !pl.param.ans_required {
return Ok(());
}
info!(
time_correcrtion = time_correction,
"Responding with AppTimeAns"
);
let ans = clocksync::v1::Payload::AppTimeAns(clocksync::v1::AppTimeAnsPayload {
time_correction,
param: clocksync::v1::AppTimeAnsPayloadParam {
token_ans: pl.param.token_req,
},
});
device_queue::enqueue_item(device_queue::DeviceQueueItem {
dev_eui: dev.dev_eui,
f_port: dp.app_layer_params.ts003_f_port.into(),
data: ans.to_vec()?,
..Default::default()
})
.await?;
Ok(())
}
async fn handle_v2_app_time_req(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
pl: clocksync::v2::AppTimeReqPayload,
) -> Result<()> {
info!("Handling AppTimeReq");
let now_time_since_gps = if let Some(t) = helpers::get_time_since_gps_epoch(rx_info) {
chrono::Duration::from_std(t)?
} else {
helpers::get_rx_timestamp_chrono(rx_info).to_gps_time()
};
let dev_time_since_gps = chrono::Duration::seconds(pl.device_time.into());
let time_diff = (now_time_since_gps - dev_time_since_gps).num_seconds();
let time_correction: i32 = if time_diff < 0 {
time_diff.try_into().unwrap_or(i32::MIN)
} else {
time_diff.try_into().unwrap_or(i32::MAX)
};
if time_diff == 0 && !pl.param.ans_required {
return Ok(());
}
info!(
time_correcrtion = time_correction,
"Responding with AppTimeAns"
);
let ans = clocksync::v2::Payload::AppTimeAns(clocksync::v2::AppTimeAnsPayload {
time_correction,
param: clocksync::v2::AppTimeAnsPayloadParam {
token_ans: pl.param.token_req,
},
});
device_queue::enqueue_item(device_queue::DeviceQueueItem {
dev_eui: dev.dev_eui,
f_port: dp.app_layer_params.ts003_f_port.into(),
data: ans.to_vec()?,
..Default::default()
})
.await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::applayer::handle_uplink;
use crate::storage::{application, device_queue, fields, tenant};
use crate::test;
use lrwn::EUI64;
use std::time::Duration;
#[tokio::test]
async fn test_handle_v1_app_time_req() {
struct Test {
name: String,
rx_info: gw::UplinkRxInfo,
req: clocksync::v1::AppTimeReqPayload,
expected: Option<clocksync::v1::AppTimeAnsPayload>,
}
let tests = vec![
Test {
name: "device synced".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
..Default::default()
},
req: clocksync::v1::AppTimeReqPayload {
device_time: 1234,
param: clocksync::v1::AppTimeReqPayloadParam {
token_req: 8,
ans_required: false,
},
},
expected: None,
},
Test {
name: "device synced - ans required".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
..Default::default()
},
req: clocksync::v1::AppTimeReqPayload {
device_time: 1234,
param: clocksync::v1::AppTimeReqPayloadParam {
token_req: 8,
ans_required: true,
},
},
expected: Some(clocksync::v1::AppTimeAnsPayload {
time_correction: 0,
param: clocksync::v1::AppTimeAnsPayloadParam { token_ans: 8 },
}),
},
Test {
name: "device not synced (positive correction)".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
..Default::default()
},
req: clocksync::v1::AppTimeReqPayload {
device_time: 1200,
param: clocksync::v1::AppTimeReqPayloadParam {
token_req: 8,
ans_required: false,
},
},
expected: Some(clocksync::v1::AppTimeAnsPayload {
time_correction: 34,
param: clocksync::v1::AppTimeAnsPayloadParam { token_ans: 8 },
}),
},
Test {
name: "device not synced (negative correction)".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1200).try_into().unwrap()),
..Default::default()
},
req: clocksync::v1::AppTimeReqPayload {
device_time: 1234,
param: clocksync::v1::AppTimeReqPayloadParam {
token_req: 8,
ans_required: false,
},
},
expected: Some(clocksync::v1::AppTimeAnsPayload {
time_correction: -34,
param: clocksync::v1::AppTimeAnsPayloadParam { token_ans: 8 },
}),
},
];
let _guard = test::prepare().await;
let t = tenant::create(tenant::Tenant {
name: "test-tenant".into(),
..Default::default()
})
.await
.unwrap();
let app = application::create(application::Application {
name: "test-app".into(),
tenant_id: t.id,
..Default::default()
})
.await
.unwrap();
let dp = device_profile::create(device_profile::DeviceProfile {
name: "test-dp".into(),
tenant_id: t.id,
app_layer_params: fields::AppLayerParams {
ts003_version: Some(Ts003Version::V100),
..Default::default()
},
..Default::default()
})
.await
.unwrap();
let d = device::create(device::Device {
name: "test-dev".into(),
dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
application_id: app.id,
device_profile_id: dp.id,
..Default::default()
})
.await
.unwrap();
for tst in &tests {
println!("> {}", tst.name);
device_queue::flush_for_dev_eui(&d.dev_eui).await.unwrap();
let pl = clocksync::v1::Payload::AppTimeReq(tst.req.clone());
handle_uplink(
&d,
&dp,
&[tst.rx_info.clone()],
dp.app_layer_params.ts003_f_port,
&pl.to_vec().unwrap(),
)
.await;
let queue_items = device_queue::get_for_dev_eui(&d.dev_eui).await.unwrap();
if let Some(expected_pl) = &tst.expected {
assert_eq!(1, queue_items.len());
let qi = queue_items.first().unwrap();
assert_eq!(dp.app_layer_params.ts003_f_port as i16, qi.f_port);
let qi_pl = clocksync::v1::Payload::from_slice(false, &qi.data).unwrap();
let expected_pl = clocksync::v1::Payload::AppTimeAns(expected_pl.clone());
assert_eq!(expected_pl, qi_pl);
} else {
assert!(queue_items.is_empty());
}
}
}
#[tokio::test]
async fn test_handle_v2_app_time_req() {
struct Test {
name: String,
rx_info: gw::UplinkRxInfo,
req: clocksync::v2::AppTimeReqPayload,
expected: Option<clocksync::v2::AppTimeAnsPayload>,
}
let tests = vec![
Test {
name: "device synced".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
..Default::default()
},
req: clocksync::v2::AppTimeReqPayload {
device_time: 1234,
param: clocksync::v2::AppTimeReqPayloadParam {
token_req: 8,
ans_required: false,
},
},
expected: None,
},
Test {
name: "device synced - ans required".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
..Default::default()
},
req: clocksync::v2::AppTimeReqPayload {
device_time: 1234,
param: clocksync::v2::AppTimeReqPayloadParam {
token_req: 8,
ans_required: true,
},
},
expected: Some(clocksync::v2::AppTimeAnsPayload {
time_correction: 0,
param: clocksync::v2::AppTimeAnsPayloadParam { token_ans: 8 },
}),
},
Test {
name: "device not synced (positive correction)".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1234).try_into().unwrap()),
..Default::default()
},
req: clocksync::v2::AppTimeReqPayload {
device_time: 1200,
param: clocksync::v2::AppTimeReqPayloadParam {
token_req: 8,
ans_required: false,
},
},
expected: Some(clocksync::v2::AppTimeAnsPayload {
time_correction: 34,
param: clocksync::v2::AppTimeAnsPayloadParam { token_ans: 8 },
}),
},
Test {
name: "device not synced (negative correction)".into(),
rx_info: gw::UplinkRxInfo {
time_since_gps_epoch: Some(Duration::from_secs(1200).try_into().unwrap()),
..Default::default()
},
req: clocksync::v2::AppTimeReqPayload {
device_time: 1234,
param: clocksync::v2::AppTimeReqPayloadParam {
token_req: 8,
ans_required: false,
},
},
expected: Some(clocksync::v2::AppTimeAnsPayload {
time_correction: -34,
param: clocksync::v2::AppTimeAnsPayloadParam { token_ans: 8 },
}),
},
];
let _guard = test::prepare().await;
let t = tenant::create(tenant::Tenant {
name: "test-tenant".into(),
..Default::default()
})
.await
.unwrap();
let app = application::create(application::Application {
name: "test-app".into(),
tenant_id: t.id,
..Default::default()
})
.await
.unwrap();
let dp = device_profile::create(device_profile::DeviceProfile {
name: "test-dp".into(),
tenant_id: t.id,
app_layer_params: fields::AppLayerParams {
ts003_version: Some(Ts003Version::V200),
..Default::default()
},
..Default::default()
})
.await
.unwrap();
let d = device::create(device::Device {
name: "test-dev".into(),
dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
application_id: app.id,
device_profile_id: dp.id,
..Default::default()
})
.await
.unwrap();
for tst in &tests {
println!("> {}", tst.name);
device_queue::flush_for_dev_eui(&d.dev_eui).await.unwrap();
let pl = clocksync::v2::Payload::AppTimeReq(tst.req.clone());
handle_uplink(
&d,
&dp,
&[tst.rx_info.clone()],
dp.app_layer_params.ts003_f_port,
&pl.to_vec().unwrap(),
)
.await;
let queue_items = device_queue::get_for_dev_eui(&d.dev_eui).await.unwrap();
if let Some(expected_pl) = &tst.expected {
assert_eq!(1, queue_items.len());
let qi = queue_items.first().unwrap();
assert_eq!(dp.app_layer_params.ts003_f_port as i16, qi.f_port);
let qi_pl = clocksync::v2::Payload::from_slice(false, &qi.data).unwrap();
let expected_pl = clocksync::v2::Payload::AppTimeAns(expected_pl.clone());
assert_eq!(expected_pl, qi_pl);
} else {
assert!(queue_items.is_empty());
}
}
}
}

View File

@ -0,0 +1,179 @@
use anyhow::Result;
use chrono::Utc;
use tracing::{info, warn};
use crate::storage::fields::device_profile::Ts004Version;
use crate::storage::{device, device_profile, fuota};
use lrwn::applayer::fragmentation;
pub async fn handle_uplink(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
data: &[u8],
) -> Result<()> {
let version = dp
.app_layer_params
.ts004_version
.ok_or_else(|| anyhow!("Device does not support TS004"))?;
match version {
Ts004Version::V100 => handle_uplink_v100(dev, data).await,
Ts004Version::V200 => handle_uplink_v200(dev, data).await,
}
}
async fn handle_uplink_v100(dev: &device::Device, data: &[u8]) -> Result<()> {
let pl = fragmentation::v1::Payload::from_slice(true, data)?;
match pl {
fragmentation::v1::Payload::FragSessionSetupAns(pl) => {
handle_v1_frag_session_setup_ans(dev, pl).await?
}
fragmentation::v1::Payload::FragSessionStatusAns(pl) => {
handle_v1_frag_session_status_ans(dev, pl).await?
}
_ => {}
}
Ok(())
}
async fn handle_uplink_v200(dev: &device::Device, data: &[u8]) -> Result<()> {
let pl = fragmentation::v2::Payload::from_slice(true, data)?;
match pl {
fragmentation::v2::Payload::FragSessionSetupAns(pl) => {
handle_v2_frag_session_setup_ans(dev, pl).await?
}
fragmentation::v2::Payload::FragSessionStatusAns(pl) => {
handle_v2_frag_session_status_ans(dev, pl).await?
}
_ => {}
}
Ok(())
}
async fn handle_v1_frag_session_setup_ans(
dev: &device::Device,
pl: fragmentation::v1::FragSessionSetupAnsPayload,
) -> Result<()> {
info!("Handling FragSessionSetupAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.encoding_unsupported
| pl.not_enough_memory
| pl.frag_session_index_not_supported
| pl.wrong_descriptor
{
warn!(
frag_index = pl.frag_index,
encoding_unsupported = pl.encoding_unsupported,
not_enough_memory = pl.not_enough_memory,
frag_session_index_not_supported = pl.frag_session_index_not_supported,
wrong_descriptor = pl.wrong_descriptor,
"FragSessionAns contains errors"
);
fuota_dev.error_msg = format!("Error: FragSessionAns response encoding_unsupported={}, not_enough_memory={}, frag_session_index_not_supported={}, wrong_descriptor={}", pl.encoding_unsupported, pl.not_enough_memory, pl.frag_session_index_not_supported, pl.wrong_descriptor);
} else {
fuota_dev.frag_session_setup_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v2_frag_session_setup_ans(
dev: &device::Device,
pl: fragmentation::v2::FragSessionSetupAnsPayload,
) -> Result<()> {
info!("Handling FragSessionSetupAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.frag_algo_unsupported
| pl.not_enough_memory
| pl.frag_index_unsupported
| pl.wrong_descriptor
| pl.session_cnt_replay
{
warn!(
frag_index = pl.frag_index,
frag_algo_unsupported = pl.frag_algo_unsupported,
not_enough_memory = pl.not_enough_memory,
frag_index_unsupported = pl.frag_index_unsupported,
wrong_descriptor = pl.wrong_descriptor,
session_cnt_replay = pl.session_cnt_replay,
"FragSessionAns contains errors"
);
fuota_dev.error_msg = format!("Error: FragSessionAns response frag_algo_unsupported={}, not_enough_memory={}, frag_index_unsupported={}, wrong_descriptor={}, session_cnt_replay={}", pl.frag_algo_unsupported, pl.not_enough_memory, pl.frag_index_unsupported, pl.wrong_descriptor, pl.session_cnt_replay);
} else {
fuota_dev.frag_session_setup_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v1_frag_session_status_ans(
dev: &device::Device,
pl: fragmentation::v1::FragSessionStatusAnsPayload,
) -> Result<()> {
info!("Handling FragSessionStatusAnsPayload");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.missing_frag != 0 || pl.status.not_enough_matrix_memory {
warn!(
frag_index = pl.received_and_index.frag_index,
nb_frag_received = pl.received_and_index.nb_frag_received,
missing_frag = pl.missing_frag,
not_enough_matrix_memory = pl.status.not_enough_matrix_memory,
"FragSessionStatusAns contains errors"
);
fuota_dev.error_msg = format!("Error: FragSessionStatusAns response nb_frag_received={}, missing_frag={}, not_enough_matrix_memory={}", pl.received_and_index.nb_frag_received, pl.missing_frag, pl.status.not_enough_matrix_memory);
} else {
fuota_dev.frag_status_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v2_frag_session_status_ans(
dev: &device::Device,
pl: fragmentation::v2::FragSessionStatusAnsPayload,
) -> Result<()> {
info!("Handling FragSessionStatusAnsPayload");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.missing_frag != 0
|| pl.status.memory_error
|| pl.status.mic_error
|| pl.status.session_does_not_exist
{
warn!(
frag_index = pl.received_and_index.frag_index,
nb_frag_received = pl.received_and_index.nb_frag_received,
missing_frag = pl.missing_frag,
memory_error = pl.status.memory_error,
mic_error = pl.status.mic_error,
session_does_not_exist = pl.status.session_does_not_exist,
"FragSessionStatusAns contains errors"
);
fuota_dev.error_msg = format!("Error: FragSessionStatusAns response nb_frag_received={}, missing_frag={}, memory_error={}, mic_error={}, session_does_not_exist={}", pl.received_and_index.nb_frag_received, pl.missing_frag, pl.status.memory_error, pl.status.mic_error, pl.status.session_does_not_exist);
} else {
fuota_dev.frag_status_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}

View File

@ -0,0 +1,921 @@
use std::ops::DerefMut;
use std::time::Duration;
use anyhow::Result;
use chrono::{DateTime, TimeDelta, Utc};
use tracing::info;
use lrwn::applayer::{fragmentation, multicastsetup};
use lrwn::region::MacVersion;
use crate::config;
use crate::downlink;
use crate::gpstime::ToGpsTime;
use crate::storage::fields::{
device_profile::Ts004Version, device_profile::Ts005Version, FuotaJob,
RequestFragmentationSessionStatus,
};
use crate::storage::{device, device_keys, device_profile, device_queue, fuota, multicast};
pub struct Flow {
scheduler_interval: Duration,
job: fuota::FuotaDeploymentJob,
fuota_deployment: fuota::FuotaDeployment,
device_profile: device_profile::DeviceProfile,
}
impl Flow {
pub async fn handle_job(job: fuota::FuotaDeploymentJob) -> Result<()> {
let conf = config::get();
let fuota_deployment = fuota::get_deployment(job.fuota_deployment_id.into()).await?;
let device_profile =
device_profile::get(&fuota_deployment.device_profile_id.into()).await?;
let mut flow = Flow {
job,
fuota_deployment,
device_profile,
scheduler_interval: conf.network.scheduler.interval,
};
flow.dispatch().await
}
async fn dispatch(&mut self) -> Result<()> {
let resp = match self.job.job {
FuotaJob::CreateMcGroup => self.create_mc_group().await,
FuotaJob::AddDevsToMcGroup => self.add_devices_to_multicast_group().await,
FuotaJob::AddGwsToMcGroup => self.add_gateways_to_multicast_group().await,
FuotaJob::McGroupSetup => self.multicast_group_setup().await,
FuotaJob::FragSessionSetup => self.fragmentation_session_setup().await,
FuotaJob::McSession => self.multicast_session_setup().await,
FuotaJob::Enqueue => self.enqueue().await,
FuotaJob::FragStatus => self.fragmentation_status().await,
FuotaJob::DeleteMcGroup => self.delete_mc_group().await,
FuotaJob::Complete => self.complete().await,
};
match resp {
Ok(Some((next_job, scheduler_run_after))) => {
if self.job.job == next_job {
// Re-run the same job in the future.
let mut job = self.job.clone();
job.scheduler_run_after = scheduler_run_after;
let _ = fuota::update_job(job).await?;
} else {
// Update the current job (to increment the attempt count).
let job = self.job.clone();
let _ = fuota::update_job(job).await?;
// Create the next job (which automatically sets the current job to completed).
let _ = fuota::create_job(fuota::FuotaDeploymentJob {
fuota_deployment_id: self.job.fuota_deployment_id,
job: next_job,
max_retry_count: match next_job {
FuotaJob::McGroupSetup
| FuotaJob::FragSessionSetup
| FuotaJob::McSession => self.fuota_deployment.unicast_max_retry_count,
_ => 0,
},
scheduler_run_after,
..Default::default()
})
.await?;
}
}
Ok(None) => {
// No further jobs to execute, set the current job to completed.
let mut job = self.job.clone();
job.completed_at = Some(Utc::now());
let _ = fuota::update_job(job).await?;
}
Err(e) => {
// Re-run the same job in the future.
let mut job = self.job.clone();
job.scheduler_run_after = Utc::now() + self.scheduler_interval;
job.error_msg = format!("Error: {}", e);
let _ = fuota::update_job(job).await?;
return Err(e);
}
}
Ok(())
}
async fn create_mc_group(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
// If this job fails, then there is no need to execute the others.
if self.job.attempt_count > self.job.max_retry_count {
return Ok(None);
}
info!("Creating multicast-group for FUOTA deployment");
self.job.attempt_count += 1;
// Get McAppSKey + McNwkSKey.
let (mc_app_s_key, mc_nwk_s_key) = match self.device_profile.app_layer_params.ts005_version
{
Some(Ts005Version::V100) => (
multicastsetup::v1::get_mc_app_s_key(
self.fuota_deployment.multicast_key,
self.fuota_deployment.multicast_addr,
)?,
multicastsetup::v1::get_mc_net_s_key(
self.fuota_deployment.multicast_key,
self.fuota_deployment.multicast_addr,
)?,
),
Some(Ts005Version::V200) => (
multicastsetup::v2::get_mc_app_s_key(
self.fuota_deployment.multicast_key,
self.fuota_deployment.multicast_addr,
)?,
multicastsetup::v2::get_mc_net_s_key(
self.fuota_deployment.multicast_key,
self.fuota_deployment.multicast_addr,
)?,
),
None => return Err(anyhow!("Device-profile does not support TS005")),
};
let _ = multicast::create(multicast::MulticastGroup {
id: self.fuota_deployment.id,
application_id: self.fuota_deployment.application_id,
name: format!("fuota-{}", self.fuota_deployment.id),
region: self.device_profile.region,
mc_addr: self.fuota_deployment.multicast_addr,
mc_nwk_s_key,
mc_app_s_key,
f_cnt: 0,
group_type: self.fuota_deployment.multicast_group_type.clone(),
frequency: self.fuota_deployment.multicast_frequency,
dr: self.fuota_deployment.multicast_dr,
class_b_ping_slot_nb_k: self.fuota_deployment.multicast_class_b_ping_slot_nb_k,
class_c_scheduling_type: self.fuota_deployment.multicast_class_c_scheduling_type,
..Default::default()
})
.await?;
Ok(Some((FuotaJob::AddDevsToMcGroup, Utc::now())))
}
async fn add_devices_to_multicast_group(
&mut self,
) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
// If this job fails, then there is no need to execute the others.
if self.job.attempt_count > self.job.max_retry_count {
return Ok(None);
}
info!("Adding devices to multicast-group");
self.job.attempt_count += 1;
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
for fuota_d in fuota_devices {
multicast::add_device(&fuota_d.fuota_deployment_id, &fuota_d.dev_eui).await?;
}
Ok(Some((FuotaJob::AddGwsToMcGroup, Utc::now())))
}
async fn add_gateways_to_multicast_group(
&mut self,
) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
// If this job fails, then there is no need to execute the others.
if self.job.attempt_count > self.job.max_retry_count {
return Ok(None);
}
info!("Adding gateways to multicast-group (if any)");
self.job.attempt_count += 1;
let fuota_gws = fuota::get_gateways(self.job.fuota_deployment_id.into(), -1, 0).await?;
for fuota_gw in fuota_gws {
multicast::add_gateway(&fuota_gw.fuota_deployment_id, &fuota_gw.gateway_id).await?;
}
Ok(Some((FuotaJob::McGroupSetup, Utc::now())))
}
async fn multicast_group_setup(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
// Filter on devices that have not completed the McGroupSetup.
let fuota_devices: Vec<fuota::FuotaDeploymentDevice> = fuota_devices
.into_iter()
.filter(|d| d.mc_group_setup_completed_at.is_none())
.collect();
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
info!("Set timeout error to devices that did not respond to McGroupSetupReq");
fuota::set_device_timeout_error(
self.fuota_deployment.id.into(),
true,
false,
false,
false,
)
.await?;
if !fuota_devices.is_empty() {
self.job.warning_msg = format!(
"{} devices did not complete the multicast group setup",
fuota_devices.len()
);
}
return Ok(Some((FuotaJob::FragSessionSetup, Utc::now())));
}
info!("Sending McGroupSetupReq commands to devices");
self.job.attempt_count += 1;
for fuota_dev in &fuota_devices {
let dev_keys = device_keys::get(&fuota_dev.dev_eui).await?;
let pl = match self.device_profile.app_layer_params.ts005_version {
Some(Ts005Version::V100) => {
let mc_root_key = match self.device_profile.mac_version {
MacVersion::LORAWAN_1_0_0
| MacVersion::LORAWAN_1_0_1
| MacVersion::LORAWAN_1_0_2
| MacVersion::LORAWAN_1_0_3
| MacVersion::LORAWAN_1_0_4 => {
multicastsetup::v1::get_mc_root_key_for_gen_app_key(
dev_keys.gen_app_key,
)?
}
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
multicastsetup::v1::get_mc_root_key_for_app_key(dev_keys.app_key)?
}
};
let mc_ke_key = multicastsetup::v1::get_mc_ke_key(mc_root_key)?;
let mc_key_encrypted = multicastsetup::v1::encrypt_mc_key(
mc_ke_key,
self.fuota_deployment.multicast_key,
);
multicastsetup::v1::Payload::McGroupSetupReq(
multicastsetup::v1::McGroupSetupReqPayload {
mc_group_id_header:
multicastsetup::v1::McGroupSetupReqPayloadMcGroupIdHeader {
mc_group_id: 0,
},
mc_addr: self.fuota_deployment.multicast_addr,
mc_key_encrypted,
min_mc_f_count: 0,
max_mc_f_count: u32::MAX,
},
)
.to_vec()?
}
Some(Ts005Version::V200) => {
let mc_root_key = match self.device_profile.mac_version {
MacVersion::LORAWAN_1_0_0
| MacVersion::LORAWAN_1_0_1
| MacVersion::LORAWAN_1_0_2
| MacVersion::LORAWAN_1_0_3
| MacVersion::LORAWAN_1_0_4 => {
multicastsetup::v2::get_mc_root_key_for_gen_app_key(
dev_keys.gen_app_key,
)?
}
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
multicastsetup::v2::get_mc_root_key_for_app_key(dev_keys.app_key)?
}
};
let mc_ke_key = multicastsetup::v2::get_mc_ke_key(mc_root_key)?;
let mc_key_encrypted = multicastsetup::v2::encrypt_mc_key(
mc_ke_key,
self.fuota_deployment.multicast_key,
);
multicastsetup::v2::Payload::McGroupSetupReq(
multicastsetup::v2::McGroupSetupReqPayload {
mc_group_id_header:
multicastsetup::v2::McGroupSetupReqPayloadMcGroupIdHeader {
mc_group_id: 0,
},
mc_addr: self.fuota_deployment.multicast_addr,
mc_key_encrypted,
min_mc_f_count: 0,
max_mc_f_count: u32::MAX,
},
)
.to_vec()?
}
None => return Err(anyhow!("Device-profile does not support TS005")),
};
let _ = device_queue::enqueue_item(device_queue::DeviceQueueItem {
dev_eui: fuota_dev.dev_eui,
f_port: self.device_profile.app_layer_params.ts005_f_port.into(),
data: pl,
..Default::default()
})
.await?;
}
if !fuota_devices.is_empty() {
// There are devices pending setup, we need to re-run this job.
let scheduler_run_after =
Utc::now() + TimeDelta::seconds(self.device_profile.uplink_interval as i64);
Ok(Some((FuotaJob::McGroupSetup, scheduler_run_after)))
} else {
// All devices have completed the setup, move on to next job.
Ok(Some((FuotaJob::FragSessionSetup, Utc::now())))
}
}
async fn fragmentation_session_setup(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
let fuota_devices_completed_mc_group_setup_count = fuota_devices
.iter()
.filter(|d| d.mc_group_setup_completed_at.is_some())
.count();
// Filter on devices that have completed the previous step, but not yet the FragSessionSetup.
let fuota_devices: Vec<fuota::FuotaDeploymentDevice> = fuota_devices
.into_iter()
.filter(|d| {
d.mc_group_setup_completed_at.is_some()
&& d.frag_session_setup_completed_at.is_none()
})
.collect();
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
info!("Set timeout error to devices that did not respond to FragSessionSetupReq");
fuota::set_device_timeout_error(
self.fuota_deployment.id.into(),
false,
false,
true,
false,
)
.await?;
if !fuota_devices.is_empty() {
self.job.warning_msg = format!(
"{} devices did not complete the fragmentation session setup",
fuota_devices.len()
);
}
return Ok(Some((FuotaJob::McSession, Utc::now())));
}
info!("Sending FragSessionSetupReq commands to devices");
self.job.attempt_count += 1;
if fuota_devices_completed_mc_group_setup_count == 0 {
self.job.error_msg = "There are no devices available to complete this step".into();
return Ok(Some((FuotaJob::DeleteMcGroup, Utc::now())));
}
let fragment_size = self.fuota_deployment.fragmentation_fragment_size as usize;
let fragments =
(self.fuota_deployment.payload.len() as f32 / fragment_size as f32).ceil() as usize;
let padding =
(fragment_size - (self.fuota_deployment.payload.len() % fragment_size)) % fragment_size;
for fuota_dev in &fuota_devices {
let pl = match self.device_profile.app_layer_params.ts004_version {
Some(Ts004Version::V100) => fragmentation::v1::Payload::FragSessionSetupReq(
fragmentation::v1::FragSessionSetupReqPayload {
frag_session: fragmentation::v1::FragSessionSetuReqPayloadFragSession {
mc_group_bit_mask: [true, false, false, false],
frag_index: 0,
},
nb_frag: fragments as u16,
frag_size: fragment_size as u8,
padding: padding as u8,
control: fragmentation::v1::FragSessionSetuReqPayloadControl {
block_ack_delay: 0,
fragmentation_matrix: 0,
},
descriptor: [0, 0, 0, 0],
},
)
.to_vec()?,
Some(Ts004Version::V200) => {
let dev_keys = device_keys::get(&fuota_dev.dev_eui).await?;
let data_block_int_key = match self.device_profile.mac_version {
MacVersion::LORAWAN_1_0_0
| MacVersion::LORAWAN_1_0_1
| MacVersion::LORAWAN_1_0_2
| MacVersion::LORAWAN_1_0_3
| MacVersion::LORAWAN_1_0_4 => {
fragmentation::v2::get_data_block_int_key(dev_keys.gen_app_key)?
}
MacVersion::LORAWAN_1_1_0 | MacVersion::Latest => {
fragmentation::v2::get_data_block_int_key(dev_keys.app_key)?
}
};
let mic = fragmentation::v2::calculate_mic(
data_block_int_key,
0,
0,
[0, 0, 0, 0],
&self.fuota_deployment.payload,
)?;
fragmentation::v2::Payload::FragSessionSetupReq(
fragmentation::v2::FragSessionSetupReqPayload {
frag_session: fragmentation::v2::FragSessionSetuReqPayloadFragSession {
mc_group_bit_mask: [true, false, false, false],
frag_index: 0,
},
nb_frag: fragments as u16,
frag_size: fragment_size as u8,
padding: padding as u8,
control: fragmentation::v2::FragSessionSetuReqPayloadControl {
block_ack_delay: 0,
frag_algo: 0,
ack_reception: false,
},
descriptor: [0, 0, 0, 0],
mic,
session_cnt: 0,
},
)
.to_vec()?
}
None => return Err(anyhow!("Device-profile does not support TS004")),
};
let _ = device_queue::enqueue_item(device_queue::DeviceQueueItem {
dev_eui: fuota_dev.dev_eui,
f_port: self.device_profile.app_layer_params.ts004_f_port.into(),
data: pl,
..Default::default()
})
.await?;
}
if !fuota_devices.is_empty() {
// There are devices pending setup, we need to re-run this job.
let scheduler_run_after =
Utc::now() + TimeDelta::seconds(self.device_profile.uplink_interval as i64);
Ok(Some((FuotaJob::FragSessionSetup, scheduler_run_after)))
} else {
// All devices have completed the setup, move on to next job.
Ok(Some((FuotaJob::McSession, Utc::now())))
}
}
async fn multicast_session_setup(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
let fuota_devices_completed_frag_session_setup_count = fuota_devices
.iter()
.filter(|d| d.frag_session_setup_completed_at.is_some())
.count();
// Filter on devices that have completed the previous step, but not yet the McSession.
let fuota_devices: Vec<fuota::FuotaDeploymentDevice> = fuota_devices
.into_iter()
.filter(|d| {
d.frag_session_setup_completed_at.is_some() && d.mc_session_completed_at.is_none()
})
.collect();
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
info!("Set timeout error to devices that did not respond to McSessionReq");
fuota::set_device_timeout_error(
self.fuota_deployment.id.into(),
false,
true,
false,
false,
)
.await?;
if !fuota_devices.is_empty() {
self.job.warning_msg = format!(
"{} devices did not complete the multicast session setup",
fuota_devices.len()
);
}
return Ok(Some((
FuotaJob::Enqueue,
self.fuota_deployment
.multicast_session_start
.unwrap_or_else(|| Utc::now()),
)));
}
info!("Sending McClassB/McClassCSessionReq commands to devices");
self.job.attempt_count += 1;
if fuota_devices_completed_frag_session_setup_count == 0 {
self.job.error_msg = "There are no devices available to complete this step".into();
return Ok(Some((FuotaJob::DeleteMcGroup, Utc::now())));
}
// Calculate the session start and end dates the first time this job is executed.
if self.fuota_deployment.multicast_session_start.is_none()
&& self.fuota_deployment.multicast_session_end.is_none()
{
// We want to start the session (retry_count + 1) x the uplink_interval.
// Note that retry_count=0 means only one attempt.
let session_start = Utc::now()
+ TimeDelta::seconds(
(self.job.max_retry_count as i64 + 1)
* self.device_profile.uplink_interval as i64,
);
let session_end = {
let timeout = match self.fuota_deployment.multicast_group_type.as_ref() {
"B" => Duration::from_secs(
128 * (1 << self.fuota_deployment.multicast_timeout as u64),
),
"C" => Duration::from_secs(1 << self.fuota_deployment.multicast_timeout as u64),
_ => return Err(anyhow!("Invalid multicast-group type")),
};
session_start + timeout
};
self.fuota_deployment.multicast_session_start = Some(session_start);
self.fuota_deployment.multicast_session_end = Some(session_end);
self.fuota_deployment = fuota::update_deployment(self.fuota_deployment.clone()).await?;
}
let session_start = self
.fuota_deployment
.multicast_session_start
.ok_or_else(|| anyhow!("multicast_session_start is None"))?
.to_gps_time()
.num_seconds()
% (1 << 32);
for fuota_dev in &fuota_devices {
let pl = match self.device_profile.app_layer_params.ts005_version {
Some(Ts005Version::V100) => {
match self.fuota_deployment.multicast_group_type.as_ref() {
"B" => multicastsetup::v1::Payload::McClassBSessionReq(
multicastsetup::v1::McClassBSessionReqPayload {
mc_group_id_header:
multicastsetup::v1::McClassBSessionReqPayloadMcGroupIdHeader {
mc_group_id: 0,
},
session_time: (session_start - (session_start % 128)) as u32,
time_out_periodicity:
multicastsetup::v1::McClassBSessionReqPayloadTimeOutPeriodicity {
time_out: self.fuota_deployment.multicast_timeout as u8,
periodicity: self.fuota_deployment.multicast_class_b_ping_slot_nb_k
as u8,
},
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
dr: self.fuota_deployment.multicast_dr as u8,
},
).to_vec()?,
"C" => multicastsetup::v1::Payload::McClassCSessionReq(
multicastsetup::v1::McClassCSessionReqPayload {
mc_group_id_header:
multicastsetup::v1::McClassCSessionReqPayloadMcGroupIdHeader {
mc_group_id: 0,
},
session_time: session_start as u32,
session_time_out:
multicastsetup::v1::McClassCSessionReqPayloadSessionTimeOut {
time_out: self.fuota_deployment.multicast_timeout as u8,
},
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
dr: self.fuota_deployment.multicast_dr as u8,
},
).to_vec()?,
_ => {
return Err(anyhow!(
"Unsupported group-type: {}",
self.fuota_deployment.multicast_group_type
))
}
}
}
Some(Ts005Version::V200) => {
match self.fuota_deployment.multicast_group_type.as_ref() {
"B" => multicastsetup::v2::Payload::McClassBSessionReq(
multicastsetup::v2::McClassBSessionReqPayload {
mc_group_id_header:
multicastsetup::v2::McClassBSessionReqPayloadMcGroupIdHeader {
mc_group_id: 0,
},
session_time: (session_start - (session_start % 128)) as u32,
time_out_periodicity:
multicastsetup::v2::McClassBSessionReqPayloadTimeOutPeriodicity {
time_out: self.fuota_deployment.multicast_timeout as u8,
periodicity: self.fuota_deployment.multicast_class_b_ping_slot_nb_k
as u8,
},
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
dr: self.fuota_deployment.multicast_dr as u8,
},
).to_vec()?,
"C" => multicastsetup::v2::Payload::McClassCSessionReq(
multicastsetup::v2::McClassCSessionReqPayload {
mc_group_id_header:
multicastsetup::v2::McClassCSessionReqPayloadMcGroupIdHeader {
mc_group_id: 0,
},
session_time: session_start as u32,
session_time_out:
multicastsetup::v2::McClassCSessionReqPayloadSessionTimeOut {
time_out: self.fuota_deployment.multicast_timeout as u8,
},
dl_frequ: self.fuota_deployment.multicast_frequency as u32,
dr: self.fuota_deployment.multicast_dr as u8,
},
).to_vec()?,
_ => {
return Err(anyhow!(
"Unsupported group-type: {}",
self.fuota_deployment.multicast_group_type
))
}
}
}
None => return Err(anyhow!("Device-profile does not support TS005")),
};
device_queue::enqueue_item(device_queue::DeviceQueueItem {
dev_eui: fuota_dev.dev_eui,
f_port: self.device_profile.app_layer_params.ts005_f_port.into(),
data: pl,
..Default::default()
})
.await?;
}
if !fuota_devices.is_empty() {
// There are devices pending setup, we need to re-run this job.
let scheduler_run_after =
Utc::now() + TimeDelta::seconds(self.device_profile.uplink_interval as i64);
Ok(Some((FuotaJob::McSession, scheduler_run_after)))
} else {
Ok(Some((
FuotaJob::Enqueue,
self.fuota_deployment
.multicast_session_start
.unwrap_or_else(|| Utc::now()),
)))
}
}
async fn enqueue(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
return Ok(Some((FuotaJob::FragStatus, Utc::now())));
}
info!("Enqueueing fragmented payload to multicast group");
self.job.attempt_count += 1;
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
// Filter on devices that have completed the previous step.
let fuota_devices: Vec<fuota::FuotaDeploymentDevice> = fuota_devices
.into_iter()
.filter(|d| d.mc_session_completed_at.is_some())
.collect();
if fuota_devices.is_empty() {
self.job.error_msg = "There are no devices available to complete this step".into();
return Ok(Some((FuotaJob::DeleteMcGroup, Utc::now())));
}
let payload_length = self.fuota_deployment.payload.len();
let fragment_size = self.fuota_deployment.fragmentation_fragment_size as usize;
let padding = (fragment_size - (payload_length % fragment_size)) % fragment_size;
let fragments = (payload_length as f32 / fragment_size as f32).ceil() as usize;
let redundancy = (fragments as f32
* self.fuota_deployment.fragmentation_redundancy_percentage as f32
/ 100.0)
.ceil() as usize;
let mut payload = self.fuota_deployment.payload.clone();
payload.extend_from_slice(&vec![0; padding]);
let payloads = match self.device_profile.app_layer_params.ts004_version {
Some(Ts004Version::V100) => {
let mut payloads = Vec::new();
let encoded_fragments =
fragmentation::v1::encode(&payload, fragment_size, redundancy)?;
for (i, frag) in encoded_fragments.iter().enumerate() {
payloads.push(
fragmentation::v1::Payload::DataFragment(
fragmentation::v1::DataFragmentPayload {
index_and_n: fragmentation::v1::DataFragmentPayloadIndexAndN {
frag_index: 0,
n: (i + 1) as u16,
},
data: frag.clone(),
},
)
.to_vec()?,
);
}
payloads
}
Some(Ts004Version::V200) => {
let mut payloads = Vec::new();
let encoded_fragments =
fragmentation::v2::encode(&payload, fragment_size, redundancy)?;
for (i, frag) in encoded_fragments.iter().enumerate() {
payloads.push(
fragmentation::v2::Payload::DataFragment(
fragmentation::v2::DataFragmentPayload {
index_and_n: fragmentation::v2::DataFragmentPayloadIndexAndN {
frag_index: 0,
n: (i + 1) as u16,
},
data: frag.clone(),
},
)
.to_vec()?,
);
}
payloads
}
None => return Err(anyhow!("Device-profile does not support TS004")),
};
for pl in payloads {
let _ = downlink::multicast::enqueue(multicast::MulticastGroupQueueItem {
multicast_group_id: self.fuota_deployment.id,
f_port: self.device_profile.app_layer_params.ts004_f_port as i16,
data: pl,
..Default::default()
})
.await?;
}
match self.fuota_deployment.request_fragmentation_session_status {
RequestFragmentationSessionStatus::NoRequest => Ok(Some((
FuotaJob::DeleteMcGroup,
self.fuota_deployment
.multicast_session_end
.unwrap_or_else(|| Utc::now()),
))),
RequestFragmentationSessionStatus::AfterFragEnqueue => {
Ok(Some((FuotaJob::FragStatus, Utc::now())))
}
RequestFragmentationSessionStatus::AfterSessTimeout => Ok(Some((
FuotaJob::FragStatus,
self.fuota_deployment
.multicast_session_end
.unwrap_or_else(|| Utc::now()),
))),
}
}
async fn fragmentation_status(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
let fuota_devices_completed_mc_session_count = fuota_devices
.iter()
.filter(|d| d.mc_session_completed_at.is_some())
.count();
// Filter on devices that have completed the multicast-session setup but
// not yet responded to the FragSessionStatusReq.
let fuota_devices: Vec<fuota::FuotaDeploymentDevice> = fuota_devices
.into_iter()
.filter(|d| d.mc_session_completed_at.is_some() && d.frag_status_completed_at.is_none())
.collect();
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
info!("Set timeout error to devices that did not respond to FragSessionStatusReq");
fuota::set_device_timeout_error(
self.fuota_deployment.id.into(),
false,
false,
false,
true,
)
.await?;
if !fuota_devices.is_empty() {
self.job.warning_msg = format!(
"{} devices did not complete the fragmentation status",
fuota_devices.len()
);
}
return Ok(Some((FuotaJob::DeleteMcGroup, Utc::now())));
}
info!("Enqueue FragSessionStatusReq");
self.job.attempt_count += 1;
if fuota_devices_completed_mc_session_count == 0 {
self.job.error_msg = "There are no devices available to complete this step".into();
return Ok(Some((FuotaJob::DeleteMcGroup, Utc::now())));
}
for fuota_dev in &fuota_devices {
let pl = match self.device_profile.app_layer_params.ts004_version {
Some(Ts004Version::V100) => fragmentation::v1::Payload::FragSessionStatusReq(
fragmentation::v1::FragSessionStatusReqPayload {
participants: true,
frag_index: 0,
},
)
.to_vec()?,
Some(Ts004Version::V200) => fragmentation::v2::Payload::FragSessionStatusReq(
fragmentation::v2::FragSessionStatusReqPayload {
participants: true,
frag_index: 0,
},
)
.to_vec()?,
None => return Err(anyhow!("Device-profile does not support TS004")),
};
device_queue::enqueue_item(device_queue::DeviceQueueItem {
dev_eui: fuota_dev.dev_eui,
f_port: self.device_profile.app_layer_params.ts004_f_port.into(),
data: pl,
..Default::default()
})
.await?;
}
if !fuota_devices.is_empty() {
// There are devices pending setup, we need to re-run this job.
let scheduler_run_after =
Utc::now() + TimeDelta::seconds(self.device_profile.uplink_interval as i64);
Ok(Some((FuotaJob::FragStatus, scheduler_run_after)))
} else {
Ok(Some((
FuotaJob::DeleteMcGroup,
self.fuota_deployment
.multicast_session_end
.unwrap_or_else(|| Utc::now()),
)))
}
}
async fn delete_mc_group(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
return Ok(Some((FuotaJob::Complete, Utc::now())));
}
info!("Delete multicast group");
self.job.attempt_count += 1;
multicast::delete(&self.fuota_deployment.id).await?;
Ok(Some((FuotaJob::Complete, Utc::now())))
}
async fn complete(&mut self) -> Result<Option<(FuotaJob, DateTime<Utc>)>> {
// Proceed with next step after reaching the max attempts.
if self.job.attempt_count > self.job.max_retry_count {
return Ok(None);
}
info!("Completing FUOTA deployment");
self.job.attempt_count += 1;
if self.fuota_deployment.request_fragmentation_session_status
== RequestFragmentationSessionStatus::NoRequest
{
fuota::set_device_completed(self.fuota_deployment.id.into(), true, true, true, false)
.await?;
} else {
fuota::set_device_completed(self.fuota_deployment.id.into(), true, true, true, true)
.await?;
}
let fuota_devices = fuota::get_devices(self.job.fuota_deployment_id.into(), -1, 0).await?;
let fuota_devices_count = fuota_devices.len();
let fuota_devices: Vec<fuota::FuotaDeploymentDevice> = fuota_devices
.into_iter()
.filter(|d| d.completed_at.is_some() && d.error_msg.is_empty())
.collect();
let fuota_devices_completed_count = fuota_devices.len();
for fuota_device in &fuota_devices {
let mut d = device::get(&fuota_device.dev_eui).await?;
for (k, v) in self.fuota_deployment.on_complete_set_device_tags.iter() {
d.tags.deref_mut().insert(k.to_string(), v.to_string());
}
let _ = device::update(d).await?;
}
if fuota_devices_count != fuota_devices_completed_count {
self.job.warning_msg = format!(
"{} devices did not complete the FUOTA deployment",
fuota_devices_count - fuota_devices_completed_count
);
}
self.fuota_deployment.completed_at = Some(Utc::now());
self.fuota_deployment = fuota::update_deployment(self.fuota_deployment.clone()).await?;
Ok(None)
}
}

View File

@ -0,0 +1,9 @@
use tracing::info;
pub mod flow;
pub mod scheduler;
pub async fn setup() {
info!("Setting up FUOTA scheduler loop");
tokio::spawn(scheduler::scheduler_loop());
}

View File

@ -0,0 +1,45 @@
use anyhow::Result;
use tokio::time::sleep;
use tracing::{error, span, trace, Instrument, Level};
use crate::applayer::fuota::flow;
use crate::config;
use crate::storage::fuota;
pub async fn scheduler_loop() {
let conf = config::get();
loop {
trace!("Starting fuota scheduler_loop run");
if let Err(err) = schedule_batch(conf.network.scheduler.batch_size).await {
error!(error = %err, "Scheduling FUOTA batch error");
} else {
trace!("schedule_batch completed without error");
}
sleep(conf.network.scheduler.interval).await;
}
}
async fn schedule_batch(size: usize) -> Result<()> {
trace!("Get schedulable fuota jobs");
let jobs = fuota::get_schedulable_jobs(size).await?;
trace!(job_count = jobs.len(), "Got this number of fuota jobs");
let mut handles = vec![];
for job in jobs {
// Spawn the batch as async tasks.
let handle = tokio::spawn(async move {
let span = span!(Level::INFO, "job", fuota_deployment_id = %job.fuota_deployment_id, job = %job.job);
if let Err(e) = flow::Flow::handle_job(job).instrument(span).await {
error!(error = %e, "Handle FUOTA job error");
}
});
handles.push(handle);
}
futures::future::join_all(handles).await;
Ok(())
}

View File

@ -0,0 +1,49 @@
use anyhow::Result;
use tracing::{span, warn, Instrument, Level};
use crate::storage::{device, device_profile};
use chirpstack_api::gw;
pub mod clocksync;
pub mod fragmentation;
pub mod fuota;
pub mod multicastsetup;
pub async fn handle_uplink(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
f_port: u8,
data: &[u8],
) {
if let Err(e) = _handle_uplink(dev, dp, rx_info, f_port, data).await {
warn!(error = %e, "Handle applayer payload error");
}
}
async fn _handle_uplink(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
rx_info: &[gw::UplinkRxInfo],
f_port: u8,
data: &[u8],
) -> Result<()> {
if dp.app_layer_params.ts003_f_port == f_port {
let span = span!(Level::INFO, "ts003");
clocksync::handle_uplink(dev, dp, rx_info, data)
.instrument(span)
.await
} else if dp.app_layer_params.ts004_f_port == f_port {
let span = span!(Level::INFO, "ts004");
fragmentation::handle_uplink(dev, dp, data)
.instrument(span)
.await
} else if dp.app_layer_params.ts005_f_port == f_port {
let span = span!(Level::INFO, "ts005");
multicastsetup::handle_uplink(dev, dp, data)
.instrument(span)
.await
} else {
return Err(anyhow!("Unexpected f_port {}", f_port));
}
}

View File

@ -0,0 +1,263 @@
use anyhow::Result;
use chrono::Utc;
use tracing::{info, warn};
use crate::storage::fields::device_profile::Ts005Version;
use crate::storage::{device, device_profile, fuota};
use lrwn::applayer::multicastsetup;
pub async fn handle_uplink(
dev: &device::Device,
dp: &device_profile::DeviceProfile,
data: &[u8],
) -> Result<()> {
let version = dp
.app_layer_params
.ts005_version
.ok_or_else(|| anyhow!("Device does not support TS005"))?;
match version {
Ts005Version::V100 => handle_uplink_v100(dev, data).await,
Ts005Version::V200 => handle_uplink_v200(dev, data).await,
}
}
async fn handle_uplink_v100(dev: &device::Device, data: &[u8]) -> Result<()> {
let pl = multicastsetup::v1::Payload::from_slice(true, data)?;
match pl {
multicastsetup::v1::Payload::McGroupSetupAns(pl) => {
handle_v1_mc_group_setup_ans(dev, pl).await?
}
multicastsetup::v1::Payload::McClassBSessionAns(pl) => {
handle_v1_mc_class_b_session_ans(dev, pl).await?
}
multicastsetup::v1::Payload::McClassCSessionAns(pl) => {
handle_v1_mc_class_c_session_ans(dev, pl).await?
}
_ => {}
}
Ok(())
}
async fn handle_uplink_v200(dev: &device::Device, data: &[u8]) -> Result<()> {
let pl = multicastsetup::v2::Payload::from_slice(true, data)?;
match pl {
multicastsetup::v2::Payload::McGroupSetupAns(pl) => {
handle_v2_mc_group_setup_ans(dev, pl).await?
}
multicastsetup::v2::Payload::McClassBSessionAns(pl) => {
handle_v2_mc_class_b_session_ans(dev, pl).await?
}
multicastsetup::v2::Payload::McClassCSessionAns(pl) => {
handle_v2_mc_class_c_session_ans(dev, pl).await?
}
_ => {}
}
Ok(())
}
async fn handle_v1_mc_group_setup_ans(
dev: &device::Device,
pl: multicastsetup::v1::McGroupSetupAnsPayload,
) -> Result<()> {
info!("Handling McGroupSetupAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.mc_group_id_header.id_error {
warn!(
mc_group_id = pl.mc_group_id_header.mc_group_id,
id_error = true,
"McGroupSetupAns contains errors"
);
fuota_dev.error_msg = "Error: McGroupSetupAns response id_error=true".into();
} else {
fuota_dev.mc_group_setup_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v2_mc_group_setup_ans(
dev: &device::Device,
pl: multicastsetup::v2::McGroupSetupAnsPayload,
) -> Result<()> {
info!("Handling McGroupSetupAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.mc_group_id_header.id_error {
warn!(
mc_group_id = pl.mc_group_id_header.mc_group_id,
id_error = true,
"McGroupSetupAns contains errors"
);
fuota_dev.error_msg = "Error: McGroupSetupAns response id_error=true".into();
} else {
fuota_dev.mc_group_setup_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v1_mc_class_b_session_ans(
dev: &device::Device,
pl: multicastsetup::v1::McClassBSessionAnsPayload,
) -> Result<()> {
info!("Handling McClassBSessionAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.status_and_mc_group_id.dr_error
| pl.status_and_mc_group_id.freq_error
| pl.status_and_mc_group_id.mc_group_undefined
{
warn!(
dr_error = pl.status_and_mc_group_id.dr_error,
freq_error = pl.status_and_mc_group_id.freq_error,
mc_group_undefined = pl.status_and_mc_group_id.mc_group_undefined,
"McClassBSessionAns contains errors"
);
fuota_dev.error_msg= format!("Error: McClassBSessionAns response dr_error: {}, freq_error: {}, mc_group_undefined: {}",
pl.status_and_mc_group_id.dr_error,
pl.status_and_mc_group_id.freq_error,
pl.status_and_mc_group_id.mc_group_undefined,
);
} else {
info!(
time_to_start = pl.time_to_start.unwrap_or_default(),
"McClassBSessionAns OK"
);
fuota_dev.mc_session_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v2_mc_class_b_session_ans(
dev: &device::Device,
pl: multicastsetup::v2::McClassBSessionAnsPayload,
) -> Result<()> {
info!("Handling McClassBSessionAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.status_and_mc_group_id.dr_error
| pl.status_and_mc_group_id.freq_error
| pl.status_and_mc_group_id.mc_group_undefined
| pl.status_and_mc_group_id.start_missed
{
warn!(
dr_error = pl.status_and_mc_group_id.dr_error,
freq_error = pl.status_and_mc_group_id.freq_error,
mc_group_undefined = pl.status_and_mc_group_id.mc_group_undefined,
start_missed = pl.status_and_mc_group_id.start_missed,
"McClassBSessionAns contains errors"
);
fuota_dev.error_msg= format!("Error: McClassBSessionAns response dr_error: {}, freq_error: {}, mc_group_undefined: {}, start_missed: {}",
pl.status_and_mc_group_id.dr_error,
pl.status_and_mc_group_id.freq_error,
pl.status_and_mc_group_id.mc_group_undefined,
pl.status_and_mc_group_id.start_missed,
);
} else {
info!(
time_to_start = pl.time_to_start.unwrap_or_default(),
"McClassBSessionAns OK"
);
fuota_dev.mc_session_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v1_mc_class_c_session_ans(
dev: &device::Device,
pl: multicastsetup::v1::McClassCSessionAnsPayload,
) -> Result<()> {
info!("Handling McClassCSessionAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.status_and_mc_group_id.dr_error
| pl.status_and_mc_group_id.freq_error
| pl.status_and_mc_group_id.mc_group_undefined
{
warn!(
dr_error = pl.status_and_mc_group_id.dr_error,
freq_error = pl.status_and_mc_group_id.freq_error,
mc_group_undefined = pl.status_and_mc_group_id.mc_group_undefined,
"McClassCSessionAns contains errors"
);
fuota_dev.error_msg = format!("Error: McClassCSessionAns response dr_error: {}, freq_error: {}, mc_group_undefined: {}",
pl.status_and_mc_group_id.dr_error,
pl.status_and_mc_group_id.freq_error,
pl.status_and_mc_group_id.mc_group_undefined,
);
} else {
info!(
time_to_start = pl.time_to_start.unwrap_or_default(),
"McClassCSessionAns OK"
);
fuota_dev.mc_session_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}
async fn handle_v2_mc_class_c_session_ans(
dev: &device::Device,
pl: multicastsetup::v2::McClassCSessionAnsPayload,
) -> Result<()> {
info!("Handling McClassCSessionAns");
let mut fuota_dev = fuota::get_latest_device_by_dev_eui(dev.dev_eui).await?;
if pl.status_and_mc_group_id.dr_error
| pl.status_and_mc_group_id.freq_error
| pl.status_and_mc_group_id.mc_group_undefined
| pl.status_and_mc_group_id.start_missed
{
warn!(
dr_error = pl.status_and_mc_group_id.dr_error,
freq_error = pl.status_and_mc_group_id.freq_error,
mc_group_undefined = pl.status_and_mc_group_id.mc_group_undefined,
start_missed = pl.status_and_mc_group_id.start_missed,
"McClassCSessionAns contains errors"
);
fuota_dev.error_msg = format!("Error: McClassCSessionAns response dr_error: {}, freq_error: {}, mc_group_undefined: {}, start_missed: {}",
pl.status_and_mc_group_id.dr_error,
pl.status_and_mc_group_id.freq_error,
pl.status_and_mc_group_id.mc_group_undefined,
pl.status_and_mc_group_id.start_missed,
);
} else {
info!(
time_to_start = pl.time_to_start.unwrap_or_default(),
"McClassCSessionAns OK"
);
fuota_dev.mc_session_completed_at = Some(Utc::now());
}
let _ = fuota::update_device(fuota_dev).await?;
Ok(())
}

View File

@ -3,9 +3,7 @@ use handlebars::Handlebars;
use super::super::config; use super::super::config;
pub fn run() { pub fn run() {
#[allow(clippy::useless_vec)] let template = r#"
let template = vec![
r#"
# Logging configuration # Logging configuration
[logging] [logging]
@ -21,10 +19,11 @@ r#"
# Log as JSON. # Log as JSON.
json={{ logging.json }} json={{ logging.json }}
"#,
#[cfg(feature = "postgres")]
r#"
# PostgreSQL configuration. # PostgreSQL configuration.
#
# Note: this option is only available to ChirpStack with PostgreSQL support (default).
[postgresql] [postgresql]
# PostgreSQL DSN. # PostgreSQL DSN.
@ -49,16 +48,20 @@ r#"
# the server-certificate is not signed by a CA in the platform certificate # the server-certificate is not signed by a CA in the platform certificate
# store. # store.
ca_cert="{{ postgresql.ca_cert }}" ca_cert="{{ postgresql.ca_cert }}"
"#,
#[cfg(feature = "sqlite")]
r#"
# SQLite configuration. # SQLite configuration.
#
# Note: this option is only available to ChirpStack with SQLite support.
[sqlite] [sqlite]
# Sqlite DB path. # Sqlite DB path.
# #
# Format example: sqlite:///<DATABASE>. # Make sure the path exists and that the ChirpStack process has read-write
# access to it. If the database file does not exists, it will be created the
# first time ChirpStack starts.
# #
# Format example: sqlite:///<DATABASE>.
path="{{ sqlite.path }}" path="{{ sqlite.path }}"
# Max open connections. # Max open connections.
@ -77,8 +80,8 @@ r#"
"{{this}}", "{{this}}",
{{/each}} {{/each}}
] ]
"#,
r#"
# Redis configuration. # Redis configuration.
[redis] [redis]
@ -990,7 +993,7 @@ r#"
# default tileserver_url (OSM). If you configure a different tile-server, you # default tileserver_url (OSM). If you configure a different tile-server, you
# might need to update the map_attribution. # might need to update the map_attribution.
map_attribution="{{ui.map_attribution}}" map_attribution="{{ui.map_attribution}}"
"#].join("\n"); "#;
let mut reg = Handlebars::new(); let mut reg = Handlebars::new();
reg.register_escape_fn(|s| s.to_string().replace('"', r#"\""#)); reg.register_escape_fn(|s| s.to_string().replace('"', r#"\""#));

View File

@ -5,7 +5,7 @@ use signal_hook_tokio::Signals;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::gateway; use crate::gateway;
use crate::{adr, api, backend, downlink, integration, region, storage}; use crate::{adr, api, applayer::fuota, backend, downlink, integration, region, storage};
pub async fn run() -> Result<()> { pub async fn run() -> Result<()> {
info!( info!(
@ -21,6 +21,7 @@ pub async fn run() -> Result<()> {
integration::setup().await?; integration::setup().await?;
gateway::backend::setup().await?; gateway::backend::setup().await?;
downlink::setup().await; downlink::setup().await;
fuota::setup().await;
api::setup().await?; api::setup().await?;
let mut signals = Signals::new([SIGINT, SIGTERM]).unwrap(); let mut signals = Signals::new([SIGINT, SIGTERM]).unwrap();

View File

@ -227,7 +227,7 @@ pub mod test {
let out = decode(Utc::now(), 10, &vars, &decoder, &[0x01, 0x02, 0x03]).await; let out = decode(Utc::now(), 10, &vars, &decoder, &[0x01, 0x02, 0x03]).await;
assert_eq!( assert_eq!(
"JS error: Error: foo is not defined\n at decodeUplink (eval_script:3:1)\n at <eval> (eval_script:8:9)\n", "JS error: Error: foo is not defined\n at decodeUplink (eval_script:3:1)\n at <eval> (eval_script:8:22)\n",
out.err().unwrap().to_string() out.err().unwrap().to_string()
); );
} }
@ -368,7 +368,7 @@ pub mod test {
}; };
let out = encode(10, &vars, &encoder, &input).await; let out = encode(10, &vars, &encoder, &input).await;
assert_eq!("JS error: Error: foo is not defined\n at encodeDownlink (eval_script:3:1)\n at <eval> (eval_script:8:9)\n", out.err().unwrap().to_string()); assert_eq!("JS error: Error: foo is not defined\n at encodeDownlink (eval_script:3:1)\n at <eval> (eval_script:8:24)\n", out.err().unwrap().to_string());
} }
#[tokio::test] #[tokio::test]

View File

@ -1,12 +1,12 @@
use rand::seq::SliceRandom;
use rand::RngCore; use rand::RngCore;
use crate::config; use crate::config;
use lrwn::DevAddr; use lrwn::DevAddr;
use rand::seq::IndexedRandom;
pub fn get_random_dev_addr() -> DevAddr { pub fn get_random_dev_addr() -> DevAddr {
let conf = config::get(); let conf = config::get();
let mut rng = rand::thread_rng(); let mut rng = rand::rng();
// Get configured DevAddr prefixes. // Get configured DevAddr prefixes.
let prefixes = if conf.network.dev_addr_prefixes.is_empty() { let prefixes = if conf.network.dev_addr_prefixes.is_empty() {

View File

@ -65,7 +65,7 @@ impl Data {
must_ack: bool, must_ack: bool,
mac_commands: Vec<lrwn::MACCommandSet>, mac_commands: Vec<lrwn::MACCommandSet>,
) -> Result<()> { ) -> Result<()> {
let downlink_id: u32 = rand::thread_rng().gen(); let downlink_id: u32 = rand::rng().random();
let span = span!(Level::INFO, "data_down", downlink_id = downlink_id); let span = span!(Level::INFO, "data_down", downlink_id = downlink_id);
match Data::_handle_response( match Data::_handle_response(
@ -107,7 +107,7 @@ impl Data {
must_ack: bool, must_ack: bool,
mac_commands: Vec<lrwn::MACCommandSet>, mac_commands: Vec<lrwn::MACCommandSet>,
) -> Result<()> { ) -> Result<()> {
let downlink_id: u32 = rand::thread_rng().gen(); let downlink_id: u32 = rand::rng().random();
let span = span!(Level::INFO, "data_down", downlink_id = downlink_id); let span = span!(Level::INFO, "data_down", downlink_id = downlink_id);
match Data::_handle_response_relayed( match Data::_handle_response_relayed(
@ -138,7 +138,7 @@ impl Data {
} }
pub async fn handle_schedule_next_queue_item(device: device::Device) -> Result<()> { pub async fn handle_schedule_next_queue_item(device: device::Device) -> Result<()> {
let downlink_id: u32 = rand::thread_rng().gen(); let downlink_id: u32 = rand::rng().random();
let span = let span =
span!(Level::INFO, "schedule", dev_eui = %device.dev_eui, downlink_id = downlink_id); span!(Level::INFO, "schedule", dev_eui = %device.dev_eui, downlink_id = downlink_id);
@ -643,7 +643,8 @@ impl Data {
self._set_rx_parameters().await?; self._set_rx_parameters().await?;
self._set_tx_parameters().await?; self._set_tx_parameters().await?;
if self.device_profile.is_relay { if let Some(relay_params) = self.device_profile.relay_params.clone() {
if relay_params.is_relay {
self._update_relay_conf().await?; self._update_relay_conf().await?;
self._update_filter_list().await?; self._update_filter_list().await?;
self._update_uplink_list().await?; self._update_uplink_list().await?;
@ -651,9 +652,10 @@ impl Data {
self._configure_fwd_limit_req().await?; self._configure_fwd_limit_req().await?;
} }
if self.device_profile.is_relay_ed { if relay_params.is_relay_ed {
self._update_end_device_conf().await?; self._update_end_device_conf().await?;
} }
}
let ds = self.device.get_device_session()?; let ds = self.device.get_device_session()?;
@ -1799,6 +1801,7 @@ impl Data {
let dev_eui = self.device.dev_eui; let dev_eui = self.device.dev_eui;
let ds = self.device.get_device_session_mut()?; let ds = self.device.get_device_session_mut()?;
let relay_params = self.device_profile.relay_params.clone().unwrap_or_default();
// Get the current relay state. // Get the current relay state.
let relay = if let Some(r) = &ds.relay { let relay = if let Some(r) = &ds.relay {
@ -1807,48 +1810,37 @@ impl Data {
internal::Relay::default() internal::Relay::default()
}; };
if relay.join_req_limit_reload_rate if relay.join_req_limit_reload_rate != relay_params.relay_join_req_limit_reload_rate as u32
!= self.device_profile.relay_join_req_limit_reload_rate as u32 || relay.notify_limit_reload_rate != relay_params.relay_notify_limit_reload_rate as u32
|| relay.notify_limit_reload_rate
!= self.device_profile.relay_notify_limit_reload_rate as u32
|| relay.global_uplink_limit_reload_rate || relay.global_uplink_limit_reload_rate
!= self.device_profile.relay_global_uplink_limit_reload_rate as u32 != relay_params.relay_global_uplink_limit_reload_rate as u32
|| relay.overall_limit_reload_rate || relay.overall_limit_reload_rate
!= self.device_profile.relay_overall_limit_reload_rate as u32 != relay_params.relay_overall_limit_reload_rate as u32
|| relay.join_req_limit_bucket_size || relay.join_req_limit_bucket_size
!= self.device_profile.relay_join_req_limit_bucket_size as u32 != relay_params.relay_join_req_limit_bucket_size as u32
|| relay.notify_limit_bucket_size || relay.notify_limit_bucket_size != relay_params.relay_notify_limit_bucket_size as u32
!= self.device_profile.relay_notify_limit_bucket_size as u32
|| relay.global_uplink_limit_bucket_size || relay.global_uplink_limit_bucket_size
!= self.device_profile.relay_global_uplink_limit_bucket_size as u32 != relay_params.relay_global_uplink_limit_bucket_size as u32
|| relay.overall_limit_bucket_size || relay.overall_limit_bucket_size
!= self.device_profile.relay_overall_limit_bucket_size as u32 != relay_params.relay_overall_limit_bucket_size as u32
{ {
let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::ConfigureFwdLimitReq( let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::ConfigureFwdLimitReq(
lrwn::ConfigureFwdLimitReqPayload { lrwn::ConfigureFwdLimitReqPayload {
reload_rate: lrwn::FwdLimitReloadRatePL { reload_rate: lrwn::FwdLimitReloadRatePL {
overall_reload_rate: self.device_profile.relay_overall_limit_reload_rate overall_reload_rate: relay_params.relay_overall_limit_reload_rate as u8,
as u8, global_uplink_reload_rate: relay_params
global_uplink_reload_rate: self
.device_profile
.relay_global_uplink_limit_reload_rate .relay_global_uplink_limit_reload_rate
as u8, as u8,
notify_reload_rate: self.device_profile.relay_notify_limit_reload_rate notify_reload_rate: relay_params.relay_notify_limit_reload_rate as u8,
as u8, join_req_reload_rate: relay_params.relay_join_req_limit_reload_rate as u8,
join_req_reload_rate: self.device_profile.relay_join_req_limit_reload_rate
as u8,
reset_limit_counter: lrwn::ResetLimitCounter::NoChange, reset_limit_counter: lrwn::ResetLimitCounter::NoChange,
}, },
load_capacity: lrwn::FwdLimitLoadCapacityPL { load_capacity: lrwn::FwdLimitLoadCapacityPL {
overall_limit_size: self.device_profile.relay_overall_limit_bucket_size overall_limit_size: relay_params.relay_overall_limit_bucket_size as u8,
as u8, global_uplink_limit_size: relay_params.relay_global_uplink_limit_bucket_size
global_uplink_limit_size: self
.device_profile
.relay_global_uplink_limit_bucket_size
as u8,
notify_limit_size: self.device_profile.relay_notify_limit_bucket_size as u8,
join_req_limit_size: self.device_profile.relay_join_req_limit_bucket_size
as u8, as u8,
notify_limit_size: relay_params.relay_notify_limit_bucket_size as u8,
join_req_limit_size: relay_params.relay_join_req_limit_bucket_size as u8,
}, },
}, },
)]); )]);
@ -2048,6 +2040,7 @@ impl Data {
let dev_eui = self.device.dev_eui; let dev_eui = self.device.dev_eui;
let ds = self.device.get_device_session_mut()?; let ds = self.device.get_device_session_mut()?;
let relay_params = self.device_profile.relay_params.clone().unwrap_or_default();
// Get the current relay state. // Get the current relay state.
let relay = if let Some(r) = &ds.relay { let relay = if let Some(r) = &ds.relay {
@ -2056,33 +2049,31 @@ impl Data {
internal::Relay::default() internal::Relay::default()
}; };
if relay.enabled != self.device_profile.relay_enabled if relay.enabled != relay_params.relay_enabled
|| relay.cad_periodicity != self.device_profile.relay_cad_periodicity as u32 || relay.cad_periodicity != relay_params.relay_cad_periodicity as u32
|| relay.default_channel_index != self.device_profile.relay_default_channel_index as u32 || relay.default_channel_index != relay_params.default_channel_index as u32
|| relay.second_channel_freq != self.device_profile.relay_second_channel_freq as u32 || relay.second_channel_freq != relay_params.second_channel_freq as u32
|| relay.second_channel_dr != self.device_profile.relay_second_channel_dr as u32 || relay.second_channel_dr != relay_params.second_channel_dr as u32
|| relay.second_channel_ack_offset || relay.second_channel_ack_offset != relay_params.second_channel_ack_offset as u32
!= self.device_profile.relay_second_channel_ack_offset as u32
{ {
let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::RelayConfReq( let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::RelayConfReq(
lrwn::RelayConfReqPayload { lrwn::RelayConfReqPayload {
channel_settings_relay: lrwn::ChannelSettingsRelay { channel_settings_relay: lrwn::ChannelSettingsRelay {
start_stop: match self.device_profile.relay_enabled { start_stop: match relay_params.relay_enabled {
true => 1, true => 1,
false => 0, false => 0,
}, },
cad_periodicity: self.device_profile.relay_cad_periodicity as u8, cad_periodicity: relay_params.relay_cad_periodicity as u8,
default_ch_idx: self.device_profile.relay_default_channel_index as u8, default_ch_idx: relay_params.default_channel_index as u8,
second_ch_idx: if self.device_profile.relay_second_channel_freq > 0 { second_ch_idx: if relay_params.second_channel_freq > 0 {
1 1
} else { } else {
0 0
}, },
second_ch_dr: self.device_profile.relay_second_channel_dr as u8, second_ch_dr: relay_params.second_channel_dr as u8,
second_ch_ack_offset: self.device_profile.relay_second_channel_ack_offset second_ch_ack_offset: relay_params.second_channel_ack_offset as u8,
as u8,
}, },
second_ch_freq: self.device_profile.relay_second_channel_freq as u32, second_ch_freq: relay_params.second_channel_freq as u32,
}, },
)]); )]);
mac_command::set_pending(&dev_eui, lrwn::CID::RelayConfReq, &set).await?; mac_command::set_pending(&dev_eui, lrwn::CID::RelayConfReq, &set).await?;
@ -2099,6 +2090,7 @@ impl Data {
let dev_eui = self.device.dev_eui; let dev_eui = self.device.dev_eui;
let ds = self.device.get_device_session_mut()?; let ds = self.device.get_device_session_mut()?;
let relay_params = self.device_profile.relay_params.clone().unwrap_or_default();
// Get the current relay state. // Get the current relay state.
let relay = if let Some(r) = &ds.relay { let relay = if let Some(r) = &ds.relay {
@ -2107,32 +2099,30 @@ impl Data {
internal::Relay::default() internal::Relay::default()
}; };
if relay.ed_activation_mode != self.device_profile.relay_ed_activation_mode.to_u8() as u32 if relay.ed_activation_mode != relay_params.ed_activation_mode.to_u8() as u32
|| relay.ed_smart_enable_level != self.device_profile.relay_ed_smart_enable_level as u32 || relay.ed_smart_enable_level != relay_params.ed_smart_enable_level as u32
|| relay.ed_back_off != self.device_profile.relay_ed_back_off as u32 || relay.ed_back_off != relay_params.ed_back_off as u32
|| relay.second_channel_freq != self.device_profile.relay_second_channel_freq as u32 || relay.second_channel_freq != relay_params.second_channel_freq as u32
|| relay.second_channel_dr != self.device_profile.relay_second_channel_dr as u32 || relay.second_channel_dr != relay_params.second_channel_dr as u32
|| relay.second_channel_ack_offset || relay.second_channel_ack_offset != relay_params.second_channel_ack_offset as u32
!= self.device_profile.relay_second_channel_ack_offset as u32
{ {
let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::EndDeviceConfReq( let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::EndDeviceConfReq(
lrwn::EndDeviceConfReqPayload { lrwn::EndDeviceConfReqPayload {
activation_relay_mode: lrwn::ActivationRelayMode { activation_relay_mode: lrwn::ActivationRelayMode {
relay_mode_activation: self.device_profile.relay_ed_activation_mode, relay_mode_activation: relay_params.ed_activation_mode,
smart_enable_level: self.device_profile.relay_ed_smart_enable_level as u8, smart_enable_level: relay_params.ed_smart_enable_level as u8,
}, },
channel_settings_ed: lrwn::ChannelSettingsED { channel_settings_ed: lrwn::ChannelSettingsED {
second_ch_ack_offset: self.device_profile.relay_second_channel_ack_offset second_ch_ack_offset: relay_params.second_channel_ack_offset as u8,
as u8, second_ch_dr: relay_params.second_channel_dr as u8,
second_ch_dr: self.device_profile.relay_second_channel_dr as u8, second_ch_idx: if relay_params.second_channel_freq > 0 {
second_ch_idx: if self.device_profile.relay_second_channel_freq > 0 {
1 1
} else { } else {
0 0
}, },
backoff: self.device_profile.relay_ed_back_off as u8, backoff: relay_params.ed_back_off as u8,
}, },
second_ch_freq: self.device_profile.relay_second_channel_freq as u32, second_ch_freq: relay_params.second_channel_freq as u32,
}, },
)]); )]);
mac_command::set_pending(&dev_eui, lrwn::CID::EndDeviceConfReq, &set).await?; mac_command::set_pending(&dev_eui, lrwn::CID::EndDeviceConfReq, &set).await?;
@ -2696,6 +2686,7 @@ fn filter_mac_commands(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::storage::fields;
use crate::test; use crate::test;
use lrwn::{DevAddr, EUI64}; use lrwn::{DevAddr, EUI64};
use tokio::time::sleep; use tokio::time::sleep;
@ -2715,8 +2706,11 @@ mod test {
let dp = device_profile::create(device_profile::DeviceProfile { let dp = device_profile::create(device_profile::DeviceProfile {
name: "dp".into(), name: "dp".into(),
tenant_id: t.id, tenant_id: t.id,
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
..Default::default() ..Default::default()
}),
..Default::default()
}) })
.await .await
.unwrap(); .unwrap();
@ -3440,8 +3434,11 @@ mod test {
let dp_relay = device_profile::create(device_profile::DeviceProfile { let dp_relay = device_profile::create(device_profile::DeviceProfile {
name: "dp-relay".into(), name: "dp-relay".into(),
tenant_id: t.id, tenant_id: t.id,
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
..Default::default() ..Default::default()
}),
..Default::default()
}) })
.await .await
.unwrap(); .unwrap();
@ -3449,9 +3446,12 @@ mod test {
let dp_ed = device_profile::create(device_profile::DeviceProfile { let dp_ed = device_profile::create(device_profile::DeviceProfile {
name: "dp-ed".into(), name: "dp-ed".into(),
tenant_id: t.id, tenant_id: t.id,
relay_params: Some(fields::RelayParams {
is_relay_ed: true, is_relay_ed: true,
relay_ed_uplink_limit_bucket_size: 2, ed_uplink_limit_bucket_size: 2,
relay_ed_uplink_limit_reload_rate: 1, ed_uplink_limit_reload_rate: 1,
..Default::default()
}),
..Default::default() ..Default::default()
}) })
.await .await
@ -3900,8 +3900,11 @@ mod test {
let dp_relay = device_profile::create(device_profile::DeviceProfile { let dp_relay = device_profile::create(device_profile::DeviceProfile {
name: "dp-relay".into(), name: "dp-relay".into(),
tenant_id: t.id, tenant_id: t.id,
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
..Default::default() ..Default::default()
}),
..Default::default()
}) })
.await .await
.unwrap(); .unwrap();
@ -4023,13 +4026,16 @@ mod test {
..Default::default() ..Default::default()
}, },
device_profile: device_profile::DeviceProfile { device_profile: device_profile::DeviceProfile {
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
relay_enabled: true, relay_enabled: true,
relay_cad_periodicity: 1, relay_cad_periodicity: 1,
relay_default_channel_index: 0, default_channel_index: 0,
relay_second_channel_freq: 868300000, second_channel_freq: 868300000,
relay_second_channel_dr: 3, second_channel_dr: 3,
relay_second_channel_ack_offset: 2, second_channel_ack_offset: 2,
..Default::default()
}),
..Default::default() ..Default::default()
}, },
expected_mac_commands: vec![], expected_mac_commands: vec![],
@ -4049,13 +4055,16 @@ mod test {
..Default::default() ..Default::default()
}, },
device_profile: device_profile::DeviceProfile { device_profile: device_profile::DeviceProfile {
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
relay_enabled: true, relay_enabled: true,
relay_cad_periodicity: 1, relay_cad_periodicity: 1,
relay_default_channel_index: 0, default_channel_index: 0,
relay_second_channel_freq: 868500000, second_channel_freq: 868500000,
relay_second_channel_dr: 3, second_channel_dr: 3,
relay_second_channel_ack_offset: 2, second_channel_ack_offset: 2,
..Default::default()
}),
..Default::default() ..Default::default()
}, },
expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![ expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![
@ -4134,12 +4143,15 @@ mod test {
..Default::default() ..Default::default()
}, },
device_profile: device_profile::DeviceProfile { device_profile: device_profile::DeviceProfile {
relay_ed_activation_mode: lrwn::RelayModeActivation::EnableRelayMode, relay_params: Some(fields::RelayParams {
relay_ed_smart_enable_level: 1, ed_activation_mode: lrwn::RelayModeActivation::EnableRelayMode,
relay_ed_back_off: 16, ed_smart_enable_level: 1,
relay_second_channel_freq: 868100000, ed_back_off: 16,
relay_second_channel_dr: 3, second_channel_freq: 868100000,
relay_second_channel_ack_offset: 4, second_channel_dr: 3,
second_channel_ack_offset: 4,
..Default::default()
}),
..Default::default() ..Default::default()
}, },
expected_mac_commands: vec![], expected_mac_commands: vec![],
@ -4159,12 +4171,15 @@ mod test {
..Default::default() ..Default::default()
}, },
device_profile: device_profile::DeviceProfile { device_profile: device_profile::DeviceProfile {
relay_ed_activation_mode: lrwn::RelayModeActivation::EnableRelayMode, relay_params: Some(fields::RelayParams {
relay_ed_smart_enable_level: 1, ed_activation_mode: lrwn::RelayModeActivation::EnableRelayMode,
relay_ed_back_off: 16, ed_smart_enable_level: 1,
relay_second_channel_freq: 868100000, ed_back_off: 16,
relay_second_channel_dr: 3, second_channel_freq: 868100000,
relay_second_channel_ack_offset: 4, second_channel_dr: 3,
second_channel_ack_offset: 4,
..Default::default()
}),
..Default::default() ..Default::default()
}, },
expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![ expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![
@ -4247,6 +4262,7 @@ mod test {
..Default::default() ..Default::default()
}, },
device_profile: device_profile::DeviceProfile { device_profile: device_profile::DeviceProfile {
relay_params: Some(fields::RelayParams {
relay_join_req_limit_reload_rate: 10, relay_join_req_limit_reload_rate: 10,
relay_join_req_limit_bucket_size: 0, relay_join_req_limit_bucket_size: 0,
relay_notify_limit_reload_rate: 15, relay_notify_limit_reload_rate: 15,
@ -4256,6 +4272,8 @@ mod test {
relay_overall_limit_reload_rate: 25, relay_overall_limit_reload_rate: 25,
relay_overall_limit_bucket_size: 3, relay_overall_limit_bucket_size: 3,
..Default::default() ..Default::default()
}),
..Default::default()
}, },
expected_mac_commands: vec![], expected_mac_commands: vec![],
}, },
@ -4276,6 +4294,7 @@ mod test {
..Default::default() ..Default::default()
}, },
device_profile: device_profile::DeviceProfile { device_profile: device_profile::DeviceProfile {
relay_params: Some(fields::RelayParams {
relay_join_req_limit_reload_rate: 10, relay_join_req_limit_reload_rate: 10,
relay_join_req_limit_bucket_size: 0, relay_join_req_limit_bucket_size: 0,
relay_notify_limit_reload_rate: 15, relay_notify_limit_reload_rate: 15,
@ -4285,6 +4304,8 @@ mod test {
relay_overall_limit_reload_rate: 25, relay_overall_limit_reload_rate: 25,
relay_overall_limit_bucket_size: 3, relay_overall_limit_bucket_size: 3,
..Default::default() ..Default::default()
}),
..Default::default()
}, },
expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![ expected_mac_commands: vec![lrwn::MACCommandSet::new(vec![
lrwn::MACCommand::ConfigureFwdLimitReq(lrwn::ConfigureFwdLimitReqPayload { lrwn::MACCommand::ConfigureFwdLimitReq(lrwn::ConfigureFwdLimitReqPayload {
@ -4521,8 +4542,11 @@ mod test {
let dp_relay = device_profile::create(device_profile::DeviceProfile { let dp_relay = device_profile::create(device_profile::DeviceProfile {
name: "dp-relay".into(), name: "dp-relay".into(),
tenant_id: t.id, tenant_id: t.id,
relay_params: Some(fields::RelayParams {
is_relay: true, is_relay: true,
..Default::default() ..Default::default()
}),
..Default::default()
}) })
.await .await
.unwrap(); .unwrap();

View File

@ -54,7 +54,7 @@ impl Data {
xmit_data_req: pl, xmit_data_req: pl,
dl_meta_data: dl_meta, dl_meta_data: dl_meta,
downlink_frame: gw::DownlinkFrame { downlink_frame: gw::DownlinkFrame {
downlink_id: rand::thread_rng().gen(), downlink_id: rand::rng().random(),
..Default::default() ..Default::default()
}, },
}; };

View File

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
use rand::seq::SliceRandom; use rand::seq::IndexedRandom;
use uuid::Uuid; use uuid::Uuid;
use chirpstack_api::{gw, internal}; use chirpstack_api::{gw, internal};
@ -74,7 +74,7 @@ pub fn select_downlink_gateway(
// Return a random item from the new_items slice (filtered by min_snr_margin). // Return a random item from the new_items slice (filtered by min_snr_margin).
// If new_items is empty, then choose will return None and we return the first item from // If new_items is empty, then choose will return None and we return the first item from
// rx_info.item. // rx_info.item.
Ok(match new_items.choose(&mut rand::thread_rng()) { Ok(match new_items.choose(&mut rand::rng()) {
Some(v) => v.clone(), Some(v) => v.clone(),
None => rx_info.items[0].clone(), None => rx_info.items[0].clone(),
}) })

View File

@ -37,7 +37,7 @@ impl JoinAccept<'_> {
device: &device::Device, device: &device::Device,
join_accept: &PhyPayload, join_accept: &PhyPayload,
) -> Result<()> { ) -> Result<()> {
let downlink_id: u32 = rand::thread_rng().gen(); let downlink_id: u32 = rand::rng().random();
let span = span!(Level::INFO, "join_accept", downlink_id = downlink_id); let span = span!(Level::INFO, "join_accept", downlink_id = downlink_id);
let fut = JoinAccept::_handle(downlink_id, ufs, tenant, device, join_accept); let fut = JoinAccept::_handle(downlink_id, ufs, tenant, device, join_accept);
@ -51,7 +51,7 @@ impl JoinAccept<'_> {
device: &device::Device, device: &device::Device,
join_accept: &PhyPayload, join_accept: &PhyPayload,
) -> Result<()> { ) -> Result<()> {
let downlink_id: u32 = rand::thread_rng().gen(); let downlink_id: u32 = rand::rng().random();
let span = span!( let span = span!(
Level::INFO, Level::INFO,
"join_accept_relayed", "join_accept_relayed",

View File

@ -53,7 +53,7 @@ impl Multicast {
let mut ctx = Multicast { let mut ctx = Multicast {
downlink_frame: gw::DownlinkFrame { downlink_frame: gw::DownlinkFrame {
downlink_id: rand::thread_rng().gen(), downlink_id: rand::rng().random(),
gateway_id: qi.gateway_id.to_string(), gateway_id: qi.gateway_id.to_string(),
..Default::default() ..Default::default()
}, },

View File

@ -43,7 +43,7 @@ impl PassiveRoamingDownlink {
network_conf, network_conf,
region_conf, region_conf,
downlink_frame: gw::DownlinkFrame { downlink_frame: gw::DownlinkFrame {
downlink_id: rand::thread_rng().gen(), downlink_id: rand::rng().random(),
..Default::default() ..Default::default()
}, },
downlink_gateway: None, downlink_gateway: None,

View File

@ -282,8 +282,12 @@ impl TxAck {
qi.is_pending = true; qi.is_pending = true;
if dev.enabled_class == DeviceClass::C { if dev.enabled_class == DeviceClass::C {
let timeout = let timeout_sec = dp
Utc::now() + Duration::try_seconds(dp.class_c_timeout as i64).unwrap_or_default(); .class_c_params
.as_ref()
.map(|v| v.timeout)
.unwrap_or_default() as i64;
let timeout = Utc::now() + Duration::try_seconds(timeout_sec).unwrap_or_default();
qi.timeout_after = Some(timeout); qi.timeout_after = Some(timeout);
} }

View File

@ -5,6 +5,7 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use chrono::Utc;
use handlebars::Handlebars; use handlebars::Handlebars;
use prometheus_client::encoding::EncodeLabelSet; use prometheus_client::encoding::EncodeLabelSet;
use prometheus_client::metrics::counter::Counter; use prometheus_client::metrics::counter::Counter;
@ -98,8 +99,8 @@ impl<'a> MqttBackend<'a> {
// get client id, this will generate a random client_id when no client_id has been // get client id, this will generate a random client_id when no client_id has been
// configured. // configured.
let client_id = if conf.client_id.is_empty() { let client_id = if conf.client_id.is_empty() {
let mut rnd = rand::thread_rng(); let mut rnd = rand::rng();
let client_id: u64 = rnd.gen(); let client_id: u64 = rnd.random();
format!("{:x}", client_id) format!("{:x}", client_id)
} else { } else {
conf.client_id.clone() conf.client_id.clone()
@ -359,6 +360,11 @@ async fn message_callback(
event.v4_migrate(); event.v4_migrate();
} }
if let Some(rx_info) = &mut event.rx_info {
set_gateway_json(&rx_info.gateway_id, json);
rx_info.ns_time = Some(Utc::now().into());
}
tokio::spawn(uplink::deduplicate_uplink( tokio::spawn(uplink::deduplicate_uplink(
region_common_name, region_common_name,
region_config_id.to_string(), region_config_id.to_string(),

View File

@ -68,17 +68,34 @@ pub fn private_key_to_pkcs8(pem: &str) -> Result<String> {
let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?; let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?;
Ok(pkcs8_pem.as_str().to_owned()) Ok(pkcs8_pem.as_str().to_owned())
} else if pem.contains("EC PRIVATE KEY") { } else if pem.contains("EC PRIVATE KEY") {
use elliptic_curve::{ use sec1::{
pkcs8::{EncodePrivateKey, LineEnding}, der::{Decode, Encode, EncodePem},
SecretKey, pkcs8::{AlgorithmIdentifierRef, PrivateKeyInfo},
EcPrivateKey, LineEnding,
}; };
// We assume it is a P256 based secret-key, which is the most popular curve. // Get a SEC1 ECPrivateKey from the PEM string input
// Attempting to decode it as P256 is still better than just failing to read it. let pem = pem::parse(pem).context("Parse PEM string")?;
let pkey: SecretKey<p256::NistP256> = let pkey =
SecretKey::from_sec1_pem(pem).context("Read EC SEC1")?; EcPrivateKey::from_der(pem.contents()).context("Decode PEM into SEC1 ECPrivateKey")?;
let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?;
Ok(pkcs8_pem.as_str().to_owned()) // Retrieve the curve name from the decoded private key's parameters
let params_oid = pkey.parameters.and_then(|params| params.named_curve());
// Get the proper types to construct a PKCS#8 PrivateKeyInfo
let private_key = &pkey.to_der()?;
let algorithm = AlgorithmIdentifierRef {
oid: sec1::ALGORITHM_OID,
parameters: params_oid.as_ref().map(Into::into),
};
let pkcs8 = PrivateKeyInfo {
algorithm,
private_key,
public_key: None,
};
Ok(pkcs8.to_pem(LineEnding::default())?)
} else { } else {
Ok(pem.to_string()) Ok(pem.to_string())
} }

View File

@ -71,17 +71,34 @@ pub fn private_key_to_pkcs8(pem: &str) -> Result<String> {
let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?; let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?;
Ok(pkcs8_pem.as_str().to_owned()) Ok(pkcs8_pem.as_str().to_owned())
} else if pem.contains("EC PRIVATE KEY") { } else if pem.contains("EC PRIVATE KEY") {
use elliptic_curve::{ use sec1::{
pkcs8::{EncodePrivateKey, LineEnding}, der::{Decode, Encode, EncodePem},
SecretKey, pkcs8::{AlgorithmIdentifierRef, PrivateKeyInfo},
EcPrivateKey, LineEnding,
}; };
// We assume it is a P256 based secret-key, which is the most popular curve. // Get a SEC1 ECPrivateKey from the PEM string input
// Attempting to decode it as P256 is still better than just failing to read it. let pem = pem::parse(pem).context("Parse PEM string")?;
let pkey: SecretKey<p256::NistP256> = let pkey =
SecretKey::from_sec1_pem(pem).context("Read EC SEC1")?; EcPrivateKey::from_der(pem.contents()).context("Decode PEM into SEC1 ECPrivateKey")?;
let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?;
Ok(pkcs8_pem.as_str().to_owned()) // Retrieve the curve name from the decoded private key's parameters
let params_oid = pkey.parameters.and_then(|params| params.named_curve());
// Get the proper types to construct a PKCS#8 PrivateKeyInfo
let private_key = &pkey.to_der()?;
let algorithm = AlgorithmIdentifierRef {
oid: sec1::ALGORITHM_OID,
parameters: params_oid.as_ref().map(Into::into),
};
let pkcs8 = PrivateKeyInfo {
algorithm,
private_key,
public_key: None,
};
Ok(pkcs8.to_pem(LineEnding::default())?)
} else { } else {
Ok(pem.to_string()) Ok(pem.to_string())
} }

View File

@ -66,8 +66,8 @@ impl<'a> Integration<'a> {
// get client id, this will generate a random client_id when no client_id has been // get client id, this will generate a random client_id when no client_id has been
// configured. // configured.
let client_id = if conf.client_id.is_empty() { let client_id = if conf.client_id.is_empty() {
let mut rnd = rand::thread_rng(); let mut rnd = rand::rng();
let client_id: u64 = rnd.gen(); let client_id: u64 = rnd.random();
format!("{:x}", client_id) format!("{:x}", client_id)
} else { } else {
conf.client_id.clone() conf.client_id.clone()

View File

@ -42,6 +42,7 @@ pub fn handle(
#[cfg(test)] #[cfg(test)]
pub mod test { pub mod test {
use super::*; use super::*;
use crate::storage::fields;
use chirpstack_api::internal; use chirpstack_api::internal;
use std::collections::HashMap; use std::collections::HashMap;
@ -70,13 +71,18 @@ pub mod test {
}; };
let dp = device_profile::DeviceProfile { let dp = device_profile::DeviceProfile {
supports_otaa: false, supports_otaa: false,
abp_rx1_delay: 1, abp_params: Some(fields::AbpParams {
abp_rx1_dr_offset: 0, rx1_delay: 1,
abp_rx2_dr: 0, rx1_dr_offset: 0,
abp_rx2_freq: 868300000, rx2_dr: 0,
class_b_ping_slot_dr: 2, rx2_freq: 868300000,
class_b_ping_slot_freq: 868100000, }),
class_b_ping_slot_nb_k: 1, class_b_params: Some(fields::ClassBParams {
ping_slot_dr: 2,
ping_slot_freq: 868100000,
ping_slot_nb_k: 1,
timeout: 0,
}),
..Default::default() ..Default::default()
}; };

View File

@ -1,4 +1,3 @@
// Required by rust::table macro.
#![recursion_limit = "256"] #![recursion_limit = "256"]
#[macro_use] #[macro_use]
@ -20,7 +19,9 @@ use tracing_subscriber::{filter, prelude::*};
use lrwn::EUI64; use lrwn::EUI64;
mod adr; mod adr;
mod aeskey;
mod api; mod api;
mod applayer;
mod backend; mod backend;
mod certificate; mod certificate;
mod cmd; mod cmd;

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::{BTreeMap, HashMap};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
@ -15,7 +15,7 @@ use tracing::info;
use uuid::Uuid; use uuid::Uuid;
use super::error::Error; use super::error::Error;
use super::schema::{application, application_integration}; use super::schema::{application, application_integration, device, device_profile};
use super::{fields, get_async_db_conn}; use super::{fields, get_async_db_conn};
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)] #[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
@ -594,6 +594,95 @@ pub async fn get_measurement_keys(application_id: &Uuid) -> Result<Vec<String>,
Ok(keys.iter().map(|k| k.key.clone()).collect()) Ok(keys.iter().map(|k| k.key.clone()).collect())
} }
pub async fn get_device_profiles(
application_id: Uuid,
) -> Result<Vec<(fields::Uuid, String)>, Error> {
let result: Vec<(fields::Uuid, String)> = device_profile::dsl::device_profile
.select((device_profile::dsl::id, device_profile::dsl::name))
.distinct()
.inner_join(device::table.on(device::dsl::device_profile_id.eq(device_profile::dsl::id)))
.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id)))
.order_by(device_profile::dsl::name)
.load(&mut get_async_db_conn().await?)
.await?;
Ok(result)
}
#[derive(QueryableByName)]
struct DeviceTags {
#[diesel(sql_type = diesel::sql_types::Text)]
key: String,
#[diesel(sql_type = diesel::sql_types::Text)]
value: String,
}
#[cfg(feature = "postgres")]
pub async fn get_device_tags(application_id: Uuid) -> Result<BTreeMap<String, Vec<String>>, Error> {
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
let items: Vec<DeviceTags> = diesel::sql_query(
r#"
select
distinct
t.key,
t.value
from device d
join lateral jsonb_each_text(d.tags) t
on true
where
d.application_id = $1
order by
t.key,
t.value
"#,
)
.bind::<fields::sql_types::Uuid, _>(fields::Uuid::from(application_id))
.load(&mut get_async_db_conn().await?)
.await
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
for item in &items {
let entry = out.entry(item.key.clone()).or_default();
entry.push(item.value.clone());
}
Ok(out)
}
#[cfg(feature = "sqlite")]
pub async fn get_device_tags(application_id: Uuid) -> Result<BTreeMap<String, Vec<String>>, Error> {
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
let items: Vec<DeviceTags> = diesel::sql_query(
r#"
select
distinct
t.key,
t.value
from
device d,
json_each(d.tags) as t
where
d.application_id = ?1
order by
t.key,
t.value
"#,
)
.bind::<fields::sql_types::Uuid, _>(fields::Uuid::from(application_id))
.load(&mut get_async_db_conn().await?)
.await
.map_err(|e| Error::from_diesel(e, application_id.to_string()))?;
for item in &items {
let entry = out.entry(item.key.clone()).or_default();
entry.push(item.value.clone());
}
Ok(out)
}
#[cfg(test)] #[cfg(test)]
pub mod test { pub mod test {
use super::*; use super::*;

View File

@ -207,13 +207,25 @@ pub struct DeviceListItem {
pub margin: Option<i32>, pub margin: Option<i32>,
pub external_power_source: bool, pub external_power_source: bool,
pub battery_level: Option<fields::BigDecimal>, pub battery_level: Option<fields::BigDecimal>,
pub tags: fields::KeyValue,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct Filters { pub struct Filters {
pub application_id: Option<Uuid>, pub application_id: Option<Uuid>,
pub multicast_group_id: Option<Uuid>, pub multicast_group_id: Option<Uuid>,
pub device_profile_id: Option<Uuid>,
pub search: Option<String>, pub search: Option<String>,
pub tags: HashMap<String, String>,
}
#[derive(Clone, Debug, Default)]
pub enum OrderBy {
#[default]
Name,
DevEui,
LastSeenAt,
DeviceProfileName,
} }
#[derive(QueryableByName, PartialEq, Eq, Debug)] #[derive(QueryableByName, PartialEq, Eq, Debug)]
@ -581,6 +593,10 @@ pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
q = q.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id))); q = q.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id)));
} }
if let Some(device_profile_id) = &filters.device_profile_id {
q = q.filter(device::dsl::device_profile_id.eq(fields::Uuid::from(device_profile_id)));
}
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
{ {
@ -599,6 +615,22 @@ pub async fn get_count(filters: &Filters) -> Result<i64, Error> {
); );
} }
if !filters.tags.is_empty() {
#[cfg(feature = "postgres")]
{
q = q.filter(device::dsl::tags.contains(serde_json::json!(&filters.tags)));
}
#[cfg(feature = "sqlite")]
{
for (k, v) in filters.tags.iter() {
q = q.filter(
dsl::sql::<diesel::sql_types::Bool>(&format!("device.tags->>'{}' =", k))
.bind::<diesel::sql_types::Text, _>(v),
);
}
}
}
Ok(q.first(&mut get_async_db_conn().await?).await?) Ok(q.first(&mut get_async_db_conn().await?).await?)
} }
@ -606,6 +638,8 @@ pub async fn list(
limit: i64, limit: i64,
offset: i64, offset: i64,
filters: &Filters, filters: &Filters,
order_by: OrderBy,
order_by_desc: bool,
) -> Result<Vec<DeviceListItem>, Error> { ) -> Result<Vec<DeviceListItem>, Error> {
let mut q = device::dsl::device let mut q = device::dsl::device
.inner_join(device_profile::table) .inner_join(device_profile::table)
@ -622,6 +656,7 @@ pub async fn list(
device::margin, device::margin,
device::external_power_source, device::external_power_source,
device::battery_level, device::battery_level,
device::tags,
)) ))
.distinct() .distinct()
.into_boxed(); .into_boxed();
@ -630,6 +665,10 @@ pub async fn list(
q = q.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id))); q = q.filter(device::dsl::application_id.eq(fields::Uuid::from(application_id)));
} }
if let Some(device_profile_id) = &filters.device_profile_id {
q = q.filter(device::dsl::device_profile_id.eq(fields::Uuid::from(device_profile_id)));
}
if let Some(search) = &filters.search { if let Some(search) = &filters.search {
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
{ {
@ -648,8 +687,42 @@ pub async fn list(
); );
} }
q.order_by(device::dsl::name) if !filters.tags.is_empty() {
.limit(limit) #[cfg(feature = "postgres")]
{
q = q.filter(device::dsl::tags.contains(serde_json::json!(&filters.tags)));
}
#[cfg(feature = "sqlite")]
{
for (k, v) in filters.tags.iter() {
q = q.filter(
dsl::sql::<diesel::sql_types::Bool>(&format!("device.tags->>'{}' =", k))
.bind::<diesel::sql_types::Text, _>(v),
);
}
}
}
q = match order_by_desc {
true => match order_by {
OrderBy::Name => q.order_by(device::dsl::name.desc()),
OrderBy::DevEui => q.order_by(device::dsl::dev_eui.desc()),
OrderBy::LastSeenAt => q
.order_by(device::dsl::last_seen_at.desc())
.then_order_by(device::dsl::name),
OrderBy::DeviceProfileName => q.order_by(device_profile::dsl::name.desc()),
},
false => match order_by {
OrderBy::Name => q.order_by(device::dsl::name),
OrderBy::DevEui => q.order_by(device::dsl::dev_eui),
OrderBy::LastSeenAt => q
.order_by(device::dsl::last_seen_at)
.then_order_by(device::dsl::name),
OrderBy::DeviceProfileName => q.order_by(device_profile::dsl::name),
},
};
q.limit(limit)
.offset(offset) .offset(offset)
.load(&mut get_async_db_conn().await?) .load(&mut get_async_db_conn().await?)
.await .await
@ -870,11 +943,14 @@ pub mod test {
use lrwn::AES128Key; use lrwn::AES128Key;
struct FilterTest<'a> { struct FilterTest<'a> {
name: String,
filters: Filters, filters: Filters,
devs: Vec<&'a Device>, devs: Vec<&'a Device>,
count: usize, count: usize,
limit: i64, limit: i64,
offset: i64, offset: i64,
order: OrderBy,
order_by_desc: bool,
} }
pub async fn create_device( pub async fn create_device(
@ -930,70 +1006,175 @@ pub mod test {
let d_get = get(&d.dev_eui).await.unwrap(); let d_get = get(&d.dev_eui).await.unwrap();
assert_eq!(d, d_get); assert_eq!(d, d_get);
// delete
delete(&d.dev_eui).await.unwrap();
assert!(delete(&d.dev_eui).await.is_err());
}
#[tokio::test]
async fn test_device_list() {
let _guard = test::prepare().await;
let dp = storage::device_profile::test::create_device_profile(None).await;
let d1 = create_device(
EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
dp.id.into(),
None,
)
.await;
let d2 = create(Device {
name: "zzz-tags-1".into(),
dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]),
tags: fields::KeyValue::new(
[("version".to_string(), "1.1.0".to_string())]
.iter()
.cloned()
.collect(),
),
..d1.clone()
})
.await
.unwrap();
let d3 = create(Device {
name: "zzz-tags-2".into(),
dev_eui: EUI64::from_be_bytes([3, 2, 3, 4, 5, 6, 7, 8]),
tags: fields::KeyValue::new(
[("version".to_string(), "1.2.0".to_string())]
.iter()
.cloned()
.collect(),
),
..d1.clone()
})
.await
.unwrap();
// get count and list // get count and list
let tests = vec![ let tests = vec![
FilterTest { FilterTest {
name: "no filters".into(),
filters: Filters { filters: Filters {
application_id: None, application_id: None,
multicast_group_id: None, multicast_group_id: None,
search: None, search: None,
..Default::default()
}, },
devs: vec![&d], devs: vec![&d1, &d2, &d3],
count: 1, count: 3,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
FilterTest { FilterTest {
name: "filter by search - no match".into(),
filters: Filters { filters: Filters {
application_id: None, application_id: None,
multicast_group_id: None, multicast_group_id: None,
search: Some("uup".into()), search: Some("tee".into()),
..Default::default()
}, },
devs: vec![], devs: vec![],
count: 0, count: 0,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
FilterTest { FilterTest {
name: "filter by search - match".into(),
filters: Filters { filters: Filters {
application_id: None, application_id: None,
multicast_group_id: None, multicast_group_id: None,
search: Some("upd".into()), search: Some("tes".into()),
..Default::default()
}, },
devs: vec![&d], devs: vec![&d1],
count: 1, count: 1,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
FilterTest { FilterTest {
name: "filter by application_id".into(),
filters: Filters { filters: Filters {
application_id: Some(d.application_id.into()), application_id: Some(d1.application_id.into()),
multicast_group_id: None, multicast_group_id: None,
search: None, search: None,
..Default::default()
}, },
devs: vec![&d], devs: vec![&d1, &d2, &d3],
count: 1, count: 3,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
FilterTest { FilterTest {
name: "filter by application_id - no match".into(),
filters: Filters { filters: Filters {
application_id: Some(Uuid::new_v4()), application_id: Some(Uuid::new_v4()),
multicast_group_id: None, multicast_group_id: None,
search: None, search: None,
..Default::default()
}, },
devs: vec![], devs: vec![],
count: 0, count: 0,
limit: 10, limit: 10,
offset: 0, offset: 0,
order: OrderBy::Name,
order_by_desc: false,
},
FilterTest {
name: "filter by tags - 1.1.0".into(),
filters: Filters {
tags: [("version".to_string(), "1.1.0".to_string())]
.iter()
.cloned()
.collect(),
..Default::default()
},
devs: vec![&d2],
count: 1,
limit: 10,
offset: 0,
order: OrderBy::Name,
order_by_desc: false,
},
FilterTest {
name: "filter by tags - 1.2.0".into(),
filters: Filters {
tags: [("version".to_string(), "1.2.0".to_string())]
.iter()
.cloned()
.collect(),
..Default::default()
},
devs: vec![&d3],
count: 1,
limit: 10,
offset: 0,
order: OrderBy::Name,
order_by_desc: false,
}, },
]; ];
for tst in tests { for tst in tests {
println!(" > {}", tst.name);
let count = get_count(&tst.filters).await.unwrap() as usize; let count = get_count(&tst.filters).await.unwrap() as usize;
assert_eq!(tst.count, count); assert_eq!(tst.count, count);
let items = list(tst.limit, tst.offset, &tst.filters).await.unwrap(); let items = list(
tst.limit,
tst.offset,
&tst.filters,
tst.order,
tst.order_by_desc,
)
.await
.unwrap();
assert_eq!( assert_eq!(
tst.devs tst.devs
.iter() .iter()
@ -1005,10 +1186,6 @@ pub mod test {
.collect::<String>() .collect::<String>()
); );
} }
// delete
delete(&d.dev_eui).await.unwrap();
assert!(delete(&d.dev_eui).await.is_err());
} }
#[tokio::test] #[tokio::test]

View File

@ -20,6 +20,7 @@ pub struct DeviceKeys {
pub app_key: AES128Key, pub app_key: AES128Key,
pub dev_nonces: fields::DevNonces, pub dev_nonces: fields::DevNonces,
pub join_nonce: i32, pub join_nonce: i32,
pub gen_app_key: AES128Key,
} }
impl Default for DeviceKeys { impl Default for DeviceKeys {
@ -27,19 +28,14 @@ impl Default for DeviceKeys {
let now = Utc::now(); let now = Utc::now();
DeviceKeys { DeviceKeys {
dev_eui: EUI64::from_be_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), dev_eui: Default::default(),
created_at: now, created_at: now,
updated_at: now, updated_at: now,
nwk_key: AES128Key::from_bytes([ nwk_key: Default::default(),
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, app_key: Default::default(),
0x00, 0x00, dev_nonces: Default::default(),
]),
app_key: AES128Key::from_bytes([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
]),
dev_nonces: fields::DevNonces::default(),
join_nonce: 0, join_nonce: 0,
gen_app_key: Default::default(),
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More