1 /*
2  * Copyright (C) 2011 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;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.net.MacAddress;
25 import android.net.wifi.OuiKeyedData;
26 import android.net.wifi.ParcelUtil;
27 import android.os.Build;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.Log;
31 
32 import androidx.annotation.RequiresApi;
33 
34 import com.android.modules.utils.build.SdkLevel;
35 import com.android.wifi.flags.Flags;
36 
37 import java.net.Inet4Address;
38 import java.net.InetAddress;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45 
46 /**
47  * A class representing a Wi-Fi P2p group. A p2p group consists of a single group
48  * owner and one or more clients. In the case of a group with only two devices, one
49  * will be the group owner and the other will be a group client.
50  *
51  * {@see WifiP2pManager}
52  */
53 public class WifiP2pGroup implements Parcelable {
54 
55     /**
56      * The temporary network id.
57      * @see #getNetworkId()
58      */
59     public static final int NETWORK_ID_TEMPORARY = -1;
60 
61     /**
62      * The temporary network id.
63      *
64      * @hide
65      */
66     @UnsupportedAppUsage
67     public static final int TEMPORARY_NET_ID = NETWORK_ID_TEMPORARY;
68 
69     /**
70      * The persistent network id.
71      * If a matching persistent profile is found, use it.
72      * Otherwise, create a new persistent profile.
73      * @see #getNetworkId()
74      */
75     public static final int NETWORK_ID_PERSISTENT = -2;
76 
77     /**
78      * Group owner P2P interface MAC address.
79      * @hide
80      */
81     @UnsupportedAppUsage
82     public byte[] interfaceAddress;
83 
84     /** The network name */
85     private String mNetworkName;
86 
87     /** Group owner */
88     private WifiP2pDevice mOwner;
89 
90     /** Device is group owner */
91     private boolean mIsGroupOwner;
92 
93     /** Group clients */
94     private List<WifiP2pDevice> mClients = new ArrayList<WifiP2pDevice>();
95 
96     /** The passphrase used for WPA2-PSK */
97     private String mPassphrase;
98 
99     private String mInterface;
100 
101     /** The network ID in wpa_supplicant */
102     private int mNetId;
103 
104     /** The frequency (in MHz) used by this group */
105     private int mFrequency;
106 
107     /** List of {@link OuiKeyedData} providing vendor-specific configuration data. */
108     private @NonNull List<OuiKeyedData> mVendorData = Collections.emptyList();
109 
110     /**
111      * P2P Client IPV4 address allocated via EAPOL-Key exchange.
112      * @hide
113      */
114     public static class P2pGroupClientEapolIpAddressData {
115         /*
116          * The P2P Client IP address.
117          */
118         public final Inet4Address mIpAddressClient;
119         /*
120          * The P2P Group Owner IP address.
121          */
122         public final Inet4Address mIpAddressGo;
123         /*
124          * The subnet that the P2P Group Owner is using.
125          */
126         public final Inet4Address mIpAddressMask;
127 
128         /*
129          * Set P2pClientEapolIpAddressData
130          */
P2pGroupClientEapolIpAddressData(Inet4Address ipAddressClient, Inet4Address ipAddressGo, Inet4Address ipAddressMask)131         public P2pGroupClientEapolIpAddressData(Inet4Address ipAddressClient,
132                 Inet4Address ipAddressGo, Inet4Address ipAddressMask) {
133             this.mIpAddressClient = ipAddressClient;
134             this.mIpAddressGo = ipAddressGo;
135             this.mIpAddressMask = ipAddressMask;
136         }
137     }
138 
139     /**
140      * P2P Client IP address information obtained via EAPOL Handshake.
141      * @hide
142      */
143     public P2pGroupClientEapolIpAddressData p2pClientEapolIpInfo;
144 
145     /** P2P group started string pattern */
146     private static final Pattern groupStartedPattern = Pattern.compile(
147         "ssid=\"(.+)\" " +
148         "freq=(\\d+) " +
149         "(?:psk=)?([0-9a-fA-F]{64})?" +
150         "(?:passphrase=)?(?:\"(.{0,63})\")? " +
151         "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" +
152         " ?(\\[PERSISTENT\\])?"
153     );
154 
WifiP2pGroup()155     public WifiP2pGroup() {
156     }
157 
158     /**
159      * @param supplicantEvent formats supported include
160      *
161      *  P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
162      *  [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|
163      *  passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT]
164      *
165      *  P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED
166      *
167      *  P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13
168      *  bssid=fa:7b:7a:42:82:13 unknown-network
169      *
170      *  P2P-INVITATION-RECEIVED sa=b8:f9:34:2a:c7:9d persistent=0
171      *
172      *  Note: The events formats can be looked up in the wpa_supplicant code
173      *  @hide
174      */
175     @UnsupportedAppUsage
WifiP2pGroup(String supplicantEvent)176     public WifiP2pGroup(String supplicantEvent) throws IllegalArgumentException {
177 
178         String[] tokens = supplicantEvent.split(" ");
179 
180         if (tokens.length < 3) {
181             throw new IllegalArgumentException("Malformed supplicant event");
182         }
183 
184         if (tokens[0].startsWith("P2P-GROUP")) {
185             mInterface = tokens[1];
186             mIsGroupOwner = tokens[2].equals("GO");
187 
188             Matcher match = groupStartedPattern.matcher(supplicantEvent);
189             if (!match.find()) {
190                 return;
191             }
192 
193             mNetworkName = match.group(1);
194             // It throws NumberFormatException if the string cannot be parsed as an integer.
195             mFrequency = Integer.parseInt(match.group(2));
196             // psk is unused right now
197             //String psk = match.group(3);
198             mPassphrase = match.group(4);
199             mOwner = new WifiP2pDevice(match.group(5));
200             if (match.group(6) != null) {
201                 mNetId = NETWORK_ID_PERSISTENT;
202             } else {
203                 mNetId = NETWORK_ID_TEMPORARY;
204             }
205         } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) {
206             String sa = null;
207             mNetId = NETWORK_ID_PERSISTENT;
208             for (String token : tokens) {
209                 String[] nameValue = token.split("=");
210                 if (nameValue.length != 2) continue;
211 
212                 if (nameValue[0].equals("sa")) {
213                     sa = nameValue[1];
214 
215                     // set source address into the client list.
216                     WifiP2pDevice dev = new WifiP2pDevice();
217                     dev.deviceAddress = nameValue[1];
218                     mClients.add(dev);
219                     continue;
220                 }
221 
222                 if (nameValue[0].equals("go_dev_addr")) {
223                     mOwner = new WifiP2pDevice(nameValue[1]);
224                     continue;
225                 }
226 
227                 if (nameValue[0].equals("persistent")) {
228                     mNetId = Integer.parseInt(nameValue[1]);
229                     continue;
230                 }
231             }
232         } else {
233             throw new IllegalArgumentException("Malformed supplicant event");
234         }
235     }
236 
237     /** @hide */
setNetworkName(String networkName)238     public void setNetworkName(String networkName) {
239         mNetworkName = networkName;
240     }
241 
242     /**
243      * Get the network name (SSID) of the group. Legacy Wi-Fi clients will discover
244      * the p2p group using the network name.
245      */
getNetworkName()246     public String getNetworkName() {
247         return mNetworkName;
248     }
249 
250     /** @hide */
251     @UnsupportedAppUsage
setIsGroupOwner(boolean isGo)252     public void setIsGroupOwner(boolean isGo) {
253         mIsGroupOwner = isGo;
254     }
255 
256     /** Check whether this device is the group owner of the created p2p group */
isGroupOwner()257     public boolean isGroupOwner() {
258         return mIsGroupOwner;
259     }
260 
261     /** @hide */
setOwner(WifiP2pDevice device)262     public void setOwner(WifiP2pDevice device) {
263         mOwner = device;
264     }
265 
266     /** Get the details of the group owner as a {@link WifiP2pDevice} object */
getOwner()267     public WifiP2pDevice getOwner() {
268         return mOwner;
269     }
270 
271     /** @hide */
addClient(String address)272     public void addClient(String address) {
273         addClient(new WifiP2pDevice(address));
274     }
275 
276     /** @hide */
addClient(WifiP2pDevice device)277     public void addClient(WifiP2pDevice device) {
278         for (WifiP2pDevice client : mClients) {
279             if (client.equals(device)) return;
280         }
281         mClients.add(new WifiP2pDevice(device));
282     }
283 
284     /** @hide */
setClientInterfaceMacAddress(@onNull String deviceAddress, @NonNull final MacAddress interfaceMacAddress)285     public void setClientInterfaceMacAddress(@NonNull String deviceAddress,
286             @NonNull final MacAddress interfaceMacAddress) {
287         if (null == interfaceMacAddress) {
288             Log.e("setClientInterfaceMacAddress", "cannot set null interface mac address");
289             return;
290         }
291         for (WifiP2pDevice client : mClients) {
292             if (client.deviceAddress.equals(deviceAddress)) {
293                 Log.i("setClientInterfaceMacAddress", "device: " + deviceAddress
294                         + " interfaceAddress: " + interfaceMacAddress.toString());
295                 client.setInterfaceMacAddress(interfaceMacAddress);
296                 break;
297             }
298         }
299     }
300     /** @hide */
setClientIpAddress(@onNull final MacAddress interfaceMacAddress, @NonNull final InetAddress ipAddress)301     public void setClientIpAddress(@NonNull final MacAddress interfaceMacAddress,
302             @NonNull final InetAddress ipAddress) {
303         if (null == interfaceMacAddress) {
304             Log.e("setClientIpAddress", "cannot set IP address with null interface mac address");
305             return;
306         }
307         if (null == ipAddress) {
308             Log.e("setClientIpAddress", "Null IP - Failed to set IP address in WifiP2pDevice");
309             return;
310         }
311         for (WifiP2pDevice client : mClients) {
312             if (interfaceMacAddress.equals(client.getInterfaceMacAddress())) {
313                 Log.i("setClientIpAddress", "Update the IP address"
314                         + " device: " + client.deviceAddress + " interfaceAddress: "
315                         + interfaceMacAddress.toString() + " IP: " + ipAddress.getHostAddress());
316                 client.setIpAddress(ipAddress);
317                 break;
318             }
319         }
320     }
321 
322     /** @hide */
removeClient(String address)323     public boolean removeClient(String address) {
324         return mClients.remove(new WifiP2pDevice(address));
325     }
326 
327     /** @hide */
removeClient(WifiP2pDevice device)328     public boolean removeClient(WifiP2pDevice device) {
329         return mClients.remove(device);
330     }
331 
332     /** @hide */
333     @UnsupportedAppUsage
isClientListEmpty()334     public boolean isClientListEmpty() {
335         return mClients.size() == 0;
336     }
337 
338     /**
339      * Returns {@code true} if the device is part of the group, {@code false} otherwise.
340      *
341      * @hide
342      */
contains(@ullable WifiP2pDevice device)343     public boolean contains(@Nullable WifiP2pDevice device) {
344         return mOwner.equals(device) || mClients.contains(device);
345     }
346 
347     /** Get the list of clients currently part of the p2p group */
getClientList()348     public Collection<WifiP2pDevice> getClientList() {
349         return Collections.unmodifiableCollection(mClients);
350     }
351 
352     /** @hide */
setPassphrase(String passphrase)353     public void setPassphrase(String passphrase) {
354         mPassphrase = passphrase;
355     }
356 
357     /**
358      * Get the passphrase of the group. This function will return a valid passphrase only
359      * at the group owner. Legacy Wi-Fi clients will need this passphrase alongside
360      * network name obtained from {@link #getNetworkName()} to join the group
361      */
getPassphrase()362     public String getPassphrase() {
363         return mPassphrase;
364     }
365 
366     /** @hide */
367     @UnsupportedAppUsage
setInterface(String intf)368     public void setInterface(String intf) {
369         mInterface = intf;
370     }
371 
372     /** Get the interface name on which the group is created */
getInterface()373     public String getInterface() {
374         return mInterface;
375     }
376 
377     /** The network ID of the P2P group in wpa_supplicant. */
getNetworkId()378     public int getNetworkId() {
379         return mNetId;
380     }
381 
382     /** @hide */
383     @UnsupportedAppUsage
setNetworkId(int netId)384     public void setNetworkId(int netId) {
385         this.mNetId = netId;
386     }
387 
388     /** Get the operating frequency (in MHz) of the p2p group */
getFrequency()389     public int getFrequency() {
390         return mFrequency;
391     }
392 
393     /** @hide */
setFrequency(int freq)394     public void setFrequency(int freq) {
395         this.mFrequency = freq;
396     }
397 
398     /**
399      * Return the vendor-provided configuration data, if it exists. See also {@link
400      * #setVendorData(List)}
401      *
402      * @return Vendor configuration data, or empty list if it does not exist.
403      * @hide
404      */
405     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
406     @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
407     @NonNull
408     @SystemApi
getVendorData()409     public List<OuiKeyedData> getVendorData() {
410         if (!SdkLevel.isAtLeastV()) {
411             throw new UnsupportedOperationException();
412         }
413         return mVendorData;
414     }
415 
416     /**
417      * Set additional vendor-provided configuration data.
418      *
419      * @param vendorData List of {@link OuiKeyedData} containing the vendor-provided
420      *     configuration data. Note that multiple elements with the same OUI are allowed.
421      * @hide
422      */
423     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
424     @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
425     @SystemApi
setVendorData(@onNull List<OuiKeyedData> vendorData)426     public void setVendorData(@NonNull List<OuiKeyedData> vendorData) {
427         if (!SdkLevel.isAtLeastV()) {
428             throw new UnsupportedOperationException();
429         }
430         if (vendorData == null) {
431             throw new IllegalArgumentException("setVendorData received a null value");
432         }
433         mVendorData = new ArrayList<>(vendorData);
434     }
435 
toString()436     public String toString() {
437         StringBuffer sbuf = new StringBuffer();
438         sbuf.append("network: ").append(mNetworkName);
439         sbuf.append("\n isGO: ").append(mIsGroupOwner);
440         sbuf.append("\n GO: ").append(mOwner);
441         for (WifiP2pDevice client : mClients) {
442             sbuf.append("\n Client: ").append(client);
443         }
444         sbuf.append("\n interface: ").append(mInterface);
445         sbuf.append("\n networkId: ").append(mNetId);
446         sbuf.append("\n frequency: ").append(mFrequency);
447         sbuf.append("\n vendorData: ").append(mVendorData);
448         return sbuf.toString();
449     }
450 
451     /** Implement the Parcelable interface */
describeContents()452     public int describeContents() {
453         return 0;
454     }
455 
456     /** copy constructor */
WifiP2pGroup(WifiP2pGroup source)457     public WifiP2pGroup(WifiP2pGroup source) {
458         if (source != null) {
459             mNetworkName = source.getNetworkName();
460             mOwner = new WifiP2pDevice(source.getOwner());
461             mIsGroupOwner = source.mIsGroupOwner;
462             for (WifiP2pDevice d : source.getClientList()) mClients.add(d);
463             mPassphrase = source.getPassphrase();
464             mInterface = source.getInterface();
465             mNetId = source.getNetworkId();
466             mFrequency = source.getFrequency();
467             if (SdkLevel.isAtLeastV()) {
468                 mVendorData = new ArrayList<>(source.getVendorData());
469             }
470         }
471     }
472 
473     /** Implement the Parcelable interface */
writeToParcel(Parcel dest, int flags)474     public void writeToParcel(Parcel dest, int flags) {
475         dest.writeString(mNetworkName);
476         dest.writeParcelable(mOwner, flags);
477         dest.writeByte(mIsGroupOwner ? (byte) 1: (byte) 0);
478         dest.writeInt(mClients.size());
479         for (WifiP2pDevice client : mClients) {
480             dest.writeParcelable(client, flags);
481         }
482         dest.writeString(mPassphrase);
483         dest.writeString(mInterface);
484         dest.writeInt(mNetId);
485         dest.writeInt(mFrequency);
486         if (SdkLevel.isAtLeastV()) {
487             dest.writeList(mVendorData);
488         }
489     }
490 
491     /** Implement the Parcelable interface */
492     public static final @android.annotation.NonNull Creator<WifiP2pGroup> CREATOR =
493         new Creator<WifiP2pGroup>() {
494             public WifiP2pGroup createFromParcel(Parcel in) {
495                 WifiP2pGroup group = new WifiP2pGroup();
496                 group.setNetworkName(in.readString());
497                 group.setOwner((WifiP2pDevice)in.readParcelable(null));
498                 group.setIsGroupOwner(in.readByte() == (byte)1);
499                 int clientCount = in.readInt();
500                 for (int i=0; i<clientCount; i++) {
501                     group.addClient((WifiP2pDevice) in.readParcelable(null));
502                 }
503                 group.setPassphrase(in.readString());
504                 group.setInterface(in.readString());
505                 group.setNetworkId(in.readInt());
506                 group.setFrequency(in.readInt());
507                 if (SdkLevel.isAtLeastV()) {
508                     group.setVendorData(ParcelUtil.readOuiKeyedDataList(in));
509                 }
510                 return group;
511             }
512 
513             public WifiP2pGroup[] newArray(int size) {
514                 return new WifiP2pGroup[size];
515             }
516         };
517 }
518