Add callbacks for service offload

Components that can provide offload like IpClient (packet
filter offloading) can use the API to register a callback to be notified
when offload is necessary.

Bug: 269240366
Test: atest CtsNetTestCases
Change-Id: I8080702f5b530001b88e79e504f4722ac01bc576
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index ffa2857..9520ef6 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -82,6 +82,17 @@
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
 
+// The filegroup lists files that are necessary for verifying building mdns as a standalone,
+// for use with service-connectivity-mdns-standalone-build-test
+filegroup {
+    name: "framework-connectivity-t-mdns-standalone-build-sources",
+    srcs: [
+        "src/android/net/nsd/OffloadEngine.java",
+        "src/android/net/nsd/OffloadServiceInfo.java",
+    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
 java_library {
     name: "framework-connectivity-t-pre-jarjar",
     defaults: ["framework-connectivity-t-defaults"],
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index 5533154..e671db1 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -17,6 +17,7 @@
 package android.net.nsd;
 
 import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.IOffloadEngine;
 import android.net.nsd.NsdServiceInfo;
 import android.os.Messenger;
 
@@ -35,4 +36,6 @@
     void stopResolution(int listenerKey);
     void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo);
     void unregisterServiceInfoCallback(int listenerKey);
+    void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType);
+    void unregisterOffloadEngine(in IOffloadEngine cb);
 }
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/IOffloadEngine.aidl b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
new file mode 100644
index 0000000..379c2e0
--- /dev/null
+++ b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2023, 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.nsd;
+
+import android.net.nsd.OffloadServiceInfo;
+
+/**
+ * Callbacks from NsdService to inform providers of packet offload.
+ * @hide
+ */
+oneway interface IOffloadEngine {
+    void onOffloadServiceUpdated(in OffloadServiceInfo info);
+    void onOffloadServiceRemoved(in OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 2930cbd..934f185 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,9 @@
 
 package android.net.nsd;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
 import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
 
@@ -45,9 +48,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -246,6 +251,10 @@
     public static final int UNREGISTER_SERVICE_CALLBACK             = 31;
     /** @hide */
     public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED   = 32;
+    /** @hide */
+    public static final int REGISTER_OFFLOAD_ENGINE                 = 33;
+    /** @hide */
+    public static final int UNREGISTER_OFFLOAD_ENGINE               = 34;
 
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -313,8 +322,107 @@
     private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
             mPerNetworkDiscoveryMap = new ArrayMap<>();
 
+    @GuardedBy("mOffloadEngines")
+    private final ArrayList<OffloadEngineProxy> mOffloadEngines = new ArrayList<>();
     private final ServiceHandler mHandler;
 
+    private static class OffloadEngineProxy extends IOffloadEngine.Stub {
+        private final Executor mExecutor;
+        private final OffloadEngine mEngine;
+
+        private OffloadEngineProxy(@NonNull Executor executor, @NonNull OffloadEngine appCb) {
+            mExecutor = executor;
+            mEngine = appCb;
+        }
+
+        @Override
+        public void onOffloadServiceUpdated(OffloadServiceInfo info) {
+            mExecutor.execute(() -> mEngine.onOffloadServiceUpdated(info));
+        }
+
+        @Override
+        public void onOffloadServiceRemoved(OffloadServiceInfo info) {
+            mExecutor.execute(() -> mEngine.onOffloadServiceRemoved(info));
+        }
+    }
+
+    /**
+     * Registers an OffloadEngine with NsdManager.
+     *
+     * A caller can register itself as an OffloadEngine if it supports mDns hardware offload.
+     * The caller must implement the {@link OffloadEngine} interface and update hardware offload
+     * state property when the {@link OffloadEngine#onOffloadServiceUpdated} and
+     * {@link OffloadEngine#onOffloadServiceRemoved} callback are called. Multiple engines may be
+     * registered for the same interface, and that the same engine cannot be registered twice.
+     *
+     * @param ifaceName  indicates which network interface the hardware offload runs on
+     * @param offloadType    the type of offload that the offload engine support
+     * @param offloadCapability    the capabilities of the offload engine
+     * @param executor   the executor on which to receive the offload callbacks
+     * @param engine     the OffloadEngine that will receive the offload callbacks
+     * @throws IllegalStateException if the engine is already registered.
+     *
+     * @hide
+     */
+    //@SystemApi
+    @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+            NETWORK_STACK})
+    public void registerOffloadEngine(@NonNull String ifaceName,
+            @OffloadEngine.OffloadType long offloadType,
+            @OffloadEngine.OffloadCapability long offloadCapability, @NonNull Executor executor,
+            @NonNull OffloadEngine engine) {
+        Objects.requireNonNull(ifaceName);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(engine);
+        final OffloadEngineProxy cbImpl = new OffloadEngineProxy(executor, engine);
+        synchronized (mOffloadEngines) {
+            if (CollectionUtils.contains(mOffloadEngines, impl -> impl.mEngine == engine)) {
+                throw new IllegalStateException("This engine is already registered");
+            }
+            mOffloadEngines.add(cbImpl);
+        }
+        try {
+            mService.registerOffloadEngine(ifaceName, cbImpl, offloadCapability, offloadType);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Unregisters an OffloadEngine from NsdService.
+     *
+     * A caller can unregister itself as an OffloadEngine when it doesn't want to receive the
+     * callback anymore. The OffloadEngine must have been previously registered with the system
+     * using the {@link NsdManager#registerOffloadEngine} method.
+     *
+     * @param engine OffloadEngine object to be removed from NsdService
+     * @throws IllegalStateException if the engine is not registered.
+     *
+     * @hide
+     */
+    //@SystemApi
+    @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+            NETWORK_STACK})
+    public void unregisterOffloadEngine(@NonNull OffloadEngine engine) {
+        Objects.requireNonNull(engine);
+        final OffloadEngineProxy cbImpl;
+        synchronized (mOffloadEngines) {
+            final int index = CollectionUtils.indexOf(mOffloadEngines,
+                    impl -> impl.mEngine == engine);
+            if (index < 0) {
+                throw new IllegalStateException("This engine is not registered");
+            }
+            cbImpl = mOffloadEngines.remove(index);
+        }
+
+        try {
+            mService.unregisterOffloadEngine(cbImpl);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     private class PerNetworkDiscoveryTracker {
         final String mServiceType;
         final int mProtocolType;
diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java
new file mode 100644
index 0000000..d939725
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadEngine.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.nsd;
+
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * OffloadEngine is an interface for mDns hardware offloading.
+ *
+ * An offloading engine can interact with the firmware code to instruct the hardware to
+ * offload some of mDns network traffic before it reached android OS. This can improve the
+ * power consumption performance of the host system by not always waking up the OS to handle
+ * the mDns packet when the device is in low power mode.
+ *
+ * @hide
+ */
+//@SystemApi
+public interface OffloadEngine {
+    /**
+     * Indicates that the OffloadEngine can generate replies to mDns queries.
+     *
+     * @see OffloadServiceInfo#getOffloadPayload()
+     */
+    int OFFLOAD_TYPE_REPLY = 1;
+    /**
+     * Indicates that the OffloadEngine can filter and drop mDns queries.
+     */
+    int OFFLOAD_TYPE_FILTER_QUERIES = 1 << 1;
+    /**
+     * Indicates that the OffloadEngine can filter and drop mDns replies. It can allow mDns packets
+     * to be received even when no app holds a {@link android.net.wifi.WifiManager.MulticastLock}.
+     */
+    int OFFLOAD_TYPE_FILTER_REPLIES = 1 << 2;
+
+    /**
+     * Indicates that the OffloadEngine can bypass multicast lock.
+     */
+    int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, prefix = {"OFFLOAD_TYPE"}, value = {
+            OFFLOAD_TYPE_REPLY,
+            OFFLOAD_TYPE_FILTER_QUERIES,
+            OFFLOAD_TYPE_FILTER_REPLIES,
+    })
+    @interface OffloadType {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, prefix = {"OFFLOAD_CAPABILITY"}, value = {
+            OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK
+    })
+    @interface OffloadCapability {}
+
+    /**
+     * To be called when the OffloadServiceInfo is added or updated.
+     *
+     * @param info The OffloadServiceInfo to add or update.
+     */
+    void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info);
+
+    /**
+     * To be called when the OffloadServiceInfo is removed.
+     *
+     * @param info The OffloadServiceInfo to remove.
+     */
+    void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
new file mode 100644
index 0000000..4aec720
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2023 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.nsd;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.net.module.util.HexDump;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The OffloadServiceInfo class contains all the necessary information the OffloadEngine needs to
+ * know about how to offload an mDns service. The OffloadServiceInfo is keyed on
+ * {@link OffloadServiceInfo.Key} which is a (serviceName, serviceType) pair.
+ *
+ * @hide
+ */
+// @SystemApi
+public final class OffloadServiceInfo implements Parcelable {
+    @NonNull
+    private final Key mKey;
+    @NonNull
+    private final String mHostname;
+    @NonNull final List<String> mSubtypes;
+    @Nullable
+    private final byte[] mOffloadPayload;
+    private final int mPriority;
+    private final long mOffloadType;
+
+    /**
+     * Creates a new OffloadServiceInfo object with the specified parameters.
+     *
+     * @param key The key of the service.
+     * @param subtypes The list of subTypes of the service.
+     * @param hostname The name of the host offering the service. It is meaningful only when
+     *                 offloadType contains OFFLOAD_REPLY.
+     * @param offloadPayload The raw udp payload for hardware offloading.
+     * @param priority The priority of the service, @see #getPriority.
+     * @param offloadType The type of the service offload, @see #getOffloadType.
+     */
+    public OffloadServiceInfo(@NonNull Key key,
+            @NonNull List<String> subtypes, @NonNull String hostname,
+            @Nullable byte[] offloadPayload,
+            @IntRange(from = 0, to = Integer.MAX_VALUE) int priority,
+            @OffloadEngine.OffloadType long offloadType) {
+        Objects.requireNonNull(key);
+        Objects.requireNonNull(subtypes);
+        Objects.requireNonNull(hostname);
+        mKey = key;
+        mSubtypes = subtypes;
+        mHostname = hostname;
+        mOffloadPayload = offloadPayload;
+        mPriority = priority;
+        mOffloadType = offloadType;
+    }
+
+    /**
+     * Creates a new OffloadServiceInfo object from a Parcel.
+     *
+     * @param in The Parcel to read the object from.
+     *
+     * @hide
+     */
+    public OffloadServiceInfo(@NonNull Parcel in) {
+        mKey = in.readParcelable(Key.class.getClassLoader(),
+                Key.class);
+        mSubtypes = in.createStringArrayList();
+        mHostname = in.readString();
+        mOffloadPayload = in.createByteArray();
+        mPriority = in.readInt();
+        mOffloadType = in.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mKey, flags);
+        dest.writeStringList(mSubtypes);
+        dest.writeString(mHostname);
+        dest.writeByteArray(mOffloadPayload);
+        dest.writeInt(mPriority);
+        dest.writeLong(mOffloadType);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<OffloadServiceInfo> CREATOR = new Creator<OffloadServiceInfo>() {
+        @Override
+        public OffloadServiceInfo createFromParcel(Parcel in) {
+            return new OffloadServiceInfo(in);
+        }
+
+        @Override
+        public OffloadServiceInfo[] newArray(int size) {
+            return new OffloadServiceInfo[size];
+        }
+    };
+
+    /**
+     * Get the {@link Key}.
+     */
+    @NonNull
+    public Key getKey() {
+        return mKey;
+    }
+
+    /**
+     * Get the host name. (e.g. "Android.local" )
+     */
+    @NonNull
+    public String getHostname() {
+        return mHostname;
+    }
+
+    /**
+     * Get the service subtypes. (e.g. ["_ann"] )
+     */
+    @NonNull
+    public List<String> getSubtypes() {
+        return Collections.unmodifiableList(mSubtypes);
+    }
+
+    /**
+     * Get the raw udp payload that the OffloadEngine can use to directly reply the incoming query.
+     * <p>
+     * It is null if the OffloadEngine can not handle transmit. The packet must be sent as-is when
+     * replying to query.
+     */
+    @Nullable
+    public byte[] getOffloadPayload() {
+        if (mOffloadPayload == null) {
+            return null;
+        } else {
+            return mOffloadPayload.clone();
+        }
+    }
+
+    /**
+     * Get the offloadType.
+     * <p>
+     * For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both
+     * filter the mDNS queries and replies, the {@link #mOffloadType} =
+     * ({@link OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES} |
+     * {@link OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES}).
+     */
+    @OffloadEngine.OffloadType public long getOffloadType() {
+        return mOffloadType;
+    }
+
+    /**
+     * Get the priority for the OffloadServiceInfo.
+     * <p>
+     * When OffloadEngine don't have enough resource
+     * (e.g. not enough memory) to offload all the OffloadServiceInfo. The OffloadServiceInfo
+     * having lower priority values should be handled by the OffloadEngine first.
+     */
+    public int getPriority() {
+        return mPriority;
+    }
+
+    /**
+     * Only for debug purpose, the string can be long as the raw packet is dump in the string.
+     */
+    @Override
+    public String toString() {
+        return String.format(
+                "OffloadServiceInfo{ mOffloadServiceInfoKey=%s, mHostName=%s, "
+                        + "mOffloadPayload=%s, mPriority=%d, mOffloadType=%d, mSubTypes=%s }",
+                mKey,
+                mHostname, HexDump.dumpHexString(mOffloadPayload), mPriority,
+                mOffloadType, mSubtypes.toString());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof OffloadServiceInfo)) return false;
+        OffloadServiceInfo that = (OffloadServiceInfo) o;
+        return mPriority == that.mPriority && mOffloadType == that.mOffloadType
+                && mKey.equals(that.mKey)
+                && mHostname.equals(
+                that.mHostname) && Arrays.equals(mOffloadPayload,
+                that.mOffloadPayload)
+                && mSubtypes.equals(that.mSubtypes);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mKey, mHostname, mPriority,
+                mOffloadType, mSubtypes);
+        result = 31 * result + Arrays.hashCode(mOffloadPayload);
+        return result;
+    }
+
+    /**
+     * The {@link OffloadServiceInfo.Key} is the (serviceName, serviceType) pair.
+     */
+    public static final class Key implements Parcelable {
+        @NonNull
+        private final String mServiceName;
+        @NonNull
+        private final String mServiceType;
+
+        /**
+         * Creates a new OffloadServiceInfoKey object with the specified parameters.
+         *
+         * @param serviceName The name of the service.
+         * @param serviceType The type of the service.
+         */
+        public Key(@NonNull String serviceName, @NonNull String serviceType) {
+            Objects.requireNonNull(serviceName);
+            Objects.requireNonNull(serviceType);
+            mServiceName = serviceName;
+            mServiceType = serviceType;
+        }
+
+        /**
+         * Creates a new OffloadServiceInfoKey object from a Parcel.
+         *
+         * @param in The Parcel to read the object from.
+         *
+         * @hide
+         */
+        public Key(@NonNull Parcel in) {
+            mServiceName = in.readString();
+            mServiceType = in.readString();
+        }
+        /**
+         * Get the service name. (e.g. "NsdChat")
+         */
+        @NonNull
+        public String getServiceName() {
+            return mServiceName;
+        }
+
+        /**
+         * Get the service type. (e.g. "_http._tcp.local" )
+         */
+        @NonNull
+        public String getServiceType() {
+            return mServiceType;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(mServiceName);
+            dest.writeString(mServiceType);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        public static final Creator<Key> CREATOR =
+                new Creator<Key>() {
+            @Override
+            public Key createFromParcel(Parcel in) {
+                return new Key(in);
+            }
+
+            @Override
+            public Key[] newArray(int size) {
+                return new Key[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Key)) return false;
+            Key that = (Key) o;
+            return Objects.equals(mServiceName, that.mServiceName) && Objects.equals(
+                    mServiceType, that.mServiceType);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mServiceName, mServiceType);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("OffloadServiceInfoKey{ mServiceName=%s, mServiceType=%s }",
+                    mServiceName, mServiceType);
+        }
+    }
+}