1 /* 2 * Copyright (C) 2023 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; 18 19 import android.annotation.Nullable; 20 import android.util.Log; 21 22 import com.android.server.wifi.hal.WifiChip; 23 24 import org.json.JSONArray; 25 import org.json.JSONException; 26 import org.json.JSONObject; 27 28 import java.time.Instant; 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * This class stores information about the response of the AFC Server to a query 34 */ 35 public class AfcServerResponse { 36 private static final String TAG = "WifiAfcServerResponse"; 37 static final int SUCCESS_AFC_RESPONSE_CODE = 0; 38 static final int SUCCESS_HTTP_RESPONSE_CODE = 200; 39 40 /* 41 * The value represented by the AFC response code is mapped out as follows, taken from the AFC 42 * server specs: 43 * -1: General Failure 44 * 0: SUCCESS 45 * 100 – 199: General errors related to the protocol 46 * 300 – 399: Error events specific to message exchanges for the Available Spectrum Inquiry 47 */ 48 private int mAfcResponseCode; 49 // HTTP response code is 200 if the request succeeded, otherwise the request failed 50 private int mHttpResponseCode; 51 private String mAfcResponseDescription; 52 private WifiChip.AfcChannelAllowance mAfcChannelAllowance; 53 54 /** 55 * Parses the spectrum inquiry response object received from the AFC server into an 56 * AfcServerResponse object. Returns null if the available spectrum inquiry response is 57 * incorrectly formatted. 58 * 59 * @param httpResponseCode the HTTP response code of the AFC server. 60 * @param spectrumInquiryResponse the available spectrum inquiry response to parse. 61 * @return the parsed response object as an AfcServerResponse, or null if the input JSON is 62 * incorrectly formatted. 63 */ fromSpectrumInquiryResponse(int httpResponseCode, JSONObject spectrumInquiryResponse)64 public static AfcServerResponse fromSpectrumInquiryResponse(int httpResponseCode, 65 JSONObject spectrumInquiryResponse) { 66 AfcServerResponse afcServerResponse = new AfcServerResponse(); 67 68 if (spectrumInquiryResponse == null) { 69 return null; 70 } 71 72 afcServerResponse.setHttpResponseCode(httpResponseCode); 73 afcServerResponse.setAfcResponseCode(parseAfcResponseCode(spectrumInquiryResponse)); 74 afcServerResponse.setAfcResponseDescription(getResponseShortDescriptionFromJSON( 75 spectrumInquiryResponse)); 76 77 // no need to keep parsing if either the AFC or HTTP codes indicate a failed request 78 if (afcServerResponse.mAfcResponseCode != SUCCESS_AFC_RESPONSE_CODE 79 || afcServerResponse.mHttpResponseCode != SUCCESS_HTTP_RESPONSE_CODE) { 80 return afcServerResponse; 81 } 82 83 // parse the available frequencies and channels, and their expiration time 84 try { 85 WifiChip.AfcChannelAllowance afcChannelAllowance = new WifiChip.AfcChannelAllowance(); 86 afcChannelAllowance.availableAfcFrequencyInfos = parseAvailableFrequencyInfo( 87 spectrumInquiryResponse.optJSONArray("availableFrequencyInfo")); 88 afcChannelAllowance.availableAfcChannelInfos = parseAvailableChannelInfo( 89 spectrumInquiryResponse.optJSONArray("availableChannelInfo")); 90 91 // expiry time is required as we know the response was successful 92 String availabilityExpireTimeString = spectrumInquiryResponse.getString( 93 "availabilityExpireTime"); 94 afcChannelAllowance.availabilityExpireTimeMs = 95 convertExpireTimeStringToTimestamp(availabilityExpireTimeString); 96 97 afcServerResponse.setAfcChannelAllowance(afcChannelAllowance); 98 } catch (JSONException | NullPointerException e) { 99 Log.e(TAG, e.toString()); 100 return null; 101 } 102 return afcServerResponse; 103 } 104 105 /** 106 * Parses the available frequencies of an AfcServerResponse. 107 */ parseAvailableFrequencyInfo( JSONArray availableFrequencyInfoJSON)108 private static List<WifiChip.AvailableAfcFrequencyInfo> parseAvailableFrequencyInfo( 109 JSONArray availableFrequencyInfoJSON) { 110 if (availableFrequencyInfoJSON != null) { 111 List<WifiChip.AvailableAfcFrequencyInfo> availableFrequencyInfo = new ArrayList<>(); 112 113 try { 114 for (int i = 0; i < availableFrequencyInfoJSON.length(); ++i) { 115 JSONObject frequencyRange = availableFrequencyInfoJSON.getJSONObject(i) 116 .getJSONObject("frequencyRange"); 117 118 WifiChip.AvailableAfcFrequencyInfo availableFrequency = new WifiChip 119 .AvailableAfcFrequencyInfo(); 120 availableFrequency.startFrequencyMhz = 121 frequencyRange.getInt("lowFrequency"); 122 availableFrequency.endFrequencyMhz = 123 frequencyRange.getInt("highFrequency"); 124 availableFrequency.maxPsdDbmPerMhz = 125 availableFrequencyInfoJSON.getJSONObject(i).getInt("maxPsd"); 126 127 availableFrequencyInfo.add(availableFrequency); 128 } 129 return availableFrequencyInfo; 130 } catch (JSONException | NullPointerException e) { 131 Log.e(TAG, "Error occurred when parsing available AFC frequency info."); 132 } 133 } 134 return null; 135 } 136 137 /** 138 * Parses the available channels of an AfcServerResponse. 139 */ parseAvailableChannelInfo( JSONArray availableChannelInfoJSON)140 private static List<WifiChip.AvailableAfcChannelInfo> parseAvailableChannelInfo( 141 JSONArray availableChannelInfoJSON) { 142 if (availableChannelInfoJSON != null) { 143 List<WifiChip.AvailableAfcChannelInfo> availableChannelInfo = new ArrayList<>(); 144 145 try { 146 for (int i = 0; i < availableChannelInfoJSON.length(); ++i) { 147 int globalOperatingClass = availableChannelInfoJSON 148 .getJSONObject(i).getInt("globalOperatingClass"); 149 150 for (int j = 0; j < availableChannelInfoJSON.getJSONObject(i).getJSONArray( 151 "channelCfi").length(); ++j) { 152 WifiChip.AvailableAfcChannelInfo availableChannel = new WifiChip 153 .AvailableAfcChannelInfo(); 154 155 availableChannel.globalOperatingClass = globalOperatingClass; 156 availableChannel.channelCfi = availableChannelInfoJSON.getJSONObject(i) 157 .getJSONArray("channelCfi").getInt(j); 158 availableChannel.maxEirpDbm = availableChannelInfoJSON.getJSONObject(i) 159 .getJSONArray("maxEirp").getInt(j); 160 availableChannelInfo.add(availableChannel); 161 } 162 } 163 return availableChannelInfo; 164 } catch (JSONException | NullPointerException e) { 165 Log.e(TAG, "Error occurred when parsing available AFC channel info."); 166 } 167 } 168 return null; 169 } 170 171 /** 172 * Converts a date string in the format: YYYY-MM-DDThh:mm:ssZ to a Unix timestamp. 173 * 174 * @param availabilityExpireTime the expiration time of the AFC frequency and channel 175 * availability. 176 * @return the expiration time as a Unix timestamp in milliseconds. 177 */ convertExpireTimeStringToTimestamp(String availabilityExpireTime)178 static long convertExpireTimeStringToTimestamp(String availabilityExpireTime) { 179 // Sometimes the expiration time is incorrectly formatted and includes appended 180 // milliseconds after the seconds section, so we want to remove these before converting to 181 // a timestamp 182 if (availabilityExpireTime.length() > 20) { 183 availabilityExpireTime = availabilityExpireTime.substring(0, 19) + "Z"; 184 } 185 186 return Instant.parse(availabilityExpireTime).toEpochMilli(); 187 } 188 189 /** 190 * Returns the AFC response code for an available spectrum inquiry response or Integer.MIN_VALUE 191 * if it does not exist. 192 */ parseAfcResponseCode(JSONObject spectrumInquiryResponse)193 static int parseAfcResponseCode(JSONObject spectrumInquiryResponse) { 194 try { 195 // parse the response code and return it. Throws an error if a field does not exist. 196 int responseCode = spectrumInquiryResponse.getJSONObject("response") 197 .getInt("responseCode"); 198 199 return responseCode; 200 } catch (JSONException | NullPointerException e) { 201 Log.e(TAG, "The available spectrum inquiry response does not contain a response " 202 + "code field."); 203 204 // Currently the internal Google AFC server doesn't provide an AFC response code of 0 205 // for successful queries. 206 // TODO (b/294594249): If the server is modified to correctly provide an AFC response 207 // code of 0 upon a successful query, handle this case accordingly. 208 return 0; 209 } 210 } 211 212 /** 213 * Returns the short description of an available spectrum inquiry response, or an empty string 214 * if it does not exist. 215 */ getResponseShortDescriptionFromJSON(JSONObject spectrumInquiryResponse)216 static String getResponseShortDescriptionFromJSON(JSONObject spectrumInquiryResponse) { 217 try { 218 // parse and return the response description. Throws an error if a field does not exist. 219 return spectrumInquiryResponse.getJSONObject("response").getString("shortDescription"); 220 } catch (JSONException | NullPointerException e) { 221 return ""; 222 } 223 } 224 225 /** 226 * Sets the AFC response code associated with an available spectrum inquiry response. 227 */ setAfcResponseCode(int afcResponseCode)228 public void setAfcResponseCode(int afcResponseCode) { 229 mAfcResponseCode = afcResponseCode; 230 } 231 232 /** 233 * Sets the description associated with an available spectrum inquiry response. 234 */ setAfcResponseDescription(String afcResponseDescription)235 public void setAfcResponseDescription(String afcResponseDescription) { 236 mAfcResponseDescription = afcResponseDescription; 237 } 238 239 /** 240 * Sets the AFC channel and frequency allowance. 241 */ setAfcChannelAllowance(WifiChip.AfcChannelAllowance afcChannelAllowance)242 public void setAfcChannelAllowance(WifiChip.AfcChannelAllowance afcChannelAllowance) { 243 mAfcChannelAllowance = afcChannelAllowance; 244 } 245 246 /** 247 * Sets the AFC server HTTP response code. 248 */ setHttpResponseCode(int httpResponseCode)249 public void setHttpResponseCode(int httpResponseCode) { 250 mHttpResponseCode = httpResponseCode; 251 } 252 253 /** 254 * Returns the AFC response code of the available spectrum inquiry response. 255 */ getAfcResponseCode()256 public int getAfcResponseCode() { 257 return mAfcResponseCode; 258 } 259 260 /** 261 * Returns the HTTP response code of the AFC server's response. 262 */ getHttpResponseCode()263 public int getHttpResponseCode() { 264 return mHttpResponseCode; 265 } 266 267 /** 268 * Returns the AFC response description. 269 */ getAfcResponseDescription()270 public String getAfcResponseDescription() { 271 return mAfcResponseDescription; 272 } 273 274 /** 275 * Returns the available channels and frequencies received from the AFC query. 276 */ 277 @Nullable getAfcChannelAllowance()278 public WifiChip.AfcChannelAllowance getAfcChannelAllowance() { 279 return mAfcChannelAllowance; 280 } 281 282 @Override toString()283 public String toString() { 284 StringBuilder sbuf = new StringBuilder(); 285 286 if (mAfcChannelAllowance != null) { 287 // print frequency info 288 if (mAfcChannelAllowance.availableAfcFrequencyInfos != null) { 289 sbuf.append("Available frequencies:\n"); 290 291 for (WifiChip.AvailableAfcFrequencyInfo frequency : 292 mAfcChannelAllowance.availableAfcFrequencyInfos) { 293 sbuf.append(" [" + frequency.startFrequencyMhz + ", " 294 + frequency.endFrequencyMhz + "], maxPsd: " + frequency.maxPsdDbmPerMhz 295 + "dBm per MHZ\n"); 296 } 297 } 298 299 // print channel info 300 if (mAfcChannelAllowance.availableAfcChannelInfos != null) { 301 sbuf.append("Available channels:\n"); 302 303 for (WifiChip.AvailableAfcChannelInfo channel : 304 mAfcChannelAllowance.availableAfcChannelInfos) { 305 sbuf.append(" Global operating class: " + channel.globalOperatingClass 306 + ", Cfi: " + channel.channelCfi + " maxEirp: " + channel.maxEirpDbm 307 + "dBm\n"); 308 } 309 } 310 311 sbuf.append("Availability expiration time (ms): " 312 + mAfcChannelAllowance.availabilityExpireTimeMs + "\n"); 313 } 314 315 sbuf.append("HTTP response code: " + mHttpResponseCode + "\n"); 316 sbuf.append("AFC response code: " + mAfcResponseCode + "\n"); 317 sbuf.append("AFC response short description: " + mAfcResponseDescription + "\n"); 318 return sbuf.toString(); 319 } 320 } 321