Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad280950 authored by Daniel Bright's avatar Daniel Bright Committed by Jayachandran C
Browse files

Add support for Qos filter matching

1) Tracks qos callbacks
2) Listens for sessions from RIL and notifies the connectivity service

Bug: 158315614
Test: atest com.android.internal.telephony.dataconnection.QosCallbackTrackerTest

Change-Id: I5bbaf1d7eaadfc62f9abae66530ec1269ff59f75
parent 5778a71b
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -106,7 +106,7 @@ import android.telephony.data.DataCallResponse.HandoverFailureMode;
import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.telephony.data.Qos;
import android.telephony.data.QosSession;
import android.telephony.data.QosBearerSession;
import android.telephony.data.SliceInfo;
import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
@@ -7383,7 +7383,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
        int pduSessionId = DataCallResponse.PDU_SESSION_ID_NOT_SET;

        List<LinkAddress> laList = new ArrayList<>();
        List<QosSession> qosSessions = new ArrayList<>();
        List<QosBearerSession> qosSessions = new ArrayList<>();
        SliceInfo sliceInfo = null;

        if (dcResult instanceof android.hardware.radio.V1_0.SetupDataCallResult) {
@@ -7473,7 +7473,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
            pduSessionId = result.pduSessionId;
            defaultQos = Qos.create(result.defaultQos);
            qosSessions = result.qosSessions.stream().map(session ->
                    QosSession.create(session)).collect(Collectors.toList());
                    QosBearerSession.create(session)).collect(Collectors.toList());
            sliceInfo = convertToSliceInfo(result.sliceInfo);
        } else {
            Rlog.e(RILJ_LOG_TAG, "Unsupported SetupDataCallResult " + dcResult);
@@ -7542,7 +7542,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
                .setHandoverFailureMode(handoverFailureMode)
                .setPduSessionId(pduSessionId)
                .setDefaultQos(defaultQos)
                .setQosSessions(qosSessions)
                .setQosBearerSessions(qosSessions)
                .setSliceInfo(sliceInfo)
                .build();
    }
