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;
20import android.os.Parcel;
21import android.os.Parcelable;
22
23import java.io.ByteArrayInputStream;
24import java.io.DataInputStream;
25import java.io.IOException;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.List;
29
30/**
31 * The class for a response of service discovery.
32 *
33 * @hide
34 */
35public class WifiP2pServiceResponse implements Parcelable {
36
37    private static int MAX_BUF_SIZE = 1024;
38
39    /**
40     * Service type. It's defined in table63 in Wi-Fi Direct specification.
41     */
42    protected int mServiceType;
43
44    /**
45     * Status code of service discovery response.
46     * It's defined in table65 in Wi-Fi Direct specification.
47     * @see Status
48     */
49    protected int mStatus;
50
51    /**
52     * Service transaction ID.
53     * This is a nonzero value used to match the service request/response TLVs.
54     */
55    protected int mTransId;
56
57    /**
58     * Source device.
59     */
60    protected WifiP2pDevice mDevice;
61
62    /**
63     * Service discovery response data based on the requested on
64     * the service protocol type. The protocol format depends on the service type.
65     */
66    protected byte[] mData;
67
68
69    /**
70     * The status code of service discovery response.
71     * Currently 4 status codes are defined and the status codes from  4 to 255
72     * are reserved.
73     *
74     * See Wi-Fi Direct specification for the detail.
75     */
76    public static class Status {
77        /** success */
78        public static final int SUCCESS = 0;
79
80        /** the service protocol type is not available */
81        public static final int SERVICE_PROTOCOL_NOT_AVAILABLE = 1;
82
83        /** the requested information is not available */
84        public static final int REQUESTED_INFORMATION_NOT_AVAILABLE = 2;
85
86        /** bad request */
87        public static final int BAD_REQUEST = 3;
88
89        /** @hide */
90        public static String toString(int status) {
91            switch(status) {
92            case SUCCESS:
93                return "SUCCESS";
94            case SERVICE_PROTOCOL_NOT_AVAILABLE:
95                return "SERVICE_PROTOCOL_NOT_AVAILABLE";
96            case REQUESTED_INFORMATION_NOT_AVAILABLE:
97                return "REQUESTED_INFORMATION_NOT_AVAILABLE";
98            case BAD_REQUEST:
99                return "BAD_REQUEST";
100            default:
101                return "UNKNOWN";
102            }
103        }
104
105        /** not used */
106        private Status() {}
107    }
108
109    /**
110     * Hidden constructor. This is only used in framework.
111     *
112     * @param serviceType service discovery type.
113     * @param status status code.
114     * @param transId transaction id.
115     * @param device source device.
116     * @param data query data.
117     */
118    protected WifiP2pServiceResponse(int serviceType, int status, int transId,
119            WifiP2pDevice device, byte[] data) {
120        mServiceType = serviceType;
121        mStatus = status;
122        mTransId = transId;
123        mDevice = device;
124        mData = data;
125    }
126
127    /**
128     * Return the service type of service discovery response.
129     *
130     * @return service discovery type.<br>
131     * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_BONJOUR}
132     */
133    public int getServiceType() {
134        return mServiceType;
135    }
136
137    /**
138     * Return the status code of service discovery response.
139     *
140     * @return status code.
141     * @see Status
142     */
143    public int getStatus() {
144        return mStatus;
145    }
146
147    /**
148     * Return the transaction id of service discovery response.
149     *
150     * @return transaction id.
151     * @hide
152     */
153    public int getTransactionId() {
154        return mTransId;
155    }
156
157    /**
158     * Return response data.
159     *
160     * <pre>Data format depends on service type
161     *
162     * @return a query or response data.
163     */
164    public byte[] getRawData() {
165        return mData;
166    }
167
168    /**
169     * Returns the source device of service discovery response.
170     *
171     * <pre>This is valid only when service discovery response.
172     *
173     * @return the source device of service discovery response.
174     */
175    public WifiP2pDevice getSrcDevice() {
176        return mDevice;
177    }
178
179    /** @hide */
180    public void setSrcDevice(WifiP2pDevice dev) {
181        if (dev == null) return;
182        this.mDevice = dev;
183    }
184
185
186    /**
187     * Create the list of  WifiP2pServiceResponse instance from supplicant event.
188     *
189     * <pre>The format is as follows.
190     * P2P-SERV-DISC-RESP &lt;address&gt; &lt;update indicator&gt; &lt;response data&gt;
191     * e.g) P2P-SERV-DISC-RESP 02:03:7f:11:62:da 1 0300000101
192     *
193     * @param supplicantEvent wpa_supplicant event string.
194     * @return if parse failed, return null
195     * @hide
196     */
197    public static List<WifiP2pServiceResponse> newInstance(String supplicantEvent) {
198
199        List<WifiP2pServiceResponse> respList = new ArrayList<WifiP2pServiceResponse>();
200        String[] args = supplicantEvent.split(" ");
201        if (args.length != 4) {
202            return null;
203        }
204        WifiP2pDevice dev = new WifiP2pDevice();
205        String srcAddr = args[1];
206        dev.deviceAddress = srcAddr;
207        //String updateIndicator = args[2];//not used.
208        byte[] bin = hexStr2Bin(args[3]);
209        if (bin == null) {
210            return null;
211        }
212
213        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bin));
214        try {
215            while (dis.available() > 0) {
216                /*
217                 * Service discovery header is as follows.
218                 * ______________________________________________________________
219                 * |           Length(2byte)     | Type(1byte) | TransId(1byte)}|
220                 * ______________________________________________________________
221                 * | status(1byte)  |            vendor specific(variable)      |
222                 */
223                // The length equals to 3 plus the number of octets in the vendor
224                // specific content field. And this is little endian.
225                int length = ((dis.readByte() & 0xff) +
226                        ((dis.readByte() & 0xff) << 8)) - 3;
227                int type = dis.readUnsignedByte();
228                byte transId = dis.readByte();
229                int status = dis.readUnsignedByte();
230                if (length < 0) {
231                    return null;
232                }
233                if (length == 0) {
234                    if (status == Status.SUCCESS) {
235                        respList.add(new WifiP2pServiceResponse(type, status,
236                            transId, dev, null));
237                    }
238                    continue;
239                }
240                if (length > MAX_BUF_SIZE) {
241                    dis.skip(length);
242                    continue;
243                }
244                byte[] data = new byte[length];
245                dis.readFully(data);
246
247                WifiP2pServiceResponse resp;
248                if (type ==  WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
249                    resp = WifiP2pDnsSdServiceResponse.newInstance(status,
250                            transId, dev, data);
251                } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
252                    resp = WifiP2pUpnpServiceResponse.newInstance(status,
253                            transId, dev, data);
254                } else {
255                    resp = new WifiP2pServiceResponse(type, status, transId, dev, data);
256                }
257                if (resp != null && resp.getStatus() == Status.SUCCESS) {
258                    respList.add(resp);
259                }
260            }
261            return respList;
262        } catch (IOException e) {
263            e.printStackTrace();
264        }
265
266        if (respList.size() > 0) {
267            return respList;
268        }
269        return null;
270    }
271
272    /**
273     * Converts hex string to byte array.
274     *
275     * @param hex hex string. if invalid, return null.
276     * @return binary data.
277     */
278    private static byte[] hexStr2Bin(String hex) {
279        int sz = hex.length()/2;
280        byte[] b = new byte[hex.length()/2];
281
282        for (int i=0;i<sz;i++) {
283            try {
284                b[i] = (byte)Integer.parseInt(hex.substring(i*2, i*2+2), 16);
285            } catch (Exception e) {
286                e.printStackTrace();
287                return null;
288            }
289        }
290        return b;
291    }
292
293    @Override
294    public String toString() {
295        StringBuffer sbuf = new StringBuffer();
296        sbuf.append("serviceType:").append(mServiceType);
297        sbuf.append(" status:").append(Status.toString(mStatus));
298        sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
299        sbuf.append(" data:").append(mData);
300        return sbuf.toString();
301    }
302
303    @Override
304    public boolean equals(Object o) {
305        if (o == this) {
306            return true;
307        }
308        if (!(o instanceof WifiP2pServiceResponse)) {
309            return false;
310        }
311
312        WifiP2pServiceResponse req = (WifiP2pServiceResponse)o;
313
314        return (req.mServiceType == mServiceType) &&
315            (req.mStatus == mStatus) &&
316                equals(req.mDevice.deviceAddress, mDevice.deviceAddress) &&
317                Arrays.equals(req.mData, mData);
318    }
319
320    private boolean equals(Object a, Object b) {
321        if (a == null && b == null) {
322            return true;
323        } else if (a != null) {
324            return a.equals(b);
325        }
326        return false;
327    }
328
329    @Override
330    public int hashCode() {
331        int result = 17;
332        result = 31 * result + mServiceType;
333        result = 31 * result + mStatus;
334        result = 31 * result + mTransId;
335        result = 31 * result + (mDevice.deviceAddress == null ?
336                0 : mDevice.deviceAddress.hashCode());
337        result = 31 * result + (mData == null ? 0 : mData.hashCode());
338        return result;
339    }
340
341    /** Implement the Parcelable interface {@hide} */
342    public int describeContents() {
343        return 0;
344    }
345
346    /** Implement the Parcelable interface {@hide} */
347    public void writeToParcel(Parcel dest, int flags) {
348        dest.writeInt(mServiceType);
349        dest.writeInt(mStatus);
350        dest.writeInt(mTransId);
351        dest.writeParcelable(mDevice, flags);
352        if (mData == null || mData.length == 0) {
353            dest.writeInt(0);
354        } else {
355            dest.writeInt(mData.length);
356            dest.writeByteArray(mData);
357        }
358    }
359
360    /** Implement the Parcelable interface {@hide} */
361    public static final Creator<WifiP2pServiceResponse> CREATOR =
362        new Creator<WifiP2pServiceResponse>() {
363            public WifiP2pServiceResponse createFromParcel(Parcel in) {
364
365                int type = in.readInt();
366                int status = in.readInt();
367                int transId = in.readInt();
368                WifiP2pDevice dev = (WifiP2pDevice)in.readParcelable(null);
369                int len = in.readInt();
370                byte[] data = null;
371                if (len > 0) {
372                    data = new byte[len];
373                    in.readByteArray(data);
374                }
375                if (type ==  WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
376                    return WifiP2pDnsSdServiceResponse.newInstance(status,
377                            transId, dev, data);
378                } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
379                    return WifiP2pUpnpServiceResponse.newInstance(status,
380                            transId, dev, data);
381                }
382                return new WifiP2pServiceResponse(type, status, transId, dev, data);
383            }
384
385            public WifiP2pServiceResponse[] newArray(int size) {
386                return new WifiP2pServiceResponse[size];
387            }
388        };
389}
390