diff --git a/framework-t/src/android/net/EthernetPortInfo.java b/framework-t/src/android/net/EthernetPortInfo.java new file mode 100644 index 0000000000..ba811eeb4c --- /dev/null +++ b/framework-t/src/android/net/EthernetPortInfo.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2025 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; + +import android.annotation.Nullable; +import android.net.EthernetManager.InterfaceState; +import android.net.EthernetManager.Role; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * A class representing information of an ethernet interface. + * EthernetPortInfo is mainly used in network callbacks. + * @hide + */ +public final class EthernetPortInfo implements Parcelable { + // Role of this ethernet interface, one of {@link EthernetManager.ROLE_SERVER}, + // {@link EthernetManager.ROLE_CLIENT}, or {@link EthernetManager.ROLE_NONE}. + @Role + private final int mRole; + + // State of this ethernet interface, one of {@link EthernetManager.STATE_LINK_UP}, + // {@link EthernetManager.STATE_LINK_DOWN}, or {@link EthernetManager.STATE_ABSENT}. + @InterfaceState + private final int mState; + + // Configuration of this ethernet interface. + private final EthernetConfiguration mConfig; + + // Name of this ethernet interface. + private final String mInterfaceName; + + // MAC address of this ethernet interface. + private final MacAddress mMacAddress; + + // Interface index of this ethernet interface. + private final int mInterfaceIndex; + + public EthernetPortInfo(String interfaceName, MacAddress macAddress, + int interfaceIndex, EthernetConfiguration config, @Role int role, + @InterfaceState int state) { + if (role != EthernetManager.ROLE_CLIENT && role != EthernetManager.ROLE_SERVER + && role != EthernetManager.ROLE_NONE) { + throw new IllegalArgumentException("Interface role is not valid, should be one of " + + "{EthernetManager.ROLE_SERVER, EthernetManager.ROLE_CLIENT, " + + "EthernetManager.ROLE_NONE}."); + } + mRole = role; + + if (state != EthernetManager.STATE_ABSENT && state != EthernetManager.STATE_LINK_DOWN + && state != EthernetManager.STATE_LINK_UP) { + throw new IllegalArgumentException("Interface state is not valid, should be one of " + + "{EthernetManager.STATE_ABSENT, EthernetManager.STATE_LINK_DOWN, " + + "EthernetManager.STATE_LINK_UP}."); + } + mState = state; + + if (TextUtils.isEmpty(interfaceName) || macAddress == null || interfaceIndex <= 0) { + throw new IllegalArgumentException("All of interface name, MAC address and interface" + + " index need to be valid."); + } + mInterfaceName = interfaceName; + mMacAddress = macAddress; + mInterfaceIndex = interfaceIndex; + + Objects.requireNonNull(config); + mConfig = config; + } + + /** + * Returns role of this ethernet interface, one of {@link EthernetManager.ROLE_SERVER}, + * {@link EthernetManager.ROLE_CLIENT}, or {@link EthernetManager.ROLE_NONE}. + */ + @Role + public int getRole() { + return mRole; + } + + + /** + * Returns state of this ethernet interface, one of {@link EthernetManager.STATE_LINK_UP}, + * {@link EthernetManager.STATE_LINK_DOWN}, or {@link EthernetManager.STATE_ABSENT}. + */ + @InterfaceState + public int getState() { + return mState; + } + + /** + * Returns the static configuration regarding meteredness. Note that this meteredness is only + * configured meteredness, while real meteredness state is only retrievable via + * {@link ConnectivityManager}. + */ + public boolean getMeteredConfiguration() { + return mConfig.getNetworkCapabilities() + .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + } + + /** Returns the name of this ethernet interface */ + public String getInterfaceName() { + return mInterfaceName; + } + + /** Returns the MAC address of this ethernet interface */ + public MacAddress getMacAddress() { + return mMacAddress; + } + + /** Returns the interface index of this ethernet interface */ + public int getInterfaceIndex() { + return mInterfaceIndex; + } + + /** Returns the configuration of this ethernet interface */ + public EthernetConfiguration getConfiguration() { + return mConfig; + } + + /** Returns a NetworkSpecifier that is guaranteed to match with this ethernet interface. */ + public NetworkSpecifier getNetworkSpecifier() { + return new EthernetNetworkSpecifier(mInterfaceName, mMacAddress); + } + + @Override + public String toString() { + return "Role:" + mRole + + ", State:" + mState + + ", Configurations: " + mConfig + + ", Interface name:" + mInterfaceName + + ", MAC address:" + Objects.toString(mMacAddress) + + ", Interface index:" + mInterfaceIndex; + } + + @Override + public int hashCode() { + return Objects.hash(mRole, mState, mConfig, + mInterfaceName, mMacAddress, mInterfaceIndex); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof EthernetPortInfo)) { + return false; + } + + final EthernetPortInfo other = (EthernetPortInfo) o; + return (this.mRole == other.mRole) + && (this.mState == other.mState) + && Objects.equals(this.mConfig, other.mConfig) + && TextUtils.equals(this.mInterfaceName, other.mInterfaceName) + && Objects.equals(this.mMacAddress, other.mMacAddress) + && (this.mInterfaceIndex == other.mInterfaceIndex); + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRole); + dest.writeInt(mState); + dest.writeParcelable(mConfig, flags); + dest.writeString(mInterfaceName); + dest.writeParcelable(mMacAddress, flags); + dest.writeInt(mInterfaceIndex); + } + + /** Implement the Parcelable interface */ + public static final Creator CREATOR = + new Creator() { + @Override + public EthernetPortInfo createFromParcel(Parcel in) { + int role = in.readInt(); + int state = in.readInt(); + EthernetConfiguration config = in.readParcelable( + EthernetConfiguration.class.getClassLoader()); + String ifname = in.readString(); + MacAddress hwaddr = in.readParcelable( + MacAddress.class.getClassLoader()); + int index = in.readInt(); + return new EthernetPortInfo( + ifname, hwaddr, index, config, role, state); + } + + @Override + public EthernetPortInfo[] newArray(int size) { + return new EthernetPortInfo[size]; + } + }; + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/tests/unit/java/android/net/EthernetPortInfoTest.java b/tests/unit/java/android/net/EthernetPortInfoTest.java new file mode 100644 index 0000000000..6061f091f5 --- /dev/null +++ b/tests/unit/java/android/net/EthernetPortInfoTest.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2025 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; + +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; + +import android.os.Build; + +import androidx.test.filters.SmallTest; + +import com.android.modules.utils.build.SdkLevel; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.ArrayList; + +@SmallTest +@IgnoreUpTo(Build.VERSION_CODES.S_V2) +@RunWith(DevSdkIgnoreRunner.class) +public class EthernetPortInfoTest { + private final String mTestInterfaceName1 = "eth0"; + private final MacAddress mTestMacAddress1 = MacAddress.fromString("0A:1B:2C:3D:4E:5F"); + private final int mTestInterfaceIndex1 = 1; + + private final String mTestInterfaceName2 = "eth1"; + private final MacAddress mTestMacAddress2 = MacAddress.fromString("DE:AD:BE:EF:00:01"); + private final int mTestInterfaceIndex2 = 2; + + private EthernetConfiguration mConfig1; + private EthernetConfiguration mConfig2; + private NetworkCapabilities mNetworkCapabilities; + private EthernetPortInfo mEthernetPortInfo1; + private EthernetPortInfo mEthernetPortInfo2; + + @Before + public void setUp() { + LinkAddress linkAddr = new LinkAddress("192.168.1.100/25"); + InetAddress gateway = InetAddresses.parseNumericAddress("192.168.1.1"); + InetAddress dns1 = InetAddresses.parseNumericAddress("8.8.8.8"); + InetAddress dns2 = InetAddresses.parseNumericAddress("8.8.4.4"); + ArrayList dnsServers = new ArrayList<>(); + dnsServers.add(dns1); + dnsServers.add(dns2); + StaticIpConfiguration staticIpConfig = new StaticIpConfiguration.Builder() + .setIpAddress(linkAddr) + .setGateway(gateway) + .setDnsServers(dnsServers) + .build(); + ProxyInfo proxy1 = ProxyInfo.buildDirectProxy("test1", 8888); + ProxyInfo proxy2 = ProxyInfo.buildDirectProxy("test2", 8888); + + IpConfiguration ipConfig1 = new IpConfiguration.Builder() + .setStaticIpConfiguration(staticIpConfig) + .setHttpProxy(proxy1) + .build(); + IpConfiguration ipConfig2 = new IpConfiguration.Builder() + .setStaticIpConfiguration(staticIpConfig) + .setHttpProxy(proxy2) + .build(); + + mNetworkCapabilities = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .build(); + mConfig1 = new EthernetConfiguration(ipConfig1, mNetworkCapabilities); + mConfig2 = new EthernetConfiguration(ipConfig2, mNetworkCapabilities); + + mEthernetPortInfo1 = new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP); + mEthernetPortInfo2 = new EthernetPortInfo( + mTestInterfaceName2, mTestMacAddress2, mTestInterfaceIndex2, mConfig2, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP); + } + + @Test + public void testConstructor() { + EthernetPortInfo portInfo = new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP); + assertEquals(portInfo.getInterfaceName(), mTestInterfaceName1); + assertEquals(portInfo.getMacAddress(), mTestMacAddress1); + assertEquals(portInfo.getInterfaceIndex(), mTestInterfaceIndex1); + assertEquals(portInfo.getConfiguration(), mConfig1); + assertEquals(portInfo.getMeteredConfiguration(), + mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)); + assertEquals(portInfo.getRole(), EthernetManager.ROLE_CLIENT); + assertEquals(portInfo.getState(), EthernetManager.STATE_LINK_UP); + } + + @Test + public void testConstructorWithNullIfname() { + assertThrows("All of interface name, MAC address and interface" + + " index need to be valid.", + IllegalArgumentException.class, + () -> new EthernetPortInfo( + null, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP)); + } + + @Test + public void testConstructorWithEmptyIfname() { + assertThrows("All of interface name, MAC address and interface" + + " index need to be valid.", + IllegalArgumentException.class, + () -> new EthernetPortInfo( + "", mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP)); + } + + @Test + public void testConstructorWithNullAddress() { + assertThrows("All of interface name, MAC address and interface" + + " index need to be valid.", + IllegalArgumentException.class, + () -> new EthernetPortInfo( + mTestInterfaceName1, null, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP)); + } + + @Test + public void testConstructorWithInvalidIndex() { + assertThrows("All of interface name, MAC address and interface" + + " index need to be valid.", + IllegalArgumentException.class, + () -> new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, 0, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP)); + } + + @Test + public void testConstructorWithNullConfig() { + assertThrows(NullPointerException.class, + () -> new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, null, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP)); + } + + @Test + public void testConstructorWithInvalidRole() { + assertThrows("Interface role is not valid, should be one of " + + "{EthernetManager.ROLE_SERVER, EthernetManager.ROLE_CLIENT, " + + "EthernetManager.ROLE_NONE}.", + IllegalArgumentException.class, + () -> new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + -1, EthernetManager.STATE_LINK_UP)); + } + + @Test + public void testConstructorWithInvalidInterfaceState() { + assertThrows("Interface state is not valid, should be one of " + + "{EthernetManager.STATE_ABSENT, EthernetManager.STATE_LINK_DOWN, " + + "EthernetManager.STATE_LINK_UP}.", + IllegalArgumentException.class, + () -> new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, -1)); + } + + @Test + public void testHashCodeConsistency() { + int hash1 = mEthernetPortInfo1.hashCode(); + int hash2 = mEthernetPortInfo1.hashCode(); + assertEquals(hash1, hash2); + } + + @Test + public void testHashCodeOfDifferentConfig() { + int hash1 = mEthernetPortInfo1.hashCode(); + int hash2 = mEthernetPortInfo2.hashCode(); + assertNotEquals(hash1, hash2); + } + + @Test + public void testHashCodeOfTheSameConfig() { + EthernetPortInfo portInfo1 = new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP); + EthernetPortInfo portInfo2 = new EthernetPortInfo( + mTestInterfaceName1, mTestMacAddress1, mTestInterfaceIndex1, mConfig1, + EthernetManager.ROLE_CLIENT, EthernetManager.STATE_LINK_UP); + int hash1 = portInfo1.hashCode(); + int hash2 = portInfo2.hashCode(); + assertEquals(hash1, hash2); + } + + @Test + public void testToString() { + String expect; + // For devices with SDK level at least V, NOT_BANDWIDTH_CONSTRAINED is included as a + // default capability. + if (SdkLevel.isAtLeastV()) { + expect = "Role:1, State:2, Configurations: IP configurations: " + + "IP assignment: STATIC\n" + + "Static configuration: IP address 192.168.1.100/25 Gateway 192.168.1.1 " + + " DNS servers: [ 8.8.8.8 8.8.4.4 ] Domains \n" + + "Proxy settings: PAC\n" + + "HTTP proxy: [test1] 8888\n" + + "Network capabilities: [ Transports: ETHERNET Capabilities: " + + "NOT_RESTRICTED&TRUSTED&NOT_VPN&NOT_BANDWIDTH_CONSTRAINED " + + "UnderlyingNetworks: Null], Interface name:eth0, " + + "MAC address:0a:1b:2c:3d:4e:5f, Interface index:1"; + } else { + expect = "Role:1, State:2, Configurations: IP configurations: " + + "IP assignment: STATIC\n" + + "Static configuration: IP address 192.168.1.100/25 Gateway 192.168.1.1 " + + " DNS servers: [ 8.8.8.8 8.8.4.4 ] Domains \n" + + "Proxy settings: PAC\n" + + "HTTP proxy: [test1] 8888\n" + + "Network capabilities: [ Transports: ETHERNET Capabilities: " + + "NOT_RESTRICTED&TRUSTED&NOT_VPN UnderlyingNetworks: Nu" + + "ll], Interface name:eth0, MAC address:0a:1b:2c:3d:4e:5f, Interface index:1"; + + } + assertEquals(mEthernetPortInfo1.toString(), expect); + } + + @Test + public void testParcel() { + assertParcelingIsLossless(mEthernetPortInfo1); + } +}