1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.thread.cts; 18 19 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; 20 21 import static com.google.common.io.BaseEncoding.base16; 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static org.junit.Assert.assertThrows; 25 26 import android.net.IpPrefix; 27 import android.net.thread.ActiveOperationalDataset; 28 import android.net.thread.ActiveOperationalDataset.SecurityPolicy; 29 import android.net.thread.OperationalDatasetTimestamp; 30 import android.net.thread.PendingOperationalDataset; 31 import android.net.thread.utils.ThreadFeatureCheckerRule; 32 import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature; 33 import android.util.SparseArray; 34 35 import androidx.test.ext.junit.runners.AndroidJUnit4; 36 import androidx.test.filters.SmallTest; 37 38 import com.google.common.primitives.Bytes; 39 import com.google.common.testing.EqualsTester; 40 41 import org.junit.Rule; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 45 import java.net.InetAddress; 46 import java.time.Duration; 47 48 /** Tests for {@link PendingOperationalDataset}. */ 49 @SmallTest 50 @RequiresThreadFeature 51 @RunWith(AndroidJUnit4.class) 52 public final class PendingOperationalDatasetTest { 53 @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule(); 54 createActiveDataset()55 private static ActiveOperationalDataset createActiveDataset() throws Exception { 56 SparseArray<byte[]> channelMask = new SparseArray<>(1); 57 channelMask.put(0, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0}); 58 59 return new ActiveOperationalDataset.Builder() 60 .setActiveTimestamp(new OperationalDatasetTimestamp(100, 10, false)) 61 .setExtendedPanId(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}) 62 .setPanId(12345) 63 .setNetworkName("defaultNet") 64 .setChannel(0, 18) 65 .setChannelMask(channelMask) 66 .setPskc(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) 67 .setNetworkKey(new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) 68 .setMeshLocalPrefix(new IpPrefix(InetAddress.getByName("fd00::1"), 64)) 69 .setSecurityPolicy(new SecurityPolicy(672, new byte[] {(byte) 0xff, (byte) 0xf8})) 70 .build(); 71 } 72 73 @Test parcelable_parcelingIsLossLess()74 public void parcelable_parcelingIsLossLess() throws Exception { 75 PendingOperationalDataset dataset = 76 new PendingOperationalDataset( 77 createActiveDataset(), 78 new OperationalDatasetTimestamp(31536000, 200, false), 79 Duration.ofHours(100)); 80 81 assertParcelingIsLossless(dataset); 82 } 83 84 @Test equalityTests()85 public void equalityTests() throws Exception { 86 ActiveOperationalDataset activeDataset1 = 87 new ActiveOperationalDataset.Builder(createActiveDataset()) 88 .setNetworkName("net1") 89 .build(); 90 ActiveOperationalDataset activeDataset2 = 91 new ActiveOperationalDataset.Builder(createActiveDataset()) 92 .setNetworkName("net2") 93 .build(); 94 95 new EqualsTester() 96 .addEqualityGroup( 97 new PendingOperationalDataset( 98 activeDataset1, 99 new OperationalDatasetTimestamp(31536000, 100, false), 100 Duration.ofMillis(0)), 101 new PendingOperationalDataset( 102 activeDataset1, 103 new OperationalDatasetTimestamp(31536000, 100, false), 104 Duration.ofMillis(0))) 105 .addEqualityGroup( 106 new PendingOperationalDataset( 107 activeDataset2, 108 new OperationalDatasetTimestamp(31536000, 100, false), 109 Duration.ofMillis(0)), 110 new PendingOperationalDataset( 111 activeDataset2, 112 new OperationalDatasetTimestamp(31536000, 100, false), 113 Duration.ofMillis(0))) 114 .addEqualityGroup( 115 new PendingOperationalDataset( 116 activeDataset2, 117 new OperationalDatasetTimestamp(15768000, 0, false), 118 Duration.ofMillis(0)), 119 new PendingOperationalDataset( 120 activeDataset2, 121 new OperationalDatasetTimestamp(15768000, 0, false), 122 Duration.ofMillis(0))) 123 .addEqualityGroup( 124 new PendingOperationalDataset( 125 activeDataset2, 126 new OperationalDatasetTimestamp(15768000, 0, false), 127 Duration.ofMillis(100)), 128 new PendingOperationalDataset( 129 activeDataset2, 130 new OperationalDatasetTimestamp(15768000, 0, false), 131 Duration.ofMillis(100))) 132 .testEquals(); 133 } 134 135 @Test constructor_correctValuesAreSet()136 public void constructor_correctValuesAreSet() throws Exception { 137 final ActiveOperationalDataset activeDataset = createActiveDataset(); 138 PendingOperationalDataset dataset = 139 new PendingOperationalDataset( 140 activeDataset, 141 new OperationalDatasetTimestamp(31536000, 200, false), 142 Duration.ofHours(100)); 143 144 assertThat(dataset.getActiveOperationalDataset()).isEqualTo(activeDataset); 145 assertThat(dataset.getPendingTimestamp()) 146 .isEqualTo(new OperationalDatasetTimestamp(31536000, 200, false)); 147 assertThat(dataset.getDelayTimer()).isEqualTo(Duration.ofHours(100)); 148 } 149 150 @Test fromThreadTlvs_openthreadTlvs_success()151 public void fromThreadTlvs_openthreadTlvs_success() { 152 // An example Pending Operational Dataset which is generated with OpenThread CLI: 153 // Pending Timestamp: 2 154 // Active Timestamp: 1 155 // Channel: 26 156 // Channel Mask: 0x07fff800 157 // Delay: 46354 158 // Ext PAN ID: a74182f4d3f4de41 159 // Mesh Local Prefix: fd46:c1b9:e159:5574::/64 160 // Network Key: ed916e454d96fd00184f10a6f5c9e1d3 161 // Network Name: OpenThread-bff8 162 // PAN ID: 0xbff8 163 // PSKc: 264f78414adc683191863d968f72d1b7 164 // Security Policy: 672 onrc 165 final byte[] OPENTHREAD_PENDING_DATASET_TLVS = 166 base16().lowerCase() 167 .decode( 168 "0e0800000000000100003308000000000002000034040000b51200030000" 169 + "1a35060004001fffe00208a74182f4d3f4de410708fd46c1b9" 170 + "e15955740510ed916e454d96fd00184f10a6f5c9e1d3030f4f" 171 + "70656e5468726561642d626666380102bff80410264f78414a" 172 + "dc683191863d968f72d1b70c0402a0f7f8"); 173 174 PendingOperationalDataset pendingDataset = 175 PendingOperationalDataset.fromThreadTlvs(OPENTHREAD_PENDING_DATASET_TLVS); 176 177 ActiveOperationalDataset activeDataset = pendingDataset.getActiveOperationalDataset(); 178 assertThat(pendingDataset.getPendingTimestamp().getSeconds()).isEqualTo(2L); 179 assertThat(activeDataset.getActiveTimestamp().getSeconds()).isEqualTo(1L); 180 assertThat(activeDataset.getChannel()).isEqualTo(26); 181 assertThat(activeDataset.getChannelMask().get(0)) 182 .isEqualTo(new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0}); 183 assertThat(pendingDataset.getDelayTimer().toMillis()).isEqualTo(46354); 184 assertThat(activeDataset.getExtendedPanId()) 185 .isEqualTo(base16().lowerCase().decode("a74182f4d3f4de41")); 186 assertThat(activeDataset.getMeshLocalPrefix()) 187 .isEqualTo(new IpPrefix("fd46:c1b9:e159:5574::/64")); 188 assertThat(activeDataset.getNetworkKey()) 189 .isEqualTo(base16().lowerCase().decode("ed916e454d96fd00184f10a6f5c9e1d3")); 190 assertThat(activeDataset.getNetworkName()).isEqualTo("OpenThread-bff8"); 191 assertThat(activeDataset.getPanId()).isEqualTo(0xbff8); 192 assertThat(activeDataset.getPskc()) 193 .isEqualTo(base16().lowerCase().decode("264f78414adc683191863d968f72d1b7")); 194 assertThat(activeDataset.getSecurityPolicy().getRotationTimeHours()).isEqualTo(672); 195 assertThat(activeDataset.getSecurityPolicy().getFlags()) 196 .isEqualTo(new byte[] {(byte) 0xf7, (byte) 0xf8}); 197 } 198 199 @Test fromThreadTlvs_completePendingDatasetTlvs_success()200 public void fromThreadTlvs_completePendingDatasetTlvs_success() throws Exception { 201 final ActiveOperationalDataset activeDataset = createActiveDataset(); 202 203 // Type Length Value 204 // 0x33 0x08 0x0000000000010000 (Pending Timestamp TLV) 205 // 0x34 0x04 0x0000012C (Delay Timer TLV) 206 final byte[] pendingTimestampAndDelayTimerTlvs = 207 base16().decode("3308000000000001000034040000012C"); 208 final byte[] pendingDatasetTlvs = 209 Bytes.concat(pendingTimestampAndDelayTimerTlvs, activeDataset.toThreadTlvs()); 210 211 PendingOperationalDataset dataset = 212 PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs); 213 214 assertThat(dataset.getActiveOperationalDataset()).isEqualTo(activeDataset); 215 assertThat(dataset.getPendingTimestamp()) 216 .isEqualTo(new OperationalDatasetTimestamp(1, 0, false)); 217 assertThat(dataset.getDelayTimer()).isEqualTo(Duration.ofMillis(300)); 218 } 219 220 @Test fromThreadTlvs_PendingTimestampTlvIsMissing_throwsIllegalArgument()221 public void fromThreadTlvs_PendingTimestampTlvIsMissing_throwsIllegalArgument() 222 throws Exception { 223 // Type Length Value 224 // 0x34 0x04 0x00000064 (Delay Timer TLV) 225 final byte[] pendingTimestampAndDelayTimerTlvs = base16().decode("34040000012C"); 226 final byte[] pendingDatasetTlvs = 227 Bytes.concat( 228 pendingTimestampAndDelayTimerTlvs, createActiveDataset().toThreadTlvs()); 229 230 assertThrows( 231 IllegalArgumentException.class, 232 () -> PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs)); 233 } 234 235 @Test fromThreadTlvs_delayTimerTlvIsMissing_throwsIllegalArgument()236 public void fromThreadTlvs_delayTimerTlvIsMissing_throwsIllegalArgument() throws Exception { 237 // Type Length Value 238 // 0x33 0x08 0x0000000000010000 (Pending Timestamp TLV) 239 final byte[] pendingTimestampAndDelayTimerTlvs = base16().decode("33080000000000010000"); 240 final byte[] pendingDatasetTlvs = 241 Bytes.concat( 242 pendingTimestampAndDelayTimerTlvs, createActiveDataset().toThreadTlvs()); 243 244 assertThrows( 245 IllegalArgumentException.class, 246 () -> PendingOperationalDataset.fromThreadTlvs(pendingDatasetTlvs)); 247 } 248 249 @Test fromThreadTlvs_activeDatasetTlvs_throwsIllegalArgument()250 public void fromThreadTlvs_activeDatasetTlvs_throwsIllegalArgument() throws Exception { 251 final byte[] activeDatasetTlvs = createActiveDataset().toThreadTlvs(); 252 253 assertThrows( 254 IllegalArgumentException.class, 255 () -> PendingOperationalDataset.fromThreadTlvs(activeDatasetTlvs)); 256 } 257 258 @Test fromThreadTlvs_malformedTlvs_throwsIllegalArgument()259 public void fromThreadTlvs_malformedTlvs_throwsIllegalArgument() { 260 final byte[] invalidTlvs = new byte[] {0x00}; 261 262 assertThrows( 263 IllegalArgumentException.class, 264 () -> PendingOperationalDataset.fromThreadTlvs(invalidTlvs)); 265 } 266 267 @Test toThreadTlvs_conversionIsLossLess()268 public void toThreadTlvs_conversionIsLossLess() throws Exception { 269 PendingOperationalDataset dataset1 = 270 new PendingOperationalDataset( 271 createActiveDataset(), 272 new OperationalDatasetTimestamp(31536000, 200, false), 273 Duration.ofHours(100)); 274 275 PendingOperationalDataset dataset2 = 276 PendingOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs()); 277 278 assertThat(dataset2).isEqualTo(dataset1); 279 } 280 } 281