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