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 package android.os;
17 
18 import static android.system.OsConstants.MAP_SHARED;
19 import static android.system.OsConstants.PROT_READ;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SuppressLint;
24 import android.system.ErrnoException;
25 import android.system.Os;
26 import android.util.Log;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.io.FileDescriptor;
31 import java.nio.ByteBuffer;
32 import java.nio.DirectByteBuffer;
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * Provides utilities for dealing with HidlMemory.
38  *
39  * @hide
40  */
41 public final class HidlMemoryUtil {
42     static private final String TAG = "HidlMemoryUtil";
43 
HidlMemoryUtil()44     private HidlMemoryUtil() {
45     }
46 
47     /**
48      * Copies a byte-array into a new Ashmem region and return it as HidlMemory.
49      * The returned instance owns the underlying file descriptors, and the client should generally
50      * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
51      * be closed).
52      *
53      * @param input The input byte array.
54      * @return A HidlMemory instance, containing a copy of the input.
55      */
56     public static @NonNull
byteArrayToHidlMemory(@onNull byte[] input)57     HidlMemory byteArrayToHidlMemory(@NonNull byte[] input) {
58         return byteArrayToHidlMemory(input, null);
59     }
60 
61     /**
62      * Copies a byte-array into a new Ashmem region and return it as HidlMemory.
63      * The returned instance owns the underlying file descriptors, and the client should generally
64      * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
65      * be closed).
66      *
67      * @param input The input byte array.
68      * @param name  An optional name for the ashmem region.
69      * @return A HidlMemory instance, containing a copy of the input.
70      */
71     public static @NonNull
byteArrayToHidlMemory(@onNull byte[] input, @Nullable String name)72     HidlMemory byteArrayToHidlMemory(@NonNull byte[] input, @Nullable String name) {
73         Preconditions.checkNotNull(input);
74 
75         if (input.length == 0) {
76             return new HidlMemory("ashmem", 0, null);
77         }
78 
79         try (SharedMemory shmem = SharedMemory.create(name != null ? name : "", input.length)) {
80             ByteBuffer buffer = shmem.mapReadWrite();
81             buffer.put(input);
82             shmem.unmap(buffer);
83             return sharedMemoryToHidlMemory(shmem);
84         } catch (ErrnoException e) {
85             throw new RuntimeException(e);
86         }
87     }
88 
89     /**
90      * Copies a byte list into a new Ashmem region and return it as HidlMemory.
91      * The returned instance owns the underlying file descriptors, and the client should generally
92      * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
93      * be closed).
94      *
95      * @param input The input byte list.
96      * @return A HidlMemory instance, containing a copy of the input.
97      */
98     public static @NonNull
byteListToHidlMemory(@onNull List<Byte> input)99     HidlMemory byteListToHidlMemory(@NonNull List<Byte> input) {
100         return byteListToHidlMemory(input, null);
101     }
102 
103     /**
104      * Copies a byte list into a new Ashmem region and return it as HidlMemory.
105      * The returned instance owns the underlying file descriptors, and the client should generally
106      * call close on it when no longer in use (or otherwise, when the object gets destroyed it will
107      * be closed).
108      *
109      * @param input The input byte list.
110      * @param name  An optional name for the ashmem region.
111      * @return A HidlMemory instance, containing a copy of the input.
112      */
113     public static @NonNull
byteListToHidlMemory(@onNull List<Byte> input, @Nullable String name)114     HidlMemory byteListToHidlMemory(@NonNull List<Byte> input, @Nullable String name) {
115         Preconditions.checkNotNull(input);
116 
117         if (input.isEmpty()) {
118             return new HidlMemory("ashmem", 0, null);
119         }
120 
121         try (SharedMemory shmem = SharedMemory.create(name != null ? name : "", input.size())) {
122             ByteBuffer buffer = shmem.mapReadWrite();
123             for (Byte b : input) {
124                 buffer.put(b);
125             }
126             shmem.unmap(buffer);
127             return sharedMemoryToHidlMemory(shmem);
128         } catch (ErrnoException e) {
129             throw new RuntimeException(e);
130         }
131     }
132 
133     /**
134      * Copies all data from a HidlMemory instance into a byte array.
135      *
136      * @param mem The HidlMemory instance. Must be of name "ashmem" and of size that doesn't exceed
137      *            {@link Integer#MAX_VALUE}.
138      * @return A byte array, containing a copy of the input.
139      */
140     public static @NonNull
hidlMemoryToByteArray(@onNull HidlMemory mem)141     byte[] hidlMemoryToByteArray(@NonNull HidlMemory mem) {
142         Preconditions.checkNotNull(mem);
143         Preconditions.checkArgumentInRange(mem.getSize(), 0L, (long) Integer.MAX_VALUE,
144                 "Memory size");
145         Preconditions.checkArgument(mem.getSize() == 0 || mem.getName().equals("ashmem"),
146                 "Unsupported memory type: %s", mem.getName());
147 
148         if (mem.getSize() == 0) {
149             return new byte[0];
150         }
151 
152         ByteBuffer buffer = getBuffer(mem);
153         byte[] result = new byte[buffer.remaining()];
154         buffer.get(result);
155         return result;
156     }
157 
158     /**
159      * Copies all data from a HidlMemory instance into a byte list.
160      *
161      * @param mem The HidlMemory instance. Must be of name "ashmem" and of size that doesn't exceed
162      *            {@link Integer#MAX_VALUE}.
163      * @return A byte list, containing a copy of the input.
164      */
165     @SuppressLint("ConcreteCollection")
166     public static @NonNull
hidlMemoryToByteList(@onNull HidlMemory mem)167     ArrayList<Byte> hidlMemoryToByteList(@NonNull HidlMemory mem) {
168         Preconditions.checkNotNull(mem);
169         Preconditions.checkArgumentInRange(mem.getSize(), 0L, (long) Integer.MAX_VALUE,
170                 "Memory size");
171         Preconditions.checkArgument(mem.getSize() == 0 || mem.getName().equals("ashmem"),
172                 "Unsupported memory type: %s", mem.getName());
173 
174         if (mem.getSize() == 0) {
175             return new ArrayList<>();
176         }
177 
178         ByteBuffer buffer = getBuffer(mem);
179 
180         ArrayList<Byte> result = new ArrayList<>(buffer.remaining());
181         while (buffer.hasRemaining()) {
182             result.add(buffer.get());
183         }
184         return result;
185     }
186 
187     /**
188      * Converts a SharedMemory to a HidlMemory without copying.
189      *
190      * @param shmem The shared memory object. Null means "empty" and will still result in a non-null
191      *              return value.
192      * @return The HidlMemory instance.
193      */
sharedMemoryToHidlMemory(@ullable SharedMemory shmem)194     @NonNull public static HidlMemory sharedMemoryToHidlMemory(@Nullable SharedMemory shmem) {
195         if (shmem == null) {
196             return new HidlMemory("ashmem", 0, null);
197         }
198         return fileDescriptorToHidlMemory(shmem.getFileDescriptor(), shmem.getSize());
199     }
200 
201     /**
202      * Converts a FileDescriptor to a HidlMemory without copying.
203      *
204      * @param fd   The FileDescriptor object. Null is allowed if size is 0 and will still result in
205      *             a non-null return value.
206      * @param size The size of the memory buffer.
207      * @return The HidlMemory instance.
208      */
fileDescriptorToHidlMemory(@ullable FileDescriptor fd, int size)209     @NonNull public static HidlMemory fileDescriptorToHidlMemory(@Nullable FileDescriptor fd,
210             int size) {
211         Preconditions.checkArgument(fd != null || size == 0);
212         if (fd == null) {
213             return new HidlMemory("ashmem", 0, null);
214         }
215         try {
216             NativeHandle handle = new NativeHandle(Os.dup(fd), true);
217             return new HidlMemory("ashmem", size, handle);
218         } catch (ErrnoException e) {
219             throw new RuntimeException(e);
220         }
221     }
222 
getBuffer(@onNull HidlMemory mem)223     private static ByteBuffer getBuffer(@NonNull HidlMemory mem) {
224         try {
225             final int size = (int) mem.getSize();
226 
227             if (size == 0) {
228                 return ByteBuffer.wrap(new byte[0]);
229             }
230 
231             NativeHandle handle = mem.getHandle();
232 
233             final long address = Os.mmap(0, size, PROT_READ, MAP_SHARED, handle.getFileDescriptor(),
234                     0);
235             return new DirectByteBuffer(size, address, handle.getFileDescriptor(), () -> {
236                 try {
237                     Os.munmap(address, size);
238                 } catch (ErrnoException e) {
239                     Log.wtf(TAG, e);
240                 }
241             }, true);
242         } catch (ErrnoException e) {
243             throw new RuntimeException(e);
244         }
245     }
246 }
247