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