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 
17 package android.hardware.usb;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.os.Build;
22 import android.util.Log;
23 
24 import com.android.internal.util.Preconditions;
25 
26 import dalvik.system.CloseGuard;
27 
28 import java.nio.BufferOverflowException;
29 import java.nio.ByteBuffer;
30 import java.util.Objects;
31 
32 /**
33  * A class representing USB request packet.
34  * This can be used for both reading and writing data to or from a
35  * {@link android.hardware.usb.UsbDeviceConnection}.
36  * UsbRequests can be used to transfer data on bulk and interrupt endpoints.
37  * Requests on bulk endpoints can be sent synchronously via {@link UsbDeviceConnection#bulkTransfer}
38  * or asynchronously via {@link #queue} and {@link UsbDeviceConnection#requestWait}.
39  * Requests on interrupt endpoints are only send and received asynchronously.
40  *
41  * <p>Requests on endpoint zero are not supported by this class;
42  * use {@link UsbDeviceConnection#controlTransfer} for endpoint zero requests instead.
43  */
44 public class UsbRequest {
45 
46     private static final String TAG = "UsbRequest";
47 
48     // From drivers/usb/core/devio.c
49     static final int MAX_USBFS_BUFFER_SIZE = 16384;
50 
51     // used by the JNI code
52     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
53     private long mNativeContext;
54 
55     private UsbEndpoint mEndpoint;
56 
57     /** The buffer that is currently being read / written */
58     @UnsupportedAppUsage
59     private ByteBuffer mBuffer;
60 
61     /** The amount of data to read / write when using {@link #queue} */
62     @UnsupportedAppUsage
63     private int mLength;
64 
65     // for client use
66     private Object mClientData;
67 
68     // Prevent the connection from being finalized
69     private UsbDeviceConnection mConnection;
70 
71     /**
72      * Whether this buffer was {@link #queue(ByteBuffer) queued using the new behavior} or
73      * {@link #queue(ByteBuffer, int) queued using the deprecated behavior}.
74      */
75     private boolean mIsUsingNewQueue;
76 
77     /** Temporary buffer than might be used while buffer is enqueued */
78     private ByteBuffer mTempBuffer;
79 
80     private final CloseGuard mCloseGuard = CloseGuard.get();
81 
82     /**
83      * Lock for queue, enqueue and dequeue, so a queue operation can be finished by a dequeue
84      * operation on a different thread.
85      */
86     private final Object mLock = new Object();
87 
UsbRequest()88     public UsbRequest() {
89     }
90 
91     /**
92      * Initializes the request so it can read or write data on the given endpoint.
93      * Whether the request allows reading or writing depends on the direction of the endpoint.
94      *
95      * @param endpoint the endpoint to be used for this request.
96      * @return true if the request was successfully opened.
97      */
initialize(UsbDeviceConnection connection, UsbEndpoint endpoint)98     public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) {
99         mEndpoint = endpoint;
100         mConnection = Objects.requireNonNull(connection, "connection");
101 
102         boolean wasInitialized = native_init(connection, endpoint.getAddress(),
103                 endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval());
104 
105         if (wasInitialized) {
106             mCloseGuard.open("UsbRequest.close");
107         }
108 
109         return wasInitialized;
110     }
111 
112     /**
113      * Releases all resources related to this request.
114      */
close()115     public void close() {
116         synchronized (mLock) {
117             if (mNativeContext != 0) {
118                 mEndpoint = null;
119                 mConnection = null;
120                 native_close();
121                 mCloseGuard.close();
122             }
123         }
124     }
125 
126     @Override
finalize()127     protected void finalize() throws Throwable {
128         try {
129             if (mCloseGuard != null) {
130                 mCloseGuard.warnIfOpen();
131             }
132 
133             close();
134         } finally {
135             super.finalize();
136         }
137     }
138 
139     /**
140      * Returns the endpoint for the request, or null if the request is not opened.
141      *
142      * @return the request's endpoint
143      */
getEndpoint()144     public UsbEndpoint getEndpoint() {
145         return mEndpoint;
146     }
147 
148     /**
149      * Returns the client data for the request.
150      * This can be used in conjunction with {@link #setClientData}
151      * to associate another object with this request, which can be useful for
152      * maintaining state between calls to {@link #queue} and
153      * {@link android.hardware.usb.UsbDeviceConnection#requestWait}
154      *
155      * @return the client data for the request
156      */
getClientData()157     public Object getClientData() {
158         return mClientData;
159     }
160 
161     /**
162      * Sets the client data for the request.
163      * This can be used in conjunction with {@link #getClientData}
164      * to associate another object with this request, which can be useful for
165      * maintaining state between calls to {@link #queue} and
166      * {@link android.hardware.usb.UsbDeviceConnection#requestWait}
167      *
168      * @param data the client data for the request
169      */
setClientData(Object data)170     public void setClientData(Object data) {
171         mClientData = data;
172     }
173 
174     /**
175      * Queues the request to send or receive data on its endpoint.
176      * <p>For OUT endpoints, the given buffer data will be sent on the endpoint. For IN endpoints,
177      * the endpoint will attempt to read the given number of bytes into the specified buffer. If the
178      * queueing operation is successful, return true. The result will be returned via
179      * {@link UsbDeviceConnection#requestWait}</p>
180      *
181      * @param buffer the buffer containing the bytes to write, or location to store the results of a
182      *               read. Position and array offset will be ignored and assumed to be 0. Limit and
183      *               capacity will be ignored. Once the request
184      *               {@link UsbDeviceConnection#requestWait() is processed} the position will be set
185      *               to the number of bytes read/written.
186      * @param length number of bytes to read or write. Before {@value Build.VERSION_CODES#P}, a
187      *               value larger than 16384 bytes would be truncated down to 16384. In API
188      *               {@value Build.VERSION_CODES#P} and after, any value of length is valid.
189      *
190      * @return true if the queueing operation succeeded
191      *
192      * @deprecated Use {@link #queue(ByteBuffer)} instead.
193      */
194     @Deprecated
queue(ByteBuffer buffer, int length)195     public boolean queue(ByteBuffer buffer, int length) {
196         UsbDeviceConnection connection = mConnection;
197         if (connection == null) {
198             // The expected exception by CTS Verifier - USB Device test
199             throw new NullPointerException("invalid connection");
200         }
201 
202         // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
203         // the connection being closed while queueing.
204         return connection.queueRequest(this, buffer, length);
205     }
206 
207     /**
208      * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
209      * there, to prevent the connection being closed while queueing.
210      */
queueIfConnectionOpen(ByteBuffer buffer, int length)211     /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) {
212         UsbDeviceConnection connection = mConnection;
213         if (connection == null || !connection.isOpen()) {
214             // The expected exception by CTS Verifier - USB Device test
215             throw new NullPointerException("invalid connection");
216         }
217 
218         boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
219         boolean result;
220 
221         if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P
222                 && length > MAX_USBFS_BUFFER_SIZE) {
223             length = MAX_USBFS_BUFFER_SIZE;
224         }
225 
226         synchronized (mLock) {
227             // save our buffer for when the request has completed
228             mBuffer = buffer;
229             mLength = length;
230 
231             // Note: On a buffer slice we lost the capacity information about the underlying buffer,
232             // hence we cannot check if the access would be a data leak/memory corruption.
233 
234             if (buffer.isDirect()) {
235                 result = native_queue_direct(buffer, length, out);
236             } else if (buffer.hasArray()) {
237                 result = native_queue_array(buffer.array(), length, out);
238             } else {
239                 throw new IllegalArgumentException("buffer is not direct and has no array");
240             }
241             if (!result) {
242                 mBuffer = null;
243                 mLength = 0;
244             }
245         }
246 
247         return result;
248     }
249 
250     /**
251      * Queues the request to send or receive data on its endpoint.
252      *
253      * <p>For OUT endpoints, the remaining bytes of the buffer will be sent on the endpoint. For IN
254      * endpoints, the endpoint will attempt to fill the remaining bytes of the buffer. If the
255      * queueing operation is successful, return true. The result will be returned via
256      * {@link UsbDeviceConnection#requestWait}</p>
257      *
258      * @param buffer the buffer containing the bytes to send, or the buffer to fill. The state
259      *               of the buffer is undefined until the request is returned by
260      *               {@link UsbDeviceConnection#requestWait}. If the request failed the buffer
261      *               will be unchanged; if the request succeeded the position of the buffer is
262      *               incremented by the number of bytes sent/received. Before
263      *               {@value Build.VERSION_CODES#P}, a buffer of length larger than 16384 bytes
264      *               would throw IllegalArgumentException. In API {@value Build.VERSION_CODES#P}
265      *               and after, any size buffer is valid.
266      *
267      * @return true if the queueing operation succeeded
268      */
queue(@ullable ByteBuffer buffer)269     public boolean queue(@Nullable ByteBuffer buffer) {
270         UsbDeviceConnection connection = mConnection;
271         if (connection == null) {
272             // The expected exception by CTS Verifier - USB Device test
273             throw new IllegalStateException("invalid connection");
274         }
275 
276         // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
277         // the connection being closed while queueing.
278         return connection.queueRequest(this, buffer);
279     }
280 
281     /**
282      * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
283      * there, to prevent the connection being closed while queueing.
284      */
queueIfConnectionOpen(@ullable ByteBuffer buffer)285     /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) {
286         UsbDeviceConnection connection = mConnection;
287         if (connection == null || !connection.isOpen()) {
288             // The expected exception by CTS Verifier - USB Device test
289             throw new IllegalStateException("invalid connection");
290         }
291 
292         // Request need to be initialized
293         Preconditions.checkState(mNativeContext != 0, "request is not initialized");
294 
295         // Request can not be currently queued
296         Preconditions.checkState(!mIsUsingNewQueue, "this request is currently queued");
297 
298         boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
299         boolean wasQueued;
300 
301         synchronized (mLock) {
302             mBuffer = buffer;
303 
304             if (buffer == null) {
305                 // Null buffers enqueue empty USB requests which is supported
306                 mIsUsingNewQueue = true;
307                 wasQueued = native_queue(null, 0, 0);
308             } else {
309                 if (connection.getContext().getApplicationInfo().targetSdkVersion
310                         < Build.VERSION_CODES.P) {
311                     // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once
312                     Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE,
313                             "number of remaining bytes");
314                 }
315 
316                 // Can not receive into read-only buffers.
317                 Preconditions.checkArgument(!(buffer.isReadOnly() && !isSend), "buffer can not be "
318                         + "read-only when receiving data");
319 
320                 if (!buffer.isDirect()) {
321                     mTempBuffer = ByteBuffer.allocateDirect(mBuffer.remaining());
322 
323                     if (isSend) {
324                         // Copy buffer into temporary buffer
325                         mBuffer.mark();
326                         mTempBuffer.put(mBuffer);
327                         mTempBuffer.flip();
328                         mBuffer.reset();
329                     }
330 
331                     // Send/Receive into the temp buffer instead
332                     buffer = mTempBuffer;
333                 }
334 
335                 mIsUsingNewQueue = true;
336                 wasQueued = native_queue(buffer, buffer.position(), buffer.remaining());
337             }
338         }
339 
340         if (!wasQueued) {
341             mIsUsingNewQueue = false;
342             mTempBuffer = null;
343             mBuffer = null;
344         }
345 
346         return wasQueued;
347     }
348 
dequeue(boolean useBufferOverflowInsteadOfIllegalArg)349     /* package */ void dequeue(boolean useBufferOverflowInsteadOfIllegalArg) {
350         boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
351         int bytesTransferred;
352 
353         synchronized (mLock) {
354             if (mIsUsingNewQueue) {
355                 bytesTransferred = native_dequeue_direct();
356                 mIsUsingNewQueue = false;
357 
358                 if (mBuffer == null) {
359                     // Nothing to do
360                 } else if (mTempBuffer == null) {
361                     mBuffer.position(mBuffer.position() + bytesTransferred);
362                 } else {
363                     mTempBuffer.limit(bytesTransferred);
364 
365                     // The user might have modified mBuffer which might make put/position fail.
366                     // Changing the buffer while a request is in flight is not supported. Still,
367                     // make sure to free mTempBuffer correctly.
368                     try {
369                         if (isSend) {
370                             mBuffer.position(mBuffer.position() + bytesTransferred);
371                         } else {
372                             // Copy temp buffer back into original buffer
373                             mBuffer.put(mTempBuffer);
374                         }
375                     } finally {
376                         mTempBuffer = null;
377                     }
378                 }
379             } else {
380                 if (mBuffer.isDirect()) {
381                     bytesTransferred = native_dequeue_direct();
382                 } else {
383                     bytesTransferred = native_dequeue_array(mBuffer.array(), mLength, isSend);
384                 }
385                 if (bytesTransferred >= 0) {
386                     int bytesToStore = Math.min(bytesTransferred, mLength);
387                     try {
388                         mBuffer.position(bytesToStore);
389                     } catch (IllegalArgumentException e) {
390                         if (useBufferOverflowInsteadOfIllegalArg) {
391                             Log.e(TAG, "Buffer " + mBuffer + " does not have enough space to read "
392                                     + bytesToStore + " bytes", e);
393                             throw new BufferOverflowException();
394                         } else {
395                             throw e;
396                         }
397                     }
398                 }
399             }
400 
401             mBuffer = null;
402             mLength = 0;
403         }
404     }
405 
406     /**
407      * Cancels a pending queue operation.
408      *
409      * @return true if cancelling succeeded
410      */
cancel()411     public boolean cancel() {
412         UsbDeviceConnection connection = mConnection;
413         if (connection == null) {
414             return false;
415         }
416 
417         return connection.cancelRequest(this);
418     }
419 
420     /**
421      * Cancels a pending queue operation (for use when the UsbDeviceConnection associated
422      * with this request is synchronized). This ensures we don't have a race where the
423      * device is closed and then the request is canceled which would lead to a
424      * use-after-free because the cancel operation uses the device connection
425      * information freed in the when UsbDeviceConnection is closed.<br/>
426      *
427      * This method assumes the connected is not closed while this method is executed.
428      *
429      * @return true if cancelling succeeded.
430      */
cancelIfOpen()431     /* package */ boolean cancelIfOpen() {
432         UsbDeviceConnection connection = mConnection;
433         if (mNativeContext == 0 || (connection != null && !connection.isOpen())) {
434             Log.w(TAG,
435                     "Detected attempt to cancel a request on a connection which isn't open");
436             return false;
437         }
438         return native_cancel();
439     }
440 
native_init(UsbDeviceConnection connection, int ep_address, int ep_attributes, int ep_max_packet_size, int ep_interval)441     private native boolean native_init(UsbDeviceConnection connection, int ep_address,
442             int ep_attributes, int ep_max_packet_size, int ep_interval);
native_close()443     private native void native_close();
native_queue(ByteBuffer buffer, int offset, int length)444     private native boolean native_queue(ByteBuffer buffer, int offset, int length);
native_queue_array(byte[] buffer, int length, boolean out)445     private native boolean native_queue_array(byte[] buffer, int length, boolean out);
native_dequeue_array(byte[] buffer, int length, boolean out)446     private native int native_dequeue_array(byte[] buffer, int length, boolean out);
native_queue_direct(ByteBuffer buffer, int length, boolean out)447     private native boolean native_queue_direct(ByteBuffer buffer, int length, boolean out);
native_dequeue_direct()448     private native int native_dequeue_direct();
native_cancel()449     private native boolean native_cancel();
450 }
451