1 /*
2  * Copyright (C) 2021 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 com.google.android.iwlan.epdg;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
20 
21 import static com.google.android.iwlan.epdg.NaptrDnsResolver.QUERY_TYPE_NAPTR;
22 import static com.google.android.iwlan.epdg.NaptrDnsResolver.TYPE_SRV;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertNotNull;
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.Mockito.doAnswer;
29 import static org.mockito.Mockito.lenient;
30 
31 import android.net.DnsResolver;
32 import android.net.Network;
33 import android.support.annotation.NonNull;
34 import android.support.annotation.Nullable;
35 import android.util.Log;
36 
37 import com.google.android.iwlan.epdg.NaptrDnsResolver.NaptrTarget;
38 
39 import org.junit.After;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.mockito.ArgumentMatchers;
43 import org.mockito.Mock;
44 import org.mockito.MockitoAnnotations;
45 import org.mockito.MockitoSession;
46 
47 import java.net.UnknownHostException;
48 import java.util.List;
49 import java.util.concurrent.CompletableFuture;
50 import java.util.concurrent.CompletionException;
51 import java.util.concurrent.ExecutionException;
52 import java.util.concurrent.Executor;
53 import java.util.concurrent.Executors;
54 
55 public class NaptrDnsResolverTest {
56     private static final String TAG = "NaptrDnsResolverTest";
57 
58     private static final String TEST_DOMAIN_NAME = "columbia.edu";
59     private static final String TEST_DOMAIN_NAME_U_RECORD = "columbia.urecord.edu";
60 
61     // SRV record response to TEST_DOMAIN_NAME. Reproduced with "dig columbia.edu -tNAPTR".
62     private static final byte[] TEST_DOMAIN_NAME_NAPTR_RESPONSE = {
63         -15, 85, -127, -128, 0, 1, 0, 1, 0, 0, 0, 0, 8, 99, 111, 108, 117, 109, 98, 105, 97, 3, 101,
64         100, 117, 0, 0, 35, 0, 1, -64, 12, 0, 35, 0, 1, 0, 0, 11, -88, 0, 39, 0, 1, 0, 0, 1, 115, 7,
65         83, 73, 80, 43, 68, 50, 85, 0, 4, 95, 115, 105, 112, 4, 95, 117, 100, 112, 8, 99, 111, 108,
66         117, 109, 98, 105, 97, 3, 101, 100, 117, 0
67     };
68 
69     // Same as TEST_DOMAIN_NAME_NAPTR_RESPONSE, but with flag field set to 'u' instead of 's'.
70     private static final byte[] TEST_DOMAIN_NAME_U_RECORD_NAPTR_RESPONSE = {
71         -15, 85, -127, -128, 0, 1, 0, 1, 0, 0, 0, 0, 8, 99, 111, 108, 117, 109, 98, 105, 97, 3, 101,
72         100, 117, 0, 0, 35, 0, 1, -64, 12, 0, 35, 0, 1, 0, 0, 11, -88, 0, 39, 0, 1, 0, 0, 1, 117, 7,
73         83, 73, 80, 43, 68, 50, 85, 0, 4, 95, 115, 105, 112, 4, 95, 117, 100, 112, 8, 99, 111, 108,
74         117, 109, 98, 105, 97, 3, 101, 100, 117, 0
75     };
76 
77     @Mock private Network mMockNetwork;
78     @Mock private DnsResolver mMockDnsResolver;
79 
80     MockitoSession mStaticMockSession;
81     CompletableFuture<List<NaptrTarget>> mNaptrDnsResult;
82     DnsResolver.Callback<List<NaptrTarget>> mNaptrDnsCb;
83 
NaptrDnsResolverTest()84     public NaptrDnsResolverTest() {
85         mNaptrDnsResult = new CompletableFuture<>();
86         mNaptrDnsCb =
87                 new DnsResolver.Callback<List<NaptrTarget>>() {
88                     @Override
89                     public void onAnswer(@NonNull final List<NaptrTarget> answer, final int rcode) {
90                         if (rcode == 0 && answer.size() != 0) {
91                             mNaptrDnsResult.complete(answer);
92                         } else {
93                             mNaptrDnsResult.completeExceptionally(new UnknownHostException());
94                         }
95                     }
96 
97                     @Override
98                     public void onError(@Nullable final DnsResolver.DnsException error) {
99                         mNaptrDnsResult.completeExceptionally(error);
100                     }
101                 };
102     }
103 
104     @Before
setUp()105     public void setUp() throws Exception {
106         MockitoAnnotations.initMocks(this);
107         mStaticMockSession = mockitoSession().mockStatic(DnsResolver.class).startMocking();
108 
109         // lenient() here is used to mock the static method.
110         lenient().when(DnsResolver.getInstance()).thenReturn(mMockDnsResolver);
111     }
112 
113     @After
cleanUp()114     public void cleanUp() throws Exception {
115         mStaticMockSession.finishMocking();
116     }
117 
118     // Demonstrates that NAPTR responses with flag field 'S' and 'A' will be parsed as expected.
119     @Test
testValidHostNameGivesNaptrResponse()120     public void testValidHostNameGivesNaptrResponse()
121             throws ExecutionException, InterruptedException {
122         doAnswer(
123                         invocation -> {
124                             final Executor executor = invocation.getArgument(5);
125                             final DnsResolver.Callback<byte[]> callback = invocation.getArgument(7);
126                             executor.execute(
127                                     () -> callback.onAnswer(TEST_DOMAIN_NAME_NAPTR_RESPONSE, 0));
128                             return null;
129                         })
130                 .when(mMockDnsResolver)
131                 .rawQuery(
132                         any(),
133                         ArgumentMatchers.eq(TEST_DOMAIN_NAME),
134                         ArgumentMatchers.eq(DnsResolver.CLASS_IN),
135                         ArgumentMatchers.eq(QUERY_TYPE_NAPTR),
136                         anyInt(),
137                         any(),
138                         any(),
139                         any());
140 
141         NaptrDnsResolver.query(
142                 mMockNetwork,
143                 TEST_DOMAIN_NAME,
144                 Executors.newSingleThreadExecutor(),
145                 null,
146                 mNaptrDnsCb);
147         List<NaptrTarget> records = mNaptrDnsResult.join();
148         assertEquals(1, records.size());
149 
150         NaptrTarget record = records.get(0);
151         assertEquals("_sip._udp.columbia.edu", record.mName);
152         // SRV record type.
153         assertEquals(TYPE_SRV, record.mType);
154     }
155 
156     // Demonstrates that NAPTR responses with flag field 'U' and 'P' are unexpected and with throw
157     // a DnsException.
158     @Test
testValidHostNameGivesParsingErrorForUnexpectedResponse()159     public void testValidHostNameGivesParsingErrorForUnexpectedResponse()
160             throws ExecutionException, InterruptedException {
161         doAnswer(
162                         invocation -> {
163                             final Executor executor = invocation.getArgument(5);
164                             final DnsResolver.Callback<byte[]> callback = invocation.getArgument(7);
165                             executor.execute(
166                                     () ->
167                                             callback.onAnswer(
168                                                     TEST_DOMAIN_NAME_U_RECORD_NAPTR_RESPONSE, 0));
169                             return null;
170                         })
171                 .when(mMockDnsResolver)
172                 .rawQuery(
173                         any(),
174                         ArgumentMatchers.eq(TEST_DOMAIN_NAME_U_RECORD),
175                         ArgumentMatchers.eq(DnsResolver.CLASS_IN),
176                         ArgumentMatchers.eq(QUERY_TYPE_NAPTR),
177                         anyInt(),
178                         any(),
179                         any(),
180                         any());
181 
182         NaptrDnsResolver.query(
183                 mMockNetwork,
184                 TEST_DOMAIN_NAME_U_RECORD,
185                 Executors.newSingleThreadExecutor(),
186                 null,
187                 mNaptrDnsCb);
188 
189         DnsResolver.DnsException exception = null;
190         try {
191             mNaptrDnsResult.join();
192         } catch (CompletionException e) {
193             exception = (DnsResolver.DnsException) e.getCause();
194             Log.d(TAG, e.getMessage() + e.getCause());
195         }
196         assertNotNull("Exception wasn't thrown!", exception);
197         assertEquals(DnsResolver.ERROR_PARSE, exception.code);
198     }
199 }
200