Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ For details about compatibility between different releases, see the **Commitment

### Fixed

- Applying the MAC settings profile values to the device's MAC state during the join procedure (OTAA) or factory reset (ABP).

### Security

## [3.36.1] - 2026-06-24
Expand Down
1 change: 1 addition & 0 deletions pkg/networkserver/grpc_deviceregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,7 @@ func (ns *NetworkServer) ResetFactoryDefaults(ctx context.Context, req *ttnpb.Re
"lorawan_phy_version",
"lorawan_version",
"mac_settings",
"mac_settings_profile_ids",
"multicast",
"session.dev_addr",
"session.keys",
Expand Down
66 changes: 49 additions & 17 deletions pkg/networkserver/grpc_deviceregistry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1019,9 +1019,23 @@ func TestDeviceRegistryResetFactoryDefaults(t *testing.T) {
macSettings := test.Must(DefaultConfig.DefaultMACSettings.Parse())
activateOpt := EndDeviceOptions.Activate(macSettings, true, activeSessionOpts)

macSettingsProfileID := &ttnpb.MACSettingsProfileIdentifiers{
ApplicationIds: &ttnpb.ApplicationIdentifiers{
ApplicationId: "test-app-id",
},
ProfileId: "test-mac-settings-profile-id",
}
macSettingsProfile := &ttnpb.MACSettingsProfile{
Ids: macSettingsProfileID,
MacSettings: &ttnpb.MACSettings{
Rx1Delay: &ttnpb.RxDelayValue{Value: ttnpb.RxDelay_RX_DELAY_2},
},
}

// TODO: Refactor into same structure as Set
for _, tc := range []struct {
CreateDevice *SetDeviceRequest
Profile *ttnpb.MACSettingsProfile
}{
{},

Expand Down Expand Up @@ -1059,6 +1073,12 @@ func TestDeviceRegistryResetFactoryDefaults(t *testing.T) {
EndDeviceOptions.WithLorawanPhyVersion(ttnpb.PHYVersion_RP001_V1_0_3_REV_A),
}),
},
{
CreateDevice: MakeABPSetDeviceRequest(macSettings, activeSessionOpts, nil, []test.EndDeviceOption{
EndDeviceOptions.WithMacSettingsProfileIds(macSettingsProfileID),
}),
Profile: macSettingsProfile,
},
} {
for _, conf := range []struct {
Paths []string
Expand Down Expand Up @@ -1134,22 +1154,24 @@ func TestDeviceRegistryResetFactoryDefaults(t *testing.T) {
if tc.CreateDevice == nil {
return "no device"
}
return MakeTestCaseName(
fmt.Sprintf("paths:[%s]", strings.Join(conf.Paths, ",")),
func() string {
if tc.CreateDevice.EndDevice.SupportsJoin {
return "OTAA"
}
if tc.CreateDevice.EndDevice.Session == nil {
return MakeTestCaseName("ABP", "no session")
}
return fmt.Sprintf(MakeTestCaseName("ABP", "dev_addr:%s", "queue_len:%d", "session_keys:%v"),
types.MustDevAddr(tc.CreateDevice.Session.DevAddr).OrZero(),
len(tc.CreateDevice.EndDevice.Session.QueuedApplicationDownlinks),
tc.CreateDevice.Session.Keys,
)
}(),
)
nameParts := []string{fmt.Sprintf("paths:[%s]", strings.Join(conf.Paths, ","))}
switch {
case tc.CreateDevice.SupportsJoin:
nameParts = append(nameParts, "OTAA")
case tc.CreateDevice.Session == nil:
nameParts = append(nameParts, MakeTestCaseName("ABP", "no session"))
default:
nameParts = append(nameParts, fmt.Sprintf(
MakeTestCaseName("ABP", "dev_addr:%s", "queue_len:%d", "session_keys:%v"),
types.MustDevAddr(tc.CreateDevice.Session.DevAddr).OrZero(),
len(tc.CreateDevice.Session.QueuedApplicationDownlinks),
tc.CreateDevice.Session.Keys,
))
if tc.Profile != nil {
nameParts = append(nameParts, "with_profile")
}
}
return MakeTestCaseName(nameParts...)
}(),
Parallel: true,
Func: func(ctx context.Context, t *testing.T, a *assertions.Assertion) {
Expand Down Expand Up @@ -1183,6 +1205,16 @@ func TestDeviceRegistryResetFactoryDefaults(t *testing.T) {
clock := test.NewMockClock(time.Now().UTC())
defer SetMockClock(clock)()

if tc.Profile != nil {
_, err := env.MACSettingsProfileRegistry.Set(ctx, tc.Profile.Ids, []string{"ids", "mac_settings"},
func(context.Context, *ttnpb.MACSettingsProfile) (*ttnpb.MACSettingsProfile, []string, error) {
return tc.Profile, []string{"ids", "mac_settings"}, nil
})
if !a.So(err, should.BeNil) {
return
}
}

req := &ttnpb.ResetAndGetEndDeviceRequest{
EndDeviceIds: test.MakeEndDeviceIdentifiers(),
FieldMask: ttnpb.FieldMask(conf.Paths...),
Expand Down Expand Up @@ -1244,7 +1276,7 @@ func TestDeviceRegistryResetFactoryDefaults(t *testing.T) {
}
var newErr error
defaultMACSettings := test.Must(DefaultConfig.DefaultMACSettings.Parse())
macState, newErr = mac.NewState(created, fps, defaultMACSettings, nil)
macState, newErr = mac.NewState(created, fps, defaultMACSettings, tc.Profile.GetMacSettings())
if newErr != nil {
a.So(err, should.NotBeNil)
a.So(err, should.HaveSameErrorDefinitionAs, newErr)
Expand Down
4 changes: 3 additions & 1 deletion pkg/networkserver/grpc_gsns.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,11 +840,13 @@ func appendRecentUplink(
}

var handleDataUplinkGetPaths = [...]string{
"battery_percentage",
"frequency_plan_id",
"last_dev_status_received_at",
"lorawan_phy_version",
"lorawan_version",
"mac_settings",
"mac_settings_profile_ids",
"mac_state",
"multicast",
"pending_mac_state",
Expand All @@ -853,7 +855,6 @@ var handleDataUplinkGetPaths = [...]string{
"supports_class_b",
"supports_class_c",
"supports_join",
"battery_percentage",
}

// mergeMetadata merges the metadata collected for up.
Expand Down Expand Up @@ -1264,6 +1265,7 @@ func (ns *NetworkServer) handleJoinRequest(ctx context.Context, up *ttnpb.Uplink
"lorawan_phy_version",
"lorawan_version",
"mac_settings",
"mac_settings_profile_ids",
"session.dev_addr",
"supports_class_b",
"supports_class_c",
Expand Down
56 changes: 56 additions & 0 deletions pkg/networkserver/networkserver_flow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ type OTAAFlowTestConfig struct {
CreateDevice *ttnpb.SetEndDeviceRequest
Func func(context.Context, TestEnvironment, *ttnpb.EndDevice)

Profile *ttnpb.MACSettingsProfile

UplinkMACCommanders []MACCommander
UplinkEventBuilders []events.Builder
DownlinkHeadMACCommanders []MACCommander
Expand All @@ -171,6 +173,18 @@ func makeOTAAFlowTest(conf OTAAFlowTestConfig) func(context.Context, TestEnviron

start := time.Now()

if conf.Profile != nil {
profilePaths := []string{"ids", "mac_settings"} // nolint: goconst
_, err := env.MACSettingsProfileRegistry.Set(ctx, conf.Profile.Ids, profilePaths,
func(context.Context, *ttnpb.MACSettingsProfile) (*ttnpb.MACSettingsProfile, []string, error) {
return conf.Profile, profilePaths, nil
})
if !a.So(err, should.BeNil) {
t.Error("Failed to register MAC settings profile")
return
}
}

dev, err, ok := env.AssertSetDevice(ctx, true, conf.CreateDevice,
ttnpb.Right_RIGHT_APPLICATION_DEVICES_WRITE,
)
Expand Down Expand Up @@ -202,6 +216,8 @@ func makeOTAAFlowTest(conf OTAAFlowTestConfig) func(context.Context, TestEnviron
"GsNs-join-2",
},

Profile: conf.Profile,

ClusterResponse: &NsJsHandleJoinResponse{
Response: &ttnpb.JoinResponse{
RawPayload: bytes.Repeat([]byte{0x42}, responseLen),
Expand Down Expand Up @@ -432,11 +448,51 @@ func makeClassCOTAAFlowTest(macVersion ttnpb.MACVersion, phyVersion ttnpb.PHYVer
})
}

func makeOTAAFlowTestWithMACSettingsProfile( // nolint: lll
macVersion ttnpb.MACVersion, phyVersion ttnpb.PHYVersion, fpID string,
) func(context.Context, TestEnvironment) {
const testProfileID = "test-mac-settings-profile-id"
profileID := &ttnpb.MACSettingsProfileIdentifiers{
ApplicationIds: test.DefaultApplicationIdentifiers,
ProfileId: testProfileID,
}
profile := &ttnpb.MACSettingsProfile{
Ids: profileID,
MacSettings: &ttnpb.MACSettings{
Rx1Delay: &ttnpb.RxDelayValue{Value: ttnpb.RxDelay_RX_DELAY_2},
},
}
return makeOTAAFlowTest(OTAAFlowTestConfig{
Profile: profile,
CreateDevice: &ttnpb.SetEndDeviceRequest{
EndDevice: MakeOTAAEndDevice(
EndDeviceOptions.WithFrequencyPlanId(fpID),
EndDeviceOptions.WithLorawanVersion(macVersion),
EndDeviceOptions.WithLorawanPhyVersion(phyVersion),
EndDeviceOptions.WithMacSettingsProfileIds(profileID),
),
FieldMask: ttnpb.FieldMask(
"frequency_plan_id",
"lorawan_phy_version",
"lorawan_version",
"mac_settings_profile_ids",
"supports_join",
),
},
DownlinkTailMACCommanders: []MACCommander{ttnpb.MACCommandIdentifier_CID_DEV_STATUS},
DownlinkTailEventBuilders: []events.Builder{mac.EvtEnqueueDevStatusRequest},
Func: func(context.Context, TestEnvironment, *ttnpb.EndDevice) {},
})
}

func TestFlow(t *testing.T) {
ForEachFrequencyPlanLoRaWANVersionPair(t, func(makeName func(...string) string, fpID string, _ *frequencyplans.FrequencyPlan, phy *band.Band, macVersion ttnpb.MACVersion, phyVersion ttnpb.PHYVersion) {
for flowName, handleFlowTest := range map[string]func(context.Context, TestEnvironment){
MakeTestCaseName("Class A", "OTAA"): makeClassAOTAAFlowTest(macVersion, phyVersion, fpID),
MakeTestCaseName("Class C", "OTAA"): makeClassCOTAAFlowTest(macVersion, phyVersion, fpID),
MakeTestCaseName("Class A", "OTAA", "with profile"): makeOTAAFlowTestWithMACSettingsProfile( // nolint: lll
macVersion, phyVersion, fpID,
),
} {
test.RunSubtest(t, test.SubtestConfig{
Name: makeName(flowName),
Expand Down
4 changes: 3 additions & 1 deletion pkg/networkserver/networkserver_util_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,8 @@ type JoinAssertionConfig struct {
RxMetadatas [][]*ttnpb.RxMetadata
CorrelationIDs []string

Profile *ttnpb.MACSettingsProfile

ClusterResponse *NsJsHandleJoinResponse
InteropResponse *InteropClientHandleJoinRequestResponse
}
Expand Down Expand Up @@ -1600,7 +1602,7 @@ func (env TestEnvironment) AssertJoin(ctx context.Context, conf JoinAssertionCon
t, a := test.MustNewTFromContext(ctx)
t.Helper()

var profileMACSettings *ttnpb.MACSettings
profileMACSettings := conf.Profile.GetMacSettings()
defaultMACSettings := test.Must(env.Config.DefaultMACSettings.Parse())

defaultLoRaWANVersion := mac.DeviceDefaultLoRaWANVersion(conf.Device)
Expand Down
Loading