1 /*
2  * Copyright (C) 2023 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.companion.transport;
18 
19 import android.annotation.NonNull;
20 import android.companion.IOnMessageReceivedListener;
21 import android.content.Context;
22 import android.os.Build;
23 import android.os.ParcelFileDescriptor;
24 import android.os.RemoteException;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 
28 import com.android.internal.annotations.GuardedBy;
29 
30 import libcore.util.EmptyArray;
31 
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.concurrent.CompletableFuture;
38 import java.util.concurrent.Future;
39 import java.util.concurrent.atomic.AtomicInteger;
40 
41 /**
42  * This class represents the channel established between two devices.
43  */
44 public abstract class Transport {
45     protected static final String TAG = "CDM_CompanionTransport";
46     protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
47 
48     static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
49     public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
50     public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
51 
52     static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
53     static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
54 
55     protected static final int HEADER_LENGTH = 12;
56 
57     protected final int mAssociationId;
58     protected final ParcelFileDescriptor mFd;
59     protected final InputStream mRemoteIn;
60     protected final OutputStream mRemoteOut;
61     protected final Context mContext;
62 
63     /**
64      * Message type -> Listener
65      *
66      * For now, the transport only supports 1 listener for each message type. If there's a need in
67      * the future to allow multiple listeners to receive callbacks for the same message type, the
68      * value of the map can be a list.
69      */
70     private final Map<Integer, IOnMessageReceivedListener> mListeners;
71 
72     private OnTransportClosedListener mOnTransportClosed;
73 
isRequest(int message)74     private static boolean isRequest(int message) {
75         return (message & 0xFF000000) == 0x63000000;
76     }
77 
isResponse(int message)78     private static boolean isResponse(int message) {
79         return (message & 0xFF000000) == 0x33000000;
80     }
81 
82     @GuardedBy("mPendingRequests")
83     protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
84             new SparseArray<>();
85     protected final AtomicInteger mNextSequence = new AtomicInteger();
86 
Transport(int associationId, ParcelFileDescriptor fd, Context context)87     Transport(int associationId, ParcelFileDescriptor fd, Context context) {
88         mAssociationId = associationId;
89         mFd = fd;
90         mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
91         mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
92         mContext = context;
93         mListeners = new HashMap<>();
94     }
95 
96     /**
97      * Add a listener when a message is received for the message type
98      * @param message Message type
99      * @param listener Execute when a message with the type is received
100      */
addListener(int message, IOnMessageReceivedListener listener)101     public void addListener(int message, IOnMessageReceivedListener listener) {
102         mListeners.put(message, listener);
103     }
104 
getAssociationId()105     public int getAssociationId() {
106         return mAssociationId;
107     }
108 
getFd()109     protected ParcelFileDescriptor getFd() {
110         return mFd;
111     }
112 
113     /**
114      * Start listening to messages.
115      */
start()116     abstract void start();
117 
118     /**
119      * Soft stop listening to the incoming data without closing the streams.
120      */
stop()121     abstract void stop();
122 
123     /**
124      * Stop listening to the incoming data and close the streams. If a listener for closed event
125      * is set, then trigger it to assist with its clean-up.
126      */
close()127     void close() {
128         if (mOnTransportClosed != null) {
129             mOnTransportClosed.onClosed(this);
130         }
131     }
132 
sendMessage(int message, int sequence, @NonNull byte[] data)133     protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
134             throws IOException;
135 
requestForResponse(int message, byte[] data)136     public Future<byte[]> requestForResponse(int message, byte[] data) {
137         if (DEBUG) Slog.d(TAG, "Requesting for response");
138         final int sequence = mNextSequence.incrementAndGet();
139         final CompletableFuture<byte[]> pending = new CompletableFuture<>();
140         synchronized (mPendingRequests) {
141             mPendingRequests.put(sequence, pending);
142         }
143 
144         try {
145             sendMessage(message, sequence, data);
146         } catch (IOException e) {
147             synchronized (mPendingRequests) {
148                 mPendingRequests.remove(sequence);
149             }
150             pending.completeExceptionally(e);
151         }
152 
153         return pending;
154     }
155 
handleMessage(int message, int sequence, @NonNull byte[] data)156     protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
157             throws IOException {
158         if (DEBUG) {
159             Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
160                     + " sequence " + sequence + " length " + data.length
161                     + " from association " + mAssociationId);
162         }
163 
164         if (isRequest(message)) {
165             try {
166                 processRequest(message, sequence, data);
167             } catch (IOException e) {
168                 Slog.w(TAG, "Failed to respond to 0x" + Integer.toHexString(message), e);
169             }
170         } else if (isResponse(message)) {
171             processResponse(message, sequence, data);
172         } else {
173             Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message));
174         }
175     }
176 
processRequest(int message, int sequence, byte[] data)177     private void processRequest(int message, int sequence, byte[] data)
178             throws IOException {
179         switch (message) {
180             case MESSAGE_REQUEST_PING: {
181                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
182                 break;
183             }
184             case MESSAGE_REQUEST_CONTEXT_SYNC: {
185                 callback(message, data);
186                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
187                 break;
188             }
189             case MESSAGE_REQUEST_PERMISSION_RESTORE: {
190                 try {
191                     callback(message, data);
192                     sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
193                 } catch (Exception e) {
194                     Slog.w(TAG, "Failed to restore permissions");
195                     sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
196                 }
197                 break;
198             }
199             default: {
200                 Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message));
201                 sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
202                 break;
203             }
204         }
205     }
206 
callback(int message, byte[] data)207     private void callback(int message, byte[] data) {
208         if (mListeners.containsKey(message)) {
209             try {
210                 mListeners.get(message).onMessageReceived(getAssociationId(), data);
211                 Slog.i(TAG, "Message 0x" + Integer.toHexString(message)
212                         + " is received from associationId " + mAssociationId
213                         + ", sending data length " + data.length + " to the listener.");
214             } catch (RemoteException ignored) {
215             }
216         }
217     }
218 
processResponse(int message, int sequence, byte[] data)219     private void processResponse(int message, int sequence, byte[] data) {
220         final CompletableFuture<byte[]> future;
221         synchronized (mPendingRequests) {
222             future = mPendingRequests.removeReturnOld(sequence);
223         }
224         if (future == null) {
225             Slog.w(TAG, "Ignoring unknown sequence " + sequence);
226             return;
227         }
228 
229         switch (message) {
230             case MESSAGE_RESPONSE_SUCCESS: {
231                 future.complete(data);
232                 break;
233             }
234             case MESSAGE_RESPONSE_FAILURE: {
235                 future.completeExceptionally(new RuntimeException("Remote failure"));
236                 break;
237             }
238             default: {
239                 Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message));
240             }
241         }
242     }
243 
setOnTransportClosedListener(OnTransportClosedListener callback)244     void setOnTransportClosedListener(OnTransportClosedListener callback) {
245         this.mOnTransportClosed = callback;
246     }
247 
248     // Interface to pass transport to the transport manager to assist with detachment.
249     @FunctionalInterface
250     interface OnTransportClosedListener {
onClosed(Transport transport)251         void onClosed(Transport transport);
252     }
253 }
254