1 /*
2  * Copyright (C) 2010 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.annotation.NonNull;
20 import android.content.Context;
21 import android.util.Base64;
22 import android.util.SparseLongArray;
23 
24 import com.android.internal.R;
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.server.wifi.util.ByteArrayRingBuffer;
27 import com.android.server.wifi.util.StringUtil;
28 
29 import java.io.BufferedReader;
30 import java.io.ByteArrayOutputStream;
31 import java.io.FileDescriptor;
32 import java.io.IOException;
33 import java.io.InputStreamReader;
34 import java.io.PrintWriter;
35 import java.nio.charset.Charset;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Calendar;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.stream.Collectors;
44 import java.util.zip.Deflater;
45 
46 /**
47  * Tracks various logs for framework.
48  */
49 class WifiDiagnostics extends BaseWifiDiagnostics {
50     /**
51      * Thread-safety:
52      * 1) All non-private methods are |synchronized|.
53      * 2) Callbacks into WifiDiagnostics use non-private (and hence, synchronized) methods. See, e.g,
54      *    onRingBufferData(), onWifiAlert().
55      */
56 
57     private static final String TAG = "WifiDiags";
58     private static final boolean DBG = false;
59 
60     /** log level flags; keep these consistent with wifi_logger.h */
61 
62     /** No logs whatsoever */
63     public static final int VERBOSE_NO_LOG = 0;
64     /** No logs whatsoever */
65     public static final int VERBOSE_NORMAL_LOG = 1;
66     /** Be careful since this one can affect performance and power */
67     public static final int VERBOSE_LOG_WITH_WAKEUP  = 2;
68     /** Be careful since this one can affect performance and power and memory */
69     public static final int VERBOSE_DETAILED_LOG_WITH_WAKEUP  = 3;
70 
71     /** ring buffer flags; keep these consistent with wifi_logger.h */
72     public static final int RING_BUFFER_FLAG_HAS_BINARY_ENTRIES     = 0x00000001;
73     public static final int RING_BUFFER_FLAG_HAS_ASCII_ENTRIES      = 0x00000002;
74     public static final int RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES = 0x00000004;
75 
76     /** various reason codes */
77     public static final int REPORT_REASON_NONE                      = 0;
78     public static final int REPORT_REASON_ASSOC_FAILURE             = 1;
79     public static final int REPORT_REASON_AUTH_FAILURE              = 2;
80     public static final int REPORT_REASON_AUTOROAM_FAILURE          = 3;
81     public static final int REPORT_REASON_DHCP_FAILURE              = 4;
82     public static final int REPORT_REASON_UNEXPECTED_DISCONNECT     = 5;
83     public static final int REPORT_REASON_SCAN_FAILURE              = 6;
84     public static final int REPORT_REASON_USER_ACTION               = 7;
85     public static final int REPORT_REASON_WIFINATIVE_FAILURE        = 8;
86     public static final int REPORT_REASON_REACHABILITY_LOST         = 9;
87     public static final int REPORT_REASON_FATAL_FW_ALERT            = 10;
88 
89     /** number of bug reports to hold */
90     public static final int MAX_BUG_REPORTS                         = 4;
91 
92     /** number of alerts to hold */
93     public static final int MAX_ALERT_REPORTS                       = 1;
94 
95     /** minimum wakeup interval for each of the log levels */
96     private static final int MinWakeupIntervals[] = new int[] { 0, 3600, 60, 10 };
97     /** minimum buffer size for each of the log levels */
98     private static final int MinBufferSizes[] = new int[] { 0, 16384, 16384, 65536 };
99 
100     /** Map from dump reason to elapsed time millis */
101     private final SparseLongArray mLastDumpTime = new SparseLongArray();
102 
103     /** Minimum dump period with same error code */
104     public static final long MIN_DUMP_TIME_WINDOW_MILLIS = 10 * 60 * 1000; // 10 mins
105 
106     @VisibleForTesting public static final String FIRMWARE_DUMP_SECTION_HEADER =
107             "FW Memory dump";
108     @VisibleForTesting public static final String DRIVER_DUMP_SECTION_HEADER =
109             "Driver state dump";
110 
111     private final int RING_BUFFER_BYTE_LIMIT_SMALL;
112     private final int RING_BUFFER_BYTE_LIMIT_LARGE;
113     private int mLogLevel = VERBOSE_NO_LOG;
114     private boolean mIsLoggingEventHandlerRegistered;
115     private WifiNative.RingBufferStatus[] mRingBuffers;
116     private WifiNative.RingBufferStatus mPerPacketRingBuffer;
117     private final BuildProperties mBuildProperties;
118     private final WifiLog mLog;
119     private final LastMileLogger mLastMileLogger;
120     private final Runtime mJavaRuntime;
121     private final WifiMetrics mWifiMetrics;
122     private int mMaxRingBufferSizeBytes;
123     private final List<Integer> mFatalFirmwareAlertErrorCodeList;
124     private WifiInjector mWifiInjector;
125     private Clock mClock;
126 
WifiDiagnostics(Context context, WifiInjector wifiInjector, WifiNative wifiNative, BuildProperties buildProperties, LastMileLogger lastMileLogger, Clock clock)127     public WifiDiagnostics(Context context, WifiInjector wifiInjector,
128                            WifiNative wifiNative, BuildProperties buildProperties,
129                            LastMileLogger lastMileLogger, Clock clock) {
130         super(wifiNative);
131         RING_BUFFER_BYTE_LIMIT_SMALL = context.getResources().getInteger(
132                 R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb) * 1024;
133         RING_BUFFER_BYTE_LIMIT_LARGE = context.getResources().getInteger(
134                 R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb) * 1024;
135         int[] fatalFirmwareAlertErrorCodeArray = context.getResources().getIntArray(
136                 R.array.config_wifi_fatal_firmware_alert_error_code_list);
137 
138         mBuildProperties = buildProperties;
139         mIsLoggingEventHandlerRegistered = false;
140         mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_SMALL;
141         mFatalFirmwareAlertErrorCodeList = Arrays.stream(fatalFirmwareAlertErrorCodeArray)
142                 .boxed().collect(Collectors.toList());
143         mLog = wifiInjector.makeLog(TAG);
144         mLastMileLogger = lastMileLogger;
145         mJavaRuntime = wifiInjector.getJavaRuntime();
146         mWifiMetrics = wifiInjector.getWifiMetrics();
147         mWifiInjector = wifiInjector;
148         mClock = clock;
149     }
150 
151     @Override
startLogging(boolean verboseEnabled)152     public synchronized void startLogging(boolean verboseEnabled) {
153         mFirmwareVersion = mWifiNative.getFirmwareVersion();
154         mDriverVersion = mWifiNative.getDriverVersion();
155         mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
156 
157         if (!mIsLoggingEventHandlerRegistered) {
158             mIsLoggingEventHandlerRegistered = mWifiNative.setLoggingEventHandler(mHandler);
159         }
160 
161         if (verboseEnabled) {
162             mLogLevel = VERBOSE_LOG_WITH_WAKEUP;
163             mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_LARGE;
164         } else {
165             mLogLevel = VERBOSE_NORMAL_LOG;
166             mMaxRingBufferSizeBytes = enableVerboseLoggingForDogfood()
167                     ? RING_BUFFER_BYTE_LIMIT_LARGE : RING_BUFFER_BYTE_LIMIT_SMALL;
168             clearVerboseLogs();
169         }
170 
171         if (mRingBuffers == null) {
172             fetchRingBuffers();
173         }
174 
175         if (mRingBuffers != null) {
176             /* log level may have changed, so restart logging with new levels */
177             stopLoggingAllBuffers();
178             resizeRingBuffers();
179             startLoggingAllExceptPerPacketBuffers();
180         }
181 
182         if (!mWifiNative.startPktFateMonitoring(mWifiNative.getClientInterfaceName())) {
183             mLog.wC("Failed to start packet fate monitoring");
184         }
185     }
186 
187     @Override
startPacketLog()188     public synchronized void startPacketLog() {
189         if (mPerPacketRingBuffer != null) {
190             startLoggingRingBuffer(mPerPacketRingBuffer);
191         } else {
192             if (DBG) mLog.tC("There is no per packet ring buffer");
193         }
194     }
195 
196     @Override
stopPacketLog()197     public synchronized void stopPacketLog() {
198         if (mPerPacketRingBuffer != null) {
199             stopLoggingRingBuffer(mPerPacketRingBuffer);
200         } else {
201             if (DBG) mLog.tC("There is no per packet ring buffer");
202         }
203     }
204 
205     @Override
stopLogging()206     public synchronized void stopLogging() {
207         if (mIsLoggingEventHandlerRegistered) {
208             if (!mWifiNative.resetLogHandler()) {
209                 mLog.wC("Fail to reset log handler");
210             } else {
211                 if (DBG) mLog.tC("Reset log handler");
212             }
213             // Clear mIsLoggingEventHandlerRegistered even if resetLogHandler() failed, because
214             // the log handler is in an indeterminate state.
215             mIsLoggingEventHandlerRegistered = false;
216         }
217         if (mLogLevel != VERBOSE_NO_LOG) {
218             stopLoggingAllBuffers();
219             mRingBuffers = null;
220             mLogLevel = VERBOSE_NO_LOG;
221         }
222     }
223 
224     @Override
reportConnectionEvent(byte event)225     public synchronized void reportConnectionEvent(byte event) {
226         mLastMileLogger.reportConnectionEvent(event);
227         if (event == CONNECTION_EVENT_FAILED || event == CONNECTION_EVENT_TIMEOUT) {
228             mPacketFatesForLastFailure = fetchPacketFates();
229         }
230     }
231 
232     @Override
captureBugReportData(int reason)233     public synchronized void captureBugReportData(int reason) {
234         BugReport report = captureBugreport(reason, isVerboseLoggingEnabled());
235         mLastBugReports.addLast(report);
236         flushDump(reason);
237     }
238 
239     @Override
captureAlertData(int errorCode, byte[] alertData)240     public synchronized void captureAlertData(int errorCode, byte[] alertData) {
241         BugReport report = captureBugreport(errorCode, isVerboseLoggingEnabled());
242         report.alertData = alertData;
243         mLastAlerts.addLast(report);
244         /* Flush HAL ring buffer when detecting data stall */
245         if (mFatalFirmwareAlertErrorCodeList.contains(errorCode)) {
246             flushDump(REPORT_REASON_FATAL_FW_ALERT);
247         }
248     }
249 
250     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)251     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
252         super.dump(pw);
253 
254         for (int i = 0; i < mLastAlerts.size(); i++) {
255             pw.println("--------------------------------------------------------------------");
256             pw.println("Alert dump " + i);
257             pw.print(mLastAlerts.get(i));
258             pw.println("--------------------------------------------------------------------");
259         }
260 
261         for (int i = 0; i < mLastBugReports.size(); i++) {
262             pw.println("--------------------------------------------------------------------");
263             pw.println("Bug dump " + i);
264             pw.print(mLastBugReports.get(i));
265             pw.println("--------------------------------------------------------------------");
266         }
267 
268         pw.println("Last Flush Time: " + mLastDumpTime.toString());
269         pw.println("--------------------------------------------------------------------");
270 
271         dumpPacketFates(pw);
272         mLastMileLogger.dump(pw);
273 
274         pw.println("--------------------------------------------------------------------");
275     }
276 
277     @Override
278     /**
279      * Initiates a system-level bugreport, in a non-blocking fashion.
280      */
takeBugReport(String bugTitle, String bugDetail)281     public void takeBugReport(String bugTitle, String bugDetail) {
282         if (mBuildProperties.isUserBuild()) {
283             return;
284         }
285 
286         try {
287             mWifiInjector.getActivityManagerService().requestWifiBugReport(
288                     bugTitle, bugDetail);
289         } catch (Exception e) {  // diagnostics should never crash system_server
290             mLog.err("error taking bugreport: %").c(e.getClass().getName()).flush();
291         }
292     }
293 
294     /* private methods and data */
295     class BugReport {
296         long systemTimeMs;
297         long kernelTimeNanos;
298         int errorCode;
299         HashMap<String, byte[][]> ringBuffers = new HashMap();
300         byte[] fwMemoryDump;
301         byte[] mDriverStateDump;
302         byte[] alertData;
303         LimitedCircularArray<String> kernelLogLines;
304         ArrayList<String> logcatLines;
305 
clearVerboseLogs()306         void clearVerboseLogs() {
307             fwMemoryDump = null;
308             mDriverStateDump = null;
309         }
310 
toString()311         public String toString() {
312             StringBuilder builder = new StringBuilder();
313 
314             Calendar c = Calendar.getInstance();
315             c.setTimeInMillis(systemTimeMs);
316             builder.append("system time = ").append(
317                     String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)).append("\n");
318 
319             long kernelTimeMs = kernelTimeNanos/(1000*1000);
320             builder.append("kernel time = ").append(kernelTimeMs/1000).append(".").append
321                     (kernelTimeMs%1000).append("\n");
322 
323             if (alertData == null)
324                 builder.append("reason = ").append(errorCode).append("\n");
325             else {
326                 builder.append("errorCode = ").append(errorCode);
327                 builder.append("data \n");
328                 builder.append(compressToBase64(alertData)).append("\n");
329             }
330 
331             if (kernelLogLines != null) {
332                 builder.append("kernel log: \n");
333                 for (int i = 0; i < kernelLogLines.size(); i++) {
334                     builder.append(kernelLogLines.get(i)).append("\n");
335                 }
336                 builder.append("\n");
337             }
338 
339             if (logcatLines != null) {
340                 builder.append("system log: \n");
341                 for (int i = 0; i < logcatLines.size(); i++) {
342                     builder.append(logcatLines.get(i)).append("\n");
343                 }
344                 builder.append("\n");
345             }
346 
347             for (HashMap.Entry<String, byte[][]> e : ringBuffers.entrySet()) {
348                 String ringName = e.getKey();
349                 byte[][] buffers = e.getValue();
350                 builder.append("ring-buffer = ").append(ringName).append("\n");
351 
352                 int size = 0;
353                 for (int i = 0; i < buffers.length; i++) {
354                     size += buffers[i].length;
355                 }
356 
357                 byte[] buffer = new byte[size];
358                 int index = 0;
359                 for (int i = 0; i < buffers.length; i++) {
360                     System.arraycopy(buffers[i], 0, buffer, index, buffers[i].length);
361                     index += buffers[i].length;
362                 }
363 
364                 builder.append(compressToBase64(buffer));
365                 builder.append("\n");
366             }
367 
368             if (fwMemoryDump != null) {
369                 builder.append(FIRMWARE_DUMP_SECTION_HEADER);
370                 builder.append("\n");
371                 builder.append(compressToBase64(fwMemoryDump));
372                 builder.append("\n");
373             }
374 
375             if (mDriverStateDump != null) {
376                 builder.append(DRIVER_DUMP_SECTION_HEADER);
377                 if (StringUtil.isAsciiPrintable(mDriverStateDump)) {
378                     builder.append(" (ascii)\n");
379                     builder.append(new String(mDriverStateDump, Charset.forName("US-ASCII")));
380                     builder.append("\n");
381                 } else {
382                     builder.append(" (base64)\n");
383                     builder.append(compressToBase64(mDriverStateDump));
384                 }
385             }
386 
387             return builder.toString();
388         }
389     }
390 
391     class LimitedCircularArray<E> {
392         private ArrayList<E> mArrayList;
393         private int mMax;
LimitedCircularArray(int max)394         LimitedCircularArray(int max) {
395             mArrayList = new ArrayList<E>(max);
396             mMax = max;
397         }
398 
addLast(E e)399         public final void addLast(E e) {
400             if (mArrayList.size() >= mMax)
401                 mArrayList.remove(0);
402             mArrayList.add(e);
403         }
404 
size()405         public final int size() {
406             return mArrayList.size();
407         }
408 
get(int i)409         public final E get(int i) {
410             return mArrayList.get(i);
411         }
412     }
413 
414     private final LimitedCircularArray<BugReport> mLastAlerts =
415             new LimitedCircularArray<BugReport>(MAX_ALERT_REPORTS);
416     private final LimitedCircularArray<BugReport> mLastBugReports =
417             new LimitedCircularArray<BugReport>(MAX_BUG_REPORTS);
418     private final HashMap<String, ByteArrayRingBuffer> mRingBufferData = new HashMap();
419 
420     private final WifiNative.WifiLoggerEventHandler mHandler =
421             new WifiNative.WifiLoggerEventHandler() {
422         @Override
423         public void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
424             WifiDiagnostics.this.onRingBufferData(status, buffer);
425         }
426 
427         @Override
428         public void onWifiAlert(int errorCode, byte[] buffer) {
429             WifiDiagnostics.this.onWifiAlert(errorCode, buffer);
430         }
431     };
432 
onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer)433     synchronized void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
434         ByteArrayRingBuffer ring = mRingBufferData.get(status.name);
435         if (ring != null) {
436             ring.appendBuffer(buffer);
437         }
438     }
439 
onWifiAlert(int errorCode, @NonNull byte[] buffer)440     synchronized void onWifiAlert(int errorCode, @NonNull byte[] buffer) {
441         captureAlertData(errorCode, buffer);
442         mWifiMetrics.logFirmwareAlert(errorCode);
443     }
444 
isVerboseLoggingEnabled()445     private boolean isVerboseLoggingEnabled() {
446         return mLogLevel > VERBOSE_NORMAL_LOG;
447     }
448 
clearVerboseLogs()449     private void clearVerboseLogs() {
450         mPacketFatesForLastFailure = null;
451 
452         for (int i = 0; i < mLastAlerts.size(); i++) {
453             mLastAlerts.get(i).clearVerboseLogs();
454         }
455 
456         for (int i = 0; i < mLastBugReports.size(); i++) {
457             mLastBugReports.get(i).clearVerboseLogs();
458         }
459     }
460 
fetchRingBuffers()461     private boolean fetchRingBuffers() {
462         if (mRingBuffers != null) return true;
463 
464         mRingBuffers = mWifiNative.getRingBufferStatus();
465         if (mRingBuffers != null) {
466             for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
467                 if (DBG) mLog.trace("RingBufferStatus is: %").c(buffer.name).flush();
468                 if (mRingBufferData.containsKey(buffer.name) == false) {
469                     mRingBufferData.put(buffer.name,
470                             new ByteArrayRingBuffer(mMaxRingBufferSizeBytes));
471                 }
472                 if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
473                     mPerPacketRingBuffer = buffer;
474                 }
475             }
476         } else {
477             mLog.wC("no ring buffers found");
478         }
479 
480         return mRingBuffers != null;
481     }
482 
resizeRingBuffers()483     private void resizeRingBuffers() {
484         for (ByteArrayRingBuffer byteArrayRingBuffer : mRingBufferData.values()) {
485             byteArrayRingBuffer.resize(mMaxRingBufferSizeBytes);
486         }
487     }
488 
startLoggingAllExceptPerPacketBuffers()489     private boolean startLoggingAllExceptPerPacketBuffers() {
490 
491         if (mRingBuffers == null) {
492             if (DBG) mLog.tC("No ring buffers to log anything!");
493             return false;
494         }
495 
496         for (WifiNative.RingBufferStatus buffer : mRingBuffers){
497 
498             if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
499                 /* skip per-packet-buffer */
500                 if (DBG) mLog.trace("skipped per packet logging ring %").c(buffer.name).flush();
501                 continue;
502             }
503 
504             startLoggingRingBuffer(buffer);
505         }
506 
507         return true;
508     }
509 
startLoggingRingBuffer(WifiNative.RingBufferStatus buffer)510     private boolean startLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
511 
512         int minInterval = MinWakeupIntervals[mLogLevel];
513         int minDataSize = MinBufferSizes[mLogLevel];
514 
515         if (mWifiNative.startLoggingRingBuffer(
516                 mLogLevel, 0, minInterval, minDataSize, buffer.name) == false) {
517             if (DBG) mLog.warn("Could not start logging ring %").c(buffer.name).flush();
518             return false;
519         }
520 
521         return true;
522     }
523 
stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer)524     private boolean stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
525         if (mWifiNative.startLoggingRingBuffer(0, 0, 0, 0, buffer.name) == false) {
526             if (DBG) mLog.warn("Could not stop logging ring %").c(buffer.name).flush();
527         }
528         return true;
529     }
530 
stopLoggingAllBuffers()531     private boolean stopLoggingAllBuffers() {
532         if (mRingBuffers != null) {
533             for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
534                 stopLoggingRingBuffer(buffer);
535             }
536         }
537         return true;
538     }
539 
enableVerboseLoggingForDogfood()540     private boolean enableVerboseLoggingForDogfood() {
541         return true;
542 
543     }
544 
flushDump(int errorCode)545     private boolean flushDump(int errorCode) {
546         if (errorCode == REPORT_REASON_USER_ACTION) return false;
547 
548         long currentTime = mClock.getWallClockMillis();
549         int index = mLastDumpTime.indexOfKey(errorCode);
550         if (index >= 0) {
551             if (currentTime - mLastDumpTime.valueAt(index) < MIN_DUMP_TIME_WINDOW_MILLIS) {
552                 return false;
553             }
554         }
555         if (!mWifiNative.flushRingBufferData()) {
556             mLog.wC("could not flush ringbuffer");
557             return false;
558         }
559         mLastDumpTime.put(errorCode, currentTime);
560         return true;
561     }
562 
captureBugreport(int errorCode, boolean captureFWDump)563     private BugReport captureBugreport(int errorCode, boolean captureFWDump) {
564         BugReport report = new BugReport();
565         report.errorCode = errorCode;
566         report.systemTimeMs = System.currentTimeMillis();
567         report.kernelTimeNanos = System.nanoTime();
568 
569         if (mRingBuffers != null) {
570             for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
571                 /* this will push data in mRingBuffers */
572                 mWifiNative.getRingBufferData(buffer.name);
573                 ByteArrayRingBuffer data = mRingBufferData.get(buffer.name);
574                 byte[][] buffers = new byte[data.getNumBuffers()][];
575                 for (int i = 0; i < data.getNumBuffers(); i++) {
576                     buffers[i] = data.getBuffer(i).clone();
577                 }
578                 report.ringBuffers.put(buffer.name, buffers);
579             }
580         }
581 
582         report.logcatLines = getLogcat(127);
583         report.kernelLogLines = getKernelLog(127);
584 
585         if (captureFWDump) {
586             report.fwMemoryDump = mWifiNative.getFwMemoryDump();
587             report.mDriverStateDump = mWifiNative.getDriverStateDump();
588         }
589         return report;
590     }
591 
592     @VisibleForTesting
getBugReports()593     LimitedCircularArray<BugReport> getBugReports() {
594         return mLastBugReports;
595     }
596 
597     @VisibleForTesting
getAlertReports()598     LimitedCircularArray<BugReport> getAlertReports() {
599         return mLastAlerts;
600     }
601 
compressToBase64(byte[] input)602     private String compressToBase64(byte[] input) {
603         String result;
604         //compress
605         Deflater compressor = new Deflater();
606         compressor.setLevel(Deflater.BEST_SPEED);
607         compressor.setInput(input);
608         compressor.finish();
609         ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
610         final byte[] buf = new byte[1024];
611 
612         while (!compressor.finished()) {
613             int count = compressor.deflate(buf);
614             bos.write(buf, 0, count);
615         }
616 
617         try {
618             compressor.end();
619             bos.close();
620         } catch (IOException e) {
621             mLog.wC("ByteArrayOutputStream close error");
622             result =  android.util.Base64.encodeToString(input, Base64.DEFAULT);
623             return result;
624         }
625 
626         byte[] compressed = bos.toByteArray();
627         if (DBG) {
628             mLog.dump("length is: %").c(compressed == null ? 0 : compressed.length).flush();
629         }
630 
631         //encode
632         result = android.util.Base64.encodeToString(
633                 compressed.length < input.length ? compressed : input , Base64.DEFAULT);
634 
635         if (DBG) {
636             mLog.dump("FwMemoryDump length is: %").c(result.length()).flush();
637         }
638 
639         return result;
640     }
641 
getLogcat(int maxLines)642     private ArrayList<String> getLogcat(int maxLines) {
643         ArrayList<String> lines = new ArrayList<String>(maxLines);
644         try {
645             Process process = mJavaRuntime.exec(String.format("logcat -t %d", maxLines));
646             BufferedReader reader = new BufferedReader(
647                     new InputStreamReader(process.getInputStream()));
648             String line;
649             while ((line = reader.readLine()) != null) {
650                 lines.add(line);
651             }
652             reader = new BufferedReader(
653                     new InputStreamReader(process.getErrorStream()));
654             while ((line = reader.readLine()) != null) {
655                 lines.add(line);
656             }
657             process.waitFor();
658         } catch (InterruptedException|IOException e) {
659             mLog.dump("Exception while capturing logcat: %").c(e.toString()).flush();
660         }
661         return lines;
662     }
663 
getKernelLog(int maxLines)664     private LimitedCircularArray<String> getKernelLog(int maxLines) {
665         if (DBG) mLog.tC("Reading kernel log ...");
666         LimitedCircularArray<String> lines = new LimitedCircularArray<String>(maxLines);
667         String log = mWifiNative.readKernelLog();
668         String logLines[] = log.split("\n");
669         for (int i = 0; i < logLines.length; i++) {
670             lines.addLast(logLines[i]);
671         }
672         if (DBG) mLog.dump("Added % lines").c(logLines.length).flush();
673         return lines;
674     }
675 
676     /** Packet fate reporting */
677     private ArrayList<WifiNative.FateReport> mPacketFatesForLastFailure;
678 
fetchPacketFates()679     private ArrayList<WifiNative.FateReport> fetchPacketFates() {
680         ArrayList<WifiNative.FateReport> mergedFates = new ArrayList<WifiNative.FateReport>();
681         WifiNative.TxFateReport[] txFates =
682                 new WifiNative.TxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN];
683         if (mWifiNative.getTxPktFates(mWifiNative.getClientInterfaceName(), txFates)) {
684             for (int i = 0; i < txFates.length && txFates[i] != null; i++) {
685                 mergedFates.add(txFates[i]);
686             }
687         }
688 
689         WifiNative.RxFateReport[] rxFates =
690                 new WifiNative.RxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN];
691         if (mWifiNative.getRxPktFates(mWifiNative.getClientInterfaceName(), rxFates)) {
692             for (int i = 0; i < rxFates.length && rxFates[i] != null; i++) {
693                 mergedFates.add(rxFates[i]);
694             }
695         }
696 
697         Collections.sort(mergedFates, new Comparator<WifiNative.FateReport>() {
698             @Override
699             public int compare(WifiNative.FateReport lhs, WifiNative.FateReport rhs) {
700                 return Long.compare(lhs.mDriverTimestampUSec, rhs.mDriverTimestampUSec);
701             }
702         });
703 
704         return mergedFates;
705     }
706 
dumpPacketFates(PrintWriter pw)707     private void dumpPacketFates(PrintWriter pw) {
708         dumpPacketFatesInternal(pw, "Last failed connection fates", mPacketFatesForLastFailure,
709                 isVerboseLoggingEnabled());
710         dumpPacketFatesInternal(pw, "Latest fates", fetchPacketFates(), isVerboseLoggingEnabled());
711     }
712 
dumpPacketFatesInternal(PrintWriter pw, String description, ArrayList<WifiNative.FateReport> fates, boolean verbose)713     private static void dumpPacketFatesInternal(PrintWriter pw, String description,
714             ArrayList<WifiNative.FateReport> fates, boolean verbose) {
715         if (fates == null) {
716             pw.format("No fates fetched for \"%s\"\n", description);
717             return;
718         }
719 
720         if (fates.size() == 0) {
721             pw.format("HAL provided zero fates for \"%s\"\n", description);
722             return;
723         }
724 
725         pw.format("--------------------- %s ----------------------\n", description);
726 
727         StringBuilder verboseOutput = new StringBuilder();
728         pw.print(WifiNative.FateReport.getTableHeader());
729         for (WifiNative.FateReport fate : fates) {
730             pw.print(fate.toTableRowString());
731             if (verbose) {
732                 // Important: only print Personally Identifiable Information (PII) if verbose
733                 // logging is turned on.
734                 verboseOutput.append(fate.toVerboseStringWithPiiAllowed());
735                 verboseOutput.append("\n");
736             }
737         }
738 
739         if (verbose) {
740             pw.format("\n>>> VERBOSE PACKET FATE DUMP <<<\n\n");
741             pw.print(verboseOutput.toString());
742         }
743 
744         pw.println("--------------------------------------------------------------------");
745     }
746 }
747