Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 1 | /* -*- Mode: Java; tab-width: 4 -*- |
| 2 | * |
| 3 | * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 17 | To do: |
| 18 | - implement remove() |
| 19 | - fix set() to replace existing values |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 20 | */ |
| 21 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 22 | package android.net.nsd; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 23 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 24 | import android.os.Parcelable; |
| 25 | import android.os.Parcel; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 26 | |
Irfan Sheriff | 21ba815 | 2012-04-04 16:22:21 -0700 | [diff] [blame] | 27 | import java.util.Arrays; |
| 28 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 29 | /** |
| 30 | * This class handles TXT record data for DNS based service discovery as specified at |
| 31 | * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11 |
| 32 | * |
| 33 | * DNS-SD specifies that a TXT record corresponding to an SRV record consist of |
| 34 | * a packed array of bytes, each preceded by a length byte. Each string |
| 35 | * is an attribute-value pair. |
| 36 | * |
| 37 | * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it |
| 38 | * as need be to implement its various methods. |
Irfan Sheriff | 527ba07 | 2012-05-09 14:15:04 -0700 | [diff] [blame] | 39 | * @hide |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 40 | * |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 41 | */ |
| 42 | public class DnsSdTxtRecord implements Parcelable { |
| 43 | private static final byte mSeperator = '='; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 44 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 45 | private byte[] mData; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 46 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 47 | /** Constructs a new, empty TXT record. */ |
| 48 | public DnsSdTxtRecord() { |
| 49 | mData = new byte[0]; |
| 50 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 51 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 52 | /** Constructs a new TXT record from a byte array in the standard format. */ |
| 53 | public DnsSdTxtRecord(byte[] data) { |
| 54 | mData = (byte[]) data.clone(); |
| 55 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 56 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 57 | /** Copy constructor */ |
| 58 | public DnsSdTxtRecord(DnsSdTxtRecord src) { |
| 59 | if (src != null && src.mData != null) { |
| 60 | mData = (byte[]) src.mData.clone(); |
| 61 | } |
| 62 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 63 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 64 | /** |
| 65 | * Set a key/value pair. Setting an existing key will replace its value. |
| 66 | * @param key Must be ascii with no '=' |
| 67 | * @param value matching value to key |
| 68 | */ |
| 69 | public void set(String key, String value) { |
| 70 | byte[] keyBytes; |
| 71 | byte[] valBytes; |
| 72 | int valLen; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 73 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 74 | if (value != null) { |
| 75 | valBytes = value.getBytes(); |
| 76 | valLen = valBytes.length; |
| 77 | } else { |
| 78 | valBytes = null; |
| 79 | valLen = 0; |
| 80 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 81 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 82 | try { |
| 83 | keyBytes = key.getBytes("US-ASCII"); |
| 84 | } |
| 85 | catch (java.io.UnsupportedEncodingException e) { |
| 86 | throw new IllegalArgumentException("key should be US-ASCII"); |
| 87 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 88 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 89 | for (int i = 0; i < keyBytes.length; i++) { |
| 90 | if (keyBytes[i] == '=') { |
| 91 | throw new IllegalArgumentException("= is not a valid character in key"); |
| 92 | } |
| 93 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 94 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 95 | if (keyBytes.length + valLen >= 255) { |
| 96 | throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes"); |
| 97 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 98 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 99 | int currentLoc = remove(key); |
| 100 | if (currentLoc == -1) |
| 101 | currentLoc = keyCount(); |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 102 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 103 | insert(keyBytes, valBytes, currentLoc); |
| 104 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 105 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 106 | /** |
| 107 | * Get a value for a key |
| 108 | * |
| 109 | * @param key |
| 110 | * @return The value associated with the key |
| 111 | */ |
| 112 | public String get(String key) { |
| 113 | byte[] val = this.getValue(key); |
| 114 | return val != null ? new String(val) : null; |
| 115 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 116 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 117 | /** Remove a key/value pair. If found, returns the index or -1 if not found */ |
| 118 | public int remove(String key) { |
| 119 | int avStart = 0; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 120 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 121 | for (int i=0; avStart < mData.length; i++) { |
| 122 | int avLen = mData[avStart]; |
| 123 | if (key.length() <= avLen && |
| 124 | (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) { |
| 125 | String s = new String(mData, avStart + 1, key.length()); |
| 126 | if (0 == key.compareToIgnoreCase(s)) { |
| 127 | byte[] oldBytes = mData; |
| 128 | mData = new byte[oldBytes.length - avLen - 1]; |
| 129 | System.arraycopy(oldBytes, 0, mData, 0, avStart); |
| 130 | System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart, |
| 131 | oldBytes.length - avStart - avLen - 1); |
| 132 | return i; |
| 133 | } |
| 134 | } |
| 135 | avStart += (0xFF & (avLen + 1)); |
| 136 | } |
| 137 | return -1; |
| 138 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 139 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 140 | /** Return the count of keys */ |
| 141 | public int keyCount() { |
| 142 | int count = 0, nextKey; |
| 143 | for (nextKey = 0; nextKey < mData.length; count++) { |
| 144 | nextKey += (0xFF & (mData[nextKey] + 1)); |
| 145 | } |
| 146 | return count; |
| 147 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 148 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 149 | /** Return true if key is present, false if not. */ |
| 150 | public boolean contains(String key) { |
| 151 | String s = null; |
| 152 | for (int i = 0; null != (s = this.getKey(i)); i++) { |
| 153 | if (0 == key.compareToIgnoreCase(s)) return true; |
| 154 | } |
| 155 | return false; |
| 156 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 157 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 158 | /* Gets the size in bytes */ |
| 159 | public int size() { |
| 160 | return mData.length; |
| 161 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 162 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 163 | /* Gets the raw data in bytes */ |
| 164 | public byte[] getRawData() { |
Irfan Sheriff | 21ba815 | 2012-04-04 16:22:21 -0700 | [diff] [blame] | 165 | return (byte[]) mData.clone(); |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 166 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 167 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 168 | private void insert(byte[] keyBytes, byte[] value, int index) { |
| 169 | byte[] oldBytes = mData; |
| 170 | int valLen = (value != null) ? value.length : 0; |
| 171 | int insertion = 0; |
| 172 | int newLen, avLen; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 173 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 174 | for (int i = 0; i < index && insertion < mData.length; i++) { |
| 175 | insertion += (0xFF & (mData[insertion] + 1)); |
| 176 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 177 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 178 | avLen = keyBytes.length + valLen + (value != null ? 1 : 0); |
| 179 | newLen = avLen + oldBytes.length + 1; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 180 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 181 | mData = new byte[newLen]; |
| 182 | System.arraycopy(oldBytes, 0, mData, 0, insertion); |
| 183 | int secondHalfLen = oldBytes.length - insertion; |
| 184 | System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen); |
| 185 | mData[insertion] = (byte) avLen; |
| 186 | System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length); |
| 187 | if (value != null) { |
| 188 | mData[insertion + 1 + keyBytes.length] = mSeperator; |
| 189 | System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen); |
| 190 | } |
| 191 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 192 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 193 | /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ |
| 194 | private String getKey(int index) { |
| 195 | int avStart = 0; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 196 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 197 | for (int i=0; i < index && avStart < mData.length; i++) { |
| 198 | avStart += mData[avStart] + 1; |
| 199 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 200 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 201 | if (avStart < mData.length) { |
| 202 | int avLen = mData[avStart]; |
| 203 | int aLen = 0; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 204 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 205 | for (aLen=0; aLen < avLen; aLen++) { |
| 206 | if (mData[avStart + aLen + 1] == mSeperator) break; |
| 207 | } |
| 208 | return new String(mData, avStart + 1, aLen); |
| 209 | } |
| 210 | return null; |
| 211 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 212 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 213 | /** |
| 214 | * Look up a key in the TXT record by zero-based index and return its value. |
| 215 | * Returns null if index exceeds the total number of keys. |
| 216 | * Returns null if the key is present with no value. |
| 217 | */ |
| 218 | private byte[] getValue(int index) { |
| 219 | int avStart = 0; |
| 220 | byte[] value = null; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 221 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 222 | for (int i=0; i < index && avStart < mData.length; i++) { |
| 223 | avStart += mData[avStart] + 1; |
| 224 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 225 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 226 | if (avStart < mData.length) { |
| 227 | int avLen = mData[avStart]; |
| 228 | int aLen = 0; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 229 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 230 | for (aLen=0; aLen < avLen; aLen++) { |
| 231 | if (mData[avStart + aLen + 1] == mSeperator) { |
| 232 | value = new byte[avLen - aLen - 1]; |
| 233 | System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1); |
| 234 | break; |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | return value; |
| 239 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 240 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 241 | private String getValueAsString(int index) { |
| 242 | byte[] value = this.getValue(index); |
| 243 | return value != null ? new String(value) : null; |
| 244 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 245 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 246 | private byte[] getValue(String forKey) { |
| 247 | String s = null; |
| 248 | int i; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 249 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 250 | for (i = 0; null != (s = this.getKey(i)); i++) { |
| 251 | if (0 == forKey.compareToIgnoreCase(s)) { |
| 252 | return this.getValue(i); |
| 253 | } |
| 254 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 255 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 256 | return null; |
| 257 | } |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 258 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 259 | /** |
| 260 | * Return a string representation. |
| 261 | * Example : {key1=value1},{key2=value2}.. |
| 262 | * |
| 263 | * For a key say like "key3" with null value |
| 264 | * {key1=value1},{key2=value2}{key3} |
| 265 | */ |
| 266 | public String toString() { |
| 267 | String a, result = null; |
| 268 | |
| 269 | for (int i = 0; null != (a = this.getKey(i)); i++) { |
| 270 | String av = "{" + a; |
| 271 | String val = this.getValueAsString(i); |
| 272 | if (val != null) |
| 273 | av += "=" + val + "}"; |
| 274 | else |
| 275 | av += "}"; |
| 276 | if (result == null) |
| 277 | result = av; |
| 278 | else |
| 279 | result = result + ", " + av; |
| 280 | } |
| 281 | return result != null ? result : ""; |
| 282 | } |
| 283 | |
Irfan Sheriff | 21ba815 | 2012-04-04 16:22:21 -0700 | [diff] [blame] | 284 | @Override |
| 285 | public boolean equals(Object o) { |
| 286 | if (o == this) { |
| 287 | return true; |
| 288 | } |
| 289 | if (!(o instanceof DnsSdTxtRecord)) { |
| 290 | return false; |
| 291 | } |
| 292 | |
| 293 | DnsSdTxtRecord record = (DnsSdTxtRecord)o; |
| 294 | return Arrays.equals(record.mData, mData); |
| 295 | } |
| 296 | |
| 297 | @Override |
| 298 | public int hashCode() { |
| 299 | return Arrays.hashCode(mData); |
| 300 | } |
| 301 | |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 302 | /** Implement the Parcelable interface */ |
| 303 | public int describeContents() { |
| 304 | return 0; |
| 305 | } |
| 306 | |
| 307 | /** Implement the Parcelable interface */ |
| 308 | public void writeToParcel(Parcel dest, int flags) { |
| 309 | dest.writeByteArray(mData); |
| 310 | } |
| 311 | |
| 312 | /** Implement the Parcelable interface */ |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame^] | 313 | public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR = |
Irfan Sheriff | 7d024d3 | 2012-03-22 17:01:39 -0700 | [diff] [blame] | 314 | new Creator<DnsSdTxtRecord>() { |
| 315 | public DnsSdTxtRecord createFromParcel(Parcel in) { |
| 316 | DnsSdTxtRecord info = new DnsSdTxtRecord(); |
| 317 | in.readByteArray(info.mData); |
| 318 | return info; |
| 319 | } |
| 320 | |
| 321 | public DnsSdTxtRecord[] newArray(int size) { |
| 322 | return new DnsSdTxtRecord[size]; |
| 323 | } |
| 324 | }; |
Irfan Sheriff | 26d4452 | 2012-04-03 12:13:27 -0700 | [diff] [blame] | 325 | } |