summaryrefslogtreecommitdiffstats
path: root/service/java/com/android/server/wifi/WifiBackupRestore.java
blob: 9dae5c9b941a9c86de805e29a31de0c64e91c9b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wifi;

import android.net.IpConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.os.Process;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;

import com.android.internal.util.FastXmlSerializer;
import com.android.server.net.IpConfigStore;
import com.android.server.wifi.util.NativeUtil;
import com.android.server.wifi.util.WifiPermissionsUtil;
import com.android.server.wifi.util.XmlUtil;
import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Class used to backup/restore data using the SettingsBackupAgent.
 * There are 2 symmetric API's exposed here:
 * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up.
 * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data.
 * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across
 * revisions.
 */
public class WifiBackupRestore {
    private static final String TAG = "WifiBackupRestore";

    /**
     * Current backup data version.
     * Note: before Android P this used to be an {@code int}, however support for minor versions
     * has been added in Android P. Currently this field is a {@code float} representing
     * "majorVersion.minorVersion" of the backed up data. MinorVersion starts with 0 and should
     * be incremented when necessary. MajorVersion starts with 1 and bumping it up requires
     * also resetting minorVersion to 0.
     *
     * MajorVersion will be incremented for modifications of the XML schema, excluding additive
     * modifications in <WifiConfiguration> and/or <IpConfiguration> tags.
     * Should the major version be bumped up, a new {@link WifiBackupDataParser} parser needs to
     * be added and returned from {@link #getWifiBackupDataParser(int)} ()}.
     * Note that bumping up the major version will result in inability to restore the backup
     * set to those lower versions of SDK_INT that don't support the version.
     *
     * MinorVersion will only be incremented for addition of <WifiConfiguration> and/or
     * <IpConfiguration> tags. Any other modifications to the schema should result in bumping up
     * the major version and resetting the minor version to 0.
     * Note that bumping up only the minor version will still allow restoring the backup set to
     * lower versions of SDK_INT.
     */
    private static final float CURRENT_BACKUP_DATA_VERSION = 1.1f;

    /** This list of older versions will be used to restore data from older backups. */
    /**
     * First version of the backup data format.
     */
    private static final int INITIAL_BACKUP_DATA_VERSION = 1;

    /**
     * List of XML section header tags in the backed up data
     */
    private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData";
    private static final String XML_TAG_VERSION = "Version";

    static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList";
    static final String XML_TAG_SECTION_HEADER_NETWORK = "Network";
    static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
    static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";

    /**
     * Regex to mask out passwords in backup data dump.
     */
    private static final String PSK_MASK_LINE_MATCH_PATTERN =
            "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>";
    private static final String PSK_MASK_SEARCH_PATTERN =
            "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)";
    private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3";

