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;
18 
19 import static com.google.common.io.BaseEncoding.base16;
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.junit.Assert.assertThrows;
23 
24 import android.net.thread.ActiveOperationalDataset.Builder;
25 import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
26 import android.util.SparseArray;
27 
28 import androidx.test.ext.junit.runners.AndroidJUnit4;
29 import androidx.test.filters.SmallTest;
30 
31 import com.google.common.primitives.Bytes;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.mockito.MockitoAnnotations;
37 
38 /** Unit tests for {@link ActiveOperationalDataset}. */
39 @SmallTest
40 @RunWith(AndroidJUnit4.class)
41 public class ActiveOperationalDatasetTest {
42     // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
43     // Active Timestamp: 1
44     // Channel: 19
45     // Channel Mask: 0x07FFF800
46     // Ext PAN ID: ACC214689BC40BDF
47     // Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
48     // Network Key: F26B3153760F519A63BAFDDFFC80D2AF
49     // Network Name: OpenThread-d9a0
50     // PAN ID: 0xD9A0
51     // PSKc: A245479C836D551B9CA557F7B9D351B4
52     // Security Policy: 672 onrcb
53     private static final byte[] VALID_DATASET_TLVS =
54             base16().decode(
55                             "0E080000000000010000000300001335060004001FFFE002"
56                                     + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
57                                     + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
58                                     + "642D643961300102D9A00410A245479C836D551B9CA557F7"
59                                     + "B9D351B40C0402A0FFF8");
60 
61     @Before
setUp()62     public void setUp() {
63         MockitoAnnotations.initMocks(this);
64     }
65 
addTlv(byte[] dataset, String tlvHex)66     private static byte[] addTlv(byte[] dataset, String tlvHex) {
67         return Bytes.concat(dataset, base16().decode(tlvHex));
68     }
69 
70     @Test
fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained()71     public void fromThreadTlvs_containsUnknownTlvs_unknownTlvsRetained() {
72         byte[] datasetWithUnknownTlvs = addTlv(VALID_DATASET_TLVS, "AA01FFBB020102");
73 
74         ActiveOperationalDataset dataset1 =
75                 ActiveOperationalDataset.fromThreadTlvs(datasetWithUnknownTlvs);
76         ActiveOperationalDataset dataset2 =
77                 ActiveOperationalDataset.fromThreadTlvs(dataset1.toThreadTlvs());
78 
79         SparseArray<byte[]> unknownTlvs = dataset2.getUnknownTlvs();
80         assertThat(unknownTlvs.size()).isEqualTo(2);
81         assertThat(unknownTlvs.get(0xAA)).isEqualTo(new byte[] {(byte) 0xFF});
82         assertThat(unknownTlvs.get(0xBB)).isEqualTo(new byte[] {0x01, 0x02});
83         assertThat(dataset2).isEqualTo(dataset1);
84     }
85 
86     @Test
builder_buildWithTooLongTlvs_throwsIllegalState()87     public void builder_buildWithTooLongTlvs_throwsIllegalState() {
88         Builder builder = new Builder(ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS));
89         for (int i = 0; i < 10; i++) {
90             builder.addUnknownTlv(i, new byte[20]);
91         }
92 
93         assertThrows(IllegalStateException.class, () -> new Builder().build());
94     }
95 
96     @Test
builder_setUnknownTlvs_success()97     public void builder_setUnknownTlvs_success() {
98         ActiveOperationalDataset dataset1 =
99                 ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS);
100         SparseArray<byte[]> unknownTlvs = new SparseArray<>(2);
101         unknownTlvs.put(0x33, new byte[] {1, 2, 3});
102         unknownTlvs.put(0x44, new byte[] {1, 2, 3, 4});
103 
104         ActiveOperationalDataset dataset2 =
105                 new ActiveOperationalDataset.Builder(dataset1).setUnknownTlvs(unknownTlvs).build();
106 
107         assertThat(dataset1.getUnknownTlvs().size()).isEqualTo(0);
108         assertThat(dataset2.getUnknownTlvs().size()).isEqualTo(2);
109         assertThat(dataset2.getUnknownTlvs().get(0x33)).isEqualTo(new byte[] {1, 2, 3});
110         assertThat(dataset2.getUnknownTlvs().get(0x44)).isEqualTo(new byte[] {1, 2, 3, 4});
111     }
112 
113     @Test
securityPolicy_fromTooShortTlvValue_throwsIllegalArgument()114     public void securityPolicy_fromTooShortTlvValue_throwsIllegalArgument() {
115         assertThrows(
116                 IllegalArgumentException.class,
117                 () -> SecurityPolicy.fromTlvValue(new byte[] {0x01}));
118         assertThrows(
119                 IllegalArgumentException.class,
120                 () -> SecurityPolicy.fromTlvValue(new byte[] {0x01, 0x02}));
121     }
122 
123     @Test
securityPolicy_toTlvValue_conversionIsLossLess()124     public void securityPolicy_toTlvValue_conversionIsLossLess() {
125         SecurityPolicy policy1 = new SecurityPolicy(200, new byte[] {(byte) 0xFF, (byte) 0xF8});
126 
127         SecurityPolicy policy2 = SecurityPolicy.fromTlvValue(policy1.toTlvValue());
128 
129         assertThat(policy2).isEqualTo(policy1);
130     }
131 }
132