Compare commits

...

53 Commits

Author SHA1 Message Date
4b77fa441d Bump version to 4.12.0-test.2 2025-03-20 12:25:23 +00:00
c137136d4d Update chirpstack configfile template.
See also chirpstack/chirpstack-docs#25
2025-03-20 11:49:07 +00:00
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
730ed09840 Update Rust toolchain version. 2025-03-20 10:58:30 +00:00
105ea2806a Remove rand_core and import re-export. 2025-03-20 10:53:14 +00:00
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
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
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
7c134a549d Update dependencies. 2025-03-20 10:39:52 +00:00
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
293cfe2664 api: Update Go generated code. 2025-03-20 09:03:47 +00:00
5bbd71ab3a Add warnings to fuota deployment job + UI.
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
f02256245c lrwn: Align v2 fragmentation fec with LBM stack.
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
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
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
351406c363 lrwn: Implement v2 app layer fragmentation structs + encoding. 2025-03-13 15:09:03 +00:00
8b59136942 lrwn: Implement v2 app layer multicast setup structs. 2025-03-13 15:09:03 +00:00
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
5a7694a3a4 Bump version to 4.12.0-test.1 2025-03-13 15:09:03 +00:00
98ba2f3198 Set device tags after FUOTA complete. 2025-03-13 15:09:03 +00:00
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
71cc1aca74 Set FUOTA deployment completed_at. 2025-03-13 15:09:03 +00:00
27f6d2cf03 Implement full FUOTA flow + UI components. 2025-03-13 15:09:03 +00:00
b8ab0182de ui: Make app-layer params configurable. 2025-03-13 15:09:03 +00:00
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
e75b62f335 lrwn: Add function for encrypting McKey. 2025-03-13 15:09:03 +00:00
cac682c245 Implement handling AppTimeReq / AppTimeAns. 2025-03-13 15:09:03 +00:00
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
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
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
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
43753958ef api: List devices by device-profile + expose tags. 2025-03-13 15:09:03 +00:00
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
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
38386b23f2 Add start job + get schedulable jobs functions + API. 2025-03-13 15:09:03 +00:00
a3e27d8b65 Add tests + add fuota jobs functions. 2025-03-13 15:09:03 +00:00
9b735d6521 Add first fuota storage functions / API. 2025-03-13 15:09:03 +00:00
d000cd3385 Add option to filter devices by tags. 2025-03-13 15:09:03 +00:00
ac52cce7ee api: Extend 'limit' field documentation. 2025-03-13 15:09:03 +00:00
bbce25efbf Add app-layer params field to device-profile API. 2025-03-13 15:09:03 +00:00
4e7ab31714 Add app-layer params field to device-profile schema. 2025-03-13 15:09:03 +00:00
3c3c1f125d Refactor device-profile relay fields. 2025-03-13 15:09:03 +00:00
909eaed1ba Refactor device-profile class-c fields. 2025-03-13 15:09:03 +00:00
b8c02b943c Refactor device-profile class-b fields. 2025-03-13 15:09:03 +00:00
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
f3d3262006 lrwn: Implement v1 applayer multicastsetup key functions. 2025-03-13 15:09:03 +00:00
ffe01d387c lrwn: Implement applayer v1 fragmentation encoding func. 2025-03-13 15:09:03 +00:00
d1f4f42a79 lrwn: Implement v1 applayer fragmentation structs. 2025-03-13 15:09:03 +00:00
bf21297a42 lrwn: Replace Duration with u32 in applayer timesync. 2025-03-13 15:09:03 +00:00
bcb8aaad4f lrwn: Implement v1 applayer multicast setup structs. 2025-03-13 15:09:03 +00:00
f43c9154bc lrwn: Implement v1 applayer clock sync structs. 2025-03-13 15:09:03 +00:00
3e7f09db62 Add Yandex ID OAuth provider support. (#622) 2025-03-12 13:03:35 +00:00
01246dd124 Add sorting to device and gw table. (#579)
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
173 changed files with 20724 additions and 3391 deletions

1527
Cargo.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,8 @@ const (
ApplicationService_UpdateIftttIntegration_FullMethodName = "/api.ApplicationService/UpdateIftttIntegration"
ApplicationService_DeleteIftttIntegration_FullMethodName = "/api.ApplicationService/DeleteIftttIntegration"
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.
@ -167,6 +169,10 @@ type ApplicationServiceClient interface {
DeleteIftttIntegration(ctx context.Context, in *DeleteIftttIntegrationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Generates application ID specific client-certificate.
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 {
@ -600,6 +606,24 @@ func (c *applicationServiceClient) GenerateMqttIntegrationClientCertificate(ctx
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.
// All implementations must embed UnimplementedApplicationServiceServer
// for forward compatibility
@ -698,6 +722,10 @@ type ApplicationServiceServer interface {
DeleteIftttIntegration(context.Context, *DeleteIftttIntegrationRequest) (*emptypb.Empty, error)
// Generates application ID specific client-certificate.
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()
}
@ -846,6 +874,12 @@ func (UnimplementedApplicationServiceServer) DeleteIftttIntegration(context.Cont
func (UnimplementedApplicationServiceServer) GenerateMqttIntegrationClientCertificate(context.Context, *GenerateMqttIntegrationClientCertificateRequest) (*GenerateMqttIntegrationClientCertificateResponse, error) {
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() {}
// 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)
}
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.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -1900,6 +1970,14 @@ var ApplicationService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GenerateMqttIntegrationClientCertificate",
Handler: _ApplicationService_GenerateMqttIntegrationClientCertificate_Handler,
},
{
MethodName: "ListDeviceProfiles",
Handler: _ApplicationService_ListDeviceProfiles_Handler,
},
{
MethodName: "ListDeviceTags",
Handler: _ApplicationService_ListDeviceTags_Handler,
},
},
Streams: []grpc.StreamDesc{},
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}
}
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 {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -552,6 +708,8 @@ type DeviceProfile struct {
// it.
// 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"`
// 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() {
@ -955,6 +1113,13 @@ func (x *DeviceProfile) GetRx1Delay() uint32 {
return 0
}
func (x *DeviceProfile) GetAppLayerParams() *AppLayerParams {
if x != nil {
return x.AppLayerParams
}
return nil
}
type Measurement struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1010,6 +1175,97 @@ func (x *Measurement) GetKind() MeasurementKind {
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 {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1039,7 +1295,7 @@ type DeviceProfileListItem struct {
func (x *DeviceProfileListItem) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1051,7 +1307,7 @@ func (x *DeviceProfileListItem) String() string {
func (*DeviceProfileListItem) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1064,7 +1320,7 @@ func (x *DeviceProfileListItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeviceProfileListItem.ProtoReflect.Descriptor instead.
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 {
@ -1148,7 +1404,7 @@ type CreateDeviceProfileRequest struct {
func (x *CreateDeviceProfileRequest) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1160,7 +1416,7 @@ func (x *CreateDeviceProfileRequest) String() string {
func (*CreateDeviceProfileRequest) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1173,7 +1429,7 @@ func (x *CreateDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateDeviceProfileRequest.ProtoReflect.Descriptor instead.
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 {
@ -1194,7 +1450,7 @@ type CreateDeviceProfileResponse struct {
func (x *CreateDeviceProfileResponse) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1206,7 +1462,7 @@ func (x *CreateDeviceProfileResponse) String() string {
func (*CreateDeviceProfileResponse) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1219,7 +1475,7 @@ func (x *CreateDeviceProfileResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateDeviceProfileResponse.ProtoReflect.Descriptor instead.
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 {
@ -1240,7 +1496,7 @@ type GetDeviceProfileRequest struct {
func (x *GetDeviceProfileRequest) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1252,7 +1508,7 @@ func (x *GetDeviceProfileRequest) String() string {
func (*GetDeviceProfileRequest) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1265,7 +1521,7 @@ func (x *GetDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetDeviceProfileRequest.ProtoReflect.Descriptor instead.
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 {
@ -1290,7 +1546,7 @@ type GetDeviceProfileResponse struct {
func (x *GetDeviceProfileResponse) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1302,7 +1558,7 @@ func (x *GetDeviceProfileResponse) String() string {
func (*GetDeviceProfileResponse) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1315,7 +1571,7 @@ func (x *GetDeviceProfileResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetDeviceProfileResponse.ProtoReflect.Descriptor instead.
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 {
@ -1350,7 +1606,7 @@ type UpdateDeviceProfileRequest struct {
func (x *UpdateDeviceProfileRequest) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1362,7 +1618,7 @@ func (x *UpdateDeviceProfileRequest) String() string {
func (*UpdateDeviceProfileRequest) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1375,7 +1631,7 @@ func (x *UpdateDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateDeviceProfileRequest.ProtoReflect.Descriptor instead.
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 {
@ -1396,7 +1652,7 @@ type DeleteDeviceProfileRequest struct {
func (x *DeleteDeviceProfileRequest) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1408,7 +1664,7 @@ func (x *DeleteDeviceProfileRequest) String() string {
func (*DeleteDeviceProfileRequest) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1421,7 +1677,7 @@ func (x *DeleteDeviceProfileRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteDeviceProfileRequest.ProtoReflect.Descriptor instead.
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 {
@ -1437,6 +1693,7 @@ type ListDeviceProfilesRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -1448,7 +1705,7 @@ type ListDeviceProfilesRequest struct {
func (x *ListDeviceProfilesRequest) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1460,7 +1717,7 @@ func (x *ListDeviceProfilesRequest) String() string {
func (*ListDeviceProfilesRequest) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1473,7 +1730,7 @@ func (x *ListDeviceProfilesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListDeviceProfilesRequest.ProtoReflect.Descriptor instead.
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 {
@ -1517,7 +1774,7 @@ type ListDeviceProfilesResponse struct {
func (x *ListDeviceProfilesResponse) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1529,7 +1786,7 @@ func (x *ListDeviceProfilesResponse) String() string {
func (*ListDeviceProfilesResponse) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1542,7 +1799,7 @@ func (x *ListDeviceProfilesResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListDeviceProfilesResponse.ProtoReflect.Descriptor instead.
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 {
@ -1572,7 +1829,7 @@ type ListDeviceProfileAdrAlgorithmsResponse struct {
func (x *ListDeviceProfileAdrAlgorithmsResponse) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1584,7 +1841,7 @@ func (x *ListDeviceProfileAdrAlgorithmsResponse) String() string {
func (*ListDeviceProfileAdrAlgorithmsResponse) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1597,7 +1854,7 @@ func (x *ListDeviceProfileAdrAlgorithmsResponse) ProtoReflect() protoreflect.Mes
// Deprecated: Use ListDeviceProfileAdrAlgorithmsResponse.ProtoReflect.Descriptor instead.
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 {
@ -1627,7 +1884,7 @@ type AdrAlgorithmListItem struct {
func (x *AdrAlgorithmListItem) Reset() {
*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.StoreMessageInfo(mi)
}
@ -1639,7 +1896,7 @@ func (x *AdrAlgorithmListItem) String() string {
func (*AdrAlgorithmListItem) ProtoMessage() {}
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 {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1652,7 +1909,7 @@ func (x *AdrAlgorithmListItem) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdrAlgorithmListItem.ProtoReflect.Descriptor instead.
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 {
@ -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,
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,
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,
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,
@ -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,
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,
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,
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, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61,
0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0x4b, 0x0a, 0x0b, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75,
0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73,
0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0x4b, 0x0a, 0x0b, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22,
0x9e, 0x02, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x61, 0x72, 0x61,
0x6d, 0x73, 0x12, 0x36, 0x0a, 0x0d, 0x74, 0x73, 0x30, 0x30, 0x33, 0x5f, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e,
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,
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,
@ -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,
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,
0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x03, 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, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70,
0x4f, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x49, 0x0a, 0x0c, 0x54, 0x73, 0x30, 0x30, 0x33,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x53, 0x30, 0x30, 0x33,
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, 0x33, 0x5f, 0x56, 0x31, 0x30, 0x30,
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,
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64,
0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x65,
0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44,
0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76,
0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x61, 0x70, 0x69,
0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x76, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69,
0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 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, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d,
0x3a, 0x01, 0x2a, 0x1a, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69, 0x63,
0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a,
0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 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, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64,
0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x7b,
0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3,
0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63,
0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x11, 0x4c,
0x69, 0x73, 0x74, 0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73,
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x72, 0x2d, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68,
0x6d, 0x73, 0x42, 0x98, 0x01, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73,
0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65,
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 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,
0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61,
0x70, 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, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12,
0x65, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74,
0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x65,
0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x76, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x76,
0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 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, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x2d, 0x3a, 0x01, 0x2a, 0x1a, 0x28, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63,
0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x64, 0x65, 0x76, 0x69,
0x63, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x69, 0x64, 0x7d, 0x12, 0x64,
0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69,
0x6c, 0x65, 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, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x2a, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2f,
0x7b, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69,
0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x11,
0x4c, 0x69, 0x73, 0x74, 0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d,
0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x41, 0x64, 0x72, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23,
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x73, 0x2f, 0x61, 0x64, 0x72, 0x2d, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74,
0x68, 0x6d, 0x73, 0x42, 0x98, 0x01, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70,
0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63,
0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 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 (
@ -2053,76 +2346,84 @@ func file_api_device_profile_proto_rawDescGZIP() []byte {
return file_api_device_profile_proto_rawDescData
}
var file_api_device_profile_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
var file_api_device_profile_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
var file_api_device_profile_proto_enumTypes = make([]protoimpl.EnumInfo, 8)
var file_api_device_profile_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_api_device_profile_proto_goTypes = []any{
(CodecRuntime)(0), // 0: api.CodecRuntime
(MeasurementKind)(0), // 1: api.MeasurementKind
(CadPeriodicity)(0), // 2: api.CadPeriodicity
(SecondChAckOffset)(0), // 3: api.SecondChAckOffset
(RelayModeActivation)(0), // 4: api.RelayModeActivation
(*DeviceProfile)(nil), // 5: api.DeviceProfile
(*Measurement)(nil), // 6: api.Measurement
(*DeviceProfileListItem)(nil), // 7: api.DeviceProfileListItem
(*CreateDeviceProfileRequest)(nil), // 8: api.CreateDeviceProfileRequest
(*CreateDeviceProfileResponse)(nil), // 9: api.CreateDeviceProfileResponse
(*GetDeviceProfileRequest)(nil), // 10: api.GetDeviceProfileRequest
(*GetDeviceProfileResponse)(nil), // 11: api.GetDeviceProfileResponse
(*UpdateDeviceProfileRequest)(nil), // 12: api.UpdateDeviceProfileRequest
(*DeleteDeviceProfileRequest)(nil), // 13: api.DeleteDeviceProfileRequest
(*ListDeviceProfilesRequest)(nil), // 14: api.ListDeviceProfilesRequest
(*ListDeviceProfilesResponse)(nil), // 15: api.ListDeviceProfilesResponse
(*ListDeviceProfileAdrAlgorithmsResponse)(nil), // 16: api.ListDeviceProfileAdrAlgorithmsResponse
(*AdrAlgorithmListItem)(nil), // 17: api.AdrAlgorithmListItem
nil, // 18: api.DeviceProfile.TagsEntry
nil, // 19: api.DeviceProfile.MeasurementsEntry
(common.Region)(0), // 20: common.Region
(common.MacVersion)(0), // 21: common.MacVersion
(common.RegParamsRevision)(0), // 22: common.RegParamsRevision
(*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 24: google.protobuf.Empty
(Ts003Version)(0), // 5: api.Ts003Version
(Ts004Version)(0), // 6: api.Ts004Version
(Ts005Version)(0), // 7: api.Ts005Version
(*DeviceProfile)(nil), // 8: api.DeviceProfile
(*Measurement)(nil), // 9: api.Measurement
(*AppLayerParams)(nil), // 10: api.AppLayerParams
(*DeviceProfileListItem)(nil), // 11: api.DeviceProfileListItem
(*CreateDeviceProfileRequest)(nil), // 12: api.CreateDeviceProfileRequest
(*CreateDeviceProfileResponse)(nil), // 13: api.CreateDeviceProfileResponse
(*GetDeviceProfileRequest)(nil), // 14: api.GetDeviceProfileRequest
(*GetDeviceProfileResponse)(nil), // 15: api.GetDeviceProfileResponse
(*UpdateDeviceProfileRequest)(nil), // 16: api.UpdateDeviceProfileRequest
(*DeleteDeviceProfileRequest)(nil), // 17: api.DeleteDeviceProfileRequest
(*ListDeviceProfilesRequest)(nil), // 18: api.ListDeviceProfilesRequest
(*ListDeviceProfilesResponse)(nil), // 19: api.ListDeviceProfilesResponse
(*ListDeviceProfileAdrAlgorithmsResponse)(nil), // 20: api.ListDeviceProfileAdrAlgorithmsResponse
(*AdrAlgorithmListItem)(nil), // 21: api.AdrAlgorithmListItem
nil, // 22: api.DeviceProfile.TagsEntry
nil, // 23: api.DeviceProfile.MeasurementsEntry
(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{
20, // 0: api.DeviceProfile.region:type_name -> common.Region
21, // 1: api.DeviceProfile.mac_version:type_name -> common.MacVersion
22, // 2: api.DeviceProfile.reg_params_revision:type_name -> common.RegParamsRevision
24, // 0: api.DeviceProfile.region:type_name -> common.Region
25, // 1: api.DeviceProfile.mac_version:type_name -> common.MacVersion
26, // 2: api.DeviceProfile.reg_params_revision:type_name -> common.RegParamsRevision
0, // 3: api.DeviceProfile.payload_codec_runtime:type_name -> api.CodecRuntime
18, // 4: api.DeviceProfile.tags:type_name -> api.DeviceProfile.TagsEntry
19, // 5: api.DeviceProfile.measurements:type_name -> api.DeviceProfile.MeasurementsEntry
22, // 4: api.DeviceProfile.tags:type_name -> api.DeviceProfile.TagsEntry
23, // 5: api.DeviceProfile.measurements:type_name -> api.DeviceProfile.MeasurementsEntry
2, // 6: api.DeviceProfile.relay_cad_periodicity:type_name -> api.CadPeriodicity
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
1, // 9: api.Measurement.kind:type_name -> api.MeasurementKind
23, // 10: api.DeviceProfileListItem.created_at:type_name -> google.protobuf.Timestamp
23, // 11: api.DeviceProfileListItem.updated_at:type_name -> google.protobuf.Timestamp
20, // 12: api.DeviceProfileListItem.region:type_name -> common.Region
21, // 13: api.DeviceProfileListItem.mac_version:type_name -> common.MacVersion
22, // 14: api.DeviceProfileListItem.reg_params_revision:type_name -> common.RegParamsRevision
5, // 15: api.CreateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile
5, // 16: api.GetDeviceProfileResponse.device_profile:type_name -> api.DeviceProfile
23, // 17: api.GetDeviceProfileResponse.created_at:type_name -> google.protobuf.Timestamp
23, // 18: api.GetDeviceProfileResponse.updated_at:type_name -> google.protobuf.Timestamp
5, // 19: api.UpdateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile
7, // 20: api.ListDeviceProfilesResponse.result:type_name -> api.DeviceProfileListItem
17, // 21: api.ListDeviceProfileAdrAlgorithmsResponse.result:type_name -> api.AdrAlgorithmListItem
6, // 22: api.DeviceProfile.MeasurementsEntry.value:type_name -> api.Measurement
8, // 23: api.DeviceProfileService.Create:input_type -> api.CreateDeviceProfileRequest
10, // 24: api.DeviceProfileService.Get:input_type -> api.GetDeviceProfileRequest
12, // 25: api.DeviceProfileService.Update:input_type -> api.UpdateDeviceProfileRequest
13, // 26: api.DeviceProfileService.Delete:input_type -> api.DeleteDeviceProfileRequest
14, // 27: api.DeviceProfileService.List:input_type -> api.ListDeviceProfilesRequest
24, // 28: api.DeviceProfileService.ListAdrAlgorithms:input_type -> google.protobuf.Empty
9, // 29: api.DeviceProfileService.Create:output_type -> api.CreateDeviceProfileResponse
11, // 30: api.DeviceProfileService.Get:output_type -> api.GetDeviceProfileResponse
24, // 31: api.DeviceProfileService.Update:output_type -> google.protobuf.Empty
24, // 32: api.DeviceProfileService.Delete:output_type -> google.protobuf.Empty
15, // 33: api.DeviceProfileService.List:output_type -> api.ListDeviceProfilesResponse
16, // 34: api.DeviceProfileService.ListAdrAlgorithms:output_type -> api.ListDeviceProfileAdrAlgorithmsResponse
29, // [29:35] is the sub-list for method output_type
23, // [23:29] is the sub-list for method input_type
23, // [23:23] is the sub-list for extension type_name
23, // [23:23] is the sub-list for extension extendee
0, // [0:23] is the sub-list for field type_name
10, // 9: api.DeviceProfile.app_layer_params:type_name -> api.AppLayerParams
1, // 10: api.Measurement.kind:type_name -> api.MeasurementKind
5, // 11: api.AppLayerParams.ts003_version:type_name -> api.Ts003Version
6, // 12: api.AppLayerParams.ts004_version:type_name -> api.Ts004Version
7, // 13: api.AppLayerParams.ts005_version:type_name -> api.Ts005Version
27, // 14: api.DeviceProfileListItem.created_at:type_name -> google.protobuf.Timestamp
27, // 15: api.DeviceProfileListItem.updated_at:type_name -> google.protobuf.Timestamp
24, // 16: api.DeviceProfileListItem.region:type_name -> common.Region
25, // 17: api.DeviceProfileListItem.mac_version:type_name -> common.MacVersion
26, // 18: api.DeviceProfileListItem.reg_params_revision:type_name -> common.RegParamsRevision
8, // 19: api.CreateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile
8, // 20: api.GetDeviceProfileResponse.device_profile:type_name -> api.DeviceProfile
27, // 21: api.GetDeviceProfileResponse.created_at:type_name -> google.protobuf.Timestamp
27, // 22: api.GetDeviceProfileResponse.updated_at:type_name -> google.protobuf.Timestamp
8, // 23: api.UpdateDeviceProfileRequest.device_profile:type_name -> api.DeviceProfile
11, // 24: api.ListDeviceProfilesResponse.result:type_name -> api.DeviceProfileListItem
21, // 25: api.ListDeviceProfileAdrAlgorithmsResponse.result:type_name -> api.AdrAlgorithmListItem
9, // 26: api.DeviceProfile.MeasurementsEntry.value:type_name -> api.Measurement
12, // 27: api.DeviceProfileService.Create:input_type -> api.CreateDeviceProfileRequest
14, // 28: api.DeviceProfileService.Get:input_type -> api.GetDeviceProfileRequest
16, // 29: api.DeviceProfileService.Update:input_type -> api.UpdateDeviceProfileRequest
17, // 30: api.DeviceProfileService.Delete:input_type -> api.DeleteDeviceProfileRequest
18, // 31: api.DeviceProfileService.List:input_type -> api.ListDeviceProfilesRequest
28, // 32: api.DeviceProfileService.ListAdrAlgorithms:input_type -> google.protobuf.Empty
13, // 33: api.DeviceProfileService.Create:output_type -> api.CreateDeviceProfileResponse
15, // 34: api.DeviceProfileService.Get:output_type -> api.GetDeviceProfileResponse
28, // 35: api.DeviceProfileService.Update:output_type -> google.protobuf.Empty
28, // 36: api.DeviceProfileService.Delete:output_type -> google.protobuf.Empty
19, // 37: api.DeviceProfileService.List:output_type -> api.ListDeviceProfilesResponse
20, // 38: api.DeviceProfileService.ListAdrAlgorithms:output_type -> api.ListDeviceProfileAdrAlgorithmsResponse
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() }
@ -2135,8 +2436,8 @@ func file_api_device_profile_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_device_profile_proto_rawDesc,
NumEnums: 5,
NumMessages: 15,
NumEnums: 8,
NumMessages: 16,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -735,6 +735,7 @@ type ListDeviceProfileTemplatesRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
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}
}
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 {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -579,6 +628,7 @@ type ListGatewaysRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
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"`
// 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"`
// 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() {
@ -656,6 +710,20 @@ func (x *ListGatewaysRequest) GetMulticastGroupId() string {
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 {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1256,6 +1324,7 @@ type ListRelayGatewaysRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
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,
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,
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,
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,
@ -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,
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,
0x70, 0x49, 0x64, 0x22, 0x65, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74,
0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06,
0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74,
0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x48, 0x0a, 0x27, 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, 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, 0x22, 0xb2, 0x01, 0x0a, 0x28, 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, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6c, 0x73, 0x43, 0x65, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07,
0x74, 0x6c, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74,
0x6c, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x43, 0x65, 0x72, 0x74, 0x12, 0x39,
0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x22, 0xd0, 0x01, 0x0a, 0x18, 0x47, 0x65,
0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 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, 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,
0x70, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79,
0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x5f, 0x64, 0x65, 0x73,
0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79,
0x44, 0x65, 0x73, 0x63, 0x22, 0x35, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x12,
0x08, 0x0a, 0x04, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x41, 0x54,
0x45, 0x57, 0x41, 0x59, 0x5f, 0x49, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x41, 0x53,
0x54, 0x5f, 0x53, 0x45, 0x45, 0x4e, 0x5f, 0x41, 0x54, 0x10, 0x02, 0x22, 0x65, 0x0a, 0x14, 0x4c,
0x69, 0x73, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43,
0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x22, 0x48, 0x0a, 0x27, 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, 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, 0x22, 0xb2, 0x01, 0x0a,
0x28, 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, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x6c, 0x73,
0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x6c, 0x73,
0x43, 0x65, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6c, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6c, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a,
0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x63, 0x61, 0x43, 0x65, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
0x73, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e,
0x41, 0x74, 0x22, 0x65, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 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, 0x0d, 0x52, 0x05, 0x6c,
0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09,
0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x19, 0x4c, 0x69, 0x73,
0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74,
0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65,
0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74,
0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x8b, 0x03, 0x0a, 0x14, 0x52,
0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49,
0x74, 0x65, 0x6d, 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, 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, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 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, 0x06, 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, 0x07, 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, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53,
0x65, 0x65, 0x6e, 0x41, 0x74, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a,
0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28,
0x0a, 0x10, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x19, 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, 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, 0x22, 0x53, 0x0a,
0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41,
0x74, 0x22, 0xd0, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
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, 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, 0xcd, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 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, 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, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x27, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x61, 0x74, 0x65, 0x77, 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, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a,
0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x22, 0x65, 0x0a, 0x18, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 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, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06,
0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66,
0x66, 0x73, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49,
0x64, 0x22, 0x6f, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f,
0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
0x31, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x22, 0x8b, 0x03, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 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, 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, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65,
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 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, 0x06, 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,
0x07, 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, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x12, 0x27, 0x0a,
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52,
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e,
0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64,
0x22, 0x53, 0x0a, 0x19, 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, 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, 0x22, 0x53, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 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, 0xcd, 0x01, 0x0a, 0x0c, 0x52,
0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 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, 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,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f,
0x12, 0x2d, 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, 0x64, 0x75, 0x74,
0x79, 0x2d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12,
0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x12, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c,
0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x12, 0x33, 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,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 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, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x12, 0x2d, 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, 0x64, 0x75, 0x74, 0x79, 0x2d, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2d,
0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52,
0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x1b, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47,
0x65, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x12, 0x33,
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,
0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x61,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12,
0x1c, 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, 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,
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, 0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x72,
0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x74, 0x65, 0x6e,
0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67,
0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d,
0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79,
0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65,
0x6c, 0x65, 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,
0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x2a, 0x33, 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, 0x42, 0x92, 0x01, 0x0a,
0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61,
0x70, 0x69, 0x42, 0x0c, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
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,
0x65, 0x77, 0x61, 0x79, 0x73, 0x2f, 0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74,
0x65, 0x77, 0x61, 0x79, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
0x7b, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x72,
0x65, 0x6c, 0x61, 0x79, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x6c,
0x65, 0x74, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12,
0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 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, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x2a,
0x33, 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, 0x42, 0x92, 0x01, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x69, 0x72,
0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0c, 0x47, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 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 (
@ -2086,113 +2165,115 @@ func file_api_gateway_proto_rawDescGZIP() []byte {
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_goTypes = []any{
(GatewayState)(0), // 0: api.GatewayState
(*Gateway)(nil), // 1: api.Gateway
(*GatewayListItem)(nil), // 2: api.GatewayListItem
(*CreateGatewayRequest)(nil), // 3: api.CreateGatewayRequest
(*GetGatewayRequest)(nil), // 4: api.GetGatewayRequest
(*GetGatewayResponse)(nil), // 5: api.GetGatewayResponse
(*UpdateGatewayRequest)(nil), // 6: api.UpdateGatewayRequest
(*DeleteGatewayRequest)(nil), // 7: api.DeleteGatewayRequest
(*ListGatewaysRequest)(nil), // 8: api.ListGatewaysRequest
(*ListGatewaysResponse)(nil), // 9: api.ListGatewaysResponse
(*GenerateGatewayClientCertificateRequest)(nil), // 10: api.GenerateGatewayClientCertificateRequest
(*GenerateGatewayClientCertificateResponse)(nil), // 11: api.GenerateGatewayClientCertificateResponse
(*GetGatewayMetricsRequest)(nil), // 12: api.GetGatewayMetricsRequest
(*GetGatewayMetricsResponse)(nil), // 13: api.GetGatewayMetricsResponse
(*GetGatewayDutyCycleMetricsRequest)(nil), // 14: api.GetGatewayDutyCycleMetricsRequest
(*GetGatewayDutyCycleMetricsResponse)(nil), // 15: api.GetGatewayDutyCycleMetricsResponse
(*GetRelayGatewayRequest)(nil), // 16: api.GetRelayGatewayRequest
(*GetRelayGatewayResponse)(nil), // 17: api.GetRelayGatewayResponse
(*ListRelayGatewaysRequest)(nil), // 18: api.ListRelayGatewaysRequest
(*ListRelayGatewaysResponse)(nil), // 19: api.ListRelayGatewaysResponse
(*RelayGatewayListItem)(nil), // 20: api.RelayGatewayListItem
(*UpdateRelayGatewayRequest)(nil), // 21: api.UpdateRelayGatewayRequest
(*DeleteRelayGatewayRequest)(nil), // 22: api.DeleteRelayGatewayRequest
(*RelayGateway)(nil), // 23: api.RelayGateway
nil, // 24: api.Gateway.TagsEntry
nil, // 25: api.Gateway.MetadataEntry
nil, // 26: api.GatewayListItem.PropertiesEntry
(*common.Location)(nil), // 27: common.Location
(*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp
(common.Aggregation)(0), // 29: common.Aggregation
(*common.Metric)(nil), // 30: common.Metric
(*emptypb.Empty)(nil), // 31: google.protobuf.Empty
(ListGatewaysRequest_OrderBy)(0), // 1: api.ListGatewaysRequest.OrderBy
(*Gateway)(nil), // 2: api.Gateway
(*GatewayListItem)(nil), // 3: api.GatewayListItem
(*CreateGatewayRequest)(nil), // 4: api.CreateGatewayRequest
(*GetGatewayRequest)(nil), // 5: api.GetGatewayRequest
(*GetGatewayResponse)(nil), // 6: api.GetGatewayResponse
(*UpdateGatewayRequest)(nil), // 7: api.UpdateGatewayRequest
(*DeleteGatewayRequest)(nil), // 8: api.DeleteGatewayRequest
(*ListGatewaysRequest)(nil), // 9: api.ListGatewaysRequest
(*ListGatewaysResponse)(nil), // 10: api.ListGatewaysResponse
(*GenerateGatewayClientCertificateRequest)(nil), // 11: api.GenerateGatewayClientCertificateRequest
(*GenerateGatewayClientCertificateResponse)(nil), // 12: api.GenerateGatewayClientCertificateResponse
(*GetGatewayMetricsRequest)(nil), // 13: api.GetGatewayMetricsRequest
(*GetGatewayMetricsResponse)(nil), // 14: api.GetGatewayMetricsResponse
(*GetGatewayDutyCycleMetricsRequest)(nil), // 15: api.GetGatewayDutyCycleMetricsRequest
(*GetGatewayDutyCycleMetricsResponse)(nil), // 16: api.GetGatewayDutyCycleMetricsResponse
(*GetRelayGatewayRequest)(nil), // 17: api.GetRelayGatewayRequest
(*GetRelayGatewayResponse)(nil), // 18: api.GetRelayGatewayResponse
(*ListRelayGatewaysRequest)(nil), // 19: api.ListRelayGatewaysRequest
(*ListRelayGatewaysResponse)(nil), // 20: api.ListRelayGatewaysResponse
(*RelayGatewayListItem)(nil), // 21: api.RelayGatewayListItem
(*UpdateRelayGatewayRequest)(nil), // 22: api.UpdateRelayGatewayRequest
(*DeleteRelayGatewayRequest)(nil), // 23: api.DeleteRelayGatewayRequest
(*RelayGateway)(nil), // 24: api.RelayGateway
nil, // 25: api.Gateway.TagsEntry
nil, // 26: api.Gateway.MetadataEntry
nil, // 27: api.GatewayListItem.PropertiesEntry
(*common.Location)(nil), // 28: common.Location
(*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp
(common.Aggregation)(0), // 30: common.Aggregation
(*common.Metric)(nil), // 31: common.Metric
(*emptypb.Empty)(nil), // 32: google.protobuf.Empty
}
var file_api_gateway_proto_depIdxs = []int32{
27, // 0: api.Gateway.location:type_name -> common.Location
24, // 1: api.Gateway.tags:type_name -> api.Gateway.TagsEntry
25, // 2: api.Gateway.metadata:type_name -> api.Gateway.MetadataEntry
27, // 3: api.GatewayListItem.location:type_name -> common.Location
26, // 4: api.GatewayListItem.properties:type_name -> api.GatewayListItem.PropertiesEntry
28, // 5: api.GatewayListItem.created_at:type_name -> google.protobuf.Timestamp
28, // 6: api.GatewayListItem.updated_at:type_name -> google.protobuf.Timestamp
28, // 7: api.GatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp
28, // 0: api.Gateway.location:type_name -> common.Location
25, // 1: api.Gateway.tags:type_name -> api.Gateway.TagsEntry
26, // 2: api.Gateway.metadata:type_name -> api.Gateway.MetadataEntry
28, // 3: api.GatewayListItem.location:type_name -> common.Location
27, // 4: api.GatewayListItem.properties:type_name -> api.GatewayListItem.PropertiesEntry
29, // 5: api.GatewayListItem.created_at:type_name -> google.protobuf.Timestamp
29, // 6: api.GatewayListItem.updated_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
1, // 9: api.CreateGatewayRequest.gateway:type_name -> api.Gateway
1, // 10: api.GetGatewayResponse.gateway:type_name -> api.Gateway
28, // 11: api.GetGatewayResponse.created_at:type_name -> google.protobuf.Timestamp
28, // 12: api.GetGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp
28, // 13: api.GetGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp
1, // 14: api.UpdateGatewayRequest.gateway:type_name -> api.Gateway
2, // 15: api.ListGatewaysResponse.result:type_name -> api.GatewayListItem
28, // 16: api.GenerateGatewayClientCertificateResponse.expires_at:type_name -> google.protobuf.Timestamp
28, // 17: api.GetGatewayMetricsRequest.start:type_name -> google.protobuf.Timestamp
28, // 18: api.GetGatewayMetricsRequest.end:type_name -> google.protobuf.Timestamp
29, // 19: api.GetGatewayMetricsRequest.aggregation:type_name -> common.Aggregation
30, // 20: api.GetGatewayMetricsResponse.rx_packets:type_name -> common.Metric
30, // 21: api.GetGatewayMetricsResponse.tx_packets:type_name -> common.Metric
30, // 22: api.GetGatewayMetricsResponse.tx_packets_per_freq:type_name -> common.Metric
30, // 23: api.GetGatewayMetricsResponse.rx_packets_per_freq:type_name -> common.Metric
30, // 24: api.GetGatewayMetricsResponse.tx_packets_per_dr:type_name -> common.Metric
30, // 25: api.GetGatewayMetricsResponse.rx_packets_per_dr:type_name -> common.Metric
30, // 26: api.GetGatewayMetricsResponse.tx_packets_per_status:type_name -> common.Metric
28, // 27: api.GetGatewayDutyCycleMetricsRequest.start:type_name -> google.protobuf.Timestamp
28, // 28: api.GetGatewayDutyCycleMetricsRequest.end:type_name -> google.protobuf.Timestamp
30, // 29: api.GetGatewayDutyCycleMetricsResponse.max_load_percentage:type_name -> common.Metric
30, // 30: api.GetGatewayDutyCycleMetricsResponse.window_percentage:type_name -> common.Metric
23, // 31: api.GetRelayGatewayResponse.relay_gateway:type_name -> api.RelayGateway
28, // 32: api.GetRelayGatewayResponse.created_at:type_name -> google.protobuf.Timestamp
28, // 33: api.GetRelayGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp
28, // 34: api.GetRelayGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp
20, // 35: api.ListRelayGatewaysResponse.result:type_name -> api.RelayGatewayListItem
28, // 36: api.RelayGatewayListItem.created_at:type_name -> google.protobuf.Timestamp
28, // 37: api.RelayGatewayListItem.updated_at:type_name -> google.protobuf.Timestamp
28, // 38: api.RelayGatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp
0, // 39: api.RelayGatewayListItem.state:type_name -> api.GatewayState
23, // 40: api.UpdateRelayGatewayRequest.relay_gateway:type_name -> api.RelayGateway
3, // 41: api.GatewayService.Create:input_type -> api.CreateGatewayRequest
4, // 42: api.GatewayService.Get:input_type -> api.GetGatewayRequest
6, // 43: api.GatewayService.Update:input_type -> api.UpdateGatewayRequest
7, // 44: api.GatewayService.Delete:input_type -> api.DeleteGatewayRequest
8, // 45: api.GatewayService.List:input_type -> api.ListGatewaysRequest
10, // 46: api.GatewayService.GenerateClientCertificate:input_type -> api.GenerateGatewayClientCertificateRequest
12, // 47: api.GatewayService.GetMetrics:input_type -> api.GetGatewayMetricsRequest
14, // 48: api.GatewayService.GetDutyCycleMetrics:input_type -> api.GetGatewayDutyCycleMetricsRequest
16, // 49: api.GatewayService.GetRelayGateway:input_type -> api.GetRelayGatewayRequest
18, // 50: api.GatewayService.ListRelayGateways:input_type -> api.ListRelayGatewaysRequest
21, // 51: api.GatewayService.UpdateRelayGateway:input_type -> api.UpdateRelayGatewayRequest
22, // 52: api.GatewayService.DeleteRelayGateway:input_type -> api.DeleteRelayGatewayRequest
31, // 53: api.GatewayService.Create:output_type -> google.protobuf.Empty
5, // 54: api.GatewayService.Get:output_type -> api.GetGatewayResponse
31, // 55: api.GatewayService.Update:output_type -> google.protobuf.Empty
31, // 56: api.GatewayService.Delete:output_type -> google.protobuf.Empty
9, // 57: api.GatewayService.List:output_type -> api.ListGatewaysResponse
11, // 58: api.GatewayService.GenerateClientCertificate:output_type -> api.GenerateGatewayClientCertificateResponse
13, // 59: api.GatewayService.GetMetrics:output_type -> api.GetGatewayMetricsResponse
15, // 60: api.GatewayService.GetDutyCycleMetrics:output_type -> api.GetGatewayDutyCycleMetricsResponse
17, // 61: api.GatewayService.GetRelayGateway:output_type -> api.GetRelayGatewayResponse
19, // 62: api.GatewayService.ListRelayGateways:output_type -> api.ListRelayGatewaysResponse
31, // 63: api.GatewayService.UpdateRelayGateway:output_type -> google.protobuf.Empty
31, // 64: api.GatewayService.DeleteRelayGateway:output_type -> google.protobuf.Empty
53, // [53:65] is the sub-list for method output_type
41, // [41:53] is the sub-list for method input_type
41, // [41:41] is the sub-list for extension type_name
41, // [41:41] is the sub-list for extension extendee
0, // [0:41] is the sub-list for field type_name
2, // 9: api.CreateGatewayRequest.gateway:type_name -> api.Gateway
2, // 10: api.GetGatewayResponse.gateway:type_name -> api.Gateway
29, // 11: api.GetGatewayResponse.created_at:type_name -> google.protobuf.Timestamp
29, // 12: api.GetGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp
29, // 13: api.GetGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp
2, // 14: api.UpdateGatewayRequest.gateway:type_name -> api.Gateway
1, // 15: api.ListGatewaysRequest.order_by:type_name -> api.ListGatewaysRequest.OrderBy
3, // 16: api.ListGatewaysResponse.result:type_name -> api.GatewayListItem
29, // 17: api.GenerateGatewayClientCertificateResponse.expires_at:type_name -> google.protobuf.Timestamp
29, // 18: api.GetGatewayMetricsRequest.start:type_name -> google.protobuf.Timestamp
29, // 19: api.GetGatewayMetricsRequest.end:type_name -> google.protobuf.Timestamp
30, // 20: api.GetGatewayMetricsRequest.aggregation:type_name -> common.Aggregation
31, // 21: api.GetGatewayMetricsResponse.rx_packets:type_name -> common.Metric
31, // 22: api.GetGatewayMetricsResponse.tx_packets:type_name -> common.Metric
31, // 23: api.GetGatewayMetricsResponse.tx_packets_per_freq:type_name -> common.Metric
31, // 24: api.GetGatewayMetricsResponse.rx_packets_per_freq:type_name -> common.Metric
31, // 25: api.GetGatewayMetricsResponse.tx_packets_per_dr:type_name -> common.Metric
31, // 26: api.GetGatewayMetricsResponse.rx_packets_per_dr:type_name -> common.Metric
31, // 27: api.GetGatewayMetricsResponse.tx_packets_per_status:type_name -> common.Metric
29, // 28: api.GetGatewayDutyCycleMetricsRequest.start:type_name -> google.protobuf.Timestamp
29, // 29: api.GetGatewayDutyCycleMetricsRequest.end:type_name -> google.protobuf.Timestamp
31, // 30: api.GetGatewayDutyCycleMetricsResponse.max_load_percentage:type_name -> common.Metric
31, // 31: api.GetGatewayDutyCycleMetricsResponse.window_percentage:type_name -> common.Metric
24, // 32: api.GetRelayGatewayResponse.relay_gateway:type_name -> api.RelayGateway
29, // 33: api.GetRelayGatewayResponse.created_at:type_name -> google.protobuf.Timestamp
29, // 34: api.GetRelayGatewayResponse.updated_at:type_name -> google.protobuf.Timestamp
29, // 35: api.GetRelayGatewayResponse.last_seen_at:type_name -> google.protobuf.Timestamp
21, // 36: api.ListRelayGatewaysResponse.result:type_name -> api.RelayGatewayListItem
29, // 37: api.RelayGatewayListItem.created_at:type_name -> google.protobuf.Timestamp
29, // 38: api.RelayGatewayListItem.updated_at:type_name -> google.protobuf.Timestamp
29, // 39: api.RelayGatewayListItem.last_seen_at:type_name -> google.protobuf.Timestamp
0, // 40: api.RelayGatewayListItem.state:type_name -> api.GatewayState
24, // 41: api.UpdateRelayGatewayRequest.relay_gateway:type_name -> api.RelayGateway
4, // 42: api.GatewayService.Create:input_type -> api.CreateGatewayRequest
5, // 43: api.GatewayService.Get:input_type -> api.GetGatewayRequest
7, // 44: api.GatewayService.Update:input_type -> api.UpdateGatewayRequest
8, // 45: api.GatewayService.Delete:input_type -> api.DeleteGatewayRequest
9, // 46: api.GatewayService.List:input_type -> api.ListGatewaysRequest
11, // 47: api.GatewayService.GenerateClientCertificate:input_type -> api.GenerateGatewayClientCertificateRequest
13, // 48: api.GatewayService.GetMetrics:input_type -> api.GetGatewayMetricsRequest
15, // 49: api.GatewayService.GetDutyCycleMetrics:input_type -> api.GetGatewayDutyCycleMetricsRequest
17, // 50: api.GatewayService.GetRelayGateway:input_type -> api.GetRelayGatewayRequest
19, // 51: api.GatewayService.ListRelayGateways:input_type -> api.ListRelayGatewaysRequest
22, // 52: api.GatewayService.UpdateRelayGateway:input_type -> api.UpdateRelayGatewayRequest
23, // 53: api.GatewayService.DeleteRelayGateway:input_type -> api.DeleteRelayGatewayRequest
32, // 54: api.GatewayService.Create:output_type -> google.protobuf.Empty
6, // 55: api.GatewayService.Get:output_type -> api.GetGatewayResponse
32, // 56: api.GatewayService.Update:output_type -> google.protobuf.Empty
32, // 57: api.GatewayService.Delete:output_type -> google.protobuf.Empty
10, // 58: api.GatewayService.List:output_type -> api.ListGatewaysResponse
12, // 59: api.GatewayService.GenerateClientCertificate:output_type -> api.GenerateGatewayClientCertificateResponse
14, // 60: api.GatewayService.GetMetrics:output_type -> api.GetGatewayMetricsResponse
16, // 61: api.GatewayService.GetDutyCycleMetrics:output_type -> api.GetGatewayDutyCycleMetricsResponse
18, // 62: api.GatewayService.GetRelayGateway:output_type -> api.GetRelayGatewayResponse
20, // 63: api.GatewayService.ListRelayGateways:output_type -> api.ListRelayGatewaysResponse
32, // 64: api.GatewayService.UpdateRelayGateway:output_type -> google.protobuf.Empty
32, // 65: api.GatewayService.DeleteRelayGateway:output_type -> google.protobuf.Empty
54, // [54:66] is the sub-list for method output_type
42, // [42:54] is the sub-list for method input_type
42, // [42:42] is the sub-list for extension 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() }
@ -2205,7 +2286,7 @@ func file_api_gateway_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_gateway_proto_rawDesc,
NumEnums: 1,
NumEnums: 2,
NumMessages: 26,
NumExtensions: 0,
NumServices: 1,

View File

@ -251,6 +251,7 @@ type ListApiKeysRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -630,6 +631,7 @@ type GlobalSearchRequest struct {
// Search query.
Search string `protobuf:"bytes,1,opt,name=search,proto3" json:"search,omitempty"`
// 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"`
// Offset offset of the result-set (for pagination).
Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -685,6 +685,7 @@ type ListMulticastGroupsRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -84,6 +84,7 @@ type ListRelaysRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -313,6 +314,7 @@ type ListRelayDevicesRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -571,6 +571,7 @@ type ListTenantsRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@ -1172,6 +1173,7 @@ type ListTenantUsersRequest struct {
// Tenant ID (UUID).
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.
// 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"`
// Offset in the result-set (for pagination).
Offset uint32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`

View File

@ -590,6 +590,7 @@ type ListUsersRequest struct {
unknownFields protoimpl.UnknownFields
// 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"`
// Offset in the result-set (for pagination).
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 (
github.com/golang/protobuf v1.5.4 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
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.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
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/go.mod h1:K4kfzHtI0kqWA79gecJarFtDn/Mls+GxQcg3Zox91Ac=
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/multicast_group.proto
$(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/relay.proto
$(PROTOC_PATH) $(PROTOC_ARGS) ../proto/api/fuota.proto
integration:
mkdir -p integration

View File

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

View File

@ -8,7 +8,7 @@ plugins {
}
group = "io.chirpstack"
version = "4.11.1"
version = "4.12.0-test.2"
repositories {
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/multicast_group.proto
$(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/relay.proto
$(PROTOC_PATH) ${PROTOC_GRPC_ARGS} ../proto/api/fuota.proto
integration:
$(PROTOC_PATH) ${PROTOC_ARGS} ../proto/integration/integration.proto

2
api/js/package.json vendored
View File

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

View File

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

View File

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

View File

@ -427,6 +427,20 @@ service ApplicationService {
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 {
@ -529,6 +543,7 @@ message DeleteApplicationRequest {
message ListApplicationsRequest {
// 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;
// Offset in the result-set (for pagination).
@ -1098,3 +1113,39 @@ message GenerateMqttIntegrationClientCertificateResponse {
// Expires at defines the expiration date of the certificate.
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.
DeviceStatus device_status = 9;
// Device tags.
map<string, string> tags = 10;
}
message DeviceKeys {
@ -275,6 +278,11 @@ message DeviceKeys {
// Application root key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.1.x devices!
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 {
@ -319,6 +327,7 @@ message DeleteDeviceRequest {
message ListDevicesRequest {
// 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).
@ -332,6 +341,25 @@ message ListDevicesRequest {
// Multicst-group ID (UUID) to filter devices on.
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 {

View File

@ -98,6 +98,39 @@ enum RelayModeActivation {
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
// device-profiles.
service DeviceProfileService {
@ -421,6 +454,9 @@ message DeviceProfile {
// it.
// Valid options are 1 - 15 (0 = always use system RX1 Delay).
uint32 rx1_delay = 53;
// Application Layer parameters.
AppLayerParams app_layer_params = 54;
}
message Measurement {
@ -431,6 +467,26 @@ message Measurement {
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 {
// Device-profile ID (UUID).
string id = 1;
@ -501,6 +557,7 @@ message DeleteDeviceProfileRequest {
message ListDeviceProfilesRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -231,6 +231,7 @@ message DeleteDeviceProfileTemplateRequest {
message ListDeviceProfileTemplatesRequest {
// 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;
// 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 {
// 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).
@ -229,6 +230,18 @@ message ListGatewaysRequest {
// Multicast-group ID (UUID) to filter gateways on.
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 {
@ -338,6 +351,7 @@ message GetRelayGatewayResponse {
message ListRelayGatewaysRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -109,6 +109,7 @@ message DeleteApiKeyRequest {
message ListApiKeysRequest {
// 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;
// Offset in the result-set (for pagination).
@ -177,6 +178,7 @@ message GlobalSearchRequest {
string search = 1;
// 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;
// Offset offset of the result-set (for pagination).

View File

@ -237,6 +237,7 @@ message DeleteMulticastGroupRequest {
message ListMulticastGroupsRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -56,6 +56,7 @@ message RelayListItem {
message ListRelaysRequest {
// 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).
@ -92,6 +93,7 @@ message RemoveRelayDeviceRequest {
message ListRelayDevicesRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -200,6 +200,7 @@ message DeleteTenantRequest {
message ListTenantsRequest {
// 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;
// Offset in the result-set (for pagination).
@ -313,6 +314,7 @@ message ListTenantUsersRequest {
string tenant_id = 1;
// 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;
// Offset in the result-set (for pagination).

View File

@ -161,6 +161,7 @@ message DeleteUserRequest {
message ListUsersRequest {
// 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;
// Offset in the result-set (for pagination).

View File

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

7
api/rust/Cargo.toml vendored
View File

@ -1,7 +1,7 @@
[package]
name = "chirpstack_api"
description = "ChirpStack Protobuf / gRPC API definitions."
version = "4.11.1"
version = "4.12.0-test.2"
authors = ["Orne Brocaar <info@brocaar.com>"]
license = "MIT"
homepage = "https://www.chirpstack.io"
@ -18,13 +18,12 @@
prost = "0.13"
prost-types = "0.13"
hex = "0.4"
rand = "0.8"
rand = "0.9"
tonic = { version = "0.12", features = [
"codegen",
"prost",
], 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-types = { version = "0.7", 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()
.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(),

View File

@ -427,6 +427,20 @@ service ApplicationService {
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 {
@ -529,6 +543,7 @@ message DeleteApplicationRequest {
message ListApplicationsRequest {
// 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;
// Offset in the result-set (for pagination).
@ -1098,3 +1113,39 @@ message GenerateMqttIntegrationClientCertificateResponse {
// Expires at defines the expiration date of the certificate.
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.
DeviceStatus device_status = 9;
// Device tags.
map<string, string> tags = 10;
}
message DeviceKeys {
@ -275,6 +278,11 @@ message DeviceKeys {
// Application root key (128 bit).
// Note: This field only needs to be set for LoRaWAN 1.1.x devices!
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 {
@ -319,6 +327,7 @@ message DeleteDeviceRequest {
message ListDevicesRequest {
// 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).
@ -332,6 +341,25 @@ message ListDevicesRequest {
// Multicst-group ID (UUID) to filter devices on.
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 {

View File

@ -98,6 +98,39 @@ enum RelayModeActivation {
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
// device-profiles.
service DeviceProfileService {
@ -421,6 +454,9 @@ message DeviceProfile {
// it.
// Valid options are 1 - 15 (0 = always use system RX1 Delay).
uint32 rx1_delay = 53;
// Application Layer parameters.
AppLayerParams app_layer_params = 54;
}
message Measurement {
@ -431,6 +467,26 @@ message Measurement {
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 {
// Device-profile ID (UUID).
string id = 1;
@ -501,6 +557,7 @@ message DeleteDeviceProfileRequest {
message ListDeviceProfilesRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -231,6 +231,7 @@ message DeleteDeviceProfileTemplateRequest {
message ListDeviceProfileTemplatesRequest {
// 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;
// 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 {
// 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).
@ -229,6 +230,18 @@ message ListGatewaysRequest {
// Multicast-group ID (UUID) to filter gateways on.
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 {
@ -338,6 +351,7 @@ message GetRelayGatewayResponse {
message ListRelayGatewaysRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -109,6 +109,7 @@ message DeleteApiKeyRequest {
message ListApiKeysRequest {
// 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;
// Offset in the result-set (for pagination).
@ -177,6 +178,7 @@ message GlobalSearchRequest {
string search = 1;
// 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;
// Offset offset of the result-set (for pagination).

View File

@ -237,6 +237,7 @@ message DeleteMulticastGroupRequest {
message ListMulticastGroupsRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -56,6 +56,7 @@ message RelayListItem {
message ListRelaysRequest {
// 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).
@ -92,6 +93,7 @@ message RemoveRelayDeviceRequest {
message ListRelayDevicesRequest {
// 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;
// Offset in the result-set (for pagination).

View File

@ -200,6 +200,7 @@ message DeleteTenantRequest {
message ListTenantsRequest {
// 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;
// Offset in the result-set (for pagination).
@ -313,6 +314,7 @@ message ListTenantUsersRequest {
string tenant_id = 1;
// 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;
// Offset in the result-set (for pagination).

View File

@ -161,6 +161,7 @@ message DeleteUserRequest {
message ListUsersRequest {
// 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;
// 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 self.rx_info.is_none() {
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
self.rx_info = Some(UplinkRxInfo {
gateway_id: hex::encode(&rx_info.gateway_id),
uplink_id: rng.gen::<u32>(),
uplink_id: rng.random::<u32>(),
gw_time: rx_info.time,
ns_time: None,
time_since_gps_epoch: rx_info.time_since_gps_epoch,

View File

@ -1,6 +1,6 @@
[package]
name = "backend"
version = "4.11.1"
version = "4.12.0-test.2"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018"
publish = false
@ -12,14 +12,14 @@
anyhow = "1.0"
tracing = "0.1"
hex = "0.4"
rand = "0.8"
rand = "0.9"
aes-kw = "0.2"
reqwest = { version = "0.12", features = [
"json",
"rustls-tls",
], default-features = false }
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 = [
"json",
] }

View File

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

View File

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

View File

@ -47,12 +47,12 @@ dist:
test:
cargo fmt --check
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:
cargo fmt --check
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:
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 async_trait::async_trait;
use rand::seq::SliceRandom;
use rand::seq::IndexedRandom;
use super::{Handler, Request, Response};
use crate::region;
@ -134,7 +134,7 @@ impl Handler for Algorithm {
// In case there are multiple with the same coding-rate, we take
// a random one.
resp.dr = drs
.choose(&mut rand::thread_rng())
.choose(&mut rand::rng())
.cloned()
.ok_or_else(|| anyhow!("Random returned None"))?;
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)
}
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)]

View File

@ -12,7 +12,8 @@ use super::error::Error;
use crate::api::auth::AuthID;
use crate::helpers::errors::PrintFullError;
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};
@ -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)]
pub mod test {
use super::*;
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 std::str::FromStr;
@ -4619,4 +4836,298 @@ pub mod test {
];
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 {
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
.validate(
@ -270,17 +275,25 @@ impl DeviceService for Device {
let filters = device::Filters {
application_id: Some(app_id),
multicast_group_id: mg_id,
device_profile_id: dp_id,
search: if req.search.is_empty() {
None
} else {
Some(req.search.to_string())
},
tags: req.tags.clone(),
};
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)
.await
.map_err(|e| e.status())?;
let items = device::list(
req.limit as i64,
req.offset as i64,
&filters,
req.order_by().from_proto(),
req.order_by_desc,
)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListDevicesResponse {
total_count: count as u32,
@ -309,6 +322,7 @@ impl DeviceService for Device {
}),
false => None,
},
tags: d.tags.into_hashmap(),
})
.collect(),
});
@ -340,11 +354,8 @@ impl DeviceService for Device {
let dk = device_keys::DeviceKeys {
dev_eui,
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
app_key: if !req_dk.app_key.is_empty() {
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?
} else {
AES128Key::null()
},
app_key: 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())?,
..Default::default()
};
@ -378,6 +389,7 @@ impl DeviceService for Device {
dev_eui: dk.dev_eui.to_string(),
nwk_key: dk.nwk_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)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)),
@ -414,11 +426,8 @@ impl DeviceService for Device {
dev_nonces: dk.dev_nonces,
join_nonce: dk.join_nonce,
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
app_key: if !req_dk.app_key.is_empty() {
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?
} else {
AES128Key::null()
},
app_key: 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())?,
..Default::default()
};
let _ = device_keys::update(dk).await.map_err(|e| e.status())?;
@ -1362,6 +1371,9 @@ pub mod test {
multicast_group_id: "".into(),
limit: 10,
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();
@ -1376,6 +1388,7 @@ pub mod test {
dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(),
app_key: "02020304050607080202030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}),
},
);
@ -1394,6 +1407,7 @@ pub mod test {
dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(),
app_key: "02020304050607080202030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}),
get_keys_resp.get_ref().device_keys
);
@ -1406,6 +1420,7 @@ pub mod test {
dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(),
app_key: "03020304050607080302030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}),
},
);
@ -1424,6 +1439,7 @@ pub mod test {
dev_eui: "0102030405060708".into(),
nwk_key: "01020304050607080102030405060708".into(),
app_key: "03020304050607080302030405060708".into(),
gen_app_key: "03020304050607080202030405060708".into(),
}),
get_keys_resp.get_ref().device_keys
);

View File

@ -60,15 +60,6 @@ impl DeviceProfileService for DeviceProfile {
supports_otaa: req_dp.supports_otaa,
supports_class_b: req_dp.supports_class_b,
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()),
measurements: fields::Measurements::new(
req_dp
@ -88,32 +79,77 @@ impl DeviceProfileService for DeviceProfile {
auto_detect_measurements: req_dp.auto_detect_measurements,
region_config_id: (!req_dp.region_config_id.is_empty())
.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,
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()
};
@ -145,6 +181,10 @@ impl DeviceProfileService for DeviceProfile {
.await?;
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 {
device_profile: Some(api::DeviceProfile {
@ -164,15 +204,15 @@ impl DeviceProfileService for DeviceProfile {
supports_otaa: dp.supports_otaa,
supports_class_b: dp.supports_class_b,
supports_class_c: dp.supports_class_c,
class_b_timeout: dp.class_b_timeout as u32,
class_b_ping_slot_nb_k: dp.class_b_ping_slot_nb_k as u32,
class_b_ping_slot_dr: dp.class_b_ping_slot_dr as u32,
class_b_ping_slot_freq: dp.class_b_ping_slot_freq as u32,
class_c_timeout: dp.class_c_timeout as u32,
abp_rx1_delay: dp.abp_rx1_delay as u32,
abp_rx1_dr_offset: dp.abp_rx1_dr_offset as u32,
abp_rx2_dr: dp.abp_rx2_dr as u32,
abp_rx2_freq: dp.abp_rx2_freq as u32,
class_b_timeout: class_b_params.timeout as u32,
class_b_ping_slot_nb_k: class_b_params.ping_slot_nb_k as u32,
class_b_ping_slot_dr: class_b_params.ping_slot_dr as u32,
class_b_ping_slot_freq: class_b_params.ping_slot_freq as u32,
class_c_timeout: class_c_params.timeout as u32,
abp_rx1_delay: abp_params.rx1_delay as u32,
abp_rx1_dr_offset: abp_params.rx1_dr_offset as u32,
abp_rx2_dr: abp_params.rx2_dr as u32,
abp_rx2_freq: abp_params.rx2_freq as u32,
tags: dp.tags.into_hashmap(),
measurements: dp
.measurements
@ -190,32 +230,46 @@ impl DeviceProfileService for DeviceProfile {
.collect(),
auto_detect_measurements: dp.auto_detect_measurements,
region_config_id: dp.region_config_id.clone().unwrap_or_default(),
is_relay: dp.is_relay,
is_relay_ed: dp.is_relay_ed,
relay_ed_relay_only: dp.relay_ed_relay_only,
relay_enabled: dp.relay_enabled,
relay_cad_periodicity: dp.relay_cad_periodicity as i32,
relay_default_channel_index: dp.relay_default_channel_index as u32,
relay_second_channel_freq: dp.relay_second_channel_freq as u32,
relay_second_channel_dr: dp.relay_second_channel_dr as u32,
relay_second_channel_ack_offset: dp.relay_second_channel_ack_offset as i32,
relay_ed_activation_mode: dp.relay_ed_activation_mode.to_proto().into(),
relay_ed_smart_enable_level: dp.relay_ed_smart_enable_level as u32,
relay_ed_back_off: dp.relay_ed_back_off as u32,
relay_ed_uplink_limit_bucket_size: dp.relay_ed_uplink_limit_bucket_size as u32,
relay_ed_uplink_limit_reload_rate: dp.relay_ed_uplink_limit_reload_rate as u32,
relay_join_req_limit_reload_rate: dp.relay_join_req_limit_reload_rate as u32,
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
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 as i32,
relay_default_channel_index: relay_params.default_channel_index as u32,
relay_second_channel_freq: relay_params.second_channel_freq as u32,
relay_second_channel_dr: relay_params.second_channel_dr as u32,
relay_second_channel_ack_offset: relay_params.second_channel_ack_offset as i32,
relay_ed_activation_mode: relay_params.ed_activation_mode.to_proto().into(),
relay_ed_smart_enable_level: relay_params.ed_smart_enable_level as u32,
relay_ed_back_off: relay_params.ed_back_off as u32,
relay_ed_uplink_limit_bucket_size: relay_params.ed_uplink_limit_bucket_size as u32,
relay_ed_uplink_limit_reload_rate: relay_params.ed_uplink_limit_reload_rate as u32,
relay_join_req_limit_reload_rate: relay_params.relay_join_req_limit_reload_rate
as u32,
relay_overall_limit_reload_rate: dp.relay_overall_limit_reload_rate as u32,
relay_join_req_limit_bucket_size: dp.relay_join_req_limit_bucket_size as u32,
relay_notify_limit_bucket_size: dp.relay_notify_limit_bucket_size as u32,
relay_global_uplink_limit_bucket_size: dp.relay_global_uplink_limit_bucket_size
relay_notify_limit_reload_rate: relay_params.relay_notify_limit_reload_rate as u32,
relay_global_uplink_limit_reload_rate: relay_params
.relay_global_uplink_limit_reload_rate
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,
relay_overall_limit_bucket_size: dp.relay_overall_limit_bucket_size as u32,
allow_roaming: dp.allow_roaming,
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)),
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_class_b: req_dp.supports_class_b,
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()),
measurements: fields::Measurements::new(
req_dp
@ -290,32 +335,80 @@ impl DeviceProfileService for DeviceProfile {
auto_detect_measurements: req_dp.auto_detect_measurements,
region_config_id: (!req_dp.region_config_id.is_empty())
.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,
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()
})
.await
@ -506,6 +599,14 @@ pub mod test {
mac_version: common::MacVersion::Lorawan103.into(),
reg_params_revision: common::RegParamsRevision::A.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()
}),
get_resp.get_ref().device_profile
@ -546,6 +647,7 @@ pub mod test {
mac_version: common::MacVersion::Lorawan103.into(),
reg_params_revision: common::RegParamsRevision::A.into(),
adr_algorithm_id: "default".into(),
app_layer_params: Some(api::AppLayerParams::default()),
..Default::default()
}),
get_resp.get_ref().device_profile

View File

@ -47,6 +47,17 @@ impl ToStatus for storage::error::Error {
storage::error::Error::ProstDecode(_) => {
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,9 +238,15 @@ impl GatewayService for Gateway {
};
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)
.await
.map_err(|e| e.status())?;
let result = gateway::list(
req.limit as i64,
req.offset as i64,
&filters,
req.order_by().from_proto(),
req.order_by_desc,
)
.await
.map_err(|e| e.status())?;
let mut resp = Response::new(api::ListGatewaysResponse {
total_count: count as u32,

View File

@ -1,11 +1,14 @@
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 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> {
#[allow(clippy::wrong_self_convention)]
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 {
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_template_service_server::DeviceProfileTemplateServiceServer;
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::internal_service_server::InternalServiceServer;
use chirpstack_api::api::multicast_group_service_server::MulticastGroupServiceServer;
@ -53,6 +54,7 @@ pub mod device;
pub mod device_profile;
pub mod device_profile_template;
pub mod error;
pub mod fuota;
pub mod gateway;
mod grpc_multiplex;
pub mod helpers;
@ -175,6 +177,10 @@ pub async fn setup() -> Result<()> {
.add_service(RelayServiceServer::with_interceptor(
relay::Relay::new(validator::RequestValidator::new()),
auth::auth_interceptor,
))
.add_service(FuotaServiceServer::with_interceptor(
fuota::Fuota::new(validator::RequestValidator::new()),
auth::auth_interceptor,
));
let backend_handle = tokio::spawn(backend::setup());

View File

@ -28,6 +28,12 @@ struct ClerkUserinfo {
pub user_id: String,
}
#[derive(Deserialize)]
struct YandexUserinfo {
pub default_email: String,
pub id: String,
}
#[derive(Deserialize)]
pub struct CallbackArgs {
pub code: String,
@ -129,9 +135,11 @@ pub async fn get_user(code: &str, state: &str) -> Result<User> {
let conf = config::get();
let provider = conf.user_authentication.oauth2.provider.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() {
"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)),
}
}
@ -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(
token: &oauth2::CsrfToken,
verifier: &oauth2::PkceCodeVerifier,

View File

@ -182,7 +182,7 @@ pub mod test {
use super::*;
use crate::api::auth::validator::RequestValidator;
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;
#[tokio::test]
@ -229,7 +229,10 @@ pub mod test {
let dp_relay = device_profile::create(device_profile::DeviceProfile {
name: "test-dp".into(),
tenant_id: t.id,
is_relay: true,
relay_params: Some(fields::RelayParams {
is_relay: true,
..Default::default()
}),
..Default::default()
})
.await

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;
pub fn run() {
#[allow(clippy::useless_vec)]
let template = vec![
r#"
let template = r#"
# Logging configuration
[logging]
@ -21,10 +19,11 @@ r#"
# Log as JSON.
json={{ logging.json }}
"#,
#[cfg(feature = "postgres")]
r#"
# PostgreSQL configuration.
#
# Note: this option is only available to ChirpStack with PostgreSQL support (default).
[postgresql]
# PostgreSQL DSN.
@ -49,16 +48,20 @@ r#"
# the server-certificate is not signed by a CA in the platform certificate
# store.
ca_cert="{{ postgresql.ca_cert }}"
"#,
#[cfg(feature = "sqlite")]
r#"
# SQLite configuration.
#
# Note: this option is only available to ChirpStack with SQLite support.
[sqlite]
# 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 }}"
# Max open connections.
@ -77,8 +80,8 @@ r#"
"{{this}}",
{{/each}}
]
"#,
r#"
# Redis configuration.
[redis]
@ -990,7 +993,7 @@ r#"
# default tileserver_url (OSM). If you configure a different tile-server, you
# might need to update the map_attribution.
map_attribution="{{ui.map_attribution}}"
"#].join("\n");
"#;
let mut reg = Handlebars::new();
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 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<()> {
info!(
@ -21,6 +21,7 @@ pub async fn run() -> Result<()> {
integration::setup().await?;
gateway::backend::setup().await?;
downlink::setup().await;
fuota::setup().await;
api::setup().await?;
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;
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()
);
}
@ -368,7 +368,7 @@ pub mod test {
};
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]

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use std::str::FromStr;
use anyhow::Result;
use rand::seq::SliceRandom;
use rand::seq::IndexedRandom;
use uuid::Uuid;
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).
// If new_items is empty, then choose will return None and we return the first item from
// rx_info.item.
Ok(match new_items.choose(&mut rand::thread_rng()) {
Ok(match new_items.choose(&mut rand::rng()) {
Some(v) => v.clone(),
None => rx_info.items[0].clone(),
})

View File

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

View File

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

View File

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

View File

@ -282,8 +282,12 @@ impl TxAck {
qi.is_pending = true;
if dev.enabled_class == DeviceClass::C {
let timeout =
Utc::now() + Duration::try_seconds(dp.class_c_timeout as i64).unwrap_or_default();
let timeout_sec = dp
.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);
}

View File

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

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())?;
Ok(pkcs8_pem.as_str().to_owned())
} else if pem.contains("EC PRIVATE KEY") {
use elliptic_curve::{
pkcs8::{EncodePrivateKey, LineEnding},
SecretKey,
use sec1::{
der::{Decode, Encode, EncodePem},
pkcs8::{AlgorithmIdentifierRef, PrivateKeyInfo},
EcPrivateKey, LineEnding,
};
// We assume it is a P256 based secret-key, which is the most popular curve.
// Attempting to decode it as P256 is still better than just failing to read it.
let pkey: SecretKey<p256::NistP256> =
SecretKey::from_sec1_pem(pem).context("Read EC SEC1")?;
let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?;
Ok(pkcs8_pem.as_str().to_owned())
// Get a SEC1 ECPrivateKey from the PEM string input
let pem = pem::parse(pem).context("Parse PEM string")?;
let pkey =
EcPrivateKey::from_der(pem.contents()).context("Decode PEM into SEC1 ECPrivateKey")?;
// 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 {
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())?;
Ok(pkcs8_pem.as_str().to_owned())
} else if pem.contains("EC PRIVATE KEY") {
use elliptic_curve::{
pkcs8::{EncodePrivateKey, LineEnding},
SecretKey,
use sec1::{
der::{Decode, Encode, EncodePem},
pkcs8::{AlgorithmIdentifierRef, PrivateKeyInfo},
EcPrivateKey, LineEnding,
};
// We assume it is a P256 based secret-key, which is the most popular curve.
// Attempting to decode it as P256 is still better than just failing to read it.
let pkey: SecretKey<p256::NistP256> =
SecretKey::from_sec1_pem(pem).context("Read EC SEC1")?;
let pkcs8_pem = pkey.to_pkcs8_pem(LineEnding::default())?;
Ok(pkcs8_pem.as_str().to_owned())
// Get a SEC1 ECPrivateKey from the PEM string input
let pem = pem::parse(pem).context("Parse PEM string")?;
let pkey =
EcPrivateKey::from_der(pem.contents()).context("Decode PEM into SEC1 ECPrivateKey")?;
// 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 {
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
// configured.
let client_id = if conf.client_id.is_empty() {
let mut rnd = rand::thread_rng();
let client_id: u64 = rnd.gen();
let mut rnd = rand::rng();
let client_id: u64 = rnd.random();
format!("{:x}", client_id)
} else {
conf.client_id.clone()

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::str::FromStr;
@ -15,7 +15,7 @@ use tracing::info;
use uuid::Uuid;
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};
#[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())
}
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)]
pub mod test {
use super::*;

View File

@ -207,13 +207,25 @@ pub struct DeviceListItem {
pub margin: Option<i32>,
pub external_power_source: bool,
pub battery_level: Option<fields::BigDecimal>,
pub tags: fields::KeyValue,
}
#[derive(Default, Clone)]
pub struct Filters {
pub application_id: Option<Uuid>,
pub multicast_group_id: Option<Uuid>,
pub device_profile_id: Option<Uuid>,
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)]
@ -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)));
}
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 {
#[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?)
}
@ -606,6 +638,8 @@ pub async fn list(
limit: i64,
offset: i64,
filters: &Filters,
order_by: OrderBy,
order_by_desc: bool,
) -> Result<Vec<DeviceListItem>, Error> {
let mut q = device::dsl::device
.inner_join(device_profile::table)
@ -622,6 +656,7 @@ pub async fn list(
device::margin,
device::external_power_source,
device::battery_level,
device::tags,
))
.distinct()
.into_boxed();
@ -630,6 +665,10 @@ pub async fn list(
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 {
#[cfg(feature = "postgres")]
{
@ -648,8 +687,42 @@ pub async fn list(
);
}
q.order_by(device::dsl::name)
.limit(limit)
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),
);
}
}
}
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)
.load(&mut get_async_db_conn().await?)
.await
@ -870,11 +943,14 @@ pub mod test {
use lrwn::AES128Key;
struct FilterTest<'a> {
name: String,
filters: Filters,
devs: Vec<&'a Device>,
count: usize,
limit: i64,
offset: i64,
order: OrderBy,
order_by_desc: bool,
}
pub async fn create_device(
@ -930,70 +1006,175 @@ pub mod test {
let d_get = get(&d.dev_eui).await.unwrap();
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
let tests = vec![
FilterTest {
name: "no filters".into(),
filters: Filters {
application_id: None,
multicast_group_id: None,
search: None,
..Default::default()
},
devs: vec![&d],
count: 1,
devs: vec![&d1, &d2, &d3],
count: 3,
limit: 10,
offset: 0,
order: OrderBy::Name,
order_by_desc: false,
},
FilterTest {
name: "filter by search - no match".into(),
filters: Filters {
application_id: None,
multicast_group_id: None,
search: Some("uup".into()),
search: Some("tee".into()),
..Default::default()
},
devs: vec![],
count: 0,
limit: 10,
offset: 0,
order: OrderBy::Name,
order_by_desc: false,
},
FilterTest {
name: "filter by search - match".into(),
filters: Filters {
application_id: None,
multicast_group_id: None,
search: Some("upd".into()),
search: Some("tes".into()),
..Default::default()
},
devs: vec![&d],
devs: vec![&d1],
count: 1,
limit: 10,
offset: 0,
order: OrderBy::Name,
order_by_desc: false,
},
FilterTest {
name: "filter by application_id".into(),
filters: Filters {
application_id: Some(d.application_id.into()),
application_id: Some(d1.application_id.into()),
multicast_group_id: None,
search: None,
..Default::default()
},
devs: vec![&d],
count: 1,
devs: vec![&d1, &d2, &d3],
count: 3,
limit: 10,
offset: 0,
order: OrderBy::Name,
order_by_desc: false,
},
FilterTest {
name: "filter by application_id - no match".into(),
filters: Filters {
application_id: Some(Uuid::new_v4()),
multicast_group_id: None,
search: None,
..Default::default()
},
devs: vec![],
count: 0,
limit: 10,
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 {
println!(" > {}", tst.name);
let count = get_count(&tst.filters).await.unwrap() as usize;
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!(
tst.devs
.iter()
@ -1005,10 +1186,6 @@ pub mod test {
.collect::<String>()
);
}
// delete
delete(&d.dev_eui).await.unwrap();
assert!(delete(&d.dev_eui).await.is_err());
}
#[tokio::test]

View File

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

View File

@ -34,15 +34,6 @@ pub struct DeviceProfile {
pub supports_otaa: bool,
pub supports_class_b: bool,
pub supports_class_c: bool,
pub class_b_timeout: i32,
pub class_b_ping_slot_nb_k: i32,
pub class_b_ping_slot_dr: i16,
pub class_b_ping_slot_freq: i64,
pub class_c_timeout: i32,
pub abp_rx1_delay: i16,
pub abp_rx1_dr_offset: i16,
pub abp_rx2_dr: i16,
pub abp_rx2_freq: i64,
pub tags: fields::KeyValue,
pub payload_codec_script: String,
pub flush_queue_on_activate: bool,
@ -50,30 +41,13 @@ pub struct DeviceProfile {
pub measurements: fields::Measurements,
pub auto_detect_measurements: bool,
pub region_config_id: Option<String>,
pub is_relay: bool,
pub is_relay_ed: bool,
pub relay_ed_relay_only: bool,
pub relay_enabled: bool,
pub relay_cad_periodicity: i16,
pub relay_default_channel_index: i16,
pub relay_second_channel_freq: i64,
pub relay_second_channel_dr: i16,
pub relay_second_channel_ack_offset: i16,
pub relay_ed_activation_mode: lrwn::RelayModeActivation,
pub relay_ed_smart_enable_level: i16,
pub relay_ed_back_off: i16,
pub relay_ed_uplink_limit_bucket_size: i16,
pub relay_ed_uplink_limit_reload_rate: i16,
pub relay_join_req_limit_reload_rate: i16,
pub relay_notify_limit_reload_rate: i16,
pub relay_global_uplink_limit_reload_rate: i16,
pub relay_overall_limit_reload_rate: i16,
pub relay_join_req_limit_bucket_size: i16,
pub relay_notify_limit_bucket_size: i16,
pub relay_global_uplink_limit_bucket_size: i16,
pub relay_overall_limit_bucket_size: i16,
pub allow_roaming: bool,
pub rx1_delay: i16,
pub abp_params: Option<fields::AbpParams>,
pub class_b_params: Option<fields::ClassBParams>,
pub class_c_params: Option<fields::ClassCParams>,
pub relay_params: Option<fields::RelayParams>,
pub app_layer_params: fields::AppLayerParams,
}
impl DeviceProfile {
@ -113,43 +87,17 @@ impl Default for DeviceProfile {
supports_otaa: false,
supports_class_b: false,
supports_class_c: false,
class_b_timeout: 0,
class_b_ping_slot_nb_k: 0,
class_b_ping_slot_dr: 0,
class_b_ping_slot_freq: 0,
class_c_timeout: 0,
abp_rx1_delay: 0,
abp_rx1_dr_offset: 0,
abp_rx2_dr: 0,
abp_rx2_freq: 0,
tags: fields::KeyValue::new(HashMap::new()),
measurements: fields::Measurements::new(HashMap::new()),
auto_detect_measurements: false,
region_config_id: None,
is_relay: false,
is_relay_ed: false,
relay_ed_relay_only: false,
relay_enabled: false,
relay_cad_periodicity: 0,
relay_default_channel_index: 0,
relay_second_channel_freq: 0,
relay_second_channel_dr: 0,
relay_second_channel_ack_offset: 0,
relay_ed_activation_mode: lrwn::RelayModeActivation::DisableRelayMode,
relay_ed_smart_enable_level: 0,
relay_ed_back_off: 0,
relay_ed_uplink_limit_bucket_size: 0,
relay_ed_uplink_limit_reload_rate: 0,
relay_join_req_limit_reload_rate: 0,
relay_notify_limit_reload_rate: 0,
relay_global_uplink_limit_reload_rate: 0,
relay_overall_limit_reload_rate: 0,
relay_join_req_limit_bucket_size: 0,
relay_notify_limit_bucket_size: 0,
relay_global_uplink_limit_bucket_size: 0,
relay_overall_limit_bucket_size: 0,
allow_roaming: false,
rx1_delay: 0,
abp_params: None,
class_b_params: None,
class_c_params: None,
relay_params: None,
app_layer_params: fields::AppLayerParams::default(),
}
}
}
@ -157,16 +105,21 @@ impl Default for DeviceProfile {
impl DeviceProfile {
pub fn reset_session_to_boot_params(&self, ds: &mut internal::DeviceSession) {
ds.mac_version = self.mac_version.to_proto().into();
ds.class_b_ping_slot_dr = self.class_b_ping_slot_dr as u32;
ds.class_b_ping_slot_freq = self.class_b_ping_slot_freq as u32;
ds.class_b_ping_slot_nb = 1 << self.class_b_ping_slot_nb_k as u32;
ds.nb_trans = 1;
if self.is_relay_ed {
ds.relay = Some(internal::Relay {
ed_relay_only: self.relay_ed_relay_only,
..Default::default()
});
if let Some(class_b_params) = &self.class_b_params {
ds.class_b_ping_slot_dr = class_b_params.ping_slot_dr as u32;
ds.class_b_ping_slot_freq = class_b_params.ping_slot_freq as u32;
ds.class_b_ping_slot_nb = 1 << class_b_params.ping_slot_nb_k as u32;
}
if let Some(relay_params) = &self.relay_params {
if relay_params.is_relay_ed {
ds.relay = Some(internal::Relay {
ed_relay_only: relay_params.ed_relay_only,
..Default::default()
});
}
}
if !self.supports_otaa {
@ -174,11 +127,14 @@ impl DeviceProfile {
ds.min_supported_tx_power_index = 0;
ds.max_supported_tx_power_index = 0;
ds.extra_uplink_channels = HashMap::new();
ds.rx1_delay = self.abp_rx1_delay as u32;
ds.rx1_dr_offset = self.abp_rx1_dr_offset as u32;
ds.rx2_dr = self.abp_rx2_dr as u32;
ds.rx2_frequency = self.abp_rx2_freq as u32;
ds.enabled_uplink_channel_indices = Vec::new();
if let Some(abp_params) = &self.abp_params {
ds.rx1_delay = abp_params.rx1_delay as u32;
ds.rx1_dr_offset = abp_params.rx1_dr_offset as u32;
ds.rx2_dr = abp_params.rx2_dr as u32;
ds.rx2_frequency = abp_params.rx2_freq as u32;
}
}
}
}
@ -244,49 +200,17 @@ pub async fn update(dp: DeviceProfile) -> Result<DeviceProfile, Error> {
device_profile::supports_otaa.eq(&dp.supports_otaa),
device_profile::supports_class_b.eq(&dp.supports_class_b),
device_profile::supports_class_c.eq(&dp.supports_class_c),
device_profile::class_b_timeout.eq(&dp.class_b_timeout),
device_profile::class_b_ping_slot_nb_k.eq(&dp.class_b_ping_slot_nb_k),
device_profile::class_b_ping_slot_dr.eq(&dp.class_b_ping_slot_dr),
device_profile::class_b_ping_slot_freq.eq(&dp.class_b_ping_slot_freq),
device_profile::class_c_timeout.eq(&dp.class_c_timeout),
device_profile::abp_rx1_delay.eq(&dp.abp_rx1_delay),
device_profile::abp_rx1_dr_offset.eq(&dp.abp_rx1_dr_offset),
device_profile::abp_rx2_dr.eq(&dp.abp_rx2_dr),
device_profile::abp_rx2_freq.eq(&dp.abp_rx2_freq),
device_profile::tags.eq(&dp.tags),
device_profile::measurements.eq(&dp.measurements),
device_profile::auto_detect_measurements.eq(&dp.auto_detect_measurements),
device_profile::region_config_id.eq(&dp.region_config_id),
device_profile::is_relay.eq(&dp.is_relay),
device_profile::is_relay_ed.eq(&dp.is_relay_ed),
device_profile::relay_ed_relay_only.eq(&dp.relay_ed_relay_only),
device_profile::relay_enabled.eq(&dp.relay_enabled),
device_profile::relay_cad_periodicity.eq(&dp.relay_cad_periodicity),
device_profile::relay_default_channel_index.eq(&dp.relay_default_channel_index),
device_profile::relay_second_channel_freq.eq(&dp.relay_second_channel_freq),
device_profile::relay_second_channel_dr.eq(&dp.relay_second_channel_dr),
device_profile::relay_second_channel_ack_offset.eq(&dp.relay_second_channel_ack_offset),
device_profile::relay_ed_activation_mode.eq(&dp.relay_ed_activation_mode),
device_profile::relay_ed_smart_enable_level.eq(&dp.relay_ed_smart_enable_level),
device_profile::relay_ed_back_off.eq(&dp.relay_ed_back_off),
device_profile::relay_ed_uplink_limit_bucket_size
.eq(&dp.relay_ed_uplink_limit_bucket_size),
device_profile::relay_ed_uplink_limit_reload_rate
.eq(&dp.relay_ed_uplink_limit_reload_rate),
device_profile::relay_join_req_limit_reload_rate
.eq(&dp.relay_join_req_limit_reload_rate),
device_profile::relay_notify_limit_reload_rate.eq(&dp.relay_notify_limit_reload_rate),
device_profile::relay_global_uplink_limit_reload_rate
.eq(&dp.relay_global_uplink_limit_reload_rate),
device_profile::relay_overall_limit_reload_rate.eq(&dp.relay_overall_limit_reload_rate),
device_profile::relay_join_req_limit_bucket_size
.eq(&dp.relay_join_req_limit_bucket_size),
device_profile::relay_notify_limit_bucket_size.eq(&dp.relay_notify_limit_bucket_size),
device_profile::relay_global_uplink_limit_bucket_size
.eq(&dp.relay_global_uplink_limit_bucket_size),
device_profile::relay_overall_limit_bucket_size.eq(&dp.relay_overall_limit_bucket_size),
device_profile::allow_roaming.eq(&dp.allow_roaming),
device_profile::rx1_delay.eq(&dp.rx1_delay),
device_profile::abp_params.eq(&dp.abp_params),
device_profile::class_b_params.eq(&dp.class_b_params),
device_profile::class_c_params.eq(&dp.class_c_params),
device_profile::relay_params.eq(&dp.relay_params),
device_profile::app_layer_params.eq(&dp.app_layer_params),
))
.get_result(&mut get_async_db_conn().await?)
.await

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