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