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.WifiP2pManager;
20import android.os.Parcel;
21import android.os.Parcelable;
22
23/**
24 * A class for creating a service discovery request for use with
25 * {@link WifiP2pManager#addServiceRequest} and {@link WifiP2pManager#removeServiceRequest}
26 *
27 * <p>This class is used to create service discovery request for custom
28 * vendor specific service discovery protocol {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}
29 * or to search all service protocols {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}.
30 *
31 * <p>For the purpose of creating a UPnP or Bonjour service request, use
32 * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest} respectively.
33 *
34 * {@see WifiP2pManager}
35 * {@see WifiP2pUpnpServiceRequest}
36 * {@see WifiP2pDnsSdServiceRequest}
37 */
38public class WifiP2pServiceRequest implements Parcelable {
39
40    /**
41     * Service discovery protocol. It's defined in table63 in Wi-Fi Direct specification.
42     */
43    private int mProtocolType;
44
45    /**
46     * The length of the service request TLV.
47     * The value is equal to 2 plus the number of octets in the
48     * query data field.
49     */
50    private int mLength;
51
52    /**
53     * Service transaction ID.
54     * This is a nonzero value used to match the service request/response TLVs.
55     */
56    private int mTransId;
57
58    /**
59     * The hex dump string of query data for the requested service information.
60     *
61     * e.g) DnsSd apple file sharing over tcp (dns name=_afpovertcp._tcp.local.)
62     * 0b5f6166706f766572746370c00c000c01
63     */
64    private String mQuery;
65
66    /**
67     * This constructor is only used in newInstance().
68     *
69     * @param protocolType service discovery protocol.
70     * @param query The part of service specific query.
71     * @hide
72     */
73    protected WifiP2pServiceRequest(int protocolType, String query) {
74        validateQuery(query);
75
76        mProtocolType = protocolType;
77        mQuery = query;
78        if (query != null) {
79            mLength = query.length()/2 + 2;
80        } else {
81            mLength = 2;
82        }
83    }
84
85    /**
86     * This constructor is only used in Parcelable.
87     *
88     * @param serviceType service discovery type.
89     * @param length the length of service discovery packet.
90     * @param transId the transaction id
91     * @param query The part of service specific query.
92     */
93    private WifiP2pServiceRequest(int serviceType, int length,
94            int transId, String query) {
95        mProtocolType = serviceType;
96        mLength = length;
97        mTransId = transId;
98        mQuery = query;
99    }
100
101    /**
102     * Return transaction id.
103     *
104     * @return transaction id
105     * @hide
106     */
107    public int getTransactionId() {
108        return mTransId;
109    }
110
111    /**
112     * Set transaction id.
113     *
114     * @param id
115     * @hide
116     */
117    public void setTransactionId(int id) {
118        mTransId = id;
119    }
120
121    /**
122     * Return wpa_supplicant request string.
123     *
124     * The format is the hex dump of the following frame.
125     * <pre>
126     * _______________________________________________________________
127     * |        Length (2)        |   Type (1)   | Transaction ID (1) |
128     * |                  Query Data (variable)                       |
129     * </pre>
130     *
131     * @return wpa_supplicant request string.
132     * @hide
133     */
134    public String getSupplicantQuery() {
135        StringBuffer sb = new StringBuffer();
136        // length is retained as little endian format.
137        sb.append(String.format("%02x", (mLength) & 0xff));
138        sb.append(String.format("%02x", (mLength >> 8) & 0xff));
139        sb.append(String.format("%02x", mProtocolType));
140        sb.append(String.format("%02x", mTransId));
141        if (mQuery != null) {
142            sb.append(mQuery);
143        }
144
145        return sb.toString();
146    }
147
148    /**
149     * Validate query.
150     *
151     * <p>If invalid, throw IllegalArgumentException.
152     * @param query The part of service specific query.
153     */
154    private void validateQuery(String query) {
155        if (query == null) {
156            return;
157        }
158
159        int UNSIGNED_SHORT_MAX = 0xffff;
160        if (query.length()%2 == 1) {
161            throw new IllegalArgumentException(
162                    "query size is invalid. query=" + query);
163        }
164        if (query.length()/2 > UNSIGNED_SHORT_MAX) {
165            throw new IllegalArgumentException(
166                    "query size is too large. len=" + query.length());
167        }
168
169        // check whether query is hex string.
170        query = query.toLowerCase();
171        char[] chars = query.toCharArray();
172        for (char c: chars) {
173            if (!((c >= '0' && c <= '9') ||
174                    (c >= 'a' && c <= 'f'))){
175                throw new IllegalArgumentException(
176                        "query should be hex string. query=" + query);
177            }
178        }
179    }
180
181    /**
182     * Create a service discovery request.
183     *
184     * @param protocolType can be {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}
185     * or {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
186     * In order to create a UPnP or Bonjour service request, use
187     * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest}
188     * respectively
189     *
190     * @param queryData hex string that is vendor specific.  Can be null.
191     * @return service discovery request.
192     */
193    public static WifiP2pServiceRequest newInstance(int protocolType, String queryData) {
194        return new WifiP2pServiceRequest(protocolType, queryData);
195    }
196
197    /**
198     * Create a service discovery request.
199     *
200     * @param protocolType can be {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL}
201     * or {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
202     * In order to create a UPnP or Bonjour service request, use
203     * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pDnsSdServiceRequest}
204     * respectively
205     *
206     * @return service discovery request.
207     */
208    public static WifiP2pServiceRequest newInstance(int protocolType ) {
209        return new WifiP2pServiceRequest(protocolType, null);
210    }
211
212    @Override
213    public boolean equals(Object o) {
214        if (o == this) {
215            return true;
216        }
217        if (!(o instanceof WifiP2pServiceRequest)) {
218            return false;
219        }
220
221        WifiP2pServiceRequest req = (WifiP2pServiceRequest)o;
222
223        /*
224         * Not compare transaction id.
225         * Transaction id may be changed on each service discovery operation.
226         */
227        if ((req.mProtocolType != mProtocolType) ||
228                (req.mLength != mLength)) {
229            return false;
230        }
231
232        if (req.mQuery == null && mQuery == null) {
233            return true;
234        } else if (req.mQuery != null) {
235            return req.mQuery.equals(mQuery);
236        }
237        return false;
238   }
239
240    @Override
241    public int hashCode() {
242        int result = 17;
243        result = 31 * result + mProtocolType;
244        result = 31 * result + mLength;
245        result = 31 * result + (mQuery == null ? 0 : mQuery.hashCode());
246        return result;
247    }
248
249    /** Implement the Parcelable interface {@hide} */
250    public int describeContents() {
251        return 0;
252    }
253
254    /** Implement the Parcelable interface {@hide} */
255    public void writeToParcel(Parcel dest, int flags) {
256        dest.writeInt(mProtocolType);
257        dest.writeInt(mLength);
258        dest.writeInt(mTransId);
259        dest.writeString(mQuery);
260    }
261
262    /** Implement the Parcelable interface {@hide} */
263    public static final Creator<WifiP2pServiceRequest> CREATOR =
264        new Creator<WifiP2pServiceRequest>() {
265            public WifiP2pServiceRequest createFromParcel(Parcel in) {
266                int servType = in.readInt();
267                int length = in.readInt();
268                int transId = in.readInt();
269                String query = in.readString();
270                return new WifiP2pServiceRequest(servType, length, transId, query);
271            }
272
273            public WifiP2pServiceRequest[] newArray(int size) {
274                return new WifiP2pServiceRequest[size];
275            }
276        };
277}
278