+32 −5
Original line number Diff line number Diff line
@@ -67,7 +67,7 @@ import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.telephony.data.DataServiceCallback;
import android.telephony.data.Qos;
import android.telephony.data.QosSession;
import android.telephony.data.QosBearerSession;
import android.telephony.data.SliceInfo;
import android.text.TextUtils;
import android.util.LocalLog;
@@ -305,7 +305,7 @@ public class DataConnection extends StateMachine {
    private int mDownlinkBandwidth = 14;
    private int mUplinkBandwidth = 14;
    private Qos mDefaultQos = null;
    private List<QosSession> mQosSessions = new ArrayList<>();
    private List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
    private SliceInfo mSliceInfo;

    /** The corresponding network agent for this data connection. */
@@ -618,9 +618,29 @@ public class DataConnection extends StateMachine {
        return mSliceInfo;
    }

    public void updateQosParameters(DataCallResponse response) {
    public void updateQosParameters(final @Nullable DataCallResponse response) {
        if (response == null) {
            mDefaultQos = null;
            mQosBearerSessions = null;
            return;
        }

        mDefaultQos = response.getDefaultQos();
        mQosSessions = response.getQosSessions();
        mQosBearerSessions = response.getQosBearerSessions();

        if (mNetworkAgent != null) {
            syncQosToNetworkAgent();
        }
    }

    private void syncQosToNetworkAgent() {
        final DcNetworkAgent networkAgent = mNetworkAgent;
        final List<QosBearerSession> qosBearerSessions = mQosBearerSessions;
        if (qosBearerSessions == null) {
            networkAgent.updateQosBearerSessions(new ArrayList<>());
            return;
        }
        networkAgent.updateQosBearerSessions(qosBearerSessions);
    }

    /**
@@ -2694,6 +2714,9 @@ public class DataConnection extends StateMachine {
                sendMessage(obtainMessage(EVENT_UPDATE_SUSPENDED_STATE));
            }

            // The qos parameters are set when the call is connected
            syncQosToNetworkAgent();

            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
                mPhone.mCi.registerForNattKeepaliveStatus(
                        getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
@@ -2722,6 +2745,7 @@ public class DataConnection extends StateMachine {
            // which is when IWLAN handover is ongoing. Instead of unregistering, the agent will
            // be transferred to the new data connection on the other transport.
            if (mNetworkAgent != null) {
                syncQosToNetworkAgent();
                if (mHandoverState == HANDOVER_STATE_IDLE) {
                    mNetworkAgent.unregister(DataConnection.this);
                }
@@ -3145,6 +3169,9 @@ public class DataConnection extends StateMachine {
                    if (DBG) log(str);
                    if (dp.mApnContext != null) dp.mApnContext.requestLog(str);

                    // Clear out existing qos sessions
                    updateQosParameters(null);

                    if (dp.mTag == mTag) {
                        // Transition to inactive but send notifications after
                        // we've entered the mInactive state.
@@ -3718,7 +3745,7 @@ public class DataConnection extends StateMachine {
        pw.println("mDownlinkBandwidth" + mDownlinkBandwidth);
        pw.println("mUplinkBandwidth=" + mUplinkBandwidth);
        pw.println("mDefaultQos=" + mDefaultQos);
        pw.println("mQosSessions=" + mQosSessions);
        pw.println("mQosBearerSessions=" + mQosBearerSessions);
        pw.println("disallowedApnTypes="
                + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
        pw.println("mInstanceNumber=" + mInstanceNumber);
+42 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.QosFilter;
import android.net.SocketKeepalive;
import android.net.Uri;
import android.os.Message;
@@ -35,6 +36,8 @@ import android.telephony.AnomalyReporter;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.QosBearerSession;
import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.SparseArray;
@@ -48,10 +51,14 @@ import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * This class represents a network agent which is communication channel between
@@ -68,7 +75,7 @@ public class DcNetworkAgent extends NetworkAgent {

    private final int mId;

    private Phone mPhone;
    private final Phone mPhone;

    private int mTransportType;

@@ -76,6 +83,10 @@ public class DcNetworkAgent extends NetworkAgent {

    public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();

    private final QosCallbackTracker mQosCallbackTracker = new QosCallbackTracker(this);

    private final Executor mQosCallbackExecutor = Executors.newSingleThreadExecutor();

    private DataConnection mDataConnection;

    private final LocalLog mNetCapsLocalLog = new LocalLog(50);
@@ -345,6 +356,36 @@ public class DcNetworkAgent extends NetworkAgent {
                .sendToTarget();
    }

    @Override
    public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
        mQosCallbackExecutor.execute(() -> mQosCallbackTracker.addFilter(qosCallbackId,
              new QosCallbackTracker.IFilter() {
                  @Override
                  public boolean matchesLocalAddress(
                          InetAddress address, int startPort, int endPort) {
                      return filter.matchesLocalAddress(address, startPort, endPort);
                  }
              }));
    }

    @Override
    public void onQosCallbackUnregistered(final int qosCallbackId) {
        mQosCallbackExecutor.execute(() -> mQosCallbackTracker.removeFilter(qosCallbackId));
    }

    void updateQosBearerSessions(final List<QosBearerSession> qosBearerSessions) {
        mQosCallbackExecutor.execute(() -> mQosCallbackTracker.updateSessions(qosBearerSessions));
    }

    public void notifyQosSessionAvailable(final int qosCallbackId, final int sessionId,
            @NonNull final EpsBearerQosSessionAttributes attributes) {
        super.sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
    }

    public void notifyQosSessionLost(final int qosCallbackId, final int sessionId) {
        super.sendQosSessionLost(qosCallbackId, sessionId);
    }

    @Override
    public String toString() {
        return "DcNetworkAgent-"
+215 −0
Original line number Diff line number Diff line
/*
 * 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.internal.telephony.dataconnection;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LinkAddress;
import android.telephony.data.EpsQos;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.QosBearerFilter;
import android.telephony.data.QosBearerSession;

import com.android.telephony.Rlog;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Matches filters with qos sessions and send corresponding available and lost events.
 *
 * Note: This class is <b>NOT</b> thread-safe
 *
 * {@hide}
 */
public class QosCallbackTracker {
    private static final String LOG_TAG = QosCallbackTracker.class.getSimpleName();
    @NonNull private final DcNetworkAgent mDcNetworkAgent;
    @NonNull private final Map<Integer, QosBearerSession> mQosBearerSessions;

    // We perform an exact match on the address
    @NonNull private final Map<Integer, IFilter> mCallbacksToFilter;

    /**
     * Construct a new tracker
     * @param dcNetworkAgent the network agent to send events to
     */
    public QosCallbackTracker(@NonNull final DcNetworkAgent dcNetworkAgent) {
        mQosBearerSessions = new HashMap<>();
        mCallbacksToFilter = new HashMap<>();
        mDcNetworkAgent = dcNetworkAgent;
    }

    /**
     * Add new filter that is to receive events
     *
     * @param callbackId the associated callback id
     * @param filter provides the matching logic
     */
    public void addFilter(final int callbackId, final IFilter filter) {
        logd("addFilter: callbackId=" + callbackId);
        // Called from mDcNetworkAgent
        mCallbacksToFilter.put(callbackId, filter);

        //On first change. Check all sessions and send.
        for (final QosBearerSession session : mQosBearerSessions.values()) {
            if (doFiltersMatch(session, filter)) {
                sendSessionAvailable(callbackId, session, filter);
            }
        }
    }

    /**
     * Remove the filter with the associated callback id
     *
     * @param callbackId the qos callback id
     */
    public void removeFilter(final int callbackId) {
        logd("removeFilter: callbackId=" + callbackId);
        mCallbacksToFilter.remove(callbackId);
    }

    /**
     * Update the list of qos sessions and send out corresponding events
     *
     * @param sessions the new list of qos sessions
     */
    public void updateSessions(@NonNull final List<QosBearerSession> sessions) {
        logd("updateSessions: sessions size=" + sessions.size());
        final List<QosBearerSession> sessionsToAdd = new ArrayList<>();
        final Map<Integer, QosBearerSession> incomingSessions = new HashMap<>();
        for (final QosBearerSession incomingSession : sessions) {
            incomingSessions.put(incomingSession.getQosBearerSessionId(), incomingSession);

            final QosBearerSession existingSession = mQosBearerSessions.get(
                    incomingSession.getQosBearerSessionId());
            for (final int callbackId : mCallbacksToFilter.keySet()) {
                final IFilter filter = mCallbacksToFilter.get(callbackId);

                final boolean incomingSessionMatch = doFiltersMatch(incomingSession, filter);
                final boolean existingSessionMatch =
                        existingSession != null && doFiltersMatch(existingSession, filter);

                if (!existingSessionMatch && incomingSessionMatch) {
                    // The filter matches now and didn't match earlier
                    sendSessionAvailable(callbackId, incomingSession, filter);
                }

                if (existingSessionMatch && incomingSessionMatch) {
                    // The same sessions matches the same filter, but if the qos changed,
                    // the callback still needs to be notified
                    if (!incomingSession.getQos().equals(existingSession.getQos())) {
                        sendSessionAvailable(callbackId, incomingSession, filter);
                    }
                }
            }
            sessionsToAdd.add(incomingSession);
        }

        final List<Integer> sessionsToRemove = new ArrayList<>();
        // Find sessions that no longer exist
        for (final QosBearerSession existingSession : mQosBearerSessions.values()) {
            if (!incomingSessions.containsKey(existingSession.getQosBearerSessionId())) {
                for (final int callbackId : mCallbacksToFilter.keySet()) {
                    final IFilter filter = mCallbacksToFilter.get(callbackId);
                    // The filter matches which means it was previously available, and now is lost
                    if (doFiltersMatch(existingSession, filter)) {
                        sendSessionLost(callbackId, existingSession);
                    }
                }
                sessionsToRemove.add(existingSession.getQosBearerSessionId());
            }
        }

        // Add in the new or existing sessions with updated information
        for (final QosBearerSession sessionToAdd : sessionsToAdd) {
            mQosBearerSessions.put(sessionToAdd.getQosBearerSessionId(), sessionToAdd);
        }

        // Remove any old sessions
        for (final int sessionToRemove : sessionsToRemove) {
            mQosBearerSessions.remove(sessionToRemove);
        }
    }

    private boolean doFiltersMatch(
            final QosBearerSession qosBearerSession, final IFilter filter) {
        return getMatchingQosBearerFilter(qosBearerSession, filter) != null;
    }

    private QosBearerFilter getMatchingQosBearerFilter(
            final QosBearerSession qosBearerSession, final IFilter filter) {
        QosBearerFilter qosFilter = null;

        for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
            for (final LinkAddress qosAddress : sessionFilter.getLocalAddresses()) {
                if (filter.matchesLocalAddress(qosAddress.getAddress(),
                        sessionFilter.getLocalPortRange().getStart(),
                        sessionFilter.getLocalPortRange().getEnd())) {
                    // Find for the highest precedence filter
                    if (qosFilter == null
                            || sessionFilter.getPrecedence() < qosFilter.getPrecedence()) {
                        qosFilter = sessionFilter;
                    }
                }
            }
        }
        return qosFilter;
    }

    private void sendSessionAvailable(final int callbackId,
            @NonNull final QosBearerSession session, @NonNull IFilter filter) {
        QosBearerFilter qosBearerFilter = getMatchingQosBearerFilter(session, filter);
        List<InetSocketAddress> remoteAddresses = new ArrayList<>();
        EpsQos qos = (EpsQos) session.getQos();
        if(qosBearerFilter.getRemoteAddresses().size() > 0) {
            remoteAddresses.add(
                  new InetSocketAddress(qosBearerFilter.getRemoteAddresses().get(0).getAddress(),
                  qosBearerFilter.getRemotePortRange().getStart()));
        }
        EpsBearerQosSessionAttributes epsBearerAttr =
                new EpsBearerQosSessionAttributes(qos.getQci(),
                        qos.getUplinkBandwidth().getMaxBitrateKbps(),
                        qos.getDownlinkBandwidth().getMaxBitrateKbps(),
                        qos.getDownlinkBandwidth().getGuaranteedBitrateKbps(),
                        qos.getUplinkBandwidth().getGuaranteedBitrateKbps(),
                        remoteAddresses);
        mDcNetworkAgent.notifyQosSessionAvailable(
                callbackId, session.getQosBearerSessionId(), epsBearerAttr);
    }

    private void sendSessionLost(final int callbackId, @NonNull final QosBearerSession session) {
        mDcNetworkAgent.notifyQosSessionLost(callbackId, session.getQosBearerSessionId());
    }

    public interface IFilter {
        public boolean matchesLocalAddress(InetAddress address, int startPort, int endPort);
    }

    /**
     * Log with debug level
     *
     * @param s is string log
     */
    private void logd(String s) {
        Rlog.d(LOG_TAG, s);
    }
}
+12 −12
Original line number Diff line number Diff line
@@ -158,8 +158,8 @@ import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataProfile;
import android.telephony.data.EpsQos;
import android.telephony.data.QosFilter;
import android.telephony.data.QosSession;
import android.telephony.data.QosBearerFilter;
import android.telephony.data.QosBearerSession;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

@@ -2180,7 +2180,7 @@ public class RILTest extends TelephonyTest {
                .setMtu(1500)
                .setMtuV4(1500)
                .setMtuV6(1500)
                .setQosSessions(new ArrayList<>())
                .setQosBearerSessions(new ArrayList<>())
                .build();

        assertEquals(response, RIL.convertDataCallResult(result10));
@@ -2254,7 +2254,7 @@ public class RILTest extends TelephonyTest {
                .setMtu(3000)
                .setMtuV4(1500)
                .setMtuV6(3000)
                .setQosSessions(new ArrayList<>())
                .setQosBearerSessions(new ArrayList<>())
                .build();

        assertEquals(response, RIL.convertDataCallResult(result15));
@@ -2322,18 +2322,18 @@ public class RILTest extends TelephonyTest {
        result16.qosSessions = new ArrayList<>(Arrays.asList(halQosSession));

        EpsQos epsQos = new EpsQos(halEpsQos);
        QosFilter qosFilter = new QosFilter(
        QosBearerFilter qosFilter = new QosBearerFilter(
                Arrays.asList(
                        new LinkAddress(InetAddresses.parseNumericAddress("122.22.22.22"), 32)),
                Arrays.asList(
                        new LinkAddress(InetAddresses.parseNumericAddress("144.44.44.44"), 32)),
                new QosFilter.PortRange(123, 123), new QosFilter.PortRange(223, 223),
                QosFilter.QOS_PROTOCOL_UDP, 7, 987, 678,
                QosFilter.QOS_FILTER_DIRECTION_BIDIRECTIONAL, 45);
        ArrayList<QosFilter> qosFilters = new ArrayList<>();
        ArrayList<QosSession> qosSessions = new ArrayList<>();
                new QosBearerFilter.PortRange(123, 123), new QosBearerFilter.PortRange(223, 223),
                QosBearerFilter.QOS_PROTOCOL_UDP, 7, 987, 678,
                QosBearerFilter.QOS_FILTER_DIRECTION_BIDIRECTIONAL, 45);
        ArrayList<QosBearerFilter> qosFilters = new ArrayList<>();
        ArrayList<QosBearerSession> qosSessions = new ArrayList<>();
        qosFilters.add(qosFilter);
        QosSession qosSession = new QosSession(1234, epsQos, qosFilters);
        QosBearerSession qosSession = new QosBearerSession(1234, epsQos, qosFilters);
        qosSessions.add(qosSession);

        response = new DataCallResponse.Builder()
@@ -2359,7 +2359,7 @@ public class RILTest extends TelephonyTest {
                .setMtuV6(3000)
                .setHandoverFailureMode(DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY)
                .setDefaultQos(epsQos)
                .setQosSessions(qosSessions)
                .setQosBearerSessions(qosSessions)
                .build();

        assertEquals(response, RIL.convertDataCallResult(result16));
Loading