• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.wifi.nl80211;
18 
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.util.Log;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.net.module.util.netlink.StructNlAttr;
26 import com.android.net.module.util.netlink.StructNlMsgHdr;
27 
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38 
39 /**
40  * Representation of a generic Netlink message. Consists of a Netlink header,
41  * generic Netlink header, and an optional list of attributes.
42  */
43 public class GenericNetlinkMsg {
44     private static final String TAG = "GenericNetlinkMsg";
45     public static final int MIN_STRUCT_SIZE =
46             StructNlMsgHdr.STRUCT_SIZE + StructGenNlMsgHdr.STRUCT_SIZE;
47     private static final int SHORT_ATTRIBUTE_SIZE = StructNlAttr.NLA_HEADERLEN + Short.BYTES;
48 
49     public final StructNlMsgHdr nlHeader;
50     public final StructGenNlMsgHdr genNlHeader;
51     public final Map<Short, StructNlAttr> attributes;
52 
GenericNetlinkMsg(short command, short type, short flags, int sequence)53     public GenericNetlinkMsg(short command, short type, short flags, int sequence) {
54         attributes = new HashMap<>();
55         genNlHeader = new StructGenNlMsgHdr(command);
56         nlHeader = new StructNlMsgHdr();
57         nlHeader.nlmsg_len = MIN_STRUCT_SIZE;
58         nlHeader.nlmsg_pid = 0; // set to 0 when communicating with the kernel
59         nlHeader.nlmsg_flags = flags;
60         nlHeader.nlmsg_type = type;
61         nlHeader.nlmsg_seq = sequence;
62     }
63 
GenericNetlinkMsg(StructNlMsgHdr nlHeader, StructGenNlMsgHdr genNlHeader, Map<Short, StructNlAttr> attributes)64     private GenericNetlinkMsg(StructNlMsgHdr nlHeader, StructGenNlMsgHdr genNlHeader,
65             Map<Short, StructNlAttr> attributes) {
66         this.nlHeader = nlHeader;
67         this.genNlHeader = genNlHeader;
68         this.attributes = attributes;
69     }
70 
71     /**
72      * Add a new attribute to this message.
73      */
addAttribute(@onNull StructNlAttr attribute)74     public void addAttribute(@NonNull StructNlAttr attribute) {
75         if (attribute == null) {
76             return;
77         }
78         short attributeId = attribute.nla_type;
79         if (attributes.containsKey(attributeId)) {
80             // Recalculate the total size if this attribute is being replaced
81             StructNlAttr oldAttribute = attributes.get(attributeId);
82             nlHeader.nlmsg_len -= oldAttribute.getAlignedLength();
83         }
84         nlHeader.nlmsg_len += attribute.getAlignedLength();
85         attributes.put(attributeId, attribute);
86     }
87 
88     /**
89      * Retrieve an existing attribute from this message.
90      *
91      * @return Attribute if it exists, null otherwise.
92      */
93     @Nullable
getAttribute(short attributeId)94     public StructNlAttr getAttribute(short attributeId) {
95         return attributes.get(attributeId);
96     }
97 
98     /**
99      * Retrieve the value of a short attribute, if it exists.
100      *
101      * @param attributeId of the attribute to retrieve
102      * @return value if it exists, or null if an error was encountered
103      */
getAttributeValueAsShort(short attributeId)104     public Short getAttributeValueAsShort(short attributeId) {
105         StructNlAttr attribute = getAttribute(attributeId);
106         if (attribute == null || attribute.nla_len != SHORT_ATTRIBUTE_SIZE) return null;
107         // StructNlAttr does not support retrieving shorts directly
108         ByteBuffer buffer = attribute.getValueAsByteBuffer();
109         return buffer.getShort();
110     }
111 
112     /**
113      * Retrieve the inner attributes from a nested attribute.
114      *
115      * @param outerAttribute containing nested inner attributes
116      * @return Inner attributes if valid, or null otherwise.
117      *         Attributes will be represented as an (attributeId -> attribute) map.
118      */
getInnerNestedAttributes( @onNull StructNlAttr outerAttribute)119     public static @Nullable Map<Short, StructNlAttr> getInnerNestedAttributes(
120             @NonNull StructNlAttr outerAttribute) {
121         if (outerAttribute == null) return null;
122         // Outer attribute's payload is a byte buffer containing additional attributes
123         ByteBuffer innerBytes = outerAttribute.getValueAsByteBuffer();
124         if (innerBytes == null) return null;
125         return parseAttributesToMap(innerBytes, innerBytes.remaining());
126     }
127 
128     /**
129      * Check that this message contains the expected fields.
130      */
verifyFields(short command, short... attributeIds)131     public boolean verifyFields(short command, short... attributeIds) {
132         if (command != genNlHeader.command) {
133             Log.e(TAG, "Found unexpected command. expected=" + command
134                     + ", actual=" + genNlHeader.command);
135             return false;
136         }
137         for (short attributeId : attributeIds) {
138             if (!attributes.containsKey(attributeId)) {
139                 Log.e(TAG, "Message does not contain any attribute with id=" + attributeId);
140                 return false;
141             }
142         }
143         return true;
144     }
145 
146     /**
147      * Write this StructNl80211Msg to a new byte array.
148      */
toByteArray()149     public byte[] toByteArray() {
150         byte[] bytes = new byte[nlHeader.nlmsg_len];
151         ByteBuffer buffer = ByteBuffer.wrap(bytes);
152         this.pack(buffer);
153         return bytes;
154     }
155 
156     /**
157      * Write this GenericNetlinkMsg to a ByteBuffer.
158      */
pack(@onNull ByteBuffer byteBuffer)159     public void pack(@NonNull ByteBuffer byteBuffer) {
160         if (byteBuffer == null || byteBuffer.remaining() < nlHeader.nlmsg_len) {
161             return;
162         }
163         // Nl80211 expects the message to be in native byte order
164         ByteOrder originalByteOrder = byteBuffer.order();
165         byteBuffer.order(ByteOrder.nativeOrder());
166 
167         nlHeader.pack(byteBuffer);
168         genNlHeader.pack(byteBuffer);
169 
170         for (StructNlAttr attribute : attributes.values()) {
171             attribute.pack(byteBuffer);
172         }
173         byteBuffer.order(originalByteOrder);
174     }
175 
176     /**
177      * Parse attributes from a ByteBuffer to an (attributeId -> attribute) map.
178      *
179      * @param byteBuffer containing the attributes
180      * @param expectedSize of all the attributes
181      * @return map containing the parsed attributes, or null if an error occurred
182      */
183     @VisibleForTesting
parseAttributesToMap( @onNull ByteBuffer byteBuffer, int expectedSize)184     protected static @Nullable Map<Short, StructNlAttr> parseAttributesToMap(
185             @NonNull ByteBuffer byteBuffer, int expectedSize) {
186         if (byteBuffer == null) return null;
187         Map<Short, StructNlAttr> attributes = new HashMap<>();
188         int remainingSize = expectedSize;
189 
190         while (remainingSize >= StructNlAttr.NLA_HEADERLEN) {
191             StructNlAttr attribute = StructNlAttr.parse(byteBuffer);
192             if (attribute == null) {
193                 Log.e(TAG, "Unable to parse attribute. bufRemaining=" + byteBuffer.remaining());
194                 return null;
195             }
196             remainingSize -= attribute.getAlignedLength();
197             if (remainingSize < 0) {
198                 Log.e(TAG, "Attribute is larger than the remaining size. attributeSize="
199                         + attribute.getAlignedLength() + ", remainingSize=" + remainingSize);
200                 return null;
201             }
202             attributes.put(attribute.nla_type, attribute);
203         }
204 
205         if (remainingSize != 0) {
206             Log.e(TAG, "Expected size does not match the parsed size. expected=" + expectedSize
207                     + ", remaining=" + remainingSize);
208             return null;
209         }
210         return attributes;
211     }
212 
213     /**
214      * Read a GenericNetlinkMsg from a ByteBuffer.
215      *
216      * @return Parsed GenericNetlinkMsg object, or null if an error occurred.
217      */
218     @Nullable
parse(@onNull ByteBuffer byteBuffer)219     public static GenericNetlinkMsg parse(@NonNull ByteBuffer byteBuffer) {
220         if (byteBuffer == null || byteBuffer.remaining() < MIN_STRUCT_SIZE) {
221             Log.e(TAG, "Invalid byte buffer received");
222             return null;
223         }
224 
225         ByteOrder originalByteOrder = byteBuffer.order();
226         byteBuffer.order(ByteOrder.nativeOrder());
227         try {
228             StructNlMsgHdr nlHeader = StructNlMsgHdr.parse(byteBuffer);
229             StructGenNlMsgHdr genNlHeader = StructGenNlMsgHdr.parse(byteBuffer);
230             if (nlHeader == null || genNlHeader == null) {
231                 Log.e(TAG, "Unable to parse message headers");
232                 return null;
233             }
234 
235             int remainingSize = nlHeader.nlmsg_len - MIN_STRUCT_SIZE;
236             if (byteBuffer.remaining() < remainingSize) {
237                 Log.e(TAG, "Byte buffer is smaller than the expected message size");
238                 return null;
239             }
240             Map<Short, StructNlAttr> attributes = parseAttributesToMap(byteBuffer, remainingSize);
241             if (attributes == null) return null;
242             return new GenericNetlinkMsg(nlHeader, genNlHeader, attributes);
243         } finally {
244             byteBuffer.order(originalByteOrder);
245         }
246     }
247 
attributesAreEqual(Map<Short, StructNlAttr> attributes, Map<Short, StructNlAttr> otherAttributes)248     private static boolean attributesAreEqual(Map<Short, StructNlAttr> attributes,
249             Map<Short, StructNlAttr> otherAttributes) {
250         if (attributes.size() != otherAttributes.size()) return false;
251         Set<Short> attributeIds = attributes.keySet();
252         Set<Short> otherAttributeIds = otherAttributes.keySet();
253         if (!attributeIds.containsAll(otherAttributeIds)) return false;
254 
255         for (short attributeId : attributeIds) {
256             StructNlAttr attribute = attributes.get(attributeId);
257             StructNlAttr otherAttribute = otherAttributes.get(attributeId);
258             if (!Arrays.equals(attribute.nla_value, otherAttribute.nla_value)) {
259                 return false;
260             }
261         }
262         return true;
263     }
264 
265     @Override
equals(Object o)266     public boolean equals(Object o) {
267         if (o == this) return true;
268         if (o == null || !(o instanceof GenericNetlinkMsg)) return false;
269         GenericNetlinkMsg other = (GenericNetlinkMsg) o;
270         return this.nlHeader.nlmsg_len == other.nlHeader.nlmsg_len
271                 && this.nlHeader.nlmsg_flags == other.nlHeader.nlmsg_flags
272                 && this.nlHeader.nlmsg_pid == other.nlHeader.nlmsg_pid
273                 && this.nlHeader.nlmsg_seq == other.nlHeader.nlmsg_seq
274                 && this.nlHeader.nlmsg_type == other.nlHeader.nlmsg_type
275                 && this.genNlHeader.equals(other.genNlHeader)
276                 && attributesAreEqual(this.attributes, other.attributes);
277     }
278 
279     @Override
hashCode()280     public int hashCode() {
281         int hash = Objects.hash(nlHeader.nlmsg_len, nlHeader.nlmsg_flags, nlHeader.nlmsg_pid,
282                 nlHeader.nlmsg_seq, nlHeader.nlmsg_type, genNlHeader.hashCode());
283         // Sort attributes to guarantee hashing order
284         List<Short> sortedAttributeIds = new ArrayList<>(attributes.keySet());
285         Collections.sort(sortedAttributeIds);
286         for (short attributeId : sortedAttributeIds) {
287             StructNlAttr attribute = attributes.get(attributeId);
288             hash = Objects.hash(hash, attribute.nla_type, Arrays.hashCode(attribute.nla_value));
289         }
290         return hash;
291     }
292 
293     @Override
toString()294     public String toString() {
295         return "GenericNetlinkMsg{ "
296                 + "nlHeader{" + nlHeader + "}, "
297                 + "genNlHeader{" + genNlHeader + "}, "
298                 + "attributes{" + attributes.values() + "} "
299                 + "}";
300     }
301 }
302