blob: 6fdb0d0f84626dda1b7a5d89cf542004234237f2 [file] [log] [blame]
Irfan Sherifffa291e62012-04-04 13:18:17 -07001/*
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
17package android.net.nsd;
18
19import android.os.Parcelable;
20import android.os.Parcel;
Christopher Laneb72d8b42014-03-17 16:35:45 -070021import android.util.Log;
22import android.util.ArrayMap;
Irfan Sherifffa291e62012-04-04 13:18:17 -070023
Christopher Laneb72d8b42014-03-17 16:35:45 -070024import java.io.UnsupportedEncodingException;
Irfan Sheriff817388e2012-04-11 14:52:19 -070025import java.net.InetAddress;
Christopher Laneb72d8b42014-03-17 16:35:45 -070026import java.nio.charset.StandardCharsets;
27import java.util.Collections;
28import java.util.Map;
29
Irfan Sheriff817388e2012-04-11 14:52:19 -070030
Irfan Sherifffa291e62012-04-04 13:18:17 -070031/**
Irfan Sheriff92784672012-04-13 12:15:41 -070032 * A class representing service information for network service discovery
33 * {@see NsdManager}
Irfan Sherifffa291e62012-04-04 13:18:17 -070034 */
Irfan Sheriff22af38c2012-05-03 16:44:27 -070035public final class NsdServiceInfo implements Parcelable {
Irfan Sherifffa291e62012-04-04 13:18:17 -070036
Christopher Laneb72d8b42014-03-17 16:35:45 -070037 private static final String TAG = "NsdServiceInfo";
38
Irfan Sherifffa291e62012-04-04 13:18:17 -070039 private String mServiceName;
40
Irfan Sheriff817388e2012-04-11 14:52:19 -070041 private String mServiceType;
Irfan Sherifffa291e62012-04-04 13:18:17 -070042
Christopher Laneb72d8b42014-03-17 16:35:45 -070043 private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<String, byte[]>();
Irfan Sherifffa291e62012-04-04 13:18:17 -070044
Irfan Sheriff817388e2012-04-11 14:52:19 -070045 private InetAddress mHost;
Irfan Sherifffa291e62012-04-04 13:18:17 -070046
47 private int mPort;
48
Irfan Sheriff22af38c2012-05-03 16:44:27 -070049 public NsdServiceInfo() {
Irfan Sherifffa291e62012-04-04 13:18:17 -070050 }
51
Irfan Sheriff92784672012-04-13 12:15:41 -070052 /** @hide */
Christopher Laneb72d8b42014-03-17 16:35:45 -070053 public NsdServiceInfo(String sn, String rt) {
Irfan Sherifffa291e62012-04-04 13:18:17 -070054 mServiceName = sn;
Irfan Sheriff817388e2012-04-11 14:52:19 -070055 mServiceType = rt;
Irfan Sherifffa291e62012-04-04 13:18:17 -070056 }
57
Irfan Sheriff92784672012-04-13 12:15:41 -070058 /** Get the service name */
Irfan Sherifffa291e62012-04-04 13:18:17 -070059 public String getServiceName() {
60 return mServiceName;
61 }
62
Irfan Sheriff92784672012-04-13 12:15:41 -070063 /** Set the service name */
Irfan Sherifffa291e62012-04-04 13:18:17 -070064 public void setServiceName(String s) {
65 mServiceName = s;
66 }
67
Irfan Sheriff92784672012-04-13 12:15:41 -070068 /** Get the service type */
Irfan Sherifffa291e62012-04-04 13:18:17 -070069 public String getServiceType() {
Irfan Sheriff817388e2012-04-11 14:52:19 -070070 return mServiceType;
Irfan Sherifffa291e62012-04-04 13:18:17 -070071 }
72
Irfan Sheriff92784672012-04-13 12:15:41 -070073 /** Set the service type */
Irfan Sherifffa291e62012-04-04 13:18:17 -070074 public void setServiceType(String s) {
Irfan Sheriff817388e2012-04-11 14:52:19 -070075 mServiceType = s;
Irfan Sherifffa291e62012-04-04 13:18:17 -070076 }
77
Irfan Sheriff92784672012-04-13 12:15:41 -070078 /** Get the host address. The host address is valid for a resolved service. */
Irfan Sheriff817388e2012-04-11 14:52:19 -070079 public InetAddress getHost() {
80 return mHost;
Irfan Sherifffa291e62012-04-04 13:18:17 -070081 }
82
Irfan Sheriff92784672012-04-13 12:15:41 -070083 /** Set the host address */
Irfan Sheriff817388e2012-04-11 14:52:19 -070084 public void setHost(InetAddress s) {
85 mHost = s;
Irfan Sherifffa291e62012-04-04 13:18:17 -070086 }
87
Irfan Sheriff92784672012-04-13 12:15:41 -070088 /** Get port number. The port number is valid for a resolved service. */
Irfan Sherifffa291e62012-04-04 13:18:17 -070089 public int getPort() {
90 return mPort;
91 }
92
Irfan Sheriff92784672012-04-13 12:15:41 -070093 /** Set port number */
Irfan Sherifffa291e62012-04-04 13:18:17 -070094 public void setPort(int p) {
95 mPort = p;
96 }
97
Christopher Laneb72d8b42014-03-17 16:35:45 -070098 /** @hide */
99 public void setAttribute(String key, byte[] value) {
100 // Key must be printable US-ASCII, excluding =.
101 for (int i = 0; i < key.length(); ++i) {
102 char character = key.charAt(i);
103 if (character < 0x20 || character > 0x7E) {
104 throw new IllegalArgumentException("Key strings must be printable US-ASCII");
105 } else if (character == 0x3D) {
106 throw new IllegalArgumentException("Key strings must not include '='");
107 }
108 }
109
110 // Key length + value length must be < 255.
111 if (key.length() + (value == null ? 0 : value.length) >= 255) {
112 throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
113 }
114
115 // Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
116 if (key.length() > 9) {
117 Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
118 }
119
120 // Check against total TXT record size limits.
121 // Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
122 int txtRecordSize = getTxtRecordSize();
123 int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
124 if (futureSize > 1300) {
125 throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
126 } else if (futureSize > 400) {
127 Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
128 }
129
130 mTxtRecord.put(key, value);
131 }
132
133 /**
134 * Add a service attribute as a key/value pair.
135 *
136 * <p> Service attributes are included as DNS-SD TXT record pairs.
137 *
138 * <p> The key must be US-ASCII printable characters, excluding the '=' character. Values may
139 * be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.
140 *
141 * <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
142 * {@link NsdServiceInfo}. Calling {@link #setAttribute} twice with the same key will overwrite
143 * first value.
144 */
145 public void setAttribute(String key, String value) {
146 try {
147 setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
148 } catch (UnsupportedEncodingException e) {
149 throw new IllegalArgumentException("Value must be UTF-8");
150 }
151 }
152
153 /** Remove an attribute by key */
154 public void removeAttribute(String key) {
155 mTxtRecord.remove(key);
156 }
157
158 /**
159 * Retrive attributes as a map of String keys to byte[] values.
160 *
161 * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
162 * {@link #removeAttribute}.
163 */
164 public Map<String, byte[]> getAttributes() {
165 return Collections.unmodifiableMap(mTxtRecord);
166 }
167
168 private int getTxtRecordSize() {
169 int txtRecordSize = 0;
170 for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
171 txtRecordSize += 2; // One for the length byte, one for the = between key and value.
172 txtRecordSize += entry.getKey().length();
173 byte[] value = entry.getValue();
174 txtRecordSize += value == null ? 0 : value.length;
175 }
176 return txtRecordSize;
177 }
178
179 /** @hide */
180 public byte[] getTxtRecord() {
181 int txtRecordSize = getTxtRecordSize();
182 if (txtRecordSize == 0) {
183 return null;
184 }
185
186 byte[] txtRecord = new byte[txtRecordSize];
187 int ptr = 0;
188 for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
189 String key = entry.getKey();
190 byte[] value = entry.getValue();
191
192 // One byte to record the length of this key/value pair.
193 txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
194
195 // The key, in US-ASCII.
196 // Note: use the StandardCharsets const here because it doesn't raise exceptions and we
197 // already know the key is ASCII at this point.
198 System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
199 key.length());
200 ptr += key.length();
201
202 // US-ASCII '=' character.
203 txtRecord[ptr++] = (byte)'=';
204
205 // The value, as any raw bytes.
206 if (value != null) {
207 System.arraycopy(value, 0, txtRecord, ptr, value.length);
208 ptr += value.length;
209 }
210 }
211 return txtRecord;
212 }
213
Irfan Sherifffa291e62012-04-04 13:18:17 -0700214 public String toString() {
215 StringBuffer sb = new StringBuffer();
216
Christopher Laneb72d8b42014-03-17 16:35:45 -0700217 sb.append("name: ").append(mServiceName)
218 .append(", type: ").append(mServiceType)
219 .append(", host: ").append(mHost)
220 .append(", port: ").append(mPort);
221
222 byte[] txtRecord = getTxtRecord();
223 if (txtRecord != null) {
224 sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
225 }
Irfan Sherifffa291e62012-04-04 13:18:17 -0700226 return sb.toString();
227 }
228
229 /** Implement the Parcelable interface */
230 public int describeContents() {
231 return 0;
232 }
233
234 /** Implement the Parcelable interface */
235 public void writeToParcel(Parcel dest, int flags) {
236 dest.writeString(mServiceName);
Irfan Sheriff817388e2012-04-11 14:52:19 -0700237 dest.writeString(mServiceType);
Irfan Sheriff817388e2012-04-11 14:52:19 -0700238 if (mHost != null) {
Christopher Laneb72d8b42014-03-17 16:35:45 -0700239 dest.writeInt(1);
Irfan Sheriff817388e2012-04-11 14:52:19 -0700240 dest.writeByteArray(mHost.getAddress());
241 } else {
Christopher Laneb72d8b42014-03-17 16:35:45 -0700242 dest.writeInt(0);
Irfan Sheriff817388e2012-04-11 14:52:19 -0700243 }
Irfan Sherifffa291e62012-04-04 13:18:17 -0700244 dest.writeInt(mPort);
Christopher Laneb72d8b42014-03-17 16:35:45 -0700245
246 // TXT record key/value pairs.
247 dest.writeInt(mTxtRecord.size());
248 for (String key : mTxtRecord.keySet()) {
249 byte[] value = mTxtRecord.get(key);
250 if (value != null) {
251 dest.writeInt(1);
252 dest.writeInt(value.length);
253 dest.writeByteArray(value);
254 } else {
255 dest.writeInt(0);
256 }
257 dest.writeString(key);
258 }
Irfan Sherifffa291e62012-04-04 13:18:17 -0700259 }
260
261 /** Implement the Parcelable interface */
Irfan Sheriff22af38c2012-05-03 16:44:27 -0700262 public static final Creator<NsdServiceInfo> CREATOR =
263 new Creator<NsdServiceInfo>() {
264 public NsdServiceInfo createFromParcel(Parcel in) {
265 NsdServiceInfo info = new NsdServiceInfo();
Irfan Sherifffa291e62012-04-04 13:18:17 -0700266 info.mServiceName = in.readString();
Irfan Sheriff817388e2012-04-11 14:52:19 -0700267 info.mServiceType = in.readString();
Irfan Sheriff817388e2012-04-11 14:52:19 -0700268
Christopher Laneb72d8b42014-03-17 16:35:45 -0700269 if (in.readInt() == 1) {
Irfan Sheriff817388e2012-04-11 14:52:19 -0700270 try {
271 info.mHost = InetAddress.getByAddress(in.createByteArray());
272 } catch (java.net.UnknownHostException e) {}
273 }
274
Irfan Sherifffa291e62012-04-04 13:18:17 -0700275 info.mPort = in.readInt();
Christopher Laneb72d8b42014-03-17 16:35:45 -0700276
277 // TXT record key/value pairs.
278 int recordCount = in.readInt();
279 for (int i = 0; i < recordCount; ++i) {
280 byte[] valueArray = null;
281 if (in.readInt() == 1) {
282 int valueLength = in.readInt();
283 valueArray = new byte[valueLength];
284 in.readByteArray(valueArray);
285 }
286 info.mTxtRecord.put(in.readString(), valueArray);
287 }
Irfan Sherifffa291e62012-04-04 13:18:17 -0700288 return info;
289 }
290
Irfan Sheriff22af38c2012-05-03 16:44:27 -0700291 public NsdServiceInfo[] newArray(int size) {
292 return new NsdServiceInfo[size];
Irfan Sherifffa291e62012-04-04 13:18:17 -0700293 }
294 };
Irfan Sherifffa291e62012-04-04 13:18:17 -0700295}