1 package com.android.hotspot2; 2 3 import com.android.anqp.Constants; 4 5 import java.nio.ByteBuffer; 6 import java.util.ArrayList; 7 import java.util.Calendar; 8 import java.util.Collection; 9 import java.util.LinkedList; 10 import java.util.List; 11 import java.util.TimeZone; 12 13 import static com.android.anqp.Constants.BYTE_MASK; 14 import static com.android.anqp.Constants.NIBBLE_MASK; 15 16 public abstract class Utils { 17 18 public static final long UNSET_TIME = -1; 19 20 private static final int EUI48Length = 6; 21 private static final int EUI64Length = 8; 22 private static final long EUI48Mask = 0xffffffffffffL; 23 private static final String[] PLMNText = {"org", "3gppnetwork", "mcc*", "mnc*", "wlan"}; 24 25 public static List<String> splitDomain(String domain) { 26 27 if (domain.endsWith(".")) 28 domain = domain.substring(0, domain.length() - 1); 29 int at = domain.indexOf('@'); 30 if (at >= 0) 31 domain = domain.substring(at + 1); 32 33 String[] labels = domain.toLowerCase().split("\\."); 34 LinkedList<String> labelList = new LinkedList<String>(); 35 for (String label : labels) { 36 labelList.addFirst(label); 37 } 38 39 return labelList; 40 } 41 42 public static long parseMac(String s) { 43 44 long mac = 0; 45 int count = 0; 46 for (int n = 0; n < s.length(); n++) { 47 int nibble = Utils.fromHex(s.charAt(n), true); // Set lenient to not blow up on ':' 48 if (nibble >= 0) { // ... and use only legit hex. 49 mac = (mac << 4) | nibble; 50 count++; 51 } 52 } 53 if (count < 12 || (count & 1) == 1) { 54 throw new IllegalArgumentException("Bad MAC address: '" + s + "'"); 55 } 56 return mac; 57 } 58 59 public static String macToString(long mac) { 60 int len = (mac & ~EUI48Mask) != 0 ? EUI64Length : EUI48Length; 61 StringBuilder sb = new StringBuilder(); 62 boolean first = true; 63 for (int n = (len - 1) * Byte.SIZE; n >= 0; n -= Byte.SIZE) { 64 if (first) { 65 first = false; 66 } else { 67 sb.append(':'); 68 } 69 sb.append(String.format("%02x", (mac >>> n) & Constants.BYTE_MASK)); 70 } 71 return sb.toString(); 72 } 73 74 public static String getMccMnc(List<String> domain) { 75 if (domain.size() != PLMNText.length) { 76 return null; 77 } 78 79 for (int n = 0; n < PLMNText.length; n++) { 80 String expect = PLMNText[n]; 81 int len = expect.endsWith("*") ? expect.length() - 1 : expect.length(); 82 if (!domain.get(n).regionMatches(0, expect, 0, len)) { 83 return null; 84 } 85 } 86 87 String prefix = domain.get(2).substring(3) + domain.get(3).substring(3); 88 for (int n = 0; n < prefix.length(); n++) { 89 char ch = prefix.charAt(n); 90 if (ch < '0' || ch > '9') { 91 return null; 92 } 93 } 94 return prefix; 95 } 96 97 public static String bssidsToString(Collection<Long> bssids) { 98 StringBuilder sb = new StringBuilder(); 99 for (Long bssid : bssids) { 100 sb.append(String.format(" %012x", bssid)); 101 } 102 return sb.toString(); 103 } 104 105 public static String roamingConsortiumsToString(long[] ois) { 106 if (ois == null) { 107 return "null"; 108 } 109 List<Long> list = new ArrayList<Long>(ois.length); 110 for (long oi : ois) { 111 list.add(oi); 112 } 113 return roamingConsortiumsToString(list); 114 } 115 116 public static String roamingConsortiumsToString(Collection<Long> ois) { 117 StringBuilder sb = new StringBuilder(); 118 boolean first = true; 119 for (long oi : ois) { 120 if (first) { 121 first = false; 122 } else { 123 sb.append(", "); 124 } 125 if (Long.numberOfLeadingZeros(oi) > 40) { 126 sb.append(String.format("%06x", oi)); 127 } else { 128 sb.append(String.format("%010x", oi)); 129 } 130 } 131 return sb.toString(); 132 } 133 134 public static String toUnicodeEscapedString(String s) { 135 StringBuilder sb = new StringBuilder(s.length()); 136 for (int n = 0; n < s.length(); n++) { 137 char ch = s.charAt(n); 138 if (ch >= ' ' && ch < 127) { 139 sb.append(ch); 140 } else { 141 sb.append("\\u").append(String.format("%04x", (int) ch)); 142 } 143 } 144 return sb.toString(); 145 } 146 147 public static String toHexString(byte[] data) { 148 if (data == null) { 149 return "null"; 150 } 151 StringBuilder sb = new StringBuilder(data.length * 3); 152 153 boolean first = true; 154 for (byte b : data) { 155 if (first) { 156 first = false; 157 } else { 158 sb.append(' '); 159 } 160 sb.append(String.format("%02x", b & BYTE_MASK)); 161 } 162 return sb.toString(); 163 } 164 165 public static String toHex(byte[] octets) { 166 StringBuilder sb = new StringBuilder(octets.length * 2); 167 for (byte o : octets) { 168 sb.append(String.format("%02x", o & BYTE_MASK)); 169 } 170 return sb.toString(); 171 } 172 173 public static byte[] hexToBytes(String text) { 174 if ((text.length() & 1) == 1) { 175 throw new NumberFormatException("Odd length hex string: " + text.length()); 176 } 177 byte[] data = new byte[text.length() >> 1]; 178 int position = 0; 179 for (int n = 0; n < text.length(); n += 2) { 180 data[position] = 181 (byte) (((fromHex(text.charAt(n), false) & NIBBLE_MASK) << 4) | 182 (fromHex(text.charAt(n + 1), false) & NIBBLE_MASK)); 183 position++; 184 } 185 return data; 186 } 187 188 public static int fromHex(char ch, boolean lenient) throws NumberFormatException { 189 if (ch <= '9' && ch >= '0') { 190 return ch - '0'; 191 } else if (ch >= 'a' && ch <= 'f') { 192 return ch + 10 - 'a'; 193 } else if (ch <= 'F' && ch >= 'A') { 194 return ch + 10 - 'A'; 195 } else if (lenient) { 196 return -1; 197 } else { 198 throw new NumberFormatException("Bad hex-character: " + ch); 199 } 200 } 201 202 private static char toAscii(int b) { 203 return b >= ' ' && b < 0x7f ? (char) b : '.'; 204 } 205 206 static boolean isDecimal(String s) { 207 for (int n = 0; n < s.length(); n++) { 208 char ch = s.charAt(n); 209 if (ch < '0' || ch > '9') { 210 return false; 211 } 212 } 213 return true; 214 } 215 216 public static <T extends Comparable> int compare(Comparable<T> c1, T c2) { 217 if (c1 == null) { 218 return c2 == null ? 0 : -1; 219 } else if (c2 == null) { 220 return 1; 221 } else { 222 return c1.compareTo(c2); 223 } 224 } 225 226 public static String bytesToBingoCard(ByteBuffer data, int len) { 227 ByteBuffer dup = data.duplicate(); 228 dup.limit(dup.position() + len); 229 return bytesToBingoCard(dup); 230 } 231 232 public static String bytesToBingoCard(ByteBuffer data) { 233 ByteBuffer dup = data.duplicate(); 234 StringBuilder sbx = new StringBuilder(); 235 while (dup.hasRemaining()) { 236 sbx.append(String.format("%02x ", dup.get() & BYTE_MASK)); 237 } 238 dup = data.duplicate(); 239 sbx.append(' '); 240 while (dup.hasRemaining()) { 241 sbx.append(String.format("%c", toAscii(dup.get() & BYTE_MASK))); 242 } 243 return sbx.toString(); 244 } 245 246 public static String toHMS(long millis) { 247 long time = millis >= 0 ? millis : -millis; 248 long tmp = time / 1000L; 249 long ms = time - tmp * 1000L; 250 251 time = tmp; 252 tmp /= 60L; 253 long s = time - tmp * 60L; 254 255 time = tmp; 256 tmp /= 60L; 257 long m = time - tmp * 60L; 258 259 return String.format("%s%d:%02d:%02d.%03d", millis < 0 ? "-" : "", tmp, m, s, ms); 260 } 261 262 public static String toUTCString(long ms) { 263 if (ms < 0) { 264 return "unset"; 265 } 266 Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 267 c.setTimeInMillis(ms); 268 return String.format("%4d/%02d/%02d %2d:%02d:%02dZ", 269 c.get(Calendar.YEAR), 270 c.get(Calendar.MONTH) + 1, 271 c.get(Calendar.DAY_OF_MONTH), 272 c.get(Calendar.HOUR_OF_DAY), 273 c.get(Calendar.MINUTE), 274 c.get(Calendar.SECOND)); 275 } 276 277 public static String unquote(String s) { 278 if (s == null) { 279 return null; 280 } else if (s.length() > 1 && s.startsWith("\"") && s.endsWith("\"")) { 281 return s.substring(1, s.length() - 1); 282 } else { 283 return s; 284 } 285 } 286 287 288 public static void delay(long ms) { 289 long until = System.currentTimeMillis() + ms; 290 for (; ; ) { 291 long remainder = until - System.currentTimeMillis(); 292 if (remainder <= 0) { 293 break; 294 } 295 try { 296 Thread.sleep(remainder); 297 } catch (InterruptedException ie) { /**/ } 298 } 299 } 300 } 301