    private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN =
            "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">";
    private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>";
    private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)";
    private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3";

    private final WifiPermissionsUtil mWifiPermissionsUtil;
    /**
     * Verbose logging flag.
     */
    private boolean mVerboseLoggingEnabled = false;

    /**
     * Store the dump of the backup/restore data for debugging. This is only stored when verbose
     * logging is enabled in developer options.
     */
    private byte[] mDebugLastBackupDataRetrieved;
    private byte[] mDebugLastBackupDataRestored;
    private byte[] mDebugLastSupplicantBackupDataRestored;

    public WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil) {
        mWifiPermissionsUtil = wifiPermissionsUtil;
    }

    /**
     * Retrieve an XML byte stream representing the data that needs to be backed up from the
     * provided configurations.
     *
     * @param configurations list of currently saved networks that needs to be backed up.
     * @return Raw byte stream of XML that needs to be backed up.
     */
    public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) {
        if (configurations == null) {
            Log.e(TAG, "Invalid configuration list received");
            return new byte[0];
        }

        try {
            final XmlSerializer out = new FastXmlSerializer();
            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            out.setOutput(outputStream, StandardCharsets.UTF_8.name());

            // Start writing the XML stream.
            XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);

            XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION);

            writeNetworkConfigurationsToXml(out, configurations);

            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);

            byte[] data = outputStream.toByteArray();

            if (mVerboseLoggingEnabled) {
                mDebugLastBackupDataRetrieved = data;
            }

            return data;
        } catch (XmlPullParserException e) {
            Log.e(TAG, "Error retrieving the backup data: " + e);
        } catch (IOException e) {
            Log.e(TAG, "Error retrieving the backup data: " + e);
        }
        return new byte[0];
    }

    /**
     * Write the list of configurations to the XML stream.
     */
    private void writeNetworkConfigurationsToXml(
            XmlSerializer out, List<WifiConfiguration> configurations)
            throws XmlPullParserException, IOException {
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
        for (WifiConfiguration configuration : configurations) {
            // We don't want to backup/restore enterprise/passpoint configurations.
            if (configuration.isEnterprise() || configuration.isPasspoint()) {
                continue;
            }
            if (!mWifiPermissionsUtil.checkConfigOverridePermission(configuration.creatorUid)) {
                Log.d(TAG, "Ignoring network from an app with no config override permission: "
                        + configuration.configKey());
                continue;
            }
            // Write this configuration data now.
            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
            writeNetworkConfigurationToXml(out, configuration);
            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
        }
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
    }

    /**
     * Write the configuration data elements from the provided Configuration to the XML stream.
     * Uses XmlUtils to write the values of each element.
     */
    private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)
            throws XmlPullParserException, IOException {
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
        WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration);
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
        IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
    }

    /**
     * Parse out the configurations from the back up data.
     *
     * @param data raw byte stream representing the XML data.
     * @return list of networks retrieved from the backed up data.
     */
    public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) {
        if (data == null || data.length == 0) {
            Log.e(TAG, "Invalid backup data received");
            return null;
        }
        try {
            if (mVerboseLoggingEnabled) {
                mDebugLastBackupDataRestored = data;
            }

            final XmlPullParser in = Xml.newPullParser();
            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
            in.setInput(inputStream, StandardCharsets.UTF_8.name());

            // Start parsing the XML stream.
            XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
            int rootTagDepth = in.getDepth();

            int majorVersion = -1;
            int minorVersion = -1;
            try {
                float version = (float) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);

                // parse out major and minor versions
                String versionStr = new Float(version).toString();
                int separatorPos = versionStr.indexOf('.');
                if (separatorPos == -1) {
                    majorVersion = Integer.parseInt(versionStr);
                    minorVersion = 0;
                } else {
                    majorVersion = Integer.parseInt(versionStr.substring(0, separatorPos));
                    minorVersion = Integer.parseInt(versionStr.substring(separatorPos + 1));
                }
            } catch (ClassCastException cce) {
                // Integer cannot be cast to Float for data coming from before Android P
                majorVersion = 1;
                minorVersion = 0;
            }
            Log.d(TAG, "Version of backup data - major: " + majorVersion
                    + "; minor: " + minorVersion);

            WifiBackupDataParser parser = getWifiBackupDataParser(majorVersion);
            if (parser == null) {
                Log.w(TAG, "Major version of backup data is unknown to this Android"
                        + " version; not restoring");
                return null;
            } else {
                return parser.parseNetworkConfigurationsFromXml(in, rootTagDepth, minorVersion);
            }
        } catch (XmlPullParserException | IOException | ClassCastException
                | IllegalArgumentException e) {
            Log.e(TAG, "Error parsing the backup data: " + e);
        }
        return null;
    }

    private WifiBackupDataParser getWifiBackupDataParser(int majorVersion) {
        switch (majorVersion) {
            case INITIAL_BACKUP_DATA_VERSION:
                return new WifiBackupDataV1Parser();
            default:
                Log.e(TAG, "Unrecognized majorVersion of backup data: " + majorVersion);
                return null;
        }
    }

    /**
     * Create log dump of the backup data in XML format with the preShared & WEP key masked.
     *
     * PSK keys are written in the following format in XML:
     * <string name="PreSharedKey">WifiBackupRestorePsk</string>
     *
     * WEP Keys are written in following format in XML:
     * <string-array name="WEPKeys" num="4">
     *  <item value="WifiBackupRestoreWep1" />
     *  <item value="WifiBackupRestoreWep2" />
     *  <item value="WifiBackupRestoreWep3" />
     *  <item value="WifiBackupRestoreWep3" />
     * </string-array>
     */
    private String createLogFromBackupData(byte[] data) {
        StringBuilder sb = new StringBuilder();
        try {
            String xmlString = new String(data, StandardCharsets.UTF_8.name());
            boolean wepKeysLine = false;
            for (String line : xmlString.split("\n")) {
                if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
                    line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
                }
                if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) {
                    wepKeysLine = true;
                } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) {
                    wepKeysLine = false;
                } else if (wepKeysLine) {
                    line = line.replaceAll(
                            WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
                }
                sb.append(line).append("\n");
            }
        } catch (UnsupportedEncodingException e) {
            return "";
        }
        return sb.toString();
    }

    /**
     * Restore state from the older supplicant back up data.
     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
     *
     * @param supplicantData Raw byte stream of wpa_supplicant.conf
     * @param ipConfigData   Raw byte stream of ipconfig.txt
     * @return list of networks retrieved from the backed up data.
     */
    public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData(
            byte[] supplicantData, byte[] ipConfigData) {
        if (supplicantData == null || supplicantData.length == 0) {
            Log.e(TAG, "Invalid supplicant backup data received");
            return null;
        }

        if (mVerboseLoggingEnabled) {
            mDebugLastSupplicantBackupDataRestored = supplicantData;
        }

        SupplicantBackupMigration.SupplicantNetworks supplicantNetworks =
                new SupplicantBackupMigration.SupplicantNetworks();
        // Incorporate the networks present in the backup data.
        char[] restoredAsChars = new char[supplicantData.length];
        for (int i = 0; i < supplicantData.length; i++) {
            restoredAsChars[i] = (char) supplicantData[i];
        }

        BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars));
        supplicantNetworks.readNetworksFromStream(in);

        // Retrieve corresponding WifiConfiguration objects.
        List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations();

        // Now retrieve all the IpConfiguration objects and set in the corresponding
        // WifiConfiguration objects if ipconfig data is present.
        if (ipConfigData != null && ipConfigData.length != 0) {
            SparseArray<IpConfiguration> networks =
                    IpConfigStore.readIpAndProxyConfigurations(
                            new ByteArrayInputStream(ipConfigData));
            if (networks != null) {
                for (int i = 0; i < networks.size(); i++) {
                    int id = networks.keyAt(i);
                    for (WifiConfiguration configuration : configurations) {
                        // This is a dangerous lookup, but that's how it is currently written.
                        if (configuration.configKey().hashCode() == id) {
                            configuration.setIpConfiguration(networks.valueAt(i));
                        }
                    }
                }
            } else {
                Log.e(TAG, "Failed to parse ipconfig data");
            }
        } else {
            Log.e(TAG, "Invalid ipconfig backup data received");
        }
        return configurations;
    }

    /**
     * Enable verbose logging.
     *
     * @param verbose verbosity level.
     */
    public void enableVerboseLogging(int verbose) {
        mVerboseLoggingEnabled = (verbose > 0);
        if (!mVerboseLoggingEnabled) {
            mDebugLastBackupDataRetrieved = null;
            mDebugLastBackupDataRestored = null;
            mDebugLastSupplicantBackupDataRestored = null;
        }
    }

    /**
     * Dump out the last backup/restore data if verbose logging is enabled.
     *
     * @param fd   unused
     * @param pw   PrintWriter for writing dump to
     * @param args unused
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Dump of WifiBackupRestore");
        if (mDebugLastBackupDataRetrieved != null) {
            pw.println("Last backup data retrieved: "
                    + createLogFromBackupData(mDebugLastBackupDataRetrieved));
        }
        if (mDebugLastBackupDataRestored != null) {
            pw.println("Last backup data restored: "
                    + createLogFromBackupData(mDebugLastBackupDataRestored));
        }
        if (mDebugLastSupplicantBackupDataRestored != null) {
            pw.println("Last old backup data restored: "
                    + SupplicantBackupMigration.createLogFromBackupData(
                            mDebugLastSupplicantBackupDataRestored));
        }
    }

    /**
     * These sub classes contain the logic to parse older backups and restore wifi state from it.
     * Most of the code here has been migrated over from BackupSettingsAgent.
     * This is kind of ugly text parsing, but it is needed to support the migration of this data.
     */
    public static class SupplicantBackupMigration {
        /**
         * List of keys to look out for in wpa_supplicant.conf parsing.
         * These key values are declared in different parts of the wifi codebase today.
         */
        public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
        public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
        public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName;
        public static final String SUPPLICANT_KEY_CLIENT_CERT =
                WifiEnterpriseConfig.CLIENT_CERT_KEY;
        public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY;
        public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY;
        public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY;
        public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName;
        public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0];
        public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1];
        public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2];
        public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3];
        public static final String SUPPLICANT_KEY_WEP_KEY_IDX =
                WifiConfiguration.wepTxKeyIdxVarName;
        public static final String SUPPLICANT_KEY_ID_STR = "id_str";

        /**
         * Regex to mask out passwords in backup data dump.
         */
        private static final String PSK_MASK_LINE_MATCH_PATTERN =
                ".*" + SUPPLICANT_KEY_PSK + ".*=.*";
        private static final String PSK_MASK_SEARCH_PATTERN =
                "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)";
        private static final String PSK_MASK_REPLACE_PATTERN = "$1*";

        private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN =
                ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*";
        private static final String WEP_KEYS_MASK_SEARCH_PATTERN =
                "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)";
        private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*";

        /**
         * Create log dump of the backup data in wpa_supplicant.conf format with the preShared &
         * WEP key masked.
         *
         * PSK keys are written in the following format in wpa_supplicant.conf:
         *  psk=WifiBackupRestorePsk
         *
         * WEP Keys are written in following format in wpa_supplicant.conf:
         *  wep_keys0=WifiBackupRestoreWep0
         *  wep_keys1=WifiBackupRestoreWep1
         *  wep_keys2=WifiBackupRestoreWep2
         *  wep_keys3=WifiBackupRestoreWep3
         */
        public static String createLogFromBackupData(byte[] data) {
            StringBuilder sb = new StringBuilder();
            try {
                String supplicantConfString = new String(data, StandardCharsets.UTF_8.name());
                for (String line : supplicantConfString.split("\n")) {
                    if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
                        line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
                    }
                    if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) {
                        line = line.replaceAll(
                                WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
                    }
                    sb.append(line).append("\n");
                }
            } catch (UnsupportedEncodingException e) {
                return "";
            }
            return sb.toString();
        }

        /**
         * Class for capturing a network definition from the wifi supplicant config file.
         */
        static class SupplicantNetwork {
            private String mParsedSSIDLine;
            private String mParsedHiddenLine;
            private String mParsedKeyMgmtLine;
            private String mParsedPskLine;
            private String[] mParsedWepKeyLines = new String[4];
            private String mParsedWepTxKeyIdxLine;
            private String mParsedIdStrLine;
            public boolean certUsed = false;
            public boolean isEap = false;

            /**
             * Read lines from wpa_supplicant.conf stream for this network.
             */
            public static SupplicantNetwork readNetworkFromStream(BufferedReader in) {
                final SupplicantNetwork n = new SupplicantNetwork();
                String line;
                try {
                    while (in.ready()) {
                        line = in.readLine();
                        if (line == null || line.startsWith("}")) {
                            break;
                        }
                        n.parseLine(line);
                    }
                } catch (IOException e) {
                    return null;
                }
                return n;
            }

            /**
             * Parse a line from wpa_supplicant.conf stream for this network.
             */
            void parseLine(String line) {
                // Can't rely on particular whitespace patterns so strip leading/trailing.
                line = line.trim();
                if (line.isEmpty()) return; // only whitespace; drop the line.

                // Now parse the network block within wpa_supplicant.conf and store the important
                // lines for processing later.
                if (line.startsWith(SUPPLICANT_KEY_SSID + "=")) {
                    mParsedSSIDLine = line;
                } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN + "=")) {
                    mParsedHiddenLine = line;
                } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT + "=")) {
                    mParsedKeyMgmtLine = line;
                    if (line.contains("EAP")) {
                        isEap = true;
                    }
                } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT + "=")) {
                    certUsed = true;
                } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT + "=")) {
                    certUsed = true;
                } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH + "=")) {
                    certUsed = true;
                } else if (line.startsWith(SUPPLICANT_KEY_EAP + "=")) {
                    isEap = true;
                } else if (line.startsWith(SUPPLICANT_KEY_PSK + "=")) {
                    mParsedPskLine = line;
                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0 + "=")) {
                    mParsedWepKeyLines[0] = line;
                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1 + "=")) {
                    mParsedWepKeyLines[1] = line;
                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2 + "=")) {
                    mParsedWepKeyLines[2] = line;
                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3 + "=")) {
                    mParsedWepKeyLines[3] = line;
                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX + "=")) {
                    mParsedWepTxKeyIdxLine = line;
                } else if (line.startsWith(SUPPLICANT_KEY_ID_STR + "=")) {
                    mParsedIdStrLine = line;
                }
            }

            /**
             * Create WifiConfiguration object from the parsed data for this network.
             */
            public WifiConfiguration createWifiConfiguration() {
                if (mParsedSSIDLine == null) {
                    // No SSID => malformed network definition
                    return null;
                }
                WifiConfiguration configuration = new WifiConfiguration();
                configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1);

                if (mParsedHiddenLine != null) {
                    // Can't use Boolean.valueOf() because it works only for true/false.
                    configuration.hiddenSSID =
                            Integer.parseInt(mParsedHiddenLine.substring(
                                    mParsedHiddenLine.indexOf('=') + 1)) != 0;
                }
                if (mParsedKeyMgmtLine == null) {
                    // no key_mgmt line specified; this is defined as equivalent to
                    // "WPA-PSK WPA-EAP".
                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
                } else {
                    // Need to parse the mParsedKeyMgmtLine line
                    final String bareKeyMgmt =
                            mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1);
                    String[] typeStrings = bareKeyMgmt.split("\\s+");

                    // Parse out all the key management regimes permitted for this network.
                    // The literal strings here are the standard values permitted in
                    // wpa_supplicant.conf.
                    for (int i = 0; i < typeStrings.length; i++) {
                        final String ktype = typeStrings[i];
                        if (ktype.equals("NONE")) {
                            configuration.allowedKeyManagement.set(
                                    WifiConfiguration.KeyMgmt.NONE);
                        } else if (ktype.equals("WPA-PSK")) {
                            configuration.allowedKeyManagement.set(
                                    WifiConfiguration.KeyMgmt.WPA_PSK);
                        } else if (ktype.equals("WPA-EAP")) {
                            configuration.allowedKeyManagement.set(
                                    WifiConfiguration.KeyMgmt.WPA_EAP);
                        } else if (ktype.equals("IEEE8021X")) {
                            configuration.allowedKeyManagement.set(
                                    WifiConfiguration.KeyMgmt.IEEE8021X);
                        }
                    }
                }
                if (mParsedPskLine != null) {
                    configuration.preSharedKey =
                            mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1);
                }
                if (mParsedWepKeyLines[0] != null) {
                    configuration.wepKeys[0] =
                            mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1);
                }
                if (mParsedWepKeyLines[1] != null) {
                    configuration.wepKeys[1] =
                            mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1);
                }
                if (mParsedWepKeyLines[2] != null) {
                    configuration.wepKeys[2] =
                            mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1);
                }
                if (mParsedWepKeyLines[3] != null) {
                    configuration.wepKeys[3] =
                            mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1);
                }
                if (mParsedWepTxKeyIdxLine != null) {
                    configuration.wepTxKeyIndex =
                            Integer.valueOf(mParsedWepTxKeyIdxLine.substring(
                                    mParsedWepTxKeyIdxLine.indexOf('=') + 1));
                }
                if (mParsedIdStrLine != null) {
                    String idString =
                            mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1);
                    if (idString != null) {
                        Map<String, String> extras =
                                SupplicantStaNetworkHal.parseNetworkExtra(
                                        NativeUtil.removeEnclosingQuotes(idString));
                        if (extras == null) {
                            Log.e(TAG, "Error parsing network extras, ignoring network.");
                            return null;
                        }
                        String configKey = extras.get(
                                SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY);
                        // No ConfigKey was passed but we need it for validating the parsed
                        // network so we stop the restore.
                        if (configKey == null) {
                            Log.e(TAG, "Configuration key was not passed, ignoring network.");
                            return null;
                        }
                        if (!configKey.equals(configuration.configKey())) {
                            // ConfigKey mismatches are expected for private networks because the
                            // UID is not preserved across backup/restore.
                            Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey
                                    + ", Calculated: " + configuration.configKey());
                        }
                        // For wpa_supplicant backup data, parse out the creatorUid to ensure that
                        // these networks were created by system apps.
                        int creatorUid =
                                Integer.parseInt(extras.get(
                                        SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID));
                        if (creatorUid >= Process.FIRST_APPLICATION_UID) {
                            Log.d(TAG, "Ignoring network from non-system app: "
                                    + configuration.configKey());
                            return null;
                        }
                    }
                }
                return configuration;
            }
        }

        /**
         * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={}
         * blocks and eliminating duplicates
         */
        static class SupplicantNetworks {
            final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8);

            /**
             * Parse the wpa_supplicant.conf file stream and add networks.
             */
            public void readNetworksFromStream(BufferedReader in) {
                try {
                    String line;
                    while (in.ready()) {
                        line = in.readLine();
                        if (line != null) {
                            if (line.startsWith("network")) {
                                SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in);

                                // An IOException occurred while trying to read the network.
                                if (net == null) {
                                    Log.e(TAG, "Error while parsing the network.");
                                    continue;
                                }

                                // Networks that use certificates for authentication can't be
                                // restored because the certificates they need don't get restored
                                // (because they are stored in keystore, and can't be restored).
                                // Similarly, omit EAP network definitions to avoid propagating
                                // controlled enterprise network definitions.
                                if (net.isEap || net.certUsed) {
                                    Log.d(TAG, "Skipping enterprise network for restore: "
                                            + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine);
                                    continue;
                                }
                                mNetworks.add(net);
                            }
                        }
                    }
                } catch (IOException e) {
                    // whatever happened, we're done now
                }
            }

            /**
             * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf
             */
            public List<WifiConfiguration> retrieveWifiConfigurations() {
                ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
                for (SupplicantNetwork net : mNetworks) {
                    try {
                        WifiConfiguration wifiConfiguration = net.createWifiConfiguration();
                        if (wifiConfiguration != null) {
                            Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey());
                            wifiConfigurations.add(wifiConfiguration);
                        }
                    } catch (NumberFormatException e) {
                        // Occurs if we are unable to parse the hidden SSID, WEP Key index or
                        // creator UID.
                        Log.e(TAG, "Error parsing wifi configuration: " + e);
                        return null;
                    }
                }
                return wifiConfigurations;
            }
        }
    }
}