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