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