/** * @param response * @throws IOException */ public static void forwardResponse(final SipServletResponse response) throws IOException { if (logger.isInfoEnabled()) { logger.info(String.format("B2BUA: Got response: \n %s", response)); } CallDetailRecordsDao records = daoManager.getCallDetailRecordsDao(); // container handles CANCEL related responses no need to forward them if (response.getStatus() == 487 || (response.getStatus() == 200 && response.getMethod().equalsIgnoreCase("CANCEL"))) { if (logger.isDebugEnabled()) { logger.debug("response to CANCEL not forwarding"); } // Update CallDetailRecord SipServletRequest request = (SipServletRequest) getLinkedSession(response).getAttribute(B2BUA_LAST_REQUEST); CallDetailRecord callRecord = records.getCallDetailRecord((Sid) request.getSession().getAttribute(CDR_SID)); if (callRecord != null) { logger.info("CDR found! Updating"); callRecord = callRecord.setStatus(CallStateChanged.State.CANCELED.name()); final DateTime now = DateTime.now(); callRecord = callRecord.setEndTime(now); final int seconds = (int) (DateTime.now().getMillis() - callRecord.getStartTime().getMillis()) / 1000; callRecord = callRecord.setDuration(seconds); records.updateCallDetailRecord(callRecord); } return; } // forward the response response.getSession().setAttribute(B2BUA_LAST_RESPONSE, response); SipServletRequest request = (SipServletRequest) getLinkedSession(response).getAttribute(B2BUA_LAST_REQUEST); SipServletResponse resp = request.createResponse(response.getStatus()); CallDetailRecord callRecord = records.getCallDetailRecord((Sid) request.getSession().getAttribute(CDR_SID)); if (response.getContent() != null) { final byte[] sdp = response.getRawContent(); String offer = null; if (response.getContentType().equalsIgnoreCase("application/sdp")) { // Issue 306: https://telestax.atlassian.net/browse/RESTCOMM-306 Registration registration = daoManager.getRegistrationsDao().getRegistration(callRecord.getTo()); final String externalIp = registration.getLocation().split(":")[1].split("@")[1]; try { logger.debug("Got original address from Registration :" + externalIp); offer = patch(sdp, externalIp); } catch (SdpException e) { logger.error("Unexpected exception while patching sdp ", e); } if (offer != null) { resp.setContent(offer, response.getContentType()); } else { resp.setContent(sdp, response.getContentType()); } } } resp.send(); // CallDetailRecord callRecord = records.getCallDetailRecord((Sid) // request.getSession().getAttribute(CDR_SID)); if (callRecord != null) { logger.info("CDR found! Updating"); if (!request.getMethod().equalsIgnoreCase("BYE")) { if (response.getStatus() == 100 || response.getStatus() == 180) { callRecord = callRecord.setStatus(CallStateChanged.State.RINGING.name()); } else if (response.getStatus() == 200 || response.getStatus() == 202) { callRecord = callRecord.setStatus(CallStateChanged.State.IN_PROGRESS.name()); callRecord = callRecord.setAnsweredBy(((SipURI) response.getTo().getURI()).getUser()); final DateTime now = DateTime.now(); callRecord = callRecord.setStartTime(now); } else if (response.getStatus() == 486 || response.getStatus() == 600) { callRecord = callRecord.setStatus(CallStateChanged.State.BUSY.name()); } else if (response.getStatus() > 400) { callRecord = callRecord.setStatus(CallStateChanged.State.FAILED.name()); } } else { callRecord = callRecord.setStatus(CallStateChanged.State.COMPLETED.name()); final DateTime now = DateTime.now(); callRecord = callRecord.setEndTime(now); final int seconds = (int) ((DateTime.now().getMillis() - callRecord.getStartTime().getMillis()) / 1000); callRecord = callRecord.setDuration(seconds); } records.updateCallDetailRecord(callRecord); } }
/** * @param request * @param client * @param toClient * @throws IOException */ public static boolean redirectToB2BUA( final SipServletRequest request, final Client client, Client toClient, DaoManager storage, SipFactory sipFactory) throws IOException { request.getSession().setAttribute("lastRequest", request); if (logger.isInfoEnabled()) { logger.info("B2BUA (p2p proxy): Got request:\n" + request.getMethod()); logger.info( String.format( "B2BUA: Proxying a session between %s and %s", client.getUri(), toClient.getUri())); } if (daoManager == null) { daoManager = storage; } String user = ((SipURI) request.getTo().getURI()).getUser(); final RegistrationsDao registrations = daoManager.getRegistrationsDao(); final Registration registration = registrations.getRegistration(user); if (registration != null) { final String location = registration.getLocation(); SipURI to; SipURI from; try { to = (SipURI) sipFactory.createURI(location); from = (SipURI) sipFactory.createURI( (registrations.getRegistration(client.getLogin())).getLocation()); final SipSession incomingSession = request.getSession(); // create and send the outgoing invite and do the session linking incomingSession.setAttribute(B2BUA_LAST_REQUEST, request); SipServletRequest outRequest = sipFactory.createRequest( request.getApplicationSession(), request.getMethod(), request.getFrom().getURI(), request.getTo().getURI()); outRequest.setRequestURI(to); if (request.getContent() != null) { final byte[] sdp = request.getRawContent(); String offer = null; if (request.getContentType().equalsIgnoreCase("application/sdp")) { // Issue 308: https://telestax.atlassian.net/browse/RESTCOMM-308 String externalIp = request.getInitialRemoteAddr(); // Issue 306: https://telestax.atlassian.net/browse/RESTCOMM-306 final String initialIpBeforeLB = request.getHeader("X-Sip-Balancer-InitialRemoteAddr"); try { if (initialIpBeforeLB != null && !initialIpBeforeLB.isEmpty()) { offer = patch(sdp, initialIpBeforeLB); } else { offer = patch(sdp, externalIp); } } catch (SdpException e) { logger.error("Unexpected exception while patching sdp ", e); } } if (offer != null) { outRequest.setContent(offer, request.getContentType()); } else { outRequest.setContent(sdp, request.getContentType()); } } final SipSession outgoingSession = outRequest.getSession(); if (request.isInitial()) { incomingSession.setAttribute(B2BUA_LINKED_SESSION, outgoingSession); outgoingSession.setAttribute(B2BUA_LINKED_SESSION, incomingSession); } outgoingSession.setAttribute(B2BUA_LAST_REQUEST, outRequest); request.createResponse(100).send(); // Issue #307: https://telestax.atlassian.net/browse/RESTCOMM-307 request.getSession().setAttribute("toInetUri", to); outRequest.send(); outRequest.getSession().setAttribute("fromInetUri", from); final CallDetailRecord.Builder builder = CallDetailRecord.builder(); builder.setSid(Sid.generate(Sid.Type.CALL)); builder.setDateCreated(DateTime.now()); builder.setAccountSid(client.getAccountSid()); builder.setTo(toClient.getFriendlyName()); builder.setCallerName(client.getFriendlyName()); builder.setFrom(client.getFriendlyName()); // builder.setForwardedFrom(callInfo.forwardedFrom()); // builder.setPhoneNumberSid(phoneId); builder.setStatus(CallStateChanged.State.QUEUED.name()); builder.setDirection("Client-To-Client"); builder.setApiVersion(client.getApiVersion()); builder.setPrice(new BigDecimal("0.00")); // TODO implement currency property to be read from Configuration builder.setPriceUnit(Currency.getInstance("USD")); final StringBuilder buffer = new StringBuilder(); buffer.append("/").append(client.getApiVersion()).append("/Accounts/"); buffer.append(client.getAccountSid().toString()).append("/Calls/"); buffer.append(client.getSid().toString()); final URI uri = URI.create(buffer.toString()); builder.setUri(uri); CallDetailRecordsDao records = daoManager.getCallDetailRecordsDao(); CallDetailRecord callRecord = builder.build(); records.addCallDetailRecord(callRecord); incomingSession.setAttribute(CDR_SID, callRecord.getSid()); outgoingSession.setAttribute(CDR_SID, callRecord.getSid()); return true; // successfully proxied the SIP request between two registered clients } catch (ServletParseException badUriEx) { if (logger.isInfoEnabled()) { logger.info( String.format("B2BUA: Error parsing Client Contact URI: %s", location), badUriEx); } } } return false; }
// Issue 139: https://bitbucket.org/telestax/telscale-restcomm/issue/139 @SuppressWarnings("unchecked") protected Response updateCall( final String sid, final String callSid, final MultivaluedMap<String, String> data, final MediaType responseType) { final Sid accountSid = new Sid(sid); try { secure(daos.getAccountsDao().getAccount(accountSid), "RestComm:Modify:Calls"); } catch (final AuthorizationException exception) { return status(UNAUTHORIZED).build(); } final Timeout expires = new Timeout(Duration.create(60, TimeUnit.SECONDS)); final CallDetailRecordsDao dao = daos.getCallDetailRecordsDao(); final CallDetailRecord cdr = dao.getCallDetailRecord(new Sid(callSid)); final String url = data.getFirst("Url"); String method = data.getFirst("Method"); final String status = data.getFirst("Status"); final String fallBackUrl = data.getFirst("FallbackUrl"); String fallBackMethod = data.getFirst("FallbackMethod"); final String statusCallBack = data.getFirst("StatusCallback"); String statusCallbackMethod = data.getFirst("StatusCallbackMethod"); // Restcomm- Move connected call leg (if exists) to the new URL Boolean moveConnectedCallLeg = Boolean.valueOf(data.getFirst("MoveConnectedCallLeg")); String callPath = null; final ActorRef call; final CallInfo callInfo; try { callPath = cdr.getCallPath(); Future<Object> future = (Future<Object>) ask(callManager, new GetCall(callPath), expires); call = (ActorRef) Await.result(future, Duration.create(10, TimeUnit.SECONDS)); future = (Future<Object>) ask(call, new GetCallInfo(), expires); CallResponse<CallInfo> response = (CallResponse<CallInfo>) Await.result(future, Duration.create(10, TimeUnit.SECONDS)); callInfo = response.get(); } catch (Exception exception) { return status(INTERNAL_SERVER_ERROR).entity(exception.getMessage()).build(); } if (method == null) method = "POST"; if (url != null && status != null) { // Throw exception. We can either redirect a running call using Url or change the state of a // Call with Status final String errorMessage = "You can either redirect a running call using \"Url\" or change the state of a Call with \"Status\""; return status(javax.ws.rs.core.Response.Status.CONFLICT).entity(errorMessage).build(); } // Modify state of a call if (status != null) { if (status.equalsIgnoreCase("canceled")) { if (callInfo.state().name().equalsIgnoreCase("queued") || callInfo.state().name().equalsIgnoreCase("ringing")) { if (call != null) { call.tell(new Hangup(), null); } } else { // Do Nothing. We can only cancel Queued or Ringing calls } } if (status.equalsIgnoreCase("completed")) { // Specifying "completed" will attempt to hang up a call even if it's already in progress. if (call != null) { call.tell(new Hangup(), null); } } } if (url != null && call != null) { try { final String version = getApiVersion(data); final URI uri = (new URL(url)).toURI(); URI fallbackUri = (fallBackUrl != null) ? (new URL(fallBackUrl)).toURI() : null; fallBackMethod = (fallBackMethod == null) ? "POST" : fallBackMethod; URI callbackUri = (statusCallBack != null) ? (new URL(statusCallBack)).toURI() : null; statusCallbackMethod = (statusCallbackMethod == null) ? "POST" : statusCallbackMethod; final UpdateCallScript update = new UpdateCallScript( call, accountSid, version, uri, method, fallbackUri, fallBackMethod, callbackUri, statusCallbackMethod, moveConnectedCallLeg); callManager.tell(update, null); } catch (Exception exception) { return status(INTERNAL_SERVER_ERROR).entity(exception.getMessage()).build(); } } if (APPLICATION_JSON_TYPE == responseType) { return ok(gson.toJson(cdr), APPLICATION_JSON).build(); } else if (APPLICATION_XML_TYPE == responseType) { return ok(xstream.toXML(new RestCommResponse(cdr)), APPLICATION_XML).build(); } else { return null; } }