1 package com.android.hotspot2; 2 3 import com.android.anqp.Constants; 4 5 import java.nio.ByteBuffer; 6 import java.nio.charset.CharacterCodingException; 7 import java.nio.charset.CharsetDecoder; 8 import java.nio.charset.StandardCharsets; 9 import java.util.ArrayList; 10 import java.util.Calendar; 11 import java.util.Collection; 12 import java.util.LinkedList; 13 import java.util.List; 14 import java.util.TimeZone; 15 16 import static com.android.anqp.Constants.BYTE_MASK; 17 import static com.android.anqp.Constants.NIBBLE_MASK; 18 19 public abstract class Utils { 20 21 public static final long UNSET_TIME = -1; 22 23 private static final int EUI48Length = 6; 24 private static final int EUI64Length = 8; 25 private static final long EUI48Mask = 0xffffffffffffL; 26 private static final String[] PLMNText = {"org", "3gppnetwork", "mcc*", "mnc*", "wlan"}; 27 splitDomain(String domain)28 public static List<String> splitDomain(String domain) { 29 30 if (domain.endsWith(".")) 31 domain = domain.substring(0, domain.length() - 1); 32 int at = domain.indexOf('@'); 33 if (at >= 0) 34 domain = domain.substring(at + 1); 35 36 String[] labels = domain.toLowerCase().split("\\."); 37 LinkedList<String> labelList = new LinkedList<String>(); 38 for (String label : labels) { 39 labelList.addFirst(label); 40 } 41 42 return labelList; 43 } 44 parseMac(String s)45 public static long parseMac(String s) { 46 47 long mac = 0; 48 int count = 0; 49 for (int n = 0; n < s.length(); n++) { 50 int nibble = Utils.fromHex(s.charAt(n), true); // Set lenient to not blow up on ':' 51 if (nibble >= 0) { // ... and use only legit hex. 52 mac = (mac << 4) | nibble; 53 count++; 54 } 55 } 56 if (count < 12 || (count & 1) == 1) { 57 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 58 } 59 return mac; 60 } 61 macToString(long mac)62 public static String macToString(long mac) { 63 int len = (mac & ~EUI48Mask) != 0 ? EUI64Length : EUI48Length; 64 StringBuilder sb = new StringBuilder(); 65 boolean first = true; 66 for (int n = (len - 1) * Byte.SIZE; n >= 0; n -= Byte.SIZE) { 67 if (first) { 68 first = false; 69 } else { 70 sb.append(':'); 71 } 72 sb.append(String.format("%02x", (mac >>> n) & Constants.BYTE_MASK)); 73 } 74 return sb.toString(); 75 } 76 getMccMnc(List<String> domain)77 public static String getMccMnc(List<String> domain) { 78 if (domain.size() != PLMNText.length) { 79 return null; 80 } 81 82 for (int n = 0; n < PLMNText.length; n++) { 83 String expect = PLMNText[n]; 84 int len = expect.endsWith("*") ? expect.length() - 1 : expect.length(); 85 if (!domain.get(n).regionMatches(0, expect, 0, len)) { 86 return null; 87 } 88 } 89 90 String prefix = domain.get(2).substring(3) + domain.get(3).substring(3); 91 for (int n = 0; n < prefix.length(); n++) { 92 char ch = prefix.charAt(n); 93 if (ch < '0' || ch > '9') { 94 return null; 95 } 96 } 97 return prefix; 98 } 99 toIpString(int leIp)100 public static String toIpString(int leIp) { 101 return String.format("%d.%d.%d.%d", 102 leIp & BYTE_MASK, 103 (leIp >> 8) & BYTE_MASK, 104 (leIp >> 16) & BYTE_MASK, 105 (leIp >> 24) & BYTE_MASK); 106 } 107 bssidsToString(Collection<Long> bssids)108 public static String bssidsToString(Collection<Long> bssids) { 109 StringBuilder sb = new StringBuilder(); 110 for (Long bssid : bssids) { 111 sb.append(String.format(" %012x", bssid)); 112 } 113 return sb.toString(); 114 } 115 roamingConsortiumsToString(long[] ois)116 public static String roamingConsortiumsToString(long[] ois) { 117 if (ois == null) { 118 return "null"; 119 } 120 List<Long> list = new ArrayList<Long>(ois.length); 121 for (long oi : ois) { 122 list.add(oi); 123 } 124 return roamingConsortiumsToString(list); 125 } 126 roamingConsortiumsToString(Collection<Long> ois)127 public static String roamingConsortiumsToString(Collection<Long> ois) { 128 StringBuilder sb = new StringBuilder(); 129 boolean first = true; 130 for (long oi : ois) { 131 if (first) { 132 first = false; 133 } else { 134 sb.append(", "); 135 } 136 if (Long.numberOfLeadingZeros(oi) > 40) { 137 sb.append(String.format("%06x", oi)); 138 } else { 139 sb.append(String.format("%010x", oi)); 140 } 141 } 142 return sb.toString(); 143 } 144 toUnicodeEscapedString(String s)145 public static String toUnicodeEscapedString(String s) { 146 StringBuilder sb = new StringBuilder(s.length()); 147 for (int n = 0; n < s.length(); n++) { 148 char ch = s.charAt(n); 149 if (ch >= ' ' && ch < 127) { 150 sb.append(ch); 151 } else { 152 sb.append("\\u").append(String.format("%04x", (int) ch)); 153 } 154 } 155 return sb.toString(); 156 } 157 toHexString(byte[] data)158 public static String toHexString(byte[] data) { 159 if (data == null) { 160 return "null"; 161 } 162 StringBuilder sb = new StringBuilder(data.length * 3); 163 164 boolean first = true; 165 for (byte b : data) { 166 if (first) { 167 first = false; 168 } else { 169 sb.append(' '); 170 } 171 sb.append(String.format("%02x", b & BYTE_MASK)); 172 } 173 return sb.toString(); 174 } 175 toHex(byte[] octets)176 public static String toHex(byte[] octets) { 177 StringBuilder sb = new StringBuilder(octets.length * 2); 178 for (byte o : octets) { 179 sb.append(String.format("%02x", o & BYTE_MASK)); 180 } 181 return sb.toString(); 182 } 183 hexToBytes(String text)184 public static byte[] hexToBytes(String text) { 185 if ((text.length() & 1) == 1) { 186 throw new NumberFormatException("Odd length hex string: " + text.length()); 187 } 188 byte[] data = new byte[text.length() >> 1]; 189 int position = 0; 190 for (int n = 0; n < text.length(); n += 2) { 191 data[position] = 192 (byte) (((fromHex(text.charAt(n), false) & NIBBLE_MASK) << 4) | 193 (fromHex(text.charAt(n + 1), false) & NIBBLE_MASK)); 194 position++; 195 } 196 return data; 197 } 198 fromHex(char ch, boolean lenient)199 public static int fromHex(char ch, boolean lenient) throws NumberFormatException { 200 if (ch <= '9' && ch >= '0') { 201 return ch - '0'; 202 } else if (ch >= 'a' && ch <= 'f') { 203 return ch + 10 - 'a'; 204 } else if (ch <= 'F' && ch >= 'A') { 205 return ch + 10 - 'A'; 206 } else if (lenient) { 207 return -1; 208 } else { 209 throw new NumberFormatException("Bad hex-character: " + ch); 210 } 211 } 212 toAscii(int b)213 private static char toAscii(int b) { 214 return b >= ' ' && b < 0x7f ? (char) b : '.'; 215 } 216 isDecimal(String s)217 static boolean isDecimal(String s) { 218 for (int n = 0; n < s.length(); n++) { 219 char ch = s.charAt(n); 220 if (ch < '0' || ch > '9') { 221 return false; 222 } 223 } 224 return true; 225 } 226 compare(Comparable<T> c1, T c2)227 public static <T extends Comparable> int compare(Comparable<T> c1, T c2) { 228 if (c1 == null) { 229 return c2 == null ? 0 : -1; 230 } else if (c2 == null) { 231 return 1; 232 } else { 233 return c1.compareTo(c2); 234 } 235 } 236 bytesToBingoCard(ByteBuffer data, int len)237 public static String bytesToBingoCard(ByteBuffer data, int len) { 238 ByteBuffer dup = data.duplicate(); 239 dup.limit(dup.position() + len); 240 return bytesToBingoCard(dup); 241 } 242 bytesToBingoCard(ByteBuffer data)243 public static String bytesToBingoCard(ByteBuffer data) { 244 ByteBuffer dup = data.duplicate(); 245 StringBuilder sbx = new StringBuilder(); 246 while (dup.hasRemaining()) { 247 sbx.append(String.format("%02x ", dup.get() & BYTE_MASK)); 248 } 249 dup = data.duplicate(); 250 sbx.append(' '); 251 while (dup.hasRemaining()) { 252 sbx.append(String.format("%c", toAscii(dup.get() & BYTE_MASK))); 253 } 254 return sbx.toString(); 255 } 256 toHMS(long millis)257 public static String toHMS(long millis) { 258 long time = millis >= 0 ? millis : -millis; 259 long tmp = time / 1000L; 260 long ms = time - tmp * 1000L; 261 262 time = tmp; 263 tmp /= 60L; 264 long s = time - tmp * 60L; 265 266 time = tmp; 267 tmp /= 60L; 268 long m = time - tmp * 60L; 269 270 return String.format("%s%d:%02d:%02d.%03d", millis < 0 ? "-" : "", tmp, m, s, ms); 271 } 272 toUTCString(long ms)273 public static String toUTCString(long ms) { 274 if (ms < 0) { 275 return "unset"; 276 } 277 Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 278 c.setTimeInMillis(ms); 279 return String.format("%4d/%02d/%02d %2d:%02d:%02dZ", 280 c.get(Calendar.YEAR), 281 c.get(Calendar.MONTH) + 1, 282 c.get(Calendar.DAY_OF_MONTH), 283 c.get(Calendar.HOUR_OF_DAY), 284 c.get(Calendar.MINUTE), 285 c.get(Calendar.SECOND)); 286 } 287 288 /** 289 * Decode a wpa_supplicant SSID. wpa_supplicant uses double quotes around plain strings, or 290 * expects a hex-string if no quotes appear. 291 * For Ascii encoded string, any octet < 32 or > 127 is encoded as 292 * a "\x" followed by the hex representation of the octet. 293 * Exception chars are ", \, \e, \n, \r, \t which are escaped by a \ 294 * See src/utils/common.c for the implementation in the supplicant. 295 * 296 * @param ssid The SSID from the config. 297 * @return The actual string content of the SSID 298 */ decodeSsid(String ssid)299 public static String decodeSsid(String ssid) { 300 if (ssid.length() <= 1) { 301 return ssid; 302 } else if (ssid.startsWith("\"") && ssid.endsWith("\"")) { 303 return unescapeSsid(ssid.substring(1, ssid.length() - 1)); 304 } else if ((ssid.length() & 1) == 1) { 305 return ssid; 306 } 307 308 byte[] codepoints; 309 try { 310 codepoints = new byte[ssid.length() / 2]; 311 for (int n = 0; n < ssid.length(); n += 2) { 312 codepoints[n / 2] = (byte) decodeHexPair(ssid, n); 313 } 314 } catch (NumberFormatException nfe) { 315 return ssid; 316 } 317 318 try { 319 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 320 return decoder.decode(ByteBuffer.wrap(codepoints)).toString(); 321 } catch (CharacterCodingException cce) { 322 /* Do nothing, try LATIN-1 */ 323 } 324 try { 325 CharsetDecoder decoder = StandardCharsets.ISO_8859_1.newDecoder(); 326 return decoder.decode(ByteBuffer.wrap(codepoints)).toString(); 327 } catch (CharacterCodingException cce) { // Should not be possible. 328 return ssid; 329 } 330 } 331 unescapeSsid(String s)332 private static String unescapeSsid(String s) { 333 StringBuilder sb = new StringBuilder(); 334 for (int n = 0; n < s.length(); n++) { 335 char ch = s.charAt(n); 336 if (ch != '\\' || n >= s.length() - 1) { 337 sb.append(ch); 338 } else { 339 n++; 340 ch = s.charAt(n); 341 switch (ch) { 342 case '"': 343 case '\\': 344 default: 345 sb.append(ch); 346 break; 347 case 'e': 348 sb.append((char) 27); // Escape char 349 break; 350 case 'n': 351 sb.append('\n'); 352 break; 353 case 'r': 354 sb.append('\r'); 355 break; 356 case 't': 357 sb.append('\t'); 358 break; 359 case 'x': 360 if (s.length() - n < 3) { 361 sb.append('\\').append(ch); 362 } else { 363 n++; 364 sb.append((char) decodeHexPair(s, n)); 365 n++; 366 } 367 break; 368 } 369 } 370 } 371 return sb.toString(); 372 } 373 decodeHexPair(String s, int position)374 private static int decodeHexPair(String s, int position) { 375 return fromHex(s.charAt(position)) << 4 | fromHex(s.charAt(position + 1)); 376 } 377 fromHex(char ch)378 private static int fromHex(char ch) { 379 if (ch >= '0' && ch <= '9') { 380 return ch - '0'; 381 } else if (ch >= 'A' && ch <= 'F') { 382 return ch - 'A' + 10; 383 } else if (ch >= 'a' && ch <= 'f') { 384 return ch - 'a' + 10; 385 } else { 386 throw new NumberFormatException(String.format("Not hex: '%c'", ch)); 387 } 388 } 389 delay(long ms)390 public static void delay(long ms) { 391 long until = System.currentTimeMillis() + ms; 392 for (; ; ) { 393 long remainder = until - System.currentTimeMillis(); 394 if (remainder <= 0) { 395 break; 396 } 397 try { 398 Thread.sleep(remainder); 399 } catch (InterruptedException ie) { /**/ } 400 } 401 } 402 mapEnum(int ordinal, Class<T> enumClass)403 public static <T extends Enum<T>> T mapEnum(int ordinal, Class<T> enumClass) { 404 T[] constants = enumClass.getEnumConstants(); 405 return ordinal >= 0 && ordinal < constants.length ? constants[ordinal]: null; 406 } 407 } 408