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 
17 package android.net.wifi.p2p.nsd;
18 
19 import android.net.wifi.p2p.WifiP2pDevice;
20 
21 import java.io.ByteArrayInputStream;
22 import java.io.DataInputStream;
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.Map;
26 
27 /**
28  * A class for a response of bonjour service discovery.
29  *
30  * @hide
31  */
32 public 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      */
getDnsQueryName()91     public String getDnsQueryName() {
92         return mDnsQueryName;
93     }
94 
95     /**
96      * Return query DNS type.
97      * @return DNS type.
98      */
getDnsType()99     public int getDnsType() {
100         return mDnsType;
101     }
102 
103     /**
104      * Return bonjour version number.
105      * @return version number.
106      */
getVersion()107     public int getVersion() {
108         return mVersion;
109     }
110 
111     /**
112      * Return instance name.
113      * @return
114      */
getInstanceName()115     public String getInstanceName() {
116         return mInstanceName;
117     }
118 
119     /**
120      * Return TXT record data.
121      * @return TXT record data.
122      */
getTxtRecord()123     public Map<String, String> getTxtRecord() {
124         return mTxtRecord;
125     }
126 
127     @Override
toString()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      */
WifiP2pDnsSdServiceResponse(int status, int tranId, WifiP2pDevice dev, byte[] data)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      */
parse()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      */
readDnsName(DataInputStream dis)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      */
readTxtData(DataInputStream dis)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      */
newInstance(int status, int transId, WifiP2pDevice dev, byte[] data)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