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