blob: 795ed147b43229b2b381708c6186f535b3c6465a [file] [log] [blame]
/*
* 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.net.QosSession;
import android.telephony.data.EpsQos;
import android.telephony.data.NrQos;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
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 {
@NonNull private final String mTag;
@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;
mTag = "QosCallbackTracker" + "-" + mDcNetworkAgent.getNetwork().getNetId();
}
/**
* 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 boolean matchesByLocalAddress(
QosBearerFilter sessionFilter, final IFilter filter) {
for (final LinkAddress qosAddress : sessionFilter.getLocalAddresses()) {
return filter.matchesLocalAddress(qosAddress.getAddress(),
sessionFilter.getLocalPortRange().getStart(),
sessionFilter.getLocalPortRange().getEnd());
}
return false;
}
private boolean matchesByRemoteAddress(
QosBearerFilter sessionFilter, final IFilter filter) {
for (final LinkAddress qosAddress : sessionFilter.getRemoteAddresses()) {
return filter.matchesRemoteAddress(qosAddress.getAddress(),
sessionFilter.getRemotePortRange().getStart(),
sessionFilter.getRemotePortRange().getEnd());
}
return false;
}
private boolean matchesByRemoteAndLocalAddress(
QosBearerFilter sessionFilter, final IFilter filter) {
for (final LinkAddress remoteAddress : sessionFilter.getRemoteAddresses()) {
for (final LinkAddress localAddress : sessionFilter.getLocalAddresses()) {
return filter.matchesRemoteAddress(remoteAddress.getAddress(),
sessionFilter.getRemotePortRange().getStart(),
sessionFilter.getRemotePortRange().getEnd())
&& filter.matchesLocalAddress(localAddress.getAddress(),
sessionFilter.getLocalPortRange().getStart(),
sessionFilter.getLocalPortRange().getEnd());
}
}
return false;
}
private QosBearerFilter getFilterByPrecedence(
QosBearerFilter qosFilter, QosBearerFilter sessionFilter) {
// Find for the highest precedence filter, lower the value is the higher the precedence
return qosFilter == null || sessionFilter.getPrecedence() < qosFilter.getPrecedence()
? sessionFilter : qosFilter;
}
private QosBearerFilter getMatchingQosBearerFilter(
final QosBearerSession qosBearerSession, final IFilter filter) {
QosBearerFilter qosFilter = null;
for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
if (!sessionFilter.getLocalAddresses().isEmpty()
&& !sessionFilter.getRemoteAddresses().isEmpty()
&& sessionFilter.getLocalPortRange().isValid()
&& sessionFilter.getRemotePortRange().isValid()) {
if (matchesByRemoteAndLocalAddress(sessionFilter, filter)) {
qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
}
} else if (!sessionFilter.getRemoteAddresses().isEmpty()
&& sessionFilter.getRemotePortRange().isValid()) {
if (matchesByRemoteAddress(sessionFilter, filter)) {
qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
}
} else if (!sessionFilter.getLocalAddresses().isEmpty()
&& sessionFilter.getLocalPortRange().isValid()) {
if (matchesByLocalAddress(sessionFilter, filter)) {
qosFilter = getFilterByPrecedence(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<>();
if(qosBearerFilter.getRemoteAddresses().size() > 0) {
remoteAddresses.add(
new InetSocketAddress(qosBearerFilter.getRemoteAddresses().get(0).getAddress(),
qosBearerFilter.getRemotePortRange().getStart()));
}
if (session.getQos() instanceof EpsQos) {
EpsQos qos = (EpsQos) session.getQos();
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);
} else {
NrQos qos = (NrQos) session.getQos();
NrQosSessionAttributes nrQosAttr =
new NrQosSessionAttributes(qos.get5Qi(), qos.getQfi(),
qos.getUplinkBandwidth().getMaxBitrateKbps(),
qos.getDownlinkBandwidth().getMaxBitrateKbps(),
qos.getDownlinkBandwidth().getGuaranteedBitrateKbps(),
qos.getUplinkBandwidth().getGuaranteedBitrateKbps(),
qos.getAveragingWindow(), remoteAddresses);
mDcNetworkAgent.notifyQosSessionAvailable(
callbackId, session.getQosBearerSessionId(), nrQosAttr);
}
logd("sendSessionAvailable, callbackId=" + callbackId);
}
private void sendSessionLost(final int callbackId, @NonNull final QosBearerSession session) {
mDcNetworkAgent.notifyQosSessionLost(callbackId, session.getQosBearerSessionId(),
session.getQos() instanceof EpsQos ?
QosSession.TYPE_EPS_BEARER : QosSession.TYPE_NR_BEARER);
logd("sendSessionLost, callbackId=" + callbackId);
}
public interface IFilter {
public boolean matchesLocalAddress(InetAddress address, int startPort, int endPort);
public boolean matchesRemoteAddress(InetAddress address, int startPort, int endPort);
}
/**
* Log with debug level
*
* @param s is string log
*/
private void logd(String s) {
Rlog.d(mTag, s);
}
}