1 /*
2  * Copyright (C) 2018 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 android.net.wifi.rtt;
18 
19 import android.annotation.Nullable;
20 import android.location.Address;
21 import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.os.Parcelable.Creator;
25 import android.util.SparseArray;
26 
27 import java.nio.charset.StandardCharsets;
28 import java.util.Arrays;
29 import java.util.Locale;
30 import java.util.Objects;
31 
32 /**
33  * Decodes the Type Length Value (TLV) elements found in a Location Civic Record as defined by IEEE
34  * P802.11-REVmc/D8.0 section 9.4.2.22.13 using the format described in IETF RFC 4776.
35  *
36  * <p>The TLVs each define a key, value pair for a civic address type such as apt, street, city,
37  * county, and country. The class provides a general getter method to extract a value for an element
38  * key, returning null if not set.
39  *
40  * @hide
41  */
42 public final class CivicLocation implements Parcelable {
43     // Address (class) line indexes
44     private static final int ADDRESS_LINE_0_ROOM_DESK_FLOOR = 0;
45     private static final int ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT = 1;
46     private static final int ADDRESS_LINE_2_CITY = 2;
47     private static final int ADDRESS_LINE_3_STATE_POSTAL_CODE = 3;
48     private static final int ADDRESS_LINE_4_COUNTRY = 4;
49 
50     // Buffer management
51     private static final int MIN_CIVIC_BUFFER_SIZE = 3;
52     private static final int MAX_CIVIC_BUFFER_SIZE = 256;
53     private static final int COUNTRY_CODE_LENGTH = 2;
54     private static final int BYTE_MASK = 0xFF;
55     private static final int TLV_TYPE_INDEX = 0;
56     private static final int TLV_LENGTH_INDEX = 1;
57     private static final int TLV_VALUE_INDEX = 2;
58 
59     private final boolean mIsValid;
60     private final String mCountryCode; // Two character country code (ISO 3166 standard).
61     private SparseArray<String> mCivicAddressElements =
62             new SparseArray<>(MIN_CIVIC_BUFFER_SIZE);
63 
64 
65     /**
66      * Constructor
67      *
68      * @param civicTLVs    a byte buffer containing parameters in the form type, length, value
69      * @param countryCode the two letter code defined by the ISO 3166 standard
70      *
71      * @hide
72      */
CivicLocation(@ullable byte[] civicTLVs, @Nullable String countryCode)73     public CivicLocation(@Nullable byte[] civicTLVs, @Nullable String countryCode) {
74         this.mCountryCode = countryCode;
75         if (countryCode == null || countryCode.length() != COUNTRY_CODE_LENGTH) {
76             this.mIsValid = false;
77             return;
78         }
79         boolean isValid = false;
80         if (civicTLVs != null
81                 && civicTLVs.length >= MIN_CIVIC_BUFFER_SIZE
82                 && civicTLVs.length < MAX_CIVIC_BUFFER_SIZE) {
83             isValid = parseCivicTLVs(civicTLVs);
84         }
85 
86         mIsValid = isValid;
87     }
88 
CivicLocation(Parcel in)89     private CivicLocation(Parcel in) {
90         mIsValid = in.readByte() != 0;
91         mCountryCode = in.readString();
92         mCivicAddressElements = in.readSparseArray(this.getClass().getClassLoader());
93     }
94 
95     public static final @android.annotation.NonNull Creator<CivicLocation> CREATOR = new Creator<CivicLocation>() {
96         @Override
97         public CivicLocation createFromParcel(Parcel in) {
98             return new CivicLocation(in);
99         }
100 
101         @Override
102         public CivicLocation[] newArray(int size) {
103             return new CivicLocation[size];
104         }
105     };
106 
107     @Override
describeContents()108     public int describeContents() {
109         return 0;
110     }
111 
112     @Override
writeToParcel(Parcel parcel, int flags)113     public void writeToParcel(Parcel parcel, int flags) {
114         parcel.writeByte((byte) (mIsValid ? 1 : 0));
115         parcel.writeString(mCountryCode);
116         parcel.writeSparseArray((android.util.SparseArray) mCivicAddressElements);
117     }
118 
119     /**
120      * Check TLV format and store TLV key/value pairs in this object so they can be queried by key.
121      *
122      * @param civicTLVs the buffer of TLV elements
123      * @return a boolean indicating success of the parsing process
124      */
parseCivicTLVs(byte[] civicTLVs)125     private boolean parseCivicTLVs(byte[] civicTLVs) {
126         int bufferPtr = 0;
127         int bufferLength = civicTLVs.length;
128 
129         // Iterate through the sub-elements contained in the LCI IE checking the accumulated
130         // element lengths do not overflow the total buffer length
131         while (bufferPtr < bufferLength) {
132             int civicAddressType = civicTLVs[bufferPtr + TLV_TYPE_INDEX] & BYTE_MASK;
133             int civicAddressTypeLength = civicTLVs[bufferPtr + TLV_LENGTH_INDEX];
134             if (civicAddressTypeLength != 0) {
135                 if (bufferPtr + TLV_VALUE_INDEX + civicAddressTypeLength > bufferLength) {
136                     return false;
137                 }
138                 mCivicAddressElements.put(civicAddressType,
139                         new String(civicTLVs, bufferPtr + TLV_VALUE_INDEX,
140                                 civicAddressTypeLength, StandardCharsets.UTF_8));
141             }
142             bufferPtr += civicAddressTypeLength + TLV_VALUE_INDEX;
143         }
144         return true;
145     }
146 
147     /**
148      * Getter for the value of a civic Address element type.
149      *
150      * @param key an integer code for the element type key
151      * @return the string value associated with that element type
152      */
153     @Nullable
getCivicElementValue(@ivicLocationKeysType int key)154     public String getCivicElementValue(@CivicLocationKeysType int key) {
155         return mCivicAddressElements.get(key);
156     }
157 
158     /**
159      * Converts a CivicLocation object to a SparseArray.
160      *
161      * @return the SparseArray<string> representation of the CivicLocation
162      */
163     @Nullable
toSparseArray()164     public SparseArray<String> toSparseArray() {
165         return mCivicAddressElements;
166     }
167 
168     /**
169      * Generates a comma separated string of all the defined elements.
170      *
171      * @return a compiled string representing all elements
172      */
173     @Override
toString()174     public String toString() {
175         return mCivicAddressElements.toString();
176     }
177 
178     /**
179      * Converts Civic Location to the best effort Address Object.
180      *
181      * @return the {@link Address} object based on the Civic Location data
182      */
183     @Nullable
toAddress()184     public Address toAddress() {
185         if (!mIsValid) {
186             return null;
187         }
188         Address address = new Address(Locale.US);
189         String room = formatAddressElement("Room: ", getCivicElementValue(CivicLocationKeys.ROOM));
190         String desk =
191                 formatAddressElement(" Desk: ", getCivicElementValue(CivicLocationKeys.DESK));
192         String floor =
193                 formatAddressElement(", Flr: ", getCivicElementValue(CivicLocationKeys.FLOOR));
194         String houseNumber = formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNO));
195         String houseNumberSuffix =
196                 formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNS));
197         String road =
198                 formatAddressElement(" ", getCivicElementValue(
199                         CivicLocationKeys.PRIMARY_ROAD_NAME));
200         String roadSuffix = formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.STS));
201         String apt = formatAddressElement(", Apt: ", getCivicElementValue(CivicLocationKeys.APT));
202         String city = formatAddressElement("", getCivicElementValue(CivicLocationKeys.CITY));
203         String state = formatAddressElement("", getCivicElementValue(CivicLocationKeys.STATE));
204         String postalCode =
205                 formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.POSTAL_CODE));
206 
207         // Aggregation into common address format
208         String addressLine0 =
209                 new StringBuilder().append(room).append(desk).append(floor).toString();
210         String addressLine1 =
211                 new StringBuilder().append(houseNumber).append(houseNumberSuffix).append(road)
212                         .append(roadSuffix).append(apt).toString();
213         String addressLine2 = city;
214         String addressLine3 = new StringBuilder().append(state).append(postalCode).toString();
215         String addressLine4 = mCountryCode;
216 
217         // Setting Address object line fields by common convention.
218         address.setAddressLine(ADDRESS_LINE_0_ROOM_DESK_FLOOR, addressLine0);
219         address.setAddressLine(ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT, addressLine1);
220         address.setAddressLine(ADDRESS_LINE_2_CITY, addressLine2);
221         address.setAddressLine(ADDRESS_LINE_3_STATE_POSTAL_CODE, addressLine3);
222         address.setAddressLine(ADDRESS_LINE_4_COUNTRY, addressLine4);
223 
224         // Other compatible fields between the CIVIC_ADDRESS and the Address Class.
225         address.setFeatureName(getCivicElementValue(CivicLocationKeys.NAM)); // Structure name
226         address.setSubThoroughfare(getCivicElementValue(CivicLocationKeys.HNO));
227         address.setThoroughfare(getCivicElementValue(CivicLocationKeys.PRIMARY_ROAD_NAME));
228         address.setSubLocality(getCivicElementValue(CivicLocationKeys.NEIGHBORHOOD));
229         address.setSubAdminArea(getCivicElementValue(CivicLocationKeys.COUNTY));
230         address.setAdminArea(getCivicElementValue(CivicLocationKeys.STATE));
231         address.setPostalCode(getCivicElementValue(CivicLocationKeys.POSTAL_CODE));
232         address.setCountryCode(mCountryCode); // Country
233         return address;
234     }
235 
236     /**
237      * Prepares an address element so that it can be integrated into an address line convention.
238      *
239      * <p>If an address element is null, the return string will be empty e.g. "".
240      *
241      * @param label a string defining the type of address element
242      * @param value a string defining the elements value
243      * @return the formatted version of the value, with null values converted to empty strings
244      */
formatAddressElement(String label, String value)245     private String formatAddressElement(String label, String value) {
246         if (value != null) {
247             return label + value;
248         } else {
249             return "";
250         }
251     }
252 
253     @Override
equals(Object obj)254     public boolean equals(Object obj) {
255         if (this == obj) {
256             return true;
257         }
258         if (!(obj instanceof CivicLocation)) {
259             return false;
260         }
261         CivicLocation other = (CivicLocation) obj;
262         return mIsValid == other.mIsValid
263                 && Objects.equals(mCountryCode, other.mCountryCode)
264                 && isSparseArrayStringEqual(mCivicAddressElements, other.mCivicAddressElements);
265     }
266 
267     @Override
hashCode()268     public int hashCode() {
269         int[] civicAddressKeys = getSparseArrayKeys(mCivicAddressElements);
270         String[] civicAddressValues = getSparseArrayValues(mCivicAddressElements);
271         return Objects.hash(mIsValid, mCountryCode, Arrays.hashCode(civicAddressKeys),
272                 Arrays.hashCode(civicAddressValues));
273     }
274 
275     /**
276      * Tests if the Civic Location object is valid
277      *
278      * @return a boolean defining mIsValid
279      */
isValid()280     public boolean isValid() {
281         return mIsValid;
282     }
283 
284     /**
285      * Tests if two sparse arrays are equal on a key for key basis
286      *
287      * @param sa1 the first sparse array
288      * @param sa2 the second sparse array
289      * @return the boolean result after comparing values key by key
290      */
isSparseArrayStringEqual(SparseArray<String> sa1, SparseArray<String> sa2)291     private boolean isSparseArrayStringEqual(SparseArray<String> sa1, SparseArray<String> sa2) {
292         int size = sa1.size();
293         if (size != sa2.size()) {
294             return false;
295         }
296         for (int i = 0; i < size; i++) {
297             String sa1Value = sa1.valueAt(i);
298             String sa2Value = sa2.valueAt(i);
299             if (!sa1Value.equals(sa2Value)) {
300                 return false;
301             }
302         }
303         return true;
304     }
305 
306     /**
307      * Extract an array of all the keys in a SparseArray<String>
308      *
309      * @param sa the sparse array of Strings
310      * @return an integer array of all keys in the SparseArray<String>
311      */
getSparseArrayKeys(SparseArray<String> sa)312     private int[] getSparseArrayKeys(SparseArray<String> sa) {
313         int size = sa.size();
314         int[] keys = new int[size];
315         for (int i = 0; i < size; i++) {
316             keys[i] = sa.keyAt(i);
317         }
318         return keys;
319     }
320 
321     /**
322      * Extract an array of all the String values in a SparseArray<String>
323      *
324      * @param sa the sparse array of Strings
325      * @return a String array of all values in the SparseArray<String>
326      */
getSparseArrayValues(SparseArray<String> sa)327     private String[] getSparseArrayValues(SparseArray<String> sa) {
328         int size = sa.size();
329         String[] values = new String[size];
330         for (int i = 0; i < size; i++) {
331             values[i] = sa.valueAt(i);
332         }
333         return values;
334     }
335 }
336