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