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