All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdiameter.client.impl.router.RouterImpl Maven / Gradle / Ivy

There is a newer version: 2.0.5
Show newest version
/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2016, TeleStax Inc. and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see 
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 *   JBoss, Home of Professional Open Source
 *   Copyright 2007-2011, Red Hat, Inc. and individual contributors
 *   by the @authors tag. See the copyright.txt in the distribution for a
 *   full listing of individual contributors.
 *
 *   This is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation; either version 2.1 of
 *   the License, or (at your option) any later version.
 *
 *   This software is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this software; if not, write to the Free
 *   Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 *   02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jdiameter.client.impl.router;

import org.jdiameter.api.*;
import org.jdiameter.client.api.IAnswer;
import org.jdiameter.client.api.IContainer;
import org.jdiameter.client.api.IMessage;
import org.jdiameter.client.api.IRequest;
import org.jdiameter.client.api.controller.IPeer;
import org.jdiameter.client.api.controller.IPeerTable;
import org.jdiameter.client.api.controller.IRealm;
import org.jdiameter.client.api.controller.IRealmTable;
import org.jdiameter.client.api.router.IRouter;
import org.jdiameter.client.impl.helpers.AppConfiguration;
import org.jdiameter.client.impl.helpers.Parameters;
import org.jdiameter.client.impl.parser.MessageImpl;
import org.jdiameter.common.api.concurrent.IConcurrentFactory;
import org.jdiameter.server.api.agent.IAgentConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.UnknownServiceException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static org.jdiameter.client.impl.helpers.Parameters.*;
import static org.jdiameter.server.impl.helpers.Parameters.*;

/**
 * Diameter Routing Core
 *
 * @author  Alexandre Mendonca 
 * @author  Bartosz Baranowski 
 */
@SuppressWarnings("all") //3rd party lib
public class RouterImpl implements IRouter
{

    public static final int DONT_CACHE = 0;
    public static final int ALL_SESSION = 1;
    public static final int ALL_REALM = 2;
    public static final int REALM_AND_APPLICATION = 3;
    public static final int ALL_APPLICATION = 4;
    public static final int ALL_HOST = 5;
    public static final int ALL_USER = 6;
    //
    private static final Logger logger = LoggerFactory.getLogger(RouterImpl.class);
    protected MetaData metaData;
    //
    //private ConcurrentHashMap network = new ConcurrentHashMap();
    protected IRealmTable realmTable;
    // Redirection feature
    public final int REDIRECT_TABLE_SIZE = 1024;
    //TODO: index it differently.
    protected List redirectTable = new ArrayList(REDIRECT_TABLE_SIZE);
    protected IConcurrentFactory concurrentFactory;

    protected IContainer container;

    // Answer routing feature
    public static int REQUEST_TABLE_SIZE = 10 * 1024;
    public static int REQUEST_TABLE_CLEAR_SIZE = 2 * 1024;

    protected Lock requestEntryTableLock = new ReentrantLock();
    protected ReadWriteLock redirectTableLock = new ReentrantReadWriteLock();
    //PCB added
    protected Map requestEntryMap;
    //protected List requestSortedEntryTable = new ArrayList();
    protected boolean isStopped = true;

    public RouterImpl(IContainer container, IConcurrentFactory concurrentFactory, IRealmTable realmTable, Configuration config,
                      MetaData aMetaData)
    {
        this.concurrentFactory = concurrentFactory;
        this.metaData          = aMetaData;
        this.realmTable        = realmTable;
        this.container         = container;
        logger.debug("Constructor for RouterImpl: Calling loadConfiguration");
        loadConfiguration(config);
    }

