/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2014, 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 <http://www.gnu.org/licenses/> * */ package org.restcomm.media.control.mgcp.tx.cmd; import java.io.IOException; import java.util.Collection; import org.apache.log4j.Logger; import org.restcomm.media.control.mgcp.MgcpEvent; import org.restcomm.media.control.mgcp.controller.MgcpConnection; import org.restcomm.media.control.mgcp.controller.MgcpEndpoint; import org.restcomm.media.control.mgcp.controller.NotifiedEntity; import org.restcomm.media.control.mgcp.controller.naming.UnknownEndpointException; import org.restcomm.media.control.mgcp.message.MgcpRequest; import org.restcomm.media.control.mgcp.message.MgcpResponse; import org.restcomm.media.control.mgcp.message.MgcpResponseCode; import org.restcomm.media.control.mgcp.message.Parameter; import org.restcomm.media.control.mgcp.tx.Action; import org.restcomm.media.scheduler.PriorityQueueScheduler; import org.restcomm.media.scheduler.Scheduler; import org.restcomm.media.scheduler.Task; import org.restcomm.media.scheduler.TaskChain; import org.restcomm.media.spi.ConnectionMode; import org.restcomm.media.spi.utils.Text; /** * The AuditConnection command can be used by the Call Agent to retrieve the * parameters attached to a connection. * * @author Henrique Rosa (henrique.rosa@telestax.com) * @see <a href="http://tools.ietf.org/html/rfc3435#section-2.3.11">RFC3435</a> */ public class AuditConnectionCmd extends Action { private final static Logger logger = Logger.getLogger(AuditConnectionCmd.class); // Response messages private final static Text ENDPOINT_ID_MISSING = new Text("Missing endpoint identifier"); private final static Text ENDPOINT_INEXISTENT = new Text("Endpoint not available"); private final static Text CONNECTION_ID_EXPECTED = new Text("Connection identifier was not specified"); private final static Text CONNECTION_INEXISTENT = new Text("Connection not available"); private final static Text SUCCESS= new Text("Success"); private final static Text CONNECTION_NOT_READY= new Text("Connection not ready"); // Media Server internals private final Scheduler scheduler; private TaskChain handler; // Endpoint identifier private Text localName = new Text(""); private Text domainName = new Text(""); private MgcpEndpoint[] endpoints = new MgcpEndpoint[1]; private MgcpEndpoint endpoint; // MGCP request and parameters private MgcpRequest request; /** * The <b>ConnectionId</b> parameter is the identifier of the audited * connection, within the context of the specified endpoint. */ private Parameter connectionId; /** * The <b>EndpointId</b> parameter specifies the endpoint that handles the * connection. The wildcard conventions SHALL NOT be used. */ private Text[] endpointName = new Text[] { localName, domainName }; /** * The (possibly empty) <b>RequestedInfo</b> describes the information that * is requested for the ConnectionId within the EndpointId specified. The * following connection info can be audited with this command:<br> * * CallId, NotifiedEntity, LocalConnectionOptions, Mode, * RemoteConnectionDescriptor, LocalConnectionDescriptor, * ConnectionParameters */ private Parameter requestedInfo; // Audited info private boolean queryCallId = false; private boolean queryNotifiedEntity = false; private boolean queryLocalConnectionOpts = false; private boolean queryMode = false; private boolean queryRemoteConnectionDes = false; private boolean queryLocalConnectionDes = false; private boolean queryConnectionParams = false; private int callId; private NotifiedEntity notifiedEntity; private MgcpConnection connection; private ConnectionMode connectionMode; private Text localConnectionOpts; private Text localConnectionDes; private Text remoteConnectionDes; private ConnectionParameters connectionParameters; private boolean connectionAvailable; public AuditConnectionCmd(Scheduler scheduler) { this.scheduler = scheduler; this.handler = new TaskChain(2, this.scheduler); this.handler.add(new Audit()); this.handler.add(new Respond()); this.setActionHandler(this.handler); this.setRollbackHandler(new Rollback()); } private class Audit extends Task { @Override public int getQueueNumber() { return PriorityQueueScheduler.MANAGEMENT_QUEUE; } @Override public long perform() { // Get parameters from the MGCP request request = (MgcpRequest) getEvent().getMessage(); connectionId = request.getParameter(Parameter.CONNECTION_ID); requestedInfo = request.getParameter(Parameter.REQUESTED_INFO); // Validate the parameters if(request.getEndpoint() == null || request.getEndpoint().length() == 0) { throw new MgcpCommandException(MgcpResponseCode.PROTOCOL_ERROR, ENDPOINT_ID_MISSING); } else { request.getEndpoint().divide('@', endpointName); // TODO endpoint id SHALL NOT use wildcard conventions } if (connectionId == null) { throw new MgcpCommandException(MgcpResponseCode.PROTOCOL_ERROR, CONNECTION_ID_EXPECTED); } // Search for the MGCP endpoint findMgcpEndpoints(localName, endpoints); endpoint = endpoints[0]; // Search for the connection on the endpoint connection = endpoint.getConnection(connectionId.getValue().hexToInteger()); if (connection == null) { throw new MgcpCommandException(MgcpResponseCode.INCORRECT_CONNECTION_ID, CONNECTION_INEXISTENT); } // Check connection availability connectionAvailable = connection.getConnection().isAvailable(); // Retrieve requested information from the connection if(requestedInfo != null) { Collection<Text> requestedParams = requestedInfo.getValue().split(','); auditRequestedInfo(requestedParams, connection); } return 0; } private int findMgcpEndpoints(final Text localName, final MgcpEndpoint[] endpoints) { try { int n = transaction().find(localName, endpoints); if (n == 0) { throw new MgcpCommandException(MgcpResponseCode.ENDPOINT_NOT_AVAILABLE, ENDPOINT_INEXISTENT); } return n; } catch (UnknownEndpointException e) { throw new MgcpCommandException(MgcpResponseCode.ENDPOINT_UNKNOWN, ENDPOINT_INEXISTENT); } } private void auditRequestedInfo(Collection<Text> requestedParams, MgcpConnection connection) { for (Text param : requestedParams) { if (param.equals(Parameter.CALL_ID)) { queryCallId = true; callId = connection.getCallId(); } else if (param.equals(Parameter.NOTIFIED_ENTITY)) { queryNotifiedEntity = true; notifiedEntity = endpoint.getRequest().getCallAgent(); } else if (param.equals(Parameter.LOCAL_CONNECTION_OPTIONS)) { queryLocalConnectionOpts = true; // TODO hrosa - get local connection options localConnectionOpts = new Text(""); } else if (param.equals(Parameter.MODE)) { queryMode = true; connectionMode = connection.getConnection().getMode(); } else if (param.equals(Parameter.REMOTE_CONNECTION_DESCRIPTION)) { queryRemoteConnectionDes = true; String remoteSdp = connection.getConnection().getRemoteDescriptor(); remoteConnectionDes = remoteSdp == null ? new Text("v=0") : new Text(remoteSdp); } else if (param.equals(Parameter.LOCAL_CONNECTION_DESCRIPTION)) { queryLocalConnectionDes = true; String localSdp = connection.getConnection().getLocalDescriptor(); localConnectionDes = localSdp == null ? new Text("v=0") : new Text(localSdp); } else if (param.equals(Parameter.CONNECTION_PARAMETERS)) { queryConnectionParams = true; auditConnectionParameters(connection); } } } private void auditConnectionParameters(MgcpConnection connection) { connectionParameters = new ConnectionParameters(); connectionParameters.packetsSent = connection.getPacketsTransmitted(); connectionParameters.packetsReceived = connection.getPacketsReceived(); connectionParameters.jitter = (int) connection.getConnection().getJitter(); // TODO hrosa - get transmitted octets from MGCP connection // TODO hrosa - get received octets from MGCP connection // TODO hrosa - get latency from MGCP connection // TODO hrosa - get lost packets from MGCP connection } } private class Respond extends Task { @Override public int getQueueNumber() { return PriorityQueueScheduler.MANAGEMENT_QUEUE; } @Override public long perform() { MgcpEvent evt = transaction().getProvider().createEvent(MgcpEvent.RESPONSE, getEvent().getAddress()); MgcpResponse response = (MgcpResponse) evt.getMessage(); if(connectionAvailable) { response.setResponseCode(MgcpResponseCode.TRANSACTION_WAS_EXECUTED); response.setResponseString(SUCCESS); } else { // Return a code that indicates connection exists but is unavailable response.setResponseCode(MgcpResponseCode.INSUFFICIENT_RESOURCES); response.setResponseString(CONNECTION_NOT_READY); } response.setTxID(transaction().getId()); /* * The AuditConnection response will in turn include information * about each of the items auditing info was requested for. * * If no info was requested and the EndpointId is valid, the gateway * simply checks that the connection exists, and if so returns a * positive acknowledgement. */ if(requestedInfo != null) { if(queryCallId) { response.setParameter(Parameter.CALL_ID, new Text(callId)); } if(queryNotifiedEntity) { Text entity = new Text(""); if(notifiedEntity != null) { notifiedEntity.getValue().copy(entity); entity.trim(); } response.setParameter(Parameter.NOTIFIED_ENTITY, entity); } if(queryLocalConnectionOpts) { // TODO Add local connection options to response response.setParameter(Parameter.LOCAL_CONNECTION_OPTIONS, localConnectionOpts); } if(queryMode) { response.setParameter(Parameter.MODE, new Text(connectionMode.description())); } if(queryConnectionParams) { // see http://tools.ietf.org/html/rfc3435#appendix-F.9 response.setParameter(Parameter.CONNECTION_PARAMETERS, connectionParameters.toText()); } if(queryLocalConnectionDes) { // TODO hrosa - Reusing the existing "SDP" parameter. Replace with LOCAL_CONNECTION_DESCRIPTOR response.setParameter(Parameter.SDP, localConnectionDes); } if(queryRemoteConnectionDes) { // TODO hrosa - Need to implement this. MgcpResponse only supports ONE sdp description. // response.setParameter(Parameter.SDP, remoteConnectionDes); } } try { transaction().getProvider().send(evt); } catch (IOException e) { logger.error(e); } finally { evt.recycle(); } return 0; } } private class Rollback extends Task { @Override public int getQueueNumber() { return PriorityQueueScheduler.MANAGEMENT_QUEUE; } @Override public long perform() { int code = ((MgcpCommandException)transaction().getLastError()).getCode(); Text message = ((MgcpCommandException)transaction().getLastError()).getErrorMessage(); MgcpEvent evt = transaction().getProvider().createEvent(MgcpEvent.RESPONSE, getEvent().getAddress()); MgcpResponse response = (MgcpResponse) evt.getMessage(); response.setResponseCode(code); response.setResponseString(message); response.setTxID(transaction().getId()); try { transaction().getProvider().send(evt); } catch (IOException e) { logger.error(e); } finally { evt.recycle(); } return 0; } } private class ConnectionParameters { private final String PACKETS_SENT = "PS"; private final String PACKETS_RECEIVED = "PR"; private final String PACKETS_LOST = "PL"; private final String OCTETS_SENT = "OS"; private final String OCTETS_RECEIVED = "OR"; private final String JITTER = "JI"; private final String LATENCY = "LA"; int packetsSent = -1; int packetsReceived = -1; int packetsLost = -1; int octetsSent = -1; int octetsReceived = -1; int jitter = -1; int latency = -1; public ConnectionParameters() { super(); } public Text toText() { // Blindly append all possible parameters StringBuilder builder = new StringBuilder(); appendParameter(PACKETS_SENT, packetsSent, builder); appendParameter(OCTETS_SENT, octetsSent, builder); appendParameter(PACKETS_RECEIVED, packetsReceived, builder); appendParameter(OCTETS_RECEIVED, octetsReceived, builder); appendParameter(PACKETS_LOST, packetsLost, builder); appendParameter(JITTER, jitter, builder); appendParameter(LATENCY, latency, builder); // Verify correctness of the resulting string int lastComma = builder.lastIndexOf(","); if(lastComma == builder.length() - 1) { builder.deleteCharAt(lastComma); } return new Text(builder.toString().trim()); } private void appendParameter(String parameter, int value, StringBuilder builder) { if(value >= 0) { builder.append(" ").append(parameter).append("=").append(value); } } } }