blob: 03f5f22b28356ba7362d8ebb0a4e566a64c79d18 [file] [log] [blame]
Xiao Ma6f95cef2020-09-08 19:16:15 +09001/*
2 * Copyright (C) 2020 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 android.net.util;
18
19import android.annotation.NonNull;
20
21import java.lang.annotation.ElementType;
22import java.lang.annotation.Retention;
23import java.lang.annotation.RetentionPolicy;
24import java.lang.annotation.Target;
25import java.lang.reflect.Constructor;
26import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Modifier;
28import java.math.BigInteger;
29import java.nio.BufferUnderflowException;
30import java.nio.ByteBuffer;
31import java.nio.ByteOrder;
Xiao Maa76690a2020-11-04 10:41:04 +090032import java.util.concurrent.ConcurrentHashMap;
Xiao Ma6f95cef2020-09-08 19:16:15 +090033
34/**
35 * Define a generic class that helps to parse the structured message.
36 *
37 * Example usage:
38 *
39 * // C-style NduserOption message header definition in the kernel:
40 * struct nduseroptmsg {
41 * unsigned char nduseropt_family;
42 * unsigned char nduseropt_pad1;
43 * unsigned short nduseropt_opts_len;
44 * int nduseropt_ifindex;
45 * __u8 nduseropt_icmp_type;
46 * __u8 nduseropt_icmp_code;
47 * unsigned short nduseropt_pad2;
48 * unsigned int nduseropt_pad3;
49 * }
50 *
51 * - Declare a subclass with explicit constructor or not which extends from this class to parse
52 * NduserOption header from raw bytes array.
53 *
54 * - Option w/ explicit constructor:
55 * static class NduserOptHeaderMessage extends Struct {
56 * @Field(order = 0, type = Type.U8, padding = 1)
57 * final short family;
58 * @Field(order = 1, type = Type.U16)
59 * final int len;
60 * @Field(order = 2, type = Type.S32)
61 * final int ifindex;
62 * @Field(order = 3, type = Type.U8)
63 * final short type;
64 * @Field(order = 4, type = Type.U8, padding = 6)
65 * final short code;
66 *
67 * NduserOptHeaderMessage(final short family, final int len, final int ifindex,
68 * final short type, final short code) {
69 * this.family = family;
70 * this.len = len;
71 * this.ifindex = ifindex;
72 * this.type = type;
73 * this.code = code;
74 * }
75 * }
76 *
77 * - Option w/o explicit constructor:
78 * static class NduserOptHeaderMessage extends Struct {
79 * @Field(order = 0, type = Type.U8, padding = 1)
80 * short family;
81 * @Field(order = 1, type = Type.U16)
82 * int len;
83 * @Field(order = 2, type = Type.S32)
84 * int ifindex;
85 * @Field(order = 3, type = Type.U8)
86 * short type;
87 * @Field(order = 4, type = Type.U8, padding = 6)
88 * short code;
89 * }
90 *
91 * - Parse the target message and refer the members.
92 * final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY);
93 * buf.order(ByteOrder.nativeOrder());
94 * final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf);
95 * assertEquals(10, nduserHdrMsg.family);
96 */
97public class Struct {
98 public enum Type {
99 U8, // unsigned byte, size = 1 byte
100 U16, // unsigned short, size = 2 bytes
101 U32, // unsigned int, size = 4 bytes
102 U64, // unsigned long, size = 8 bytes
103 S8, // signed byte, size = 1 byte
104 S16, // signed short, size = 2 bytes
105 S32, // signed int, size = 4 bytes
106 S64, // signed long, size = 8 bytes
107 BE16, // unsigned short in network order, size = 2 bytes
108 BE32, // unsigned int in network order, size = 4 bytes
109 BE64, // unsigned long in network order, size = 8 bytes
110 ByteArray, // byte array with predefined length
111 }
112
113 /**
114 * Indicate that the field marked with this annotation will automatically be managed by this
115 * class (e.g., will be parsed by #parse).
116 *
117 * order: The placeholder associated with each field, consecutive order starting from zero.
118 * type: The primitive data type listed in above Type enumeration.
119 * padding: Padding bytes appear after the field for alignment.
120 * arraysize: The length of byte array.
121 *
122 * Annotation associated with field MUST have order and type properties at least, padding
123 * and arraysize properties depend on the specific usage, if these properties are absence,
124 * then default value 0 will be applied.
125 */
126 @Retention(RetentionPolicy.RUNTIME)
127 @Target(ElementType.FIELD)
128 public @interface Field {
129 int order();
130 Type type();
131 int padding() default 0;
132 int arraysize() default 0;
133 }
134
135 private static class FieldInfo {
136 @NonNull
137 public final Field annotation;
138 @NonNull
139 public final java.lang.reflect.Field field;
140
141 FieldInfo(final Field annotation, final java.lang.reflect.Field field) {
142 this.annotation = annotation;
143 this.field = field;
144 }
145 }
Xiao Maa76690a2020-11-04 10:41:04 +0900146 private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
Xiao Ma6f95cef2020-09-08 19:16:15 +0900147
148 private static void checkAnnotationType(final Type type, final Class fieldType) {
149 switch (type) {
150 case U8:
151 case S16:
152 if (fieldType == Short.TYPE) return;
153 break;
154 case U16:
155 case S32:
156 case BE16:
157 if (fieldType == Integer.TYPE) return;
158 break;
159 case U32:
160 case S64:
161 case BE32:
162 if (fieldType == Long.TYPE) return;
163 break;
164 case U64:
165 case BE64:
166 if (fieldType == BigInteger.class || fieldType == Long.TYPE) return;
167 break;
168 case S8:
169 if (fieldType == Byte.TYPE) return;
170 break;
171 case ByteArray:
172 if (fieldType == byte[].class) return;
173 break;
174 default:
175 throw new IllegalArgumentException("Unknown type" + type);
176 }
177 throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
178 + "for annotation type: " + type);
179 }
180
181 private static boolean isStructSubclass(final Class clazz) {
182 return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz;
183 }
184
185 private static int getAnnotationFieldCount(final Class clazz) {
186 int count = 0;
187 for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
188 if (field.isAnnotationPresent(Field.class)) count++;
189 }
190 return count;
191 }
192
193 private static boolean matchModifier(final FieldInfo[] fields, boolean immutable) {
194 for (FieldInfo fi : fields) {
195 if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
196 }
197 return true;
198 }
199
200 private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
201 return !matchModifier(fields, true /* immutable */)
202 && !matchModifier(fields, false /* mutable */);
203 }
204
205 private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
206 final Class[] paramTypes = cons.getParameterTypes();
207 if (paramTypes.length != fields.length) return false;
Xiao Maea531442020-10-10 23:23:42 +0900208 for (int i = 0; i < paramTypes.length; i++) {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900209 if (!paramTypes[i].equals(fields[i].field.getType())) return false;
210 }
211 return true;
212 }
213
214 private static BigInteger readBigInteger(final ByteBuffer buf) {
215 // The magnitude argument of BigInteger constructor is a byte array in big-endian order.
216 // If ByteBuffer is read in little-endian, reverse the order of the bytes is required;
217 // if ByteBuffer is read in big-endian, then just keep it as-is.
218 final byte[] input = new byte[8];
219 for (int i = 0; i < 8; i++) {
220 input[(buf.order() == ByteOrder.LITTLE_ENDIAN ? input.length - 1 - i : i)] = buf.get();
221 }
222 return new BigInteger(1, input);
223 }
224
225 private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
226 throws BufferUnderflowException {
227 final Object value;
228 checkAnnotationType(fieldInfo.annotation.type(), fieldInfo.field.getType());
229 switch (fieldInfo.annotation.type()) {
230 case U8:
231 value = (short) (buf.get() & 0xFF);
232 break;
233 case U16:
234 value = (int) (buf.getShort() & 0xFFFF);
235 break;
236 case U32:
237 value = (long) (buf.getInt() & 0xFFFFFFFFL);
238 break;
239 case U64:
240 if (fieldInfo.field.getType() == BigInteger.class) {
241 value = readBigInteger(buf);
242 } else {
243 value = buf.getLong();
244 }
245 break;
246 case S8:
247 value = buf.get();
248 break;
249 case S16:
250 value = buf.getShort();
251 break;
252 case S32:
253 value = buf.getInt();
254 break;
255 case S64:
256 value = buf.getLong();
257 break;
258 case BE16:
259 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
260 value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
261 } else {
262 value = (int) (buf.getShort() & 0xFFFF);
263 }
264 break;
265 case BE32:
266 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
267 value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
268 } else {
269 value = (long) (buf.getInt() & 0xFFFFFFFFL);
270 }
271 break;
272 case BE64:
273 if (fieldInfo.field.getType() == BigInteger.class) {
274 value = readBigInteger(buf);
275 } else {
276 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
277 value = Long.reverseBytes(buf.getLong());
278 } else {
279 value = buf.getLong();
280 }
281 }
282 break;
283 case ByteArray:
284 final byte[] array = new byte[fieldInfo.annotation.arraysize()];
285 buf.get(array);
286 value = array;
287 break;
288 default:
289 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
290 }
291
292 // Skip the padding data for alignment if any.
293 if (fieldInfo.annotation.padding() > 0) {
294 buf.position(buf.position() + fieldInfo.annotation.padding());
295 }
296 return value;
297 }
298
299 private static FieldInfo[] getClassFieldInfo(final Class clazz) {
Xiao Maea531442020-10-10 23:23:42 +0900300 if (!isStructSubclass(clazz)) {
301 throw new IllegalArgumentException(clazz.getName() + " is not a subclass of "
302 + Struct.class.getName());
303 }
304
Xiao Maa76690a2020-11-04 10:41:04 +0900305 final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz);
306 if (cachedAnnotationFields != null) {
307 return cachedAnnotationFields;
308 }
Xiao Ma6f95cef2020-09-08 19:16:15 +0900309
310 // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
311 // of field appeared in the class, that is a problem when parsing raw data read from
312 // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
Xiao Maa76690a2020-11-04 10:41:04 +0900313 final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
Xiao Ma6f95cef2020-09-08 19:16:15 +0900314 for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
315 if (Modifier.isStatic(field.getModifiers())) continue;
316
317 final Field annotation = field.getAnnotation(Field.class);
318 if (annotation == null) {
319 throw new IllegalArgumentException("Field " + field.getName()
320 + " is missing the " + Field.class.getSimpleName()
321 + " annotation");
322 }
323 if (annotation.order() < 0 || annotation.order() >= annotationFields.length) {
324 throw new IllegalArgumentException("Annotation order: " + annotation.order()
325 + " is negative or non-consecutive");
326 }
327 if (annotationFields[annotation.order()] != null) {
328 throw new IllegalArgumentException("Duplicated annotation order: "
329 + annotation.order());
330 }
331 annotationFields[annotation.order()] = new FieldInfo(annotation, field);
332 }
Xiao Maa76690a2020-11-04 10:41:04 +0900333 sFieldCache.putIfAbsent(clazz, annotationFields);
Xiao Ma6f95cef2020-09-08 19:16:15 +0900334 return annotationFields;
335 }
336
337 /**
338 * Parse raw data from ByteBuffer according to the pre-defined annotation rule and return
339 * the type-variable object which is subclass of Struct class.
340 *
341 * TODO:
342 * 1. Support subclass inheritance.
343 * 2. Introduce annotation processor to enforce the subclass naming schema.
344 */
345 public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) {
346 try {
Xiao Ma6f95cef2020-09-08 19:16:15 +0900347 final FieldInfo[] foundFields = getClassFieldInfo(clazz);
348 if (hasBothMutableAndImmutableFields(foundFields)) {
349 throw new IllegalArgumentException("Class has both immutable and mutable fields");
350 }
351
352 Constructor<?> constructor = null;
353 Constructor<?> defaultConstructor = null;
354 final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
355 for (Constructor cons : constructors) {
356 if (matchConstructor(cons, foundFields)) constructor = cons;
357 if (cons.getParameterTypes().length == 0) defaultConstructor = cons;
358 }
359
360 if (constructor == null && defaultConstructor == null) {
361 throw new IllegalArgumentException("Fail to find available constructor");
362 }
363 if (constructor != null) {
Xiao Maea531442020-10-10 23:23:42 +0900364 final Object[] args = new Object[foundFields.length];
Xiao Ma6f95cef2020-09-08 19:16:15 +0900365 for (int i = 0; i < args.length; i++) {
366 args[i] = getFieldValue(buf, foundFields[i]);
367 }
368 return (T) constructor.newInstance(args);
369 }
370
371 final Object instance = defaultConstructor.newInstance();
372 for (FieldInfo fi : foundFields) {
373 fi.field.set(instance, getFieldValue(buf, fi));
374 }
375 return (T) instance;
376 } catch (IllegalAccessException
377 | InvocationTargetException
378 | InstantiationException e) {
379 throw new IllegalArgumentException("Fail to create a instance from constructor", e);
380 } catch (BufferUnderflowException e) {
381 throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e);
382 }
383 }
384}