    protected void loadConfiguration(Configuration config)
    {
        logger.debug("Loading Router Configuration. Populating Realms, Application IDs, etc");
        //add local realm : this might not be good
        String localRealm = config.getStringValue(OwnRealm.ordinal(), null);
        String localHost = config.getStringValue(Parameters.OwnDiameterURI.ordinal(), null);
        try {
            this.realmTable.addLocalRealm(localRealm, new URI(localHost).getFQDN());
        }
        catch (UnknownServiceException use) {
            throw new RuntimeException("Unable to create URI from Own URI config value:" + localHost, use);
        }
        catch (URISyntaxException use) {
            throw new RuntimeException("Unable to create URI from Own URI config value:" + localHost, use);
        }
        if (config.getChildren(RequestTable.ordinal()) != null) {
            AppConfiguration requestTableConfig = (AppConfiguration) config
                    .getChildren(org.jdiameter.server.impl.helpers.Parameters.RequestTable.ordinal())[0];
            int tSize = requestTableConfig.getIntValue(RequestTableSize.ordinal(), (Integer) RequestTableSize.defValue());
            int tClearSize = requestTableConfig.getIntValue(RequestTableClearSize.ordinal(),
                                                            (Integer) RequestTableClearSize.defValue());
            if (tSize > 0 && tClearSize >= tSize) {
                logger.warn(
                        "Configuration entry RequestTable, attribute 'clear_size' [{}] should not be greater than 'size' [{}]. Adjusting.",
                        tSize, tClearSize);
                while (tClearSize >= tSize) {
                    tSize *= 10;
                }
            }
            REQUEST_TABLE_SIZE       = tSize;
            REQUEST_TABLE_CLEAR_SIZE = tClearSize;
        }
        //PCB added thread safety
        this.requestEntryMap = new ConcurrentHashMap(REQUEST_TABLE_SIZE);
        logger.debug("Configured Request Table with size[{}] and clear size[{}].", REQUEST_TABLE_SIZE,
                     REQUEST_TABLE_CLEAR_SIZE);

        //add realms based on realm table.
        if (config.getChildren(RealmTable.ordinal()) != null) {
            logger.debug("Going to loop through configured realms and add them into a network map");
            for (Configuration items : config.getChildren(RealmTable.ordinal())) {
                if (items != null) {
                    Configuration[] m = items.getChildren(RealmEntry.ordinal());
                    for (Configuration c : m) {
                        try {
                            String name = c.getStringValue(RealmName.ordinal(), "");
                            logger.debug("Getting config for realm [{}]", name);
                            ApplicationId appId = null;
                            {
                                Configuration[] apps = c.getChildren(ApplicationId.ordinal());
                                if (apps != null) {
                                    for (Configuration a : apps) {
                                        if (a != null) {
                                            long vnd = a.getLongValue(VendorId.ordinal(), 0);
                                            long auth = a.getLongValue(AuthApplId.ordinal(), 0);
                                            long acc = a.getLongValue(AcctApplId.ordinal(), 0);
                                            if (auth != 0) {
                                                appId = org.jdiameter.api.ApplicationId.createByAuthAppId(vnd, auth);
                                            } else {
                                                appId = org.jdiameter.api.ApplicationId.createByAccAppId(vnd, acc);
                                            }
                                            if (logger.isDebugEnabled()) {
                                                logger.debug("Realm [{}] has application Acct [{}] Auth [{}] Vendor [{}]",
                                                             new Object[]{name, appId.getAcctAppId(),
                                                                          appId.getAuthAppId(),
                                                                          appId.getVendorId()});
                                            }
                                            break;
                                        }
                                    }
                                }
                            }
                            //Trim leading and trailing white spaces
                            String[] hosts = Arrays.stream(c.getStringValue(RealmHosts.ordinal(), (String) RealmHosts.defValue()).split(","))
                                                   .map(String::trim)
                                                   .toArray(String[]::new);
                            logger.debug("Adding realm [{}] with hosts [{}] to network map", name, hosts);
                            LocalAction locAction = LocalAction.valueOf(c.getStringValue(RealmLocalAction.ordinal(), "0"));
                            boolean isDynamic = c.getBooleanValue(RealmEntryIsDynamic.ordinal(), false);
                            long expirationTime = c.getLongValue(RealmEntryExpTime.ordinal(), 0);
                            //check if there is Agent, ATM we support only props there.
                            IAgentConfiguration agentConfImpl = null;
                            Configuration[] confs = c.getChildren(Agent.ordinal());
                            if (confs != null && confs.length > 0) {
                                Configuration agentConfiguration = confs[0]; //only one!
                                agentConfImpl = this.container.getAssemblerFacility()
                                                              .getComponentInstance(IAgentConfiguration.class);

                                if (agentConfImpl != null) {
                                    agentConfImpl = agentConfImpl.parse(agentConfiguration);
                                }
                            }
                            this.realmTable.addRealm(name, appId, locAction, agentConfImpl, isDynamic, expirationTime, hosts);
                        }
                        catch (Exception e) {
                            logger.warn("Unable to append realm entry", e);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void registerRequestRouteInfo(IRequest request)
    {
        logger.debug("Entering registerRequestRouteInfo");
        if (REQUEST_TABLE_SIZE == 0) {
            return; // we don't have anything to do as we are storing routing info at answer message
        }

        try {
            // PCB removed lock
            // requestEntryTableLock.writeLock().lock();
            long hopByHopId = request.getHopByHopIdentifier();
            Avp hostAvp = request.getAvps().getAvp(Avp.ORIGIN_HOST);
            // we store the peer FQDN instead of Origin-Host as we want to route back to it, in case of proxied requests this
            // should be the FQDN of the proxy, otherwise it's (should be) the same as Origin-Host
            String host = ((IMessage) request).getPeer() != null ? ((IMessage) request).getPeer().getUri().getFQDN()
                                                                 : hostAvp.getDiameterIdentity();
            Avp realmAvp = request.getAvps().getAvp(Avp.ORIGIN_REALM);

            AnswerEntry entry;
            AvpSet rrAvps = request.getAvps().getAvps(Avp.ROUTE_RECORD);
            if (rrAvps.size() > 0) {
                logger.debug("Found [{}] Route-Record AVP(s) in Request with HbH [{}], storing them for copying and routing.",
                             rrAvps.size(), request.getHopByHopIdentifier());
                ArrayList rrStrings = new ArrayList();
                for (Avp rrAvp : rrAvps) {
                    String rrAvpHost = rrAvp.getDiameterIdentity();
                    logger.trace("Route-Record in Request with HbH [{}]: [{}]", request.getHopByHopIdentifier(), rrAvpHost);
                    rrStrings.add(rrAvpHost);
                }
                entry = new AnswerEntry(hopByHopId, host, realmAvp != null ? realmAvp.getDiameterIdentity() : null, rrStrings);
            } else {
                entry = new AnswerEntry(hopByHopId, host, realmAvp != null ? realmAvp.getDiameterIdentity() : null);
            }

            int s = requestEntryMap.size();
            // PCB added logging
            logger.debug("RequestRoute map size is [{}]", s);

            //PCB added
            if (s > REQUEST_TABLE_CLEAR_SIZE) {
                try {
                    requestEntryTableLock.lock();
                    s = requestEntryMap.size();
                    logger.debug("After 'lock', RequestRoute map size is [{}]", s);
                    // The double-check with a gap is in case while about to clear the map, it drops below REQUEST_TABLE_SIZE due to a
                    // response being sent, and then the lock might not be in effect for another thread
                    // Hence lock at REQUEST_TABLE_CLEAR_SIZE and clear at REQUEST_TABLE_SIZE so that all threads would be locked not
                    // just the first to reach REQUEST_TABLE_SIZE
                    if (s > REQUEST_TABLE_SIZE) {
                        // Going to clear it out and suffer the consequences of some messages possibly not being routed back the clients..
                        logger.warn("RequestRoute map size is [{}]. There's probably a leak. Cleaning up after a short wait...",
                                    s);
                        // Lets do our best to avoid lost messages by locking table from writes (as this is the only code writing to it),
                        // sleeping for a while for responses to be sent and then clearing it
                        Thread.sleep(5000);
                        logger.warn("RequestRoute map size is now [{}] after sleeping. Clearing it!", requestEntryMap.size());
                        requestEntryMap.clear();
                    }
                }
                catch (Exception e) {
                    logger.warn("Failure trying to clear RequestRoute map", e);
                }
                finally {
                    requestEntryTableLock.unlock();
                }
            }

            String messageKey = makeRoutingKey(request);
            logger.debug(
                    "Adding request key [{}] to RequestRoute map with entry [{}] for routing answers back to the requesting peer",
                    messageKey, entry);
            requestEntryMap.put(messageKey, entry);
            // requestSortedEntryTable.add(hopByHopId);
        }
        catch (Exception e) {
            logger.warn("Unable to store route info", e);
        }
        finally {
            // requestEntryTableLock.writeLock().unlock();
        }
    }

    // PCB - Made better routing algorithm that should not grow all the time
    private String makeRoutingKey(Message message)
    {
        String sessionId = message.getSessionId();
        return new StringBuilder(sessionId != null ? sessionId : "null").append(message.getEndToEndIdentifier())
                                                                        .append(message.getHopByHopIdentifier()).toString();
    }

    private String[] getRequestRouteInfoAndCopyProxyAvps(IMessage message, boolean copy)
    {
        if (REQUEST_TABLE_SIZE == 0) {
            return ((MessageImpl) message).getRoutingInfo(); // using answer stored routing info
            // TODO: Handle copy Proxy AVPs in this case...
        }

        // using request table
        String messageKey = makeRoutingKey(message);
        AnswerEntry ans = requestEntryMap.get(messageKey);
        if (ans != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("getRequestRouteInfo found host [{}] and realm [{}] for Message key Id [{}]",
                             new Object[]{ans.getHost(), ans.getRealm(), messageKey});
            }
            if (ans.getRouteRecords() != null && ans.getRouteRecords().size() > 0) {
                AvpSet msgRouteRecords = message.getAvps().getAvps(Avp.ROUTE_RECORD);
                if (msgRouteRecords.size() > 0) {
                    logger.debug("We had Route-Records to insert but the message already has some... not doing anything");
                } else {
                    for (String rr : ans.getRouteRecords()) {
                        message.getAvps().addAvp(Avp.ROUTE_RECORD, rr, true);
                    }
                }
            }
            return new String[]{ans.getHost(), ans.getRealm()};
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("Could not find route info for message key [{}]. Table size is [{}]", messageKey,
                            requestEntryMap.size());
            }
            return null;
        }
    }

    @Override
    public String[] getRequestRouteInfo(IMessage message)
    {
        return getRequestRouteInfoAndCopyProxyAvps(message, false);
    }

    //PCB added
    @Override
    public void garbageCollectRequestRouteInfo(IMessage message)
    {
        if (REQUEST_TABLE_SIZE == 0) {
            return; // we don't have anything to do as we are storing routing info at answer message
        }

        String messageKey = makeRoutingKey(message);
        requestEntryMap.remove(messageKey);
    }

    @Override
    public IPeer getPeer(IMessage message, IPeerTable manager) throws RouteException, AvpDataException
    {
        logger.debug("Getting a peer for message [{}]", message);
        //FIXME: add ability to send without matching realm+peer pair?, that is , route based on peer table entries?
        //that is, if msg.destHost != null > getPeer(msg.destHost).sendMessage(msg);
        String destRealm = null;
        String destHost = null;
        IRealm matchedRealm = null;
        String[] info = null;
        // Get destination information
        if (message.isRequest()) {
            Avp avpRealm = message.getAvps().getAvp(Avp.DESTINATION_REALM);
            if (avpRealm == null) {
                throw new RouteException("Destination realm avp is empty");
            }
            destRealm = avpRealm.getDiameterIdentity();

            Avp avpHost = message.getAvps().getAvp(Avp.DESTINATION_HOST);
            if (avpHost != null) {
                destHost = avpHost.getDiameterIdentity();
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Looking up peer for request: [{}], DestHost=[{}], DestRealm=[{}]",
                             new Object[]{message, destHost, destRealm});
            }

            matchedRealm = (IRealm) this.realmTable.matchRealm(message);
        } else {
            //answer, search
            info = getRequestRouteInfoAndCopyProxyAvps(message, true);

            if (info != null) {
                destHost  = info[0];
                destRealm = info[1];
                logger.debug("Message is an answer. Host is [{}] and Realm is [{}] as per hopbyhop info from request", destHost,
                             destRealm);
                if (destRealm == null) {
                    logger.warn("Destination-Realm was null for hopbyhop id " + message.getHopByHopIdentifier());
                }
            } else {
                logger.debug("No Host and realm found based on hopbyhop id of the answer associated request");
            }
            //FIXME: if no info, should not send it ?
            //FIXME: add strict deff in route back table so stack does not have to lookup?
            if (logger.isDebugEnabled()) {
                logger.debug("Looking up peer for answer: [{}], DestHost=[{}], DestRealm=[{}]",
                             new Object[]{message, destHost, destRealm});
            }
            matchedRealm = (IRealm) this.realmTable.matchRealm((IAnswer) message, destRealm);
        }

        //  IPeer peer = getPeerPredProcessing(message, destRealm, destHost);
        //
        //  if (peer != null) {
        //    logger.debug("Found during preprocessing...[{}]", peer);
        //    return peer;
        //  }

        // Check realm name
        if (matchedRealm == null) {
            if (message.getAvps().getAvp(Avp.ROUTE_RECORD) == null) {
                // if it doesn't come through a proxy, we fail with unknown realm...
                throw new RouteException("Unknown realm name [" + destRealm + "]");
            } else {
                logger.debug(
                        "Realm [{}] not found, but message has Route-Record AVP so it came from proxy peer [{}]. Proceeding...",
                        destRealm, destHost);
            }
        }

        // THIS IS GET PEER, NOT ROUTE!!!!!!!
        // Redirect processing
        //redirectProcessing(message, destRealm, destHost);
        // Check previous context information, this takes care of most answers.
        if (message.getPeer() != null && destHost != null && destHost.equals(message.getPeer().getUri().getFQDN())
            && message.getPeer().hasValidConnection()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Select previous message usage peer [{}]", message.getPeer());
            }
            return message.getPeer();
        }

        // Balancing procedure

        IPeer c = destHost != null ? manager.getPeer(destHost) : null;

        if (c != null && c.hasValidConnection()) {
            logger.debug("Found a peer using destination host avp [{}] peer is [{}] with a valid connection.", destHost, c);
            //here matchedRealm MAY
            return c;
        } else {
            logger.debug(
                    "Finding peer by destination host avp [host={}] did not find anything. Now going to try finding one by destination realm [{}]",
                    destHost, destRealm);
            String[] peers = matchedRealm.getPeerNames();
            if (peers == null || peers.length == 0) {
                throw new RouteException("Unable to find context by route information [" + destRealm + " ," + destHost + "]");
            }

            // Collect peers
            ArrayList availablePeers = new ArrayList(5);
            logger.debug("Looping through peers in realm [{}]", destRealm);
            for (String peerName : peers) {
                IPeer localPeer = manager.getPeer(peerName);
                if (logger.isDebugEnabled()) {
                    logger.debug("Checking peer [{}] for name [{}]", new Object[]{localPeer, peerName});
                }
                // ammendonca: added peer state check.. should not be needed but
                // hasValidConnection is returning true for disconnected peers in *FTFlowTests
                if (localPeer != null && localPeer.getState(PeerState.class) == PeerState.OKAY) {
                    if (localPeer.hasValidConnection()) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(
                                    "Found available peer to add to available peer list with uri [{}] with a valid connection",
                                    localPeer.getUri().toString());
                        }
                        availablePeers.add(localPeer);
                    } else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Found a peer with uri [{}] with no valid connection", localPeer.getUri());
                        }
                    }
                }
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Performing Realm routing. Realm [{}] has the following peers available [{}] from list [{}]",
                             new Object[]{destRealm, availablePeers, Arrays.asList(peers)});
            }

            // Balancing
            IPeer peer = selectPeer(availablePeers);
            if (peer == null) {
                throw new RouteException(
                        "Unable to find valid connection to peer[" + destHost + "] in realm[" + destRealm + "]");
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Load balancing selected peer with uri [{}]", peer.getUri());
                }
            }

            return peer;
        }
    }

    @Override
    public IRealmTable getRealmTable()
    {
        return this.realmTable;
    }

    @Override
    public void processRedirectAnswer(IRequest request, IAnswer answer, IPeerTable table)
            throws InternalException, RouteException
    {
        try {
            Avp destinationRealmAvp = request.getAvps().getAvp(Avp.DESTINATION_REALM);
            if (destinationRealmAvp == null) {
                throw new RouteException("Request to be routed has no Destination-Realm AVP!"); // sanity check... if user messes with us
            }
            String destinationRealm = destinationRealmAvp.getDiameterIdentity();
            String[] redirectHosts = null;
            if (answer.getAvps().getAvps(Avp.REDIRECT_HOST) != null) {
                AvpSet avps = answer.getAvps().getAvps(Avp.REDIRECT_HOST);
                redirectHosts = new String[avps.size()];
                int i = 0;
                // loop detected
                for (Avp avp : avps) {
                    String r = avp.getDiameterIdentity();
                    if (r.equals(metaData.getLocalPeer().getUri().getFQDN())) {
                        throw new RouteException("Loop detected");
                    }
                    redirectHosts[i++] = r;
                }
            }
            //
            int redirectUsage = DONT_CACHE;
            Avp redirectHostUsageAvp = answer.getAvps().getAvp(Avp.REDIRECT_HOST_USAGE);
            if (redirectHostUsageAvp != null) {
                redirectUsage = redirectHostUsageAvp.getInteger32();
            }

            if (redirectUsage != DONT_CACHE) {
                long redirectCacheTime = 0;
                Avp redirectCacheMaxTimeAvp = answer.getAvps().getAvp(Avp.REDIRECT_MAX_CACHE_TIME);
                if (redirectCacheMaxTimeAvp != null) {
                    redirectCacheTime = redirectCacheMaxTimeAvp.getUnsigned32();
                }
                String primaryKey = null;
                ApplicationId secondaryKey = null;
                switch (redirectUsage) {
                    case ALL_SESSION:
                        primaryKey = request.getSessionId();
                        break;
                    case ALL_REALM:
                        primaryKey = destinationRealm;
                        break;
                    case REALM_AND_APPLICATION:
                        primaryKey = destinationRealm;
                        secondaryKey = ((IMessage) request).getSingleApplicationId();
                        break;
                    case ALL_APPLICATION:
                        secondaryKey = ((IMessage) request).getSingleApplicationId();
                        break;
                    case ALL_HOST:
                        Avp destinationHostAvp = request.getAvps().getAvp(Avp.DESTINATION_HOST);
                        if (destinationHostAvp == null) {
                            throw new RouteException("Request to be routed has no Destination-Host AVP!"); // sanity check... if user messes with us
                        }
                        primaryKey = destinationHostAvp.getDiameterIdentity();
                        break;
                    case ALL_USER:
                        Avp userNameAvp = answer.getAvps().getAvp(Avp.USER_NAME);
                        if (userNameAvp == null) {
                            throw new RouteException("Request to be routed has no User-Name AVP!"); // sanity check... if user messes with us
                        }
                        primaryKey = userNameAvp.getUTF8String();
                        break;
                }
                //
                if (redirectTable.size() > REDIRECT_TABLE_SIZE) {
                    try {
                        //yes, possible that this will trigger this procedure twice, but thats worst than locking always.
                        redirectTableLock.writeLock().lock();
                        trimRedirectTable();
                    }
                    finally {
                        redirectTableLock.writeLock().unlock();
                    }
                }
                if (REDIRECT_TABLE_SIZE > redirectTable.size()) {
                    RedirectEntry e = new RedirectEntry(primaryKey, secondaryKey, redirectCacheTime, redirectUsage,
                                                        redirectHosts, destinationRealm);
                    redirectTable.add(e);
                    //redirectProcessing(answer, destRealm.getOctetString(), destHost !=null ? destHost.getOctetString():null);
                    //we dont have to elect?
                    updateRoute(request, e.getRedirectHost());
                } else {
                    if (redirectHosts != null && redirectHosts.length > 0) {
                        String destHost = redirectHosts[0];
                        //setRouteInfo(answer, getRealmForPeer(destHost), destHost);
                        updateRoute(request, destHost);
                    }
                }
            } else {
                if (redirectHosts != null && redirectHosts.length > 0) {
                    String destHost = redirectHosts[0];
                    //setRouteInfo(answer, getRealmForPeer(destHost), destHost);
                    updateRoute(request, destHost);
                }
            }
            //now send
            table.sendMessage((IMessage) request);
        }
        catch (AvpDataException exc) {
            throw new InternalException(exc);
        }
        catch (IllegalDiameterStateException e) {
            throw new InternalException(e);
        }
        catch (IOException e) {
            throw new InternalException(e);
        }
    }

    /**
     *
     */
    private void trimRedirectTable()
    {
        for (int index = 0; index < redirectTable.size(); index++) {
            try {
                if (redirectTable.get(index).getExpiredTime() <= System.currentTimeMillis()) {
                    redirectTable.remove(index);
                    index--; //a trick :)
                }
            }
            catch (Exception e) {
                logger.debug("Error in redirect task cleanup.", e);
                break;
            }
        }
    }

    /**
     * @param request
     * @param destHost
     */
    private void updateRoute(IRequest request, String destHost)
    {
        // Realm does not change I think... :)
        request.getAvps().removeAvp(Avp.DESTINATION_HOST);
        request.getAvps().addAvp(Avp.DESTINATION_HOST, destHost, true, false, true);
    }

    @Override
    public boolean updateRoute(IRequest message) throws RouteException, AvpDataException
    {
        AvpSet set = message.getAvps();
        Avp destRealmAvp = set.getAvp(Avp.DESTINATION_REALM);
        Avp destHostAvp = set.getAvp(Avp.DESTINATION_HOST);

        if (destRealmAvp == null) {
            throw new RouteException("Request does not have Destination-Realm AVP!");
        }

        String destRealm = destRealmAvp.getDiameterIdentity();
        String destHost = destHostAvp != null ? destHostAvp.getDiameterIdentity() : null;

        boolean matchedEntry = false;
        String userName = null;
        // get Session id
        String sessionId = message.getSessionId();
        //
        Avp avpUserName = message.getAvps().getAvp(Avp.USER_NAME);
        // Get application id
        ApplicationId appId = ((IMessage) message).getSingleApplicationId();
        // User name
        if (avpUserName != null) {
            userName = avpUserName.getUTF8String();
        }
        // Processing table
        try {
            redirectTableLock.readLock().lock();

            for (int index = 0; index < redirectTable.size(); index++) {
                RedirectEntry e = redirectTable.get(index);
                switch (e.getUsageType()) {
                    case ALL_SESSION: // Usage type: ALL SESSION
                        matchedEntry = sessionId != null && e.primaryKey != null & sessionId.equals(e.primaryKey);
                        break;
                    case ALL_REALM: // Usage type: ALL REALM
                        matchedEntry = destRealm != null && e.primaryKey != null & destRealm.equals(e.primaryKey);
                        break;
                    case REALM_AND_APPLICATION: // Usage type: REALM AND APPLICATION
                        matchedEntry = destRealm != null & appId != null & e.primaryKey != null & e.secondaryKey != null
                                       & destRealm.equals(e.primaryKey)
                                       & appId.equals(e.secondaryKey);
                        break;
                    case ALL_APPLICATION: // Usage type: ALL APPLICATION
                        matchedEntry = appId != null & e.secondaryKey != null & appId.equals(e.secondaryKey);
                        break;
                    case ALL_HOST: // Usage type: ALL HOST
                        matchedEntry = destHost != null & e.primaryKey != null & destHost.equals(e.primaryKey);
                        break;
                    case ALL_USER: // Usage type: ALL USER
                        matchedEntry = userName != null & e.primaryKey != null & userName.equals(e.primaryKey);
                        break;
                }
                // Update message redirect information
                if (matchedEntry) {
                    String newDestHost = e.getRedirectHost();
                    //String newDestRealm = getRealmForPeer(destHost);
                    //setRouteInfo(message, destRealm, newDestHost);
                    updateRoute(message, newDestHost);
                    logger.debug("Redirect message from host={}; to new-host={}, realm={} ",
                                 new Object[]{destHost, newDestHost, destRealm});
                    return true;
                }
            }
        }
        finally {
            redirectTableLock.readLock().unlock();
        }
        return false;
    }

    protected IPeer getPeerPredProcessing(IMessage message, String destRealm, String destHost)
    {
        return null;
    }

    @Override
    public void start()
    {
        if (isStopped) {
            //redirectScheduler = concurrentFactory.getScheduledExecutorService(RedirectMessageTimer.name());
            //redirectEntryHandler = redirectScheduler.scheduleAtFixedRate(redirectTask, 1, 1, TimeUnit.SECONDS);
            isStopped = false;
        }
    }

    @Override
    public void stop()
    {
        isStopped = true;
        // if (redirectEntryHandler != null) {
        //  redirectEntryHandler.cancel(true);
        //}
        if (redirectTable != null) {
            redirectTable.clear();
        }
        if (requestEntryMap != null) {
            requestEntryMap.clear();
        }
        //PCB removed
        //if (requestSortedEntryTable != null) {
        //  requestSortedEntryTable.clear();
        //}
        //if (redirectScheduler != null) {
        //  concurrentFactory.shutdownNow(redirectScheduler);
        //}
    }

    @Override
    public void destroy()
    {
        try {
            if (!isStopped) {
                stop();
            }
        }
        catch (Exception exc) {
            logger.error("Unable to stop router", exc);
        }

        //redirectEntryHandler = null;
        //redirectScheduler = null;
        redirectTable   = null;
        requestEntryMap = null;
    }

    protected IPeer selectPeer(List availablePeers)
    {
        IPeer p = null;
        for (IPeer c : availablePeers) {
            if (p == null || c.getRating() >= p.getRating()) {
                p = c;
            }
        }
        return p;
    }

    //    protected void redirectProcessing(IMessage message, final String destRealm, final String destHost) throws AvpDataException {
    //        String userName = null;
    //        // get Session id
    //        String sessionId = message.getSessionId();
    //        //
    //        Avp avpUserName = message.getAvps().getAvp(Avp.USER_NAME);
    //        // Get application id
    //        ApplicationId appId = message.getSingleApplicationId();
    //        // User name
    //        if (avpUserName != null)
    //            userName = avpUserName.getUTF8String();
    //        // Processing table
    //        for (RedirectEntry e : redirectTable.values()) {
    //            boolean matchedEntry = false;
    //            switch (e.getUsageType()) {
    //                case ALL_SESSION: // Usage type: ALL SESSION
    //                  matchedEntry = sessionId != null && e.primaryKey != null &
    //                  sessionId.equals(e.primaryKey);
    //                  break;
    //                case ALL_REALM: // Usage type: ALL REALM
    //                  matchedEntry = destRealm != null && e.primaryKey != null &
    //                  destRealm.equals(e.primaryKey);
    //                  break;
    //                case REALM_AND_APPLICATION: // Usage type: REALM AND APPLICATION
    //                  matchedEntry = destRealm != null & appId != null & e.primaryKey != null & e.secondaryKey != null &
    //                  destRealm.equals(e.primaryKey) & appId.equals(e.secondaryKey);
    //                  break;
    //                case ALL_APPLICATION: // Usage type: ALL APPLICATION
    //                  matchedEntry = appId != null & e.secondaryKey != null &
    //                  appId.equals(e.secondaryKey);
    //                  break;
    //                case ALL_HOST: // Usage type: ALL HOST
    //                  matchedEntry = destHost != null & e.primaryKey != null &
    //                  destHost.equals(e.primaryKey);
    //                  break;
    //                case ALL_USER: // Usage type: ALL USER
    //                  matchedEntry = userName != null & e.primaryKey != null &
    //                  userName.equals(e.primaryKey);
    //                  break;
    //            }
    //            // Update message redirect information
    //            if (matchedEntry) {
    //              String newDestHost  = e.getRedirectHost();
    //              // FIXME: Alexandre: Should use newDestHost?
    //              String newDestRealm = getRealmForPeer(destHost);
    //              setRouteInfo(message, destRealm, newDestHost);
    //              logger.debug("Redirect message from host={}; realm={} to new-host={}; new-realm={}",
    //              new Object[] {destHost, destRealm, newDestHost, newDestRealm});
    //              return;
    //            }
    //        }
    //    }
    //
    //    private void setRouteInfo(IMessage message, String destRealm, String destHost) {
    //        message.getAvps().removeAvp(Avp.DESTINATION_REALM);
    //        message.getAvps().removeAvp(Avp.DESTINATION_HOST);
    //        if (destRealm != null)
    //            message.getAvps().addAvp(Avp.DESTINATION_REALM, destRealm, true, false, true);
    //        if (destHost != null)
    //            message.getAvps().addAvp(Avp.DESTINATION_HOST, destHost, true, false,  true);
    //    }
    //does not make sense, there can be multple realms :/
    //    public String  getRealmForPeer(String destHost) {
    //        for (String key : getRealmsName()) {
    //            for (String h : getRealmPeers(key)) {
    //                if (h.trim().equals(destHost.trim()))
    //                    return key;
    //            }
    //        }
    //        return null;
    //    }

    protected class RedirectEntry
    {

        final long createTime = System.currentTimeMillis();

        String primaryKey;
        ApplicationId secondaryKey;
        long liveTime;
        int usageType;
        String[] hosts;
        String destinationRealm;

        public RedirectEntry(String key1, ApplicationId key2, long time, int usage, String[] aHosts, String destinationRealm)
                throws InternalError
        {
            // Check arguments
            if (key1 == null && key2 == null) {
                throw new InternalError("Incorrect redirection key.");
            }
            if (aHosts == null || aHosts.length == 0) {
                throw new InternalError("Incorrect redirection hosts.");
            }
            // Set values
            this.primaryKey       = key1;
            this.secondaryKey     = key2;
            this.liveTime         = time * 1000;
            this.usageType        = usage;
            this.hosts            = aHosts;
            this.destinationRealm = destinationRealm;
        }

        public int getUsageType()
        {
            return usageType;
        }

        public String[] getRedirectHosts()
        {
            return hosts;
        }

        public String getRedirectHost()
        {
            return hosts[hosts.length - 1];
        }

        public long getExpiredTime()
        {
            return createTime + liveTime;
        }

        @Override
        public int hashCode()
        {
            int result = (primaryKey != null ? primaryKey.hashCode() : 0);
            result = 31 * result + (secondaryKey != null ? secondaryKey.hashCode() : 0);
            result = 31 * result + (int) (liveTime ^ (liveTime >>> 32));
            result = 31 * result + usageType;
            result = 31 * result + (hosts != null ? hosts.hashCode() : 0);
            return result;
        }

        @Override
        public boolean equals(Object other)
        {
            if (other == this) {
                return true;
            }

            if (other instanceof RedirectEntry) {
                RedirectEntry that = (RedirectEntry) other;
                return liveTime == that.liveTime && usageType == that.usageType &&
                       Arrays.equals(hosts, that.hosts)
                       && !(primaryKey != null ? !primaryKey.equals(that.primaryKey) : that.primaryKey != null) &&
                       !(secondaryKey != null ? !secondaryKey.equals(that.secondaryKey) : that.secondaryKey != null);
            } else {
                return false;
            }
        }
    }

    protected class AnswerEntry
    {

        final long createTime = System.nanoTime();
        Long hopByHopId;
        String host, realm;
        ArrayList routeRecords;

        public AnswerEntry(Long hopByHopId)
        {
            this.hopByHopId = hopByHopId;
        }

        public AnswerEntry(Long hopByHopId, String host, String realm) throws InternalError
        {
            this.hopByHopId = hopByHopId;
            this.host       = host;
            this.realm      = realm;
        }

        public AnswerEntry(Long hopByHopId, String host, String realm, ArrayList routeRecords) throws InternalError
        {
            this.hopByHopId   = hopByHopId;
            this.host         = host;
            this.realm        = realm;
            this.routeRecords = routeRecords;
        }

        public long getCreateTime()
        {
            return createTime;
        }

        public Long getHopByHopId()
        {
            return hopByHopId;
        }

        public String getHost()
        {
            return host;
        }

        public String getRealm()
        {
            return realm;
        }

        public ArrayList getRouteRecords()
        {
            return routeRecords;
        }

        @Override
        public int hashCode()
        {
            return super.hashCode();
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            AnswerEntry that = (AnswerEntry) o;
            return hopByHopId == that.hopByHopId;
        }

        @Override
        public String toString()
        {
            return "AnswerEntry {" + "createTime=" + createTime + ", hopByHopId=" + hopByHopId + ", host=" + host + ", realm="
                   + realm + "}";
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy