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 17package android.net.wifi.p2p.nsd; 18 19import android.net.wifi.p2p.WifiP2pDevice; 20 21import java.io.ByteArrayInputStream; 22import java.io.DataInputStream; 23import java.io.IOException; 24import java.util.HashMap; 25import java.util.Map; 26 27/** 28 * A class for a response of bonjour service discovery. 29 * 30 * @hide 31 */ 32public class WifiP2pDnsSdServiceResponse extends WifiP2pServiceResponse { 33 34 /** 35 * DNS query name. 36 * e.g) 37 * for PTR 38 * "_ipp._tcp.local." 39 * for TXT 40 * "MyPrinter._ipp._tcp.local." 41 */ 42 private String mDnsQueryName; 43 44 /** 45 * Service instance name. 46 * e.g) "MyPrinter" 47 * This field is only used when the dns type equals to 48 * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR}. 49 */ 50 private String mInstanceName; 51 52 /** 53 * DNS Type. 54 * Should be {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_PTR} or 55 * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}. 56 */ 57 private int mDnsType; 58 59 /** 60 * DnsSd version number. 61 * Should be {@link WifiP2pDnsSdServiceInfo#VERSION_1}. 62 */ 63 private int mVersion; 64 65 /** 66 * Txt record. 67 * This field is only used when the dns type equals to 68 * {@link WifiP2pDnsSdServiceInfo#DNS_TYPE_TXT}. 69 */ 70 private final HashMap<String, String> mTxtRecord = new HashMap<String, String>(); 71 72 /** 73 * Virtual memory packet. 74 * see E.3 of the Wi-Fi Direct technical specification for the detail.<br> 75 * The spec can be obtained from wi-fi.org 76 * Key: pointer Value: domain name.<br> 77 */ 78 private final static Map<Integer, String> sVmpack; 79 80 static { 81 sVmpack = new HashMap<Integer, String>(); 82 sVmpack.put(0x0c, "_tcp.local."); 83 sVmpack.put(0x11, "local."); 84 sVmpack.put(0x1c, "_udp.local."); 85 } 86 87 /** 88 * Returns query DNS name. 89 * @return DNS name. 90 */ 91 public String getDnsQueryName() { 92 return mDnsQueryName; 93 } 94 95 /** 96 * Return query DNS type. 97 * @return DNS type. 98 */ 99 public int getDnsType() { 100 return mDnsType; 101 } 102 103 /** 104 * Return bonjour version number. 105 * @return version number. 106 */ 107 public int getVersion() { 108 return mVersion; 109 } 110 111 /** 112 * Return instance name. 113 * @return 114 */ 115 public String getInstanceName() { 116 return mInstanceName; 117 } 118 119 /** 120 * Return TXT record data. 121 * @return TXT record data. 122 */ 123 public Map<String, String> getTxtRecord() { 124 return mTxtRecord; 125 } 126 127 @Override 128 public String toString() { 129 StringBuffer sbuf = new StringBuffer(); 130 sbuf.append("serviceType:DnsSd(").append(mServiceType).append(")"); 131 sbuf.append(" status:").append(Status.toString(mStatus)); 132 sbuf.append(" srcAddr:").append(mDevice.deviceAddress); 133 sbuf.append(" version:").append(String.format("%02x", mVersion)); 134 sbuf.append(" dnsName:").append(mDnsQueryName); 135 sbuf.append(" TxtRecord:"); 136 for (String key : mTxtRecord.keySet()) { 137 sbuf.append(" key:").append(key).append(" value:").append(mTxtRecord.get(key)); 138 } 139 if (mInstanceName != null) { 140 sbuf.append(" InsName:").append(mInstanceName); 141 } 142 return sbuf.toString(); 143 } 144 145 /** 146 * This is only used in framework. 147 * @param status status code. 148 * @param dev source device. 149 * @param data RDATA. 150 * @hide 151 */ 152 protected WifiP2pDnsSdServiceResponse(int status, 153 int tranId, WifiP2pDevice dev, byte[] data) { 154 super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, 155 status, tranId, dev, data); 156 if (!parse()) { 157 throw new IllegalArgumentException("Malformed bonjour service response"); 158 } 159 } 160 161 /** 162 * Parse DnsSd service discovery response. 163 * 164 * @return {@code true} if the operation succeeded 165 */ 166 private boolean parse() { 167 /* 168 * The data format from Wi-Fi Direct spec is as follows. 169 * ________________________________________________ 170 * | encoded and compressed dns name (variable) | 171 * ________________________________________________ 172 * | dnstype(2byte) | version(1byte) | 173 * ________________________________________________ 174 * | RDATA (variable) | 175 */ 176 if (mData == null) { 177 // the empty is OK. 178 return true; 179 } 180 181 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData)); 182 183 mDnsQueryName = readDnsName(dis); 184 if (mDnsQueryName == null) { 185 return false; 186 } 187 188 try { 189 mDnsType = dis.readUnsignedShort(); 190 mVersion = dis.readUnsignedByte(); 191 } catch (IOException e) { 192 e.printStackTrace(); 193 return false; 194 } 195 196 if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_PTR) { 197 String rData = readDnsName(dis); 198 if (rData == null) { 199 return false; 200 } 201 if (rData.length() <= mDnsQueryName.length()) { 202 return false; 203 } 204 205 mInstanceName = rData.substring(0, 206 rData.length() - mDnsQueryName.length() -1); 207 } else if (mDnsType == WifiP2pDnsSdServiceInfo.DNS_TYPE_TXT) { 208 return readTxtData(dis); 209 } else { 210 return false; 211 } 212 213 return true; 214 } 215 216 /** 217 * Read dns name. 218 * 219 * @param dis data input stream. 220 * @return dns name 221 */ 222 private String readDnsName(DataInputStream dis) { 223 StringBuffer sb = new StringBuffer(); 224 225 // copy virtual memory packet. 226 HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack); 227 if (mDnsQueryName != null) { 228 vmpack.put(0x27, mDnsQueryName); 229 } 230 try { 231 while (true) { 232 int i = dis.readUnsignedByte(); 233 if (i == 0x00) { 234 return sb.toString(); 235 } else if (i == 0xc0) { 236 // refer to pointer. 237 String ref = vmpack.get(dis.readUnsignedByte()); 238 if (ref == null) { 239 //invalid. 240 return null; 241 } 242 sb.append(ref); 243 return sb.toString(); 244 } else { 245 byte[] data = new byte[i]; 246 dis.readFully(data); 247 sb.append(new String(data)); 248 sb.append("."); 249 } 250 } 251 } catch (IOException e) { 252 e.printStackTrace(); 253 } 254 return null; 255 } 256 257 /** 258 * Read TXT record data. 259 * 260 * @param dis 261 * @return true if TXT data is valid 262 */ 263 private boolean readTxtData(DataInputStream dis) { 264 try { 265 while (dis.available() > 0) { 266 int len = dis.readUnsignedByte(); 267 if (len == 0) { 268 break; 269 } 270 byte[] data = new byte[len]; 271 dis.readFully(data); 272 String[] keyVal = new String(data).split("="); 273 if (keyVal.length != 2) { 274 return false; 275 } 276 mTxtRecord.put(keyVal[0], keyVal[1]); 277 } 278 return true; 279 } catch (IOException e) { 280 e.printStackTrace(); 281 } 282 return false; 283 } 284 285 /** 286 * Creates DnsSd service response. 287 * This is only called from WifiP2pServiceResponse 288 * 289 * @param status status code. 290 * @param dev source device. 291 * @param data DnsSd response data. 292 * @return DnsSd service response data. 293 * @hide 294 */ 295 static WifiP2pDnsSdServiceResponse newInstance(int status, 296 int transId, WifiP2pDevice dev, byte[] data) { 297 if (status != WifiP2pServiceResponse.Status.SUCCESS) { 298 return new WifiP2pDnsSdServiceResponse(status, 299 transId, dev, null); 300 } 301 try { 302 return new WifiP2pDnsSdServiceResponse(status, 303 transId, dev, data); 304 } catch (IllegalArgumentException e) { 305 e.printStackTrace(); 306 } 307 return null; 308 } 309} 310