1fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff/*
2fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * Copyright (C) 2012 The Android Open Source Project
3fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff *
4fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * Licensed under the Apache License, Version 2.0 (the "License");
5fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * you may not use this file except in compliance with the License.
6fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * You may obtain a copy of the License at
7fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff *
8fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff *      http://www.apache.org/licenses/LICENSE-2.0
9fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff *
10fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * Unless required by applicable law or agreed to in writing, software
11fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * distributed under the License is distributed on an "AS IS" BASIS,
12fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * See the License for the specific language governing permissions and
14fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff * limitations under the License.
15fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff */
16fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
17fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriffpackage android.net.nsd;
18fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
19312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmannimport android.annotation.NonNull;
20fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriffimport android.os.Parcelable;
21fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriffimport android.os.Parcel;
22312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmannimport android.text.TextUtils;
23312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmannimport android.util.Base64;
24b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Laneimport android.util.Log;
25b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Laneimport android.util.ArrayMap;
26fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
27b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Laneimport java.io.UnsupportedEncodingException;
28817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriffimport java.net.InetAddress;
29b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Laneimport java.nio.charset.StandardCharsets;
30b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Laneimport java.util.Collections;
31b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Laneimport java.util.Map;
32b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
33817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff
34fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff/**
3592784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff * A class representing service information for network service discovery
3692784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff * {@see NsdManager}
37fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff */
3822af38c5261d2c03796b496e6edb125327cace16Irfan Sheriffpublic final class NsdServiceInfo implements Parcelable {
39fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
40b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    private static final String TAG = "NsdServiceInfo";
41b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
42fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    private String mServiceName;
43fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
44817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff    private String mServiceType;
45fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
46b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<String, byte[]>();
47fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
48817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff    private InetAddress mHost;
49fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
50fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    private int mPort;
51fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
5222af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff    public NsdServiceInfo() {
53fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
54fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
5592784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** @hide */
56b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    public NsdServiceInfo(String sn, String rt) {
57fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        mServiceName = sn;
58817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        mServiceType = rt;
59fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
60fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
6192784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Get the service name */
62fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public String getServiceName() {
63fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        return mServiceName;
64fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
65fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
6692784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Set the service name */
67fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public void setServiceName(String s) {
68fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        mServiceName = s;
69fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
70fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
7192784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Get the service type */
72fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public String getServiceType() {
73817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        return mServiceType;
74fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
75fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
7692784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Set the service type */
77fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public void setServiceType(String s) {
78817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        mServiceType = s;
79fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
80fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
8192784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Get the host address. The host address is valid for a resolved service. */
82817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff    public InetAddress getHost() {
83817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        return mHost;
84fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
85fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
8692784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Set the host address */
87817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff    public void setHost(InetAddress s) {
88817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        mHost = s;
89fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
90fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
9192784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Get port number. The port number is valid for a resolved service. */
92fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public int getPort() {
93fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        return mPort;
94fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
95fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
9692784670c48759c0db604ddb95c05a7b9bdebed8Irfan Sheriff    /** Set port number */
97fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public void setPort(int p) {
98fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        mPort = p;
99fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
100fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
101312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann    /**
102312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann     * Unpack txt information from a base-64 encoded byte array.
103312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann     *
104312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann     * @param rawRecords The raw base64 encoded records string read from netd.
105312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann     *
106312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann     * @hide
107312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann     */
108312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann    public void setTxtRecords(@NonNull String rawRecords) {
109312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        byte[] txtRecordsRawBytes = Base64.decode(rawRecords, Base64.DEFAULT);
110312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
111312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // There can be multiple TXT records after each other. Each record has to following format:
112312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        //
113312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // byte                  type                  required   meaning
114312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // -------------------   -------------------   --------   ----------------------------------
115312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // 0                     unsigned 8 bit        yes        size of record excluding this byte
116312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // 1 - n                 ASCII but not '='     yes        key
117312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // n + 1                 '='                   optional   separator of key and value
118312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // n + 2 - record size   uninterpreted bytes   optional   value
119312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        //
120312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // Example legal records:
121312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // [11, 'm', 'y', 'k', 'e', 'y', '=', 0x0, 0x4, 0x65, 0x7, 0xff]
122312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // [17, 'm', 'y', 'K', 'e', 'y', 'W', 'i', 't', 'h', 'N', 'o', 'V', 'a', 'l', 'u', 'e', '=']
123312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // [12, 'm', 'y', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'K', 'e', 'y']
124312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        //
125312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // Example corrupted records
126312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // [3, =, 1, 2]    <- key is empty
127312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // [3, 0, =, 2]    <- key contains non-ASCII character. We handle this by replacing the
128312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        //                    invalid characters instead of skipping the record.
129312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        // [30, 'a', =, 2] <- length exceeds total left over bytes in the TXT records array, we
130312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        //                    handle this by reducing the length of the record as needed.
131312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        int pos = 0;
132312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        while (pos < txtRecordsRawBytes.length) {
133312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            // recordLen is an unsigned 8 bit value
134312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            int recordLen = txtRecordsRawBytes[pos] & 0xff;
135312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            pos += 1;
136312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
137312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            try {
138312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                if (recordLen == 0) {
139312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    throw new IllegalArgumentException("Zero sized txt record");
140312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                } else if (pos + recordLen > txtRecordsRawBytes.length) {
141312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    Log.w(TAG, "Corrupt record length (pos = " + pos + "): " + recordLen);
142312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    recordLen = txtRecordsRawBytes.length - pos;
143312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                }
144312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
145312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                // Decode key-value records
146312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                String key = null;
147312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                byte[] value = null;
148312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                int valueLen = 0;
149312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                for (int i = pos; i < pos + recordLen; i++) {
150312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    if (key == null) {
151312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                        if (txtRecordsRawBytes[i] == '=') {
152312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                            key = new String(txtRecordsRawBytes, pos, i - pos,
153312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                                    StandardCharsets.US_ASCII);
154312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                        }
155312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    } else {
156312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                        if (value == null) {
157312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                            value = new byte[recordLen - key.length() - 1];
158312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                        }
159312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                        value[valueLen] = txtRecordsRawBytes[i];
160312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                        valueLen++;
161312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    }
162312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                }
163312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
164312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                // If '=' was not found we have a boolean record
165312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                if (key == null) {
166312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    key = new String(txtRecordsRawBytes, pos, recordLen, StandardCharsets.US_ASCII);
167312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                }
168312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
169312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                if (TextUtils.isEmpty(key)) {
170312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    // Empty keys are not allowed (RFC6763 6.4)
171312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    throw new IllegalArgumentException("Invalid txt record (key is empty)");
172312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                }
173312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
174312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                if (getAttributes().containsKey(key)) {
175312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    // When we have a duplicate record, the later ones are ignored (RFC6763 6.4)
176312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                    throw new IllegalArgumentException("Invalid txt record (duplicate key \"" + key + "\")");
177312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                }
178312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
179312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                setAttribute(key, value);
180312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            } catch (IllegalArgumentException e) {
181312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann                Log.e(TAG, "While parsing txt records (pos = " + pos + "): " + e.getMessage());
182312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            }
183312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
184312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            pos += recordLen;
185312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        }
186312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann    }
187312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
188b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    /** @hide */
189b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    public void setAttribute(String key, byte[] value) {
190312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        if (TextUtils.isEmpty(key)) {
191312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            throw new IllegalArgumentException("Key cannot be empty");
192312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann        }
193312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann
194b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        // Key must be printable US-ASCII, excluding =.
195b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        for (int i = 0; i < key.length(); ++i) {
196b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            char character = key.charAt(i);
197b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            if (character < 0x20 || character > 0x7E) {
198b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                throw new IllegalArgumentException("Key strings must be printable US-ASCII");
199b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            } else if (character == 0x3D) {
200b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                throw new IllegalArgumentException("Key strings must not include '='");
201b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            }
202b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
203b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
204b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        // Key length + value length must be < 255.
205b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        if (key.length() + (value == null ? 0 : value.length) >= 255) {
206b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
207b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
208b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
209b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        // Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
210b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        if (key.length() > 9) {
211b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
212b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
213b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
214b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        // Check against total TXT record size limits.
215b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        // Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
216b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        int txtRecordSize = getTxtRecordSize();
217b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
218b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        if (futureSize > 1300) {
219b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
220b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        } else if (futureSize > 400) {
221b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
222b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
223b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
224b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        mTxtRecord.put(key, value);
225b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    }
226b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
227b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    /**
228b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * Add a service attribute as a key/value pair.
229b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     *
230b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * <p> Service attributes are included as DNS-SD TXT record pairs.
231b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     *
232b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * <p> The key must be US-ASCII printable characters, excluding the '=' character.  Values may
233b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * be UTF-8 strings or null.  The total length of key + value must be less than 255 bytes.
234b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     *
235b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
236b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * {@link NsdServiceInfo}.  Calling {@link #setAttribute} twice with the same key will overwrite
237b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * first value.
238b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     */
239b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    public void setAttribute(String key, String value) {
240b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        try {
241b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
242b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        } catch (UnsupportedEncodingException e) {
243b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            throw new IllegalArgumentException("Value must be UTF-8");
244b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
245b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    }
246b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
247b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    /** Remove an attribute by key */
248b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    public void removeAttribute(String key) {
249b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        mTxtRecord.remove(key);
250b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    }
251b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
252b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    /**
2537d5da4b044183826ac8388c8bdb94ac979a81797Philip P. Moltmann     * Retrieve attributes as a map of String keys to byte[] values. The attributes map is only
2547d5da4b044183826ac8388c8bdb94ac979a81797Philip P. Moltmann     * valid for a resolved service.
255b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     *
256b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
257b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     * {@link #removeAttribute}.
258b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane     */
259b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    public Map<String, byte[]> getAttributes() {
260b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        return Collections.unmodifiableMap(mTxtRecord);
261b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    }
262b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
263b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    private int getTxtRecordSize() {
264b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        int txtRecordSize = 0;
265b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
266b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            txtRecordSize += 2;  // One for the length byte, one for the = between key and value.
267b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            txtRecordSize += entry.getKey().length();
268b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            byte[] value = entry.getValue();
269b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            txtRecordSize += value == null ? 0 : value.length;
270b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
271b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        return txtRecordSize;
272b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    }
273b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
274b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    /** @hide */
275312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann    public @NonNull byte[] getTxtRecord() {
276b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        int txtRecordSize = getTxtRecordSize();
277b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        if (txtRecordSize == 0) {
278312c61edabaa5d84eb10617cb1272417cf2f7344Philip P. Moltmann            return new byte[]{};
279b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
280b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
281b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        byte[] txtRecord = new byte[txtRecordSize];
282b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        int ptr = 0;
283b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
284b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            String key = entry.getKey();
285b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            byte[] value = entry.getValue();
286b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
287b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            // One byte to record the length of this key/value pair.
288b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
289b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
290b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            // The key, in US-ASCII.
291b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            // Note: use the StandardCharsets const here because it doesn't raise exceptions and we
292b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            // already know the key is ASCII at this point.
293b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
294b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                    key.length());
295b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            ptr += key.length();
296b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
297b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            // US-ASCII '=' character.
298b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            txtRecord[ptr++] = (byte)'=';
299b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
300b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            // The value, as any raw bytes.
301b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            if (value != null) {
302b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                System.arraycopy(value, 0, txtRecord, ptr, value.length);
303b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                ptr += value.length;
304b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            }
305b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
306b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        return txtRecord;
307b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane    }
308b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
309fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public String toString() {
310fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        StringBuffer sb = new StringBuffer();
311fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
312b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        sb.append("name: ").append(mServiceName)
313b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                .append(", type: ").append(mServiceType)
314b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                .append(", host: ").append(mHost)
315b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                .append(", port: ").append(mPort);
316b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
317b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        byte[] txtRecord = getTxtRecord();
318b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        if (txtRecord != null) {
319b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
320b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
321fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        return sb.toString();
322fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
323fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
324fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    /** Implement the Parcelable interface */
325fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public int describeContents() {
326fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        return 0;
327fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
328fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
329fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    /** Implement the Parcelable interface */
330fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    public void writeToParcel(Parcel dest, int flags) {
331fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        dest.writeString(mServiceName);
332817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        dest.writeString(mServiceType);
333817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        if (mHost != null) {
334b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            dest.writeInt(1);
335817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff            dest.writeByteArray(mHost.getAddress());
336817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        } else {
337b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            dest.writeInt(0);
338817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff        }
339fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        dest.writeInt(mPort);
340b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
341b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        // TXT record key/value pairs.
342b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        dest.writeInt(mTxtRecord.size());
343b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        for (String key : mTxtRecord.keySet()) {
344b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            byte[] value = mTxtRecord.get(key);
345b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            if (value != null) {
346b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                dest.writeInt(1);
347b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                dest.writeInt(value.length);
348b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                dest.writeByteArray(value);
349b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            } else {
350b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                dest.writeInt(0);
351b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            }
352b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane            dest.writeString(key);
353b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane        }
354fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    }
355fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
356fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff    /** Implement the Parcelable interface */
35722af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff    public static final Creator<NsdServiceInfo> CREATOR =
35822af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff        new Creator<NsdServiceInfo>() {
35922af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff            public NsdServiceInfo createFromParcel(Parcel in) {
36022af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff                NsdServiceInfo info = new NsdServiceInfo();
361fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff                info.mServiceName = in.readString();
362817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff                info.mServiceType = in.readString();
363817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff
364b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                if (in.readInt() == 1) {
365817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff                    try {
366817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff                        info.mHost = InetAddress.getByAddress(in.createByteArray());
367817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff                    } catch (java.net.UnknownHostException e) {}
368817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff                }
369817388e056a5d1d0e7cd7de2c6b0c9c80617bc5fIrfan Sheriff
370fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff                info.mPort = in.readInt();
371b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane
372b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                // TXT record key/value pairs.
373b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                int recordCount = in.readInt();
374b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                for (int i = 0; i < recordCount; ++i) {
375b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                    byte[] valueArray = null;
376b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                    if (in.readInt() == 1) {
377b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                        int valueLength = in.readInt();
378b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                        valueArray = new byte[valueLength];
379b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                        in.readByteArray(valueArray);
380b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                    }
381b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                    info.mTxtRecord.put(in.readString(), valueArray);
382b72d8b4091ab31948c91b0382a9b46afdc7ef7daChristopher Lane                }
383fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff                return info;
384fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff            }
385fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff
38622af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff            public NsdServiceInfo[] newArray(int size) {
38722af38c5261d2c03796b496e6edb125327cace16Irfan Sheriff                return new NsdServiceInfo[size];
388fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff            }
389fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff        };
390fa291e66910c41f441df0b9ea8e6f7630c298637Irfan Sheriff}
391