Move util classes to their destination package

Test: builds
Change-Id: I93e9cfd968e08f983e3fd9f7196c455c79a0f32d
(cherry picked from commit 7c9c12694aec7d688496a634f165661e0681773e)
This commit is contained in:
Chalard Jean 2020-06-26 18:34:52 +09:00
commit ba79273382
18 changed files with 33 additions and 36 deletions

View file

@ -86,6 +86,13 @@ java_defaults {
],
}
// Rules for moving classes part of the lib that haven't been
// renamed yet for lack of time
java_defaults {
name: "net-utils-jarjar-shared",
jarjar_rules: "jarjar-rules-shared.txt",
}
java_library {
name: "net-tests-utils",
srcs: [
@ -164,8 +171,6 @@ filegroup {
filegroup {
name: "net-utils-framework-wifi-common-srcs",
srcs: [
"framework/android/net/util/nsd/DnsSdTxtRecord.java",
"framework/android/net/util/MacAddressUtils.java",
"framework/com/android/net/module/util/**/*.java",
],
path: "framework",
@ -181,7 +186,7 @@ filegroup {
name: "net-utils-wifi-service-common-srcs",
srcs: [
"device/android/net/NetworkFactory.java",
"framework/android/net/util/NetUtils.java",
"framework/com/android/net/module/util/NetUtils.java",
],
visibility: [
"//frameworks/opt/net/wifi/service",

View file

@ -0,0 +1,268 @@
/*
* Copyright (C) 2016 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 com.android.net.module.util;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.IOException;
/**
* This class encapsulates the mechanics of registering a file descriptor
* with a thread's Looper and handling read events (and errors).
*
* Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override
* onStop() and onStart().
*
* Subclasses can expect a call life-cycle like the following:
*
* [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all
* goes well. Implementations may override onStart() for additional initialization.
*
* [2] yield, waiting for read event or error notification:
*
* [a] readPacket() && handlePacket()
*
* [b] if (no error):
* goto 2
* else:
* goto 3
*
* [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never
* started). Implementations may override onStop() for additional cleanup.
*
* The packet receive buffer is recycled on every read call, so subclasses
* should make any copies they would like inside their handlePacket()
* implementation.
*
* All public methods MUST only be called from the same thread with which
* the Handler constructor argument is associated.
*
* @param <BufferType> the type of the buffer used to read data.
*/
public abstract class FdEventsReader<BufferType> {
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
private static final int UNREGISTER_THIS_FD = 0;
@NonNull
private final Handler mHandler;
@NonNull
private final MessageQueue mQueue;
@NonNull
private final BufferType mBuffer;
@Nullable
private FileDescriptor mFd;
private long mPacketsReceived;
protected static void closeFd(FileDescriptor fd) {
try {
SocketUtils.closeSocket(fd);
} catch (IOException ignored) {
}
}
protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) {
mHandler = h;
mQueue = mHandler.getLooper().getQueue();
mBuffer = buffer;
}
@VisibleForTesting
@NonNull
protected MessageQueue getMessageQueue() {
return mQueue;
}
/** Start this FdEventsReader. */
public boolean start() {
if (!onCorrectThread()) {
throw new IllegalStateException("start() called from off-thread");
}
return createAndRegisterFd();
}
/** Stop this FdEventsReader and destroy the file descriptor. */
public void stop() {
if (!onCorrectThread()) {
throw new IllegalStateException("stop() called from off-thread");
}
unregisterAndDestroyFd();
}
@NonNull
public Handler getHandler() {
return mHandler;
}
protected abstract int recvBufSize(@NonNull BufferType buffer);
/** Returns the size of the receive buffer. */
public int recvBufSize() {
return recvBufSize(mBuffer);
}
/**
* Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}.
*
* <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0.
*/
public final long numPacketsReceived() {
return mPacketsReceived;
}
/**
* Subclasses MUST create the listening socket here, including setting all desired socket
* options, interface or address/port binding, etc. The socket MUST be created nonblocking.
*/
@Nullable
protected abstract FileDescriptor createFd();
/**
* Implementations MUST return the bytes read or throw an Exception.
*
* <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or
* {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer
* contents and respectively wait for further input or retry the read immediately. For all other
* exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this
* method.
*/
protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer)
throws Exception;
/**
* Called by the main loop for every packet. Any desired copies of
* |recvbuf| should be made in here, as the underlying byte array is
* reused across all reads.
*/
protected void handlePacket(@NonNull BufferType recvbuf, int length) {}
/**
* Called by the main loop to log errors. In some cases |e| may be null.
*/
protected void logError(@NonNull String msg, @Nullable Exception e) {}
/**
* Called by start(), if successful, just prior to returning.
*/
protected void onStart() {}
/**
* Called by stop() just prior to returning.
*/
protected void onStop() {}
private boolean createAndRegisterFd() {
if (mFd != null) return true;
try {
mFd = createFd();
} catch (Exception e) {
logError("Failed to create socket: ", e);
closeFd(mFd);
mFd = null;
}
if (mFd == null) return false;
getMessageQueue().addOnFileDescriptorEventListener(
mFd,
FD_EVENTS,
(fd, events) -> {
// Always call handleInput() so read/recvfrom are given
// a proper chance to encounter a meaningful errno and
// perhaps log a useful error message.
if (!isRunning() || !handleInput()) {
unregisterAndDestroyFd();
return UNREGISTER_THIS_FD;
}
return FD_EVENTS;
});
onStart();
return true;
}
private boolean isRunning() {
return (mFd != null) && mFd.valid();
}
// Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
private boolean handleInput() {
while (isRunning()) {
final int bytesRead;
try {
bytesRead = readPacket(mFd, mBuffer);
if (bytesRead < 1) {
if (isRunning()) logError("Socket closed, exiting", null);
break;
}
mPacketsReceived++;
} catch (ErrnoException e) {
if (e.errno == OsConstants.EAGAIN) {
// We've read everything there is to read this time around.
return true;
} else if (e.errno == OsConstants.EINTR) {
continue;
} else {
if (isRunning()) logError("readPacket error: ", e);
break;
}
} catch (Exception e) {
if (isRunning()) logError("readPacket error: ", e);
break;
}
try {
handlePacket(mBuffer, bytesRead);
} catch (Exception e) {
logError("handlePacket error: ", e);
Log.wtf(FdEventsReader.class.getSimpleName(), "Error handling packet", e);
}
}
return false;
}
private void unregisterAndDestroyFd() {
if (mFd == null) return;
getMessageQueue().removeOnFileDescriptorEventListener(mFd);
closeFd(mFd);
mFd = null;
onStop();
}
private boolean onCorrectThread() {
return (mHandler.getLooper() == Looper.myLooper());
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (C) 2018 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 com.android.net.module.util;
import static java.lang.Math.max;
import android.os.Handler;
import android.system.Os;
import java.io.FileDescriptor;
/**
* Specialization of {@link FdEventsReader} that reads packets into a byte array.
*
* TODO: rename this class to something more correctly descriptive (something
* like [or less horrible than] FdReadEventsHandler?).
*/
public abstract class PacketReader extends FdEventsReader<byte[]> {
public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
protected PacketReader(Handler h) {
this(h, DEFAULT_RECV_BUF_SIZE);
}
protected PacketReader(Handler h, int recvBufSize) {
super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]);
}
@Override
protected final int recvBufSize(byte[] buffer) {
return buffer.length;
}
/**
* Subclasses MAY override this to change the default read() implementation
* in favour of, say, recvfrom().
*
* Implementations MUST return the bytes read or throw an Exception.
*/
@Override
protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
return Os.read(fd, packetBuffer, 0, packetBuffer.length);
}
}

View file

@ -16,13 +16,13 @@
package com.android.testutils;
import android.net.util.PacketReader;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.PacketReader;
import java.io.FileDescriptor;
import java.io.FileOutputStream;

View file

@ -0,0 +1,325 @@
/* -*- Mode: Java; tab-width: 4 -*-
*
* Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
*
* 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.
To do:
- implement remove()
- fix set() to replace existing values
*/
package com.android.net.module.util;
import android.os.Parcelable;
import android.os.Parcel;
import java.util.Arrays;
/**
* This class handles TXT record data for DNS based service discovery as specified at
* http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
*
* DNS-SD specifies that a TXT record corresponding to an SRV record consist of
* a packed array of bytes, each preceded by a length byte. Each string
* is an attribute-value pair.
*
* The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
* as need be to implement its various methods.
* @hide
*
*/
public class DnsSdTxtRecord implements Parcelable {
private static final byte mSeparator = '=';
private byte[] mData;
/** Constructs a new, empty TXT record. */
public DnsSdTxtRecord() {
mData = new byte[0];
}
/** Constructs a new TXT record from a byte array in the standard format. */
public DnsSdTxtRecord(byte[] data) {
mData = (byte[]) data.clone();
}
/** Copy constructor */
public DnsSdTxtRecord(DnsSdTxtRecord src) {
if (src != null && src.mData != null) {
mData = (byte[]) src.mData.clone();
}
}
/**
* Set a key/value pair. Setting an existing key will replace its value.
* @param key Must be ascii with no '='
* @param value matching value to key
*/
public void set(String key, String value) {
byte[] keyBytes;
byte[] valBytes;
int valLen;
if (value != null) {
valBytes = value.getBytes();
valLen = valBytes.length;
} else {
valBytes = null;
valLen = 0;
}
try {
keyBytes = key.getBytes("US-ASCII");
}
catch (java.io.UnsupportedEncodingException e) {
throw new IllegalArgumentException("key should be US-ASCII");
}
for (int i = 0; i < keyBytes.length; i++) {
if (keyBytes[i] == '=') {
throw new IllegalArgumentException("= is not a valid character in key");
}
}
if (keyBytes.length + valLen >= 255) {
throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
}
int currentLoc = remove(key);
if (currentLoc == -1)
currentLoc = keyCount();
insert(keyBytes, valBytes, currentLoc);
}
/**
* Get a value for a key
*
* @param key
* @return The value associated with the key
*/
public String get(String key) {
byte[] val = this.getValue(key);
return val != null ? new String(val) : null;
}
/** Remove a key/value pair. If found, returns the index or -1 if not found */
public int remove(String key) {
int avStart = 0;
for (int i=0; avStart < mData.length; i++) {
int avLen = mData[avStart];
if (key.length() <= avLen &&
(key.length() == avLen || mData[avStart + key.length() + 1] == mSeparator)) {
String s = new String(mData, avStart + 1, key.length());
if (0 == key.compareToIgnoreCase(s)) {
byte[] oldBytes = mData;
mData = new byte[oldBytes.length - avLen - 1];
System.arraycopy(oldBytes, 0, mData, 0, avStart);
System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
oldBytes.length - avStart - avLen - 1);
return i;
}
}
avStart += (0xFF & (avLen + 1));
}
return -1;
}
/** Return the count of keys */
public int keyCount() {
int count = 0, nextKey;
for (nextKey = 0; nextKey < mData.length; count++) {
nextKey += (0xFF & (mData[nextKey] + 1));
}
return count;
}
/** Return true if key is present, false if not. */
public boolean contains(String key) {
String s = null;
for (int i = 0; null != (s = this.getKey(i)); i++) {
if (0 == key.compareToIgnoreCase(s)) return true;
}
return false;
}
/* Gets the size in bytes */
public int size() {
return mData.length;
}
/* Gets the raw data in bytes */
public byte[] getRawData() {
return (byte[]) mData.clone();
}
private void insert(byte[] keyBytes, byte[] value, int index) {
byte[] oldBytes = mData;
int valLen = (value != null) ? value.length : 0;
int insertion = 0;
int newLen, avLen;
for (int i = 0; i < index && insertion < mData.length; i++) {
insertion += (0xFF & (mData[insertion] + 1));
}
avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
newLen = avLen + oldBytes.length + 1;
mData = new byte[newLen];
System.arraycopy(oldBytes, 0, mData, 0, insertion);
int secondHalfLen = oldBytes.length - insertion;
System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
mData[insertion] = (byte) avLen;
System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
if (value != null) {
mData[insertion + 1 + keyBytes.length] = mSeparator;
System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
}
}
/** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
private String getKey(int index) {
int avStart = 0;
for (int i=0; i < index && avStart < mData.length; i++) {
avStart += mData[avStart] + 1;
}
if (avStart < mData.length) {
int avLen = mData[avStart];
int aLen = 0;
for (aLen=0; aLen < avLen; aLen++) {
if (mData[avStart + aLen + 1] == mSeparator) break;
}
return new String(mData, avStart + 1, aLen);
}
return null;
}
/**
* Look up a key in the TXT record by zero-based index and return its value.
* Returns null if index exceeds the total number of keys.
* Returns null if the key is present with no value.
*/
private byte[] getValue(int index) {
int avStart = 0;
byte[] value = null;
for (int i=0; i < index && avStart < mData.length; i++) {
avStart += mData[avStart] + 1;
}
if (avStart < mData.length) {
int avLen = mData[avStart];
int aLen = 0;
for (aLen=0; aLen < avLen; aLen++) {
if (mData[avStart + aLen + 1] == mSeparator) {
value = new byte[avLen - aLen - 1];
System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
break;
}
}
}
return value;
}
private String getValueAsString(int index) {
byte[] value = this.getValue(index);
return value != null ? new String(value) : null;
}
private byte[] getValue(String forKey) {
String s = null;
int i;
for (i = 0; null != (s = this.getKey(i)); i++) {
if (0 == forKey.compareToIgnoreCase(s)) {
return this.getValue(i);
}
}
return null;
}
/**
* Return a string representation.
* Example : {key1=value1},{key2=value2}..
*
* For a key say like "key3" with null value
* {key1=value1},{key2=value2}{key3}
*/
public String toString() {
String a, result = null;
for (int i = 0; null != (a = this.getKey(i)); i++) {
String av = "{" + a;
String val = this.getValueAsString(i);
if (val != null)
av += "=" + val + "}";
else
av += "}";
if (result == null)
result = av;
else
result = result + ", " + av;
}
return result != null ? result : "";
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof DnsSdTxtRecord)) {
return false;
}
DnsSdTxtRecord record = (DnsSdTxtRecord)o;
return Arrays.equals(record.mData, mData);
}
@Override
public int hashCode() {
return Arrays.hashCode(mData);
}
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(mData);
}
/** Implement the Parcelable interface */
public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
new Creator<DnsSdTxtRecord>() {
public DnsSdTxtRecord createFromParcel(Parcel in) {
DnsSdTxtRecord info = new DnsSdTxtRecord();
in.readByteArray(info.mData);
return info;
}
public DnsSdTxtRecord[] newArray(int size) {
return new DnsSdTxtRecord[size];
}
};
}

