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