/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.wifi.p2p; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.MacAddress; import android.net.wifi.OuiKeyedData; import android.net.wifi.ParcelUtil; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A class representing a Wi-Fi P2p group. A p2p group consists of a single group * owner and one or more clients. In the case of a group with only two devices, one * will be the group owner and the other will be a group client. * * {@see WifiP2pManager} */ public class WifiP2pGroup implements Parcelable { /** * The temporary network id. * @see #getNetworkId() */ public static final int NETWORK_ID_TEMPORARY = -1; /** * The temporary network id. * * @hide */ @UnsupportedAppUsage public static final int TEMPORARY_NET_ID = NETWORK_ID_TEMPORARY; /** * The persistent network id. * If a matching persistent profile is found, use it. * Otherwise, create a new persistent profile. * @see #getNetworkId() */ public static final int NETWORK_ID_PERSISTENT = -2; /** * Group owner P2P interface MAC address. * @hide */ @UnsupportedAppUsage public byte[] interfaceAddress; /** The network name */ private String mNetworkName; /** Group owner */ private WifiP2pDevice mOwner; /** Device is group owner */ private boolean mIsGroupOwner; /** Group clients */ private List mClients = new ArrayList(); /** The passphrase used for WPA2-PSK */ private String mPassphrase; private String mInterface; /** The network ID in wpa_supplicant */ private int mNetId; /** The frequency (in MHz) used by this group */ private int mFrequency; /** List of {@link OuiKeyedData} providing vendor-specific configuration data. */ private @NonNull List mVendorData = Collections.emptyList(); /** * P2P Client IPV4 address allocated via EAPOL-Key exchange. * @hide */ public static class P2pGroupClientEapolIpAddressData { /* * The P2P Client IP address. */ public final Inet4Address mIpAddressClient; /* * The P2P Group Owner IP address. */ public final Inet4Address mIpAddressGo; /* * The subnet that the P2P Group Owner is using. */ public final Inet4Address mIpAddressMask; /* * Set P2pClientEapolIpAddressData */ public P2pGroupClientEapolIpAddressData(Inet4Address ipAddressClient, Inet4Address ipAddressGo, Inet4Address ipAddressMask) { this.mIpAddressClient = ipAddressClient; this.mIpAddressGo = ipAddressGo; this.mIpAddressMask = ipAddressMask; } } /** * P2P Client IP address information obtained via EAPOL Handshake. * @hide */ public P2pGroupClientEapolIpAddressData p2pClientEapolIpInfo; /** P2P group started string pattern */ private static final Pattern groupStartedPattern = Pattern.compile( "ssid=\"(.+)\" " + "freq=(\\d+) " + "(?:psk=)?([0-9a-fA-F]{64})?" + "(?:passphrase=)?(?:\"(.{0,63})\")? " + "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" + " ?(\\[PERSISTENT\\])?" ); public WifiP2pGroup() { } /** * @param supplicantEvent formats supported include * * P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437 * [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc| * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] * * P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED * * P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13 * bssid=fa:7b:7a:42:82:13 unknown-network * * P2P-INVITATION-RECEIVED sa=b8:f9:34:2a:c7:9d persistent=0 * * Note: The events formats can be looked up in the wpa_supplicant code * @hide */ @UnsupportedAppUsage public WifiP2pGroup(String supplicantEvent) throws IllegalArgumentException { String[] tokens = supplicantEvent.split(" "); if (tokens.length < 3) { throw new IllegalArgumentException("Malformed supplicant event"); } if (tokens[0].startsWith("P2P-GROUP")) { mInterface = tokens[1]; mIsGroupOwner = tokens[2].equals("GO"); Matcher match = groupStartedPattern.matcher(supplicantEvent); if (!match.find()) { return; } mNetworkName = match.group(1); // It throws NumberFormatException if the string cannot be parsed as an integer. mFrequency = Integer.parseInt(match.group(2)); // psk is unused right now //String psk = match.group(3); mPassphrase = match.group(4); mOwner = new WifiP2pDevice(match.group(5)); if (match.group(6) != null) { mNetId = NETWORK_ID_PERSISTENT; } else { mNetId = NETWORK_ID_TEMPORARY; } } else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) { String sa = null; mNetId = NETWORK_ID_PERSISTENT; for (String token : tokens) { String[] nameValue = token.split("="); if (nameValue.length != 2) continue; if (nameValue[0].equals("sa")) { sa = nameValue[1]; // set source address into the client list. WifiP2pDevice dev = new WifiP2pDevice(); dev.deviceAddress = nameValue[1]; mClients.add(dev); continue; } if (nameValue[0].equals("go_dev_addr")) { mOwner = new WifiP2pDevice(nameValue[1]); continue; } if (nameValue[0].equals("persistent")) { mNetId = Integer.parseInt(nameValue[1]); continue; } } } else { throw new IllegalArgumentException("Malformed supplicant event"); } } /** @hide */ public void setNetworkName(String networkName) { mNetworkName = networkName; } /** * Get the network name (SSID) of the group. Legacy Wi-Fi clients will discover * the p2p group using the network name. */ public String getNetworkName() { return mNetworkName; } /** @hide */ @UnsupportedAppUsage public void setIsGroupOwner(boolean isGo) { mIsGroupOwner = isGo; } /** Check whether this device is the group owner of the created p2p group */ public boolean isGroupOwner() { return mIsGroupOwner; } /** @hide */ public void setOwner(WifiP2pDevice device) { mOwner = device; } /** Get the details of the group owner as a {@link WifiP2pDevice} object */ public WifiP2pDevice getOwner() { return mOwner; } /** @hide */ public void addClient(String address) { addClient(new WifiP2pDevice(address)); } /** @hide */ public void addClient(WifiP2pDevice device) { for (WifiP2pDevice client : mClients) { if (client.equals(device)) return; } mClients.add(new WifiP2pDevice(device)); } /** @hide */ public void setClientInterfaceMacAddress(@NonNull String deviceAddress, @NonNull final MacAddress interfaceMacAddress) { if (null == interfaceMacAddress) { Log.e("setClientInterfaceMacAddress", "cannot set null interface mac address"); return; } for (WifiP2pDevice client : mClients) { if (client.deviceAddress.equals(deviceAddress)) { Log.i("setClientInterfaceMacAddress", "device: " + deviceAddress + " interfaceAddress: " + interfaceMacAddress.toString()); client.setInterfaceMacAddress(interfaceMacAddress); break; } } } /** @hide */ public void setClientIpAddress(@NonNull final MacAddress interfaceMacAddress, @NonNull final InetAddress ipAddress) { if (null == interfaceMacAddress) { Log.e("setClientIpAddress", "cannot set IP address with null interface mac address"); return; } if (null == ipAddress) { Log.e("setClientIpAddress", "Null IP - Failed to set IP address in WifiP2pDevice"); return; } for (WifiP2pDevice client : mClients) { if (interfaceMacAddress.equals(client.getInterfaceMacAddress())) { Log.i("setClientIpAddress", "Update the IP address" + " device: " + client.deviceAddress + " interfaceAddress: " + interfaceMacAddress.toString() + " IP: " + ipAddress.getHostAddress()); client.setIpAddress(ipAddress); break; } } } /** @hide */ public boolean removeClient(String address) { return mClients.remove(new WifiP2pDevice(address)); } /** @hide */ public boolean removeClient(WifiP2pDevice device) { return mClients.remove(device); } /** @hide */ @UnsupportedAppUsage public boolean isClientListEmpty() { return mClients.size() == 0; } /** * Returns {@code true} if the device is part of the group, {@code false} otherwise. * * @hide */ public boolean contains(@Nullable WifiP2pDevice device) { return mOwner.equals(device) || mClients.contains(device); } /** Get the list of clients currently part of the p2p group */ public Collection getClientList() { return Collections.unmodifiableCollection(mClients); } /** @hide */ public void setPassphrase(String passphrase) { mPassphrase = passphrase; } /** * Get the passphrase of the group. This function will return a valid passphrase only * at the group owner. Legacy Wi-Fi clients will need this passphrase alongside * network name obtained from {@link #getNetworkName()} to join the group */ public String getPassphrase() { return mPassphrase; } /** @hide */ @UnsupportedAppUsage public void setInterface(String intf) { mInterface = intf; } /** Get the interface name on which the group is created */ public String getInterface() { return mInterface; } /** The network ID of the P2P group in wpa_supplicant. */ public int getNetworkId() { return mNetId; } /** @hide */ @UnsupportedAppUsage public void setNetworkId(int netId) { this.mNetId = netId; } /** Get the operating frequency (in MHz) of the p2p group */ public int getFrequency() { return mFrequency; } /** @hide */ public void setFrequency(int freq) { this.mFrequency = freq; } /** * Return the vendor-provided configuration data, if it exists. See also {@link * #setVendorData(List)} * * @return Vendor configuration data, or empty list if it does not exist. * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @NonNull @SystemApi public List getVendorData() { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } return mVendorData; } /** * Set additional vendor-provided configuration data. * * @param vendorData List of {@link OuiKeyedData} containing the vendor-provided * configuration data. Note that multiple elements with the same OUI are allowed. * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @SystemApi public void setVendorData(@NonNull List vendorData) { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } if (vendorData == null) { throw new IllegalArgumentException("setVendorData received a null value"); } mVendorData = new ArrayList<>(vendorData); } public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append("network: ").append(mNetworkName); sbuf.append("\n isGO: ").append(mIsGroupOwner); sbuf.append("\n GO: ").append(mOwner); for (WifiP2pDevice client : mClients) { sbuf.append("\n Client: ").append(client); } sbuf.append("\n interface: ").append(mInterface); sbuf.append("\n networkId: ").append(mNetId); sbuf.append("\n frequency: ").append(mFrequency); sbuf.append("\n vendorData: ").append(mVendorData); return sbuf.toString(); } /** Implement the Parcelable interface */ public int describeContents() { return 0; } /** copy constructor */ public WifiP2pGroup(WifiP2pGroup source) { if (source != null) { mNetworkName = source.getNetworkName(); mOwner = new WifiP2pDevice(source.getOwner()); mIsGroupOwner = source.mIsGroupOwner; for (WifiP2pDevice d : source.getClientList()) mClients.add(d); mPassphrase = source.getPassphrase(); mInterface = source.getInterface(); mNetId = source.getNetworkId(); mFrequency = source.getFrequency(); if (SdkLevel.isAtLeastV()) { mVendorData = new ArrayList<>(source.getVendorData()); } } } /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeString(mNetworkName); dest.writeParcelable(mOwner, flags); dest.writeByte(mIsGroupOwner ? (byte) 1: (byte) 0); dest.writeInt(mClients.size()); for (WifiP2pDevice client : mClients) { dest.writeParcelable(client, flags); } dest.writeString(mPassphrase); dest.writeString(mInterface); dest.writeInt(mNetId); dest.writeInt(mFrequency); if (SdkLevel.isAtLeastV()) { dest.writeList(mVendorData); } } /** Implement the Parcelable interface */ public static final @android.annotation.NonNull Creator CREATOR = new Creator() { public WifiP2pGroup createFromParcel(Parcel in) { WifiP2pGroup group = new WifiP2pGroup(); group.setNetworkName(in.readString()); group.setOwner((WifiP2pDevice)in.readParcelable(null)); group.setIsGroupOwner(in.readByte() == (byte)1); int clientCount = in.readInt(); for (int i=0; i