1 /* 2 * Copyright (C) 2016 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.android.server.wifi.hotspot2.anqp; 18 19 import static org.junit.Assert.assertEquals; 20 21 import android.net.wifi.WifiSsid; 22 23 import androidx.test.filters.SmallTest; 24 25 import com.android.server.wifi.WifiBaseTest; 26 27 import org.junit.Test; 28 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.net.ProtocolException; 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.nio.charset.StandardCharsets; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.List; 38 import java.util.Locale; 39 40 /** 41 * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.ANQPParser}. 42 */ 43 @SmallTest 44 public class ANQPParserTest extends WifiBaseTest { 45 /** 46 * Helper function for generating payload for a Venue Name ANQP element. 47 * 48 * @param language Array of languages 49 * @param text Array of text 50 * @return byte[] 51 * @throws IOException 52 */ getVenueNamePayload(String[] language, String[] text)53 private static byte[] getVenueNamePayload(String[] language, String[] text) 54 throws IOException { 55 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 56 stream.write(new byte[VenueNameElement.VENUE_INFO_LENGTH]); 57 stream.write(getI18NameListPayload(language, text)); 58 return stream.toByteArray(); 59 } 60 61 /** 62 * Helper function for generating payload for a Domain Name ANQP element. 63 * 64 * @param names Array of domain names 65 * @return byte[] 66 * @throws IOException 67 */ getDomainNamePayload(String[] names)68 private static byte[] getDomainNamePayload(String[] names) throws IOException { 69 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 70 for (String name : names) { 71 byte[] nameBytes = name.getBytes(StandardCharsets.ISO_8859_1); 72 stream.write((byte) nameBytes.length); 73 stream.write(nameBytes); 74 } 75 return stream.toByteArray(); 76 } 77 78 /** 79 * Helper function for generating payload for a Roaming Consortium ANQP element. 80 * 81 * @param ois Array of OIs 82 * @param oisLength Array of length of each corresponding OI 83 * @return byte[] 84 * @throws IOException 85 */ getRoamingConsortiumPayload(Long[] ois, int[] oisLength)86 private static byte[] getRoamingConsortiumPayload(Long[] ois, int[] oisLength) 87 throws IOException { 88 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 89 for (int i = 0; i < ois.length; i++) { 90 stream.write((byte) oisLength[i]); 91 // Write the OI data in big-endian. 92 for (int l = oisLength[i] - 1; l >= 0; l--) { 93 stream.write((byte) ((ois[i].longValue() >> l * Byte.SIZE) & 0xFF)); 94 } 95 } 96 return stream.toByteArray(); 97 } 98 99 /** 100 * Helper function for generating payload for a NAI Realm ANQP element. 101 * 102 * @param realmDataList Array of realm data. 103 * @return byte[] 104 * @throws IOException 105 */ getNAIRealmPayload(byte[][] realmDataList)106 private static byte[] getNAIRealmPayload(byte[][] realmDataList) throws IOException { 107 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 108 // Data count in little-endian 109 stream.write((byte) (realmDataList.length & 0xFF)); 110 stream.write((byte) ((realmDataList.length >> 8) & 0xFF)); 111 for (byte[] realmData : realmDataList) { 112 stream.write(realmData); 113 } 114 return stream.toByteArray(); 115 } 116 117 /** 118 * Helper function for generating payload for 3GPP Network ANQP element. 119 * 120 * @param ieiList Array of IEI data 121 * @return byte[] 122 * @throws IOException 123 */ getThreeGPPNetworkPayload(byte[][] ieiList)124 private static byte[] getThreeGPPNetworkPayload(byte[][] ieiList) throws IOException { 125 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 126 int totalIeiSize = CellularNetworkTestUtil.getDataSize(ieiList); 127 stream.write((byte) ThreeGPPNetworkElement.GUD_VERSION_1); 128 stream.write((byte) totalIeiSize); 129 for (byte[] iei : ieiList) { 130 stream.write(iei); 131 } 132 return stream.toByteArray(); 133 } 134 135 /** 136 * Helper function for generating payload for Vendor Specific ANQP element. 137 * 138 * @param oi The OI of the vendor 139 * @param type The type of the element 140 * @param subtype The subtype of the element 141 * @param payload The vendor specific data 142 * @return byte[] 143 * @throws IOException 144 */ getVendorSpecificPayload(int oi, int type, int subtype, byte[] payload)145 private static byte[] getVendorSpecificPayload(int oi, int type, int subtype, byte[] payload) 146 throws IOException { 147 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 148 stream.write((byte) ((oi >> 16) & 0xFF)); 149 stream.write((byte) ((oi >> 8) & 0xFF)); 150 stream.write((byte) (oi & 0xFF)); 151 stream.write((byte) type); 152 stream.write((byte) subtype); 153 stream.write((byte) 0); // Reserved 154 stream.write(payload); 155 return stream.toByteArray(); 156 } 157 158 /** 159 * Helper function for generating payload for a Hotspot 2.0 Operator Friendly Name ANQP element. 160 * 161 * @param language Array of language 162 * @param text Array of text 163 * @return byte[] 164 * @throws IOException 165 */ getHSFriendlyNamePayload(String[] language, String[] text)166 private static byte[] getHSFriendlyNamePayload(String[] language, String[] text) 167 throws IOException { 168 return getI18NameListPayload(language, text); 169 } 170 171 /** 172 * Helper function for generating payload for a Hotspot 2.0 WAN Metrics ANQP element. 173 * 174 * @param status Link status 175 * @param symmetric Flag indicating symmetric link speed 176 * @param capped Flag indicating link operating at max capacity 177 * @param downlinkSpeed Downlink speed 178 * @param uplinkSpeed Uplink speed 179 * @param downlinkLoad Downlink load 180 * @param uplinkLoad Uplink load 181 * @param lmd Load measurement duration 182 * @return byte[] 183 */ getHSWanMetricsPayload(int status, boolean symmetric, boolean capped, long downlinkSpeed, long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd)184 private static byte[] getHSWanMetricsPayload(int status, boolean symmetric, boolean capped, 185 long downlinkSpeed, long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd) { 186 ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE) 187 .order(ByteOrder.LITTLE_ENDIAN); 188 int wanInfo = status & HSWanMetricsElement.LINK_STATUS_MASK; 189 if (symmetric) wanInfo |= HSWanMetricsElement.SYMMETRIC_LINK_MASK; 190 if (capped) wanInfo |= HSWanMetricsElement.AT_CAPACITY_MASK; 191 buffer.put((byte) wanInfo); 192 buffer.putInt((int) (downlinkSpeed & 0xFFFFFFFFL)); 193 buffer.putInt((int) (uplinkSpeed & 0xFFFFFFFFL)); 194 buffer.put((byte) (downlinkLoad & 0xFF)); 195 buffer.put((byte) (uplinkLoad & 0xFF)); 196 buffer.putShort((short) (lmd & 0xFFFF)); 197 buffer.position(0); 198 byte[] data = new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE]; 199 buffer.get(data); 200 return data; 201 } 202 203 /** 204 * Helper function for generating payload for a Hotspot 2.0 Connection Capability ANQP 205 * element. 206 * 207 * @param protocol Network protocol 208 * @param port Network port 209 * @param status Status of the port 210 * @return byte[] 211 */ getHSConnectionCapabilityPayload(int protocol, int port, int status)212 private static byte[] getHSConnectionCapabilityPayload(int protocol, int port, int status) { 213 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 214 stream.write((byte) protocol); 215 // Write 2-byte port in little-endian. 216 stream.write((byte) (port & 0xFF)); 217 stream.write((byte) ((port >> 8) & 0xFF)); 218 stream.write((byte) status); 219 return stream.toByteArray(); 220 } 221 222 /** 223 * Helper function for generating payload for a Hotspot 2.0 OSU Providers List ANQP 224 * element. 225 * 226 * @param osuSsidBytes Bytes of OSU SSID 227 * @return byte[] 228 */ getHSOsuProvidersPayload(byte[] osuSsidBytes)229 private static byte[] getHSOsuProvidersPayload(byte[] osuSsidBytes) throws IOException { 230 ByteArrayOutputStream out = new ByteArrayOutputStream(); 231 out.write((byte) osuSsidBytes.length); 232 out.write(osuSsidBytes); 233 out.write((byte) 1); 234 out.write(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO_RAW_BYTES); 235 return out.toByteArray(); 236 } 237 238 /** 239 * Helper function for generating payload for a list of I18Name. 240 * 241 * @param language Array of language 242 * @param text Array of text 243 * @return byte[] 244 * @throws IOException 245 */ getI18NameListPayload(String[] language, String[] text)246 private static byte[] getI18NameListPayload(String[] language, String[] text) 247 throws IOException { 248 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 249 for (int i = 0; i < language.length; i++) { 250 byte[] textBytes = text[i].getBytes(StandardCharsets.UTF_8); 251 int length = I18Name.LANGUAGE_CODE_LENGTH + text[i].length(); 252 stream.write((byte) length); 253 stream.write(language[i].getBytes(StandardCharsets.US_ASCII)); 254 // Add padding for two-character language code. 255 if (language[i].getBytes(StandardCharsets.US_ASCII).length 256 < I18Name.LANGUAGE_CODE_LENGTH) { 257 stream.write(new byte[]{(byte) 0x0}); 258 } 259 stream.write(textBytes); 260 } 261 return stream.toByteArray(); 262 } 263 264 /** 265 * Verify that an expected VenueNameElement will be returned when parsing a buffer that 266 * contained a Venue Name ANQP element. 267 * 268 * @throws Exception 269 */ 270 @Test parseVenueNameElement()271 public void parseVenueNameElement() throws Exception { 272 // Test data. 273 String[] language = new String[] {"en"}; 274 String[] text = new String[] {"test"}; 275 276 // Setup expectation. 277 List<I18Name> nameList = new ArrayList<>(); 278 nameList.add(new I18Name(language[0], Locale.forLanguageTag(language[0]), text[0])); 279 VenueNameElement expected = new VenueNameElement(nameList); 280 281 ByteBuffer buffer = ByteBuffer.wrap(getVenueNamePayload(language, text)); 282 assertEquals(expected, 283 ANQPParser.parseElement(Constants.ANQPElementType.ANQPVenueName, buffer)); 284 } 285 286 /** 287 * Verify that an expected IPAddressTypeAvailabilityElement will be returned when parsing a 288 * buffer that contained an IP Address Type Availability ANQP element. 289 * 290 * @throws Exception 291 */ 292 @Test parseIPAddressTypeAvailabilityElement()293 public void parseIPAddressTypeAvailabilityElement() throws Exception { 294 // Test data. 295 int ipAddressAvailability = IPAddressTypeAvailabilityElement.IPV4_PUBLIC << 2 296 | IPAddressTypeAvailabilityElement.IPV6_AVAILABLE; 297 298 // Setup expectation. 299 IPAddressTypeAvailabilityElement expected = new IPAddressTypeAvailabilityElement( 300 IPAddressTypeAvailabilityElement.IPV4_PUBLIC, 301 IPAddressTypeAvailabilityElement.IPV6_AVAILABLE); 302 303 ByteBuffer buffer = ByteBuffer.wrap(new byte[] {(byte) ipAddressAvailability}); 304 assertEquals(expected, 305 ANQPParser.parseElement(Constants.ANQPElementType.ANQPIPAddrAvailability, buffer)); 306 } 307 308 /** 309 * Verify that an expected DomainNameElement will be returned when parsing a buffer that 310 * contained a Domain Name ANQP element. 311 * 312 * @throws Exception 313 */ 314 @Test parseDomainNameElement()315 public void parseDomainNameElement() throws Exception { 316 String[] testNames = new String[] {"test.com", "abc.com"}; 317 DomainNameElement expected = new DomainNameElement(Arrays.asList(testNames)); 318 319 ByteBuffer buffer = ByteBuffer.wrap(getDomainNamePayload(testNames)); 320 assertEquals(expected, 321 ANQPParser.parseElement(Constants.ANQPElementType.ANQPDomName, buffer)); 322 } 323 324 /** 325 * Verify that an expected RoamingConsortiumElement will be returned when parsing a buffer that 326 * contained a Roaming Consortium ANQP element. 327 * 328 * @throws Exception 329 */ 330 @Test parseRoamingConsortium()331 public void parseRoamingConsortium() throws Exception { 332 Long[] ois = new Long[] {0x12345678L, 0x5678L}; 333 int[] oisLength = new int[] {4, 2}; 334 RoamingConsortiumElement expected = new RoamingConsortiumElement(Arrays.asList(ois)); 335 336 ByteBuffer buffer = ByteBuffer.wrap(getRoamingConsortiumPayload(ois, oisLength)); 337 assertEquals(expected, 338 ANQPParser.parseElement(Constants.ANQPElementType.ANQPRoamingConsortium, buffer)); 339 } 340 341 /** 342 * Verify that an expected NAIRealmElement will be returned when parsing a buffer that 343 * contained a NAI Realm ANQP element. 344 * 345 * @throws Exception 346 */ 347 @Test parseNAIRealmElement()348 public void parseNAIRealmElement() throws Exception { 349 byte[][] testBytes = new byte[][] {NAIRealmDataTestUtil.TEST_REAML_WITH_UTF8_DATA_BYTES}; 350 NAIRealmData[] realmDataList = new NAIRealmData[] {NAIRealmDataTestUtil.TEST_REALM_DATA}; 351 NAIRealmElement expected = new NAIRealmElement(Arrays.asList(realmDataList)); 352 353 ByteBuffer buffer = ByteBuffer.wrap(getNAIRealmPayload(testBytes)); 354 assertEquals(expected, 355 ANQPParser.parseElement(Constants.ANQPElementType.ANQPNAIRealm, buffer)); 356 } 357 358 /** 359 * Verify that an expected ThreeGPPNetworkElement will be returned when parsing a buffer that 360 * contained a 3GPP Network ANQP element. 361 * 362 * @throws Exception 363 */ 364 @Test parseThreeGPPNetworkElement()365 public void parseThreeGPPNetworkElement() throws Exception { 366 byte[][] plmnBytes = new byte[][] {new byte[] {(byte) 0x87, 0x29, 0x10}}; 367 String[] plmnList = new String[] {"789012"}; 368 369 List<CellularNetwork> networkList = new ArrayList<>(); 370 networkList.add(new CellularNetwork(Arrays.asList(plmnList))); 371 ThreeGPPNetworkElement expected = new ThreeGPPNetworkElement(networkList); 372 373 ByteBuffer buffer = ByteBuffer.wrap(getThreeGPPNetworkPayload( 374 new byte[][] {CellularNetworkTestUtil.formatPLMNListIEI(plmnBytes)})); 375 assertEquals(expected, 376 ANQPParser.parseElement(Constants.ANQPElementType.ANQP3GPPNetwork, buffer)); 377 } 378 379 /** 380 * Verify that ProtocolException will be thrown when parsing a buffer that contained a 381 * vendor specific element that contained a non-Hotspot 2.0 ANQP-element. 382 * 383 * @throws Exception 384 */ 385 @Test(expected = ProtocolException.class) parseNonHS20VendorSpecificElement()386 public void parseNonHS20VendorSpecificElement() throws Exception { 387 ByteBuffer buffer = ByteBuffer.wrap( 388 getVendorSpecificPayload(0x123456, 0x12, 1, new byte[0])); 389 ANQPParser.parseElement(Constants.ANQPElementType.ANQPVendorSpec, buffer); 390 } 391 392 /** 393 * Verify that an expected HSFriendlyNameElement will be returned when parsing a buffer that 394 * contained a vendor specific element that contained a Hotspot 2.0 friendly name 395 * ANQP element. 396 * 397 * @throws Exception 398 */ 399 @Test parseVendorSpecificElementWithHSFriendlyName()400 public void parseVendorSpecificElementWithHSFriendlyName() throws Exception { 401 String[] language = new String[] {"en"}; 402 String[] text = new String[] {"test"}; 403 404 // Setup expectation. 405 List<I18Name> nameList = new ArrayList<>(); 406 nameList.add(new I18Name(language[0], Locale.forLanguageTag(language[0]), text[0])); 407 HSFriendlyNameElement expected = new HSFriendlyNameElement(nameList); 408 409 byte[] hsFriendlyNameBytes = getHSFriendlyNamePayload(language, text); 410 byte[] data = getVendorSpecificPayload( 411 ANQPParser.VENDOR_SPECIFIC_HS20_OI, ANQPParser.VENDOR_SPECIFIC_HS20_TYPE, 412 Constants.HS_FRIENDLY_NAME, hsFriendlyNameBytes); 413 assertEquals(expected, ANQPParser.parseElement( 414 Constants.ANQPElementType.ANQPVendorSpec, ByteBuffer.wrap(data))); 415 } 416 417 /** 418 * Verify that an expected HSFriendlyNameElement will be returned when parsing a buffer that 419 * contained a Hotspot 2.0 Friendly Name ANQP element. 420 * 421 * @throws Exception 422 */ 423 @Test parseHSFrendlyNameElement()424 public void parseHSFrendlyNameElement() throws Exception { 425 // Test data. 426 String[] language = new String[] {"en"}; 427 String[] text = new String[] {"test"}; 428 429 // Setup expectation. 430 List<I18Name> nameList = new ArrayList<>(); 431 nameList.add(new I18Name(language[0], Locale.forLanguageTag(language[0]), text[0])); 432 HSFriendlyNameElement expected = new HSFriendlyNameElement(nameList); 433 434 ByteBuffer buffer = ByteBuffer.wrap(getHSFriendlyNamePayload(language, text)); 435 assertEquals(expected, 436 ANQPParser.parseHS20Element(Constants.ANQPElementType.HSFriendlyName, buffer)); 437 } 438 439 /** 440 * Verify that an expected HSWanMetricsElement will be returned when parsing a buffer that 441 * contained a Hotspot 2.0 WAN Metrics ANQP element. 442 * 443 * @throws Exception 444 */ 445 @Test parseHSWANMetricsElement()446 public void parseHSWANMetricsElement() throws Exception { 447 int status = HSWanMetricsElement.LINK_STATUS_UP; 448 boolean symmetric = false; 449 boolean capped = true; 450 long downlinkSpeed = 0x12453L; 451 long uplinkSpeed = 0x12423L; 452 int downlinkLoad = 0x12; 453 int uplinkLoad = 0x23; 454 int lmd = 0x2321; 455 456 HSWanMetricsElement expected = new HSWanMetricsElement(status, symmetric, capped, 457 downlinkSpeed, uplinkSpeed, downlinkLoad, uplinkLoad, lmd); 458 459 byte[] data = getHSWanMetricsPayload(status, symmetric, capped, downlinkSpeed, 460 uplinkSpeed, downlinkLoad, uplinkLoad, lmd); 461 ByteBuffer buffer = ByteBuffer.wrap(data); 462 assertEquals(expected, 463 ANQPParser.parseHS20Element(Constants.ANQPElementType.HSWANMetrics, buffer)); 464 } 465 466 /** 467 * Verify that an expected HSConnectionCapabilityElement will be returned when parsing a 468 * buffer that contained a Hotspot 2.0 Connection Capability ANQP element. 469 * 470 * @throws Exception 471 */ 472 @Test parseHSConnectionCapabilityElement()473 public void parseHSConnectionCapabilityElement() throws Exception { 474 int protocol = 12; 475 int port = 23; 476 int status = ProtocolPortTuple.PROTO_STATUS_OPEN; 477 478 List<ProtocolPortTuple> statusList = new ArrayList<>(); 479 statusList.add(new ProtocolPortTuple(protocol, port, status)); 480 HSConnectionCapabilityElement expected = new HSConnectionCapabilityElement(statusList); 481 482 ByteBuffer buffer = ByteBuffer.wrap( 483 getHSConnectionCapabilityPayload(protocol, port, status)); 484 assertEquals(expected, 485 ANQPParser.parseHS20Element(Constants.ANQPElementType.HSConnCapability, buffer)); 486 } 487 488 /** 489 * Verify that an expected RawByteElement will be returned when parsing a buffer that 490 * contained a Hotspot 2.0 OSU Providers element. 491 * 492 * @throws Exception 493 */ 494 @Test parseHSOUSProvidersElement()495 public void parseHSOUSProvidersElement() throws Exception { 496 byte[] osuSsidBytes = "Test SSID".getBytes(StandardCharsets.UTF_8); 497 byte[] data = getHSOsuProvidersPayload(osuSsidBytes); 498 499 HSOsuProvidersElement expected = new HSOsuProvidersElement( 500 WifiSsid.fromBytes(osuSsidBytes), 501 Arrays.asList(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO)); 502 503 ByteBuffer buffer = ByteBuffer.wrap(data); 504 assertEquals(expected, 505 ANQPParser.parseHS20Element(Constants.ANQPElementType.HSOSUProviders, buffer)); 506 } 507 } 508