View file

@ -0,0 +1,223 @@
/*
* Copyright (C) 2020 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 com.android.net.module.util;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import android.annotation.NonNull;
import android.net.IpPrefix;
import com.android.internal.annotations.VisibleForTesting;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
/**
* This class represents an IP range, i.e., a contiguous block of IP addresses defined by a starting
* and ending IP address. These addresses may not be power-of-two aligned.
*
* <p>Conversion to prefixes are deterministic, and will always return the same set of {@link
* IpPrefix}(es). Ordering of IpPrefix instances is not guaranteed.
*
* @hide
*/
public final class IpRange {
private static final int SIGNUM_POSITIVE = 1;
private final byte[] mStartAddr;
private final byte[] mEndAddr;
public IpRange(@NonNull InetAddress startAddr, @NonNull InetAddress endAddr) {
Objects.requireNonNull(startAddr, "startAddr must not be null");
Objects.requireNonNull(endAddr, "endAddr must not be null");
if (!startAddr.getClass().equals(endAddr.getClass())) {
throw new IllegalArgumentException("Invalid range: Address family mismatch");
}
if (addrToBigInteger(startAddr.getAddress()).compareTo(
addrToBigInteger(endAddr.getAddress())) >= 0) {
throw new IllegalArgumentException(
"Invalid range; start address must be before end address");
}
mStartAddr = startAddr.getAddress();
mEndAddr = endAddr.getAddress();
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
public IpRange(@NonNull IpPrefix prefix) {
Objects.requireNonNull(prefix, "prefix must not be null");
// Use masked address from IpPrefix to zero out lower order bits.
mStartAddr = prefix.getRawAddress();
// Set all non-prefix bits to max.
mEndAddr = prefix.getRawAddress();
for (int bitIndex = prefix.getPrefixLength(); bitIndex < 8 * mEndAddr.length; ++bitIndex) {
mEndAddr[bitIndex / 8] |= (byte) (0x80 >> (bitIndex % 8));
}
}
private static InetAddress getAsInetAddress(byte[] address) {
try {
return InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
// Cannot happen. InetAddress.getByAddress can only throw an exception if the byte
// array is the wrong length, but are always generated from InetAddress(es).
throw new IllegalArgumentException("Address is invalid");
}
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
public InetAddress getStartAddr() {
return getAsInetAddress(mStartAddr);
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
public InetAddress getEndAddr() {
return getAsInetAddress(mEndAddr);
}
/**
* Converts this IP range to a list of IpPrefix instances.
*
* <p>This method outputs the IpPrefix instances for use in the routing architecture.
*
* <p>For example, the range 192.0.2.4 - 192.0.3.1 converts to the following prefixes:
*
* <ul>
* <li>192.0.2.128/25
* <li>192.0.2.64/26
* <li>192.0.2.32/27
* <li>192.0.2.16/28
* <li>192.0.2.8/29
* <li>192.0.2.4/30
* <li>192.0.3.0/31
* </ul>
*/
public List<IpPrefix> asIpPrefixes() {
final boolean isIpv6 = (mStartAddr.length == 16);
final List<IpPrefix> result = new ArrayList<>();
final Queue<IpPrefix> workingSet = new LinkedList<>();
// Start with the any-address. Inet4/6Address.ANY requires compiling against
// core-platform-api.
workingSet.add(new IpPrefix(isIpv6 ? getAsInetAddress(new byte[16]) /* IPv6_ANY */
: getAsInetAddress(new byte[4]) /* IPv4_ANY */, 0));
// While items are still in the queue, test and narrow to subsets.
while (!workingSet.isEmpty()) {
final IpPrefix workingPrefix = workingSet.poll();
final IpRange workingRange = new IpRange(workingPrefix);
// If the other range is contained within, it's part of the output. Do not test subsets,
// or we will end up with duplicates.
if (containsRange(workingRange)) {
result.add(workingPrefix);
continue;
}
// If there is any overlap, split the working range into it's two subsets, and
// reevaluate those.
if (overlapsRange(workingRange)) {
workingSet.addAll(getSubsetPrefixes(workingPrefix));
}
}
return result;
}
/**
* Returns the two prefixes that comprise the given prefix.
*
* <p>For example, for the prefix 192.0.2.0/24, this will return the two prefixes that combined
* make up the current prefix:
*
* <ul>
* <li>192.0.2.0/25
* <li>192.0.2.128/25
* </ul>
*/
private static List<IpPrefix> getSubsetPrefixes(IpPrefix prefix) {
final List<IpPrefix> result = new ArrayList<>();
final int currentPrefixLen = prefix.getPrefixLength();
result.add(new IpPrefix(prefix.getAddress(), currentPrefixLen + 1));
final byte[] other = prefix.getRawAddress();
other[currentPrefixLen / 8] =
(byte) (other[currentPrefixLen / 8] ^ (0x80 >> (currentPrefixLen % 8)));
result.add(new IpPrefix(getAsInetAddress(other), currentPrefixLen + 1));
return result;
}
/**
* Checks if the other IP range is contained within this one
*
* <p>Checks based on byte values. For other to be contained within this IP range, other's
* starting address must be greater or equal to the current IpRange's starting address, and the
* other's ending address must be less than or equal to the current IP range's ending address.
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
public boolean containsRange(IpRange other) {
return addrToBigInteger(mStartAddr).compareTo(addrToBigInteger(other.mStartAddr)) <= 0
&& addrToBigInteger(mEndAddr).compareTo(addrToBigInteger(other.mEndAddr)) >= 0;
}
/**
* Checks if the other IP range overlaps with this one
*
* <p>Checks based on byte values. For there to be overlap, this IpRange's starting address must
* be less than the other's ending address, and vice versa.
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
public boolean overlapsRange(IpRange other) {
return addrToBigInteger(mStartAddr).compareTo(addrToBigInteger(other.mEndAddr)) <= 0
&& addrToBigInteger(other.mStartAddr).compareTo(addrToBigInteger(mEndAddr)) <= 0;
}
@Override
public int hashCode() {
return Objects.hash(mStartAddr, mEndAddr);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof IpRange)) {
return false;
}
final IpRange other = (IpRange) obj;
return Arrays.equals(mStartAddr, other.mStartAddr)
&& Arrays.equals(mEndAddr, other.mEndAddr);
}
/** Gets the InetAddress in BigInteger form */
private static BigInteger addrToBigInteger(byte[] addr) {
// Since addr.getAddress() returns network byte order (big-endian), it is compatible with
// the BigInteger constructor (which assumes big-endian).
return new BigInteger(SIGNUM_POSITIVE, addr);
}
}

View file

@ -0,0 +1,225 @@
/*
* Copyright (C) 2019 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 com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
import android.text.TextUtils;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* Collection of link properties utilities.
* @hide
*/
public final class LinkPropertiesUtils {
/**
* @param <T> The type of data to compare.
*/
public static class CompareResult<T> {
public final List<T> removed = new ArrayList<>();
public final List<T> added = new ArrayList<>();
public CompareResult() {}
public CompareResult(@Nullable Collection<T> oldItems, @Nullable Collection<T> newItems) {
if (oldItems != null) {
removed.addAll(oldItems);
}
if (newItems != null) {
for (T newItem : newItems) {
if (!removed.remove(newItem)) {
added.add(newItem);
}
}
}
}
@Override
public String toString() {
return "removed=[" + TextUtils.join(",", removed)
+ "] added=[" + TextUtils.join(",", added)
+ "]";
}
}
/**
* Generic class to compare two lists of items of type {@code T} whose properties can change.
* The items to be compared must provide a way to calculate a corresponding key of type
* {@code K} such that if (and only if) an old and a new item have the same key, then the new
* item is an update of the old item. Both the old list and the new list may not contain more
* than one item with the same key, and may not contain any null items.
*
* @param <K> A class that represents the key of the items to be compared.
* @param <T> The class that represents the object to be compared.
*/
public static class CompareOrUpdateResult<K, T> {
public final List<T> added = new ArrayList<>();
public final List<T> removed = new ArrayList<>();
public final List<T> updated = new ArrayList<>();
/**
* Compares two lists of items.
* @param oldItems the old list of items.
* @param newItems the new list of items.
* @param keyCalculator a {@link Function} that calculates an item's key.
*/
public CompareOrUpdateResult(Collection<T> oldItems, Collection<T> newItems,
Function<T, K> keyCalculator) {
HashMap<K, T> updateTracker = new HashMap<>();
if (oldItems != null) {
for (T oldItem : oldItems) {
updateTracker.put(keyCalculator.apply(oldItem), oldItem);
}
}
if (newItems != null) {
for (T newItem : newItems) {
T oldItem = updateTracker.remove(keyCalculator.apply(newItem));
if (oldItem != null) {
if (!oldItem.equals(newItem)) {
// Update of existing item.
updated.add(newItem);
}
} else {
// New item.
added.add(newItem);
}
}
}
removed.addAll(updateTracker.values());
}
@Override
public String toString() {
return "removed=[" + TextUtils.join(",", removed)
+ "] added=[" + TextUtils.join(",", added)
+ "] updated=[" + TextUtils.join(",", updated)
+ "]";
}
}
/**
* Compares the addresses in {@code left} LinkProperties with {@code right}
* LinkProperties, examining only addresses on the base link.
*
* @param left A LinkProperties with the old list of addresses.
* @param right A LinkProperties with the new list of addresses.
* @return the differences between the addresses.
*/
public static @NonNull CompareResult<LinkAddress> compareAddresses(
@Nullable LinkProperties left, @Nullable LinkProperties right) {
/*
* Duplicate the LinkAddresses into removed, we will be removing
* address which are common between mLinkAddresses and target
* leaving the addresses that are different. And address which
* are in target but not in mLinkAddresses are placed in the
* addedAddresses.
*/
return new CompareResult<>(left != null ? left.getLinkAddresses() : null,
right != null ? right.getLinkAddresses() : null);
}
/**
* Compares {@code left} {@code LinkProperties} interface addresses against the {@code right}.
*
* @param left A LinkProperties.
* @param right LinkProperties to be compared with {@code left}.
* @return {@code true} if both are identical, {@code false} otherwise.
*/
public static boolean isIdenticalAddresses(@NonNull LinkProperties left,
@NonNull LinkProperties right) {
final Collection<InetAddress> leftAddresses = left.getAddresses();
final Collection<InetAddress> rightAddresses = right.getAddresses();
return (leftAddresses.size() == rightAddresses.size())
? leftAddresses.containsAll(rightAddresses) : false;
}
/**
* Compares {@code left} {@code LinkProperties} DNS addresses against the {@code right}.
*
* @param left A LinkProperties.
* @param right A LinkProperties to be compared with {@code left}.
* @return {@code true} if both are identical, {@code false} otherwise.
*/
public static boolean isIdenticalDnses(@NonNull LinkProperties left,
@NonNull LinkProperties right) {
final Collection<InetAddress> leftDnses = left.getDnsServers();
final Collection<InetAddress> rightDnses = right.getDnsServers();
final String leftDomains = left.getDomains();
final String rightDomains = right.getDomains();
if (leftDomains == null) {
if (rightDomains != null) return false;
} else {
if (!leftDomains.equals(rightDomains)) return false;
}
return (leftDnses.size() == rightDnses.size())
? leftDnses.containsAll(rightDnses) : false;
}
/**
* Compares {@code left} {@code LinkProperties} HttpProxy against the {@code right}.
*
* @param left A LinkProperties.
* @param right A LinkProperties to be compared with {@code left}.
* @return {@code true} if both are identical, {@code false} otherwise.
*/
public static boolean isIdenticalHttpProxy(@NonNull LinkProperties left,
@NonNull LinkProperties right) {
return Objects.equals(left.getHttpProxy(), right.getHttpProxy());
}
/**
* Compares {@code left} {@code LinkProperties} interface name against the {@code right}.
*
* @param left A LinkProperties.
* @param right A LinkProperties to be compared with {@code left}.
* @return {@code true} if both are identical, {@code false} otherwise.
*/
public static boolean isIdenticalInterfaceName(@NonNull LinkProperties left,
@NonNull LinkProperties right) {
return TextUtils.equals(left.getInterfaceName(), right.getInterfaceName());
}
/**
* Compares {@code left} {@code LinkProperties} Routes against the {@code right}.
*
* @param left A LinkProperties.
* @param right A LinkProperties to be compared with {@code left}.
* @return {@code true} if both are identical, {@code false} otherwise.
*/
public static boolean isIdenticalRoutes(@NonNull LinkProperties left,
@NonNull LinkProperties right) {
final Collection<RouteInfo> leftRoutes = left.getRoutes();
final Collection<RouteInfo> rightRoutes = right.getRoutes();
return (leftRoutes.size() == rightRoutes.size())
? leftRoutes.containsAll(rightRoutes) : false;
}
}

View file

@ -0,0 +1,137 @@
/*
* Copyright 2019 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 com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.MacAddress;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;
/**
* Collection of MAC address utilities.
* @hide
*/
public final class MacAddressUtils {
private static final long VALID_LONG_MASK = (1L << 48) - 1;
private static final long LOCALLY_ASSIGNED_MASK = longAddrFromByteAddr(
MacAddress.fromString("2:0:0:0:0:0").toByteArray());
private static final long MULTICAST_MASK = longAddrFromByteAddr(
MacAddress.fromString("1:0:0:0:0:0").toByteArray());
private static final long OUI_MASK = longAddrFromByteAddr(
MacAddress.fromString("ff:ff:ff:0:0:0").toByteArray());
private static final long NIC_MASK = longAddrFromByteAddr(
MacAddress.fromString("0:0:0:ff:ff:ff").toByteArray());
// Matches WifiInfo.DEFAULT_MAC_ADDRESS
private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
private static final int ETHER_ADDR_LEN = 6;
/**
* @return true if this MacAddress is a multicast address.
*/
public static boolean isMulticastAddress(@NonNull MacAddress address) {
return (longAddrFromByteAddr(address.toByteArray()) & MULTICAST_MASK) != 0;
}
/**
* Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
* unicast bit, are randomly selected.
*
* The locally assigned bit is always set to 1. The multicast bit is always set to 0.
*
* @return a random locally assigned, unicast MacAddress.
*/
public static @NonNull MacAddress createRandomUnicastAddress() {
return createRandomUnicastAddress(null, new SecureRandom());
}
/**
* Returns a randomly generated MAC address using the given Random object and the same
* OUI values as the given MacAddress.
*
* The locally assigned bit is always set to 1. The multicast bit is always set to 0.
*
* @param base a base MacAddress whose OUI is used for generating the random address.
* If base == null then the OUI will also be randomized.
* @param r a standard Java Random object used for generating the random address.
* @return a random locally assigned MacAddress.
*/
public static @NonNull MacAddress createRandomUnicastAddress(@Nullable MacAddress base,
@NonNull Random r) {
long addr;
if (base == null) {
addr = r.nextLong() & VALID_LONG_MASK;
} else {
addr = (longAddrFromByteAddr(base.toByteArray()) & OUI_MASK)
| (NIC_MASK & r.nextLong());
}
addr |= LOCALLY_ASSIGNED_MASK;
addr &= ~MULTICAST_MASK;
MacAddress mac = MacAddress.fromBytes(byteAddrFromLongAddr(addr));
if (mac.equals(DEFAULT_MAC_ADDRESS)) {
return createRandomUnicastAddress(base, r);
}
return mac;
}
/**
* Convert a byte address to long address.
*/
public static long longAddrFromByteAddr(byte[] addr) {
Objects.requireNonNull(addr);
if (!isMacAddress(addr)) {
throw new IllegalArgumentException(
Arrays.toString(addr) + " was not a valid MAC address");
}
long longAddr = 0;
for (byte b : addr) {
final int uint8Byte = b & 0xff;
longAddr = (longAddr << 8) + uint8Byte;
}
return longAddr;
}
/**
* Convert a long address to byte address.
*/
public static byte[] byteAddrFromLongAddr(long addr) {
byte[] bytes = new byte[ETHER_ADDR_LEN];
int index = ETHER_ADDR_LEN;
while (index-- > 0) {
bytes[index] = (byte) addr;
addr = addr >> 8;
}
return bytes;
}
/**
* Returns true if the given byte array is a valid MAC address.
* A valid byte array representation for a MacAddress is a non-null array of length 6.
*
* @param addr a byte array.
* @return true if the given byte array is not null and has the length of a MAC address.
*
*/
public static boolean isMacAddress(byte[] addr) {
return addr != null && addr.length == ETHER_ADDR_LEN;
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2019 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 com.android.net.module.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.RouteInfo;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Collection;
/**
* Collection of network common utilities.
* @hide
*/
public final class NetUtils {
/**
* Check if IP address type is consistent between two InetAddress.
* @return true if both are the same type. False otherwise.
*/
public static boolean addressTypeMatches(@NonNull InetAddress left,
@NonNull InetAddress right) {
return (((left instanceof Inet4Address) && (right instanceof Inet4Address))
|| ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
}
/**
* Find the route from a Collection of routes that best matches a given address.
* May return null if no routes are applicable.
* @param routes a Collection of RouteInfos to chose from
* @param dest the InetAddress your trying to get to
* @return the RouteInfo from the Collection that best fits the given address
*/
@Nullable
public static RouteInfo selectBestRoute(@Nullable Collection<RouteInfo> routes,
@Nullable InetAddress dest) {
if ((routes == null) || (dest == null)) return null;
RouteInfo bestRoute = null;
// pick a longest prefix match under same address type
for (RouteInfo route : routes) {
if (addressTypeMatches(route.getDestination().getAddress(), dest)) {
if ((bestRoute != null)
&& (bestRoute.getDestination().getPrefixLength()
>= route.getDestination().getPrefixLength())) {
continue;
}
if (route.matches(dest)) bestRoute = route;
}
}
return bestRoute;
}
}

View file

@ -1,8 +1,2 @@
# TODO: move the classes to the target package in java
rule android.net.util.IpRange* com.android.net.module.util.IpRange@1
rule android.net.util.MacAddressUtils* com.android.net.module.util.MacAddressUtils@1
rule android.net.util.LinkPropertiesUtils* com.android.net.module.util.LinkPropertiesUtils@1
rule android.net.util.NetUtils* com.android.net.module.util.NetUtils@1
rule android.net.util.nsd.** com.android.net.module.util.nsd.@1
rule android.annotation.** com.android.net.module.annotation.@1
rule com.android.internal.annotations.** com.android.net.module.annotation.@1

View file

@ -6,7 +6,6 @@ android_library {
name: "NetworkStaticLibTestsLib",
srcs: ["src/**/*.java","src/**/*.kt"],
min_sdk_version: "29",
jarjar_rules: "jarjar-rules.txt",
static_libs: [
"net-utils-framework-common",
"androidx.test.rules",

View file

@ -17,6 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.libnet.tests">
<uses-permission android:name="android.permission.INTERNET" />
<application>
<uses-library android:name="android.test.runner" />
</application>

View file

@ -1,6 +0,0 @@
# TODO: move the classes to the target package in java
rule android.net.util.IpRange* com.android.net.module.util.IpRange@1
rule android.net.util.MacAddressUtils* com.android.net.module.util.MacAddressUtils@1
rule android.net.util.LinkPropertiesUtils* com.android.net.module.util.LinkPropertiesUtils@1
rule android.net.util.NetUtils* com.android.net.module.util.NetUtils@1
rule android.net.util.nsd.** com.android.net.module.util.nsd.@1

View file

@ -28,6 +28,8 @@ import android.net.IpPrefix;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.IpRange;
import org.junit.Test;
import org.junit.runner.RunWith;

View file

@ -26,11 +26,13 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult;
import android.net.util.LinkPropertiesUtils.CompareResult;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.LinkPropertiesUtils;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import org.junit.Test;
import org.junit.runner.RunWith;

View file

@ -24,6 +24,8 @@ import android.net.MacAddress;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.MacAddressUtils;
import org.junit.Test;
import org.junit.runner.RunWith;

View file

@ -27,6 +27,8 @@ import android.net.RouteInfo;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.NetUtils;
import org.junit.Test;
import org.junit.runner.RunWith;

View file

@ -16,7 +16,6 @@
package com.android.net.module.util;
import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
@ -24,14 +23,13 @@ import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_SNDTIMEO;
import static com.android.net.module.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.net.util.PacketReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.system.ErrnoException;
@ -93,8 +91,7 @@ public class PacketReaderTest {
Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
} catch (ErrnoException | SocketException e) {
closeFd(s);
fail();
return null;
throw new RuntimeException("Failed to create FD", e);
}
mLocalSocket = s;