/**
* Copyright 2010 Voxeo Corporation 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.voxeo.moho.sip;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.regex.PatternSyntaxException;
import javax.media.mscontrol.MediaErr;
import javax.media.mscontrol.networkconnection.SdpPortManagerEvent;
import javax.servlet.sip.Address;
import javax.servlet.sip.B2buaHelper;
import javax.servlet.sip.Proxy;
import javax.servlet.sip.Rel100Exception;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TooManyHopsException;
import javax.servlet.sip.UAMode;
import javax.servlet.sip.URI;
import org.apache.log4j.Logger;
import com.voxeo.moho.ApplicationContext;
import com.voxeo.moho.Constants;
import com.voxeo.moho.Endpoint;
import com.voxeo.moho.SignalException;
import com.voxeo.moho.common.util.Utils;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.util.SDPUtils;
import com.voxeo.moho.util.SessionUtils;
public class SIPHelper {
private static final Logger LOG = Logger.getLogger(SIPHelper.class);
private static final String LINKED_MESSAGE = "linked.message";
public static SipServletRequest createSipInitnalRequest(final SipFactory factory, final String method,
final Address from, final Address to, final Map<String, String> headers,
SipApplicationSession applicationSession, SipServletRequest origRequest, ApplicationContext appContext) {
SipServletRequest req = null;
if (origRequest != null) {
LOG.debug("Continue routing from orig req:" + origRequest);
Map<String, List<String>> headerMap = new HashMap<String, List<String>>();
List<String> fromList = new ArrayList<String>();
fromList.add(from.toString());
headerMap.put("From", fromList);
List<String> toList = new ArrayList<String>();
toList.add(to.toString());
headerMap.put("To", toList);
if (!Utils.isCopyHeadersForContinueRouting(appContext)) {
headerMap.put(Constants.PrismB2BUADoNotCopyHeader, new ArrayList<String>());
try {
req = origRequest.getB2buaHelper().createRequest(origRequest, true, headerMap);
}
catch (final TooManyHopsException e) {
throw new RuntimeException(e);
}
if (Utils.getHeadersToCopyForContinueRouting(appContext) != null) {
String[] copyHeaders = Utils.getHeadersToCopyForContinueRouting(appContext).split(",");
for (String copyHeader : copyHeaders) {
SIPHelper.copyHeader(copyHeader.trim(), origRequest, req);
}
}
}
else {
try{
req = origRequest.getB2buaHelper().createRequest(origRequest, true, headerMap);
}
catch (final TooManyHopsException e) {
throw new RuntimeException(e);
}
}
try {
req.setContent(null, null);
}
catch (UnsupportedEncodingException e) {
LOG.error("", e);
}
req.removeHeader("Content-Type");
Address reqFrom = req.getFrom();
if (from.getDisplayName() != null) {
reqFrom.setDisplayName(from.getDisplayName());
}
else {
reqFrom.setDisplayName(null);
}
reqFrom.setURI(from.getURI());
for (final Iterator<String> names = from.getParameterNames(); names.hasNext();) {
String name = names.next();
if (name.equalsIgnoreCase("tag")) {
continue;
}
reqFrom.setParameter(name, from.getParameter(name));
}
Address reqTo = req.getTo();
if (to.getDisplayName() != null) {
reqTo.setDisplayName(to.getDisplayName());
}
else {
reqTo.setDisplayName(null);
}
reqTo.setURI(to.getURI());
for (final Iterator<String> names = to.getParameterNames(); names.hasNext();) {
String name = names.next();
if (name.equalsIgnoreCase("tag")) {
continue;
}
reqTo.setParameter(name, to.getParameter(name));
}
}
else {
SipApplicationSession createdAppSession = null;
try {
if(applicationSession == null) {
createdAppSession = factory.createApplicationSession();
}
req = factory.createRequest(applicationSession != null ? applicationSession : createdAppSession,
method, from, to);
}
catch(Exception ex) {
LOG.error("Exception when creating INVITE request.", ex);
if(createdAppSession != null) {
try{
createdAppSession.invalidate();
}
catch(Exception e) {
LOG.error("Exception when invalidating application session:"+ createdAppSession);
}
}
throw new SignalException(ex);
}
}
req.setRequestURI(to.getURI());
URI ruri = req.getRequestURI();
// if (ruri instanceof SipURI) {
// SipURI sruri = (SipURI) ruri;
// if (sruri.getUserParam() == null) {
// sruri.setUserParam("phone");
// req.setRequestURI(sruri);
// }
// }
Map<String, String> clones = null;
if (headers != null) {
clones = new HashMap<String, String>();
clones.putAll(headers);
for (final Map.Entry<String, String> e : clones.entrySet()) {
if (e.getKey().equalsIgnoreCase("Route")) {
try {
String[] values = e.getValue().split("\\|\\|\\|");
int length = values.length;
for (int i = length - 1; i >= 0; i--) {
LOG.debug("route[" + i + "]: " + values[i]);
try {
req.pushRoute(factory.createAddress(values[i]));
}
catch (ServletParseException ex) {
LOG.error("Invalid Route Header: " + values[i]);
}
}
}
catch (PatternSyntaxException ex) {
LOG.error(ex);
}
}
}
clones.remove("Route");
clones.remove("route");
}
SIPHelper.addHeaders(req, clones);
return req;
}
public static void addHeaders(final SipServletMessage message, final Map<String, String> headers) {
if (headers != null) {
for (final Map.Entry<String, String> e : headers.entrySet()) {
message.addHeader(e.getKey(), e.getValue());
}
}
}
public static byte[] getRawContentWOException(final SipServletMessage msg) {
try {
return msg.getRawContent();
}
catch (final IOException e) {
LOG.warn("", e);
return null;
}
}
public static void copyContent(final SipServletMessage source, final SipServletMessage target) {
try {
final byte[] content = source.getRawContent();
if (content != null) {
EventSource eventSource = SessionUtils.getEventSource(target);
target.setContent(SDPUtils.formulateSDP((SIPCallImpl) eventSource, content), source.getContentType());
}
}
catch (final Throwable t) {
throw new IllegalArgumentException(t);
}
}
public static boolean isProvisionalResponse(final SipServletResponse res) {
return res.getStatus() < 200;
}
public static boolean isSuccessResponse(final SipServletResponse res) {
return res.getStatus() >= 200 && res.getStatus() <= 299;
}
public static boolean isErrorResponse(final SipServletResponse res) {
return res.getStatus() >= 300;
}
public static boolean isBusy(final SipServletResponse res) {
return res.getStatus() == SipServletResponse.SC_BUSY_HERE
|| res.getStatus() == SipServletResponse.SC_BUSY_EVERYWHERE;
}
public static boolean isDecline(final SipServletResponse res) {
return res.getStatus() == SipServletResponse.SC_DECLINE;
}
public static boolean isNotAcceptableHere(final SipServletResponse res) {
return res.getStatus() == SipServletResponse.SC_NOT_ACCEPTABLE_HERE;
}
public static boolean isTimeout(final SipServletResponse res) {
return res.getStatus() == SipServletResponse.SC_REQUEST_TIMEOUT;
}
public static boolean isRedirect(final SipServletResponse res) {
return res.getStatus() >= 300 && res.getStatus() <= 399;
}
public static boolean isInvite(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("INVITE");
}
public static boolean isInitial(final SipServletRequest req) {
return req.isInitial();
}
public static boolean isReinvite(final SipServletMessage msg) {
if (msg instanceof SipServletRequest) {
return msg.getMethod().equalsIgnoreCase("INVITE") && !((SipServletRequest) msg).isInitial();
}
else {
return msg.getMethod().equalsIgnoreCase("INVITE") && !((SipServletResponse) msg).getRequest().isInitial();
}
}
public static boolean isAck(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("ACK");
}
public static boolean isCancel(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("CANCEL");
}
public static boolean isBye(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("BYE");
}
public static boolean isPrack(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("PRACK");
}
public static boolean isRegister(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("REGISTER");
}
public static boolean isUpdate(final SipServletMessage msg) {
return msg.getMethod().equalsIgnoreCase("UPDATE");
}
public static void forwardRequestByB2buaHelper(final SipServletRequest req, final Map<String, String> headers)
throws IOException {
final B2buaHelper b2b = req.getB2buaHelper();
if (req.getMethod().equalsIgnoreCase("ACK")) {
final SipSession ss = b2b.getLinkedSession(req.getSession());
final java.util.List<SipServletMessage> msgs = b2b.getPendingMessages(ss, UAMode.UAC);
for (final SipServletMessage msg : msgs) {
if (msg instanceof SipServletResponse) {
final SipServletResponse res = (SipServletResponse) msg;
// send Ack for SUCCESS response
if (res.getStatus() == SipServletResponse.SC_OK) {
final SipServletRequest ack = res.createAck();
SIPHelper.copyContent(req, ack);
ack.send();
}
}
}
}
else if (req.getMethod().equalsIgnoreCase("CANCEL")) {
final SipSession ss = b2b.getLinkedSession(req.getSession());
final SipServletRequest cancel = b2b.createCancel(ss);
cancel.send();
}
else {
final SipSession leg1 = req.getSession();
final SipSession leg2 = req.getB2buaHelper().getLinkedSession(leg1);
final SipServletRequest req2 = req.getB2buaHelper().createRequest(leg2, req, null);
SIPHelper.copyContent(req, req2);
req2.send();
}
}
public static void forwardResponseByB2buaHelper(final SipServletResponse res, final Map<String, String> headers)
throws IOException {
final B2buaHelper b2b = res.getRequest().getB2buaHelper();
final SipSession peer = b2b.getLinkedSession(res.getSession());
SipServletResponse cpyresp = null;
if (res.getRequest().isInitial()) {
cpyresp = b2b.createResponseToOriginalRequest(peer, res.getStatus(), res.getReasonPhrase());
}
else {
final SipServletRequest otherReq = b2b.getLinkedSipServletRequest(res.getRequest());
cpyresp = otherReq.createResponse(res.getStatus(), res.getReasonPhrase());
}
SIPHelper.copyContent(res, cpyresp);
cpyresp.send();
}
public static void handleErrorSdpPortManagerEvent(final SdpPortManagerEvent event, final SipServletRequest req) {
final MediaErr error = event.getError();
try {
if (SdpPortManagerEvent.SDP_NOT_ACCEPTABLE.equals(error) || MediaErr.NOT_SUPPORTED.equals(error)) {
// Send 488 error response to INVITE
req.createResponse(SipServletResponse.SC_NOT_ACCEPTABLE_HERE).send();
}
else if (SdpPortManagerEvent.RESOURCE_UNAVAILABLE.equals(error)) {
// Send 486 error response to INVITE
req.createResponse(SipServletResponse.SC_BUSY_HERE).send();
}
else {
// Some unknown error. Send 500 error response to INVITE
req.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send();
}
}
catch (final IOException e) {
LOG.warn("IOException when sending error response ", e);
}
}
public static void linkSIPMessage(final SipServletMessage msg1, final SipServletMessage msg2) {
msg1.setAttribute(LINKED_MESSAGE, msg2);
msg2.setAttribute(LINKED_MESSAGE, msg1);
}
public static SipServletMessage getLinkSIPMessage(final SipServletMessage msg) {
return (SipServletMessage) msg.getAttribute(LINKED_MESSAGE);
}
public static void unlinkSIPMessage(final SipServletMessage msg1) {
final SipServletMessage msg2 = getLinkSIPMessage(msg1);
if (msg2 != null) {
msg1.removeAttribute(LINKED_MESSAGE);
msg2.removeAttribute(LINKED_MESSAGE);
}
}
public static void sendSubsequentRequest(final SipSession session, final SipServletRequest origReq,
final Map<String, String> headers) throws IOException {
final SipServletRequest newReq = session.createRequest(origReq.getMethod());
SIPHelper.addHeaders(newReq, headers);
SIPHelper.copyContent(origReq, newReq);
SIPHelper.linkSIPMessage(origReq, newReq);
newReq.send();
}
public static void relayPrack(final SipServletResponse waitingPrackResp, final SipServletRequest origReq,
final Map<String, String> headers) throws IOException, Rel100Exception {
final SipServletRequest newReq = waitingPrackResp.createPrack();
SIPHelper.addHeaders(newReq, headers);
SIPHelper.copyContent(origReq, newReq);
SIPHelper.linkSIPMessage(origReq, newReq);
newReq.send();
}
public static void relayResponse(SipServletResponse origResp) throws IOException {
SipServletRequest peerReq = (SipServletRequest) SIPHelper.getLinkSIPMessage(origResp.getRequest());
if (peerReq != null) {
SipServletResponse peerResp = peerReq.createResponse(origResp.getStatus());
SIPHelper.copyContent(origResp, peerResp);
peerResp.send();
}
else {
LOG.warn("Didn't find linked request for response:" + origResp);
}
}
public static void sendReinvite(final SipSession session, final SipServletMessage origReq,
final Map<String, String> headers) throws IOException {
final SipServletRequest reinvite = session.createRequest("INVITE");
SIPHelper.addHeaders(reinvite, headers);
if (origReq != null) {
SIPHelper.copyContent(origReq, reinvite);
}
reinvite.send();
}
public static boolean isContainSDP(final SipServletRequest req) {
try {
if (req.getContent() == null) {
return false;
}
else {
return true;
}
}
catch (final Throwable t) {
return false;
}
}
public static void proxyTo(final SipFactory factory, SipServletRequest initialRequest,
final Map<String, String> headers, boolean recordRoute, boolean parallel, Endpoint... destinations)
throws SignalException {
if (destinations == null || destinations.length == 0) {
throw new IllegalArgumentException("Illegal endpoints");
}
try {
addHeaders(initialRequest, headers);
Proxy proxy = initialRequest.getProxy();
proxy.setParallel(parallel);
proxy.setRecordRoute(recordRoute);
if (isRegister(initialRequest)) {
proxy.setAddToPath(true);
}
proxy.setSupervised(false);
List<URI> uris = new LinkedList<URI>();
for (Endpoint endpoint : destinations) {
if (endpoint.getURI() == null) {
throw new IllegalArgumentException("Illegal endpoints:" + endpoint);
}
Address address = factory.createAddress(endpoint.getURI().toString());
uris.add(address.getURI());
}
proxy.proxyTo(uris);
}
catch (TooManyHopsException e) {
LOG.error("", e);
throw new SignalException(e);
}
catch (ServletParseException e) {
LOG.error("", e);
throw new SignalException(e);
}
}
public static URI getCleanUri(URI uri) {
if (uri.isSipURI()) {
SipURI sipURI = (SipURI) uri.clone();
Iterator<String> iterator = sipURI.getParameterNames();
while (iterator != null && iterator.hasNext()) {
iterator.next();
iterator.remove();
}
return sipURI;
}
else {
return uri;
}
}
public static boolean support100rel(SipServletRequest req) {
boolean result = false;
ListIterator<String> values = req.getHeaders("Supported");
while (values.hasNext()) {
String value = values.next();
if (value.equalsIgnoreCase("100rel")) {
result = true;
break;
}
}
return result;
}
public static boolean needPrack(SipServletResponse resp) {
if (resp.getStatus() > 199 || resp.getStatus() < 101) {
return false;
}
boolean result = false;
ListIterator<String> values = resp.getHeaders("Require");
while (values.hasNext()) {
String value = values.next();
if (value.equalsIgnoreCase("100rel")) {
result = true;
break;
}
}
return result;
}
public static void trySendPrack(SipServletResponse resp) throws IOException {
if (!needPrack(resp)) {
return;
}
try {
resp.createPrack().send();
}
catch (Rel100Exception ex) {
LOG.warn(ex.getMessage());
}
catch (IllegalStateException ex) {
LOG.warn(ex.getMessage());
}
}
public static void remove100relSupport(SipServletRequest req) {
ListIterator<String> values = req.getHeaders("Supported");
while (values.hasNext()) {
String value = values.next();
if (value.equalsIgnoreCase("100rel")) {
values.remove();
}
}
values = req.getHeaders("Require");
while (values.hasNext()) {
String value = values.next();
if (value.equalsIgnoreCase("100rel")) {
values.remove();
}
}
}
public static void copyPandXHeaders(SipServletMessage origReq, SipServletMessage req) {
Iterator<String> headerNames = origReq.getHeaderNames();
while (headerNames.hasNext()) {
String headerName = headerNames.next();
if (headerName.startsWith("P-") || headerName.startsWith("p-") || headerName.startsWith("X-")
|| headerName.startsWith("x-")) {
req.removeHeader(headerName);
ListIterator<String> values = origReq.getHeaders(headerName);
while (values.hasNext()) {
String headerValue = values.next();
req.addHeader(headerName, headerValue);
}
}
}
}
public static void copyHeader(String header, SipServletMessage origMessage, SipServletMessage targetMessage) {
if (header == null || header.trim().isEmpty()) {
return;
}
ListIterator<String> values = origMessage.getHeaders(header);
if (values != null) {
while (values.hasNext()) {
targetMessage.addHeader(header, values.next());
}
}
}
}