1/*
2 * Copyright (C) 2016 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.connectivity;
18
19import android.content.Context;
20import android.net.ConnectivityMetricsEvent;
21import android.net.IIpConnectivityMetrics;
22import android.net.metrics.IpConnectivityLog;
23import android.os.IBinder;
24import android.os.Parcelable;
25import android.text.TextUtils;
26import android.util.Base64;
27import android.util.Log;
28import com.android.internal.annotations.GuardedBy;
29import com.android.internal.annotations.VisibleForTesting;
30import com.android.server.SystemService;
31import java.io.FileDescriptor;
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35
36import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
37
38/** {@hide} */
39final public class IpConnectivityMetrics extends SystemService {
40    private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
41    private static final boolean DBG = false;
42
43    private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
44
45    // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
46    private static final int DEFAULT_BUFFER_SIZE = 2000;
47
48    // Lock ensuring that concurrent manipulations of the event buffer are correct.
49    // There are three concurrent operations to synchronize:
50    //  - appending events to the buffer.
51    //  - iterating throught the buffer.
52    //  - flushing the buffer content and replacing it by a new buffer.
53    private final Object mLock = new Object();
54
55    @VisibleForTesting
56    public final Impl impl = new Impl();
57    private DnsEventListenerService mDnsListener;
58
59    @GuardedBy("mLock")
60    private ArrayList<ConnectivityMetricsEvent> mBuffer;
61    @GuardedBy("mLock")
62    private int mDropped;
63    @GuardedBy("mLock")
64    private int mCapacity;
65
66    public IpConnectivityMetrics(Context ctx) {
67        super(ctx);
68        initBuffer();
69    }
70
71    @Override
72    public void onStart() {
73        if (DBG) Log.d(TAG, "onStart");
74    }
75
76    @Override
77    public void onBootPhase(int phase) {
78        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
79            if (DBG) Log.d(TAG, "onBootPhase");
80            mDnsListener = new DnsEventListenerService(getContext());
81
82            publishBinderService(SERVICE_NAME, impl);
83            publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener);
84        }
85    }
86
87    @VisibleForTesting
88    public int bufferCapacity() {
89        return DEFAULT_BUFFER_SIZE; // TODO: read from config
90    }
91
92    private void initBuffer() {
93        synchronized (mLock) {
94            mDropped = 0;
95            mCapacity = bufferCapacity();
96            mBuffer = new ArrayList<>(mCapacity);
97        }
98    }
99
100    private int append(ConnectivityMetricsEvent event) {
101        if (DBG) Log.d(TAG, "logEvent: " + event);
102        synchronized (mLock) {
103            final int left = mCapacity - mBuffer.size();
104            if (event == null) {
105                return left;
106            }
107            if (left == 0) {
108                mDropped++;
109                return 0;
110            }
111            mBuffer.add(event);
112            return left - 1;
113        }
114    }
115
116    private String flushEncodedOutput() {
117        final ArrayList<ConnectivityMetricsEvent> events;
118        final int dropped;
119        synchronized (mLock) {
120            events = mBuffer;
121            dropped = mDropped;
122            initBuffer();
123        }
124
125        final byte[] data;
126        try {
127            data = IpConnectivityEventBuilder.serialize(dropped, events);
128        } catch (IOException e) {
129            Log.e(TAG, "could not serialize events", e);
130            return "";
131        }
132
133        return Base64.encodeToString(data, Base64.DEFAULT);
134    }
135
136    /**
137     * Clears the event buffer and prints its content as a protobuf serialized byte array
138     * inside a base64 encoded string.
139     */
140    private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
141        pw.print(flushEncodedOutput());
142    }
143
144    /**
145     * Prints the content of the event buffer, either using the events ASCII representation
146     * or using protobuf text format.
147     */
148    private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
149        final ArrayList<ConnectivityMetricsEvent> events;
150        synchronized (mLock) {
151            events = new ArrayList(mBuffer);
152        }
153
154        if (args.length > 1 && args[1].equals("proto")) {
155            for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
156                pw.print(ev.toString());
157            }
158            return;
159        }
160
161        for (ConnectivityMetricsEvent ev : events) {
162            pw.println(ev.toString());
163        }
164    }
165
166    private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
167        synchronized (mLock) {
168            pw.println("Buffered events: " + mBuffer.size());
169            pw.println("Buffer capacity: " + mCapacity);
170            pw.println("Dropped events: " + mDropped);
171        }
172        if (mDnsListener != null) {
173            mDnsListener.dump(pw);
174        }
175    }
176
177    private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
178        if (args.length == 0) {
179            pw.println("No command");
180            return;
181        }
182        pw.println("Unknown command " + TextUtils.join(" ", args));
183    }
184
185    public final class Impl extends IIpConnectivityMetrics.Stub {
186        static final String CMD_FLUSH   = "flush";
187        static final String CMD_LIST    = "list";
188        static final String CMD_STATS   = "stats";
189        static final String CMD_DEFAULT = CMD_STATS;
190
191        @Override
192        public int logEvent(ConnectivityMetricsEvent event) {
193            enforceConnectivityInternalPermission();
194            return append(event);
195        }
196
197        @Override
198        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
199            enforceDumpPermission();
200            if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
201            final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
202            switch (cmd) {
203                case CMD_FLUSH:
204                    cmdFlush(fd, pw, args);
205                    return;
206                case CMD_LIST:
207                    cmdList(fd, pw, args);
208                    return;
209                case CMD_STATS:
210                    cmdStats(fd, pw, args);
211                    return;
212                default:
213                    cmdDefault(fd, pw, args);
214            }
215        }
216
217        private void enforceConnectivityInternalPermission() {
218            enforcePermission(android.Manifest.permission.CONNECTIVITY_INTERNAL);
219        }
220
221        private void enforceDumpPermission() {
222            enforcePermission(android.Manifest.permission.DUMP);
223        }
224
225        private void enforcePermission(String what) {
226            getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
227        }
228    };
229}
230