1 /*
2  * Copyright (C) 2019 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.networkstack.metrics;
18 
19 import android.net.wifi.WifiInfo;
20 
21 import androidx.annotation.IntRange;
22 import androidx.annotation.NonNull;
23 import androidx.annotation.Nullable;
24 import androidx.annotation.VisibleForTesting;
25 
26 import com.android.internal.util.HexDump;
27 import com.android.net.module.util.CollectionUtils;
28 import com.android.server.connectivity.nano.CellularData;
29 import com.android.server.connectivity.nano.DataStallEventProto;
30 import com.android.server.connectivity.nano.DnsEvent;
31 import com.android.server.connectivity.nano.WifiData;
32 
33 import com.google.protobuf.nano.MessageNano;
34 
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Objects;
39 
40 /**
41  * Class to record the stats of detection level information for data stall.
42  *
43  * @hide
44  */
45 public final class DataStallDetectionStats {
46     public static final int UNKNOWN_SIGNAL_STRENGTH = -1;
47     // Default value of TCP signals.
48     @VisibleForTesting
49     public static final int UNSPECIFIED_TCP_FAIL_RATE = -1;
50     @VisibleForTesting
51     public static final int UNSPECIFIED_TCP_PACKETS_COUNT = -1;
52     @VisibleForTesting
53     @NonNull
54     public final byte[] mCellularInfo;
55     @VisibleForTesting
56     @NonNull
57     public final byte[] mWifiInfo;
58     @NonNull
59     public final byte[] mDns;
60     @VisibleForTesting
61     public final int mEvaluationType;
62     @VisibleForTesting
63     public final int mNetworkType;
64     // The TCP packets fail rate percentage from the latest tcp polling. -1 means the TCP signal is
65     // not known or not supported on the SDK version of this device.
66     @VisibleForTesting @IntRange(from = -1, to = 100)
67     public final int mTcpFailRate;
68     // Number of packets sent since the last received packet.
69     @VisibleForTesting
70     public final int mTcpSentSinceLastRecv;
71 
DataStallDetectionStats(@ullable byte[] cell, @Nullable byte[] wifi, @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType, int failRate, int sentSinceLastRecv)72     public DataStallDetectionStats(@Nullable byte[] cell, @Nullable byte[] wifi,
73                 @NonNull int[] returnCode, @NonNull long[] dnsTime, int evalType, int netType,
74                 int failRate, int sentSinceLastRecv) {
75         mCellularInfo = emptyCellDataIfNull(cell);
76         mWifiInfo = emptyWifiInfoIfNull(wifi);
77 
78         DnsEvent dns = new DnsEvent();
79         dns.dnsReturnCode = returnCode;
80         dns.dnsTime = dnsTime;
81         mDns = MessageNano.toByteArray(dns);
82         mEvaluationType = evalType;
83         mNetworkType = netType;
84         mTcpFailRate = failRate;
85         mTcpSentSinceLastRecv = sentSinceLastRecv;
86     }
87 
88     /**
89      * Because metrics data must contain data for each field even if it's not supported or not
90      * available, generate a byte array representing an empty {@link CellularData} if the
91      * {@link CellularData} is unavailable.
92      *
93      * @param cell a byte array representing current {@link CellularData} of {@code this}
94      * @return a byte array of a {@link CellularData}.
95      */
96     @VisibleForTesting
emptyCellDataIfNull(@ullable byte[] cell)97     public static byte[] emptyCellDataIfNull(@Nullable byte[] cell) {
98         if (cell != null) return cell;
99 
100         CellularData data  = new CellularData();
101         data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_UNKNOWN;
102         data.networkMccmnc = "";
103         data.simMccmnc = "";
104         data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
105         return MessageNano.toByteArray(data);
106     }
107 
108     /**
109      * Because metrics data must contain data for each field even if it's not supported or not
110      * available, generate a byte array representing an empty {@link WifiData} if the
111      * {@link WiFiData} is unavailable.
112      *
113      * @param wifi a byte array representing current {@link WiFiData} of {@code this}.
114      * @return a byte array of a {@link WiFiData}.
115      */
116     @VisibleForTesting
emptyWifiInfoIfNull(@ullable byte[] wifi)117     public static byte[] emptyWifiInfoIfNull(@Nullable byte[] wifi) {
118         if (wifi != null) return wifi;
119 
120         WifiData data = new WifiData();
121         data.wifiBand = DataStallEventProto.AP_BAND_UNKNOWN;
122         data.signalStrength = UNKNOWN_SIGNAL_STRENGTH;
123         return MessageNano.toByteArray(data);
124     }
125 
126     @Override
toString()127     public String toString() {
128         StringBuilder sb = new StringBuilder();
129         sb.append("type: ").append(mNetworkType)
130           .append(", evaluation type: ")
131           .append(mEvaluationType)
132           .append(", wifi info: ")
133           .append(HexDump.toHexString(mWifiInfo))
134           .append(", cell info: ")
135           .append(HexDump.toHexString(mCellularInfo))
136           .append(", dns: ")
137           .append(HexDump.toHexString(mDns))
138           .append(", tcp fail rate: ")
139           .append(mTcpFailRate)
140           .append(", tcp received: ")
141           .append(mTcpSentSinceLastRecv);
142         return sb.toString();
143     }
144 
145     @Override
equals(@ullable final Object o)146     public boolean equals(@Nullable final Object o) {
147         if (!(o instanceof DataStallDetectionStats)) return false;
148         final DataStallDetectionStats other = (DataStallDetectionStats) o;
149         return (mNetworkType == other.mNetworkType)
150             && (mEvaluationType == other.mEvaluationType)
151             && Arrays.equals(mWifiInfo, other.mWifiInfo)
152             && Arrays.equals(mCellularInfo, other.mCellularInfo)
153             && Arrays.equals(mDns, other.mDns)
154             && (mTcpFailRate == other.mTcpFailRate)
155             && (mTcpSentSinceLastRecv == other.mTcpSentSinceLastRecv);
156     }
157 
158     @Override
hashCode()159     public int hashCode() {
160         return Objects.hash(mNetworkType, mEvaluationType, Arrays.hashCode(mWifiInfo),
161                 Arrays.hashCode(mCellularInfo), Arrays.hashCode(mDns), mTcpFailRate,
162                 mTcpSentSinceLastRecv);
163     }
164 
165     /**
166      * Utility to create an instance of {@Link DataStallDetectionStats}
167      *
168      * @hide
169      */
170     public static class Builder {
171         @Nullable
172         private byte[] mCellularInfo;
173         @Nullable
174         private byte[] mWifiInfo;
175         @NonNull
176         private final List<Integer> mDnsReturnCode = new ArrayList<Integer>();
177         @NonNull
178         private final List<Long> mDnsTimeStamp = new ArrayList<Long>();
179         private int mEvaluationType;
180         private int mNetworkType;
181         private int mTcpFailRate = UNSPECIFIED_TCP_FAIL_RATE;
182         private int mTcpSentSinceLastRecv = UNSPECIFIED_TCP_PACKETS_COUNT;
183 
184         /**
185          * Add a dns event into Builder.
186          *
187          * @param code the return code of the dns event.
188          * @param timeMs the elapsedRealtime in ms that the the dns event was received from netd.
189          * @return {@code this} {@link Builder} instance.
190          */
addDnsEvent(int code, long timeMs)191         public Builder addDnsEvent(int code, long timeMs) {
192             mDnsReturnCode.add(code);
193             mDnsTimeStamp.add(timeMs);
194             return this;
195         }
196 
197         /**
198          * Set the data stall evaluation type into Builder.
199          *
200          * @param type the signal type causing a data stall to be suspected.
201          * @return {@code this} {@link Builder} instance.
202          */
setEvaluationType(int type)203         public Builder setEvaluationType(int type) {
204             mEvaluationType = type;
205             return this;
206         }
207 
208         /**
209          * Set the network type into Builder.
210          *
211          * @param type the network type of the logged network.
212          * @return {@code this} {@link Builder} instance.
213          */
setNetworkType(int type)214         public Builder setNetworkType(int type) {
215             mNetworkType = type;
216             return this;
217         }
218 
219         /**
220          * Set the TCP packet fail rate into Builder. The data is included since android R.
221          *
222          * @param rate the TCP packet fail rate of the logged network. The default value is
223          *               {@code UNSPECIFIED_TCP_FAIL_RATE}, which means the TCP signal is not known
224          *               or not supported on the SDK version of this device.
225          * @return {@code this} {@link Builder} instance.
226          */
setTcpFailRate(@ntRangefrom = -1, to = 100) int rate)227         public Builder setTcpFailRate(@IntRange(from = -1, to = 100) int rate) {
228             mTcpFailRate = rate;
229             return this;
230         }
231 
232         /**
233          * Set the number of TCP packets sent since the last received packet into Builder. The data
234          * starts to be included since android R.
235          *
236          * @param count the number of packets sent since the last received packet of the logged
237          *              network. Keep it unset as default value or set to
238          *              {@code UNSPECIFIED_TCP_PACKETS_COUNT} if the tcp signal is unsupported with
239          *              current device android sdk version or the packets count is unknown.
240          * @return {@code this} {@link Builder} instance.
241          */
setTcpSentSinceLastRecv(int count)242         public Builder setTcpSentSinceLastRecv(int count) {
243             mTcpSentSinceLastRecv = count;
244             return this;
245         }
246 
247         /**
248          * Set the wifi data into Builder.
249          *
250          * @param info a {@link WifiInfo} of the connected wifi network.
251          * @return {@code this} {@link Builder} instance.
252          */
setWiFiData(@ullable final WifiInfo info)253         public Builder setWiFiData(@Nullable final WifiInfo info) {
254             WifiData data = new WifiData();
255             data.wifiBand = getWifiBand(info);
256             data.signalStrength = (info != null) ? info.getRssi() : UNKNOWN_SIGNAL_STRENGTH;
257             mWifiInfo = MessageNano.toByteArray(data);
258             return this;
259         }
260 
getWifiBand(@ullable final WifiInfo info)261         private static int getWifiBand(@Nullable final WifiInfo info) {
262             if (info == null) return DataStallEventProto.AP_BAND_UNKNOWN;
263 
264             int freq = info.getFrequency();
265             // Refer to ScanResult.is5GHz(), ScanResult.is24GHz() and ScanResult.is6GHz().
266             if (freq >= 5160 && freq <= 5865) {
267                 return DataStallEventProto.AP_BAND_5GHZ;
268             } else if (freq >= 2412 && freq <= 2484) {
269                 return DataStallEventProto.AP_BAND_2GHZ;
270             } else if (freq >= 5945 && freq <= 7105) {
271                 return DataStallEventProto.AP_BAND_6GHZ;
272             } else {
273                 return DataStallEventProto.AP_BAND_UNKNOWN;
274             }
275         }
276 
277         /**
278          * Set the cellular data into Builder.
279          *
280          * @param radioType the radio technology of the logged cellular network.
281          * @param roaming a boolean indicates if logged cellular network is roaming or not.
282          * @param networkMccmnc the mccmnc of the camped network.
283          * @param simMccmnc the mccmnc of the sim.
284          * @return {@code this} {@link Builder} instance.
285          */
setCellData(int radioType, boolean roaming, @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss)286         public Builder setCellData(int radioType, boolean roaming,
287                 @NonNull String networkMccmnc, @NonNull String simMccmnc, int ss) {
288             CellularData data  = new CellularData();
289             data.ratType = radioType;
290             data.isRoaming = roaming;
291             data.networkMccmnc = networkMccmnc;
292             data.simMccmnc = simMccmnc;
293             data.signalStrength = ss;
294             mCellularInfo = MessageNano.toByteArray(data);
295             return this;
296         }
297 
298         /**
299          * Create a new {@Link DataStallDetectionStats}.
300          */
build()301         public DataStallDetectionStats build() {
302             return new DataStallDetectionStats(mCellularInfo, mWifiInfo,
303                     CollectionUtils.toIntArray(mDnsReturnCode),
304                     CollectionUtils.toLongArray(mDnsTimeStamp),
305                     mEvaluationType, mNetworkType, mTcpFailRate, mTcpSentSinceLastRecv);
306         }
307     }
308 }
309