1package com.android.server.wifi.hotspot2;
2
3import android.content.Context;
4import android.content.SharedPreferences;
5import android.net.wifi.WifiManager;
6import android.os.SystemProperties;
7import android.telephony.TelephonyManager;
8import android.text.TextUtils;
9import android.util.Log;
10
11import com.android.server.wifi.anqp.eap.EAP;
12import com.android.server.wifi.hotspot2.omadm.MOTree;
13import com.android.server.wifi.hotspot2.omadm.OMAConstants;
14import com.android.server.wifi.hotspot2.omadm.OMAConstructed;
15
16import java.io.IOException;
17import java.nio.charset.StandardCharsets;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Locale;
23import java.util.Map;
24import java.util.concurrent.atomic.AtomicInteger;
25
26import static com.android.server.wifi.anqp.eap.NonEAPInnerAuth.NonEAPType;
27import static com.android.server.wifi.anqp.eap.NonEAPInnerAuth.mapInnerType;
28
29public class OMADMAdapter {
30    private final Context mContext;
31    private final String mImei;
32    private final String mImsi;
33    private final String mDevID;
34    private final List<PathAccessor> mDevInfo;
35    private final List<PathAccessor> mDevDetail;
36
37    private static final int IMEI_Length = 14;
38
39    private static final String[] ExtWiFiPath = {"DevDetail", "Ext", "org.wi-fi", "Wi-Fi"};
40
41    private static final Map<String, String> RTProps = new HashMap<>();
42
43    private MOTree mDevInfoTree;
44    private MOTree mDevDetailTree;
45
46    private static OMADMAdapter sInstance;
47
48    static {
49        RTProps.put(ExtWiFiPath[2], "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0");
50    }
51
52    private static abstract class PathAccessor {
53        private final String[] mPath;
54        private final int mHashCode;
55
56        protected PathAccessor(Object ... path) {
57            int length = 0;
58            for (Object o : path) {
59                if (o.getClass() == String[].class) {
60                    length += ((String[]) o).length;
61                }
62                else {
63                    length++;
64                }
65            }
66            mPath = new String[length];
67            int n = 0;
68            for (Object o : path) {
69                if (o.getClass() == String[].class) {
70                    for (String element : (String[]) o) {
71                        mPath[n++] = element;
72                    }
73                }
74                else if (o.getClass() == Integer.class) {
75                    mPath[n++] = "x" + o.toString();
76                }
77                else {
78                    mPath[n++] = o.toString();
79                }
80            }
81            mHashCode = Arrays.hashCode(mPath);
82        }
83
84        @Override
85        public int hashCode() {
86            return mHashCode;
87        }
88
89        @Override
90        public boolean equals(Object thatObject) {
91            return thatObject == this || (thatObject instanceof ConstPathAccessor &&
92                    Arrays.equals(mPath, ((PathAccessor) thatObject).mPath));
93        }
94
95        private String[] getPath() {
96            return mPath;
97        }
98
99        protected abstract Object getValue();
100    }
101
102    private static class ConstPathAccessor<T> extends PathAccessor {
103        private final T mValue;
104
105        protected ConstPathAccessor(T value, Object ... path) {
106            super(path);
107            mValue = value;
108        }
109
110        protected Object getValue() {
111            return mValue;
112        }
113    }
114
115    public static OMADMAdapter getInstance(Context context) {
116        synchronized (OMADMAdapter.class) {
117            if (sInstance == null) {
118                sInstance = new OMADMAdapter(context);
119            }
120            return sInstance;
121        }
122    }
123
124    private OMADMAdapter(Context context) {
125        mContext = context;
126
127        TelephonyManager tm = (TelephonyManager) context
128                .getSystemService(Context.TELEPHONY_SERVICE);
129        String simOperator = tm.getSimOperator();
130        mImsi = tm.getSubscriberId();
131        mImei = tm.getImei();
132        String strDevId;
133
134        /* Use MEID for sprint */
135        if ("310120".equals(simOperator) || (mImsi != null && mImsi.startsWith("310120"))) {
136                /* MEID is 14 digits. If IMEI is returned as DevId, MEID can be extracted by taking
137                 * first 14 characters. This is not always true but should be the case for sprint */
138            strDevId = tm.getDeviceId().toUpperCase(Locale.US);
139            if (strDevId != null && strDevId.length() >= IMEI_Length) {
140                strDevId = strDevId.substring(0, IMEI_Length);
141            } else {
142                Log.w(Utils.hs2LogTag(getClass()),
143                        "MEID cannot be extracted from DeviceId " + strDevId);
144            }
145        } else {
146            if (isPhoneTypeLTE()) {
147                strDevId = mImei;
148            } else {
149                strDevId = tm.getDeviceId();
150            }
151            if (strDevId == null) {
152                strDevId = "unknown";
153            }
154            strDevId = strDevId.toUpperCase(Locale.US);
155
156            if (!isPhoneTypeLTE()) {
157                strDevId = strDevId.substring(0, IMEI_Length);
158            }
159        }
160        mDevID = strDevId;
161
162        mDevInfo = new ArrayList<>();
163        mDevInfo.add(new ConstPathAccessor<>(strDevId, "DevInfo", "DevID"));
164        mDevInfo.add(new ConstPathAccessor<>(getProperty(context, "Man", "ro.product.manufacturer", "unknown"), "DevInfo", "Man"));
165        mDevInfo.add(new ConstPathAccessor<>(getProperty(context, "Mod", "ro.product.model", "generic"), "DevInfo", "Mod"));
166        mDevInfo.add(new ConstPathAccessor<>(getLocale(context), "DevInfo", "Lang"));
167        mDevInfo.add(new ConstPathAccessor<>("1.2", "DevInfo", "DmV"));
168
169        mDevDetail = new ArrayList<>();
170        mDevDetail.add(new ConstPathAccessor<>(getDeviceType(), "DevDetail", "DevType"));
171        mDevDetail.add(new ConstPathAccessor<>(SystemProperties.get("ro.product.brand"), "DevDetail", "OEM"));
172        mDevDetail.add(new ConstPathAccessor<>(getVersion(context, false), "DevDetail", "FwV"));
173        mDevDetail.add(new ConstPathAccessor<>(getVersion(context, true), "DevDetail", "SwV"));
174        mDevDetail.add(new ConstPathAccessor<>(getHwV(), "DevDetail", "HwV"));
175        mDevDetail.add(new ConstPathAccessor<>("TRUE", "DevDetail", "LrgObj"));
176
177        mDevDetail.add(new ConstPathAccessor<>(32, "DevDetail", "URI", "MaxDepth"));
178        mDevDetail.add(new ConstPathAccessor<>(2048, "DevDetail", "URI", "MaxTotLen"));
179        mDevDetail.add(new ConstPathAccessor<>(64, "DevDetail", "URI", "MaxSegLen"));
180
181        AtomicInteger index = new AtomicInteger(1);
182        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
183        mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAPv2), ExtWiFiPath, "EAPMethodList", index, "InnerMethod"));
184
185        index.incrementAndGet();
186        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
187        mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.PAP), ExtWiFiPath, "EAPMethodList", index, "InnerMethod"));
188
189        index.incrementAndGet();
190        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
191        mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAP), ExtWiFiPath, "EAPMethodList", index, "InnerMethod"));
192
193        index.incrementAndGet();
194        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
195        index.incrementAndGet();
196        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKA, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
197        index.incrementAndGet();
198        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKAPrim, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
199        index.incrementAndGet();
200        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_SIM, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
201
202        mDevDetail.add(new ConstPathAccessor<>("FALSE", ExtWiFiPath, "ManufacturingCertificate"));
203        mDevDetail.add(new ConstPathAccessor<>(mImsi, ExtWiFiPath, "IMSI"));
204        mDevDetail.add(new ConstPathAccessor<>(mImei, ExtWiFiPath, "IMEI_MEID"));
205        mDevDetail.add(new PathAccessor(ExtWiFiPath, "Wi-FiMACAddress") {
206            @Override
207            protected String getValue() {
208                return getMAC();
209            }
210        });
211    }
212
213    private static void buildNode(PathAccessor pathAccessor, int depth, OMAConstructed parent)
214            throws IOException {
215        String[] path = pathAccessor.getPath();
216        String name = path[depth];
217        if (depth < path.length - 1) {
218            OMAConstructed node = (OMAConstructed) parent.getChild(name);
219            if (node == null) {
220                node = (OMAConstructed) parent.addChild(name, RTProps.get(name),
221                        null, null);
222            }
223            buildNode(pathAccessor, depth + 1, node);
224        }
225        else if (pathAccessor.getValue() != null) {
226            parent.addChild(name, null, pathAccessor.getValue().toString(), null);
227        }
228    }
229
230    public String getMAC() {
231        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
232        return wifiManager != null ?
233                String.format("%012x",
234                        Utils.parseMac(wifiManager.getConnectionInfo().getMacAddress())) :
235                null;
236    }
237
238    public String getImei() {
239        return mImei;
240    }
241
242    public byte[] getMeid() {
243        return Arrays.copyOf(mImei.getBytes(StandardCharsets.ISO_8859_1), IMEI_Length);
244    }
245
246    public String getDevID() {
247        return mDevID;
248    }
249
250    public MOTree getMO(String urn) {
251        try {
252            switch (urn) {
253                case OMAConstants.DevInfoURN:
254                    if (mDevInfoTree == null) {
255                        OMAConstructed root = new OMAConstructed(null, "DevInfo", urn);
256                        for (PathAccessor pathAccessor : mDevInfo) {
257                            buildNode(pathAccessor, 1, root);
258                        }
259                        mDevInfoTree = MOTree.buildMgmtTree(OMAConstants.DevInfoURN,
260                                OMAConstants.OMAVersion, root);
261                    }
262                    return mDevInfoTree;
263                case OMAConstants.DevDetailURN:
264                    if (mDevDetailTree == null) {
265                        OMAConstructed root = new OMAConstructed(null, "DevDetail", urn);
266                        for (PathAccessor pathAccessor : mDevDetail) {
267                            buildNode(pathAccessor, 1, root);
268                        }
269                        mDevDetailTree = MOTree.buildMgmtTree(OMAConstants.DevDetailURN,
270                                OMAConstants.OMAVersion, root);
271                    }
272                    return mDevDetailTree;
273                default:
274                    throw new IllegalArgumentException(urn);
275            }
276        }
277        catch (IOException ioe) {
278            Log.e(Utils.hs2LogTag(getClass()), "Caught exception building OMA Tree: " + ioe, ioe);
279            return null;
280        }
281
282        /*
283        switch (urn) {
284            case DevInfoURN: return DevInfo;
285            case DevDetailURN: return DevDetail;
286            default: throw new IllegalArgumentException(urn);
287        }
288        */
289    }
290
291    // TODO: For now, assume the device supports LTE.
292    private static boolean isPhoneTypeLTE() {
293        return true;
294    }
295
296    private static String getHwV() {
297        try {
298            return SystemProperties.get("ro.hardware", "Unknown")
299                    + "." + SystemProperties.get("ro.revision", "Unknown");
300        } catch (RuntimeException e) {
301            return  "Unknown";
302        }
303    }
304
305    private static String getDeviceType() {
306        String devicetype = SystemProperties.get("ro.build.characteristics");
307        if ((((TextUtils.isEmpty(devicetype)) || (!devicetype.equals("tablet"))))) {
308            devicetype = "phone";
309        }
310        return devicetype;
311    }
312
313    private static String getVersion(Context context, boolean swv) {
314        String version;
315        try {
316            if (!isSprint(context) && swv) {
317                return "Android " + SystemProperties.get("ro.build.version.release");
318            } else {
319                version = SystemProperties.get("ro.build.version.full");
320                if (null == version || version.equals("")) {
321                    return SystemProperties.get("ro.build.id", null) + "~"
322                            + SystemProperties.get("ro.build.config.version", null) + "~"
323                            + SystemProperties.get("gsm.version.baseband", null) + "~"
324                            + SystemProperties.get("ro.gsm.flexversion", null);
325                }
326            }
327        } catch (RuntimeException e) {
328            return "Unknown";
329        }
330        return version;
331    }
332
333    private static boolean isSprint(Context context) {
334        TelephonyManager tm = (TelephonyManager) context
335                .getSystemService(Context.TELEPHONY_SERVICE);
336        String simOperator = tm.getSimOperator();
337        String imsi = tm.getSubscriberId();
338        /* Use MEID for sprint */
339        if ("310120".equals(simOperator) || (imsi != null && imsi.startsWith("310120"))) {
340            return true;
341        } else {
342            return false;
343        }
344    }
345
346    private static String getLocale(Context context) {
347        String strLang = readValueFromFile(context, "Lang");
348        if (strLang == null) {
349            strLang = Locale.getDefault().toString();
350        }
351        return strLang;
352    }
353
354    private static String getProperty(Context context, String key, String propKey, String dflt) {
355        String strMan = readValueFromFile(context, key);
356        if (strMan == null) {
357            strMan = SystemProperties.get(propKey, dflt);
358        }
359        return strMan;
360    }
361
362    private static String readValueFromFile(Context context, String propName) {
363        String ret = null;
364        // use preference instead of the system property
365        SharedPreferences prefs = context.getSharedPreferences("dmconfig", 0);
366        if (prefs.contains(propName)) {
367            ret = prefs.getString(propName, "");
368            if (ret.length() == 0) {
369                ret = null;
370            }
371        }
372        return ret;
373    }
374
375    private static final String DevDetail =
376        "<MgmtTree>" +
377            "<VerDTD>1.2</VerDTD>" +
378            "<Node>" +
379                "<NodeName>DevDetail</NodeName>" +
380                "<RTProperties>" +
381                    "<Type>" +
382                        "<DDFName>urn:oma:mo:oma-dm-devdetail:1.0</DDFName>" +
383                    "</Type>" +
384                "</RTProperties>" +
385                "<Node>" +
386                    "<NodeName>Ext</NodeName>" +
387                    "<Node>" +
388                        "<NodeName>org.wi-fi</NodeName>" +
389                        "<RTProperties>" +
390                            "<Type>" +
391                                "<DDFName>" +
392                                    "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext :1.0" +
393                                "</DDFName>" +
394                            "</Type>" +
395                        "</RTProperties>" +
396                        "<Node>" +
397                            "<NodeName>Wi-Fi</NodeName>" +
398                            "<Node>" +
399                                "<NodeName>EAPMethodList</NodeName>" +
400                                "<Node>" +
401                                    "<NodeName>Method01</NodeName>" +
402                                    "<!-- EAP-TTLS/MS-CHAPv2 -->" +
403                                    "<Node>" +
404                                        "<NodeName>EAPType</NodeName>" +
405                                        "<Value>21</Value>" +
406                                    "</Node>" +
407                                    "<Node>" +
408                                        "<NodeName>InnerMethod</NodeName>" +
409                                        "<Value>MS-CHAP-V2</Value>" +
410                                    "</Node>" +
411                                "</Node>" +
412                                "<Node>" +
413                                    "<NodeName>Method02</NodeName>" +
414                                    "<!-- EAP-TLS -->" +
415                                    "<Node>" +
416                                        "<NodeName>EAPType</NodeName>" +
417                                        "<Value>13</Value>" +
418                                    "</Node>" +
419                                "</Node>" +
420                                "<Node>" +
421                                    "<NodeName>Method03</NodeName>" +
422                                    "<!-- EAP-SIM -->" +
423                                    "<Node>" +
424                                        "<NodeName>EAPType</NodeName>" +
425                                        "<Value>18</Value>" +
426                                    "</Node>" +
427                                "</Node>" +
428                                "<Node>" +
429                                    "<NodeName>Method04</NodeName>" +
430                                    "<!-- EAP-AKA -->" +
431                                    "<Node>" +
432                                        "<NodeName>EAPType</NodeName>" +
433                                        "<Value>23</Value>" +
434                                    "</Node>" +
435                                "</Node>" +
436                                "<Node>" +
437                                    "<NodeName>Method05</NodeName>" +
438                                    "<!-- EAP-AKA' -->" +
439                                    "<Node>" +
440                                        "<NodeName>EAPType</NodeName>" +
441                                        "<Value>50</Value>" +
442                                    "</Node>" +
443                                "</Node>" +
444                                "<Node>" +
445                                    "<NodeName>Method06</NodeName>" +
446                                    "<!-- Supported method (EAP-TTLS/PAP) not mandated by Hotspot2.0-->" +
447                                    "<Node>" +
448                                        "<NodeName>EAPType</NodeName>" +
449                                        "<Value>21</Value>" +
450                                    "</Node>" +
451                                    "<Node>" +
452                                        "<NodeName>InnerMethod</NodeName>" +
453                                        "<Value>PAP</Value>" +
454                                    "</Node>" +
455                                "</Node>" +
456                                "<Node>" +
457                                    "<NodeName>Method07</NodeName>" +
458                                    "<!-- Supported method (PEAP/EAP-GTC) not mandated by Hotspot 2.0-->" +
459                                    "<Node>" +
460                                        "<NodeName>EAPType</NodeName>" +
461                                        "<Value>25</Value>" +
462                                    "</Node>" +
463                                    "<Node>" +
464                                        "<NodeName>InnerEAPType</NodeName>" +
465                                        "<Value>6</Value>" +
466                                    "</Node>" +
467                                "</Node>" +
468                            "</Node>" +
469                            "<Node>" +
470                                "<NodeName>SPCertificate</NodeName>" +
471                                "<Node>" +
472                                    "<NodeName>Cert01</NodeName>" +
473                                    "<Node>" +
474                                        "<NodeName>CertificateIssuerName</NodeName>" +
475                                        "<Value>CN=RuckusCA</Value>" +
476                                    "</Node>" +
477                                "</Node>" +
478                            "</Node>" +
479                            "<Node>" +
480                                "<NodeName>ManufacturingCertificate</NodeName>" +
481                                "<Value>FALSE</Value>" +
482                            "</Node>" +
483                            "<Node>" +
484                                "<NodeName>Wi-FiMACAddress</NodeName>" +
485                                "<Value>001d2e112233</Value>" +
486                            "</Node>" +
487                            "<Node>" +
488                                "<NodeName>ClientTriggerRedirectURI</NodeName>" +
489                                "<Value>http://127.0.0.1:12345/index.htm</Value>" +
490                            "</Node>" +
491                            "<Node>" +
492                                "<NodeName>Ops</NodeName>" +
493                                "<Node>" +
494                                    "<NodeName>launchBrowserToURI</NodeName>" +
495                                    "<Value></Value>" +
496                                "</Node>" +
497                                "<Node>" +
498                                    "<NodeName>negotiateClientCertTLS</NodeName>" +
499                                    "<Value></Value>" +
500                                "</Node>" +
501                                "<Node>" +
502                                    "<NodeName>getCertificate</NodeName>" +
503                                    "<Value></Value>" +
504                                "</Node>" +
505                            "</Node>" +
506                        "</Node>" +
507                        "<!-- End of Wi-Fi node -->" +
508                    "</Node>" +
509                    "<!-- End of org.wi-fi node -->" +
510                "</Node>" +
511                "<!-- End of Ext node -->" +
512                "<Node>" +
513                    "<NodeName>URI</NodeName>" +
514                    "<Node>" +
515                        "<NodeName>MaxDepth</NodeName>" +
516                        "<Value>32</Value>" +
517                    "</Node>" +
518                    "<Node>" +
519                        "<NodeName>MaxTotLen</NodeName>" +
520                        "<Value>2048</Value>" +
521                    "</Node>" +
522                    "<Node>" +
523                        "<NodeName>MaxSegLen</NodeName>" +
524                        "<Value>64</Value>" +
525                    "</Node>" +
526                "</Node>" +
527                "<Node>" +
528                    "<NodeName>DevType</NodeName>" +
529                    "<Value>Smartphone</Value>" +
530                "</Node>" +
531                "<Node>" +
532                    "<NodeName>OEM</NodeName>" +
533                    "<Value>ACME</Value>" +
534                "</Node>" +
535                "<Node>" +
536                    "<NodeName>FwV</NodeName>" +
537                    "<Value>1.2.100.5</Value>" +
538                "</Node>" +
539                "<Node>" +
540                    "<NodeName>SwV</NodeName>" +
541                    "<Value>9.11.130</Value>" +
542                "</Node>" +
543                "<Node>" +
544                    "<NodeName>HwV</NodeName>" +
545                    "<Value>1.0</Value>" +
546                "</Node>" +
547                "<Node>" +
548                    "<NodeName>LrgObj</NodeName>" +
549                    "<Value>TRUE</Value>" +
550                "</Node>" +
551            "</Node>" +
552        "</MgmtTree>";
553
554
555    private static final String DevInfo =
556        "<MgmtTree>" +
557            "<VerDTD>1.2</VerDTD>" +
558            "<Node>" +
559                "<NodeName>DevInfo</NodeName>" +
560                "<RTProperties>" +
561                    "<Type>" +
562                        "<DDFName>urn:oma:mo:oma-dm-devinfo:1.0" +
563                        "</DDFName>" +
564                    "</Type>" +
565                "</RTProperties>" +
566            "</Node>" +
567            "<Node>" +
568                "<NodeName>DevID</NodeName>" +
569                "<Path>DevInfo</Path>" +
570                "<Value>urn:acme:00-11-22-33-44-55</Value>" +
571            "</Node>" +
572            "<Node>" +
573                "<NodeName>Man</NodeName>" +
574                "<Path>DevInfo</Path>" +
575                "<Value>ACME</Value>" +
576            "</Node>" +
577            "<Node>" +
578                "<NodeName>Mod</NodeName>" +
579                "<Path>DevInfo</Path>" +
580                "<Value>HS2.0-01</Value>" +
581            "</Node>" +
582            "<Node>" +
583                "<NodeName>DmV</NodeName>" +
584                "<Path>DevInfo</Path>" +
585                "<Value>1.2</Value>" +
586            "</Node>" +
587            "<Node>" +
588                "<NodeName>Lang</NodeName>" +
589                "<Path>DevInfo</Path>" +
590                "<Value>en-US</Value>" +
591            "</Node>" +
592        "</MgmtTree>";
593}
594