diff --git a/api/ttn/lorawan/v3/api.md b/api/ttn/lorawan/v3/api.md index 568fcc96e0..ca57f6f0c7 100644 --- a/api/ttn/lorawan/v3/api.md +++ b/api/ttn/lorawan/v3/api.md @@ -4048,6 +4048,7 @@ SDKs are responsible for combining (if desired) the three. | `last_seen_at` | [`google.protobuf.Timestamp`](#google.protobuf.Timestamp) | | Timestamp when a device uplink has been last observed. This field is set by the Application Server and stored in the Identity Server. | | `serial_number` | [`string`](#string) | | | | `lora_alliance_profile_ids` | [`LoRaAllianceProfileIdentifiers`](#ttn.lorawan.v3.LoRaAllianceProfileIdentifiers) | | | +| `mac_settings_profile_ids` | [`MACSettingsProfileIdentifiers`](#ttn.lorawan.v3.MACSettingsProfileIdentifiers) | | MAC settings profile identifiers. | #### Field Rules diff --git a/api/ttn/lorawan/v3/api.swagger.json b/api/ttn/lorawan/v3/api.swagger.json index 626f161912..5ce3c0b0f2 100644 --- a/api/ttn/lorawan/v3/api.swagger.json +++ b/api/ttn/lorawan/v3/api.swagger.json @@ -21953,6 +21953,10 @@ }, "lora_alliance_profile_ids": { "$ref": "#/definitions/v3LoRaAllianceProfileIdentifiers" + }, + "mac_settings_profile_ids": { + "$ref": "#/definitions/v3MACSettingsProfileIdentifiers", + "description": "MAC settings profile identifiers." } }, "description": "Defines an End Device registration and its state on the network.\nThe persistence of the EndDevice is divided between the Network Server, Application Server and Join Server.\nSDKs are responsible for combining (if desired) the three." @@ -23388,6 +23392,10 @@ }, "lora_alliance_profile_ids": { "$ref": "#/definitions/v3LoRaAllianceProfileIdentifiers" + }, + "mac_settings_profile_ids": { + "$ref": "#/definitions/v3MACSettingsProfileIdentifiers", + "description": "MAC settings profile identifiers." } }, "description": "Defines an End Device registration and its state on the network.\nThe persistence of the EndDevice is divided between the Network Server, Application Server and Join Server.\nSDKs are responsible for combining (if desired) the three." @@ -23868,6 +23876,10 @@ }, "lora_alliance_profile_ids": { "$ref": "#/definitions/v3LoRaAllianceProfileIdentifiers" + }, + "mac_settings_profile_ids": { + "$ref": "#/definitions/v3MACSettingsProfileIdentifiers", + "description": "MAC settings profile identifiers." } }, "description": "Defines an End Device registration and its state on the network.\nThe persistence of the EndDevice is divided between the Network Server, Application Server and Join Server.\nSDKs are responsible for combining (if desired) the three." @@ -24171,6 +24183,10 @@ }, "lora_alliance_profile_ids": { "$ref": "#/definitions/v3LoRaAllianceProfileIdentifiers" + }, + "mac_settings_profile_ids": { + "$ref": "#/definitions/v3MACSettingsProfileIdentifiers", + "description": "MAC settings profile identifiers." } }, "description": "Defines an End Device registration and its state on the network.\nThe persistence of the EndDevice is divided between the Network Server, Application Server and Join Server.\nSDKs are responsible for combining (if desired) the three." @@ -25862,6 +25878,10 @@ }, "lora_alliance_profile_ids": { "$ref": "#/definitions/v3LoRaAllianceProfileIdentifiers" + }, + "mac_settings_profile_ids": { + "$ref": "#/definitions/v3MACSettingsProfileIdentifiers", + "description": "MAC settings profile identifiers." } }, "description": "Defines an End Device registration and its state on the network.\nThe persistence of the EndDevice is divided between the Network Server, Application Server and Join Server.\nSDKs are responsible for combining (if desired) the three." @@ -27891,6 +27911,10 @@ }, "lora_alliance_profile_ids": { "$ref": "#/definitions/v3LoRaAllianceProfileIdentifiers" + }, + "mac_settings_profile_ids": { + "$ref": "#/definitions/v3MACSettingsProfileIdentifiers", + "description": "MAC settings profile identifiers." } }, "description": "Defines an End Device registration and its state on the network.\nThe persistence of the EndDevice is divided between the Network Server, Application Server and Join Server.\nSDKs are responsible for combining (if desired) the three." diff --git a/api/ttn/lorawan/v3/end_device.proto b/api/ttn/lorawan/v3/end_device.proto index 143f2c7887..f253e61d92 100644 --- a/api/ttn/lorawan/v3/end_device.proto +++ b/api/ttn/lorawan/v3/end_device.proto @@ -1208,7 +1208,10 @@ message EndDevice { LoRaAllianceProfileIdentifiers lora_alliance_profile_ids = 56; - // next: 57; + // MAC settings profile identifiers. + MACSettingsProfileIdentifiers mac_settings_profile_ids = 57; + + // next: 58; } message EndDevices { diff --git a/config/messages.json b/config/messages.json index c0bdebb992..78287fa85b 100644 --- a/config/messages.json +++ b/config/messages.json @@ -8258,7 +8258,7 @@ }, "description": { "package": "pkg/networkserver", - "file": "grpc_deviceregistry.go" + "file": "device_state.go" } }, "error:pkg/networkserver:field_not_zero": { @@ -8267,7 +8267,7 @@ }, "description": { "package": "pkg/networkserver", - "file": "grpc_deviceregistry.go" + "file": "device_state.go" } }, "error:pkg/networkserver:field_value": { diff --git a/pkg/networkserver/device_state.go b/pkg/networkserver/device_state.go new file mode 100644 index 0000000000..e968c9f60e --- /dev/null +++ b/pkg/networkserver/device_state.go @@ -0,0 +1,303 @@ +// Copyright © 2025 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkserver + +import ( + "context" + "fmt" + "strings" + + "go.thethings.network/lorawan-stack/v3/pkg/errors" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" +) + +type setDeviceState struct { + Device *ttnpb.EndDevice + + paths []string + extraSets []string + extraGets []string + + pathsCache map[string]bool + extraSetsCache map[string]bool + extraGetsCache map[string]bool + + zeroPaths map[string]bool + onGet []func(*ttnpb.EndDevice) error +} + +// hasAnyField caches the result of ttnpb.HasAnyField in the provided cache map +// in order to avoid redundant lookups. +// +// NOTE: If the search paths are not bottom level fields, hasAnyField may have unexpected +// results, as ttnpb.HasAnyField does not consider higher search paths as being part of +// the requested paths - i.e ttnpb.HasAnyField([]string{"a.b"}, "a") == false. +func hasAnyField(fs []string, cache map[string]bool, paths ...string) bool { + for _, p := range paths { + for i := len(p); i > 0; i = strings.LastIndex(p[:i], ".") { + p := p[:i] + v, ok := cache[p] + if !ok { + continue + } + if !v { + continue + } + return true + } + v := ttnpb.HasAnyField(fs, p) + cache[p] = v + if v { + return v + } + } + return false +} + +func (st *setDeviceState) hasPathField(paths ...string) bool { + return hasAnyField(st.paths, st.pathsCache, paths...) +} + +func (st *setDeviceState) HasSetField(paths ...string) bool { + return st.hasPathField(paths...) || hasAnyField(st.extraSets, st.extraSetsCache, paths...) +} + +func (st *setDeviceState) HasGetField(paths ...string) bool { + return st.hasPathField(paths...) || hasAnyField(st.extraGets, st.extraGetsCache, paths...) +} + +func addFields(hasField func(...string) bool, selected []string, cache map[string]bool, paths ...string) []string { + for _, p := range paths { + if hasField(p) { + continue + } + cache[p] = true + selected = append(selected, p) + } + return selected +} + +func (st *setDeviceState) AddSetFields(paths ...string) { + st.extraSets = addFields(st.HasSetField, st.extraSets, st.extraSetsCache, paths...) +} + +func (st *setDeviceState) AddGetFields(paths ...string) { + st.extraGets = addFields(st.HasGetField, st.extraGets, st.extraGetsCache, paths...) +} + +func (st *setDeviceState) SetFields() []string { + return append(st.paths, st.extraSets...) +} + +func (st *setDeviceState) GetFields() []string { + return append(st.paths, st.extraGets...) +} + +// WithField calls f when path is available. +func (st *setDeviceState) WithField(f func(*ttnpb.EndDevice) error, path string) error { + if st.HasSetField(path) { + return f(st.Device) + } + st.AddGetFields(path) + st.onGet = append(st.onGet, func(stored *ttnpb.EndDevice) error { + return f(stored) + }) + return nil +} + +// WithFields calls f when all paths in paths are available. +func (st *setDeviceState) WithFields(f func(map[string]*ttnpb.EndDevice) error, paths ...string) error { + storedPaths := make([]string, 0, len(paths)) + m := make(map[string]*ttnpb.EndDevice, len(paths)) + for _, p := range paths { + if st.HasSetField(p) { + m[p] = st.Device + } else { + storedPaths = append(storedPaths, p) + } + } + if len(storedPaths) == 0 { + return f(m) + } + st.AddGetFields(storedPaths...) + st.onGet = append(st.onGet, func(stored *ttnpb.EndDevice) error { + if stored == nil { + return f(m) + } + for _, p := range storedPaths { + m[p] = stored + } + return f(m) + }) + return nil +} + +// ValidateField ensures that isValid(dev), where dev is the device containing path evaluates to true. +func (st *setDeviceState) ValidateField(isValid func(*ttnpb.EndDevice) bool, path string) error { + return st.WithField(func(dev *ttnpb.EndDevice) error { + if !isValid(dev) { + return newInvalidFieldValueError(path) + } + return nil + }, path) +} + +var errFieldNotZero = errors.DefineInvalidArgument("field_not_zero", "field `{name}` is not zero") + +// ValidateFieldIsZero ensures that path is zero. +func (st *setDeviceState) ValidateFieldIsZero(path string) error { + if st.HasSetField(path) { + if !st.Device.FieldIsZero(path) { + return newInvalidFieldValueError(path).WithCause(errFieldNotZero.WithAttributes("name", path)) + } + return nil + } + v, ok := st.zeroPaths[path] + if !ok { + st.zeroPaths[path] = true + st.AddGetFields(path) + return nil + } + if !v { + panic(fmt.Sprintf("path `%s` requested to be both zero and not zero", path)) + } + return nil +} + +var errFieldIsZero = errors.DefineInvalidArgument("field_is_zero", "field `{name}` is zero") + +// ValidateFieldIsNotZero ensures that path is not zero. +func (st *setDeviceState) ValidateFieldIsNotZero(path string) error { + if st.HasSetField(path) { + if st.Device.FieldIsZero(path) { + return newInvalidFieldValueError(path).WithCause(errFieldIsZero.WithAttributes("name", path)) + } + return nil + } + v, ok := st.zeroPaths[path] + if !ok { + st.zeroPaths[path] = false + st.AddGetFields(path) + return nil + } + if v { + panic(fmt.Sprintf("path `%s` requested to be both zero and not zero", path)) + } + return nil +} + +// ValidateFieldsAreZero ensures that each p in paths is zero. +func (st *setDeviceState) ValidateFieldsAreZero(paths ...string) error { + for _, p := range paths { + if err := st.ValidateFieldIsZero(p); err != nil { + return err + } + } + return nil +} + +// ValidateFieldsAreNotZero ensures none of p in paths is zero. +func (st *setDeviceState) ValidateFieldsAreNotZero(paths ...string) error { + for _, p := range paths { + if err := st.ValidateFieldIsNotZero(p); err != nil { + return err + } + } + return nil +} + +// The ValidateFields calls isValid with a map path -> *ttnpb.EndDevice, where the value stored under the key +// is either a pointer to stored device or to device being set in request, depending on the request fieldmask. +// The isValid is only executed once all fields are present. That means that if request sets all fields in paths +// The isValid is executed immediately, otherwise it is called later (after device fetch) by SetFunc. +func (st *setDeviceState) ValidateFields( + isValid func(map[string]*ttnpb.EndDevice) (bool, string), + paths ...string, +) error { + return st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + ok, p := isValid(m) + if !ok { + return newInvalidFieldValueError(p) + } + return nil + }, paths...) +} + +// ValidateSetField validates the field iff path is being set in request. +func (st *setDeviceState) ValidateSetField(isValid func() bool, path string) error { + if !st.HasSetField(path) { + return nil + } + if !isValid() { + return newInvalidFieldValueError(path) + } + return nil +} + +// ValidateSetField is like ValidateSetField, but allows the validator callback to return an error +// and propagates it to the caller as the cause. +func (st *setDeviceState) ValidateSetFieldWithCause(isValid func() error, path string) error { + if !st.HasSetField(path) { + return nil + } + if err := isValid(); err != nil { + return newInvalidFieldValueError(path).WithCause(err) + } + return nil +} + +// ValidateSetFields validates the fields iff at least one of p in paths is being set in request. +func (st *setDeviceState) ValidateSetFields( + isValid func(map[string]*ttnpb.EndDevice) (bool, string), + paths ...string, +) error { + if !st.HasSetField(paths...) { + return nil + } + return st.ValidateFields(isValid, paths...) +} + +// SetFunc is the function meant to be passed to SetByID. +func (st *setDeviceState) SetFunc(f func(context.Context, *ttnpb.EndDevice) error) func(context.Context, *ttnpb.EndDevice) (*ttnpb.EndDevice, []string, error) { // nolint: lll + return func(ctx context.Context, stored *ttnpb.EndDevice) (*ttnpb.EndDevice, []string, error) { + for p, shouldBeZero := range st.zeroPaths { + if stored.FieldIsZero(p) != shouldBeZero { + return nil, nil, newInvalidFieldValueError(p) + } + } + for _, g := range st.onGet { + if err := g(stored); err != nil { + return nil, nil, err + } + } + if err := f(ctx, stored); err != nil { + return nil, nil, err + } + return st.Device, st.SetFields(), nil + } +} + +func newSetDeviceState(dev *ttnpb.EndDevice, paths ...string) *setDeviceState { + return &setDeviceState{ + Device: dev, + paths: paths, + + pathsCache: make(map[string]bool), + extraSetsCache: make(map[string]bool), + extraGetsCache: make(map[string]bool), + + zeroPaths: make(map[string]bool), + } +} diff --git a/pkg/networkserver/downlink.go b/pkg/networkserver/downlink.go index 686e6a23c4..e1bcfb6836 100644 --- a/pkg/networkserver/downlink.go +++ b/pkg/networkserver/downlink.go @@ -135,7 +135,7 @@ func (ns *NetworkServer) nextDataDownlinkTaskAt(ctx context.Context, dev *ttnpb. log.FromContext(ctx).WithError(err).Warn("Failed to determine device band") return time.Time{}, nil } - slot, ok := nextDataDownlinkSlot(ctx, dev, phy, ns.defaultMACSettings, earliestAt) + slot, ok := nextDataDownlinkSlot(ctx, dev, phy, ns.defaultMACSettings, earliestAt, &ttnpb.MACSettingsProfile{}) if !ok { return time.Time{}, nil } @@ -598,7 +598,13 @@ func (ns *NetworkServer) generateDataDownlink(ctx context.Context, dev *ttnpb.En ctx = log.NewContext(ctx, logger) if mType == ttnpb.MType_CONFIRMED_DOWN && class != ttnpb.Class_CLASS_A { - confirmedAt, ok := nextConfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, ns.defaultMACSettings) + confirmedAt, ok := nextConfirmedNetworkInitiatedDownlinkAt( + ctx, + dev, + phy, + ns.defaultMACSettings, + &ttnpb.MACSettingsProfile{}, + ) if !ok { return nil, genState, ErrCorruptedMACState. WithCause(ErrNetworkDownlinkSlot) @@ -1931,7 +1937,7 @@ func (ns *NetworkServer) processDownlinkTask(ctx context.Context, consumerID str return nil, nil, nil } - if !mac.DeviceScheduleDownlinks(dev, ns.defaultMACSettings) { + if !mac.DeviceScheduleDownlinks(dev, ns.defaultMACSettings, &ttnpb.MACSettingsProfile{}) { logger.Debug("Downlink slot skipped since scheduling is disabled") return dev, nil, nil } @@ -2154,7 +2160,7 @@ func (ns *NetworkServer) processDownlinkTask(ctx context.Context, consumerID str } var earliestAt time.Time for { - v, ok := nextDataDownlinkSlot(ctx, dev, phy, ns.defaultMACSettings, earliestAt) + v, ok := nextDataDownlinkSlot(ctx, dev, phy, ns.defaultMACSettings, earliestAt, &ttnpb.MACSettingsProfile{}) if !ok { return dev, nil, nil } diff --git a/pkg/networkserver/grpc.go b/pkg/networkserver/grpc.go index 36dba03dd7..e17d52a37d 100644 --- a/pkg/networkserver/grpc.go +++ b/pkg/networkserver/grpc.go @@ -42,45 +42,45 @@ func (ns *NetworkServer) GetDefaultMACSettings(ctx context.Context, req *ttnpb.G if err != nil { return nil, err } - classBTimeout := mac.DeviceClassBTimeout(nil, ns.defaultMACSettings) - classCTimeout := mac.DeviceClassCTimeout(nil, ns.defaultMACSettings) + classBTimeout := mac.DeviceClassBTimeout(nil, ns.defaultMACSettings, nil) + classCTimeout := mac.DeviceClassCTimeout(nil, ns.defaultMACSettings, nil) adrMargin := mac.DeviceADRMargin(nil, ns.defaultMACSettings) statusTimePeriodicity := mac.DeviceStatusTimePeriodicity(nil, ns.defaultMACSettings) statusCountPeriodicity := mac.DeviceStatusCountPeriodicity(nil, ns.defaultMACSettings) settings := &ttnpb.MACSettings{ ClassBTimeout: durationpb.New(classBTimeout), - PingSlotPeriodicity: mac.DeviceDefaultPingSlotPeriodicity(nil, ns.defaultMACSettings), - PingSlotDataRateIndex: mac.DeviceDefaultPingSlotDataRateIndexValue(nil, phy, ns.defaultMACSettings), - PingSlotFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDefaultPingSlotFrequency(nil, phy, ns.defaultMACSettings)}, - BeaconFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDefaultBeaconFrequency(nil, phy, ns.defaultMACSettings)}, + PingSlotPeriodicity: mac.DeviceDefaultPingSlotPeriodicity(nil, ns.defaultMACSettings, nil), + PingSlotDataRateIndex: mac.DeviceDefaultPingSlotDataRateIndexValue(nil, phy, ns.defaultMACSettings, nil), + PingSlotFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDefaultPingSlotFrequency(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll + BeaconFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDefaultBeaconFrequency(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll ClassCTimeout: durationpb.New(classCTimeout), - Rx1Delay: &ttnpb.RxDelayValue{Value: mac.DeviceDefaultRX1Delay(nil, phy, ns.defaultMACSettings)}, - Rx1DataRateOffset: &ttnpb.DataRateOffsetValue{Value: mac.DeviceDefaultRX1DataRateOffset(nil, ns.defaultMACSettings)}, - Rx2DataRateIndex: &ttnpb.DataRateIndexValue{Value: mac.DeviceDefaultRX2DataRateIndex(nil, phy, ns.defaultMACSettings)}, - Rx2Frequency: &ttnpb.FrequencyValue{Value: mac.DeviceDefaultRX2Frequency(nil, phy, ns.defaultMACSettings)}, - MaxDutyCycle: &ttnpb.AggregatedDutyCycleValue{Value: mac.DeviceDefaultMaxDutyCycle(nil, ns.defaultMACSettings)}, - Supports_32BitFCnt: &ttnpb.BoolValue{Value: mac.DeviceSupports32BitFCnt(nil, ns.defaultMACSettings)}, + Rx1Delay: &ttnpb.RxDelayValue{Value: mac.DeviceDefaultRX1Delay(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll + Rx1DataRateOffset: &ttnpb.DataRateOffsetValue{Value: mac.DeviceDefaultRX1DataRateOffset(nil, ns.defaultMACSettings, nil)}, // nolint: lll + Rx2DataRateIndex: &ttnpb.DataRateIndexValue{Value: mac.DeviceDefaultRX2DataRateIndex(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll + Rx2Frequency: &ttnpb.FrequencyValue{Value: mac.DeviceDefaultRX2Frequency(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll + MaxDutyCycle: &ttnpb.AggregatedDutyCycleValue{Value: mac.DeviceDefaultMaxDutyCycle(nil, ns.defaultMACSettings, nil)}, // nolint: lll + Supports_32BitFCnt: &ttnpb.BoolValue{Value: mac.DeviceSupports32BitFCnt(nil, ns.defaultMACSettings, nil)}, // nolint: lll UseAdr: &ttnpb.BoolValue{Value: mac.DeviceUseADR(nil, ns.defaultMACSettings, phy)}, AdrMargin: &wrapperspb.FloatValue{Value: adrMargin}, - ResetsFCnt: &ttnpb.BoolValue{Value: mac.DeviceResetsFCnt(nil, ns.defaultMACSettings)}, + ResetsFCnt: &ttnpb.BoolValue{Value: mac.DeviceResetsFCnt(nil, ns.defaultMACSettings, nil)}, StatusTimePeriodicity: durationpb.New(statusTimePeriodicity), StatusCountPeriodicity: &wrapperspb.UInt32Value{Value: statusCountPeriodicity}, - DesiredRx1Delay: &ttnpb.RxDelayValue{Value: mac.DeviceDesiredRX1Delay(nil, phy, ns.defaultMACSettings)}, - DesiredRx1DataRateOffset: &ttnpb.DataRateOffsetValue{Value: mac.DeviceDesiredRX1DataRateOffset(nil, ns.defaultMACSettings)}, - DesiredRx2DataRateIndex: &ttnpb.DataRateIndexValue{Value: mac.DeviceDesiredRX2DataRateIndex(nil, phy, fp, ns.defaultMACSettings)}, - DesiredRx2Frequency: &ttnpb.FrequencyValue{Value: mac.DeviceDesiredRX2Frequency(nil, phy, fp, ns.defaultMACSettings)}, - DesiredMaxDutyCycle: &ttnpb.AggregatedDutyCycleValue{Value: mac.DeviceDesiredMaxDutyCycle(nil, ns.defaultMACSettings)}, - DesiredAdrAckLimitExponent: mac.DeviceDesiredADRAckLimitExponent(nil, phy, ns.defaultMACSettings), - DesiredAdrAckDelayExponent: mac.DeviceDesiredADRAckDelayExponent(nil, phy, ns.defaultMACSettings), - DesiredPingSlotDataRateIndex: mac.DeviceDesiredPingSlotDataRateIndexValue(nil, phy, fp, ns.defaultMACSettings), - DesiredPingSlotFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDesiredPingSlotFrequency(nil, phy, fp, ns.defaultMACSettings)}, - DesiredBeaconFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDesiredBeaconFrequency(nil, phy, ns.defaultMACSettings)}, - DesiredMaxEirp: &ttnpb.DeviceEIRPValue{Value: lorawan.Float32ToDeviceEIRP(mac.DeviceDesiredMaxEIRP(nil, phy, fp, ns.defaultMACSettings))}, - UplinkDwellTime: mac.DeviceUplinkDwellTime(nil, phy, ns.defaultMACSettings), - DownlinkDwellTime: mac.DeviceDownlinkDwellTime(nil, phy, ns.defaultMACSettings), - ScheduleDownlinks: &ttnpb.BoolValue{Value: mac.DeviceScheduleDownlinks(nil, ns.defaultMACSettings)}, - Relay: mac.DeviceDefaultRelaySettings(nil, ns.defaultMACSettings), - DesiredRelay: mac.DeviceDesiredRelaySettings(nil, ns.defaultMACSettings), + DesiredRx1Delay: &ttnpb.RxDelayValue{Value: mac.DeviceDesiredRX1Delay(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredRx1DataRateOffset: &ttnpb.DataRateOffsetValue{Value: mac.DeviceDesiredRX1DataRateOffset(nil, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredRx2DataRateIndex: &ttnpb.DataRateIndexValue{Value: mac.DeviceDesiredRX2DataRateIndex(nil, phy, fp, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredRx2Frequency: &ttnpb.FrequencyValue{Value: mac.DeviceDesiredRX2Frequency(nil, phy, fp, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredMaxDutyCycle: &ttnpb.AggregatedDutyCycleValue{Value: mac.DeviceDesiredMaxDutyCycle(nil, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredAdrAckLimitExponent: mac.DeviceDesiredADRAckLimitExponent(nil, phy, ns.defaultMACSettings, nil), + DesiredAdrAckDelayExponent: mac.DeviceDesiredADRAckDelayExponent(nil, phy, ns.defaultMACSettings, nil), + DesiredPingSlotDataRateIndex: mac.DeviceDesiredPingSlotDataRateIndexValue(nil, phy, fp, ns.defaultMACSettings, nil), + DesiredPingSlotFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDesiredPingSlotFrequency(nil, phy, fp, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredBeaconFrequency: &ttnpb.ZeroableFrequencyValue{Value: mac.DeviceDesiredBeaconFrequency(nil, phy, ns.defaultMACSettings, nil)}, // nolint: lll + DesiredMaxEirp: &ttnpb.DeviceEIRPValue{Value: lorawan.Float32ToDeviceEIRP(mac.DeviceDesiredMaxEIRP(nil, phy, fp, ns.defaultMACSettings, nil))}, // nolint: lll + UplinkDwellTime: mac.DeviceUplinkDwellTime(nil, phy, ns.defaultMACSettings, nil), + DownlinkDwellTime: mac.DeviceDownlinkDwellTime(nil, phy, ns.defaultMACSettings, nil), + ScheduleDownlinks: &ttnpb.BoolValue{Value: mac.DeviceScheduleDownlinks(nil, ns.defaultMACSettings, nil)}, + Relay: mac.DeviceDefaultRelaySettings(nil, ns.defaultMACSettings, nil), + DesiredRelay: mac.DeviceDesiredRelaySettings(nil, ns.defaultMACSettings, nil), } return settings, nil } diff --git a/pkg/networkserver/grpc_deviceregistry.go b/pkg/networkserver/grpc_deviceregistry.go index d050c43a3f..fd33cab23a 100644 --- a/pkg/networkserver/grpc_deviceregistry.go +++ b/pkg/networkserver/grpc_deviceregistry.go @@ -17,16 +17,13 @@ package networkserver import ( "bytes" "context" - "fmt" "strings" "go.thethings.network/lorawan-stack/v3/pkg/auth/rights" - "go.thethings.network/lorawan-stack/v3/pkg/band" "go.thethings.network/lorawan-stack/v3/pkg/crypto" "go.thethings.network/lorawan-stack/v3/pkg/crypto/cryptoutil" "go.thethings.network/lorawan-stack/v3/pkg/errors" "go.thethings.network/lorawan-stack/v3/pkg/events" - "go.thethings.network/lorawan-stack/v3/pkg/frequencyplans" "go.thethings.network/lorawan-stack/v3/pkg/log" . "go.thethings.network/lorawan-stack/v3/pkg/networkserver/internal" "go.thethings.network/lorawan-stack/v3/pkg/networkserver/internal/time" @@ -34,7 +31,6 @@ import ( "go.thethings.network/lorawan-stack/v3/pkg/specification/macspec" "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" "go.thethings.network/lorawan-stack/v3/pkg/types" - "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -288,279 +284,6 @@ func newInvalidFieldValueError(field string) *errors.Error { return errInvalidFieldValue.WithAttributes("field", field) } -type setDeviceState struct { - Device *ttnpb.EndDevice - - paths []string - extraSets []string - extraGets []string - - pathsCache map[string]bool - extraSetsCache map[string]bool - extraGetsCache map[string]bool - - zeroPaths map[string]bool - onGet []func(*ttnpb.EndDevice) error -} - -// hasAnyField caches the result of ttnpb.HasAnyField in the provided cache map -// in order to avoid redundant lookups. -// -// NOTE: If the search paths are not bottom level fields, hasAnyField may have unexpected -// results, as ttnpb.HasAnyField does not consider higher search paths as being part of -// the requested paths - i.e ttnpb.HasAnyField([]string{"a.b"}, "a") == false. -func hasAnyField(fs []string, cache map[string]bool, paths ...string) bool { - for _, p := range paths { - for i := len(p); i > 0; i = strings.LastIndex(p[:i], ".") { - p := p[:i] - v, ok := cache[p] - if !ok { - continue - } - if !v { - continue - } - return true - } - v := ttnpb.HasAnyField(fs, p) - cache[p] = v - if v { - return v - } - } - return false -} - -func (st *setDeviceState) hasPathField(paths ...string) bool { - return hasAnyField(st.paths, st.pathsCache, paths...) -} - -func (st *setDeviceState) HasSetField(paths ...string) bool { - return st.hasPathField(paths...) || hasAnyField(st.extraSets, st.extraSetsCache, paths...) -} - -func (st *setDeviceState) HasGetField(paths ...string) bool { - return st.hasPathField(paths...) || hasAnyField(st.extraGets, st.extraGetsCache, paths...) -} - -func addFields(hasField func(...string) bool, selected []string, cache map[string]bool, paths ...string) []string { - for _, p := range paths { - if hasField(p) { - continue - } - cache[p] = true - selected = append(selected, p) - } - return selected -} - -func (st *setDeviceState) AddSetFields(paths ...string) { - st.extraSets = addFields(st.HasSetField, st.extraSets, st.extraSetsCache, paths...) -} - -func (st *setDeviceState) AddGetFields(paths ...string) { - st.extraGets = addFields(st.HasGetField, st.extraGets, st.extraGetsCache, paths...) -} - -func (st *setDeviceState) SetFields() []string { - return append(st.paths, st.extraSets...) -} - -func (st *setDeviceState) GetFields() []string { - return append(st.paths, st.extraGets...) -} - -// WithFields calls f when path is available. -func (st *setDeviceState) WithField(f func(*ttnpb.EndDevice) error, path string) error { - if st.HasSetField(path) { - return f(st.Device) - } - st.AddGetFields(path) - st.onGet = append(st.onGet, func(stored *ttnpb.EndDevice) error { - return f(stored) - }) - return nil -} - -// WithFields calls f when all paths in paths are available. -func (st *setDeviceState) WithFields(f func(map[string]*ttnpb.EndDevice) error, paths ...string) error { - storedPaths := make([]string, 0, len(paths)) - m := make(map[string]*ttnpb.EndDevice, len(paths)) - for _, p := range paths { - if st.HasSetField(p) { - m[p] = st.Device - } else { - storedPaths = append(storedPaths, p) - } - } - if len(storedPaths) == 0 { - return f(m) - } - st.AddGetFields(storedPaths...) - st.onGet = append(st.onGet, func(stored *ttnpb.EndDevice) error { - if stored == nil { - return f(m) - } - for _, p := range storedPaths { - m[p] = stored - } - return f(m) - }) - return nil -} - -// ValidateField ensures that isValid(dev), where dev is the device containing path evaluates to true. -func (st *setDeviceState) ValidateField(isValid func(*ttnpb.EndDevice) bool, path string) error { - return st.WithField(func(dev *ttnpb.EndDevice) error { - if !isValid(dev) { - return newInvalidFieldValueError(path) - } - return nil - }, path) -} - -var errFieldNotZero = errors.DefineInvalidArgument("field_not_zero", "field `{name}` is not zero") - -// ValidateFieldIsZero ensures that path is zero. -func (st *setDeviceState) ValidateFieldIsZero(path string) error { - if st.HasSetField(path) { - if !st.Device.FieldIsZero(path) { - return newInvalidFieldValueError(path).WithCause(errFieldNotZero.WithAttributes("name", path)) - } - return nil - } - v, ok := st.zeroPaths[path] - if !ok { - st.zeroPaths[path] = true - st.AddGetFields(path) - return nil - } - if !v { - panic(fmt.Sprintf("path `%s` requested to be both zero and not zero", path)) - } - return nil -} - -var errFieldIsZero = errors.DefineInvalidArgument("field_is_zero", "field `{name}` is zero") - -// ValidateFieldIsNotZero ensures that path is not zero. -func (st *setDeviceState) ValidateFieldIsNotZero(path string) error { - if st.HasSetField(path) { - if st.Device.FieldIsZero(path) { - return newInvalidFieldValueError(path).WithCause(errFieldIsZero.WithAttributes("name", path)) - } - return nil - } - v, ok := st.zeroPaths[path] - if !ok { - st.zeroPaths[path] = false - st.AddGetFields(path) - return nil - } - if v { - panic(fmt.Sprintf("path `%s` requested to be both zero and not zero", path)) - } - return nil -} - -// ValidateFieldsAreZero ensures that each p in paths is zero. -func (st *setDeviceState) ValidateFieldsAreZero(paths ...string) error { - for _, p := range paths { - if err := st.ValidateFieldIsZero(p); err != nil { - return err - } - } - return nil -} - -// ValidateFieldsAreNotZero ensures none of p in paths is zero. -func (st *setDeviceState) ValidateFieldsAreNotZero(paths ...string) error { - for _, p := range paths { - if err := st.ValidateFieldIsNotZero(p); err != nil { - return err - } - } - return nil -} - -// ValidateFields calls isValid with a map path -> *ttnpb.EndDevice, where the value stored under the key -// is either a pointer to stored device or to device being set in request, depending on the request fieldmask. -// isValid is only executed once all fields are present. That means that if request sets all fields in paths -// isValid is executed immediately, otherwise it is called later (after device fetch) by SetFunc. -func (st *setDeviceState) ValidateFields(isValid func(map[string]*ttnpb.EndDevice) (bool, string), paths ...string) error { - return st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - ok, p := isValid(m) - if !ok { - return newInvalidFieldValueError(p) - } - return nil - }, paths...) -} - -// ValidateSetField validates the field iff path is being set in request. -func (st *setDeviceState) ValidateSetField(isValid func() bool, path string) error { - if !st.HasSetField(path) { - return nil - } - if !isValid() { - return newInvalidFieldValueError(path) - } - return nil -} - -// ValidateSetField is like ValidateSetField, but allows the validator callback to return an error -// and propagates it to the caller as the cause. -func (st *setDeviceState) ValidateSetFieldWithCause(isValid func() error, path string) error { - if !st.HasSetField(path) { - return nil - } - if err := isValid(); err != nil { - return newInvalidFieldValueError(path).WithCause(err) - } - return nil -} - -// ValidateSetFields validates the fields iff at least one of p in paths is being set in request. -func (st *setDeviceState) ValidateSetFields(isValid func(map[string]*ttnpb.EndDevice) (bool, string), paths ...string) error { - if !st.HasSetField(paths...) { - return nil - } - return st.ValidateFields(isValid, paths...) -} - -// SetFunc is the function meant to be passed to SetByID. -func (st *setDeviceState) SetFunc(f func(context.Context, *ttnpb.EndDevice) error) func(context.Context, *ttnpb.EndDevice) (*ttnpb.EndDevice, []string, error) { - return func(ctx context.Context, stored *ttnpb.EndDevice) (*ttnpb.EndDevice, []string, error) { - for p, shouldBeZero := range st.zeroPaths { - if stored.FieldIsZero(p) != shouldBeZero { - return nil, nil, newInvalidFieldValueError(p) - } - } - for _, g := range st.onGet { - if err := g(stored); err != nil { - return nil, nil, err - } - } - if err := f(ctx, stored); err != nil { - return nil, nil, err - } - return st.Device, st.SetFields(), nil - } -} - -func newSetDeviceState(dev *ttnpb.EndDevice, paths ...string) *setDeviceState { - return &setDeviceState{ - Device: dev, - paths: paths, - - pathsCache: make(map[string]bool), - extraSetsCache: make(map[string]bool), - extraGetsCache: make(map[string]bool), - - zeroPaths: make(map[string]bool), - } -} - func setKeyIsZero(m map[string]*ttnpb.EndDevice, get func(*ttnpb.EndDevice) *ttnpb.KeyEnvelope, path string) bool { if dev, ok := m[path+".key"]; ok { if ke := get(dev); !types.MustAES128Key(ke.GetKey()).OrZero().IsZero() { @@ -590,720 +313,55 @@ func setKeyEqual(m map[string]*ttnpb.EndDevice, getA, getB func(*ttnpb.EndDevice return true } -// ifThenFuncFieldRight represents the RHS of a functional implication. -type ifThenFuncFieldRight struct { - Func func(m map[string]*ttnpb.EndDevice) (bool, string) - Fields []string -} +// Set implements NsEndDeviceRegistryServer. +func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest) (*ttnpb.EndDevice, error) { // nolint: gocyclo,lll + st := newSetDeviceState(req.EndDevice, req.FieldMask.GetPaths()...) -var ( - ifZeroThenZeroFields = map[string][]string{ - "supports_join": { - "pending_mac_state.current_parameters.adr_ack_delay_exponent.value", - "pending_mac_state.current_parameters.adr_ack_limit_exponent.value", - "pending_mac_state.current_parameters.adr_data_rate_index", - "pending_mac_state.current_parameters.adr_nb_trans", - "pending_mac_state.current_parameters.adr_tx_power_index", - "pending_mac_state.current_parameters.beacon_frequency", - "pending_mac_state.current_parameters.channels", - "pending_mac_state.current_parameters.downlink_dwell_time.value", - "pending_mac_state.current_parameters.max_duty_cycle", - "pending_mac_state.current_parameters.max_eirp", - "pending_mac_state.current_parameters.ping_slot_data_rate_index_value.value", - "pending_mac_state.current_parameters.ping_slot_frequency", - "pending_mac_state.current_parameters.rejoin_count_periodicity", - "pending_mac_state.current_parameters.rejoin_time_periodicity", - "pending_mac_state.current_parameters.relay.mode.served.backoff", - "pending_mac_state.current_parameters.relay.mode.served.mode.always", - "pending_mac_state.current_parameters.relay.mode.served.mode.dynamic.smart_enable_level", - "pending_mac_state.current_parameters.relay.mode.served.mode.end_device_controlled", - "pending_mac_state.current_parameters.relay.mode.served.second_channel.ack_offset", - "pending_mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index", - "pending_mac_state.current_parameters.relay.mode.served.second_channel.frequency", - "pending_mac_state.current_parameters.relay.mode.served.serving_device_id", - "pending_mac_state.current_parameters.relay.mode.serving.cad_periodicity", - "pending_mac_state.current_parameters.relay.mode.serving.default_channel_index", - "pending_mac_state.current_parameters.relay.mode.serving.limits.join_requests.bucket_size", - "pending_mac_state.current_parameters.relay.mode.serving.limits.join_requests.reload_rate", - "pending_mac_state.current_parameters.relay.mode.serving.limits.notifications.bucket_size", - "pending_mac_state.current_parameters.relay.mode.serving.limits.notifications.reload_rate", - "pending_mac_state.current_parameters.relay.mode.serving.limits.overall.bucket_size", - "pending_mac_state.current_parameters.relay.mode.serving.limits.overall.reload_rate", - "pending_mac_state.current_parameters.relay.mode.serving.limits.reset_behavior", - "pending_mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", - "pending_mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", - "pending_mac_state.current_parameters.relay.mode.serving.second_channel.ack_offset", - "pending_mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index", - "pending_mac_state.current_parameters.relay.mode.serving.second_channel.frequency", - "pending_mac_state.current_parameters.relay.mode.serving.uplink_forwarding_rules", - "pending_mac_state.current_parameters.rx1_data_rate_offset", - "pending_mac_state.current_parameters.rx1_delay", - "pending_mac_state.current_parameters.rx2_data_rate_index", - "pending_mac_state.current_parameters.rx2_frequency", - "pending_mac_state.current_parameters.uplink_dwell_time.value", - "pending_mac_state.desired_parameters.adr_ack_delay_exponent.value", - "pending_mac_state.desired_parameters.adr_ack_limit_exponent.value", - "pending_mac_state.desired_parameters.adr_data_rate_index", - "pending_mac_state.desired_parameters.adr_nb_trans", - "pending_mac_state.desired_parameters.adr_tx_power_index", - "pending_mac_state.desired_parameters.beacon_frequency", - "pending_mac_state.desired_parameters.channels", - "pending_mac_state.desired_parameters.downlink_dwell_time.value", - "pending_mac_state.desired_parameters.max_duty_cycle", - "pending_mac_state.desired_parameters.max_eirp", - "pending_mac_state.desired_parameters.ping_slot_data_rate_index_value.value", - "pending_mac_state.desired_parameters.ping_slot_frequency", - "pending_mac_state.desired_parameters.rejoin_count_periodicity", - "pending_mac_state.desired_parameters.rejoin_time_periodicity", - "pending_mac_state.desired_parameters.relay.mode.served.backoff", - "pending_mac_state.desired_parameters.relay.mode.served.mode.always", - "pending_mac_state.desired_parameters.relay.mode.served.mode.dynamic.smart_enable_level", - "pending_mac_state.desired_parameters.relay.mode.served.mode.end_device_controlled", - "pending_mac_state.desired_parameters.relay.mode.served.second_channel.ack_offset", - "pending_mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index", - "pending_mac_state.desired_parameters.relay.mode.served.second_channel.frequency", - "pending_mac_state.desired_parameters.relay.mode.served.serving_device_id", - "pending_mac_state.desired_parameters.relay.mode.serving.cad_periodicity", - "pending_mac_state.desired_parameters.relay.mode.serving.default_channel_index", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.join_requests.bucket_size", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.join_requests.reload_rate", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.notifications.bucket_size", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.notifications.reload_rate", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.overall.bucket_size", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.overall.reload_rate", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.reset_behavior", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", - "pending_mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", - "pending_mac_state.desired_parameters.relay.mode.serving.second_channel.ack_offset", - "pending_mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index", - "pending_mac_state.desired_parameters.relay.mode.serving.second_channel.frequency", - "pending_mac_state.desired_parameters.relay.mode.serving.uplink_forwarding_rules", - "pending_mac_state.desired_parameters.rx1_data_rate_offset", - "pending_mac_state.desired_parameters.rx1_delay", - "pending_mac_state.desired_parameters.rx2_data_rate_index", - "pending_mac_state.desired_parameters.rx2_frequency", - "pending_mac_state.desired_parameters.uplink_dwell_time.value", - "pending_mac_state.device_class", - "pending_mac_state.last_adr_change_f_cnt_up", - "pending_mac_state.last_confirmed_downlink_at", - "pending_mac_state.last_dev_status_f_cnt_up", - "pending_mac_state.last_downlink_at", - "pending_mac_state.last_network_initiated_downlink_at", - "pending_mac_state.lorawan_version", - "pending_mac_state.pending_join_request.cf_list.ch_masks", - "pending_mac_state.pending_join_request.cf_list.freq", - "pending_mac_state.pending_join_request.cf_list.type", - "pending_mac_state.pending_join_request.downlink_settings.opt_neg", - "pending_mac_state.pending_join_request.downlink_settings.rx1_dr_offset", - "pending_mac_state.pending_join_request.downlink_settings.rx2_dr", - "pending_mac_state.pending_join_request.rx_delay", - "pending_mac_state.ping_slot_periodicity.value", - "pending_mac_state.queued_join_accept.correlation_ids", - "pending_mac_state.queued_join_accept.keys.app_s_key.encrypted_key", - "pending_mac_state.queued_join_accept.keys.app_s_key.kek_label", - "pending_mac_state.queued_join_accept.keys.app_s_key.key", - "pending_mac_state.queued_join_accept.keys.f_nwk_s_int_key.key", - "pending_mac_state.queued_join_accept.keys.nwk_s_enc_key.key", - "pending_mac_state.queued_join_accept.keys.s_nwk_s_int_key.key", - "pending_mac_state.queued_join_accept.keys.session_key_id", - "pending_mac_state.queued_join_accept.payload", - "pending_mac_state.queued_join_accept.request.cf_list.ch_masks", - "pending_mac_state.queued_join_accept.request.cf_list.freq", - "pending_mac_state.queued_join_accept.request.cf_list.type", - "pending_mac_state.queued_join_accept.request.dev_addr", - "pending_mac_state.queued_join_accept.request.downlink_settings.opt_neg", - "pending_mac_state.queued_join_accept.request.downlink_settings.rx1_dr_offset", - "pending_mac_state.queued_join_accept.request.downlink_settings.rx2_dr", - "pending_mac_state.queued_join_accept.request.net_id", - "pending_mac_state.queued_join_accept.request.rx_delay", - "pending_mac_state.recent_downlinks", - "pending_mac_state.recent_mac_command_identifiers", - "pending_mac_state.recent_uplinks", - "pending_mac_state.rejected_adr_data_rate_indexes", - "pending_mac_state.rejected_adr_tx_power_indexes", - "pending_mac_state.rejected_data_rate_ranges", - "pending_mac_state.rejected_frequencies", - "pending_mac_state.rx_windows_available", - "pending_session.dev_addr", - "pending_session.keys.f_nwk_s_int_key.key", - "pending_session.keys.nwk_s_enc_key.key", - "pending_session.keys.s_nwk_s_int_key.key", - "pending_session.keys.session_key_id", - "session.keys.session_key_id", - }, + requiredRights := append(make([]ttnpb.Right, 0, 2), + ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE, + ) + if st.HasSetField( + "pending_mac_state.queued_join_accept.keys.app_s_key.encrypted_key", + "pending_mac_state.queued_join_accept.keys.app_s_key.kek_label", + "pending_mac_state.queued_join_accept.keys.app_s_key.key", + "pending_mac_state.queued_join_accept.keys.f_nwk_s_int_key.key", + "pending_mac_state.queued_join_accept.keys.nwk_s_enc_key.key", + "pending_mac_state.queued_join_accept.keys.s_nwk_s_int_key.key", + "pending_mac_state.queued_join_accept.keys.session_key_id", + "pending_session.keys.f_nwk_s_int_key.key", + "pending_session.keys.nwk_s_enc_key.key", + "pending_session.keys.s_nwk_s_int_key.key", + "pending_session.keys.session_key_id", + "session.keys.f_nwk_s_int_key.key", + "session.keys.nwk_s_enc_key.key", + "session.keys.s_nwk_s_int_key.key", + "session.keys.session_key_id", + ) { + requiredRights = append(requiredRights, ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE_KEYS) } - - ifZeroThenNotZeroFields = map[string][]string{ - "supports_join": { - "session.dev_addr", - "session.keys.f_nwk_s_int_key.key", - // NOTE: LoRaWAN-version specific fields are validated within Set directly. - }, + if err := rights.RequireApplication(ctx, st.Device.Ids.ApplicationIds, requiredRights...); err != nil { + return nil, err } - ifNotZeroThenZeroFields = map[string][]string{ - "multicast": { - "mac_settings.desired_relay.mode.served.backoff", - "mac_settings.desired_relay.mode.served.mode.always", - "mac_settings.desired_relay.mode.served.mode.dynamic.smart_enable_level", - "mac_settings.desired_relay.mode.served.mode.end_device_controlled", - "mac_settings.desired_relay.mode.served.second_channel.ack_offset", - "mac_settings.desired_relay.mode.served.second_channel.data_rate_index", - "mac_settings.desired_relay.mode.served.second_channel.frequency", - "mac_settings.desired_relay.mode.served.serving_device_id", - "mac_settings.desired_relay.mode.serving.cad_periodicity", - "mac_settings.desired_relay.mode.serving.default_channel_index", - "mac_settings.desired_relay.mode.serving.limits.join_requests.bucket_size", - "mac_settings.desired_relay.mode.serving.limits.join_requests.reload_rate", - "mac_settings.desired_relay.mode.serving.limits.notifications.bucket_size", - "mac_settings.desired_relay.mode.serving.limits.notifications.reload_rate", - "mac_settings.desired_relay.mode.serving.limits.overall.bucket_size", - "mac_settings.desired_relay.mode.serving.limits.overall.reload_rate", - "mac_settings.desired_relay.mode.serving.limits.reset_behavior", - "mac_settings.desired_relay.mode.serving.limits.uplink_messages.bucket_size", - "mac_settings.desired_relay.mode.serving.limits.uplink_messages.reload_rate", - "mac_settings.desired_relay.mode.serving.second_channel.ack_offset", - "mac_settings.desired_relay.mode.serving.second_channel.data_rate_index", - "mac_settings.desired_relay.mode.serving.second_channel.frequency", - "mac_settings.desired_relay.mode.serving.uplink_forwarding_rules", - "mac_settings.relay.mode.served.backoff", - "mac_settings.relay.mode.served.mode.always", - "mac_settings.relay.mode.served.mode.dynamic.smart_enable_level", - "mac_settings.relay.mode.served.mode.end_device_controlled", - "mac_settings.relay.mode.served.second_channel.ack_offset", - "mac_settings.relay.mode.served.second_channel.data_rate_index", - "mac_settings.relay.mode.served.second_channel.frequency", - "mac_settings.relay.mode.served.serving_device_id", - "mac_settings.relay.mode.serving.cad_periodicity", - "mac_settings.relay.mode.serving.default_channel_index", - "mac_settings.relay.mode.serving.limits.join_requests.bucket_size", - "mac_settings.relay.mode.serving.limits.join_requests.reload_rate", - "mac_settings.relay.mode.serving.limits.notifications.bucket_size", - "mac_settings.relay.mode.serving.limits.notifications.reload_rate", - "mac_settings.relay.mode.serving.limits.overall.bucket_size", - "mac_settings.relay.mode.serving.limits.overall.reload_rate", - "mac_settings.relay.mode.serving.limits.reset_behavior", - "mac_settings.relay.mode.serving.limits.uplink_messages.bucket_size", - "mac_settings.relay.mode.serving.limits.uplink_messages.reload_rate", - "mac_settings.relay.mode.serving.second_channel.ack_offset", - "mac_settings.relay.mode.serving.second_channel.data_rate_index", - "mac_settings.relay.mode.serving.second_channel.frequency", - "mac_settings.relay.mode.serving.uplink_forwarding_rules", - "mac_settings.schedule_downlinks.value", - "mac_state.current_parameters.relay.mode.served.backoff", - "mac_state.current_parameters.relay.mode.served.mode.always", - "mac_state.current_parameters.relay.mode.served.mode.dynamic.smart_enable_level", - "mac_state.current_parameters.relay.mode.served.mode.end_device_controlled", - "mac_state.current_parameters.relay.mode.served.second_channel.ack_offset", - "mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index", - "mac_state.current_parameters.relay.mode.served.second_channel.frequency", - "mac_state.current_parameters.relay.mode.served.serving_device_id", - "mac_state.current_parameters.relay.mode.serving.cad_periodicity", - "mac_state.current_parameters.relay.mode.serving.default_channel_index", - "mac_state.current_parameters.relay.mode.serving.limits.join_requests.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.join_requests.reload_rate", - "mac_state.current_parameters.relay.mode.serving.limits.notifications.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.notifications.reload_rate", - "mac_state.current_parameters.relay.mode.serving.limits.overall.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.overall.reload_rate", - "mac_state.current_parameters.relay.mode.serving.limits.reset_behavior", - "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", - "mac_state.current_parameters.relay.mode.serving.second_channel.ack_offset", - "mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index", - "mac_state.current_parameters.relay.mode.serving.second_channel.frequency", - "mac_state.current_parameters.relay.mode.serving.uplink_forwarding_rules", - "mac_state.desired_parameters.relay.mode.served.backoff", - "mac_state.desired_parameters.relay.mode.served.mode.always", - "mac_state.desired_parameters.relay.mode.served.mode.dynamic.smart_enable_level", - "mac_state.desired_parameters.relay.mode.served.mode.end_device_controlled", - "mac_state.desired_parameters.relay.mode.served.second_channel.ack_offset", - "mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index", - "mac_state.desired_parameters.relay.mode.served.second_channel.frequency", - "mac_state.desired_parameters.relay.mode.served.serving_device_id", - "mac_state.desired_parameters.relay.mode.serving.cad_periodicity", - "mac_state.desired_parameters.relay.mode.serving.default_channel_index", - "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.limits.notifications.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.notifications.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.limits.overall.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.overall.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.limits.reset_behavior", - "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.second_channel.ack_offset", - "mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index", - "mac_state.desired_parameters.relay.mode.serving.second_channel.frequency", - "mac_state.desired_parameters.relay.mode.serving.uplink_forwarding_rules", - "mac_state.last_adr_change_f_cnt_up", - "mac_state.last_confirmed_downlink_at", - "mac_state.last_dev_status_f_cnt_up", - "mac_state.pending_application_downlink", - "mac_state.pending_requests", - "mac_state.queued_responses", - "mac_state.recent_mac_command_identifiers", - "mac_state.recent_uplinks", - "mac_state.rejected_adr_data_rate_indexes", - "mac_state.rejected_adr_tx_power_indexes", - "mac_state.rejected_data_rate_ranges", - "mac_state.rejected_frequencies", - "mac_state.rx_windows_available", - "session.last_conf_f_cnt_down", - "session.last_f_cnt_up", - "supports_join", - }, + // Account for CLI not sending ids.* paths. + st.AddSetFields( + "ids.application_ids", + "ids.device_id", + ) + if st.Device.Ids.JoinEui != nil { + st.AddSetFields( + "ids.join_eui", + ) } - - ifNotZeroThenNotZeroFields = map[string][]string{ - "supports_join": { + if st.Device.Ids.DevEui != nil { + st.AddSetFields( "ids.dev_eui", - "ids.join_eui", - }, + ) } - - ifZeroThenFuncFields = map[string][]ifThenFuncFieldRight{ - "supports_join": { - { - Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { - if dev, ok := m["ids.dev_eui"]; ok && !types.MustEUI64(dev.Ids.DevEui).OrZero().IsZero() { - return true, "" - } - if m["lorawan_version"].GetLorawanVersion() == ttnpb.MACVersion_MAC_UNKNOWN { - return false, "lorawan_version" - } - if macspec.RequireDevEUIForABP(m["lorawan_version"].LorawanVersion) && !m["multicast"].GetMulticast() { - return false, "ids.dev_eui" - } - return true, "" - }, - Fields: []string{ - "ids.dev_eui", - "lorawan_version", - "multicast", - }, - }, - - { - Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { - if !m["supports_class_b"].GetSupportsClassB() || - m["mac_settings.ping_slot_periodicity.value"].GetMacSettings().GetPingSlotPeriodicity() != nil { - return true, "" - } - return false, "mac_settings.ping_slot_periodicity.value" - }, - Fields: []string{ - "mac_settings.ping_slot_periodicity.value", - "supports_class_b", - }, - }, - }, - } - - ifNotZeroThenFuncFields = map[string][]ifThenFuncFieldRight{ - "multicast": append(func() (rs []ifThenFuncFieldRight) { - for s, eq := range map[string]func(*ttnpb.MACParameters, *ttnpb.MACParameters) bool{ - "adr_ack_delay_exponent.value": func(a, b *ttnpb.MACParameters) bool { - return proto.Equal(a.AdrAckDelayExponent, b.AdrAckDelayExponent) - }, - "adr_ack_limit_exponent.value": func(a, b *ttnpb.MACParameters) bool { - return proto.Equal(a.AdrAckLimitExponent, b.AdrAckLimitExponent) - }, - "adr_data_rate_index": func(a, b *ttnpb.MACParameters) bool { - return a.AdrDataRateIndex == b.AdrDataRateIndex - }, - "adr_nb_trans": func(a, b *ttnpb.MACParameters) bool { - return a.AdrNbTrans == b.AdrNbTrans - }, - "adr_tx_power_index": func(a, b *ttnpb.MACParameters) bool { - return a.AdrTxPowerIndex == b.AdrTxPowerIndex - }, - "beacon_frequency": func(a, b *ttnpb.MACParameters) bool { - return a.BeaconFrequency == b.BeaconFrequency - }, - "channels": func(a, b *ttnpb.MACParameters) bool { - if len(a.Channels) != len(b.Channels) { - return false - } - for i, ch := range a.Channels { - if !proto.Equal(ch, b.Channels[i]) { - return false - } - } - return true - }, - "downlink_dwell_time.value": func(a, b *ttnpb.MACParameters) bool { - return proto.Equal(a.DownlinkDwellTime, b.DownlinkDwellTime) - }, - "max_duty_cycle": func(a, b *ttnpb.MACParameters) bool { - return a.MaxDutyCycle == b.MaxDutyCycle - }, - "max_eirp": func(a, b *ttnpb.MACParameters) bool { - return a.MaxEirp == b.MaxEirp - }, - "ping_slot_data_rate_index_value.value": func(a, b *ttnpb.MACParameters) bool { - return proto.Equal(a.PingSlotDataRateIndexValue, b.PingSlotDataRateIndexValue) - }, - "ping_slot_frequency": func(a, b *ttnpb.MACParameters) bool { - return a.PingSlotFrequency == b.PingSlotFrequency - }, - "rejoin_count_periodicity": func(a, b *ttnpb.MACParameters) bool { - return a.RejoinCountPeriodicity == b.RejoinCountPeriodicity - }, - "rejoin_time_periodicity": func(a, b *ttnpb.MACParameters) bool { - return a.RejoinTimePeriodicity == b.RejoinTimePeriodicity - }, - "rx1_data_rate_offset": func(a, b *ttnpb.MACParameters) bool { - return a.Rx1DataRateOffset == b.Rx1DataRateOffset - }, - "rx1_delay": func(a, b *ttnpb.MACParameters) bool { - return a.Rx1Delay == b.Rx1Delay - }, - "rx2_data_rate_index": func(a, b *ttnpb.MACParameters) bool { - return a.Rx2DataRateIndex == b.Rx2DataRateIndex - }, - "rx2_frequency": func(a, b *ttnpb.MACParameters) bool { - return a.Rx2Frequency == b.Rx2Frequency - }, - "uplink_dwell_time.value": func(a, b *ttnpb.MACParameters) bool { - return proto.Equal(a.UplinkDwellTime, b.UplinkDwellTime) - }, - } { - curPath := "mac_state.current_parameters." + s - desPath := "mac_state.desired_parameters." + s - eq := eq - rs = append(rs, ifThenFuncFieldRight{ - Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { - curDev := m[curPath] - desDev := m[desPath] - if curDev == nil || desDev == nil { - if curDev != desDev { - return false, desPath - } - return true, "" - } - if !eq(curDev.MacState.CurrentParameters, desDev.MacState.DesiredParameters) { - return false, desPath - } - return true, "" - }, - Fields: []string{ - curPath, - desPath, - }, - }) - } - return rs - }(), - - ifThenFuncFieldRight{ - Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { - if !m["supports_class_b"].GetSupportsClassB() && !m["supports_class_c"].GetSupportsClassC() { - return false, "supports_class_b" - } - return true, "" - }, - Fields: []string{ - "supports_class_b", - "supports_class_c", - }, - }, - - ifThenFuncFieldRight{ - Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { - if !m["supports_class_b"].GetSupportsClassB() || - m["mac_settings.ping_slot_periodicity.value"].GetMacSettings().GetPingSlotPeriodicity() != nil { - return true, "" - } - return false, "mac_settings.ping_slot_periodicity.value" - }, - Fields: []string{ - "mac_settings.ping_slot_periodicity.value", - "supports_class_b", - }, - }, - ), - } - - // downlinkInfluencingSetFields contains fields that can influence downlink scheduling, e.g. trigger one or make a scheduled slot obsolete. - downlinkInfluencingSetFields = [...]string{ - "last_dev_status_received_at", - "mac_settings.schedule_downlinks.value", - "mac_state.current_parameters.adr_ack_delay_exponent.value", - "mac_state.current_parameters.adr_ack_limit_exponent.value", - "mac_state.current_parameters.adr_data_rate_index", - "mac_state.current_parameters.adr_nb_trans", - "mac_state.current_parameters.adr_tx_power_index", - "mac_state.current_parameters.beacon_frequency", - "mac_state.current_parameters.channels", - "mac_state.current_parameters.downlink_dwell_time.value", - "mac_state.current_parameters.max_duty_cycle", - "mac_state.current_parameters.max_eirp", - "mac_state.current_parameters.ping_slot_data_rate_index_value.value", - "mac_state.current_parameters.ping_slot_frequency", - "mac_state.current_parameters.rejoin_count_periodicity", - "mac_state.current_parameters.rejoin_time_periodicity", - "mac_state.current_parameters.relay.mode.served.backoff", - "mac_state.current_parameters.relay.mode.served.mode.always", - "mac_state.current_parameters.relay.mode.served.mode.dynamic.smart_enable_level", - "mac_state.current_parameters.relay.mode.served.mode.end_device_controlled", - "mac_state.current_parameters.relay.mode.served.second_channel.ack_offset", - "mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index", - "mac_state.current_parameters.relay.mode.served.second_channel.frequency", - "mac_state.current_parameters.relay.mode.serving.cad_periodicity", - "mac_state.current_parameters.relay.mode.serving.default_channel_index", - "mac_state.current_parameters.relay.mode.serving.limits.join_requests.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.join_requests.reload_rate", - "mac_state.current_parameters.relay.mode.serving.limits.notifications.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.notifications.reload_rate", - "mac_state.current_parameters.relay.mode.serving.limits.overall.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.overall.reload_rate", - "mac_state.current_parameters.relay.mode.serving.limits.reset_behavior", - "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", - "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", - "mac_state.current_parameters.relay.mode.serving.second_channel.ack_offset", - "mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index", - "mac_state.current_parameters.relay.mode.serving.second_channel.frequency", - "mac_state.current_parameters.relay.mode.serving.uplink_forwarding_rules", - "mac_state.current_parameters.rx1_data_rate_offset", - "mac_state.current_parameters.rx1_delay", - "mac_state.current_parameters.rx2_data_rate_index", - "mac_state.current_parameters.rx2_frequency", - "mac_state.current_parameters.uplink_dwell_time.value", - "mac_state.desired_parameters.adr_ack_delay_exponent.value", - "mac_state.desired_parameters.adr_ack_limit_exponent.value", - "mac_state.desired_parameters.adr_data_rate_index", - "mac_state.desired_parameters.adr_nb_trans", - "mac_state.desired_parameters.adr_tx_power_index", - "mac_state.desired_parameters.beacon_frequency", - "mac_state.desired_parameters.channels", - "mac_state.desired_parameters.downlink_dwell_time.value", - "mac_state.desired_parameters.max_duty_cycle", - "mac_state.desired_parameters.max_eirp", - "mac_state.desired_parameters.ping_slot_data_rate_index_value.value", - "mac_state.desired_parameters.ping_slot_frequency", - "mac_state.desired_parameters.rejoin_count_periodicity", - "mac_state.desired_parameters.rejoin_time_periodicity", - "mac_state.desired_parameters.relay.mode.served.backoff", - "mac_state.desired_parameters.relay.mode.served.mode.always", - "mac_state.desired_parameters.relay.mode.served.mode.dynamic.smart_enable_level", - "mac_state.desired_parameters.relay.mode.served.mode.end_device_controlled", - "mac_state.desired_parameters.relay.mode.served.second_channel.ack_offset", - "mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index", - "mac_state.desired_parameters.relay.mode.served.second_channel.frequency", - "mac_state.desired_parameters.relay.mode.serving.cad_periodicity", - "mac_state.desired_parameters.relay.mode.serving.default_channel_index", - "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.limits.notifications.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.notifications.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.limits.overall.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.overall.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.limits.reset_behavior", - "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", - "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", - "mac_state.desired_parameters.relay.mode.serving.second_channel.ack_offset", - "mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index", - "mac_state.desired_parameters.relay.mode.serving.second_channel.frequency", - "mac_state.desired_parameters.relay.mode.serving.uplink_forwarding_rules", - "mac_state.desired_parameters.rx1_data_rate_offset", - "mac_state.desired_parameters.rx1_delay", - "mac_state.desired_parameters.rx2_data_rate_index", - "mac_state.desired_parameters.rx2_frequency", - "mac_state.desired_parameters.uplink_dwell_time.value", - "mac_state.device_class", - "mac_state.last_confirmed_downlink_at", - "mac_state.last_dev_status_f_cnt_up", - "mac_state.last_downlink_at", - "mac_state.last_network_initiated_downlink_at", - "mac_state.lorawan_version", - "mac_state.ping_slot_periodicity.value", - "mac_state.queued_responses", - "mac_state.recent_mac_command_identifiers", - "mac_state.recent_uplinks", - "mac_state.rejected_adr_data_rate_indexes", - "mac_state.rejected_adr_tx_power_indexes", - "mac_state.rejected_data_rate_ranges", - "mac_state.rejected_frequencies", - "mac_state.rx_windows_available", - } - - legacyADRSettingsFields = []string{ - "mac_settings.adr_margin", - "mac_settings.use_adr.value", - "mac_settings.use_adr", - } - - adrSettingsFields = []string{ - "mac_settings.adr.mode.disabled", - "mac_settings.adr.mode.dynamic.channel_steering.mode.disabled", - "mac_settings.adr.mode.dynamic.channel_steering.mode.lora_narrow", - "mac_settings.adr.mode.dynamic.channel_steering.mode", - "mac_settings.adr.mode.dynamic.channel_steering", - "mac_settings.adr.mode.dynamic.margin", - "mac_settings.adr.mode.dynamic.max_data_rate_index.value", - "mac_settings.adr.mode.dynamic.max_data_rate_index", - "mac_settings.adr.mode.dynamic.max_nb_trans", - "mac_settings.adr.mode.dynamic.max_tx_power_index", - "mac_settings.adr.mode.dynamic.min_data_rate_index.value", - "mac_settings.adr.mode.dynamic.min_data_rate_index", - "mac_settings.adr.mode.dynamic.min_nb_trans", - "mac_settings.adr.mode.dynamic.min_tx_power_index", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9", - "mac_settings.adr.mode.dynamic.overrides", - "mac_settings.adr.mode.dynamic", - "mac_settings.adr.mode.static.data_rate_index", - "mac_settings.adr.mode.static.nb_trans", - "mac_settings.adr.mode.static.tx_power_index", - "mac_settings.adr.mode.static", - "mac_settings.adr.mode", - "mac_settings.adr", - } - - dynamicADRSettingsFields = []string{ - "mac_settings.adr.mode.dynamic.channel_steering.mode.disabled", - "mac_settings.adr.mode.dynamic.channel_steering.mode.lora_narrow", - "mac_settings.adr.mode.dynamic.channel_steering.mode", - "mac_settings.adr.mode.dynamic.channel_steering", - "mac_settings.adr.mode.dynamic.margin", - "mac_settings.adr.mode.dynamic.max_data_rate_index.value", - "mac_settings.adr.mode.dynamic.max_nb_trans", - "mac_settings.adr.mode.dynamic.max_tx_power_index", - "mac_settings.adr.mode.dynamic.min_data_rate_index.value", - "mac_settings.adr.mode.dynamic.min_nb_trans", - "mac_settings.adr.mode.dynamic.min_tx_power_index", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", - "mac_settings.adr.mode.dynamic", - } -) - -// Set implements NsEndDeviceRegistryServer. -func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest) (*ttnpb.EndDevice, error) { - st := newSetDeviceState(req.EndDevice, req.FieldMask.GetPaths()...) - - requiredRights := append(make([]ttnpb.Right, 0, 2), - ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE, - ) - if st.HasSetField( - "pending_mac_state.queued_join_accept.keys.app_s_key.encrypted_key", - "pending_mac_state.queued_join_accept.keys.app_s_key.kek_label", - "pending_mac_state.queued_join_accept.keys.app_s_key.key", - "pending_mac_state.queued_join_accept.keys.f_nwk_s_int_key.key", - "pending_mac_state.queued_join_accept.keys.nwk_s_enc_key.key", - "pending_mac_state.queued_join_accept.keys.s_nwk_s_int_key.key", - "pending_mac_state.queued_join_accept.keys.session_key_id", - "pending_session.keys.f_nwk_s_int_key.key", - "pending_session.keys.nwk_s_enc_key.key", - "pending_session.keys.s_nwk_s_int_key.key", - "pending_session.keys.session_key_id", - "session.keys.f_nwk_s_int_key.key", - "session.keys.nwk_s_enc_key.key", - "session.keys.s_nwk_s_int_key.key", - "session.keys.session_key_id", - ) { - requiredRights = append(requiredRights, ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE_KEYS) - } - if err := rights.RequireApplication(ctx, st.Device.Ids.ApplicationIds, requiredRights...); err != nil { - return nil, err - } - - // Account for CLI not sending ids.* paths. - st.AddSetFields( - "ids.application_ids", - "ids.device_id", - ) - if st.Device.Ids.JoinEui != nil { - st.AddSetFields( - "ids.join_eui", - ) - } - if st.Device.Ids.DevEui != nil { - st.AddSetFields( - "ids.dev_eui", - ) - } - if st.Device.Ids.DevAddr != nil { - st.AddSetFields( - "ids.dev_addr", - ) + if st.Device.Ids.DevAddr != nil { + st.AddSetFields( + "ids.dev_addr", + ) } if err := st.ValidateSetField( @@ -1347,160 +405,37 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest return nil, err } - // Ensure ids.dev_addr and session.dev_addr are consistent. - if st.HasSetField("ids.dev_addr") { - if err := st.ValidateField(func(dev *ttnpb.EndDevice) bool { - if st.Device.Ids.DevAddr == nil { - return dev.GetSession() == nil - } - return dev.GetSession() != nil && bytes.Equal(dev.Session.DevAddr, st.Device.Ids.DevAddr) - }, "session.dev_addr"); err != nil { - return nil, err - } - } else if st.HasSetField("session.dev_addr") { - st.Device.Ids.DevAddr = nil - if devAddr := types.MustDevAddr(st.Device.GetSession().GetDevAddr()); devAddr != nil { - st.Device.Ids.DevAddr = devAddr.Bytes() - } - st.AddSetFields( - "ids.dev_addr", - ) - } - - // Ensure FieldIsZero(left) -> FieldIsZero(r), for each r in right. - for left, right := range ifZeroThenZeroFields { - if st.HasSetField(left) { - if !st.Device.FieldIsZero(left) { - continue - } - if err := st.ValidateFieldsAreZero(right...); err != nil { - return nil, err - } - } - for _, r := range right { - if !st.HasSetField(r) || st.Device.FieldIsZero(r) { - continue - } - if err := st.ValidateFieldIsNotZero(left); err != nil { - return nil, err - } - } + fps, err := ns.FrequencyPlansStore(ctx) + if err != nil { + return nil, err } - // Ensure FieldIsZero(left) -> !FieldIsZero(r), for each r in right. - for left, right := range ifZeroThenNotZeroFields { - if st.HasSetField(left) { - if !st.Device.FieldIsZero(left) { - continue - } - if err := st.ValidateFieldsAreNotZero(right...); err != nil { - return nil, err - } + profile := &ttnpb.MACSettingsProfile{} + if st.HasSetField( + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", + ) { + if st.HasSetField(macSettingsFields...) { + return nil, newInvalidFieldValueError("mac_settings") } - for _, r := range right { - if !st.HasSetField(r) || !st.Device.FieldIsZero(r) { - continue - } - if err := st.ValidateFieldIsNotZero(left); err != nil { - return nil, err - } + profile, err = ns.macSettingsProfiles.Get(ctx, st.Device.MacSettingsProfileIds, []string{"mac_settings"}) + if err != nil { + return nil, err } - } - - // Ensure FieldIsZero(left) -> r.Func(map rr -> *ttnpb.EndDevice), for each rr in r.Fields for each r in rs. - for left, rs := range ifZeroThenFuncFields { - for _, r := range rs { - if st.HasSetField(left) { - if !st.Device.FieldIsZero(left) { - continue - } - if err := st.ValidateFields(r.Func, r.Fields...); err != nil { - return nil, err - } - } - if !st.HasSetField(r.Fields...) { - continue - } - left := left - r := r - if err := st.ValidateFields(func(m map[string]*ttnpb.EndDevice) (bool, string) { - if !m[left].FieldIsZero(left) { - return true, "" - } - return r.Func(m) - }, append([]string{left}, r.Fields...)...); err != nil { - return nil, err - } + if err = validateProfile(profile, st, fps); err != nil { + return nil, err } } - // Ensure !FieldIsZero(left) -> FieldIsZero(r), for each r in right. - for left, right := range ifNotZeroThenZeroFields { - if st.HasSetField(left) { - if st.Device.FieldIsZero(left) { - continue - } - if err := st.ValidateFieldsAreZero(right...); err != nil { - return nil, err - } - } - for _, r := range right { - if !st.HasSetField(r) || st.Device.FieldIsZero(r) { - continue - } - if err := st.ValidateFieldIsZero(left); err != nil { - return nil, err - } - } + if err := validateADR(st); err != nil { + return nil, err } - // Ensure !FieldIsZero(left) -> !FieldIsZero(r), for each r in right. - for left, right := range ifNotZeroThenNotZeroFields { - if st.HasSetField(left) { - if st.Device.FieldIsZero(left) { - continue - } - if err := st.ValidateFieldsAreNotZero(right...); err != nil { - return nil, err - } - } - for _, r := range right { - if !st.HasSetField(r) || !st.Device.FieldIsZero(r) { - continue - } - if err := st.ValidateFieldIsZero(left); err != nil { - return nil, err - } - } - } - - // Ensure !FieldIsZero(left) -> r.Func(map rr -> *ttnpb.EndDevice), for each rr in r.Fields for each r in rs. - for left, rs := range ifNotZeroThenFuncFields { - for _, r := range rs { - if st.HasSetField(left) { - if st.Device.FieldIsZero(left) { - continue - } - if err := st.ValidateFields(r.Func, r.Fields...); err != nil { - return nil, err - } - } - if !st.HasSetField(r.Fields...) { - continue - } - - left := left - r := r - if err := st.ValidateFields(func(m map[string]*ttnpb.EndDevice) (bool, string) { - if m[left].FieldIsZero(left) { - return true, "" - } - return r.Func(m) - }, append([]string{left}, r.Fields...)...); err != nil { - return nil, err - } - } + if err := validateZeroFields(st); err != nil { + return nil, err } // Ensure parameters are consistent with band specifications. @@ -1562,1056 +497,12 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest "pending_mac_state.desired_parameters.rx2_data_rate_index", "supports_class_b", ) { - var deferredPHYValidations []func(*band.Band, *frequencyplans.FrequencyPlan) error - withPHY := func(f func(*band.Band, *frequencyplans.FrequencyPlan) error) error { - deferredPHYValidations = append(deferredPHYValidations, f) - return nil - } - if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - fps, err := ns.FrequencyPlansStore(ctx) - if err != nil { - return err - } - fp, phy, err := DeviceFrequencyPlanAndBand(&ttnpb.EndDevice{ - FrequencyPlanId: m["frequency_plan_id"].GetFrequencyPlanId(), - LorawanPhyVersion: m["lorawan_phy_version"].GetLorawanPhyVersion(), - }, fps) - if err != nil { - return err - } - withPHY = func(f func(*band.Band, *frequencyplans.FrequencyPlan) error) error { - return f(phy, fp) - } - for _, f := range deferredPHYValidations { - if err := f(phy, fp); err != nil { - return err - } - } - return nil - }, - "frequency_plan_id", - "lorawan_phy_version", - ); err != nil { + if err := validateBandSpecifications(st, fps); err != nil { return nil, err } - - hasPHYUpdate := st.HasSetField( - "frequency_plan_id", - "lorawan_phy_version", - ) - hasSetField := func(field string) (fieldToRetrieve string, validate bool) { - return field, st.HasSetField(field) || hasPHYUpdate - } - - setFields := func(fields ...string) []string { - setFields := make([]string, 0, len(fields)) - for _, field := range fields { - if st.HasSetField(field) { - setFields = append(setFields, field) - } - } - return setFields - } - - if st.HasSetField( - "frequency_plan_id", - "version_ids.band_id", - ) { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, fp *frequencyplans.FrequencyPlan) error { - if devBandID := dev.GetVersionIds().GetBandId(); devBandID != "" && devBandID != fp.BandID { - return newInvalidFieldValueError("version_ids.band_id").WithCause( - errDeviceAndFrequencyPlanBandMismatch.WithAttributes( - "dev_band_id", devBandID, - "fp_band_id", fp.BandID, - ), - ) - } - return nil - }) - }, "version_ids.band_id"); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.rx2_data_rate_index.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetRx2DataRateIndex() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.Rx2DataRateIndex.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.desired_rx2_data_rate_index.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetDesiredRx2DataRateIndex() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.DesiredRx2DataRateIndex.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.ping_slot_data_rate_index.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetPingSlotDataRateIndex() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.PingSlotDataRateIndex.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.desired_ping_slot_data_rate_index.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetDesiredPingSlotDataRateIndex() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.DesiredPingSlotDataRateIndex.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.desired_relay.mode.served.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetDesiredRelay().GetServed().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.DesiredRelay.GetServed().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.desired_relay.mode.serving.default_channel_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - chIdx := dev.GetMacSettings().GetDesiredRelay().GetServing().GetDefaultChannelIndex() - if chIdx == nil { - return nil - } - if chIdx.Value >= uint32(len(phy.Relay.WORChannels)) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.desired_relay.mode.serving.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetDesiredRelay().GetServing().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.DesiredRelay.GetServing().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.adr.mode.dynamic.max_data_rate_index.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetAdr().GetDynamic().GetMaxDataRateIndex() == nil { - return nil - } - drIdx := dev.MacSettings.Adr.GetDynamic().MaxDataRateIndex.Value - _, ok := phy.DataRates[drIdx] - if !ok || drIdx > phy.MaxADRDataRateIndex { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.adr.mode.dynamic.min_data_rate_index.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetAdr().GetDynamic().GetMinDataRateIndex() == nil { - return nil - } - drIdx := dev.MacSettings.Adr.GetDynamic().MinDataRateIndex.Value - _, ok := phy.DataRates[drIdx] - if !ok || drIdx > phy.MaxADRDataRateIndex { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.adr.mode.dynamic.max_tx_power_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetAdr().GetDynamic().GetMaxTxPowerIndex() == nil { - return nil - } - if dev.MacSettings.Adr.GetDynamic().MaxTxPowerIndex.Value > uint32(phy.MaxTxPowerIndex()) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.adr.mode.dynamic.min_tx_power_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetAdr().GetDynamic().GetMinTxPowerIndex() == nil { - return nil - } - if dev.MacSettings.Adr.GetDynamic().MinTxPowerIndex.Value > uint32(phy.MaxTxPowerIndex()) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if setFields := setFields(dynamicADRSettingsFields...); hasPHYUpdate || len(setFields) > 0 { - fields := setFields - if hasPHYUpdate { - fields = append(fields, "mac_settings.adr.mode") - } - if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if phy.SupportsDynamicADR { - return nil - } - for _, field := range fields { - if m[field].GetMacSettings().GetAdr().GetDynamic() != nil { - return newInvalidFieldValueError(field) - } - } - return nil - }) - }, - fields..., - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.adr.mode.static.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetAdr().GetStatic() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.Adr.GetStatic().DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.adr.mode.static.tx_power_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetAdr().GetStatic() == nil { - return nil - } - if dev.MacSettings.Adr.GetStatic().TxPowerIndex > uint32(phy.MaxTxPowerIndex()) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.uplink_dwell_time.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetUplinkDwellTime() == nil { - return nil - } - if !phy.TxParamSetupReqSupport { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.downlink_dwell_time.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetDownlinkDwellTime() == nil { - return nil - } - if !phy.TxParamSetupReqSupport { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.relay.mode.served.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetRelay().GetServed().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.Relay.GetServed().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.relay.mode.serving.default_channel_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - chIdx := dev.GetMacSettings().GetRelay().GetServing().GetDefaultChannelIndex() - if chIdx == nil { - return nil - } - if chIdx.Value >= uint32(len(phy.Relay.WORChannels)) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_settings.relay.mode.serving.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacSettings().GetRelay().GetServing().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacSettings.Relay.GetServing().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.current_parameters.rx2_data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetMacState() == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.MacState.CurrentParameters.Rx2DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.desired_parameters.rx2_data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetMacState() == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.MacState.DesiredParameters.Rx2DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetPendingMacState().GetCurrentParameters().GetRelay().GetServed().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.Relay.GetServed().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.current_parameters.relay.mode.serving.default_channel_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetPendingMacState().GetCurrentParameters().GetRelay().GetServing() == nil { - return nil - } - chIdx := dev.PendingMacState.CurrentParameters.Relay.GetServing().DefaultChannelIndex - if chIdx >= uint32(len(phy.Relay.WORChannels)) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetPendingMacState().GetCurrentParameters().GetRelay().GetServing().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.Relay.GetServing().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.current_parameters.rx2_data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetPendingMacState() == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.Rx2DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetPendingMacState().GetDesiredParameters().GetRelay().GetServed().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.Relay.GetServed().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.desired_parameters.relay.mode.serving.default_channel_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetPendingMacState().GetDesiredParameters().GetRelay().GetServing() == nil { - return nil - } - chIdx := dev.PendingMacState.DesiredParameters.Relay.GetServing().DefaultChannelIndex - if chIdx >= uint32(len(phy.Relay.WORChannels)) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetPendingMacState().GetDesiredParameters().GetRelay().GetServing().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.Relay.GetServing().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.desired_parameters.rx2_data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetPendingMacState() == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.Rx2DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.current_parameters.ping_slot_data_rate_index_value.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetMacState() == nil || dev.MacState.CurrentParameters.PingSlotDataRateIndexValue == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.MacState.CurrentParameters.PingSlotDataRateIndexValue.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacState().GetCurrentParameters().GetRelay().GetServed().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacState.CurrentParameters.Relay.GetServed().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.current_parameters.relay.mode.serving.default_channel_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacState().GetCurrentParameters().GetRelay().GetServing() == nil { - return nil - } - chIdx := dev.MacState.CurrentParameters.Relay.GetServing().DefaultChannelIndex - if chIdx >= uint32(len(phy.Relay.WORChannels)) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacState().GetCurrentParameters().GetRelay().GetServing().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacState.CurrentParameters.Relay.GetServing().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.desired_parameters.ping_slot_data_rate_index_value.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetMacState() == nil || dev.MacState.DesiredParameters.PingSlotDataRateIndexValue == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.MacState.DesiredParameters.PingSlotDataRateIndexValue.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacState().GetDesiredParameters().GetRelay().GetServed().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacState.DesiredParameters.Relay.GetServed().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.desired_parameters.relay.mode.serving.default_channel_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacState().GetDesiredParameters().GetRelay().GetServing() == nil { - return nil - } - chIdx := dev.MacState.DesiredParameters.Relay.GetServing().DefaultChannelIndex - if chIdx >= uint32(len(phy.Relay.WORChannels)) { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if dev.GetMacState().GetDesiredParameters().GetRelay().GetServing().GetSecondChannel() == nil { - return nil - } - _, ok := phy.DataRates[dev.MacState.DesiredParameters.Relay.GetServing().SecondChannel.DataRateIndex] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - - if field, validate := hasSetField("pending_mac_state.current_parameters.ping_slot_data_rate_index_value.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetPendingMacState() == nil || dev.PendingMacState.CurrentParameters.PingSlotDataRateIndexValue == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.PingSlotDataRateIndexValue.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - if field, validate := hasSetField("pending_mac_state.desired_parameters.ping_slot_data_rate_index_value.value"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetPendingMacState() == nil || dev.PendingMacState.DesiredParameters.PingSlotDataRateIndexValue == nil { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.PingSlotDataRateIndexValue.Value] - if !ok { - return newInvalidFieldValueError(field) - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - - if field, validate := hasSetField("mac_settings.factory_preset_frequencies"); validate { - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - if dev.GetMacSettings() == nil || len(dev.MacSettings.FactoryPresetFrequencies) == 0 { - return nil - } - return withPHY(func(phy *band.Band, fp *frequencyplans.FrequencyPlan) error { - switch phy.CFListType { - case ttnpb.CFListType_FREQUENCIES: - // Factory preset frequencies in bands which provide frequencies as part of the CFList - // are interpreted as being used both for uplinks and downlinks. - for _, frequency := range dev.MacSettings.FactoryPresetFrequencies { - _, inSubBand := fp.FindSubBand(frequency) - for _, sb := range phy.SubBands { - if sb.MinFrequency <= frequency && frequency <= sb.MaxFrequency { - inSubBand = true - break - } - } - if !inSubBand { - return newInvalidFieldValueError(field) - } - } - case ttnpb.CFListType_CHANNEL_MASKS: - // Factory preset frequencies in bands which provide channel masks as part of the CFList - // are interpreted as enabling explicit uplink channels. - uplinkChannels := make(map[uint64]struct{}, len(phy.UplinkChannels)) - for _, ch := range phy.UplinkChannels { - uplinkChannels[ch.Frequency] = struct{}{} - } - for _, frequency := range dev.MacSettings.FactoryPresetFrequencies { - if _, ok := uplinkChannels[frequency]; !ok { - return newInvalidFieldValueError(field) - } - } - default: - panic("unreachable") - } - return nil - }) - }, - field, - ); err != nil { - return nil, err - } - } - - if hasPHYUpdate || st.HasSetField( - "mac_settings.ping_slot_frequency.value", - "supports_class_b", - ) { - if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - if !m["supports_class_b"].GetSupportsClassB() || - m["mac_settings.ping_slot_frequency.value"].GetMacSettings().GetPingSlotFrequency().GetValue() > 0 { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if len(phy.PingSlotFrequencies) == 0 { - return newInvalidFieldValueError("mac_settings.ping_slot_frequency.value") - } - return nil - }) - }, - "mac_settings.ping_slot_frequency.value", - "supports_class_b", - ); err != nil { - return nil, err - } - } - - if hasPHYUpdate || st.HasSetField( - "mac_settings.desired_ping_slot_frequency.value", - "supports_class_b", - ) { - if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - if !m["supports_class_b"].GetSupportsClassB() || - m["mac_settings.desired_ping_slot_frequency.value"].GetMacSettings().GetDesiredPingSlotFrequency().GetValue() > 0 { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if len(phy.PingSlotFrequencies) == 0 { - return newInvalidFieldValueError("mac_settings.desired_ping_slot_frequency.value") - } - return nil - }) - }, - "mac_settings.desired_ping_slot_frequency.value", - "supports_class_b", - ); err != nil { - return nil, err - } - } - - if hasPHYUpdate || st.HasSetField( - "mac_settings.beacon_frequency.value", - "supports_class_b", - ) { - if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - if !m["supports_class_b"].GetSupportsClassB() || - m["mac_settings.beacon_frequency.value"].GetMacSettings().GetBeaconFrequency().GetValue() > 0 { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if len(phy.Beacon.Frequencies) == 0 { - return newInvalidFieldValueError("mac_settings.beacon_frequency.value") - } - return nil - }) - }, - "mac_settings.beacon_frequency.value", - "supports_class_b", - ); err != nil { - return nil, err - } - } - - if hasPHYUpdate || st.HasSetField( - "mac_settings.desired_beacon_frequency.value", - "supports_class_b", - ) { - if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { - if !m["supports_class_b"].GetSupportsClassB() || - m["mac_settings.desired_beacon_frequency.value"].GetMacSettings().GetDesiredBeaconFrequency().GetValue() > 0 { - return nil - } - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if len(phy.Beacon.Frequencies) == 0 { - return newInvalidFieldValueError("mac_settings.desired_beacon_frequency.value") - } - return nil - }) - }, - "mac_settings.desired_beacon_frequency.value", - "supports_class_b", - ); err != nil { - return nil, err - } - } - - for p, isValid := range map[string]func(*ttnpb.EndDevice, *band.Band) bool{ - "mac_settings.use_adr.value": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return !dev.GetMacSettings().GetUseAdr().GetValue() || phy.SupportsDynamicADR - }, - "mac_state.current_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetMacState().GetCurrentParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex - }, - "mac_state.current_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetMacState().GetCurrentParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) - }, - "mac_state.current_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return len(dev.GetMacState().GetCurrentParameters().GetChannels()) <= int(phy.MaxUplinkChannels) - }, - "mac_state.desired_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetMacState().GetDesiredParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex - }, - "mac_state.desired_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetMacState().GetDesiredParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) - }, - "mac_state.desired_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return len(dev.GetMacState().GetDesiredParameters().GetChannels()) <= int(phy.MaxUplinkChannels) - }, - "pending_mac_state.current_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetPendingMacState().GetCurrentParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex - }, - "pending_mac_state.current_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetPendingMacState().GetCurrentParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) - }, - "pending_mac_state.current_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return len(dev.GetPendingMacState().GetCurrentParameters().GetChannels()) <= int(phy.MaxUplinkChannels) - }, - "pending_mac_state.desired_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetPendingMacState().GetDesiredParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex - }, - "pending_mac_state.desired_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return dev.GetPendingMacState().GetDesiredParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) - }, - "pending_mac_state.desired_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { - return len(dev.GetPendingMacState().GetDesiredParameters().GetChannels()) <= int(phy.MaxUplinkChannels) - }, - } { - if !hasPHYUpdate && !st.HasSetField(p) { - continue - } - p, isValid := p, isValid - if err := st.WithField(func(dev *ttnpb.EndDevice) error { - return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { - if !isValid(dev, phy) { - return newInvalidFieldValueError(p) - } - return nil - }) - }, p); err != nil { - return nil, err - } - } } - // Ensure ADR dynamic parameters are monotonic. - // If one of the extrema is missing, the other extrema is considered to be valid. - if err := st.ValidateSetFields(func(m map[string]*ttnpb.EndDevice) (bool, string) { - { - min := m["mac_settings.adr.mode.dynamic.min_data_rate_index.value"].GetMacSettings().GetAdr().GetDynamic().GetMinDataRateIndex() - max := m["mac_settings.adr.mode.dynamic.max_data_rate_index.value"].GetMacSettings().GetAdr().GetDynamic().GetMaxDataRateIndex() - - if min != nil && max != nil && max.Value < min.Value { - return false, "mac_settings.adr.mode.dynamic.max_data_rate_index.value" - } - } - { - min := m["mac_settings.adr.mode.dynamic.min_tx_power_index"].GetMacSettings().GetAdr().GetDynamic().GetMinTxPowerIndex() - max := m["mac_settings.adr.mode.dynamic.max_tx_power_index"].GetMacSettings().GetAdr().GetDynamic().GetMaxTxPowerIndex() - - if min != nil && max != nil && max.Value < min.Value { - return false, "mac_settings.adr.mode.dynamic.max_tx_power_index" - } - } - { - min := m["mac_settings.adr.mode.dynamic.min_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetMinNbTrans() - max := m["mac_settings.adr.mode.dynamic.max_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetMaxNbTrans() - - if min != nil && max != nil && max.Value < min.Value { - return false, "mac_settings.adr.mode.dynamic.max_nb_trans" - } - } - for drIdx := ttnpb.DataRateIndex_DATA_RATE_0; drIdx <= ttnpb.DataRateIndex_DATA_RATE_15; drIdx++ { - baseField := fmt.Sprintf("mac_settings.adr.mode.dynamic.overrides.data_rate_%d.", drIdx) - min := mac.DataRateIndexOverridesOf(m[baseField+"min_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetOverrides(), drIdx).GetMinNbTrans() // nolint: lll - max := mac.DataRateIndexOverridesOf(m[baseField+"max_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetOverrides(), drIdx).GetMaxNbTrans() // nolint: lll - - if min != nil && max != nil && max.Value < min.Value { - return false, baseField + "max_nb_trans" - } - } - return true, "" - }, - "mac_settings.adr.mode.dynamic.max_data_rate_index.value", - "mac_settings.adr.mode.dynamic.max_nb_trans", - "mac_settings.adr.mode.dynamic.max_tx_power_index", - "mac_settings.adr.mode.dynamic.min_data_rate_index.value", - "mac_settings.adr.mode.dynamic.min_nb_trans", - "mac_settings.adr.mode.dynamic.min_tx_power_index", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", - "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", - ); err != nil { + if err := validateADRDynamicParameters(st); err != nil { return nil, err } @@ -3465,7 +1356,7 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest if err != nil { return err } - macState, err := mac.NewState(st.Device, fps, ns.defaultMACSettings) + macState, err := mac.NewState(st.Device, fps, ns.defaultMACSettings, profile) if err != nil { return err } @@ -3620,7 +1511,7 @@ func (ns *NetworkServer) ResetFactoryDefaults(ctx context.Context, req *ttnpb.Re if err != nil { return nil, nil, err } - macState, err := mac.NewState(stored, fps, ns.defaultMACSettings) + macState, err := mac.NewState(stored, fps, ns.defaultMACSettings, &ttnpb.MACSettingsProfile{}) if err != nil { return nil, nil, err } diff --git a/pkg/networkserver/grpc_deviceregistry_test.go b/pkg/networkserver/grpc_deviceregistry_test.go index 66b7293b64..41a3d7658c 100644 --- a/pkg/networkserver/grpc_deviceregistry_test.go +++ b/pkg/networkserver/grpc_deviceregistry_test.go @@ -1167,7 +1167,7 @@ func TestDeviceRegistryResetFactoryDefaults(t *testing.T) { } var newErr error defaultMACSettings := test.Must(DefaultConfig.DefaultMACSettings.Parse()) - macState, newErr = mac.NewState(created, fps, defaultMACSettings) + macState, newErr = mac.NewState(created, fps, defaultMACSettings, &ttnpb.MACSettingsProfile{}) if newErr != nil { a.So(err, should.NotBeNil) a.So(err, should.HaveSameErrorDefinitionAs, newErr) diff --git a/pkg/networkserver/grpc_deviceregistry_validate.go b/pkg/networkserver/grpc_deviceregistry_validate.go new file mode 100644 index 0000000000..ca384cd1c2 --- /dev/null +++ b/pkg/networkserver/grpc_deviceregistry_validate.go @@ -0,0 +1,2296 @@ +// Copyright © 2025 The Things Network Foundation, The Things Industries B.V. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkserver + +import ( + "bytes" + "fmt" + + "go.thethings.network/lorawan-stack/v3/pkg/band" + "go.thethings.network/lorawan-stack/v3/pkg/frequencyplans" + . "go.thethings.network/lorawan-stack/v3/pkg/networkserver/internal" // nolint: revive, stylecheck + "go.thethings.network/lorawan-stack/v3/pkg/networkserver/mac" + "go.thethings.network/lorawan-stack/v3/pkg/specification/macspec" + "go.thethings.network/lorawan-stack/v3/pkg/ttnpb" + "go.thethings.network/lorawan-stack/v3/pkg/types" + "google.golang.org/protobuf/proto" +) + +// ifThenFuncFieldRight represents the RHS of a functional implication. +type ifThenFuncFieldRight struct { + Func func(m map[string]*ttnpb.EndDevice) (bool, string) + Fields []string +} + +var ( + ifZeroThenZeroFields = map[string][]string{ + "supports_join": { + "pending_mac_state.current_parameters.adr_ack_delay_exponent.value", + "pending_mac_state.current_parameters.adr_ack_limit_exponent.value", + "pending_mac_state.current_parameters.adr_data_rate_index", + "pending_mac_state.current_parameters.adr_nb_trans", + "pending_mac_state.current_parameters.adr_tx_power_index", + "pending_mac_state.current_parameters.beacon_frequency", + "pending_mac_state.current_parameters.channels", + "pending_mac_state.current_parameters.downlink_dwell_time.value", + "pending_mac_state.current_parameters.max_duty_cycle", + "pending_mac_state.current_parameters.max_eirp", + "pending_mac_state.current_parameters.ping_slot_data_rate_index_value.value", + "pending_mac_state.current_parameters.ping_slot_frequency", + "pending_mac_state.current_parameters.rejoin_count_periodicity", + "pending_mac_state.current_parameters.rejoin_time_periodicity", + "pending_mac_state.current_parameters.relay.mode.served.backoff", + "pending_mac_state.current_parameters.relay.mode.served.mode.always", + "pending_mac_state.current_parameters.relay.mode.served.mode.dynamic.smart_enable_level", + "pending_mac_state.current_parameters.relay.mode.served.mode.end_device_controlled", + "pending_mac_state.current_parameters.relay.mode.served.second_channel.ack_offset", + "pending_mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index", + "pending_mac_state.current_parameters.relay.mode.served.second_channel.frequency", + "pending_mac_state.current_parameters.relay.mode.served.serving_device_id", + "pending_mac_state.current_parameters.relay.mode.serving.cad_periodicity", + "pending_mac_state.current_parameters.relay.mode.serving.default_channel_index", + "pending_mac_state.current_parameters.relay.mode.serving.limits.join_requests.bucket_size", + "pending_mac_state.current_parameters.relay.mode.serving.limits.join_requests.reload_rate", + "pending_mac_state.current_parameters.relay.mode.serving.limits.notifications.bucket_size", + "pending_mac_state.current_parameters.relay.mode.serving.limits.notifications.reload_rate", + "pending_mac_state.current_parameters.relay.mode.serving.limits.overall.bucket_size", + "pending_mac_state.current_parameters.relay.mode.serving.limits.overall.reload_rate", + "pending_mac_state.current_parameters.relay.mode.serving.limits.reset_behavior", + "pending_mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", + "pending_mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", + "pending_mac_state.current_parameters.relay.mode.serving.second_channel.ack_offset", + "pending_mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index", + "pending_mac_state.current_parameters.relay.mode.serving.second_channel.frequency", + "pending_mac_state.current_parameters.relay.mode.serving.uplink_forwarding_rules", + "pending_mac_state.current_parameters.rx1_data_rate_offset", + "pending_mac_state.current_parameters.rx1_delay", + "pending_mac_state.current_parameters.rx2_data_rate_index", + "pending_mac_state.current_parameters.rx2_frequency", + "pending_mac_state.current_parameters.uplink_dwell_time.value", + "pending_mac_state.desired_parameters.adr_ack_delay_exponent.value", + "pending_mac_state.desired_parameters.adr_ack_limit_exponent.value", + "pending_mac_state.desired_parameters.adr_data_rate_index", + "pending_mac_state.desired_parameters.adr_nb_trans", + "pending_mac_state.desired_parameters.adr_tx_power_index", + "pending_mac_state.desired_parameters.beacon_frequency", + "pending_mac_state.desired_parameters.channels", + "pending_mac_state.desired_parameters.downlink_dwell_time.value", + "pending_mac_state.desired_parameters.max_duty_cycle", + "pending_mac_state.desired_parameters.max_eirp", + "pending_mac_state.desired_parameters.ping_slot_data_rate_index_value.value", + "pending_mac_state.desired_parameters.ping_slot_frequency", + "pending_mac_state.desired_parameters.rejoin_count_periodicity", + "pending_mac_state.desired_parameters.rejoin_time_periodicity", + "pending_mac_state.desired_parameters.relay.mode.served.backoff", + "pending_mac_state.desired_parameters.relay.mode.served.mode.always", + "pending_mac_state.desired_parameters.relay.mode.served.mode.dynamic.smart_enable_level", + "pending_mac_state.desired_parameters.relay.mode.served.mode.end_device_controlled", + "pending_mac_state.desired_parameters.relay.mode.served.second_channel.ack_offset", + "pending_mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index", + "pending_mac_state.desired_parameters.relay.mode.served.second_channel.frequency", + "pending_mac_state.desired_parameters.relay.mode.served.serving_device_id", + "pending_mac_state.desired_parameters.relay.mode.serving.cad_periodicity", + "pending_mac_state.desired_parameters.relay.mode.serving.default_channel_index", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.join_requests.bucket_size", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.join_requests.reload_rate", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.notifications.bucket_size", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.notifications.reload_rate", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.overall.bucket_size", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.overall.reload_rate", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.reset_behavior", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", + "pending_mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", + "pending_mac_state.desired_parameters.relay.mode.serving.second_channel.ack_offset", + "pending_mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index", + "pending_mac_state.desired_parameters.relay.mode.serving.second_channel.frequency", + "pending_mac_state.desired_parameters.relay.mode.serving.uplink_forwarding_rules", + "pending_mac_state.desired_parameters.rx1_data_rate_offset", + "pending_mac_state.desired_parameters.rx1_delay", + "pending_mac_state.desired_parameters.rx2_data_rate_index", + "pending_mac_state.desired_parameters.rx2_frequency", + "pending_mac_state.desired_parameters.uplink_dwell_time.value", + "pending_mac_state.device_class", + "pending_mac_state.last_adr_change_f_cnt_up", + "pending_mac_state.last_confirmed_downlink_at", + "pending_mac_state.last_dev_status_f_cnt_up", + "pending_mac_state.last_downlink_at", + "pending_mac_state.last_network_initiated_downlink_at", + "pending_mac_state.lorawan_version", + "pending_mac_state.pending_join_request.cf_list.ch_masks", + "pending_mac_state.pending_join_request.cf_list.freq", + "pending_mac_state.pending_join_request.cf_list.type", + "pending_mac_state.pending_join_request.downlink_settings.opt_neg", + "pending_mac_state.pending_join_request.downlink_settings.rx1_dr_offset", + "pending_mac_state.pending_join_request.downlink_settings.rx2_dr", + "pending_mac_state.pending_join_request.rx_delay", + "pending_mac_state.ping_slot_periodicity.value", + "pending_mac_state.queued_join_accept.correlation_ids", + "pending_mac_state.queued_join_accept.keys.app_s_key.encrypted_key", + "pending_mac_state.queued_join_accept.keys.app_s_key.kek_label", + "pending_mac_state.queued_join_accept.keys.app_s_key.key", + "pending_mac_state.queued_join_accept.keys.f_nwk_s_int_key.key", + "pending_mac_state.queued_join_accept.keys.nwk_s_enc_key.key", + "pending_mac_state.queued_join_accept.keys.s_nwk_s_int_key.key", + "pending_mac_state.queued_join_accept.keys.session_key_id", + "pending_mac_state.queued_join_accept.payload", + "pending_mac_state.queued_join_accept.request.cf_list.ch_masks", + "pending_mac_state.queued_join_accept.request.cf_list.freq", + "pending_mac_state.queued_join_accept.request.cf_list.type", + "pending_mac_state.queued_join_accept.request.dev_addr", + "pending_mac_state.queued_join_accept.request.downlink_settings.opt_neg", + "pending_mac_state.queued_join_accept.request.downlink_settings.rx1_dr_offset", + "pending_mac_state.queued_join_accept.request.downlink_settings.rx2_dr", + "pending_mac_state.queued_join_accept.request.net_id", + "pending_mac_state.queued_join_accept.request.rx_delay", + "pending_mac_state.recent_downlinks", + "pending_mac_state.recent_mac_command_identifiers", + "pending_mac_state.recent_uplinks", + "pending_mac_state.rejected_adr_data_rate_indexes", + "pending_mac_state.rejected_adr_tx_power_indexes", + "pending_mac_state.rejected_data_rate_ranges", + "pending_mac_state.rejected_frequencies", + "pending_mac_state.rx_windows_available", + "pending_session.dev_addr", + "pending_session.keys.f_nwk_s_int_key.key", + "pending_session.keys.nwk_s_enc_key.key", + "pending_session.keys.s_nwk_s_int_key.key", + "pending_session.keys.session_key_id", + "session.keys.session_key_id", + }, + } + + ifZeroThenNotZeroFields = map[string][]string{ + "supports_join": { + "session.dev_addr", + "session.keys.f_nwk_s_int_key.key", + // NOTE: LoRaWAN-version specific fields are validated within Set directly. + }, + } + + ifNotZeroThenZeroFields = map[string][]string{ + "multicast": { + "mac_settings.desired_relay.mode.served.backoff", + "mac_settings.desired_relay.mode.served.mode.always", + "mac_settings.desired_relay.mode.served.mode.dynamic.smart_enable_level", + "mac_settings.desired_relay.mode.served.mode.end_device_controlled", + "mac_settings.desired_relay.mode.served.second_channel.ack_offset", + "mac_settings.desired_relay.mode.served.second_channel.data_rate_index", + "mac_settings.desired_relay.mode.served.second_channel.frequency", + "mac_settings.desired_relay.mode.served.serving_device_id", + "mac_settings.desired_relay.mode.serving.cad_periodicity", + "mac_settings.desired_relay.mode.serving.default_channel_index", + "mac_settings.desired_relay.mode.serving.limits.join_requests.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.join_requests.reload_rate", + "mac_settings.desired_relay.mode.serving.limits.notifications.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.notifications.reload_rate", + "mac_settings.desired_relay.mode.serving.limits.overall.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.overall.reload_rate", + "mac_settings.desired_relay.mode.serving.limits.reset_behavior", + "mac_settings.desired_relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_settings.desired_relay.mode.serving.second_channel.ack_offset", + "mac_settings.desired_relay.mode.serving.second_channel.data_rate_index", + "mac_settings.desired_relay.mode.serving.second_channel.frequency", + "mac_settings.desired_relay.mode.serving.uplink_forwarding_rules", + "mac_settings.relay.mode.served.backoff", + "mac_settings.relay.mode.served.mode.always", + "mac_settings.relay.mode.served.mode.dynamic.smart_enable_level", + "mac_settings.relay.mode.served.mode.end_device_controlled", + "mac_settings.relay.mode.served.second_channel.ack_offset", + "mac_settings.relay.mode.served.second_channel.data_rate_index", + "mac_settings.relay.mode.served.second_channel.frequency", + "mac_settings.relay.mode.served.serving_device_id", + "mac_settings.relay.mode.serving.cad_periodicity", + "mac_settings.relay.mode.serving.default_channel_index", + "mac_settings.relay.mode.serving.limits.join_requests.bucket_size", + "mac_settings.relay.mode.serving.limits.join_requests.reload_rate", + "mac_settings.relay.mode.serving.limits.notifications.bucket_size", + "mac_settings.relay.mode.serving.limits.notifications.reload_rate", + "mac_settings.relay.mode.serving.limits.overall.bucket_size", + "mac_settings.relay.mode.serving.limits.overall.reload_rate", + "mac_settings.relay.mode.serving.limits.reset_behavior", + "mac_settings.relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_settings.relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_settings.relay.mode.serving.second_channel.ack_offset", + "mac_settings.relay.mode.serving.second_channel.data_rate_index", + "mac_settings.relay.mode.serving.second_channel.frequency", + "mac_settings.relay.mode.serving.uplink_forwarding_rules", + "mac_settings.schedule_downlinks.value", + "mac_state.current_parameters.relay.mode.served.backoff", + "mac_state.current_parameters.relay.mode.served.mode.always", + "mac_state.current_parameters.relay.mode.served.mode.dynamic.smart_enable_level", + "mac_state.current_parameters.relay.mode.served.mode.end_device_controlled", + "mac_state.current_parameters.relay.mode.served.second_channel.ack_offset", + "mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index", + "mac_state.current_parameters.relay.mode.served.second_channel.frequency", + "mac_state.current_parameters.relay.mode.served.serving_device_id", + "mac_state.current_parameters.relay.mode.serving.cad_periodicity", + "mac_state.current_parameters.relay.mode.serving.default_channel_index", + "mac_state.current_parameters.relay.mode.serving.limits.join_requests.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.join_requests.reload_rate", + "mac_state.current_parameters.relay.mode.serving.limits.notifications.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.notifications.reload_rate", + "mac_state.current_parameters.relay.mode.serving.limits.overall.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.overall.reload_rate", + "mac_state.current_parameters.relay.mode.serving.limits.reset_behavior", + "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_state.current_parameters.relay.mode.serving.second_channel.ack_offset", + "mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index", + "mac_state.current_parameters.relay.mode.serving.second_channel.frequency", + "mac_state.current_parameters.relay.mode.serving.uplink_forwarding_rules", + "mac_state.desired_parameters.relay.mode.served.backoff", + "mac_state.desired_parameters.relay.mode.served.mode.always", + "mac_state.desired_parameters.relay.mode.served.mode.dynamic.smart_enable_level", + "mac_state.desired_parameters.relay.mode.served.mode.end_device_controlled", + "mac_state.desired_parameters.relay.mode.served.second_channel.ack_offset", + "mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index", + "mac_state.desired_parameters.relay.mode.served.second_channel.frequency", + "mac_state.desired_parameters.relay.mode.served.serving_device_id", + "mac_state.desired_parameters.relay.mode.serving.cad_periodicity", + "mac_state.desired_parameters.relay.mode.serving.default_channel_index", + "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.limits.notifications.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.notifications.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.limits.overall.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.overall.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.limits.reset_behavior", + "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.second_channel.ack_offset", + "mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index", + "mac_state.desired_parameters.relay.mode.serving.second_channel.frequency", + "mac_state.desired_parameters.relay.mode.serving.uplink_forwarding_rules", + "mac_state.last_adr_change_f_cnt_up", + "mac_state.last_confirmed_downlink_at", + "mac_state.last_dev_status_f_cnt_up", + "mac_state.pending_application_downlink", + "mac_state.pending_requests", + "mac_state.queued_responses", + "mac_state.recent_mac_command_identifiers", + "mac_state.recent_uplinks", + "mac_state.rejected_adr_data_rate_indexes", + "mac_state.rejected_adr_tx_power_indexes", + "mac_state.rejected_data_rate_ranges", + "mac_state.rejected_frequencies", + "mac_state.rx_windows_available", + "session.last_conf_f_cnt_down", + "session.last_f_cnt_up", + "supports_join", + }, + } + + ifNotZeroThenNotZeroFields = map[string][]string{ + "supports_join": { + "ids.dev_eui", + "ids.join_eui", + }, + } + + ifZeroThenFuncFields = map[string][]ifThenFuncFieldRight{ + "supports_join": { + { + Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { + if dev, ok := m["ids.dev_eui"]; ok && !types.MustEUI64(dev.Ids.DevEui).OrZero().IsZero() { + return true, "" + } + if m["lorawan_version"].GetLorawanVersion() == ttnpb.MACVersion_MAC_UNKNOWN { + return false, "lorawan_version" + } + if macspec.RequireDevEUIForABP(m["lorawan_version"].LorawanVersion) && !m["multicast"].GetMulticast() { + return false, "ids.dev_eui" + } + return true, "" + }, + Fields: []string{ + "ids.dev_eui", + "lorawan_version", + "multicast", + }, + }, + + { + Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { + if !m["supports_class_b"].GetSupportsClassB() || + m["mac_settings.ping_slot_periodicity.value"].GetMacSettings().GetPingSlotPeriodicity() != nil { + return true, "" + } + return false, "mac_settings.ping_slot_periodicity.value" + }, + Fields: []string{ + "mac_settings.ping_slot_periodicity.value", + "supports_class_b", + }, + }, + }, + } + + ifNotZeroThenFuncFields = map[string][]ifThenFuncFieldRight{ + "multicast": append(func() (rs []ifThenFuncFieldRight) { + for s, eq := range map[string]func(*ttnpb.MACParameters, *ttnpb.MACParameters) bool{ + "adr_ack_delay_exponent.value": func(a, b *ttnpb.MACParameters) bool { + return proto.Equal(a.AdrAckDelayExponent, b.AdrAckDelayExponent) + }, + "adr_ack_limit_exponent.value": func(a, b *ttnpb.MACParameters) bool { + return proto.Equal(a.AdrAckLimitExponent, b.AdrAckLimitExponent) + }, + "adr_data_rate_index": func(a, b *ttnpb.MACParameters) bool { + return a.AdrDataRateIndex == b.AdrDataRateIndex + }, + "adr_nb_trans": func(a, b *ttnpb.MACParameters) bool { + return a.AdrNbTrans == b.AdrNbTrans + }, + "adr_tx_power_index": func(a, b *ttnpb.MACParameters) bool { + return a.AdrTxPowerIndex == b.AdrTxPowerIndex + }, + "beacon_frequency": func(a, b *ttnpb.MACParameters) bool { + return a.BeaconFrequency == b.BeaconFrequency + }, + "channels": func(a, b *ttnpb.MACParameters) bool { + if len(a.Channels) != len(b.Channels) { + return false + } + for i, ch := range a.Channels { + if !proto.Equal(ch, b.Channels[i]) { + return false + } + } + return true + }, + "downlink_dwell_time.value": func(a, b *ttnpb.MACParameters) bool { + return proto.Equal(a.DownlinkDwellTime, b.DownlinkDwellTime) + }, + "max_duty_cycle": func(a, b *ttnpb.MACParameters) bool { + return a.MaxDutyCycle == b.MaxDutyCycle + }, + "max_eirp": func(a, b *ttnpb.MACParameters) bool { + return a.MaxEirp == b.MaxEirp + }, + "ping_slot_data_rate_index_value.value": func(a, b *ttnpb.MACParameters) bool { + return proto.Equal(a.PingSlotDataRateIndexValue, b.PingSlotDataRateIndexValue) + }, + "ping_slot_frequency": func(a, b *ttnpb.MACParameters) bool { + return a.PingSlotFrequency == b.PingSlotFrequency + }, + "rejoin_count_periodicity": func(a, b *ttnpb.MACParameters) bool { + return a.RejoinCountPeriodicity == b.RejoinCountPeriodicity + }, + "rejoin_time_periodicity": func(a, b *ttnpb.MACParameters) bool { + return a.RejoinTimePeriodicity == b.RejoinTimePeriodicity + }, + "rx1_data_rate_offset": func(a, b *ttnpb.MACParameters) bool { + return a.Rx1DataRateOffset == b.Rx1DataRateOffset + }, + "rx1_delay": func(a, b *ttnpb.MACParameters) bool { + return a.Rx1Delay == b.Rx1Delay + }, + "rx2_data_rate_index": func(a, b *ttnpb.MACParameters) bool { + return a.Rx2DataRateIndex == b.Rx2DataRateIndex + }, + "rx2_frequency": func(a, b *ttnpb.MACParameters) bool { + return a.Rx2Frequency == b.Rx2Frequency + }, + "uplink_dwell_time.value": func(a, b *ttnpb.MACParameters) bool { + return proto.Equal(a.UplinkDwellTime, b.UplinkDwellTime) + }, + } { + curPath := "mac_state.current_parameters." + s + desPath := "mac_state.desired_parameters." + s + rs = append(rs, ifThenFuncFieldRight{ + Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { + curDev := m[curPath] + desDev := m[desPath] + if curDev == nil || desDev == nil { + if curDev != desDev { + return false, desPath + } + return true, "" + } + if !eq(curDev.MacState.CurrentParameters, desDev.MacState.DesiredParameters) { + return false, desPath + } + return true, "" + }, + Fields: []string{ + curPath, + desPath, + }, + }) + } + return rs + }(), + + ifThenFuncFieldRight{ + Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { + if !m["supports_class_b"].GetSupportsClassB() && !m["supports_class_c"].GetSupportsClassC() { + return false, "supports_class_b" + } + return true, "" + }, + Fields: []string{ + "supports_class_b", + "supports_class_c", + }, + }, + + ifThenFuncFieldRight{ + Func: func(m map[string]*ttnpb.EndDevice) (bool, string) { + if !m["supports_class_b"].GetSupportsClassB() || + m["mac_settings.ping_slot_periodicity.value"].GetMacSettings().GetPingSlotPeriodicity() != nil { + return true, "" + } + return false, "mac_settings.ping_slot_periodicity.value" + }, + Fields: []string{ + "mac_settings.ping_slot_periodicity.value", + "supports_class_b", + }, + }, + ), + } + + // The downlinkInfluencingSetFields contains fields that can influence downlink scheduling, + // e.g. trigger one or make a scheduled slot obsolete. + downlinkInfluencingSetFields = [...]string{ + "last_dev_status_received_at", + "mac_settings.schedule_downlinks.value", + "mac_state.current_parameters.adr_ack_delay_exponent.value", + "mac_state.current_parameters.adr_ack_limit_exponent.value", + "mac_state.current_parameters.adr_data_rate_index", + "mac_state.current_parameters.adr_nb_trans", + "mac_state.current_parameters.adr_tx_power_index", + "mac_state.current_parameters.beacon_frequency", + "mac_state.current_parameters.channels", + "mac_state.current_parameters.downlink_dwell_time.value", + "mac_state.current_parameters.max_duty_cycle", + "mac_state.current_parameters.max_eirp", + "mac_state.current_parameters.ping_slot_data_rate_index_value.value", + "mac_state.current_parameters.ping_slot_frequency", + "mac_state.current_parameters.rejoin_count_periodicity", + "mac_state.current_parameters.rejoin_time_periodicity", + "mac_state.current_parameters.relay.mode.served.backoff", + "mac_state.current_parameters.relay.mode.served.mode.always", + "mac_state.current_parameters.relay.mode.served.mode.dynamic.smart_enable_level", + "mac_state.current_parameters.relay.mode.served.mode.end_device_controlled", + "mac_state.current_parameters.relay.mode.served.second_channel.ack_offset", + "mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index", + "mac_state.current_parameters.relay.mode.served.second_channel.frequency", + "mac_state.current_parameters.relay.mode.serving.cad_periodicity", + "mac_state.current_parameters.relay.mode.serving.default_channel_index", + "mac_state.current_parameters.relay.mode.serving.limits.join_requests.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.join_requests.reload_rate", + "mac_state.current_parameters.relay.mode.serving.limits.notifications.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.notifications.reload_rate", + "mac_state.current_parameters.relay.mode.serving.limits.overall.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.overall.reload_rate", + "mac_state.current_parameters.relay.mode.serving.limits.reset_behavior", + "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_state.current_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_state.current_parameters.relay.mode.serving.second_channel.ack_offset", + "mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index", + "mac_state.current_parameters.relay.mode.serving.second_channel.frequency", + "mac_state.current_parameters.relay.mode.serving.uplink_forwarding_rules", + "mac_state.current_parameters.rx1_data_rate_offset", + "mac_state.current_parameters.rx1_delay", + "mac_state.current_parameters.rx2_data_rate_index", + "mac_state.current_parameters.rx2_frequency", + "mac_state.current_parameters.uplink_dwell_time.value", + "mac_state.desired_parameters.adr_ack_delay_exponent.value", + "mac_state.desired_parameters.adr_ack_limit_exponent.value", + "mac_state.desired_parameters.adr_data_rate_index", + "mac_state.desired_parameters.adr_nb_trans", + "mac_state.desired_parameters.adr_tx_power_index", + "mac_state.desired_parameters.beacon_frequency", + "mac_state.desired_parameters.channels", + "mac_state.desired_parameters.downlink_dwell_time.value", + "mac_state.desired_parameters.max_duty_cycle", + "mac_state.desired_parameters.max_eirp", + "mac_state.desired_parameters.ping_slot_data_rate_index_value.value", + "mac_state.desired_parameters.ping_slot_frequency", + "mac_state.desired_parameters.rejoin_count_periodicity", + "mac_state.desired_parameters.rejoin_time_periodicity", + "mac_state.desired_parameters.relay.mode.served.backoff", + "mac_state.desired_parameters.relay.mode.served.mode.always", + "mac_state.desired_parameters.relay.mode.served.mode.dynamic.smart_enable_level", + "mac_state.desired_parameters.relay.mode.served.mode.end_device_controlled", + "mac_state.desired_parameters.relay.mode.served.second_channel.ack_offset", + "mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index", + "mac_state.desired_parameters.relay.mode.served.second_channel.frequency", + "mac_state.desired_parameters.relay.mode.serving.cad_periodicity", + "mac_state.desired_parameters.relay.mode.serving.default_channel_index", + "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.join_requests.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.limits.notifications.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.notifications.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.limits.overall.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.overall.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.limits.reset_behavior", + "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_state.desired_parameters.relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_state.desired_parameters.relay.mode.serving.second_channel.ack_offset", + "mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index", + "mac_state.desired_parameters.relay.mode.serving.second_channel.frequency", + "mac_state.desired_parameters.relay.mode.serving.uplink_forwarding_rules", + "mac_state.desired_parameters.rx1_data_rate_offset", + "mac_state.desired_parameters.rx1_delay", + "mac_state.desired_parameters.rx2_data_rate_index", + "mac_state.desired_parameters.rx2_frequency", + "mac_state.desired_parameters.uplink_dwell_time.value", + "mac_state.device_class", + "mac_state.last_confirmed_downlink_at", + "mac_state.last_dev_status_f_cnt_up", + "mac_state.last_downlink_at", + "mac_state.last_network_initiated_downlink_at", + "mac_state.lorawan_version", + "mac_state.ping_slot_periodicity.value", + "mac_state.queued_responses", + "mac_state.recent_mac_command_identifiers", + "mac_state.recent_uplinks", + "mac_state.rejected_adr_data_rate_indexes", + "mac_state.rejected_adr_tx_power_indexes", + "mac_state.rejected_data_rate_ranges", + "mac_state.rejected_frequencies", + "mac_state.rx_windows_available", + } + + legacyADRSettingsFields = []string{ + "mac_settings.adr_margin", + "mac_settings.use_adr.value", + "mac_settings.use_adr", + } + + adrSettingsFields = []string{ + "mac_settings.adr.mode.disabled", + "mac_settings.adr.mode.dynamic.channel_steering.mode.disabled", + "mac_settings.adr.mode.dynamic.channel_steering.mode.lora_narrow", + "mac_settings.adr.mode.dynamic.channel_steering.mode", + "mac_settings.adr.mode.dynamic.channel_steering", + "mac_settings.adr.mode.dynamic.margin", + "mac_settings.adr.mode.dynamic.max_data_rate_index.value", + "mac_settings.adr.mode.dynamic.max_data_rate_index", + "mac_settings.adr.mode.dynamic.max_nb_trans", + "mac_settings.adr.mode.dynamic.max_tx_power_index", + "mac_settings.adr.mode.dynamic.min_data_rate_index.value", + "mac_settings.adr.mode.dynamic.min_data_rate_index", + "mac_settings.adr.mode.dynamic.min_nb_trans", + "mac_settings.adr.mode.dynamic.min_tx_power_index", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9", + "mac_settings.adr.mode.dynamic.overrides", + "mac_settings.adr.mode.dynamic", + "mac_settings.adr.mode.static.data_rate_index", + "mac_settings.adr.mode.static.nb_trans", + "mac_settings.adr.mode.static.tx_power_index", + "mac_settings.adr.mode.static", + "mac_settings.adr.mode", + "mac_settings.adr", + } + + dynamicADRSettingsFields = []string{ + "mac_settings.adr.mode.dynamic.channel_steering.mode.disabled", + "mac_settings.adr.mode.dynamic.channel_steering.mode.lora_narrow", + "mac_settings.adr.mode.dynamic.channel_steering.mode", + "mac_settings.adr.mode.dynamic.channel_steering", + "mac_settings.adr.mode.dynamic.margin", + "mac_settings.adr.mode.dynamic.max_data_rate_index.value", + "mac_settings.adr.mode.dynamic.max_nb_trans", + "mac_settings.adr.mode.dynamic.max_tx_power_index", + "mac_settings.adr.mode.dynamic.min_data_rate_index.value", + "mac_settings.adr.mode.dynamic.min_nb_trans", + "mac_settings.adr.mode.dynamic.min_tx_power_index", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", + "mac_settings.adr.mode.dynamic", + } + + macSettingsFields = []string{ + "mac_settings", + "mac_settings.adr", + "mac_settings.adr.mode", + "mac_settings.adr.mode.disabled", + "mac_settings.adr.mode.dynamic", + "mac_settings.adr.mode.dynamic.channel_steering", + "mac_settings.adr.mode.dynamic.channel_steering.mode", + "mac_settings.adr.mode.dynamic.channel_steering.mode.disabled", + "mac_settings.adr.mode.dynamic.channel_steering.mode.lora_narrow", + "mac_settings.adr.mode.dynamic.margin", + "mac_settings.adr.mode.dynamic.max_data_rate_index", + "mac_settings.adr.mode.dynamic.max_data_rate_index.value", + "mac_settings.adr.mode.dynamic.max_nb_trans", + "mac_settings.adr.mode.dynamic.max_tx_power_index", + "mac_settings.adr.mode.dynamic.min_data_rate_index", + "mac_settings.adr.mode.dynamic.min_data_rate_index.value", + "mac_settings.adr.mode.dynamic.min_nb_trans", + "mac_settings.adr.mode.dynamic.min_tx_power_index", + "mac_settings.adr.mode.dynamic.overrides", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", + "mac_settings.adr.mode.static", + "mac_settings.adr.mode.static.data_rate_index", + "mac_settings.adr.mode.static.nb_trans", + "mac_settings.adr.mode.static.tx_power_index", + "mac_settings.adr_margin", + "mac_settings.beacon_frequency", + "mac_settings.beacon_frequency.value", + "mac_settings.class_b_c_downlink_interval", + "mac_settings.class_b_timeout", + "mac_settings.class_c_timeout", + "mac_settings.desired_adr_ack_delay_exponent", + "mac_settings.desired_adr_ack_delay_exponent.value", + "mac_settings.desired_adr_ack_limit_exponent", + "mac_settings.desired_adr_ack_limit_exponent.value", + "mac_settings.desired_beacon_frequency", + "mac_settings.desired_beacon_frequency.value", + "mac_settings.desired_max_duty_cycle", + "mac_settings.desired_max_duty_cycle.value", + "mac_settings.desired_max_eirp", + "mac_settings.desired_max_eirp.value", + "mac_settings.desired_ping_slot_data_rate_index", + "mac_settings.desired_ping_slot_data_rate_index.value", + "mac_settings.desired_ping_slot_frequency", + "mac_settings.desired_ping_slot_frequency.value", + "mac_settings.desired_relay", + "mac_settings.desired_relay.mode", + "mac_settings.desired_relay.mode.served", + "mac_settings.desired_relay.mode.served.backoff", + "mac_settings.desired_relay.mode.served.mode", + "mac_settings.desired_relay.mode.served.mode.always", + "mac_settings.desired_relay.mode.served.mode.dynamic", + "mac_settings.desired_relay.mode.served.mode.dynamic.smart_enable_level", + "mac_settings.desired_relay.mode.served.mode.end_device_controlled", + "mac_settings.desired_relay.mode.served.second_channel", + "mac_settings.desired_relay.mode.served.second_channel.ack_offset", + "mac_settings.desired_relay.mode.served.second_channel.data_rate_index", + "mac_settings.desired_relay.mode.served.second_channel.frequency", + "mac_settings.desired_relay.mode.served.serving_device_id", + "mac_settings.desired_relay.mode.serving", + "mac_settings.desired_relay.mode.serving.cad_periodicity", + "mac_settings.desired_relay.mode.serving.default_channel_index", + "mac_settings.desired_relay.mode.serving.limits", + "mac_settings.desired_relay.mode.serving.limits.join_requests", + "mac_settings.desired_relay.mode.serving.limits.join_requests.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.join_requests.reload_rate", + "mac_settings.desired_relay.mode.serving.limits.notifications", + "mac_settings.desired_relay.mode.serving.limits.notifications.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.notifications.reload_rate", + "mac_settings.desired_relay.mode.serving.limits.overall", + "mac_settings.desired_relay.mode.serving.limits.overall.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.overall.reload_rate", + "mac_settings.desired_relay.mode.serving.limits.reset_behavior", + "mac_settings.desired_relay.mode.serving.limits.uplink_messages", + "mac_settings.desired_relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_settings.desired_relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_settings.desired_relay.mode.serving.second_channel", + "mac_settings.desired_relay.mode.serving.second_channel.ack_offset", + "mac_settings.desired_relay.mode.serving.second_channel.data_rate_index", + "mac_settings.desired_relay.mode.serving.second_channel.frequency", + "mac_settings.desired_relay.mode.serving.uplink_forwarding_rules", + "mac_settings.desired_rx1_data_rate_offset", + "mac_settings.desired_rx1_data_rate_offset.value", + "mac_settings.desired_rx1_delay", + "mac_settings.desired_rx1_delay.value", + "mac_settings.desired_rx2_data_rate_index", + "mac_settings.desired_rx2_data_rate_index.value", + "mac_settings.desired_rx2_frequency", + "mac_settings.desired_rx2_frequency.value", + "mac_settings.downlink_dwell_time", + "mac_settings.downlink_dwell_time.value", + "mac_settings.factory_preset_frequencies", + "mac_settings.max_duty_cycle", + "mac_settings.max_duty_cycle.value", + "mac_settings.ping_slot_data_rate_index", + "mac_settings.ping_slot_data_rate_index.value", + "mac_settings.ping_slot_frequency", + "mac_settings.ping_slot_frequency.value", + "mac_settings.ping_slot_periodicity", + "mac_settings.ping_slot_periodicity.value", + "mac_settings.relay", + "mac_settings.relay.mode", + "mac_settings.relay.mode.served", + "mac_settings.relay.mode.served.backoff", + "mac_settings.relay.mode.served.mode", + "mac_settings.relay.mode.served.mode.always", + "mac_settings.relay.mode.served.mode.dynamic", + "mac_settings.relay.mode.served.mode.dynamic.smart_enable_level", + "mac_settings.relay.mode.served.mode.end_device_controlled", + "mac_settings.relay.mode.served.second_channel", + "mac_settings.relay.mode.served.second_channel.ack_offset", + "mac_settings.relay.mode.served.second_channel.data_rate_index", + "mac_settings.relay.mode.served.second_channel.frequency", + "mac_settings.relay.mode.served.serving_device_id", + "mac_settings.relay.mode.serving", + "mac_settings.relay.mode.serving.cad_periodicity", + "mac_settings.relay.mode.serving.default_channel_index", + "mac_settings.relay.mode.serving.limits", + "mac_settings.relay.mode.serving.limits.join_requests", + "mac_settings.relay.mode.serving.limits.join_requests.bucket_size", + "mac_settings.relay.mode.serving.limits.join_requests.reload_rate", + "mac_settings.relay.mode.serving.limits.notifications", + "mac_settings.relay.mode.serving.limits.notifications.bucket_size", + "mac_settings.relay.mode.serving.limits.notifications.reload_rate", + "mac_settings.relay.mode.serving.limits.overall", + "mac_settings.relay.mode.serving.limits.overall.bucket_size", + "mac_settings.relay.mode.serving.limits.overall.reload_rate", + "mac_settings.relay.mode.serving.limits.reset_behavior", + "mac_settings.relay.mode.serving.limits.uplink_messages", + "mac_settings.relay.mode.serving.limits.uplink_messages.bucket_size", + "mac_settings.relay.mode.serving.limits.uplink_messages.reload_rate", + "mac_settings.relay.mode.serving.second_channel", + "mac_settings.relay.mode.serving.second_channel.ack_offset", + "mac_settings.relay.mode.serving.second_channel.data_rate_index", + "mac_settings.relay.mode.serving.second_channel.frequency", + "mac_settings.relay.mode.serving.uplink_forwarding_rules", + "mac_settings.resets_f_cnt", + "mac_settings.resets_f_cnt.value", + "mac_settings.rx1_data_rate_offset", + "mac_settings.rx1_data_rate_offset.value", + "mac_settings.rx1_delay", + "mac_settings.rx1_delay.value", + "mac_settings.rx2_data_rate_index", + "mac_settings.rx2_data_rate_index.value", + "mac_settings.rx2_frequency", + "mac_settings.rx2_frequency.value", + "mac_settings.schedule_downlinks", + "mac_settings.schedule_downlinks.value", + "mac_settings.status_count_periodicity", + "mac_settings.status_time_periodicity", + "mac_settings.supports_32_bit_f_cnt", + "mac_settings.supports_32_bit_f_cnt.value", + "mac_settings.uplink_dwell_time", + "mac_settings.uplink_dwell_time.value", + "mac_settings.use_adr", + "mac_settings.use_adr.value", + } +) + +// Ensure ids.dev_addr and session.dev_addr are consistent. +func validateADR(st *setDeviceState) error { + if st.HasSetField("ids.dev_addr") { + if err := st.ValidateField(func(dev *ttnpb.EndDevice) bool { + if st.Device.Ids.DevAddr == nil { + return dev.GetSession() == nil + } + return dev.GetSession() != nil && bytes.Equal(dev.Session.DevAddr, st.Device.Ids.DevAddr) + }, "session.dev_addr"); err != nil { + return err + } + } else if st.HasSetField("session.dev_addr") { + st.Device.Ids.DevAddr = nil + if devAddr := types.MustDevAddr(st.Device.GetSession().GetDevAddr()); devAddr != nil { + st.Device.Ids.DevAddr = devAddr.Bytes() + } + st.AddSetFields( + "ids.dev_addr", + ) + } + return nil +} + +func validateZeroFields(st *setDeviceState) error { // nolint: gocyclo + // Ensure FieldIsZero(left) -> FieldIsZero(r), for each r in right. + for left, right := range ifZeroThenZeroFields { + if st.HasSetField(left) { + if !st.Device.FieldIsZero(left) { + continue + } + if err := st.ValidateFieldsAreZero(right...); err != nil { + return err + } + } + for _, r := range right { + if !st.HasSetField(r) || st.Device.FieldIsZero(r) { + continue + } + if err := st.ValidateFieldIsNotZero(left); err != nil { + return err + } + } + } + + // Ensure FieldIsZero(left) -> !FieldIsZero(r), for each r in right. + for left, right := range ifZeroThenNotZeroFields { + if st.HasSetField(left) { + if !st.Device.FieldIsZero(left) { + continue + } + if err := st.ValidateFieldsAreNotZero(right...); err != nil { + return err + } + } + for _, r := range right { + if !st.HasSetField(r) || !st.Device.FieldIsZero(r) { + continue + } + if err := st.ValidateFieldIsNotZero(left); err != nil { + return err + } + } + } + + // Ensure FieldIsZero(left) -> r.Func(map rr -> *ttnpb.EndDevice), for each rr in r.Fields for each r in rs. + for left, rs := range ifZeroThenFuncFields { + for _, r := range rs { + if st.HasSetField(left) { + if !st.Device.FieldIsZero(left) { + continue + } + if err := st.ValidateFields(r.Func, r.Fields...); err != nil { + return err + } + } + if !st.HasSetField(r.Fields...) { + continue + } + + if err := st.ValidateFields(func(m map[string]*ttnpb.EndDevice) (bool, string) { + if !m[left].FieldIsZero(left) { + return true, "" + } + return r.Func(m) + }, append([]string{left}, r.Fields...)...); err != nil { + return err + } + } + } + + // Ensure !FieldIsZero(left) -> FieldIsZero(r), for each r in right. + for left, right := range ifNotZeroThenZeroFields { + if st.HasSetField(left) { + if st.Device.FieldIsZero(left) { + continue + } + if err := st.ValidateFieldsAreZero(right...); err != nil { + return err + } + } + for _, r := range right { + if !st.HasSetField(r) || st.Device.FieldIsZero(r) { + continue + } + if err := st.ValidateFieldIsZero(left); err != nil { + return err + } + } + } + + // Ensure !FieldIsZero(left) -> !FieldIsZero(r), for each r in right. + for left, right := range ifNotZeroThenNotZeroFields { + if st.HasSetField(left) { + if st.Device.FieldIsZero(left) { + continue + } + if err := st.ValidateFieldsAreNotZero(right...); err != nil { + return err + } + } + for _, r := range right { + if !st.HasSetField(r) || !st.Device.FieldIsZero(r) { + continue + } + if err := st.ValidateFieldIsZero(left); err != nil { + return err + } + } + } + + // Ensure !FieldIsZero(left) -> r.Func(map rr -> *ttnpb.EndDevice), for each rr in r.Fields for each r in rs. + for left, rs := range ifNotZeroThenFuncFields { + for _, r := range rs { + if st.HasSetField(left) { + if st.Device.FieldIsZero(left) { + continue + } + if err := st.ValidateFields(r.Func, r.Fields...); err != nil { + return err + } + } + if !st.HasSetField(r.Fields...) { + continue + } + + if err := st.ValidateFields(func(m map[string]*ttnpb.EndDevice) (bool, string) { + if m[left].FieldIsZero(left) { + return true, "" + } + return r.Func(m) + }, append([]string{left}, r.Fields...)...); err != nil { + return err + } + } + } + return nil +} + +func validateProfile( // nolint: gocyclo + profile *ttnpb.MACSettingsProfile, + st *setDeviceState, + fps *frequencyplans.Store, +) error { + fp, phy, err := DeviceFrequencyPlanAndBand(st.Device, fps) + if err != nil { + return err + } + if profile.GetMacSettings().GetRx2DataRateIndex() != nil { + _, ok := phy.DataRates[profile.MacSettings.Rx2DataRateIndex.Value] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.rx2_data_rate_index.value") + } + } + if profile.GetMacSettings().GetDesiredRx2DataRateIndex() != nil { + _, ok := phy.DataRates[profile.MacSettings.DesiredRx2DataRateIndex.Value] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_rx2_data_rate_index.value") + } + } + if profile.GetMacSettings().GetDesiredPingSlotDataRateIndex() != nil { + _, ok := phy.DataRates[profile.MacSettings.DesiredPingSlotDataRateIndex.Value] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_ping_slot_data_rate_index.value") + } + } + if profile.GetMacSettings().GetDesiredRelay().GetServed().GetSecondChannel() != nil { + _, ok := phy.DataRates[profile.MacSettings.DesiredRelay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_relay.served.second_channel.data_rate_index") // nolint: lll + } + } + if chIdx := profile.GetMacSettings().GetDesiredRelay().GetServing().GetDefaultChannelIndex(); chIdx != nil { + if chIdx.Value >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_relay.serving.default_channel_index") + } + } + if profile.GetMacSettings().GetDesiredRelay().GetServing().GetSecondChannel() != nil { + _, ok := phy.DataRates[profile.MacSettings.DesiredRelay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_relay.serving.second_channel.data_rate_index") // nolint: lll + } + } + if profile.GetMacSettings().GetAdr().GetDynamic().GetMaxDataRateIndex() != nil { + drIdx := profile.MacSettings.Adr.GetDynamic().MaxDataRateIndex.Value + _, ok := phy.DataRates[drIdx] + if !ok || drIdx > phy.MaxADRDataRateIndex { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.dynamic.max_data_rate_index") + } + } + if profile.GetMacSettings().GetAdr().GetDynamic().GetMinDataRateIndex() != nil { + drIdx := profile.MacSettings.Adr.GetDynamic().MinDataRateIndex.Value + _, ok := phy.DataRates[drIdx] + if !ok || drIdx > phy.MaxADRDataRateIndex { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.dynamic.min_data_rate_index") + } + } + if profile.GetMacSettings().GetAdr().GetDynamic().GetMaxTxPowerIndex() != nil { + if profile.MacSettings.Adr.GetDynamic().MaxTxPowerIndex.Value > uint32(phy.MaxTxPowerIndex()) { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.dynamic.max_tx_power_index") + } + } + if profile.GetMacSettings().GetAdr().GetDynamic().GetMinTxPowerIndex() != nil { + if profile.MacSettings.Adr.GetDynamic().MinTxPowerIndex.Value > uint32(phy.MaxTxPowerIndex()) { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.dynamic.min_tx_power_index") + } + } + if !phy.SupportsDynamicADR { + if profile.GetMacSettings().GetAdr().GetDynamic() != nil { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.dynamic") + } + } else { + if profile.GetMacSettings().GetAdr().GetDynamic() != nil { + minDRI := profile.GetMacSettings().GetAdr().GetDynamic().GetMinDataRateIndex() + maxDRI := profile.GetMacSettings().GetAdr().GetDynamic().GetMaxDataRateIndex() + if minDRI != nil && maxDRI != nil && maxDRI.Value < minDRI.Value { + return newInvalidFieldValueError("mac_settings.adr.mode.dynamic.max_data_rate_index.value") + } + + minNbTrans := profile.GetMacSettings().GetAdr().GetDynamic().GetMinNbTrans() + maxNbTrans := profile.GetMacSettings().GetAdr().GetDynamic().GetMaxNbTrans() + if minNbTrans != nil && maxNbTrans != nil && maxNbTrans.Value < minNbTrans.Value { + return newInvalidFieldValueError("mac_settings.adr.mode.dynamic.max_nb_trans") + } + + for drIdx := ttnpb.DataRateIndex_DATA_RATE_0; drIdx <= ttnpb.DataRateIndex_DATA_RATE_15; drIdx++ { + minOverrides := mac.DataRateIndexOverridesOf(profile.GetMacSettings().GetAdr().GetDynamic().GetOverrides(), drIdx).GetMinNbTrans() // nolint: lll + maxOverrides := mac.DataRateIndexOverridesOf(profile.GetMacSettings().GetAdr().GetDynamic().GetOverrides(), drIdx).GetMaxNbTrans() // nolint: lll + if minOverrides != nil && maxOverrides != nil && maxOverrides.Value < minOverrides.Value { + return newInvalidFieldValueError(fmt.Sprintf("mac_settings.adr.mode.dynamic.overrides.data_rate_%d.max_nb_trans", drIdx)) // nolint: lll + } + } + } + } + if profile.GetMacSettings().GetAdr().GetStatic() != nil { + _, ok := phy.DataRates[profile.MacSettings.Adr.GetStatic().DataRateIndex] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.static.data_rate_index") + } + if profile.MacSettings.Adr.GetStatic().TxPowerIndex > uint32(phy.MaxTxPowerIndex()) { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.adr.mode.static.tx_power_index") + } + } + if profile.GetMacSettings().GetUplinkDwellTime() != nil { + if !phy.TxParamSetupReqSupport { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.uplink_dwell_time.value") + } + } + if profile.GetMacSettings().GetDownlinkDwellTime() != nil { + if !phy.TxParamSetupReqSupport { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.downlink_dwell_time.value") + } + } + if profile.GetMacSettings().GetRelay().GetServed().GetSecondChannel() != nil { + _, ok := phy.DataRates[profile.MacSettings.Relay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.relay.served.second_channel.data_rate_index") + } + } + if chIdx := profile.GetMacSettings().GetRelay().GetServing().GetDefaultChannelIndex(); chIdx != nil { + if chIdx.Value >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError("mac_settings_profile.mac_settings.relay.serving.default_channel_index") + } + } + if profile.GetMacSettings().GetRelay().GetServing().GetSecondChannel() != nil { + _, ok := phy.DataRates[profile.MacSettings.Relay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.relay.serving.second_channel.data_rate_index") + } + } + if len(profile.GetMacSettings().GetFactoryPresetFrequencies()) > 0 { + switch phy.CFListType { + case ttnpb.CFListType_FREQUENCIES: + // Factory preset frequencies in bands which provide frequencies as part of the CFList + // are interpreted as being used both for uplinks and downlinks. + for _, frequency := range profile.MacSettings.FactoryPresetFrequencies { + _, inSubBand := fp.FindSubBand(frequency) + for _, sb := range phy.SubBands { + if sb.MinFrequency <= frequency && frequency <= sb.MaxFrequency { + inSubBand = true + break + } + } + if !inSubBand { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.factory_preset_frequencies") + } + } + case ttnpb.CFListType_CHANNEL_MASKS: + // Factory preset frequencies in bands which provide channel masks as part of the CFList + // are interpreted as enabling explicit uplink channels. + uplinkChannels := make(map[uint64]struct{}, len(phy.UplinkChannels)) + for _, ch := range phy.UplinkChannels { + uplinkChannels[ch.Frequency] = struct{}{} + } + for _, frequency := range profile.MacSettings.FactoryPresetFrequencies { + if _, ok := uplinkChannels[frequency]; !ok { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.factory_preset_frequencies") + } + } + default: + panic("unreachable") + } + } + if st.Device.GetSupportsClassB() { + if profile.GetMacSettings().GetPingSlotFrequency().GetValue() == 0 { + if len(phy.PingSlotFrequencies) == 0 { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.ping_slot_frequency.value") + } + } + if profile.GetMacSettings().GetDesiredPingSlotFrequency().GetValue() == 0 { + if len(phy.PingSlotFrequencies) == 0 { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_ping_slot_frequency.value") + } + } + if profile.GetMacSettings().GetBeaconFrequency().GetValue() == 0 { + if len(phy.Beacon.Frequencies) == 0 { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.beacon_frequency.value") + } + } + if profile.GetMacSettings().GetDesiredBeaconFrequency().GetValue() == 0 { + if len(phy.Beacon.Frequencies) == 0 { + return newInvalidFieldValueError("mac_settings_profile.mac_settings.desired_beacon_frequency.value") + } + } + } + return nil +} + +func validateBandSpecifications(st *setDeviceState, fps *frequencyplans.Store) error { // nolint: gocyclo + var deferredPHYValidations []func(*band.Band, *frequencyplans.FrequencyPlan) error + withPHY := func(f func(*band.Band, *frequencyplans.FrequencyPlan) error) error { // nolint: unparam + deferredPHYValidations = append(deferredPHYValidations, f) + return nil + } + if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + fp, phy, err := DeviceFrequencyPlanAndBand(&ttnpb.EndDevice{ + FrequencyPlanId: m["frequency_plan_id"].GetFrequencyPlanId(), + LorawanPhyVersion: m["lorawan_phy_version"].GetLorawanPhyVersion(), + }, fps) + if err != nil { + return err + } + withPHY = func(f func(*band.Band, *frequencyplans.FrequencyPlan) error) error { + return f(phy, fp) + } + for _, f := range deferredPHYValidations { + if err := f(phy, fp); err != nil { + return err + } + } + return nil + }, + "frequency_plan_id", + "lorawan_phy_version", + ); err != nil { + return err + } + + hasPHYUpdate := st.HasSetField( + "frequency_plan_id", + "lorawan_phy_version", + ) + hasSetField := func(field string) (fieldToRetrieve string, validate bool) { + return field, st.HasSetField(field) || hasPHYUpdate + } + + setFields := func(fields ...string) []string { + setFields := make([]string, 0, len(fields)) + for _, field := range fields { + if st.HasSetField(field) { + setFields = append(setFields, field) + } + } + return setFields + } + + if st.HasSetField( + "frequency_plan_id", + "version_ids.band_id", + ) { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(_ *band.Band, fp *frequencyplans.FrequencyPlan) error { + if devBandID := dev.GetVersionIds().GetBandId(); devBandID != "" && devBandID != fp.BandID { + return newInvalidFieldValueError("version_ids.band_id").WithCause( + errDeviceAndFrequencyPlanBandMismatch.WithAttributes( + "dev_band_id", devBandID, + "fp_band_id", fp.BandID, + ), + ) + } + return nil + }) + }, "version_ids.band_id"); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.rx2_data_rate_index.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetRx2DataRateIndex() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.Rx2DataRateIndex.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.desired_rx2_data_rate_index.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetDesiredRx2DataRateIndex() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.DesiredRx2DataRateIndex.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.ping_slot_data_rate_index.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetPingSlotDataRateIndex() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.PingSlotDataRateIndex.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.desired_ping_slot_data_rate_index.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetDesiredPingSlotDataRateIndex() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.DesiredPingSlotDataRateIndex.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.desired_relay.mode.served.second_channel.data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetDesiredRelay().GetServed().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.DesiredRelay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.desired_relay.mode.serving.default_channel_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + chIdx := dev.GetMacSettings().GetDesiredRelay().GetServing().GetDefaultChannelIndex() + if chIdx == nil { + return nil + } + if chIdx.Value >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.desired_relay.mode.serving.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetDesiredRelay().GetServing().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.DesiredRelay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.adr.mode.dynamic.max_data_rate_index.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetAdr().GetDynamic().GetMaxDataRateIndex() == nil { + return nil + } + drIdx := dev.MacSettings.Adr.GetDynamic().MaxDataRateIndex.Value + _, ok := phy.DataRates[drIdx] + if !ok || drIdx > phy.MaxADRDataRateIndex { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.adr.mode.dynamic.min_data_rate_index.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetAdr().GetDynamic().GetMinDataRateIndex() == nil { + return nil + } + drIdx := dev.MacSettings.Adr.GetDynamic().MinDataRateIndex.Value + _, ok := phy.DataRates[drIdx] + if !ok || drIdx > phy.MaxADRDataRateIndex { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.adr.mode.dynamic.max_tx_power_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetAdr().GetDynamic().GetMaxTxPowerIndex() == nil { + return nil + } + if dev.MacSettings.Adr.GetDynamic().MaxTxPowerIndex.Value > uint32(phy.MaxTxPowerIndex()) { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.adr.mode.dynamic.min_tx_power_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetAdr().GetDynamic().GetMinTxPowerIndex() == nil { + return nil + } + if dev.MacSettings.Adr.GetDynamic().MinTxPowerIndex.Value > uint32(phy.MaxTxPowerIndex()) { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if setFields := setFields(dynamicADRSettingsFields...); hasPHYUpdate || len(setFields) > 0 { + fields := setFields + if hasPHYUpdate { + fields = append(fields, "mac_settings.adr.mode") + } + if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if phy.SupportsDynamicADR { + return nil + } + for _, field := range fields { + if m[field].GetMacSettings().GetAdr().GetDynamic() != nil { + return newInvalidFieldValueError(field) + } + } + return nil + }) + }, + fields..., + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.adr.mode.static.data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetAdr().GetStatic() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.Adr.GetStatic().DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.adr.mode.static.tx_power_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetAdr().GetStatic() == nil { + return nil + } + if dev.MacSettings.Adr.GetStatic().TxPowerIndex > uint32(phy.MaxTxPowerIndex()) { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.uplink_dwell_time.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetUplinkDwellTime() == nil { + return nil + } + if !phy.TxParamSetupReqSupport { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.downlink_dwell_time.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetDownlinkDwellTime() == nil { + return nil + } + if !phy.TxParamSetupReqSupport { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.relay.mode.served.second_channel.data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetRelay().GetServed().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.Relay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.relay.mode.serving.default_channel_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + chIdx := dev.GetMacSettings().GetRelay().GetServing().GetDefaultChannelIndex() + if chIdx == nil { + return nil + } + if chIdx.Value >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_settings.relay.mode.serving.second_channel.data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacSettings().GetRelay().GetServing().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacSettings.Relay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.current_parameters.rx2_data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetMacState() == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.MacState.CurrentParameters.Rx2DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.desired_parameters.rx2_data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetMacState() == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.MacState.DesiredParameters.Rx2DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetPendingMacState().GetCurrentParameters().GetRelay().GetServed().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.Relay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.current_parameters.relay.mode.serving.default_channel_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetPendingMacState().GetCurrentParameters().GetRelay().GetServing() == nil { + return nil + } + chIdx := dev.PendingMacState.CurrentParameters.Relay.GetServing().DefaultChannelIndex + if chIdx >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetPendingMacState().GetCurrentParameters().GetRelay().GetServing().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.Relay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.current_parameters.rx2_data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetPendingMacState() == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.Rx2DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetPendingMacState().GetDesiredParameters().GetRelay().GetServed().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.Relay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.desired_parameters.relay.mode.serving.default_channel_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetPendingMacState().GetDesiredParameters().GetRelay().GetServing() == nil { + return nil + } + chIdx := dev.PendingMacState.DesiredParameters.Relay.GetServing().DefaultChannelIndex + if chIdx >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetPendingMacState().GetDesiredParameters().GetRelay().GetServing().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.Relay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.desired_parameters.rx2_data_rate_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetPendingMacState() == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.Rx2DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.current_parameters.ping_slot_data_rate_index_value.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetMacState() == nil || dev.MacState.CurrentParameters.PingSlotDataRateIndexValue == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.MacState.CurrentParameters.PingSlotDataRateIndexValue.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.current_parameters.relay.mode.served.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacState().GetCurrentParameters().GetRelay().GetServed().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacState.CurrentParameters.Relay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.current_parameters.relay.mode.serving.default_channel_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacState().GetCurrentParameters().GetRelay().GetServing() == nil { + return nil + } + chIdx := dev.MacState.CurrentParameters.Relay.GetServing().DefaultChannelIndex + if chIdx >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.current_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacState().GetCurrentParameters().GetRelay().GetServing().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacState.CurrentParameters.Relay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.desired_parameters.ping_slot_data_rate_index_value.value"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetMacState() == nil || dev.MacState.DesiredParameters.PingSlotDataRateIndexValue == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.MacState.DesiredParameters.PingSlotDataRateIndexValue.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.desired_parameters.relay.mode.served.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacState().GetDesiredParameters().GetRelay().GetServed().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacState.DesiredParameters.Relay.GetServed().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.desired_parameters.relay.mode.serving.default_channel_index"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacState().GetDesiredParameters().GetRelay().GetServing() == nil { + return nil + } + chIdx := dev.MacState.DesiredParameters.Relay.GetServing().DefaultChannelIndex + if chIdx >= uint32(len(phy.Relay.WORChannels)) { // nolint: gosec + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("mac_state.desired_parameters.relay.mode.serving.second_channel.data_rate_index"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if dev.GetMacState().GetDesiredParameters().GetRelay().GetServing().GetSecondChannel() == nil { + return nil + } + _, ok := phy.DataRates[dev.MacState.DesiredParameters.Relay.GetServing().SecondChannel.DataRateIndex] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + + if field, validate := hasSetField("pending_mac_state.current_parameters.ping_slot_data_rate_index_value.value"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetPendingMacState() == nil || dev.PendingMacState.CurrentParameters.PingSlotDataRateIndexValue == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.PendingMacState.CurrentParameters.PingSlotDataRateIndexValue.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + if field, validate := hasSetField("pending_mac_state.desired_parameters.ping_slot_data_rate_index_value.value"); validate { // nolint: lll + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetPendingMacState() == nil || dev.PendingMacState.DesiredParameters.PingSlotDataRateIndexValue == nil { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + _, ok := phy.DataRates[dev.PendingMacState.DesiredParameters.PingSlotDataRateIndexValue.Value] + if !ok { + return newInvalidFieldValueError(field) + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + + if field, validate := hasSetField("mac_settings.factory_preset_frequencies"); validate { + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + if dev.GetMacSettings() == nil || len(dev.MacSettings.FactoryPresetFrequencies) == 0 { + return nil + } + return withPHY(func(phy *band.Band, fp *frequencyplans.FrequencyPlan) error { + switch phy.CFListType { + case ttnpb.CFListType_FREQUENCIES: + // Factory preset frequencies in bands which provide frequencies as part of the CFList + // are interpreted as being used both for uplinks and downlinks. + for _, frequency := range dev.MacSettings.FactoryPresetFrequencies { + _, inSubBand := fp.FindSubBand(frequency) + for _, sb := range phy.SubBands { + if sb.MinFrequency <= frequency && frequency <= sb.MaxFrequency { + inSubBand = true + break + } + } + if !inSubBand { + return newInvalidFieldValueError(field) + } + } + case ttnpb.CFListType_CHANNEL_MASKS: + // Factory preset frequencies in bands which provide channel masks as part of the CFList + // are interpreted as enabling explicit uplink channels. + uplinkChannels := make(map[uint64]struct{}, len(phy.UplinkChannels)) + for _, ch := range phy.UplinkChannels { + uplinkChannels[ch.Frequency] = struct{}{} + } + for _, frequency := range dev.MacSettings.FactoryPresetFrequencies { + if _, ok := uplinkChannels[frequency]; !ok { + return newInvalidFieldValueError(field) + } + } + default: + panic("unreachable") + } + return nil + }) + }, + field, + ); err != nil { + return err + } + } + + if hasPHYUpdate || st.HasSetField( + "mac_settings.ping_slot_frequency.value", + "supports_class_b", + ) { + if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + if !m["supports_class_b"].GetSupportsClassB() || + m["mac_settings.ping_slot_frequency.value"].GetMacSettings().GetPingSlotFrequency().GetValue() > 0 { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if len(phy.PingSlotFrequencies) == 0 { + return newInvalidFieldValueError("mac_settings.ping_slot_frequency.value") + } + return nil + }) + }, + "mac_settings.ping_slot_frequency.value", + "supports_class_b", + ); err != nil { + return err + } + } + + if hasPHYUpdate || st.HasSetField( + "mac_settings.desired_ping_slot_frequency.value", + "supports_class_b", + ) { + if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + if !m["supports_class_b"].GetSupportsClassB() || + m["mac_settings.desired_ping_slot_frequency.value"].GetMacSettings().GetDesiredPingSlotFrequency().GetValue() > 0 { // nolint: lll + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if len(phy.PingSlotFrequencies) == 0 { + return newInvalidFieldValueError("mac_settings.desired_ping_slot_frequency.value") + } + return nil + }) + }, + "mac_settings.desired_ping_slot_frequency.value", + "supports_class_b", + ); err != nil { + return err + } + } + + if hasPHYUpdate || st.HasSetField( + "mac_settings.beacon_frequency.value", + "supports_class_b", + ) { + if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + if !m["supports_class_b"].GetSupportsClassB() || + m["mac_settings.beacon_frequency.value"].GetMacSettings().GetBeaconFrequency().GetValue() > 0 { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if len(phy.Beacon.Frequencies) == 0 { + return newInvalidFieldValueError("mac_settings.beacon_frequency.value") + } + return nil + }) + }, + "mac_settings.beacon_frequency.value", + "supports_class_b", + ); err != nil { + return err + } + } + + if hasPHYUpdate || st.HasSetField( + "mac_settings.desired_beacon_frequency.value", + "supports_class_b", + ) { + if err := st.WithFields(func(m map[string]*ttnpb.EndDevice) error { + if !m["supports_class_b"].GetSupportsClassB() || + m["mac_settings.desired_beacon_frequency.value"].GetMacSettings().GetDesiredBeaconFrequency().GetValue() > 0 { + return nil + } + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if len(phy.Beacon.Frequencies) == 0 { + return newInvalidFieldValueError("mac_settings.desired_beacon_frequency.value") + } + return nil + }) + }, + "mac_settings.desired_beacon_frequency.value", + "supports_class_b", + ); err != nil { + return err + } + } + + for p, isValid := range map[string]func(*ttnpb.EndDevice, *band.Band) bool{ + "mac_settings.use_adr.value": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return !dev.GetMacSettings().GetUseAdr().GetValue() || phy.SupportsDynamicADR // nolint: staticcheck + }, + "mac_state.current_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetMacState().GetCurrentParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex + }, + "mac_state.current_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetMacState().GetCurrentParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) + }, + "mac_state.current_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return len(dev.GetMacState().GetCurrentParameters().GetChannels()) <= int(phy.MaxUplinkChannels) + }, + "mac_state.desired_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetMacState().GetDesiredParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex + }, + "mac_state.desired_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetMacState().GetDesiredParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) + }, + "mac_state.desired_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return len(dev.GetMacState().GetDesiredParameters().GetChannels()) <= int(phy.MaxUplinkChannels) + }, + "pending_mac_state.current_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetPendingMacState().GetCurrentParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex + }, + "pending_mac_state.current_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetPendingMacState().GetCurrentParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) + }, + "pending_mac_state.current_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return len(dev.GetPendingMacState().GetCurrentParameters().GetChannels()) <= int(phy.MaxUplinkChannels) + }, + "pending_mac_state.desired_parameters.adr_data_rate_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetPendingMacState().GetDesiredParameters().GetAdrDataRateIndex() <= phy.MaxADRDataRateIndex + }, + "pending_mac_state.desired_parameters.adr_tx_power_index": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return dev.GetPendingMacState().GetDesiredParameters().GetAdrTxPowerIndex() <= uint32(phy.MaxTxPowerIndex()) + }, + "pending_mac_state.desired_parameters.channels": func(dev *ttnpb.EndDevice, phy *band.Band) bool { + return len(dev.GetPendingMacState().GetDesiredParameters().GetChannels()) <= int(phy.MaxUplinkChannels) + }, + } { + if !hasPHYUpdate && !st.HasSetField(p) { + continue + } + if err := st.WithField(func(dev *ttnpb.EndDevice) error { + return withPHY(func(phy *band.Band, _ *frequencyplans.FrequencyPlan) error { + if !isValid(dev, phy) { + return newInvalidFieldValueError(p) + } + return nil + }) + }, p); err != nil { + return err + } + } + return nil +} + +// Ensure ADR dynamic parameters are monotonic. +// If one of the extrema is missing, the other extrema is considered to be valid. +func validateADRDynamicParameters(st *setDeviceState) error { + return st.ValidateSetFields(func(m map[string]*ttnpb.EndDevice) (bool, string) { + { + min := m["mac_settings.adr.mode.dynamic.min_data_rate_index.value"].GetMacSettings().GetAdr().GetDynamic().GetMinDataRateIndex() // nolint: revive,lll + max := m["mac_settings.adr.mode.dynamic.max_data_rate_index.value"].GetMacSettings().GetAdr().GetDynamic().GetMaxDataRateIndex() // nolint: revive,lll + + if min != nil && max != nil && max.Value < min.Value { + return false, "mac_settings.adr.mode.dynamic.max_data_rate_index.value" + } + } + { + min := m["mac_settings.adr.mode.dynamic.min_tx_power_index"].GetMacSettings().GetAdr().GetDynamic().GetMinTxPowerIndex() // nolint: revive,lll + max := m["mac_settings.adr.mode.dynamic.max_tx_power_index"].GetMacSettings().GetAdr().GetDynamic().GetMaxTxPowerIndex() // nolint: revive,lll + + if min != nil && max != nil && max.Value < min.Value { + return false, "mac_settings.adr.mode.dynamic.max_tx_power_index" + } + } + { + min := m["mac_settings.adr.mode.dynamic.min_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetMinNbTrans() // nolint: revive,lll + max := m["mac_settings.adr.mode.dynamic.max_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetMaxNbTrans() // nolint: revive,lll + + if min != nil && max != nil && max.Value < min.Value { + return false, "mac_settings.adr.mode.dynamic.max_nb_trans" + } + } + for drIdx := ttnpb.DataRateIndex_DATA_RATE_0; drIdx <= ttnpb.DataRateIndex_DATA_RATE_15; drIdx++ { + baseField := fmt.Sprintf("mac_settings.adr.mode.dynamic.overrides.data_rate_%d.", drIdx) + min := mac.DataRateIndexOverridesOf(m[baseField+"min_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetOverrides(), drIdx).GetMinNbTrans() // nolint: revive,lll + max := mac.DataRateIndexOverridesOf(m[baseField+"max_nb_trans"].GetMacSettings().GetAdr().GetDynamic().GetOverrides(), drIdx).GetMaxNbTrans() // nolint: revive,lll + + if min != nil && max != nil && max.Value < min.Value { + return false, baseField + "max_nb_trans" + } + } + return true, "" + }, + "mac_settings.adr.mode.dynamic.max_data_rate_index.value", + "mac_settings.adr.mode.dynamic.max_nb_trans", + "mac_settings.adr.mode.dynamic.max_tx_power_index", + "mac_settings.adr.mode.dynamic.min_data_rate_index.value", + "mac_settings.adr.mode.dynamic.min_nb_trans", + "mac_settings.adr.mode.dynamic.min_tx_power_index", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_0.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_1.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_10.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_11.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_12.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_13.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_14.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_15.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_2.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_3.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_4.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_5.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_6.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_7.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_8.min_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.max_nb_trans", + "mac_settings.adr.mode.dynamic.overrides.data_rate_9.min_nb_trans", + ) +} diff --git a/pkg/networkserver/grpc_gsns.go b/pkg/networkserver/grpc_gsns.go index 88ab798398..b5e32d8909 100644 --- a/pkg/networkserver/grpc_gsns.go +++ b/pkg/networkserver/grpc_gsns.go @@ -307,13 +307,22 @@ func (ns *NetworkServer) matchAndHandleDataUplink(ctx context.Context, dev *ttnp } } + profile := &ttnpb.MACSettingsProfile{} + if dev.MacSettingsProfileIds != nil { + profile, err = ns.macSettingsProfiles.Get(ctx, dev.GetMacSettingsProfileIds(), []string{"mac_settings"}) + if err != nil { + log.FromContext(ctx).WithError(err).Warn("Failed to get MAC settings profile") + return nil, false, nil + } + } + // Current session match if matchType != pendingMatch && dev.Session != nil && dev.MacState != nil && devAddr.Equal(types.MustDevAddr(dev.Session.DevAddr).OrZero()) && macspec.UseLegacyMIC(cmacFMatchResult.LoRaWANVersion) == macspec.UseLegacyMIC(dev.MacState.LorawanVersion) && - (cmacFMatchResult.FullFCnt == FullFCnt(uint16(pld.FHdr.FCnt), dev.Session.LastFCntUp, mac.DeviceSupports32BitFCnt(dev, ns.defaultMACSettings)) || + (cmacFMatchResult.FullFCnt == FullFCnt(uint16(pld.FHdr.FCnt), dev.Session.LastFCntUp, mac.DeviceSupports32BitFCnt(dev, ns.defaultMACSettings, profile)) || // nolint: gosec, lll cmacFMatchResult.FullFCnt == pld.FHdr.FCnt) { fNwkSIntKey, err := cryptoutil.UnwrapAES128Key(ctx, dev.Session.Keys.FNwkSIntKey, ns.KeyService()) if err != nil { @@ -328,12 +337,14 @@ func (ns *NetworkServer) matchAndHandleDataUplink(ctx context.Context, dev *ttnp )) switch { case cmacFMatchResult.FullFCnt < dev.Session.LastFCntUp: - if pld.FHdr.FCtrl.Ack || dev.Session.LastFCntUp != cmacFMatchResult.LastFCnt || !mac.DeviceResetsFCnt(dev, ns.defaultMACSettings) { + if pld.FHdr.FCtrl.Ack || + dev.Session.LastFCntUp != cmacFMatchResult.LastFCnt || + !mac.DeviceResetsFCnt(dev, ns.defaultMACSettings, profile) { return nil, false, nil } ctx = log.NewContextWithField(ctx, "f_cnt_reset", true) - macState, err := mac.NewState(dev, fps, ns.defaultMACSettings) + macState, err := mac.NewState(dev, fps, ns.defaultMACSettings, profile) if err != nil { log.FromContext(ctx).WithError(err).Warn("Failed to generate new MAC state") return nil, false, nil @@ -543,7 +554,7 @@ macLoop: var err error switch cmd.Cid { case ttnpb.MACCommandIdentifier_CID_RESET: - evs, err = mac.HandleResetInd(ctx, dev, cmd.GetResetInd(), fps, ns.defaultMACSettings) + evs, err = mac.HandleResetInd(ctx, dev, cmd.GetResetInd(), fps, ns.defaultMACSettings, profile) case ttnpb.MACCommandIdentifier_CID_LINK_CHECK: if !deduplicated { deferredMACHandlers = append(deferredMACHandlers, makeDeferredMACHandler(dev, mac.HandleLinkCheckReq)) @@ -969,7 +980,7 @@ func (ns *NetworkServer) handleDataUplink(ctx context.Context, up *ttnpb.UplinkM MacSettings: &ttnpb.MACSettings{ Supports_32BitFCnt: match.Supports32BitFCnt, }, - }, ns.defaultMACSettings)) + }, ns.defaultMACSettings, nil)) var cmacF [4]byte cmacF, ok = matchCmacF(ctx, fNwkSIntKey, match.LoRaWANVersion, fCnt, up) @@ -977,7 +988,7 @@ func (ns *NetworkServer) handleDataUplink(ctx context.Context, up *ttnpb.UplinkM MacSettings: &ttnpb.MACSettings{ ResetsFCnt: match.ResetsFCnt, }, - }, ns.defaultMACSettings) { + }, ns.defaultMACSettings, nil) { // FCnt reset fCnt = pld.FHdr.FCnt cmacF, ok = matchCmacF(ctx, fNwkSIntKey, match.LoRaWANVersion, fCnt, up) @@ -1316,7 +1327,15 @@ func (ns *NetworkServer) handleJoinRequest(ctx context.Context, up *ttnpb.Uplink "data_rate", up.Settings.DataRate, ) - macState, err := mac.NewState(matched, fps, ns.defaultMACSettings) + profile := &ttnpb.MACSettingsProfile{} + if matched.MacSettingsProfileIds != nil { + profile, err = ns.macSettingsProfiles.Get(ctx, matched.MacSettingsProfileIds, []string{"mac_settings"}) + if err != nil { + log.FromContext(ctx).WithError(err).Warn("Failed to get MAC settings profile") + return err + } + } + macState, err := mac.NewState(matched, fps, ns.defaultMACSettings, profile) if err != nil { log.FromContext(ctx).WithError(err).Warn("Failed to reset device's MAC state") return err diff --git a/pkg/networkserver/mac/relay.go b/pkg/networkserver/mac/relay.go index b6726ff8c5..340c31fdbc 100644 --- a/pkg/networkserver/mac/relay.go +++ b/pkg/networkserver/mac/relay.go @@ -129,8 +129,14 @@ func relayCtrlUplinkListReqFields(req *ttnpb.MACCommand_RelayCtrlUplinkListReq) } // DeviceDefaultRelaySettings returns the default relay parameters for the given device. -func DeviceDefaultRelaySettings(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) *ttnpb.RelaySettings { +func DeviceDefaultRelaySettings( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.RelaySettings { switch { + case profile.GetMacSettings().GetRelay() != nil: + return profile.MacSettings.Relay case dev.GetMacSettings().GetRelay() != nil: return dev.MacSettings.Relay case defaults.Relay != nil: @@ -141,14 +147,20 @@ func DeviceDefaultRelaySettings(dev *ttnpb.EndDevice, defaults *ttnpb.MACSetting } // DeviceDesiredRelaySettings returns the desired relay parameters for the given device. -func DeviceDesiredRelaySettings(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) *ttnpb.RelaySettings { +func DeviceDesiredRelaySettings( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.RelaySettings { switch { + case profile.GetMacSettings().GetDesiredRelay() != nil: + return profile.MacSettings.DesiredRelay case dev.GetMacSettings().GetDesiredRelay() != nil: return dev.MacSettings.DesiredRelay case defaults.DesiredRelay != nil: return defaults.DesiredRelay default: - return DeviceDefaultRelaySettings(dev, defaults) + return DeviceDefaultRelaySettings(dev, defaults, profile) } } diff --git a/pkg/networkserver/mac/reset.go b/pkg/networkserver/mac/reset.go index 34e08d774a..69f0a2ac20 100644 --- a/pkg/networkserver/mac/reset.go +++ b/pkg/networkserver/mac/reset.go @@ -33,7 +33,16 @@ var ( )() ) -func HandleResetInd(ctx context.Context, dev *ttnpb.EndDevice, pld *ttnpb.MACCommand_ResetInd, fps *frequencyplans.Store, defaults *ttnpb.MACSettings) (events.Builders, error) { +// HandleResetInd handles the uplink of a reset indication. +// This method is called by the Network Server when an uplink with a reset indication is received. +func HandleResetInd( + _ context.Context, + dev *ttnpb.EndDevice, + pld *ttnpb.MACCommand_ResetInd, + fps *frequencyplans.Store, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) (events.Builders, error) { if pld == nil { return nil, ErrNoPayload.New() } @@ -45,7 +54,7 @@ func HandleResetInd(ctx context.Context, dev *ttnpb.EndDevice, pld *ttnpb.MACCom return evs, nil } - macState, err := NewState(dev, fps, defaults) + macState, err := NewState(dev, fps, defaults, profile) if err != nil { return evs, err } diff --git a/pkg/networkserver/mac/reset_test.go b/pkg/networkserver/mac/reset_test.go index 3c668bb7b9..cc37c1f72c 100644 --- a/pkg/networkserver/mac/reset_test.go +++ b/pkg/networkserver/mac/reset_test.go @@ -73,7 +73,12 @@ func TestHandleResetInd(t *testing.T) { SupportsJoin: false, FrequencyPlanId: test.EUFrequencyPlanID, } - macState, err := NewState(dev, frequencyplans.NewStore(test.FrequencyPlansFetcher), &ttnpb.MACSettings{}) + macState, err := NewState( + dev, + frequencyplans.NewStore(test.FrequencyPlansFetcher), + &ttnpb.MACSettings{}, + &ttnpb.MACSettingsProfile{}, + ) if err != nil { t.Fatalf("Failed to reset MACState: %v", errors.Stack(err)) } @@ -122,7 +127,12 @@ func TestHandleResetInd(t *testing.T) { SupportsJoin: false, FrequencyPlanId: test.EUFrequencyPlanID, } - macState, err := NewState(dev, frequencyplans.NewStore(test.FrequencyPlansFetcher), &ttnpb.MACSettings{}) + macState, err := NewState( + dev, + frequencyplans.NewStore(test.FrequencyPlansFetcher), + &ttnpb.MACSettings{}, + &ttnpb.MACSettingsProfile{}, + ) if err != nil { t.Fatalf("Failed to reset MACState: %v", errors.Stack(err)) } @@ -155,7 +165,14 @@ func TestHandleResetInd(t *testing.T) { Func: func(ctx context.Context, t *testing.T, a *assertions.Assertion) { dev := ttnpb.Clone(tc.Device) - evs, err := HandleResetInd(ctx, dev, tc.Payload, frequencyplans.NewStore(test.FrequencyPlansFetcher), &ttnpb.MACSettings{}) + evs, err := HandleResetInd( + ctx, + dev, + tc.Payload, + frequencyplans.NewStore(test.FrequencyPlansFetcher), + &ttnpb.MACSettings{}, + &ttnpb.MACSettingsProfile{}, + ) if tc.Error != nil && !a.So(err, should.EqualErrorOrDefinition, tc.Error) || tc.Error == nil && !a.So(err, should.BeNil) { t.FailNow() diff --git a/pkg/networkserver/mac/utils.go b/pkg/networkserver/mac/utils.go index e27ae2a575..9b860e69f6 100644 --- a/pkg/networkserver/mac/utils.go +++ b/pkg/networkserver/mac/utils.go @@ -88,7 +88,15 @@ func channelDataRateRange( // When waiting for a response times out, the downlink message is considered lost, and the downlink task triggers again. const DefaultClassBTimeout = 10 * time.Minute -func DeviceClassBTimeout(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) time.Duration { +// DeviceClassBTimeout returns the time-out for the device to respond to class B downlink messages. +func DeviceClassBTimeout( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) time.Duration { + if t := profile.GetMacSettings().GetClassBTimeout(); t != nil { + return ttnpb.StdDurationOrZero(t) + } if t := dev.GetMacSettings().GetClassBTimeout(); t != nil { return ttnpb.StdDurationOrZero(t) } @@ -102,7 +110,15 @@ func DeviceClassBTimeout(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) time // When waiting for a response times out, the downlink message is considered lost, and the downlink task triggers again. const DefaultClassCTimeout = 5 * time.Minute -func DeviceClassCTimeout(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) time.Duration { +// DeviceClassCTimeout returns the time-out for the device to respond to class C downlink messages. +func DeviceClassCTimeout( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) time.Duration { + if t := profile.GetMacSettings().GetClassCTimeout(); t != nil { + return ttnpb.StdDurationOrZero(t) + } if t := dev.GetMacSettings().GetClassCTimeout(); t != nil { return ttnpb.StdDurationOrZero(t) } @@ -183,8 +199,15 @@ func DevicePingSlotFrequency(dev *ttnpb.EndDevice, phy *band.Band, pingAt time.T } } -func DeviceResetsFCnt(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) bool { +// DeviceResetsFCnt returns whether the device resets frame counters on re-join. +func DeviceResetsFCnt( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) bool { switch { + case profile.GetMacSettings().GetResetsFCnt() != nil: + return profile.MacSettings.ResetsFCnt.Value case dev.GetMacSettings().GetResetsFCnt() != nil: return dev.MacSettings.ResetsFCnt.Value case defaults.GetResetsFCnt() != nil: @@ -194,8 +217,15 @@ func DeviceResetsFCnt(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) bool { } } -func DeviceSupports32BitFCnt(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) bool { +// DeviceSupports32BitFCnt returns whether the device supports 32-bit frame counters. +func DeviceSupports32BitFCnt( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) bool { switch { + case profile.GetMacSettings().GetSupports_32BitFCnt() != nil: + return profile.MacSettings.Supports_32BitFCnt.Value case dev.GetMacSettings().GetSupports_32BitFCnt() != nil: return dev.MacSettings.Supports_32BitFCnt.Value case defaults.GetSupports_32BitFCnt() != nil: @@ -207,6 +237,7 @@ func DeviceSupports32BitFCnt(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) var errClassAMulticast = errors.DefineInvalidArgument("class_a_multicast", "multicast device in class A mode") +// DeviceDefaultClass returns the default class for the device. func DeviceDefaultClass(dev *ttnpb.EndDevice) (ttnpb.Class, error) { switch { case !macspec.UseDeviceModeInd(dev.LorawanVersion) && dev.SupportsClassC: @@ -222,6 +253,7 @@ func DeviceDefaultClass(dev *ttnpb.EndDevice) (ttnpb.Class, error) { } } +// DeviceDefaultLoRaWANVersion returns the default LoRaWAN version for the device. func DeviceDefaultLoRaWANVersion(dev *ttnpb.EndDevice) ttnpb.MACVersion { switch { case dev.Multicast: @@ -233,8 +265,15 @@ func DeviceDefaultLoRaWANVersion(dev *ttnpb.EndDevice) ttnpb.MACVersion { } } -func DeviceDefaultPingSlotPeriodicity(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) *ttnpb.PingSlotPeriodValue { +// DeviceDefaultPingSlotPeriodicity returns the default ping slot periodicity for the device. +func DeviceDefaultPingSlotPeriodicity( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.PingSlotPeriodValue { switch { + case profile.GetMacSettings().GetPingSlotPeriodicity() != nil: + return profile.MacSettings.PingSlotPeriodicity case dev.GetMacSettings().GetPingSlotPeriodicity() != nil: return dev.MacSettings.PingSlotPeriodicity case defaults.GetPingSlotPeriodicity() != nil: @@ -244,10 +283,18 @@ func DeviceDefaultPingSlotPeriodicity(dev *ttnpb.EndDevice, defaults *ttnpb.MACS } } -func DeviceUplinkDwellTime(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) *ttnpb.BoolValue { +// DeviceUplinkDwellTime returns the uplink dwell time for the device. +func DeviceUplinkDwellTime( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.BoolValue { switch { case !phy.TxParamSetupReqSupport: return nil + case profile.GetMacSettings().GetUplinkDwellTime() != nil: + return &ttnpb.BoolValue{Value: profile.MacSettings.UplinkDwellTime.Value} case dev.GetMacSettings().GetUplinkDwellTime() != nil: return &ttnpb.BoolValue{Value: dev.MacSettings.UplinkDwellTime.Value} case defaults.UplinkDwellTime != nil: @@ -257,10 +304,18 @@ func DeviceUplinkDwellTime(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb } } -func DeviceDownlinkDwellTime(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) *ttnpb.BoolValue { +// DeviceDownlinkDwellTime returns the downlink dwell time for the device. +func DeviceDownlinkDwellTime( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.BoolValue { switch { case !phy.TxParamSetupReqSupport: return nil + case profile.GetMacSettings().GetDownlinkDwellTime() != nil: + return &ttnpb.BoolValue{Value: profile.MacSettings.DownlinkDwellTime.Value} case dev.GetMacSettings().GetDownlinkDwellTime() != nil: return &ttnpb.BoolValue{Value: dev.MacSettings.DownlinkDwellTime.Value} case defaults.DownlinkDwellTime != nil: @@ -270,8 +325,17 @@ func DeviceDownlinkDwellTime(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttn } } -func DeviceDesiredMaxEIRP(dev *ttnpb.EndDevice, phy *band.Band, fp *frequencyplans.FrequencyPlan, defaults *ttnpb.MACSettings) float32 { +// DeviceDesiredMaxEIRP returns the desired maximum EIRP for the device. +func DeviceDesiredMaxEIRP( + dev *ttnpb.EndDevice, + phy *band.Band, + fp *frequencyplans.FrequencyPlan, + _ *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) float32 { switch { + case profile.GetMacSettings().GetDesiredMaxEirp() != nil: + return lorawan.DeviceEIRPToFloat32(profile.GetMacSettings().GetDesiredMaxEirp().GetValue()) case dev.GetMacSettings().GetDesiredMaxEirp() != nil: return lorawan.DeviceEIRPToFloat32(dev.GetMacSettings().GetDesiredMaxEirp().GetValue()) case fp.MaxEIRP != nil && *fp.MaxEIRP > 0 && *fp.MaxEIRP < phy.DefaultMaxEIRP: @@ -281,6 +345,7 @@ func DeviceDesiredMaxEIRP(dev *ttnpb.EndDevice, phy *band.Band, fp *frequencypla } } +// DeviceDesiredUplinkDwellTime returns the desired uplink dwell time for the device. func DeviceDesiredUplinkDwellTime(phy *band.Band, fp *frequencyplans.FrequencyPlan) *ttnpb.BoolValue { switch { case !phy.TxParamSetupReqSupport: @@ -292,6 +357,7 @@ func DeviceDesiredUplinkDwellTime(phy *band.Band, fp *frequencyplans.FrequencyPl } } +// DeviceDesiredDownlinkDwellTime returns the desired downlink dwell time for the device. func DeviceDesiredDownlinkDwellTime(phy *band.Band, fp *frequencyplans.FrequencyPlan) *ttnpb.BoolValue { switch { case !phy.TxParamSetupReqSupport: @@ -303,8 +369,16 @@ func DeviceDesiredDownlinkDwellTime(phy *band.Band, fp *frequencyplans.Frequency } } -func DeviceDefaultRX1Delay(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) ttnpb.RxDelay { +// DeviceDefaultRX1Delay returns the default RX1 delay for the device. +func DeviceDefaultRX1Delay( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.RxDelay { switch { + case profile.GetMacSettings().GetRx1Delay() != nil: + return profile.MacSettings.Rx1Delay.Value case dev.GetMacSettings().GetRx1Delay() != nil: return dev.MacSettings.Rx1Delay.Value case defaults.Rx1Delay != nil: @@ -314,19 +388,35 @@ func DeviceDefaultRX1Delay(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb } } -func DeviceDesiredRX1Delay(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) ttnpb.RxDelay { +// DeviceDesiredRX1Delay returns the desired RX1 delay for the device. +func DeviceDesiredRX1Delay( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.RxDelay { switch { + case profile.GetMacSettings().GetDesiredRx1Delay() != nil: + return profile.MacSettings.DesiredRx1Delay.Value case dev.GetMacSettings().GetDesiredRx1Delay() != nil: return dev.MacSettings.DesiredRx1Delay.Value case defaults.DesiredRx1Delay != nil: return defaults.DesiredRx1Delay.Value default: - return DeviceDefaultRX1Delay(dev, phy, defaults) + return DeviceDefaultRX1Delay(dev, phy, defaults, profile) } } -func DeviceDesiredADRAckLimitExponent(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) *ttnpb.ADRAckLimitExponentValue { +// DeviceDesiredADRAckLimitExponent returns the desired ADR ack limit exponent for the device. +func DeviceDesiredADRAckLimitExponent( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.ADRAckLimitExponentValue { switch { + case profile.GetMacSettings().GetDesiredAdrAckLimitExponent() != nil: + return &ttnpb.ADRAckLimitExponentValue{Value: profile.MacSettings.DesiredAdrAckLimitExponent.Value} case dev.GetMacSettings().GetDesiredAdrAckLimitExponent() != nil: return &ttnpb.ADRAckLimitExponentValue{Value: dev.MacSettings.DesiredAdrAckLimitExponent.Value} case defaults.DesiredAdrAckLimitExponent != nil: @@ -336,8 +426,16 @@ func DeviceDesiredADRAckLimitExponent(dev *ttnpb.EndDevice, phy *band.Band, defa } } -func DeviceDesiredADRAckDelayExponent(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) *ttnpb.ADRAckDelayExponentValue { +// DeviceDesiredADRAckDelayExponent returns the desired ADR ack delay exponent for the device. +func DeviceDesiredADRAckDelayExponent( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.ADRAckDelayExponentValue { switch { + case profile.GetMacSettings().GetDesiredAdrAckDelayExponent() != nil: + return &ttnpb.ADRAckDelayExponentValue{Value: profile.MacSettings.DesiredAdrAckDelayExponent.Value} case dev.GetMacSettings().GetDesiredAdrAckDelayExponent() != nil: return &ttnpb.ADRAckDelayExponentValue{Value: dev.MacSettings.DesiredAdrAckDelayExponent.Value} case defaults.DesiredAdrAckDelayExponent != nil: @@ -347,8 +445,15 @@ func DeviceDesiredADRAckDelayExponent(dev *ttnpb.EndDevice, phy *band.Band, defa } } -func DeviceDefaultRX1DataRateOffset(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) ttnpb.DataRateOffset { +// DeviceDefaultRX1DataRateOffset returns the default RX1 data rate offset for the device. +func DeviceDefaultRX1DataRateOffset( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.DataRateOffset { switch { + case profile.GetMacSettings().GetRx1DataRateOffset() != nil: + return profile.MacSettings.Rx1DataRateOffset.Value case dev.GetMacSettings().GetRx1DataRateOffset() != nil: return dev.MacSettings.Rx1DataRateOffset.Value case defaults.Rx1DataRateOffset != nil: @@ -358,19 +463,34 @@ func DeviceDefaultRX1DataRateOffset(dev *ttnpb.EndDevice, defaults *ttnpb.MACSet } } -func DeviceDesiredRX1DataRateOffset(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) ttnpb.DataRateOffset { +// DeviceDesiredRX1DataRateOffset returns the desired RX1 data rate offset for the device. +func DeviceDesiredRX1DataRateOffset( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.DataRateOffset { switch { + case profile.GetMacSettings().GetDesiredRx1DataRateOffset() != nil: + return profile.MacSettings.DesiredRx1DataRateOffset.Value case dev.GetMacSettings().GetDesiredRx1DataRateOffset() != nil: return dev.MacSettings.DesiredRx1DataRateOffset.Value case defaults.DesiredRx1DataRateOffset != nil: return defaults.DesiredRx1DataRateOffset.Value default: - return DeviceDefaultRX1DataRateOffset(dev, defaults) + return DeviceDefaultRX1DataRateOffset(dev, defaults, profile) } } -func DeviceDefaultRX2DataRateIndex(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) ttnpb.DataRateIndex { +// DeviceDefaultRX2DataRateIndex returns the default RX2 data rate index for the device. +func DeviceDefaultRX2DataRateIndex( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.DataRateIndex { switch { + case profile.GetMacSettings().GetRx2DataRateIndex() != nil: + return profile.MacSettings.Rx2DataRateIndex.Value case dev.GetMacSettings().GetRx2DataRateIndex() != nil: return dev.MacSettings.Rx2DataRateIndex.Value case defaults.Rx2DataRateIndex != nil: @@ -380,8 +500,17 @@ func DeviceDefaultRX2DataRateIndex(dev *ttnpb.EndDevice, phy *band.Band, default } } -func DeviceDesiredRX2DataRateIndex(dev *ttnpb.EndDevice, phy *band.Band, fp *frequencyplans.FrequencyPlan, defaults *ttnpb.MACSettings) ttnpb.DataRateIndex { +// DeviceDesiredRX2DataRateIndex returns the desired RX2 data rate index for the device. +func DeviceDesiredRX2DataRateIndex( + dev *ttnpb.EndDevice, + phy *band.Band, + fp *frequencyplans.FrequencyPlan, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.DataRateIndex { switch { + case profile.GetMacSettings().GetDesiredRx2DataRateIndex() != nil: + return profile.MacSettings.DesiredRx2DataRateIndex.Value case dev.GetMacSettings().GetDesiredRx2DataRateIndex() != nil: return dev.MacSettings.DesiredRx2DataRateIndex.Value case fp.DefaultRx2DataRate != nil: @@ -389,23 +518,40 @@ func DeviceDesiredRX2DataRateIndex(dev *ttnpb.EndDevice, phy *band.Band, fp *fre case defaults.DesiredRx2DataRateIndex != nil: return defaults.DesiredRx2DataRateIndex.Value default: - return DeviceDefaultRX2DataRateIndex(dev, phy, defaults) + return DeviceDefaultRX2DataRateIndex(dev, phy, defaults, profile) } } -func DeviceDefaultRX2Frequency(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) uint64 { +// DeviceDefaultRX2Frequency returns the default RX2 frequency for the device. +func DeviceDefaultRX2Frequency( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) uint64 { switch { + case profile.GetMacSettings().GetRx2Frequency() != nil && profile.MacSettings.Rx2Frequency.Value != 0: + return profile.MacSettings.Rx2Frequency.Value case dev.GetMacSettings().GetRx2Frequency() != nil && dev.MacSettings.Rx2Frequency.Value != 0: return dev.MacSettings.Rx2Frequency.Value - case defaults.Rx2Frequency != nil && dev.MacSettings.Rx2Frequency.Value != 0: + case defaults.Rx2Frequency != nil && defaults.Rx2Frequency.Value != 0: return defaults.Rx2Frequency.Value default: return phy.DefaultRx2Parameters.Frequency } } -func DeviceDesiredRX2Frequency(dev *ttnpb.EndDevice, phy *band.Band, fp *frequencyplans.FrequencyPlan, defaults *ttnpb.MACSettings) uint64 { +// DeviceDesiredRX2Frequency returns the desired RX2 frequency for the device. +func DeviceDesiredRX2Frequency( + dev *ttnpb.EndDevice, + phy *band.Band, + fp *frequencyplans.FrequencyPlan, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) uint64 { switch { + case profile.GetMacSettings().GetDesiredRx2Frequency() != nil && profile.MacSettings.DesiredRx2Frequency.Value != 0: + return profile.MacSettings.DesiredRx2Frequency.Value case dev.GetMacSettings().GetDesiredRx2Frequency() != nil && dev.MacSettings.DesiredRx2Frequency.Value != 0: return dev.MacSettings.DesiredRx2Frequency.Value case fp.Rx2Channel != nil: @@ -413,12 +559,19 @@ func DeviceDesiredRX2Frequency(dev *ttnpb.EndDevice, phy *band.Band, fp *frequen case defaults.DesiredRx2Frequency != nil && defaults.DesiredRx2Frequency.Value != 0: return defaults.DesiredRx2Frequency.Value default: - return DeviceDefaultRX2Frequency(dev, phy, defaults) + return DeviceDefaultRX2Frequency(dev, phy, defaults, profile) } } -func DeviceDefaultMaxDutyCycle(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) ttnpb.AggregatedDutyCycle { +// DeviceDefaultMaxDutyCycle returns the default maximum duty cycle for the device. +func DeviceDefaultMaxDutyCycle( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.AggregatedDutyCycle { switch { + case profile.GetMacSettings().GetMaxDutyCycle() != nil: + return profile.MacSettings.MaxDutyCycle.Value case dev.GetMacSettings().GetMaxDutyCycle() != nil: return dev.MacSettings.MaxDutyCycle.Value case defaults.MaxDutyCycle != nil: @@ -428,19 +581,34 @@ func DeviceDefaultMaxDutyCycle(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings } } -func DeviceDesiredMaxDutyCycle(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) ttnpb.AggregatedDutyCycle { +// DeviceDesiredMaxDutyCycle returns the desired maximum duty cycle for the device. +func DeviceDesiredMaxDutyCycle( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) ttnpb.AggregatedDutyCycle { switch { + case profile.GetMacSettings().GetDesiredMaxDutyCycle() != nil: + return profile.MacSettings.DesiredMaxDutyCycle.Value case dev.GetMacSettings().GetDesiredMaxDutyCycle() != nil: return dev.MacSettings.DesiredMaxDutyCycle.Value case defaults.DesiredMaxDutyCycle != nil: return defaults.DesiredMaxDutyCycle.Value default: - return DeviceDefaultMaxDutyCycle(dev, defaults) + return DeviceDefaultMaxDutyCycle(dev, defaults, profile) } } -func DeviceDefaultPingSlotFrequency(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) uint64 { +// DeviceDefaultPingSlotFrequency returns the default ping slot frequency for the device. +func DeviceDefaultPingSlotFrequency( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) uint64 { switch { + case profile.GetMacSettings().GetPingSlotFrequency() != nil && profile.MacSettings.PingSlotFrequency.Value != 0: + return profile.MacSettings.PingSlotFrequency.Value case dev.GetMacSettings().GetPingSlotFrequency() != nil && dev.MacSettings.PingSlotFrequency.Value != 0: return dev.MacSettings.PingSlotFrequency.Value case defaults.PingSlotFrequency != nil && defaults.PingSlotFrequency.Value != 0: @@ -452,8 +620,18 @@ func DeviceDefaultPingSlotFrequency(dev *ttnpb.EndDevice, phy *band.Band, defaul } } -func DeviceDesiredPingSlotFrequency(dev *ttnpb.EndDevice, phy *band.Band, fp *frequencyplans.FrequencyPlan, defaults *ttnpb.MACSettings) uint64 { +// DeviceDesiredPingSlotFrequency returns the desired ping slot frequency for the device. +func DeviceDesiredPingSlotFrequency( + dev *ttnpb.EndDevice, + phy *band.Band, + fp *frequencyplans.FrequencyPlan, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) uint64 { switch { + case profile.GetMacSettings().GetDesiredPingSlotFrequency() != nil && + profile.MacSettings.DesiredPingSlotFrequency.Value != 0: + return profile.MacSettings.DesiredPingSlotFrequency.Value case dev.GetMacSettings().GetDesiredPingSlotFrequency() != nil && dev.MacSettings.DesiredPingSlotFrequency.Value != 0: return dev.MacSettings.DesiredPingSlotFrequency.Value case fp.PingSlot != nil && fp.PingSlot.Frequency != 0: @@ -461,12 +639,20 @@ func DeviceDesiredPingSlotFrequency(dev *ttnpb.EndDevice, phy *band.Band, fp *fr case defaults.DesiredPingSlotFrequency != nil && defaults.DesiredPingSlotFrequency.Value != 0: return defaults.DesiredPingSlotFrequency.Value default: - return DeviceDefaultPingSlotFrequency(dev, phy, defaults) + return DeviceDefaultPingSlotFrequency(dev, phy, defaults, profile) } } -func DeviceDefaultPingSlotDataRateIndexValue(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) *ttnpb.DataRateIndexValue { +// DeviceDefaultPingSlotDataRateIndexValue returns the default ping slot data rate index for the device. +func DeviceDefaultPingSlotDataRateIndexValue( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.DataRateIndexValue { switch { + case profile.GetMacSettings().GetPingSlotDataRateIndex() != nil: + return profile.MacSettings.PingSlotDataRateIndex case dev.GetMacSettings().GetPingSlotDataRateIndex() != nil: return dev.MacSettings.PingSlotDataRateIndex case defaults.PingSlotDataRateIndex != nil: @@ -478,8 +664,17 @@ func DeviceDefaultPingSlotDataRateIndexValue(dev *ttnpb.EndDevice, phy *band.Ban } } -func DeviceDesiredPingSlotDataRateIndexValue(dev *ttnpb.EndDevice, phy *band.Band, fp *frequencyplans.FrequencyPlan, defaults *ttnpb.MACSettings) *ttnpb.DataRateIndexValue { +// DeviceDesiredPingSlotDataRateIndexValue returns the desired ping slot data rate index for the device. +func DeviceDesiredPingSlotDataRateIndexValue( + dev *ttnpb.EndDevice, + phy *band.Band, + fp *frequencyplans.FrequencyPlan, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) *ttnpb.DataRateIndexValue { switch { + case profile.GetMacSettings().GetDesiredPingSlotDataRateIndex() != nil: + return profile.MacSettings.DesiredPingSlotDataRateIndex case dev.GetMacSettings().GetDesiredPingSlotDataRateIndex() != nil: return dev.MacSettings.DesiredPingSlotDataRateIndex case fp.DefaultPingSlotDataRate != nil: @@ -487,12 +682,20 @@ func DeviceDesiredPingSlotDataRateIndexValue(dev *ttnpb.EndDevice, phy *band.Ban case defaults.DesiredPingSlotDataRateIndex != nil: return defaults.DesiredPingSlotDataRateIndex default: - return DeviceDefaultPingSlotDataRateIndexValue(dev, phy, defaults) + return DeviceDefaultPingSlotDataRateIndexValue(dev, phy, defaults, profile) } } -func DeviceDefaultBeaconFrequency(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) uint64 { +// DeviceDefaultBeaconFrequency returns the default beacon frequency for the device. +func DeviceDefaultBeaconFrequency( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) uint64 { switch { + case profile.GetMacSettings().GetBeaconFrequency() != nil && profile.MacSettings.BeaconFrequency.Value != 0: + return profile.MacSettings.BeaconFrequency.Value case dev.GetMacSettings().GetBeaconFrequency() != nil && dev.MacSettings.BeaconFrequency.Value != 0: return dev.MacSettings.BeaconFrequency.Value case defaults.BeaconFrequency != nil: @@ -504,26 +707,46 @@ func DeviceDefaultBeaconFrequency(dev *ttnpb.EndDevice, phy *band.Band, defaults } } -func DeviceDesiredBeaconFrequency(dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) uint64 { +// DeviceDesiredBeaconFrequency returns the desired beacon frequency for the device. +func DeviceDesiredBeaconFrequency( + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) uint64 { switch { + case profile.GetMacSettings().GetDesiredBeaconFrequency() != nil && + profile.MacSettings.DesiredBeaconFrequency.Value != 0: + return profile.MacSettings.DesiredBeaconFrequency.Value case dev.GetMacSettings().GetDesiredBeaconFrequency() != nil && dev.MacSettings.DesiredBeaconFrequency.Value != 0: return dev.MacSettings.DesiredBeaconFrequency.Value case defaults.DesiredBeaconFrequency != nil && defaults.DesiredBeaconFrequency.Value != 0: return defaults.DesiredBeaconFrequency.Value default: - return DeviceDefaultBeaconFrequency(dev, phy, defaults) + return DeviceDefaultBeaconFrequency(dev, phy, defaults, profile) } } -func deviceFactoryPresetFrequencies(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) []uint64 { +func deviceFactoryPresetFrequencies( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) []uint64 { + if freqs := profile.GetMacSettings().GetFactoryPresetFrequencies(); len(freqs) > 0 { + return freqs + } if freqs := dev.GetMacSettings().GetFactoryPresetFrequencies(); len(freqs) > 0 { return freqs } return defaults.GetFactoryPresetFrequencies() } +// DeviceDefaultChannels returns the default channels for the device. func DeviceDefaultChannels( - dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings, + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, ) []*ttnpb.MACParameters_Channel { if len(phy.DownlinkChannels) > len(phy.UplinkChannels) || len(phy.UplinkChannels) > int(phy.MaxUplinkChannels) || @@ -533,7 +756,7 @@ func DeviceDefaultChannels( panic("uplink/downlink channel length is inconsistent") } - factoryPresetFreqs := deviceFactoryPresetFrequencies(dev, defaults) + factoryPresetFreqs := deviceFactoryPresetFrequencies(dev, defaults, profile) chs := make([]*ttnpb.MACParameters_Channel, 0, len(phy.UplinkChannels)+len(factoryPresetFreqs)) for i, phyUpCh := range phy.UplinkChannels { @@ -589,8 +812,13 @@ outer: return chs } +// DeviceDesiredChannels returns the desired channels for the device. func DeviceDesiredChannels( - dev *ttnpb.EndDevice, phy *band.Band, fp *frequencyplans.FrequencyPlan, defaults *ttnpb.MACSettings, + dev *ttnpb.EndDevice, + phy *band.Band, + fp *frequencyplans.FrequencyPlan, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, ) []*ttnpb.MACParameters_Channel { if len(phy.DownlinkChannels) > len(phy.UplinkChannels) || len(phy.UplinkChannels) > int(phy.MaxUplinkChannels) || @@ -603,7 +831,7 @@ func DeviceDesiredChannels( panic("uplink/downlink channel length is inconsistent") } - defaultChs := DeviceDefaultChannels(dev, phy, defaults) + defaultChs := DeviceDefaultChannels(dev, phy, defaults, profile) chs := make([]*ttnpb.MACParameters_Channel, 0, len(defaultChs)+len(fp.UplinkChannels)) for _, ch := range defaultChs { @@ -674,7 +902,15 @@ func DeviceDesiredChannels( const defaultClassBCDownlinkInterval = time.Second -func DeviceClassBCDownlinkInterval(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) time.Duration { +// DeviceClassBCDownlinkInterval returns the class B/C downlink interval for the device. +func DeviceClassBCDownlinkInterval( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) time.Duration { + if t := profile.GetMacSettings().GetClassBCDownlinkInterval(); t != nil { + return ttnpb.StdDurationOrZero(t) + } if t := dev.GetMacSettings().GetClassBCDownlinkInterval(); t != nil { return ttnpb.StdDurationOrZero(t) } @@ -684,7 +920,13 @@ func DeviceClassBCDownlinkInterval(dev *ttnpb.EndDevice, defaults *ttnpb.MACSett return defaultClassBCDownlinkInterval } -func NewState(dev *ttnpb.EndDevice, fps *frequencyplans.Store, defaults *ttnpb.MACSettings) (*ttnpb.MACState, error) { +// NewState returns a new MAC state for the device. +func NewState( + dev *ttnpb.EndDevice, + fps *frequencyplans.Store, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) (*ttnpb.MACState, error) { fp, phy, err := internal.DeviceFrequencyPlanAndBand(dev, fps) if err != nil { return nil, err @@ -698,58 +940,63 @@ func NewState(dev *ttnpb.EndDevice, fps *frequencyplans.Store, defaults *ttnpb.M MaxEirp: phy.DefaultMaxEIRP, AdrDataRateIndex: ttnpb.DataRateIndex_DATA_RATE_0, AdrNbTrans: 1, - Rx1Delay: DeviceDefaultRX1Delay(dev, phy, defaults), - Rx1DataRateOffset: DeviceDefaultRX1DataRateOffset(dev, defaults), - Rx2DataRateIndex: DeviceDefaultRX2DataRateIndex(dev, phy, defaults), - Rx2Frequency: DeviceDefaultRX2Frequency(dev, phy, defaults), - MaxDutyCycle: DeviceDefaultMaxDutyCycle(dev, defaults), + Rx1Delay: DeviceDefaultRX1Delay(dev, phy, defaults, profile), + Rx1DataRateOffset: DeviceDefaultRX1DataRateOffset(dev, defaults, profile), + Rx2DataRateIndex: DeviceDefaultRX2DataRateIndex(dev, phy, defaults, profile), + Rx2Frequency: DeviceDefaultRX2Frequency(dev, phy, defaults, profile), + MaxDutyCycle: DeviceDefaultMaxDutyCycle(dev, defaults, profile), RejoinTimePeriodicity: ttnpb.RejoinTimeExponent_REJOIN_TIME_0, RejoinCountPeriodicity: ttnpb.RejoinCountExponent_REJOIN_COUNT_16, - PingSlotFrequency: DeviceDefaultPingSlotFrequency(dev, phy, defaults), - BeaconFrequency: DeviceDefaultBeaconFrequency(dev, phy, defaults), - Channels: DeviceDefaultChannels(dev, phy, defaults), - UplinkDwellTime: DeviceUplinkDwellTime(dev, phy, defaults), - DownlinkDwellTime: DeviceDownlinkDwellTime(dev, phy, defaults), + PingSlotFrequency: DeviceDefaultPingSlotFrequency(dev, phy, defaults, profile), + BeaconFrequency: DeviceDefaultBeaconFrequency(dev, phy, defaults, profile), + Channels: DeviceDefaultChannels(dev, phy, defaults, profile), + UplinkDwellTime: DeviceUplinkDwellTime(dev, phy, defaults, profile), + DownlinkDwellTime: DeviceDownlinkDwellTime(dev, phy, defaults, profile), AdrAckLimitExponent: &ttnpb.ADRAckLimitExponentValue{Value: phy.ADRAckLimit}, AdrAckDelayExponent: &ttnpb.ADRAckDelayExponentValue{Value: phy.ADRAckDelay}, - PingSlotDataRateIndexValue: DeviceDefaultPingSlotDataRateIndexValue(dev, phy, defaults), - Relay: RelayParametersFromRelaySettings(DeviceDefaultRelaySettings(dev, defaults), phy), + PingSlotDataRateIndexValue: DeviceDefaultPingSlotDataRateIndexValue(dev, phy, defaults, profile), + Relay: RelayParametersFromRelaySettings(DeviceDefaultRelaySettings(dev, defaults, profile), phy), // nolint: lll } desired := current if !dev.Multicast { desired = &ttnpb.MACParameters{ - MaxEirp: DeviceDesiredMaxEIRP(dev, phy, fp, defaults), + MaxEirp: DeviceDesiredMaxEIRP(dev, phy, fp, defaults, profile), AdrDataRateIndex: ttnpb.DataRateIndex_DATA_RATE_0, AdrNbTrans: 1, - Rx1Delay: DeviceDesiredRX1Delay(dev, phy, defaults), - Rx1DataRateOffset: DeviceDesiredRX1DataRateOffset(dev, defaults), - Rx2DataRateIndex: DeviceDesiredRX2DataRateIndex(dev, phy, fp, defaults), - Rx2Frequency: DeviceDesiredRX2Frequency(dev, phy, fp, defaults), - MaxDutyCycle: DeviceDesiredMaxDutyCycle(dev, defaults), + Rx1Delay: DeviceDesiredRX1Delay(dev, phy, defaults, profile), + Rx1DataRateOffset: DeviceDesiredRX1DataRateOffset(dev, defaults, profile), + Rx2DataRateIndex: DeviceDesiredRX2DataRateIndex(dev, phy, fp, defaults, profile), + Rx2Frequency: DeviceDesiredRX2Frequency(dev, phy, fp, defaults, profile), + MaxDutyCycle: DeviceDesiredMaxDutyCycle(dev, defaults, profile), RejoinTimePeriodicity: ttnpb.RejoinTimeExponent_REJOIN_TIME_0, RejoinCountPeriodicity: ttnpb.RejoinCountExponent_REJOIN_COUNT_16, - PingSlotFrequency: DeviceDesiredPingSlotFrequency(dev, phy, fp, defaults), - BeaconFrequency: DeviceDesiredBeaconFrequency(dev, phy, defaults), - Channels: DeviceDesiredChannels(dev, phy, fp, defaults), + PingSlotFrequency: DeviceDesiredPingSlotFrequency(dev, phy, fp, defaults, profile), + BeaconFrequency: DeviceDesiredBeaconFrequency(dev, phy, defaults, profile), + Channels: DeviceDesiredChannels(dev, phy, fp, defaults, profile), UplinkDwellTime: DeviceDesiredUplinkDwellTime(phy, fp), DownlinkDwellTime: DeviceDesiredDownlinkDwellTime(phy, fp), - AdrAckLimitExponent: DeviceDesiredADRAckLimitExponent(dev, phy, defaults), - AdrAckDelayExponent: DeviceDesiredADRAckDelayExponent(dev, phy, defaults), - PingSlotDataRateIndexValue: DeviceDesiredPingSlotDataRateIndexValue(dev, phy, fp, defaults), - Relay: RelayParametersFromRelaySettings(DeviceDesiredRelaySettings(dev, defaults), phy), + AdrAckLimitExponent: DeviceDesiredADRAckLimitExponent(dev, phy, defaults, profile), + AdrAckDelayExponent: DeviceDesiredADRAckDelayExponent(dev, phy, defaults, profile), + PingSlotDataRateIndexValue: DeviceDesiredPingSlotDataRateIndexValue(dev, phy, fp, defaults, profile), + Relay: RelayParametersFromRelaySettings(DeviceDesiredRelaySettings(dev, defaults, profile), phy), // nolint: lll } } // TODO: Support rejoins. (https://github.com/TheThingsNetwork/lorawan-stack/issues/8) return &ttnpb.MACState{ LorawanVersion: DeviceDefaultLoRaWANVersion(dev), DeviceClass: class, - PingSlotPeriodicity: DeviceDefaultPingSlotPeriodicity(dev, defaults), + PingSlotPeriodicity: DeviceDefaultPingSlotPeriodicity(dev, defaults, profile), CurrentParameters: current, DesiredParameters: desired, }, nil } -func DeviceExpectedUplinkDwellTime(macState *ttnpb.MACState, fp *frequencyplans.FrequencyPlan, phy *band.Band) bool { +// DeviceExpectedUplinkDwellTime returns the expected uplink dwell time for the device. +func DeviceExpectedUplinkDwellTime( + macState *ttnpb.MACState, + fp *frequencyplans.FrequencyPlan, + phy *band.Band, +) bool { switch { case macState.GetCurrentParameters().GetUplinkDwellTime() != nil: return macState.CurrentParameters.UplinkDwellTime.Value @@ -762,7 +1009,12 @@ func DeviceExpectedUplinkDwellTime(macState *ttnpb.MACState, fp *frequencyplans. } } -func DeviceExpectedDownlinkDwellTime(macState *ttnpb.MACState, fp *frequencyplans.FrequencyPlan, phy *band.Band) bool { +// DeviceExpectedDownlinkDwellTime returns the expected downlink dwell time for the device. +func DeviceExpectedDownlinkDwellTime( + macState *ttnpb.MACState, + fp *frequencyplans.FrequencyPlan, + phy *band.Band, +) bool { switch { case macState.GetCurrentParameters().GetDownlinkDwellTime() != nil: return macState.CurrentParameters.DownlinkDwellTime.Value @@ -777,8 +1029,14 @@ func DeviceExpectedDownlinkDwellTime(macState *ttnpb.MACState, fp *frequencyplan // DeviceScheduleDownlinks checks if the Network Server should schedule downlinks // for the provided end device. -func DeviceScheduleDownlinks(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings) bool { +func DeviceScheduleDownlinks( + dev *ttnpb.EndDevice, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) bool { switch { + case profile.GetMacSettings().GetScheduleDownlinks() != nil: + return profile.MacSettings.ScheduleDownlinks.Value case dev.GetMacSettings().GetScheduleDownlinks() != nil: return dev.MacSettings.ScheduleDownlinks.Value case defaults.GetScheduleDownlinks() != nil: diff --git a/pkg/networkserver/mac/utils_internal_test.go b/pkg/networkserver/mac/utils_internal_test.go index 27835ba461..2648380f12 100644 --- a/pkg/networkserver/mac/utils_internal_test.go +++ b/pkg/networkserver/mac/utils_internal_test.go @@ -105,7 +105,7 @@ func TestDeviceDefaultChannels(t *testing.T) { pb := ttnpb.Clone(tc.Device) - deviceDefaultChannels := DeviceDefaultChannels(pb, tc.Band, &ttnpb.MACSettings{}) + deviceDefaultChannels := DeviceDefaultChannels(pb, tc.Band, &ttnpb.MACSettings{}, &ttnpb.MACSettingsProfile{}) a.So(deviceDefaultChannels, should.Resemble, tc.Channels) a.So(pb, should.Resemble, tc.Device) }, @@ -190,7 +190,13 @@ func TestDeviceDesiredChannels(t *testing.T) { pb := ttnpb.Clone(tc.Device) - deviceDesiredChannels := DeviceDesiredChannels(pb, tc.Band, tc.FrequencyPlan, &ttnpb.MACSettings{}) + deviceDesiredChannels := DeviceDesiredChannels( + pb, + tc.Band, + tc.FrequencyPlan, + &ttnpb.MACSettings{}, + &ttnpb.MACSettingsProfile{}, + ) a.So(deviceDesiredChannels, should.Resemble, tc.Channels) a.So(pb, should.Resemble, tc.Device) }, @@ -648,7 +654,7 @@ func TestNewState(t *testing.T) { Func: func(ctx context.Context, t *testing.T, a *assertions.Assertion) { pb := ttnpb.Clone(tc.Device) - macState, err := NewState(pb, tc.FrequencyPlanStore, &ttnpb.MACSettings{}) + macState, err := NewState(pb, tc.FrequencyPlanStore, &ttnpb.MACSettings{}, &ttnpb.MACSettingsProfile{}) if tc.ErrorAssertion != nil { a.So(tc.ErrorAssertion(t, err), should.BeTrue) } else { diff --git a/pkg/networkserver/networkserver.go b/pkg/networkserver/networkserver.go index 178d37d4e1..8fe7e75141 100644 --- a/pkg/networkserver/networkserver.go +++ b/pkg/networkserver/networkserver.go @@ -157,7 +157,8 @@ type NetworkServer struct { *component.Component ctx context.Context - devices DeviceRegistry + devices DeviceRegistry + macSettingsProfiles MACSettingsProfileRegistry batchDevices ttnpb.NsEndDeviceBatchRegistryServer relayConfiguration ttnpb.NsRelayConfigurationServiceServer @@ -292,6 +293,7 @@ func New(c *component.Component, conf *Config, opts ...Option) (*NetworkServer, batchDevices: &nsEndDeviceBatchRegistry{devices: conf.Devices}, relayConfiguration: &nsRelayConfigurationService{devices: conf.Devices, frequencyPlans: c.FrequencyPlansStore}, macSettingsProfile: &NsMACSettingsProfileRegistry{registry: conf.MACSettingsProfileRegistry}, + macSettingsProfiles: conf.MACSettingsProfileRegistry, downlinkTasks: conf.DownlinkTaskQueue.Queue, downlinkPriorities: downlinkPriorities, defaultMACSettings: defaultMACSettings, diff --git a/pkg/networkserver/networkserver_util_internal_test.go b/pkg/networkserver/networkserver_util_internal_test.go index fa7d0a78e3..6bb8b0036d 100644 --- a/pkg/networkserver/networkserver_util_internal_test.go +++ b/pkg/networkserver/networkserver_util_internal_test.go @@ -222,7 +222,7 @@ func MakeCFList(conf CFListConfig) *ttnpb.CFList { if dr.MaxMACPayloadSize(downlinkDwellTime)+5 < lorawan.JoinAcceptWithCFListLength { return nil } - return mac.CFList(&phy, mac.DeviceDesiredChannels(&ttnpb.EndDevice{}, &phy, fp, nil)...) + return mac.CFList(&phy, mac.DeviceDesiredChannels(&ttnpb.EndDevice{}, &phy, fp, nil, nil)...) } type NsJsJoinRequestConfig struct { @@ -1603,16 +1603,17 @@ func (env TestEnvironment) AssertJoin(ctx context.Context, conf JoinAssertionCon t.Helper() defaultMACSettings := test.Must(env.Config.DefaultMACSettings.Parse()) + macSettingsProfile := &ttnpb.MACSettingsProfile{} defaultLoRaWANVersion := mac.DeviceDefaultLoRaWANVersion(conf.Device) - defaultRX1DROffset := mac.DeviceDefaultRX1DataRateOffset(conf.Device, defaultMACSettings) - defaultRX2DRIdx := mac.DeviceDefaultRX2DataRateIndex(conf.Device, phy, defaultMACSettings) - defaultRX2Freq := mac.DeviceDefaultRX2Frequency(conf.Device, phy, defaultMACSettings) + defaultRX1DROffset := mac.DeviceDefaultRX1DataRateOffset(conf.Device, defaultMACSettings, macSettingsProfile) + defaultRX2DRIdx := mac.DeviceDefaultRX2DataRateIndex(conf.Device, phy, defaultMACSettings, macSettingsProfile) + defaultRX2Freq := mac.DeviceDefaultRX2Frequency(conf.Device, phy, defaultMACSettings, macSettingsProfile) - desiredRX1Delay := mac.DeviceDesiredRX1Delay(conf.Device, phy, defaultMACSettings) - desiredRX1DROffset := mac.DeviceDesiredRX1DataRateOffset(conf.Device, defaultMACSettings) - desiredRX2DRIdx := mac.DeviceDesiredRX2DataRateIndex(conf.Device, phy, fp, defaultMACSettings) + desiredRX1Delay := mac.DeviceDesiredRX1Delay(conf.Device, phy, defaultMACSettings, macSettingsProfile) + desiredRX1DROffset := mac.DeviceDesiredRX1DataRateOffset(conf.Device, defaultMACSettings, macSettingsProfile) + desiredRX2DRIdx := mac.DeviceDesiredRX2DataRateIndex(conf.Device, phy, fp, defaultMACSettings, macSettingsProfile) deduplicatedUpConf := upConf deduplicatedUpConf.DecodePayload = true @@ -1683,41 +1684,41 @@ func (env TestEnvironment) AssertJoin(ctx context.Context, conf JoinAssertionCon MaxEirp: phy.DefaultMaxEIRP, AdrDataRateIndex: ttnpb.DataRateIndex_DATA_RATE_0, AdrNbTrans: 1, - Rx1Delay: mac.DeviceDefaultRX1Delay(dev, phy, defaultMACSettings), + Rx1Delay: mac.DeviceDefaultRX1Delay(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll Rx1DataRateOffset: defaultRX1DROffset, Rx2DataRateIndex: defaultRX2DRIdx, Rx2Frequency: defaultRX2Freq, - MaxDutyCycle: mac.DeviceDefaultMaxDutyCycle(dev, defaultMACSettings), + MaxDutyCycle: mac.DeviceDefaultMaxDutyCycle(dev, defaultMACSettings, macSettingsProfile), // nolint: lll RejoinTimePeriodicity: ttnpb.RejoinTimeExponent_REJOIN_TIME_0, RejoinCountPeriodicity: ttnpb.RejoinCountExponent_REJOIN_COUNT_16, - PingSlotFrequency: mac.DeviceDefaultPingSlotFrequency(dev, phy, defaultMACSettings), - BeaconFrequency: mac.DeviceDefaultBeaconFrequency(dev, phy, defaultMACSettings), - Channels: mac.DeviceDefaultChannels(dev, phy, defaultMACSettings), - UplinkDwellTime: mac.DeviceUplinkDwellTime(dev, phy, defaultMACSettings), - DownlinkDwellTime: mac.DeviceDownlinkDwellTime(dev, phy, defaultMACSettings), + PingSlotFrequency: mac.DeviceDefaultPingSlotFrequency(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + BeaconFrequency: mac.DeviceDefaultBeaconFrequency(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + Channels: mac.DeviceDefaultChannels(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + UplinkDwellTime: mac.DeviceUplinkDwellTime(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + DownlinkDwellTime: mac.DeviceDownlinkDwellTime(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll AdrAckLimitExponent: &ttnpb.ADRAckLimitExponentValue{Value: phy.ADRAckLimit}, AdrAckDelayExponent: &ttnpb.ADRAckDelayExponentValue{Value: phy.ADRAckDelay}, - PingSlotDataRateIndexValue: mac.DeviceDefaultPingSlotDataRateIndexValue(dev, phy, defaultMACSettings), + PingSlotDataRateIndexValue: mac.DeviceDefaultPingSlotDataRateIndexValue(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll }, DesiredParameters: &ttnpb.MACParameters{ - MaxEirp: mac.DeviceDesiredMaxEIRP(dev, phy, fp, defaultMACSettings), + MaxEirp: mac.DeviceDesiredMaxEIRP(dev, phy, fp, defaultMACSettings, macSettingsProfile), // nolint: lll AdrDataRateIndex: ttnpb.DataRateIndex_DATA_RATE_0, AdrNbTrans: 1, Rx1Delay: desiredRX1Delay, Rx1DataRateOffset: desiredRX1DROffset, Rx2DataRateIndex: desiredRX2DRIdx, - Rx2Frequency: mac.DeviceDesiredRX2Frequency(dev, phy, fp, defaultMACSettings), - MaxDutyCycle: mac.DeviceDesiredMaxDutyCycle(dev, defaultMACSettings), + Rx2Frequency: mac.DeviceDesiredRX2Frequency(dev, phy, fp, defaultMACSettings, macSettingsProfile), // nolint: lll + MaxDutyCycle: mac.DeviceDesiredMaxDutyCycle(dev, defaultMACSettings, macSettingsProfile), // nolint: lll RejoinTimePeriodicity: ttnpb.RejoinTimeExponent_REJOIN_TIME_0, RejoinCountPeriodicity: ttnpb.RejoinCountExponent_REJOIN_COUNT_16, - PingSlotFrequency: mac.DeviceDesiredPingSlotFrequency(dev, phy, fp, defaultMACSettings), - BeaconFrequency: mac.DeviceDesiredBeaconFrequency(dev, phy, defaultMACSettings), - Channels: mac.DeviceDesiredChannels(dev, phy, fp, defaultMACSettings), + PingSlotFrequency: mac.DeviceDesiredPingSlotFrequency(dev, phy, fp, defaultMACSettings, macSettingsProfile), // nolint: lll + BeaconFrequency: mac.DeviceDesiredBeaconFrequency(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + Channels: mac.DeviceDesiredChannels(dev, phy, fp, defaultMACSettings, macSettingsProfile), // nolint: lll UplinkDwellTime: mac.DeviceDesiredUplinkDwellTime(phy, fp), DownlinkDwellTime: mac.DeviceDesiredDownlinkDwellTime(phy, fp), - AdrAckLimitExponent: mac.DeviceDesiredADRAckLimitExponent(dev, phy, defaultMACSettings), - AdrAckDelayExponent: mac.DeviceDesiredADRAckDelayExponent(dev, phy, defaultMACSettings), - PingSlotDataRateIndexValue: mac.DeviceDesiredPingSlotDataRateIndexValue(dev, phy, fp, defaultMACSettings), + AdrAckLimitExponent: mac.DeviceDesiredADRAckLimitExponent(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + AdrAckDelayExponent: mac.DeviceDesiredADRAckDelayExponent(dev, phy, defaultMACSettings, macSettingsProfile), // nolint: lll + PingSlotDataRateIndexValue: mac.DeviceDesiredPingSlotDataRateIndexValue(dev, phy, fp, defaultMACSettings, macSettingsProfile), // nolint: lll }, DeviceClass: test.Must(mac.DeviceDefaultClass(dev)), LorawanVersion: defaultLoRaWANVersion, @@ -2186,7 +2187,12 @@ func LogEvents(t *testing.T, ch <-chan test.EventPubSubPublishRequest) { var MACStateOptions = test.MACStateOptions func MakeMACState(dev *ttnpb.EndDevice, defaults *ttnpb.MACSettings, opts ...test.MACStateOption) *ttnpb.MACState { - return MACStateOptions.Compose(opts...)(test.Must(mac.NewState(dev, test.FrequencyPlanStore, defaults))) + return MACStateOptions.Compose(opts...)(test.Must(mac.NewState( + dev, + test.FrequencyPlanStore, + defaults, + &ttnpb.MACSettingsProfile{}, + ))) } type SessionOptionNamespace struct{ test.SessionOptionNamespace } diff --git a/pkg/networkserver/utils.go b/pkg/networkserver/utils.go index 43b00ca0ff..5f5fd49f8f 100644 --- a/pkg/networkserver/utils.go +++ b/pkg/networkserver/utils.go @@ -121,7 +121,13 @@ func lastClassADataDownlinkSlot(dev *ttnpb.EndDevice, phy *band.Band) (*classADo // nextUnconfirmedNetworkInitiatedDownlinkAt returns the earliest possible time instant when next unconfirmed // network-initiated data downlink can be transmitted to the device given the data known by Network Server and true, // if such time instant exists, otherwise it returns time.Time{} and false. -func nextUnconfirmedNetworkInitiatedDownlinkAt(ctx context.Context, dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) (time.Time, bool) { +func nextUnconfirmedNetworkInitiatedDownlinkAt( + ctx context.Context, + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) (time.Time, bool) { switch { case dev.GetMacState() == nil: log.FromContext(ctx).Warn("Insufficient data to compute next network-initiated unconfirmed downlink slot") @@ -145,20 +151,29 @@ func nextUnconfirmedNetworkInitiatedDownlinkAt(ctx context.Context, dev *ttnpb.E return latestTime(classA.RX2(), *ttnpb.StdTime(dev.MacState.LastDownlinkAt)), true } classA, hasClassA := lastClassADataDownlinkSlot(dev, phy) - classBCDownlinkInterval := mac.DeviceClassBCDownlinkInterval(dev, defaults) + classBCDownlinkInterval := mac.DeviceClassBCDownlinkInterval(dev, defaults, profile) if !hasClassA { return ttnpb.StdTime(dev.MacState.LastNetworkInitiatedDownlinkAt).Add(classBCDownlinkInterval), true } if ttnpb.StdTime(classA.Uplink.ReceivedAt).After(*ttnpb.StdTime(dev.MacState.LastNetworkInitiatedDownlinkAt)) { return classA.RX2(), true } - return latestTime(classA.RX2(), ttnpb.StdTime(dev.MacState.LastNetworkInitiatedDownlinkAt).Add(classBCDownlinkInterval)), true + return latestTime( + classA.RX2(), + ttnpb.StdTime(dev.MacState.LastNetworkInitiatedDownlinkAt).Add(classBCDownlinkInterval), + ), true } // nextConfirmedNetworkInitiatedDownlinkAt returns the earliest possible time instant when a confirmed // network-initiated data downlink can be transmitted to the device given the data known by Network Server and true, // if such time instant exists, otherwise it returns time.Time{} and false. -func nextConfirmedNetworkInitiatedDownlinkAt(ctx context.Context, dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings) (time.Time, bool) { +func nextConfirmedNetworkInitiatedDownlinkAt( + ctx context.Context, + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + profile *ttnpb.MACSettingsProfile, +) (time.Time, bool) { if dev.GetMacState() == nil { log.FromContext(ctx).Warn("Insufficient data to compute next network-initiated confirmed downlink slot") return time.Time{}, false @@ -167,7 +182,7 @@ func nextConfirmedNetworkInitiatedDownlinkAt(ctx context.Context, dev *ttnpb.End return time.Time{}, false } - unconfAt, ok := nextUnconfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, defaults) + unconfAt, ok := nextUnconfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, defaults, profile) switch { case !ok: return time.Time{}, false @@ -180,10 +195,10 @@ func nextConfirmedNetworkInitiatedDownlinkAt(ctx context.Context, dev *ttnpb.End var timeout time.Duration switch dev.MacState.DeviceClass { case ttnpb.Class_CLASS_B: - timeout = mac.DeviceClassBTimeout(dev, defaults) + timeout = mac.DeviceClassBTimeout(dev, defaults, profile) case ttnpb.Class_CLASS_C: - timeout = mac.DeviceClassCTimeout(dev, defaults) + timeout = mac.DeviceClassCTimeout(dev, defaults, profile) default: panic(fmt.Errorf("unmatched class: %v", dev.MacState.DeviceClass)) } @@ -224,7 +239,14 @@ func deviceHasPathForDownlink(ctx context.Context, dev *ttnpb.EndDevice, down *t // nextDataDownlinkSlot returns the next downlinkSlot before or at earliestAt when next data downlink can be transmitted to the device // given the data known by Network Server and true, if such downlinkSlot and downlink exist, otherwise it returns nil and false. -func nextDataDownlinkSlot(ctx context.Context, dev *ttnpb.EndDevice, phy *band.Band, defaults *ttnpb.MACSettings, earliestAt time.Time) (downlinkSlot, bool) { +func nextDataDownlinkSlot( // nolint: gocyclo + ctx context.Context, + dev *ttnpb.EndDevice, + phy *band.Band, + defaults *ttnpb.MACSettings, + earliestAt time.Time, + profile *ttnpb.MACSettingsProfile, +) (downlinkSlot, bool) { if dev.GetMacState() == nil { return nil, false } @@ -316,12 +338,12 @@ func nextDataDownlinkSlot(ctx context.Context, dev *ttnpb.EndDevice, phy *band.B } } - nwkUnconf, hasNwkUnconf := nextUnconfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, defaults) + nwkUnconf, hasNwkUnconf := nextUnconfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, defaults, profile) if hasNwkUnconf && dev.MacState.DeviceClass == ttnpb.Class_CLASS_B { nwkUnconf, hasNwkUnconf = mac.NextPingSlotAt(ctx, dev, latestTime(nwkUnconf, earliestAt)) } - nwkConf, hasNwkConf := nextConfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, defaults) + nwkConf, hasNwkConf := nextConfirmedNetworkInitiatedDownlinkAt(ctx, dev, phy, defaults, profile) if hasNwkConf { nwkConf = latestTime(nwkConf, nwkUnconf) } diff --git a/pkg/networkserver/utils_internal_test.go b/pkg/networkserver/utils_internal_test.go index c4242f7972..fd7bc7a56d 100644 --- a/pkg/networkserver/utils_internal_test.go +++ b/pkg/networkserver/utils_internal_test.go @@ -560,7 +560,14 @@ func TestNextDataDownlinkSlot(t *testing.T) { clock := test.NewMockClock(beaconTime.Add(time.Millisecond)) defer SetMockClock(clock)() - ret, ok := nextDataDownlinkSlot(ctx, tc.Device, LoRaWANBands[band.EU_863_870][ttnpb.PHYVersion_RP001_V1_1_REV_B], &ttnpb.MACSettings{}, tc.EarliestAt) + ret, ok := nextDataDownlinkSlot( + ctx, + tc.Device, + LoRaWANBands[band.EU_863_870][ttnpb.PHYVersion_RP001_V1_1_REV_B], + &ttnpb.MACSettings{}, + tc.EarliestAt, + &ttnpb.MACSettingsProfile{}, + ) if a.So(ok, should.Equal, tc.ExpectedOk) { a.So(ret, should.Resemble, tc.ExpectedSlot) } diff --git a/pkg/ttnpb/end_device.go b/pkg/ttnpb/end_device.go index 1e79c4d18c..c6255b6e1e 100644 --- a/pkg/ttnpb/end_device.go +++ b/pkg/ttnpb/end_device.go @@ -2685,6 +2685,14 @@ func (v *EndDevice) FieldIsZero(p string) bool { return v.MacSettings.FieldIsZero("downlink_dwell_time") case "mac_settings.downlink_dwell_time.value": return v.MacSettings.FieldIsZero("downlink_dwell_time.value") + case "mac_settings_profile_ids": + return v.MacSettingsProfileIds == nil + case "mac_settings_profile_ids.application_ids": + return v.MacSettingsProfileIds.FieldIsZero("application_ids") + case "mac_settings_profile_ids.application_ids.application_id": + return v.MacSettingsProfileIds.FieldIsZero("application_ids.application_id") + case "mac_settings_profile_ids.profile_id": + return v.MacSettingsProfileIds.FieldIsZero("profile_id") case "mac_state": return v.MacState == nil case "max_frequency": diff --git a/pkg/ttnpb/end_device.pb.go b/pkg/ttnpb/end_device.pb.go index eb1f90a831..fc0d2f6052 100644 --- a/pkg/ttnpb/end_device.pb.go +++ b/pkg/ttnpb/end_device.pb.go @@ -2485,6 +2485,8 @@ type EndDevice struct { LastSeenAt *timestamppb.Timestamp `protobuf:"bytes,54,opt,name=last_seen_at,json=lastSeenAt,proto3" json:"last_seen_at,omitempty"` SerialNumber string `protobuf:"bytes,55,opt,name=serial_number,json=serialNumber,proto3" json:"serial_number,omitempty"` LoraAllianceProfileIds *LoRaAllianceProfileIdentifiers `protobuf:"bytes,56,opt,name=lora_alliance_profile_ids,json=loraAllianceProfileIds,proto3" json:"lora_alliance_profile_ids,omitempty"` + // MAC settings profile identifiers. + MacSettingsProfileIds *MACSettingsProfileIdentifiers `protobuf:"bytes,57,opt,name=mac_settings_profile_ids,json=macSettingsProfileIds,proto3" json:"mac_settings_profile_ids,omitempty"` } func (x *EndDevice) Reset() { @@ -2890,6 +2892,13 @@ func (x *EndDevice) GetLoraAllianceProfileIds() *LoRaAllianceProfileIdentifiers return nil } +func (x *EndDevice) GetMacSettingsProfileIds() *MACSettingsProfileIdentifiers { + if x != nil { + return x.MacSettingsProfileIds + } + return nil +} + type EndDevices struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -6635,7 +6644,7 @@ var file_ttn_lorawan_v3_end_device_proto_rawDesc = []byte{ 0x6f, 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, 0x07, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x54, 0x6f, 0x3a, 0x08, 0xf2, 0xaa, - 0x19, 0x04, 0x08, 0x01, 0x10, 0x01, 0x22, 0xd7, 0x21, 0x0a, 0x09, 0x45, 0x6e, 0x64, 0x44, 0x65, + 0x19, 0x04, 0x08, 0x01, 0x10, 0x01, 0x22, 0xbf, 0x22, 0x0a, 0x09, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, @@ -6893,158 +6902,79 @@ var file_ttn_lorawan_v3_end_device_proto_rawDesc = []byte{ 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x6f, 0x52, 0x61, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x16, 0x6c, 0x6f, 0x72, 0x61, 0x41, 0x6c, 0x6c, 0x69, 0x61, - 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x73, 0x1a, 0x3d, 0x0a, - 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 0x56, 0x0a, 0x0e, - 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, - 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x08, 0xf2, 0xaa, 0x19, 0x04, 0x08, 0x01, 0x10, 0x01, 0x4a, 0x04, - 0x08, 0x25, 0x10, 0x26, 0x4a, 0x04, 0x08, 0x26, 0x10, 0x27, 0x4a, 0x04, 0x08, 0x27, 0x10, 0x28, - 0x22, 0x48, 0x0a, 0x0a, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x3a, - 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, - 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0a, - 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0xf2, 0x01, 0x0a, 0x0d, 0x44, - 0x65, 0x76, 0x41, 0x64, 0x64, 0x72, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0xc8, 0x01, 0x0a, - 0x08, 0x64, 0x65, 0x76, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, - 0xac, 0x01, 0x92, 0x41, 0x19, 0x4a, 0x0a, 0x22, 0x32, 0x36, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, - 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, - 0x06, 0x7a, 0x04, 0x68, 0x04, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, - 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, - 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, - 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, - 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x34, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x07, - 0x64, 0x65, 0x76, 0x41, 0x64, 0x64, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, - 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, - 0x5c, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, - 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, - 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, - 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x97, 0x01, - 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, - 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, - 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, - 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, - 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x0a, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0xa8, 0x02, 0x0a, 0x23, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x65, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x4b, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, - 0x33, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4c, - 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x99, 0x01, 0x0a, 0x17, 0x45, 0x6e, 0x64, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x12, 0x40, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, - 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, - 0x03, 0x69, 0x64, 0x73, 0x12, 0x3c, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, - 0x6e, 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, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, - 0x41, 0x74, 0x22, 0xa6, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x0e, 0x65, 0x6e, - 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, - 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, - 0x10, 0x01, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x73, - 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, - 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0xcb, 0x03, 0x0a, 0x25, - 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x55, 0x49, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xd0, 0x01, 0x0a, 0x08, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, - 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, 0x12, - 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, - 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, - 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, - 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, - 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, - 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, - 0x07, 0x6a, 0x6f, 0x69, 0x6e, 0x45, 0x75, 0x69, 0x12, 0xce, 0x01, 0x0a, 0x07, 0x64, 0x65, 0x76, - 0x5f, 0x65, 0x75, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, - 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, - 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, - 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, - 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x52, 0x06, 0x64, 0x65, 0x76, 0x45, 0x75, 0x69, 0x22, 0xbf, 0x04, 0x0a, 0x15, 0x4c, 0x69, - 0x73, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, - 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x70, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x73, 0x52, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, + 0x6e, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x73, 0x12, 0x66, 0x0a, + 0x18, 0x6d, 0x61, 0x63, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x5f, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x39, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2d, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, + 0x2e, 0x4d, 0x41, 0x43, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x15, + 0x6d, 0x61, 0x63, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x49, 0x64, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 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, 0x56, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 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, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, + 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x3a, 0x08, 0xf2, 0xaa, + 0x19, 0x04, 0x08, 0x01, 0x10, 0x01, 0x4a, 0x04, 0x08, 0x25, 0x10, 0x26, 0x4a, 0x04, 0x08, 0x26, + 0x10, 0x27, 0x4a, 0x04, 0x08, 0x27, 0x10, 0x28, 0x22, 0x48, 0x0a, 0x0a, 0x45, 0x6e, 0x64, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x74, + 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x22, 0xf2, 0x01, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x41, 0x64, 0x64, 0x72, 0x50, 0x72, + 0x65, 0x66, 0x69, 0x78, 0x12, 0xc8, 0x01, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x5f, 0x61, 0x64, 0x64, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0xac, 0x01, 0x92, 0x41, 0x19, 0x4a, 0x0a, 0x22, + 0x32, 0x36, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, 0x06, + 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x04, 0x70, 0x01, 0xea, + 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, + 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, 0x58, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, + 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, + 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, + 0x34, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x07, 0x64, 0x65, 0x76, 0x41, 0x64, 0x64, 0x72, 0x12, + 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x5c, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, + 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, + 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, + 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x12, - 0xbd, 0x01, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, - 0xa6, 0x01, 0xfa, 0x42, 0xa2, 0x01, 0x72, 0x9f, 0x01, 0x52, 0x00, 0x52, 0x09, 0x64, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x52, 0x0a, 0x2d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x52, 0x08, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x75, 0x69, 0x52, 0x09, 0x2d, 0x6a, - 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x75, 0x69, 0x52, 0x07, 0x64, 0x65, 0x76, 0x5f, 0x65, 0x75, 0x69, - 0x52, 0x08, 0x2d, 0x64, 0x65, 0x76, 0x5f, 0x65, 0x75, 0x69, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x52, 0x05, 0x2d, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x2d, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x52, 0x0b, - 0x2d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x52, 0x0c, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x52, 0x0d, 0x2d, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x1e, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x08, - 0xfa, 0x42, 0x05, 0x2a, 0x03, 0x18, 0xe8, 0x07, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, - 0x61, 0x67, 0x65, 0x12, 0x46, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x06, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, - 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, - 0x65, 0x72, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x5e, 0x0a, 0x06, 0x46, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4b, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x01, 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, 0x42, 0x08, 0xfa, 0x42, 0x05, 0xb2, 0x01, 0x02, - 0x38, 0x01, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x6e, - 0x63, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x94, 0x01, 0x0a, 0x13, - 0x53, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, + 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x22, + 0xa8, 0x02, 0x0a, 0x23, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, + 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x65, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x61, + 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, + 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x99, + 0x01, 0x0a, 0x17, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, + 0x53, 0x65, 0x65, 0x6e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x40, 0x0a, 0x03, 0x69, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, - 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, - 0x73, 0x6b, 0x22, 0xae, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x65, 0x74, 0x41, 0x6e, 0x64, 0x47, + 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, + 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x3c, 0x0a, 0x0c, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 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, 0x0a, + 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x41, 0x74, 0x22, 0xa6, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x0e, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x74, 0x6e, @@ -7055,101 +6985,186 @@ var file_ttn_lorawan_v3_end_device_proto_rawDesc = []byte{ 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, - 0x61, 0x73, 0x6b, 0x22, 0xbc, 0x01, 0x0a, 0x11, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, - 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, - 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, - 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, - 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0x28, 0x0a, 0x0b, 0x6d, 0x61, 0x70, 0x70, - 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, - 0x42, 0x04, 0x72, 0x02, 0x18, 0x64, 0x52, 0x0a, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x4b, - 0x65, 0x79, 0x22, 0xb8, 0x01, 0x0a, 0x17, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1b, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x72, 0x02, 0x18, 0x64, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xc8, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x42, 0x2b, 0xfa, 0x42, 0x28, 0x92, 0x01, 0x25, 0x10, 0x64, 0x18, 0x01, 0x22, 0x1f, 0x72, 0x1d, - 0x32, 0x1b, 0x5e, 0x28, 0x3f, 0x3a, 0x5c, 0x2e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, - 0x7b, 0x31, 0x2c, 0x31, 0x36, 0x7d, 0x29, 0x7b, 0x31, 0x2c, 0x32, 0x7d, 0x24, 0x52, 0x0e, 0x66, - 0x69, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xfe, 0x01, - 0x0a, 0x18, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x12, 0x7d, 0x0a, 0x07, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x74, 0x74, - 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x46, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x42, 0x2c, 0xfa, 0x42, 0x29, 0x9a, 0x01, 0x26, 0x22, 0x24, 0x72, 0x22, 0x18, 0x24, - 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, - 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, - 0x52, 0x07, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x1a, 0x63, 0x0a, 0x0c, 0x46, 0x6f, 0x72, - 0x6d, 0x61, 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, 0x3d, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x74, 0x6e, - 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xdd, - 0x01, 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x44, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x27, 0xfa, 0x42, 0x24, 0x72, 0x22, 0x18, 0x24, 0x32, 0x1e, - 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, - 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x52, 0x08, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x60, 0x0a, 0x16, - 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x74, - 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, - 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x13, 0x65, 0x6e, 0x64, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x22, 0xca, - 0x01, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, - 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x59, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, - 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, - 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0e, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x4f, 0x0a, 0x0a, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x30, - 0xfa, 0x42, 0x2d, 0x92, 0x01, 0x2a, 0x08, 0x01, 0x10, 0x14, 0x22, 0x24, 0x72, 0x22, 0x18, 0x24, - 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, - 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, - 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x73, 0x22, 0xa8, 0x02, 0x0a, 0x19, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x59, 0x0a, 0x0f, 0x61, 0x70, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, - 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, - 0x01, 0x02, 0x10, 0x01, 0x52, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x73, 0x12, 0x4f, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x30, 0xfa, 0x42, 0x2d, 0x92, 0x01, 0x2a, - 0x08, 0x01, 0x10, 0x14, 0x22, 0x24, 0x72, 0x22, 0x18, 0x24, 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, - 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, - 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, - 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x61, 0x73, 0x6b, 0x22, 0xcb, 0x03, 0x0a, 0x25, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x46, + 0x6f, 0x72, 0x45, 0x55, 0x49, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xd0, 0x01, + 0x0a, 0x08, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x75, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, 0x35, 0x37, + 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, 0xa2, 0x02, + 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, 0x70, 0x01, + 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, + 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, + 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x48, 0x45, + 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, + 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, + 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, + 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, + 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x07, 0x6a, 0x6f, 0x69, 0x6e, 0x45, 0x75, 0x69, + 0x12, 0xce, 0x01, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x5f, 0x65, 0x75, 0x69, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x42, 0xb4, 0x01, 0x92, 0x41, 0x21, 0x4a, 0x12, 0x22, 0x37, 0x30, 0x42, 0x33, 0x44, + 0x35, 0x37, 0x45, 0x44, 0x30, 0x30, 0x30, 0x41, 0x42, 0x43, 0x44, 0x22, 0x9a, 0x02, 0x01, 0x07, + 0xa2, 0x02, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0xfa, 0x42, 0x06, 0x7a, 0x04, 0x68, 0x08, + 0x70, 0x01, 0xea, 0xaa, 0x19, 0x82, 0x01, 0x0a, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, + 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, + 0x48, 0x45, 0x58, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x3f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, + 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, + 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, + 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, + 0x68, 0x61, 0x6c, 0x38, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x06, 0x64, 0x65, 0x76, 0x45, 0x75, + 0x69, 0x22, 0xbf, 0x04, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4f, 0x0a, 0x0f, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, + 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x52, 0x0e, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x0a, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0xbd, 0x01, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0xa6, 0x01, 0xfa, 0x42, 0xa2, 0x01, 0x72, 0x9f, + 0x01, 0x52, 0x00, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x52, 0x0a, + 0x2d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x52, 0x08, 0x6a, 0x6f, 0x69, 0x6e, + 0x5f, 0x65, 0x75, 0x69, 0x52, 0x09, 0x2d, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x65, 0x75, 0x69, 0x52, + 0x07, 0x64, 0x65, 0x76, 0x5f, 0x65, 0x75, 0x69, 0x52, 0x08, 0x2d, 0x64, 0x65, 0x76, 0x5f, 0x65, + 0x75, 0x69, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x05, 0x2d, 0x6e, 0x61, 0x6d, 0x65, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x2d, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x52, 0x0b, 0x2d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, + 0x74, 0x52, 0x0d, 0x2d, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, + 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x2a, 0x03, 0x18, 0xe8, 0x07, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x46, 0x0a, 0x07, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, + 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x73, 0x1a, 0x5e, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4b, 0x0a, + 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x01, + 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, + 0x42, 0x08, 0xfa, 0x42, 0x05, 0xb2, 0x01, 0x02, 0x38, 0x01, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x22, 0x94, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x65, + 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, + 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, + 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, + 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0xae, 0x01, 0x0a, 0x1b, 0x52, + 0x65, 0x73, 0x65, 0x74, 0x41, 0x6e, 0x64, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x0e, 0x65, 0x6e, + 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, + 0x10, 0x01, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x73, + 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, + 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x22, 0xbc, 0x01, 0x0a, 0x11, + 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x12, 0x42, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, + 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, + 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, - 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, - 0x10, 0x07, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x2a, 0x55, 0x0a, 0x0a, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x4f, 0x57, 0x45, 0x52, - 0x5f, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, - 0x57, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x1a, 0x0d, - 0xea, 0xaa, 0x19, 0x09, 0x18, 0x01, 0x2a, 0x05, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x42, 0x31, 0x5a, - 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, 0x70, 0x62, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x28, 0x0a, 0x0b, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, 0x64, 0x52, 0x0a, + 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x22, 0xb8, 0x01, 0x0a, 0x17, 0x45, + 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x18, 0x64, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, + 0xc8, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x54, 0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2b, 0xfa, 0x42, 0x28, 0x92, 0x01, 0x25, + 0x10, 0x64, 0x18, 0x01, 0x22, 0x1f, 0x72, 0x1d, 0x32, 0x1b, 0x5e, 0x28, 0x3f, 0x3a, 0x5c, 0x2e, + 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x31, 0x36, 0x7d, 0x29, 0x7b, + 0x31, 0x2c, 0x32, 0x7d, 0x24, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xfe, 0x01, 0x0a, 0x18, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x73, 0x12, 0x7d, 0x0a, 0x07, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, + 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x2e, 0x46, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x2c, 0xfa, 0x42, 0x29, 0x9a, + 0x01, 0x26, 0x22, 0x24, 0x72, 0x22, 0x18, 0x24, 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, + 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, + 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x73, 0x1a, 0x63, 0x0a, 0x0c, 0x46, 0x6f, 0x72, 0x6d, 0x61, 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, 0x3d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, + 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xdd, 0x01, 0x0a, 0x1f, 0x43, 0x6f, 0x6e, 0x76, 0x65, + 0x72, 0x74, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x09, 0x66, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x27, 0xfa, + 0x42, 0x24, 0x72, 0x22, 0x18, 0x24, 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, + 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, + 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x52, 0x08, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x49, 0x64, + 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x60, 0x0a, 0x16, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, + 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x73, 0x52, 0x13, 0x65, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x22, 0xca, 0x01, 0x0a, 0x1c, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x59, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, + 0x33, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, + 0x10, 0x01, 0x52, 0x0e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x73, 0x12, 0x4f, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x30, 0xfa, 0x42, 0x2d, 0x92, 0x01, 0x2a, 0x08, 0x01, + 0x10, 0x14, 0x22, 0x24, 0x72, 0x22, 0x18, 0x24, 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, + 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, + 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, 0x7d, 0x24, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x49, 0x64, 0x73, 0x22, 0xa8, 0x02, 0x0a, 0x19, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, + 0x45, 0x6e, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x59, 0x0a, 0x0f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x74, 0x6e, + 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0e, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x4f, 0x0a, 0x0a, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x42, 0x30, 0xfa, 0x42, 0x2d, 0x92, 0x01, 0x2a, 0x08, 0x01, 0x10, 0x14, 0x22, 0x24, 0x72, 0x22, + 0x18, 0x24, 0x32, 0x1e, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x28, 0x3f, 0x3a, + 0x5b, 0x2d, 0x5d, 0x3f, 0x5b, 0x61, 0x2d, 0x7a, 0x30, 0x2d, 0x39, 0x5d, 0x29, 0x7b, 0x32, 0x2c, + 0x7d, 0x24, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x73, 0x12, 0x39, 0x0a, + 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 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, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x09, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, + 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x2a, 0x55, + 0x0a, 0x0a, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, + 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, + 0x11, 0x0a, 0x0d, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x42, 0x41, 0x54, 0x54, 0x45, 0x52, 0x59, + 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x4f, 0x57, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x54, 0x45, + 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x1a, 0x0d, 0xea, 0xaa, 0x19, 0x09, 0x18, 0x01, 0x2a, 0x05, + 0x50, 0x4f, 0x57, 0x45, 0x52, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, 0x65, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6f, + 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -7425,91 +7440,92 @@ var file_ttn_lorawan_v3_end_device_proto_depIdxs = []int32{ 64, // 135: ttn.lorawan.v3.EndDevice.activated_at:type_name -> google.protobuf.Timestamp 64, // 136: ttn.lorawan.v3.EndDevice.last_seen_at:type_name -> google.protobuf.Timestamp 107, // 137: ttn.lorawan.v3.EndDevice.lora_alliance_profile_ids:type_name -> ttn.lorawan.v3.LoRaAllianceProfileIdentifiers - 18, // 138: ttn.lorawan.v3.EndDevices.end_devices:type_name -> ttn.lorawan.v3.EndDevice - 18, // 139: ttn.lorawan.v3.CreateEndDeviceRequest.end_device:type_name -> ttn.lorawan.v3.EndDevice - 18, // 140: ttn.lorawan.v3.UpdateEndDeviceRequest.end_device:type_name -> ttn.lorawan.v3.EndDevice - 108, // 141: ttn.lorawan.v3.UpdateEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask - 60, // 142: ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.updates:type_name -> ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.EndDeviceLastSeenUpdate - 102, // 143: ttn.lorawan.v3.GetEndDeviceRequest.end_device_ids:type_name -> ttn.lorawan.v3.EndDeviceIdentifiers - 108, // 144: ttn.lorawan.v3.GetEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask - 109, // 145: ttn.lorawan.v3.ListEndDevicesRequest.application_ids:type_name -> ttn.lorawan.v3.ApplicationIdentifiers - 108, // 146: ttn.lorawan.v3.ListEndDevicesRequest.field_mask:type_name -> google.protobuf.FieldMask - 61, // 147: ttn.lorawan.v3.ListEndDevicesRequest.filters:type_name -> ttn.lorawan.v3.ListEndDevicesRequest.Filter - 18, // 148: ttn.lorawan.v3.SetEndDeviceRequest.end_device:type_name -> ttn.lorawan.v3.EndDevice - 108, // 149: ttn.lorawan.v3.SetEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask - 102, // 150: ttn.lorawan.v3.ResetAndGetEndDeviceRequest.end_device_ids:type_name -> ttn.lorawan.v3.EndDeviceIdentifiers - 108, // 151: ttn.lorawan.v3.ResetAndGetEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask - 18, // 152: ttn.lorawan.v3.EndDeviceTemplate.end_device:type_name -> ttn.lorawan.v3.EndDevice - 108, // 153: ttn.lorawan.v3.EndDeviceTemplate.field_mask:type_name -> google.protobuf.FieldMask - 62, // 154: ttn.lorawan.v3.EndDeviceTemplateFormats.formats:type_name -> ttn.lorawan.v3.EndDeviceTemplateFormats.FormatsEntry - 84, // 155: ttn.lorawan.v3.ConvertEndDeviceTemplateRequest.end_device_version_ids:type_name -> ttn.lorawan.v3.EndDeviceVersionIdentifiers - 109, // 156: ttn.lorawan.v3.BatchDeleteEndDevicesRequest.application_ids:type_name -> ttn.lorawan.v3.ApplicationIdentifiers - 109, // 157: ttn.lorawan.v3.BatchGetEndDevicesRequest.application_ids:type_name -> ttn.lorawan.v3.ApplicationIdentifiers - 108, // 158: ttn.lorawan.v3.BatchGetEndDevicesRequest.field_mask:type_name -> google.protobuf.FieldMask - 75, // 159: ttn.lorawan.v3.MACParameters.Channel.min_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex - 75, // 160: ttn.lorawan.v3.MACParameters.Channel.max_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex - 75, // 161: ttn.lorawan.v3.ADRSettings.StaticMode.data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex - 95, // 162: ttn.lorawan.v3.ADRSettings.DynamicMode.margin:type_name -> google.protobuf.FloatValue - 83, // 163: ttn.lorawan.v3.ADRSettings.DynamicMode.min_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndexValue - 83, // 164: ttn.lorawan.v3.ADRSettings.DynamicMode.max_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndexValue - 74, // 165: ttn.lorawan.v3.ADRSettings.DynamicMode.min_tx_power_index:type_name -> google.protobuf.UInt32Value - 74, // 166: ttn.lorawan.v3.ADRSettings.DynamicMode.max_tx_power_index:type_name -> google.protobuf.UInt32Value - 74, // 167: ttn.lorawan.v3.ADRSettings.DynamicMode.min_nb_trans:type_name -> google.protobuf.UInt32Value - 74, // 168: ttn.lorawan.v3.ADRSettings.DynamicMode.max_nb_trans:type_name -> google.protobuf.UInt32Value - 39, // 169: ttn.lorawan.v3.ADRSettings.DynamicMode.channel_steering:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings - 41, // 170: ttn.lorawan.v3.ADRSettings.DynamicMode.overrides:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides - 42, // 171: ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.lora_narrow:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.LoRaNarrowMode - 43, // 172: ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.disabled:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.DisabledMode - 74, // 173: ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride.min_nb_trans:type_name -> google.protobuf.UInt32Value - 74, // 174: ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride.max_nb_trans:type_name -> google.protobuf.UInt32Value - 40, // 175: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_0:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 176: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_1:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 177: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_2:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 178: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_3:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 179: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_4:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 180: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_5:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 181: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_6:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 182: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_7:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 183: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_8:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 184: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_9:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 185: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_10:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 186: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_11:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 187: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_12:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 188: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_13:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 189: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_14:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 40, // 190: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_15:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride - 110, // 191: ttn.lorawan.v3.MACState.JoinRequest.downlink_settings:type_name -> ttn.lorawan.v3.DLSettings - 76, // 192: ttn.lorawan.v3.MACState.JoinRequest.rx_delay:type_name -> ttn.lorawan.v3.RxDelay - 111, // 193: ttn.lorawan.v3.MACState.JoinRequest.cf_list:type_name -> ttn.lorawan.v3.CFList - 44, // 194: ttn.lorawan.v3.MACState.JoinAccept.request:type_name -> ttn.lorawan.v3.MACState.JoinRequest - 63, // 195: ttn.lorawan.v3.MACState.JoinAccept.keys:type_name -> ttn.lorawan.v3.SessionKeys - 112, // 196: ttn.lorawan.v3.MACState.UplinkMessage.payload:type_name -> ttn.lorawan.v3.Message - 51, // 197: ttn.lorawan.v3.MACState.UplinkMessage.settings:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.TxSettings - 52, // 198: ttn.lorawan.v3.MACState.UplinkMessage.rx_metadata:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata - 64, // 199: ttn.lorawan.v3.MACState.UplinkMessage.received_at:type_name -> google.protobuf.Timestamp - 55, // 200: ttn.lorawan.v3.MACState.DownlinkMessage.payload:type_name -> ttn.lorawan.v3.MACState.DownlinkMessage.Message - 75, // 201: ttn.lorawan.v3.MACState.DataRateRange.min_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex - 75, // 202: ttn.lorawan.v3.MACState.DataRateRange.max_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex - 48, // 203: ttn.lorawan.v3.MACState.DataRateRanges.ranges:type_name -> ttn.lorawan.v3.MACState.DataRateRange - 49, // 204: ttn.lorawan.v3.MACState.RejectedDataRateRangesEntry.value:type_name -> ttn.lorawan.v3.MACState.DataRateRanges - 113, // 205: ttn.lorawan.v3.MACState.UplinkMessage.TxSettings.data_rate:type_name -> ttn.lorawan.v3.DataRate - 114, // 206: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.gateway_ids:type_name -> ttn.lorawan.v3.GatewayIdentifiers - 115, // 207: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.downlink_path_constraint:type_name -> ttn.lorawan.v3.DownlinkPathConstraint - 53, // 208: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.packet_broker:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.PacketBrokerMetadata - 54, // 209: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.relay:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.RelayMetadata - 56, // 210: ttn.lorawan.v3.MACState.DownlinkMessage.Message.m_hdr:type_name -> ttn.lorawan.v3.MACState.DownlinkMessage.Message.MHDR - 57, // 211: ttn.lorawan.v3.MACState.DownlinkMessage.Message.mac_payload:type_name -> ttn.lorawan.v3.MACState.DownlinkMessage.Message.MACPayload - 116, // 212: ttn.lorawan.v3.MACState.DownlinkMessage.Message.MHDR.m_type:type_name -> ttn.lorawan.v3.MType - 117, // 213: ttn.lorawan.v3.EndDevice.LocationsEntry.value:type_name -> ttn.lorawan.v3.Location - 102, // 214: ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.EndDeviceLastSeenUpdate.ids:type_name -> ttn.lorawan.v3.EndDeviceIdentifiers - 64, // 215: ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.EndDeviceLastSeenUpdate.last_seen_at:type_name -> google.protobuf.Timestamp - 64, // 216: ttn.lorawan.v3.ListEndDevicesRequest.Filter.updated_since:type_name -> google.protobuf.Timestamp - 30, // 217: ttn.lorawan.v3.EndDeviceTemplateFormats.FormatsEntry.value:type_name -> ttn.lorawan.v3.EndDeviceTemplateFormat - 218, // [218:218] is the sub-list for method output_type - 218, // [218:218] is the sub-list for method input_type - 218, // [218:218] is the sub-list for extension type_name - 218, // [218:218] is the sub-list for extension extendee - 0, // [0:218] is the sub-list for field type_name + 97, // 138: ttn.lorawan.v3.EndDevice.mac_settings_profile_ids:type_name -> ttn.lorawan.v3.MACSettingsProfileIdentifiers + 18, // 139: ttn.lorawan.v3.EndDevices.end_devices:type_name -> ttn.lorawan.v3.EndDevice + 18, // 140: ttn.lorawan.v3.CreateEndDeviceRequest.end_device:type_name -> ttn.lorawan.v3.EndDevice + 18, // 141: ttn.lorawan.v3.UpdateEndDeviceRequest.end_device:type_name -> ttn.lorawan.v3.EndDevice + 108, // 142: ttn.lorawan.v3.UpdateEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask + 60, // 143: ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.updates:type_name -> ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.EndDeviceLastSeenUpdate + 102, // 144: ttn.lorawan.v3.GetEndDeviceRequest.end_device_ids:type_name -> ttn.lorawan.v3.EndDeviceIdentifiers + 108, // 145: ttn.lorawan.v3.GetEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask + 109, // 146: ttn.lorawan.v3.ListEndDevicesRequest.application_ids:type_name -> ttn.lorawan.v3.ApplicationIdentifiers + 108, // 147: ttn.lorawan.v3.ListEndDevicesRequest.field_mask:type_name -> google.protobuf.FieldMask + 61, // 148: ttn.lorawan.v3.ListEndDevicesRequest.filters:type_name -> ttn.lorawan.v3.ListEndDevicesRequest.Filter + 18, // 149: ttn.lorawan.v3.SetEndDeviceRequest.end_device:type_name -> ttn.lorawan.v3.EndDevice + 108, // 150: ttn.lorawan.v3.SetEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask + 102, // 151: ttn.lorawan.v3.ResetAndGetEndDeviceRequest.end_device_ids:type_name -> ttn.lorawan.v3.EndDeviceIdentifiers + 108, // 152: ttn.lorawan.v3.ResetAndGetEndDeviceRequest.field_mask:type_name -> google.protobuf.FieldMask + 18, // 153: ttn.lorawan.v3.EndDeviceTemplate.end_device:type_name -> ttn.lorawan.v3.EndDevice + 108, // 154: ttn.lorawan.v3.EndDeviceTemplate.field_mask:type_name -> google.protobuf.FieldMask + 62, // 155: ttn.lorawan.v3.EndDeviceTemplateFormats.formats:type_name -> ttn.lorawan.v3.EndDeviceTemplateFormats.FormatsEntry + 84, // 156: ttn.lorawan.v3.ConvertEndDeviceTemplateRequest.end_device_version_ids:type_name -> ttn.lorawan.v3.EndDeviceVersionIdentifiers + 109, // 157: ttn.lorawan.v3.BatchDeleteEndDevicesRequest.application_ids:type_name -> ttn.lorawan.v3.ApplicationIdentifiers + 109, // 158: ttn.lorawan.v3.BatchGetEndDevicesRequest.application_ids:type_name -> ttn.lorawan.v3.ApplicationIdentifiers + 108, // 159: ttn.lorawan.v3.BatchGetEndDevicesRequest.field_mask:type_name -> google.protobuf.FieldMask + 75, // 160: ttn.lorawan.v3.MACParameters.Channel.min_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex + 75, // 161: ttn.lorawan.v3.MACParameters.Channel.max_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex + 75, // 162: ttn.lorawan.v3.ADRSettings.StaticMode.data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex + 95, // 163: ttn.lorawan.v3.ADRSettings.DynamicMode.margin:type_name -> google.protobuf.FloatValue + 83, // 164: ttn.lorawan.v3.ADRSettings.DynamicMode.min_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndexValue + 83, // 165: ttn.lorawan.v3.ADRSettings.DynamicMode.max_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndexValue + 74, // 166: ttn.lorawan.v3.ADRSettings.DynamicMode.min_tx_power_index:type_name -> google.protobuf.UInt32Value + 74, // 167: ttn.lorawan.v3.ADRSettings.DynamicMode.max_tx_power_index:type_name -> google.protobuf.UInt32Value + 74, // 168: ttn.lorawan.v3.ADRSettings.DynamicMode.min_nb_trans:type_name -> google.protobuf.UInt32Value + 74, // 169: ttn.lorawan.v3.ADRSettings.DynamicMode.max_nb_trans:type_name -> google.protobuf.UInt32Value + 39, // 170: ttn.lorawan.v3.ADRSettings.DynamicMode.channel_steering:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings + 41, // 171: ttn.lorawan.v3.ADRSettings.DynamicMode.overrides:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides + 42, // 172: ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.lora_narrow:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.LoRaNarrowMode + 43, // 173: ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.disabled:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.ChannelSteeringSettings.DisabledMode + 74, // 174: ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride.min_nb_trans:type_name -> google.protobuf.UInt32Value + 74, // 175: ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride.max_nb_trans:type_name -> google.protobuf.UInt32Value + 40, // 176: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_0:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 177: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_1:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 178: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_2:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 179: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_3:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 180: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_4:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 181: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_5:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 182: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_6:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 183: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_7:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 184: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_8:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 185: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_9:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 186: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_10:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 187: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_11:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 188: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_12:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 189: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_13:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 190: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_14:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 40, // 191: ttn.lorawan.v3.ADRSettings.DynamicMode.Overrides.data_rate_15:type_name -> ttn.lorawan.v3.ADRSettings.DynamicMode.PerDataRateIndexOverride + 110, // 192: ttn.lorawan.v3.MACState.JoinRequest.downlink_settings:type_name -> ttn.lorawan.v3.DLSettings + 76, // 193: ttn.lorawan.v3.MACState.JoinRequest.rx_delay:type_name -> ttn.lorawan.v3.RxDelay + 111, // 194: ttn.lorawan.v3.MACState.JoinRequest.cf_list:type_name -> ttn.lorawan.v3.CFList + 44, // 195: ttn.lorawan.v3.MACState.JoinAccept.request:type_name -> ttn.lorawan.v3.MACState.JoinRequest + 63, // 196: ttn.lorawan.v3.MACState.JoinAccept.keys:type_name -> ttn.lorawan.v3.SessionKeys + 112, // 197: ttn.lorawan.v3.MACState.UplinkMessage.payload:type_name -> ttn.lorawan.v3.Message + 51, // 198: ttn.lorawan.v3.MACState.UplinkMessage.settings:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.TxSettings + 52, // 199: ttn.lorawan.v3.MACState.UplinkMessage.rx_metadata:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata + 64, // 200: ttn.lorawan.v3.MACState.UplinkMessage.received_at:type_name -> google.protobuf.Timestamp + 55, // 201: ttn.lorawan.v3.MACState.DownlinkMessage.payload:type_name -> ttn.lorawan.v3.MACState.DownlinkMessage.Message + 75, // 202: ttn.lorawan.v3.MACState.DataRateRange.min_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex + 75, // 203: ttn.lorawan.v3.MACState.DataRateRange.max_data_rate_index:type_name -> ttn.lorawan.v3.DataRateIndex + 48, // 204: ttn.lorawan.v3.MACState.DataRateRanges.ranges:type_name -> ttn.lorawan.v3.MACState.DataRateRange + 49, // 205: ttn.lorawan.v3.MACState.RejectedDataRateRangesEntry.value:type_name -> ttn.lorawan.v3.MACState.DataRateRanges + 113, // 206: ttn.lorawan.v3.MACState.UplinkMessage.TxSettings.data_rate:type_name -> ttn.lorawan.v3.DataRate + 114, // 207: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.gateway_ids:type_name -> ttn.lorawan.v3.GatewayIdentifiers + 115, // 208: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.downlink_path_constraint:type_name -> ttn.lorawan.v3.DownlinkPathConstraint + 53, // 209: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.packet_broker:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.PacketBrokerMetadata + 54, // 210: ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.relay:type_name -> ttn.lorawan.v3.MACState.UplinkMessage.RxMetadata.RelayMetadata + 56, // 211: ttn.lorawan.v3.MACState.DownlinkMessage.Message.m_hdr:type_name -> ttn.lorawan.v3.MACState.DownlinkMessage.Message.MHDR + 57, // 212: ttn.lorawan.v3.MACState.DownlinkMessage.Message.mac_payload:type_name -> ttn.lorawan.v3.MACState.DownlinkMessage.Message.MACPayload + 116, // 213: ttn.lorawan.v3.MACState.DownlinkMessage.Message.MHDR.m_type:type_name -> ttn.lorawan.v3.MType + 117, // 214: ttn.lorawan.v3.EndDevice.LocationsEntry.value:type_name -> ttn.lorawan.v3.Location + 102, // 215: ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.EndDeviceLastSeenUpdate.ids:type_name -> ttn.lorawan.v3.EndDeviceIdentifiers + 64, // 216: ttn.lorawan.v3.BatchUpdateEndDeviceLastSeenRequest.EndDeviceLastSeenUpdate.last_seen_at:type_name -> google.protobuf.Timestamp + 64, // 217: ttn.lorawan.v3.ListEndDevicesRequest.Filter.updated_since:type_name -> google.protobuf.Timestamp + 30, // 218: ttn.lorawan.v3.EndDeviceTemplateFormats.FormatsEntry.value:type_name -> ttn.lorawan.v3.EndDeviceTemplateFormat + 219, // [219:219] is the sub-list for method output_type + 219, // [219:219] is the sub-list for method input_type + 219, // [219:219] is the sub-list for extension type_name + 219, // [219:219] is the sub-list for extension extendee + 0, // [0:219] is the sub-list for field type_name } func init() { file_ttn_lorawan_v3_end_device_proto_init() } diff --git a/pkg/ttnpb/end_device.pb.paths.fm.go b/pkg/ttnpb/end_device.pb.paths.fm.go index 07447f5e95..6828da61e3 100644 --- a/pkg/ttnpb/end_device.pb.paths.fm.go +++ b/pkg/ttnpb/end_device.pb.paths.fm.go @@ -1636,6 +1636,10 @@ var EndDeviceFieldPathsNested = []string{ "mac_settings.uplink_dwell_time.value", "mac_settings.use_adr", "mac_settings.use_adr.value", + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", "mac_state", "mac_state.current_parameters", "mac_state.current_parameters.adr_ack_delay", @@ -2206,6 +2210,7 @@ var EndDeviceFieldPathsTopLevel = []string{ "lorawan_phy_version", "lorawan_version", "mac_settings", + "mac_settings_profile_ids", "mac_state", "max_frequency", "min_frequency", @@ -2494,6 +2499,10 @@ var CreateEndDeviceRequestFieldPathsNested = []string{ "end_device.mac_settings.uplink_dwell_time.value", "end_device.mac_settings.use_adr", "end_device.mac_settings.use_adr.value", + "end_device.mac_settings_profile_ids", + "end_device.mac_settings_profile_ids.application_ids", + "end_device.mac_settings_profile_ids.application_ids.application_id", + "end_device.mac_settings_profile_ids.profile_id", "end_device.mac_state", "end_device.mac_state.current_parameters", "end_device.mac_state.current_parameters.adr_ack_delay", @@ -3284,6 +3293,10 @@ var UpdateEndDeviceRequestFieldPathsNested = []string{ "end_device.mac_settings.uplink_dwell_time.value", "end_device.mac_settings.use_adr", "end_device.mac_settings.use_adr.value", + "end_device.mac_settings_profile_ids", + "end_device.mac_settings_profile_ids.application_ids", + "end_device.mac_settings_profile_ids.application_ids.application_id", + "end_device.mac_settings_profile_ids.profile_id", "end_device.mac_state", "end_device.mac_state.current_parameters", "end_device.mac_state.current_parameters.adr_ack_delay", @@ -4125,6 +4138,10 @@ var SetEndDeviceRequestFieldPathsNested = []string{ "end_device.mac_settings.uplink_dwell_time.value", "end_device.mac_settings.use_adr", "end_device.mac_settings.use_adr.value", + "end_device.mac_settings_profile_ids", + "end_device.mac_settings_profile_ids.application_ids", + "end_device.mac_settings_profile_ids.application_ids.application_id", + "end_device.mac_settings_profile_ids.profile_id", "end_device.mac_state", "end_device.mac_state.current_parameters", "end_device.mac_state.current_parameters.adr_ack_delay", @@ -4932,6 +4949,10 @@ var EndDeviceTemplateFieldPathsNested = []string{ "end_device.mac_settings.uplink_dwell_time.value", "end_device.mac_settings.use_adr", "end_device.mac_settings.use_adr.value", + "end_device.mac_settings_profile_ids", + "end_device.mac_settings_profile_ids.application_ids", + "end_device.mac_settings_profile_ids.application_ids.application_id", + "end_device.mac_settings_profile_ids.profile_id", "end_device.mac_state", "end_device.mac_state.current_parameters", "end_device.mac_state.current_parameters.adr_ack_delay", diff --git a/pkg/ttnpb/end_device.pb.setters.fm.go b/pkg/ttnpb/end_device.pb.setters.fm.go index fa1c564259..8219c1e65f 100644 --- a/pkg/ttnpb/end_device.pb.setters.fm.go +++ b/pkg/ttnpb/end_device.pb.setters.fm.go @@ -3650,6 +3650,31 @@ func (dst *EndDevice) SetFields(src *EndDevice, paths ...string) error { dst.LoraAllianceProfileIds = nil } } + case "mac_settings_profile_ids": + if len(subs) > 0 { + var newDst, newSrc *MACSettingsProfileIdentifiers + if (src == nil || src.MacSettingsProfileIds == nil) && dst.MacSettingsProfileIds == nil { + continue + } + if src != nil { + newSrc = src.MacSettingsProfileIds + } + if dst.MacSettingsProfileIds != nil { + newDst = dst.MacSettingsProfileIds + } else { + newDst = &MACSettingsProfileIdentifiers{} + dst.MacSettingsProfileIds = newDst + } + if err := newDst.SetFields(newSrc, subs...); err != nil { + return err + } + } else { + if src != nil { + dst.MacSettingsProfileIds = src.MacSettingsProfileIds + } else { + dst.MacSettingsProfileIds = nil + } + } default: return fmt.Errorf("invalid field: '%s'", name) diff --git a/pkg/ttnpb/end_device.pb.validate.go b/pkg/ttnpb/end_device.pb.validate.go index 235b9e8160..a7af78bd9c 100644 --- a/pkg/ttnpb/end_device.pb.validate.go +++ b/pkg/ttnpb/end_device.pb.validate.go @@ -3713,6 +3713,18 @@ func (m *EndDevice) ValidateFields(paths ...string) error { } } + case "mac_settings_profile_ids": + + if v, ok := interface{}(m.GetMacSettingsProfileIds()).(interface{ ValidateFields(...string) error }); ok { + if err := v.ValidateFields(subs...); err != nil { + return EndDeviceValidationError{ + field: "mac_settings_profile_ids", + reason: "embedded message failed validation", + cause: err, + } + } + } + default: return EndDeviceValidationError{ field: name, diff --git a/pkg/ttnpb/end_device_flags.pb.go b/pkg/ttnpb/end_device_flags.pb.go index d1e97d3b00..ba6c58e18e 100644 --- a/pkg/ttnpb/end_device_flags.pb.go +++ b/pkg/ttnpb/end_device_flags.pb.go @@ -4084,6 +4084,8 @@ func AddSelectFlagsForEndDevice(flags *pflag.FlagSet, prefix string, hidden bool flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("serial-number", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("serial-number", prefix), false), flagsplugin.WithHidden(hidden))) flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("lora-alliance-profile-ids", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("lora-alliance-profile-ids", prefix), true), flagsplugin.WithHidden(hidden))) // NOTE: lora_alliance_profile_ids (LoRaAllianceProfileIdentifiers) does not seem to have select flags. + flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("mac-settings-profile-ids", prefix), flagsplugin.SelectDesc(flagsplugin.Prefix("mac-settings-profile-ids", prefix), true), flagsplugin.WithHidden(hidden))) + AddSelectFlagsForMACSettingsProfileIdentifiers(flags, flagsplugin.Prefix("mac-settings-profile-ids", prefix), hidden) } // SelectFromFlags outputs the fieldmask paths forEndDevice message from select flags. @@ -4385,6 +4387,16 @@ func PathsFromSelectFlagsForEndDevice(flags *pflag.FlagSet, prefix string) (path paths = append(paths, flagsplugin.Prefix("lora_alliance_profile_ids", prefix)) } // NOTE: lora_alliance_profile_ids (LoRaAllianceProfileIdentifiers) does not seem to have select flags. + if val, selected, err := flagsplugin.GetBool(flags, flagsplugin.Prefix("mac_settings_profile_ids", prefix)); err != nil { + return nil, err + } else if selected && val { + paths = append(paths, flagsplugin.Prefix("mac_settings_profile_ids", prefix)) + } + if selectPaths, err := PathsFromSelectFlagsForMACSettingsProfileIdentifiers(flags, flagsplugin.Prefix("mac_settings_profile_ids", prefix)); err != nil { + return nil, err + } else { + paths = append(paths, selectPaths...) + } return paths, nil } @@ -4440,6 +4452,7 @@ func AddSetFlagsForEndDevice(flags *pflag.FlagSet, prefix string, hidden bool) { flags.AddFlag(flagsplugin.NewTimestampFlag(flagsplugin.Prefix("last-seen-at", prefix), "", flagsplugin.WithHidden(hidden))) flags.AddFlag(flagsplugin.NewStringFlag(flagsplugin.Prefix("serial-number", prefix), "", flagsplugin.WithHidden(hidden))) // FIXME: Skipping LoraAllianceProfileIds because it does not seem to implement AddSetFlags. + AddSetFlagsForMACSettingsProfileIdentifiers(flags, flagsplugin.Prefix("mac-settings-profile-ids", prefix), hidden) } // SetFromFlags sets the EndDevice message from flags. @@ -4767,5 +4780,15 @@ func (m *EndDevice) SetFromFlags(flags *pflag.FlagSet, prefix string) (paths []s paths = append(paths, flagsplugin.Prefix("serial_number", prefix)) } // FIXME: Skipping LoraAllianceProfileIds because it does not seem to implement AddSetFlags. + if changed := flagsplugin.IsAnyPrefixSet(flags, flagsplugin.Prefix("mac_settings_profile_ids", prefix)); changed { + if m.MacSettingsProfileIds == nil { + m.MacSettingsProfileIds = &MACSettingsProfileIdentifiers{} + } + if setPaths, err := m.MacSettingsProfileIds.SetFromFlags(flags, flagsplugin.Prefix("mac_settings_profile_ids", prefix)); err != nil { + return nil, err + } else { + paths = append(paths, setPaths...) + } + } return paths, nil } diff --git a/pkg/ttnpb/end_device_json.pb.go b/pkg/ttnpb/end_device_json.pb.go index ea6ed100b3..246fdd70b9 100644 --- a/pkg/ttnpb/end_device_json.pb.go +++ b/pkg/ttnpb/end_device_json.pb.go @@ -3837,6 +3837,12 @@ func (x *EndDevice) MarshalProtoJSON(s *jsonplugin.MarshalState) { // NOTE: LoRaAllianceProfileIdentifiers does not seem to implement MarshalProtoJSON. golang.MarshalMessage(s, x.LoraAllianceProfileIds) } + if x.MacSettingsProfileIds != nil || s.HasField("mac_settings_profile_ids") { + s.WriteMoreIf(&wroteField) + s.WriteObjectField("mac_settings_profile_ids") + // NOTE: MACSettingsProfileIdentifiers does not seem to implement MarshalProtoJSON. + golang.MarshalMessage(s, x.MacSettingsProfileIds) + } s.WriteObjectEnd() } @@ -4172,6 +4178,16 @@ func (x *EndDevice) UnmarshalProtoJSON(s *jsonplugin.UnmarshalState) { var v LoRaAllianceProfileIdentifiers golang.UnmarshalMessage(s, &v) x.LoraAllianceProfileIds = &v + case "mac_settings_profile_ids", "macSettingsProfileIds": + s.AddField("mac_settings_profile_ids") + if s.ReadNil() { + x.MacSettingsProfileIds = nil + return + } + // NOTE: MACSettingsProfileIdentifiers does not seem to implement UnmarshalProtoJSON. + var v MACSettingsProfileIdentifiers + golang.UnmarshalMessage(s, &v) + x.MacSettingsProfileIds = &v } }) } diff --git a/pkg/ttnpb/field_mask_validation.go b/pkg/ttnpb/field_mask_validation.go index 42e9acdebc..6ac1aca86f 100644 --- a/pkg/ttnpb/field_mask_validation.go +++ b/pkg/ttnpb/field_mask_validation.go @@ -308,6 +308,10 @@ var nsEndDeviceReadFieldPaths = [...]string{ "mac_settings.uplink_dwell_time.value", "mac_settings.downlink_dwell_time", "mac_settings.downlink_dwell_time.value", + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", "mac_state", "mac_state.current_parameters", "mac_state.current_parameters.adr_ack_delay_exponent", @@ -1234,6 +1238,10 @@ var RPCFieldMaskPaths = map[string]RPCFieldMaskPathValue{ "mac_settings.uplink_dwell_time.value", "mac_settings.downlink_dwell_time", "mac_settings.downlink_dwell_time.value", + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", "mac_state", "mac_state.current_parameters", "mac_state.current_parameters.adr_ack_delay_exponent", diff --git a/pkg/ttnpb/identifiers.go b/pkg/ttnpb/identifiers.go index 0ccf66743c..4f8f0d1da8 100644 --- a/pkg/ttnpb/identifiers.go +++ b/pkg/ttnpb/identifiers.go @@ -107,6 +107,22 @@ func (ids *MACSettingsProfileIdentifiers) IsZero() bool { return ids.ProfileId == "" && ids.ApplicationIds == nil } +// FieldIsZero returns whether path p is zero. +func (v *MACSettingsProfileIdentifiers) FieldIsZero(p string) bool { + if v == nil { + return true + } + switch p { + case "application_ids": + return v.ApplicationIds == nil + case "application_ids.application_id": + return v.ApplicationIds.FieldIsZero("application_id") + case "profile_id": + return v.ProfileId == "" + } + panic(fmt.Sprintf("unknown path '%s'", p)) +} + // GetOrganizationOrUserIdentifiers returns the OrganizationIdentifiers as *OrganizationOrUserIdentifiers. func (ids *OrganizationIdentifiers) GetOrganizationOrUserIdentifiers() *OrganizationOrUserIdentifiers { if ids == nil { diff --git a/pkg/ttnpb/qrcodegenerator.pb.paths.fm.go b/pkg/ttnpb/qrcodegenerator.pb.paths.fm.go index f921d78b07..2a19075bfc 100644 --- a/pkg/ttnpb/qrcodegenerator.pb.paths.fm.go +++ b/pkg/ttnpb/qrcodegenerator.pb.paths.fm.go @@ -270,6 +270,10 @@ var GenerateEndDeviceQRCodeRequestFieldPathsNested = []string{ "end_device.mac_settings.uplink_dwell_time.value", "end_device.mac_settings.use_adr", "end_device.mac_settings.use_adr.value", + "end_device.mac_settings_profile_ids", + "end_device.mac_settings_profile_ids.application_ids", + "end_device.mac_settings_profile_ids.application_ids.application_id", + "end_device.mac_settings_profile_ids.profile_id", "end_device.mac_state", "end_device.mac_state.current_parameters", "end_device.mac_state.current_parameters.adr_ack_delay", @@ -1088,6 +1092,10 @@ var ParseEndDeviceQRCodeResponseFieldPathsNested = []string{ "end_device_template.end_device.mac_settings.uplink_dwell_time.value", "end_device_template.end_device.mac_settings.use_adr", "end_device_template.end_device.mac_settings.use_adr.value", + "end_device_template.end_device.mac_settings_profile_ids", + "end_device_template.end_device.mac_settings_profile_ids.application_ids", + "end_device_template.end_device.mac_settings_profile_ids.application_ids.application_id", + "end_device_template.end_device.mac_settings_profile_ids.profile_id", "end_device_template.end_device.mac_state", "end_device_template.end_device.mac_state.current_parameters", "end_device_template.end_device.mac_state.current_parameters.adr_ack_delay", diff --git a/sdk/js/generated/api-definition.json b/sdk/js/generated/api-definition.json index 4de765ea3b..a0c8563402 100644 --- a/sdk/js/generated/api-definition.json +++ b/sdk/js/generated/api-definition.json @@ -4760,6 +4760,10 @@ "mac_settings.uplink_dwell_time.value", "mac_settings.downlink_dwell_time", "mac_settings.downlink_dwell_time.value", + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", "mac_state", "mac_state.current_parameters", "mac_state.current_parameters.adr_ack_delay_exponent", @@ -5383,6 +5387,10 @@ "mac_settings.uplink_dwell_time.value", "mac_settings.downlink_dwell_time", "mac_settings.downlink_dwell_time.value", + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", "mac_state", "mac_state.current_parameters", "mac_state.current_parameters.adr_ack_delay_exponent", @@ -5994,6 +6002,10 @@ "mac_settings.uplink_dwell_time.value", "mac_settings.downlink_dwell_time", "mac_settings.downlink_dwell_time.value", + "mac_settings_profile_ids", + "mac_settings_profile_ids.application_ids", + "mac_settings_profile_ids.application_ids.application_id", + "mac_settings_profile_ids.profile_id", "mac_state", "mac_state.current_parameters", "mac_state.current_parameters.adr_ack_delay_exponent", diff --git a/sdk/js/generated/api.json b/sdk/js/generated/api.json index 5c4c2aae19..9be500c989 100644 --- a/sdk/js/generated/api.json +++ b/sdk/js/generated/api.json @@ -16800,6 +16800,18 @@ "isoneof": false, "oneofdecl": "", "defaultValue": "" + }, + { + "name": "mac_settings_profile_ids", + "description": "MAC settings profile identifiers.", + "label": "", + "type": "MACSettingsProfileIdentifiers", + "longType": "MACSettingsProfileIdentifiers", + "fullType": "ttn.lorawan.v3.MACSettingsProfileIdentifiers", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "" } ] },