PageRenderTime 1169ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/core/java/android/net/nsd/NsdServiceInfo.java

https://gitlab.com/drgroovestarr/frameworks_base
Java | 389 lines | 235 code | 56 blank | 98 comment | 54 complexity | ed1279c405043981f1362ba6336dab31 MD5 | raw file
  1. /*
  2. * Copyright (C) 2012 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. package android.net.nsd;
  17. import android.annotation.NonNull;
  18. import android.os.Parcelable;
  19. import android.os.Parcel;
  20. import android.text.TextUtils;
  21. import android.util.Base64;
  22. import android.util.Log;
  23. import android.util.ArrayMap;
  24. import java.io.UnsupportedEncodingException;
  25. import java.net.InetAddress;
  26. import java.nio.charset.StandardCharsets;
  27. import java.util.Collections;
  28. import java.util.Map;
  29. /**
  30. * A class representing service information for network service discovery
  31. * {@see NsdManager}
  32. */
  33. public final class NsdServiceInfo implements Parcelable {
  34. private static final String TAG = "NsdServiceInfo";
  35. private String mServiceName;
  36. private String mServiceType;
  37. private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
  38. private InetAddress mHost;
  39. private int mPort;
  40. public NsdServiceInfo() {
  41. }
  42. /** @hide */
  43. public NsdServiceInfo(String sn, String rt) {
  44. mServiceName = sn;
  45. mServiceType = rt;
  46. }
  47. /** Get the service name */
  48. public String getServiceName() {
  49. return mServiceName;
  50. }
  51. /** Set the service name */
  52. public void setServiceName(String s) {
  53. mServiceName = s;
  54. }
  55. /** Get the service type */
  56. public String getServiceType() {
  57. return mServiceType;
  58. }
  59. /** Set the service type */
  60. public void setServiceType(String s) {
  61. mServiceType = s;
  62. }
  63. /** Get the host address. The host address is valid for a resolved service. */
  64. public InetAddress getHost() {
  65. return mHost;
  66. }
  67. /** Set the host address */
  68. public void setHost(InetAddress s) {
  69. mHost = s;
  70. }
  71. /** Get port number. The port number is valid for a resolved service. */
  72. public int getPort() {
  73. return mPort;
  74. }
  75. /** Set port number */
  76. public void setPort(int p) {
  77. mPort = p;
  78. }
  79. /**
  80. * Unpack txt information from a base-64 encoded byte array.
  81. *
  82. * @param rawRecords The raw base64 encoded records string read from netd.
  83. *
  84. * @hide
  85. */
  86. public void setTxtRecords(@NonNull String rawRecords) {
  87. byte[] txtRecordsRawBytes = Base64.decode(rawRecords, Base64.DEFAULT);
  88. // There can be multiple TXT records after each other. Each record has to following format:
  89. //
  90. // byte type required meaning
  91. // ------------------- ------------------- -------- ----------------------------------
  92. // 0 unsigned 8 bit yes size of record excluding this byte
  93. // 1 - n ASCII but not '=' yes key
  94. // n + 1 '=' optional separator of key and value
  95. // n + 2 - record size uninterpreted bytes optional value
  96. //
  97. // Example legal records:
  98. // [11, 'm', 'y', 'k', 'e', 'y', '=', 0x0, 0x4, 0x65, 0x7, 0xff]
  99. // [17, 'm', 'y', 'K', 'e', 'y', 'W', 'i', 't', 'h', 'N', 'o', 'V', 'a', 'l', 'u', 'e', '=']
  100. // [12, 'm', 'y', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'K', 'e', 'y']
  101. //
  102. // Example corrupted records
  103. // [3, =, 1, 2] <- key is empty
  104. // [3, 0, =, 2] <- key contains non-ASCII character. We handle this by replacing the
  105. // invalid characters instead of skipping the record.
  106. // [30, 'a', =, 2] <- length exceeds total left over bytes in the TXT records array, we
  107. // handle this by reducing the length of the record as needed.
  108. int pos = 0;
  109. while (pos < txtRecordsRawBytes.length) {
  110. // recordLen is an unsigned 8 bit value
  111. int recordLen = txtRecordsRawBytes[pos] & 0xff;
  112. pos += 1;
  113. try {
  114. if (recordLen == 0) {
  115. throw new IllegalArgumentException("Zero sized txt record");
  116. } else if (pos + recordLen > txtRecordsRawBytes.length) {
  117. Log.w(TAG, "Corrupt record length (pos = " + pos + "): " + recordLen);
  118. recordLen = txtRecordsRawBytes.length - pos;
  119. }
  120. // Decode key-value records
  121. String key = null;
  122. byte[] value = null;
  123. int valueLen = 0;
  124. for (int i = pos; i < pos + recordLen; i++) {
  125. if (key == null) {
  126. if (txtRecordsRawBytes[i] == '=') {
  127. key = new String(txtRecordsRawBytes, pos, i - pos,
  128. StandardCharsets.US_ASCII);
  129. }
  130. } else {
  131. if (value == null) {
  132. value = new byte[recordLen - key.length() - 1];
  133. }
  134. value[valueLen] = txtRecordsRawBytes[i];
  135. valueLen++;
  136. }
  137. }
  138. // If '=' was not found we have a boolean record
  139. if (key == null) {
  140. key = new String(txtRecordsRawBytes, pos, recordLen, StandardCharsets.US_ASCII);
  141. }
  142. if (TextUtils.isEmpty(key)) {
  143. // Empty keys are not allowed (RFC6763 6.4)
  144. throw new IllegalArgumentException("Invalid txt record (key is empty)");
  145. }
  146. if (getAttributes().containsKey(key)) {
  147. // When we have a duplicate record, the later ones are ignored (RFC6763 6.4)
  148. throw new IllegalArgumentException("Invalid txt record (duplicate key \"" + key + "\")");
  149. }
  150. setAttribute(key, value);
  151. } catch (IllegalArgumentException e) {
  152. Log.e(TAG, "While parsing txt records (pos = " + pos + "): " + e.getMessage());
  153. }
  154. pos += recordLen;
  155. }
  156. }
  157. /** @hide */
  158. public void setAttribute(String key, byte[] value) {
  159. if (TextUtils.isEmpty(key)) {
  160. throw new IllegalArgumentException("Key cannot be empty");
  161. }
  162. // Key must be printable US-ASCII, excluding =.
  163. for (int i = 0; i < key.length(); ++i) {
  164. char character = key.charAt(i);
  165. if (character < 0x20 || character > 0x7E) {
  166. throw new IllegalArgumentException("Key strings must be printable US-ASCII");
  167. } else if (character == 0x3D) {
  168. throw new IllegalArgumentException("Key strings must not include '='");
  169. }
  170. }
  171. // Key length + value length must be < 255.
  172. if (key.length() + (value == null ? 0 : value.length) >= 255) {
  173. throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
  174. }
  175. // Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
  176. if (key.length() > 9) {
  177. Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
  178. }
  179. // Check against total TXT record size limits.
  180. // Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
  181. int txtRecordSize = getTxtRecordSize();
  182. int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
  183. if (futureSize > 1300) {
  184. throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
  185. } else if (futureSize > 400) {
  186. Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
  187. }
  188. mTxtRecord.put(key, value);
  189. }
  190. /**
  191. * Add a service attribute as a key/value pair.
  192. *
  193. * <p> Service attributes are included as DNS-SD TXT record pairs.
  194. *
  195. * <p> The key must be US-ASCII printable characters, excluding the '=' character. Values may
  196. * be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.
  197. *
  198. * <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
  199. * {@link NsdServiceInfo}. Calling {@link #setAttribute} twice with the same key will overwrite
  200. * first value.
  201. */
  202. public void setAttribute(String key, String value) {
  203. try {
  204. setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
  205. } catch (UnsupportedEncodingException e) {
  206. throw new IllegalArgumentException("Value must be UTF-8");
  207. }
  208. }
  209. /** Remove an attribute by key */
  210. public void removeAttribute(String key) {
  211. mTxtRecord.remove(key);
  212. }
  213. /**
  214. * Retrieve attributes as a map of String keys to byte[] values. The attributes map is only
  215. * valid for a resolved service.
  216. *
  217. * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
  218. * {@link #removeAttribute}.
  219. */
  220. public Map<String, byte[]> getAttributes() {
  221. return Collections.unmodifiableMap(mTxtRecord);
  222. }
  223. private int getTxtRecordSize() {
  224. int txtRecordSize = 0;
  225. for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
  226. txtRecordSize += 2; // One for the length byte, one for the = between key and value.
  227. txtRecordSize += entry.getKey().length();
  228. byte[] value = entry.getValue();
  229. txtRecordSize += value == null ? 0 : value.length;
  230. }
  231. return txtRecordSize;
  232. }
  233. /** @hide */
  234. public @NonNull byte[] getTxtRecord() {
  235. int txtRecordSize = getTxtRecordSize();
  236. if (txtRecordSize == 0) {
  237. return new byte[]{};
  238. }
  239. byte[] txtRecord = new byte[txtRecordSize];
  240. int ptr = 0;
  241. for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
  242. String key = entry.getKey();
  243. byte[] value = entry.getValue();
  244. // One byte to record the length of this key/value pair.
  245. txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
  246. // The key, in US-ASCII.
  247. // Note: use the StandardCharsets const here because it doesn't raise exceptions and we
  248. // already know the key is ASCII at this point.
  249. System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
  250. key.length());
  251. ptr += key.length();
  252. // US-ASCII '=' character.
  253. txtRecord[ptr++] = (byte)'=';
  254. // The value, as any raw bytes.
  255. if (value != null) {
  256. System.arraycopy(value, 0, txtRecord, ptr, value.length);
  257. ptr += value.length;
  258. }
  259. }
  260. return txtRecord;
  261. }
  262. public String toString() {
  263. StringBuffer sb = new StringBuffer();
  264. sb.append("name: ").append(mServiceName)
  265. .append(", type: ").append(mServiceType)
  266. .append(", host: ").append(mHost)
  267. .append(", port: ").append(mPort);
  268. byte[] txtRecord = getTxtRecord();
  269. if (txtRecord != null) {
  270. sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
  271. }
  272. return sb.toString();
  273. }
  274. /** Implement the Parcelable interface */
  275. public int describeContents() {
  276. return 0;
  277. }
  278. /** Implement the Parcelable interface */
  279. public void writeToParcel(Parcel dest, int flags) {
  280. dest.writeString(mServiceName);
  281. dest.writeString(mServiceType);
  282. if (mHost != null) {
  283. dest.writeInt(1);
  284. dest.writeByteArray(mHost.getAddress());
  285. } else {
  286. dest.writeInt(0);
  287. }
  288. dest.writeInt(mPort);
  289. // TXT record key/value pairs.
  290. dest.writeInt(mTxtRecord.size());
  291. for (String key : mTxtRecord.keySet()) {
  292. byte[] value = mTxtRecord.get(key);
  293. if (value != null) {
  294. dest.writeInt(1);
  295. dest.writeInt(value.length);
  296. dest.writeByteArray(value);
  297. } else {
  298. dest.writeInt(0);
  299. }
  300. dest.writeString(key);
  301. }
  302. }
  303. /** Implement the Parcelable interface */
  304. public static final Creator<NsdServiceInfo> CREATOR =
  305. new Creator<NsdServiceInfo>() {
  306. public NsdServiceInfo createFromParcel(Parcel in) {
  307. NsdServiceInfo info = new NsdServiceInfo();
  308. info.mServiceName = in.readString();
  309. info.mServiceType = in.readString();
  310. if (in.readInt() == 1) {
  311. try {
  312. info.mHost = InetAddress.getByAddress(in.createByteArray());
  313. } catch (java.net.UnknownHostException e) {}
  314. }
  315. info.mPort = in.readInt();
  316. // TXT record key/value pairs.
  317. int recordCount = in.readInt();
  318. for (int i = 0; i < recordCount; ++i) {
  319. byte[] valueArray = null;
  320. if (in.readInt() == 1) {
  321. int valueLength = in.readInt();
  322. valueArray = new byte[valueLength];
  323. in.readByteArray(valueArray);
  324. }
  325. info.mTxtRecord.put(in.readString(), valueArray);
  326. }
  327. return info;
  328. }
  329. public NsdServiceInfo[] newArray(int size) {
  330. return new NsdServiceInfo[size];
  331. }
  332. };
  333. }