1 /*
2  * Copyright (C) 2024 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;
18 
19 import android.util.Log;
20 import android.util.Xml;
21 
22 import com.android.internal.util.FastXmlSerializer;
23 import com.android.server.wifi.util.XmlUtil;
24 
25 import org.xmlpull.v1.XmlPullParser;
26 import org.xmlpull.v1.XmlPullParserException;
27 import org.xmlpull.v1.XmlSerializer;
28 
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.FileDescriptor;
32 import java.io.IOException;
33 import java.io.PrintWriter;
34 import java.io.UnsupportedEncodingException;
35 import java.nio.charset.StandardCharsets;
36 import java.text.SimpleDateFormat;
37 import java.util.Date;
38 
39 /**
40  * Class used to backup/restore data.
41  */
42 public class BackupRestoreController {
43     private static final String TAG = "BackupRestoreController";
44 
45     private final WifiSettingsBackupRestore mWifiSettingsBackupRestore;
46     private final Clock mClock;
47 
48     private static final String XML_TAG_DOCUMENT_HEADER = "WifiSettingsBackupData";
49     private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
50 
51     /**
52      * Verbose logging flag.
53      */
54     private boolean mVerboseLoggingEnabled = false;
55     /**
56      * Store the dump of the backup/restore data for debugging. This is only stored when verbose
57      * logging is enabled.
58      */
59     private byte[] mDebugLastBackupDataRetrieved;
60     private long mLastBackupDataRetrievedTimestamp = 0;
61     private byte[] mDebugLastBackupDataRestored;
62     private long mLastBackupDataRestoredTimestamp = 0;
63 
BackupRestoreController(WifiSettingsBackupRestore wifiSettingsBackupRestore, Clock clock)64     public BackupRestoreController(WifiSettingsBackupRestore wifiSettingsBackupRestore,
65             Clock clock) {
66         mWifiSettingsBackupRestore = wifiSettingsBackupRestore;
67         mClock = clock;
68     }
69 
70     /**
71      * Retrieve an XML byte stream representing the data that needs to be backed up.
72      *
73      * @return Raw byte stream of XML that needs to be backed up.
74      */
retrieveBackupData()75     public byte[] retrieveBackupData() {
76         try {
77             final XmlSerializer out = new FastXmlSerializer();
78             final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
79             out.setOutput(outputStream, StandardCharsets.UTF_8.name());
80             XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
81 
82             mWifiSettingsBackupRestore.retrieveBackupDataFromSettingsConfigStore(out, outputStream);
83 
84             XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
85             byte[] backupData = outputStream.toByteArray();
86             if (mVerboseLoggingEnabled) {
87                 mDebugLastBackupDataRetrieved = backupData;
88             }
89             mLastBackupDataRetrievedTimestamp = mClock.getWallClockMillis();
90             return backupData;
91         } catch (IOException e) {
92             Log.e(TAG, "Error retrieving the backup data: " + e);
93         }
94         return new byte[0];
95     }
96 
distpatchBackupData(String sectionName, XmlPullParser in, int depth)97     private void distpatchBackupData(String sectionName, XmlPullParser in, int depth)
98             throws XmlPullParserException, IOException {
99         switch (sectionName) {
100             case WifiSettingsBackupRestore.XML_TAG_SECTION_HEADER_WIFI_SETTINGS_DATA:
101                 mWifiSettingsBackupRestore.restoreSettingsFromBackupData(in, depth);
102                 break;
103             default:
104                 Log.i(TAG, "unknown tag: (backed up from newer version?)" + sectionName);
105         }
106     }
107 
108     /**
109      * Split the back up data to retrieve each back up session.
110      *
111      * @param data raw byte stream representing the XML data.
112      */
parserBackupDataAndDispatch(byte[] data)113     public void parserBackupDataAndDispatch(byte[] data) {
114         if (data == null || data.length == 0) {
115             Log.e(TAG, "Invalid backup data received");
116             return;
117         }
118         if (mVerboseLoggingEnabled) {
119             mDebugLastBackupDataRestored = data;
120         }
121         try {
122             final XmlPullParser in = Xml.newPullParser();
123             ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
124             in.setInput(inputStream, StandardCharsets.UTF_8.name());
125             // Start parsing the XML stream.
126             XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
127             int sectionDepth = in.getDepth();
128             String[] sectionName = new String[1];
129             while (XmlUtil.gotoNextSectionOrEnd(in, sectionName, sectionDepth)) {
130                 try {
131                     if (sectionName[0] == null) {
132                         throw new XmlPullParserException("Missing value name");
133                     }
134                     distpatchBackupData(sectionName[0], in, sectionDepth);
135                 } catch (XmlPullParserException | IOException ex) {
136                     Log.e(TAG, "Error to parser tag: " + sectionName[0]);
137                 }
138             }
139             mLastBackupDataRestoredTimestamp = mClock.getWallClockMillis();
140         } catch (XmlPullParserException | IOException ex) {
141             Log.e(TAG, "Error :" + ex);
142         }
143 
144     }
145 
146     /**
147      * Enable verbose logging.
148      *
149      * @param verboseEnabled whether or not verbosity log level is enabled.
150      */
enableVerboseLogging(boolean verboseEnabled)151     public void enableVerboseLogging(boolean verboseEnabled) {
152         mVerboseLoggingEnabled = verboseEnabled;
153     }
154 
155     /**
156      * Dump out the last backup/restore data if verbose logging is enabled.
157      *
158      * @param fd   unused
159      * @param pw   PrintWriter for writing dump to
160      * @param args unused
161      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)162     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
163         pw.println("Dump of " + TAG);
164         if (mDebugLastBackupDataRetrieved != null) {
165             pw.println("Last backup data retrieved: "
166                     + createLogFromBackupData(mDebugLastBackupDataRetrieved));
167 
168         }
169         pw.println("mLastBackupDataRetrievedTimestamp: "
170                 + (mLastBackupDataRetrievedTimestamp != 0
171                 ? FORMATTER.format(new Date(mLastBackupDataRetrievedTimestamp)) : "N/A"));
172         if (mDebugLastBackupDataRestored != null) {
173             pw.println("Last backup data restored: "
174                     + createLogFromBackupData(mDebugLastBackupDataRestored));
175         }
176         pw.println("mLastBackupDataRestoredTimestamp: "
177                 + (mLastBackupDataRestoredTimestamp != 0
178                 ? FORMATTER.format(new Date(mLastBackupDataRestoredTimestamp)) : "N/A"));
179     }
180 
createLogFromBackupData(byte[] data)181     private String createLogFromBackupData(byte[] data) {
182         if (data != null) {
183             StringBuilder sb = new StringBuilder();
184             try {
185                 String xmlString = new String(data, StandardCharsets.UTF_8.name());
186                 for (String line : xmlString.split("\n")) {
187                     sb.append(line).append("\n");
188                 }
189                 return sb.toString();
190             } catch (UnsupportedEncodingException e) {
191                 Log.e(TAG, "fail to create log from backup data. " + e);
192             }
193         }
194         return "";
195     }
196 }
197