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