/** This is a listener method. */ public void processResponse(ResponseEvent responseEvent) { try { Response response = responseEvent.getResponse(); SipProvider sipProvider = (SipProvider) responseEvent.getSource(); ClientTransaction clientTransaction = responseEvent.getClientTransaction(); ProxyDebug.println( "\n***************************************************************" + "\n***************************************************************" + "\nResponse " + response.getStatusCode() + " " + response.getReasonPhrase() + " received:\n" + response.toString()); ProxyDebug.println("Processing Response in progress"); if (ProxyDebug.debug) ProxyUtilities.printTransaction(clientTransaction); // Henrik - added handling of responses addressed to server // If we use a presenceserver, and if statuscode was OK... CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME); if (cseqHeader.getMethod().equals("SUBSCRIBE")) { presenceServer.processSubscribeResponse((Response) response.clone(), clientTransaction); } else if (cseqHeader.getMethod().equals("NOTIFY")) { // presenceServer.processNotifyResponse((Response)response.clone(), // clientTransaction); } responseForwarding.forwardResponse(sipProvider, response, clientTransaction); } catch (Exception ex) { if (ProxyDebug.debug) { ProxyDebug.println("Proxy, processResponse(), internal error, " + "exception raised:"); ProxyDebug.logException(ex); } } }
/** * handles the INVITED state. * * @param response the response * @param clientTransaction the client transaction * @throws SipException SIP stack related error */ private void handleCallParticipantInvited(Response response, ClientTransaction clientTransaction) throws ParseException, SipException, InvalidArgumentException { FromHeader fromHeader = (FromHeader) response.getHeader(FromHeader.NAME); String displayName = fromHeader.getAddress().getDisplayName(); int statusCode = response.getStatusCode(); Logger.println( "handleCallParticipantInvited " + cp + " status " + statusCode + " " + response.getReasonPhrase()); Logger.println("handleCallParticipantInvited , displayname " + displayName); CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME); if (sipCallId.equals(callIdHeader.getCallId()) && displayName.equals(cp.getDisplayName()) && (statusCode == Response.OK || statusCode == Response.SESSION_PROGRESS) && ((CSeqHeader) response.getHeader(CSeqHeader.NAME)).getMethod().equals(Request.INVITE)) { if (statusCode == Response.SESSION_PROGRESS) { /* * For some calls, we never get an OK. Instead we just get * SESSION_PROGRESS. In order to handle these calls, we treat * SESSION_PROGRESS as OK. If an OK arrives later, we'll * send an ACK. This flag allows us to enable or * disable this workaround for each call. * * The problem with always treating SESSION_PROGRESS as OK * is that in a conference everybody will hear the ringing sound * which the remote call sends until the call is actually answered. * This can be avoided if joinConfirmation is specified. * The other problem is that if we treat SESSION_PROGRESS * as though the call has been answered, then we'll start * playing the treatment before a person really answers to * hear the treatment. */ if (cp.getHandleSessionProgress() == false) { Logger.writeFile("Call " + cp + " Ignoring SESSION_PROGRESS"); return; } Logger.writeFile("Call " + cp + " Treating SESSION_PROGRESS as OK"); } if (response.getRawContent() == null) { Logger.error("Call " + cp + " no SDP in OK Response!"); cancelRequest("SIP error! no SDP in OK Response!"); return; } this.clientTransaction = clientTransaction; if (statusCode == Response.OK) { gotOk = true; Logger.writeFile("Call " + cp + " Got OK, call answered\n" + response); } ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); /* * We got an OK response. * * send an ACK back to the CallParticipant */ if (statusCode == Response.OK) { sipUtil.sendAck(clientTransaction); ackSent = true; } if (callAnswered) { Logger.writeFile("Call " + cp + " done processing OK"); return; } /* * Remember the IP and port of where to send data to * the CallParticipant. */ sdpBody = new String(response.getRawContent()); SdpInfo sdpInfo; try { sdpInfo = sipUtil.getSdpInfo(sdpBody, false); } catch (ParseException e) { Logger.error("Call " + cp + " Invalid SDP in OK Response! " + e.getMessage()); cancelRequest("SIP error! Invalid SDP in OK Response!"); return; } MediaInfo mediaInfo = sdpInfo.getMediaInfo(); InetSocketAddress isa = new InetSocketAddress(sdpInfo.getRemoteHost(), sdpInfo.getRemotePort()); InetSocketAddress rtcpAddress = sdpInfo.getRtcpAddress(); setEndpointAddress( isa, mediaInfo.getPayload(), sdpInfo.getTransmitMediaInfo().getPayload(), sdpInfo.getTelephoneEventPayload(), rtcpAddress); /* * The CallParticipant has answered. * If join confirmation is required, we remain in the * INVITED state. We set the callAnswered flag so that * if the join confirmation times out we know to * send a BYE rather than a CANCEL. */ callAnswered = true; if (cp.getJoinConfirmationTimeout() == 0) { setState(CallState.ANSWERED); } /* * Start treatment if any and wait for it to finish. * When the treatment finishes, notification will * be delivered to our parent which will indicate * we're ready for the conference. * * If there's no treatment to be played, we're ready now * unless we're waiting for join confirmation.. */ initializeCallAnsweredTreatment(); if (callAnsweredTreatment != null) { startCallAnsweredTreatment(); } else { if (cp.getJoinConfirmationTimeout() == 0) { setState(CallState.ESTABLISHED); } } } else { Logger.writeFile("Call " + cp + " Ignoring response: " + response.getReasonPhrase()); if (Logger.logLevel >= Logger.LOG_SIP) { Logger.println("Call " + cp + " Response: " + response); } } }
public synchronized void processResponse(ResponseEvent responseReceivedEvent) { try { Response response = (Response) responseReceivedEvent.getResponse(); ClientTransaction clientTransaction = responseReceivedEvent.getClientTransaction(); int statusCode = response.getStatusCode(); FromHeader fromHeader = (FromHeader) response.getHeader(FromHeader.NAME); String displayName = fromHeader.getAddress().getDisplayName(); if (Logger.logLevel >= Logger.LOG_SIP) { Logger.println( "Response: statusCode " + statusCode + " state " + getCallState() + " fromHeader " + displayName + " call participant " + cp.getName()); } if (reasonCallTerminated != null) { /* * Ignore OK and Request Terminated. * XXX what's the symbol for 487? */ if (statusCode != Response.OK && statusCode != 487) { if (Logger.logLevel >= Logger.LOG_SIP) { Logger.println("Call " + cp + ": request cancelled, ignoring response"); } } CallIdHeader callIdHeader = (CallIdHeader) response.getHeader("Call-Id"); String sipCallId = callIdHeader.getCallId(); sipServerCallback.removeSipListener(sipCallId); return; } /* * Some type of global failure that prevents the * CallParticipant from being contacted, report failure. */ if (forceGatewayError) { statusCode = 500; forceGatewayError = false; } if (statusCode >= 500 && getState() == CallState.INVITED) { Logger.error( "Call " + cp + " gateway error: " + statusCode + " " + response.getReasonPhrase()); cancelRequest("gateway error: " + statusCode + " " + response.getReasonPhrase()); return; } else if (statusCode == Response.PROXY_AUTHENTICATION_REQUIRED || statusCode == Response.UNAUTHORIZED) { if (cp.getProxyCredentials() != null) { try { SipServer.handleChallenge(response, clientTransaction, cp.getProxyCredentials()) .sendRequest(); } catch (Exception e) { Logger.println("Proxy authentification failed " + e); } } return; } else if (statusCode >= 400) { // if we get a busy or an unknown error, play busy. Logger.println("Call " + cp + " got status code :" + statusCode); cp.setCallEndTreatment(null); cp.setConferenceJoinTreatment(null); cp.setConferenceLeaveTreatment(null); /* * play busy treatment, but deallocate any resources * held up by ringBack first, if any. */ // stopCallAnsweredTreatment(); if (statusCode == Response.BUSY_HERE) { try { if (busyTreatment != null) { addTreatment(busyTreatment); // busyTreatment.waitForTreatment(); } else { Logger.println("Unable to play busy treatment!!!"); } } catch (Exception e) { Logger.error("can't start busy treatment!" + sdpBody); } CallEvent callEvent = new CallEvent(CallEvent.BUSY_HERE); callEvent.setInfo(response.getReasonPhrase()); sendCallEventNotification(callEvent); } // sipUtil.sendBye(clientTransaction); cancelRequest(response.getReasonPhrase()); return; } /* state machine */ switch (getState()) { /* * CallParticipant picked up, send treatment if any, * and wait for it to finish. */ case CallState.INVITED: if (rejectCall) { Logger.error( "Call " + cp + " gateway error: " + statusCode + " " + response.getReasonPhrase()); cancelRequest("gateway error: " + statusCode + " " + response.getReasonPhrase()); return; } handleCallParticipantInvited(response, clientTransaction); break; /* * Call established, the ACK needs to be resent. * According to Ranga, this is done by the NIST SIP Stack. */ case CallState.ESTABLISHED: if (statusCode == Response.OK) { gotOk = true; Logger.writeFile("Call " + cp + " Got OK, ESTABLISHED"); if (ackSent == false) { sipUtil.sendAck(clientTransaction); ackSent = true; } } break; case CallState.ENDED: break; // ignore the response default: Logger.error("Process Response bad state " + getState() + "\n" + response); } } catch (SipException e) { Logger.exception("Call " + cp + " SIP Stack error ", e); cancelRequest("processResponse: SIP Stack error " + e.getMessage()); } catch (Exception e) { Logger.exception("processResponse: " + cp, e); cancelRequest("processResponse: SIP Stack error " + e.getMessage()); } }
/** * @param originalRequestEvent * @param response * @param serviceUnit */ @Override public void sendResponse( RequestEvent originalRequestEvent, Response response, ServiceUnit serviceUnit, String toTag) { /** Here we need to do some specific handling specific to the type of responses being sent. */ /** * If it is a 180 RINGING response, then we need to set the To tag to the same to tag generated * when creating the associated B2BSession and B2BDialog. */ if (isDialogCreating(response.getStatusCode())) { logger.fine("Sending a ringing response..."); /** Copy the Local Tag from the B2BDialog to the SipResponse */ logger.info("Setting the To Dialog of the response to be sent to : " + toTag); SIPResponse sipResponse = (SIPResponse) response; sipResponse.setToTag(toTag); } logger.info( "Sending Response for the request associated with ServiceUnit: " + serviceUnit.getName()); /** Conveying the response sending to the UserAgent */ logger.info("Sending Response: " + response.getReasonPhrase()); Dialog sipDialog = userAgent.sendResponse(originalRequestEvent, response, serviceUnit); if (sipDialog == null) { logger.info( "The sent response " + response.getReasonPhrase() + " did not resulted in any dialog..."); } else { logger.info( "The sent response " + response.getReasonPhrase() + " has created a new Dialog with ID: " + sipDialog.getDialogId()); } /** * Set the underlying SIP Dialog of the B2BDialog for Dialog creating responses as well as the * local seq number and the current state of the dialog. */ if (isDialogCreating(response.getStatusCode())) { B2BSession b2BSessions[] = RuntimeComponentContext.getInstance().getB2BSessions(serviceUnit.getName()); for (B2BSession b2BSession : b2BSessions) { B2BDialogImpl b2BDialogImpl = null; if (sipDialog != null) { b2BDialogImpl = (B2BDialogImpl) b2BSession.getB2BDialog(sipDialog.getDialogId(), true, null); } if (b2BDialogImpl != null) { logger.info( "Setting the underlying SIP Dialog property of the B2BDialog: " + b2BDialogImpl.getDialogId()); b2BDialogImpl.setUnderlyingSipDialog(sipDialog); b2BDialogImpl.setLocalSeqNumber(sipDialog.getLocalSeqNumber()); b2BDialogImpl.setState(sipDialog.getState().toString()); logger.info( "Setting the Dialog status with ID: " + b2BDialogImpl.getDialogId() + " to " + sipDialog.getState().toString()); break; } else { logger.info("Unable to locate a B2BDialog with ID " + sipDialog.getDialogId()); } } } }
/** * @param response * @param serviceUnit */ @Override public void receiveResponse(Response response, ServiceUnit serviceUnit) { logger.info("Response Message received: " + response.getReasonPhrase()); logger.info("Forwarding response to the ClientAgentServer for processing..."); userAgent.receiveResponse(response, serviceUnit); }
/** * Implements {@link MethodProcessor#processResponse(ResponseEvent)}. Handles only responses to * SUBSCRIBE requests because they are the only requests concerning event package subscribers (and * the only requests sent by them, for that matter) and if the processing of a given response * requires event package-specific handling, delivers the response to the matching * <tt>Subscription</tt> instance. Examples of such event package-specific handling include * letting the respective <tt>Subscription</tt> handle the success or failure in the establishment * of a subscription. * * @param responseEvent a <tt>ResponseEvent</tt> specifying the SIP <tt>Response</tt> to be * processed * @return <tt>true</tt> if the SIP <tt>Response</tt> specified by <tt>responseEvent</tt> was * processed; otherwise, <tt>false</tt> */ @Override public boolean processResponse(ResponseEvent responseEvent) { Response response = responseEvent.getResponse(); CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME); if (cseqHeader == null) { logger.error("An incoming response did not contain a CSeq header"); return false; } if (!Request.SUBSCRIBE.equals(cseqHeader.getMethod())) return false; ClientTransaction clientTransaction = responseEvent.getClientTransaction(); Request request = clientTransaction.getRequest(); /* * Don't handle responses to requests not coming from this event * package. */ if (request != null) { EventHeader eventHeader = (EventHeader) request.getHeader(EventHeader.NAME); if ((eventHeader == null) || !eventPackage.equalsIgnoreCase(eventHeader.getEventType())) return false; } // Find the subscription. CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME); String callId = callIdHeader.getCallId(); Subscription subscription = getSubscription(callId); // if it's the response to an unsubscribe message, we just ignore it // whatever the response is however if we need to handle a // challenge, we do it ExpiresHeader expHeader = response.getExpires(); int statusCode = response.getStatusCode(); SipProvider sourceProvider = (SipProvider) responseEvent.getSource(); if (((expHeader != null) && (expHeader.getExpires() == 0)) || (subscription == null)) // this handle the unsubscription // case where we removed the contact // from subscribedContacts { boolean processed = false; if ((statusCode == Response.UNAUTHORIZED) || (statusCode == Response.PROXY_AUTHENTICATION_REQUIRED)) { try { processAuthenticationChallenge(clientTransaction, response, sourceProvider); processed = true; } catch (OperationFailedException e) { logger.error("can't handle the challenge", e); } } else if ((statusCode != Response.OK) && (statusCode != Response.ACCEPTED)) processed = true; // any other cases (200/202) will imply a NOTIFY, so we will // handle the end of a subscription there return processed; } if ((statusCode >= Response.OK) && (statusCode < Response.MULTIPLE_CHOICES)) { // OK (200/202) if ((statusCode == Response.OK) || (statusCode == Response.ACCEPTED)) { if (expHeader == null) { // not conform to rfc3265 logger.error("no Expires header in this response"); return false; } SubscriptionRefreshTask refreshTask = new SubscriptionRefreshTask(subscription); subscription.setTimerTask(refreshTask); int refreshDelay = expHeader.getExpires(); // try to keep a margin if the refresh delay allows it if (refreshDelay >= (2 * refreshMargin)) refreshDelay -= refreshMargin; timer.schedule(refreshTask, refreshDelay * 1000); // do it to remember the dialog in case of a polling // subscription (which means no call to finalizeSubscription) subscription.setDialog(clientTransaction.getDialog()); subscription.processSuccessResponse(responseEvent, statusCode); } } else if ((statusCode >= Response.MULTIPLE_CHOICES) && (statusCode < Response.BAD_REQUEST)) { if (logger.isInfoEnabled()) logger.info( "Response to subscribe to " + subscription.getAddress() + ": " + response.getReasonPhrase()); } else if (statusCode >= Response.BAD_REQUEST) { // if the response is a 423 response, just re-send the request // with a valid expires value if (statusCode == Response.INTERVAL_TOO_BRIEF) { MinExpiresHeader min = (MinExpiresHeader) response.getHeader(MinExpiresHeader.NAME); if (min == null) { logger.error("no minimal expires value in this 423 " + "response"); return false; } ExpiresHeader exp = request.getExpires(); try { exp.setExpires(min.getExpires()); } catch (InvalidArgumentException e) { logger.error("can't set the new expires value", e); return false; } ClientTransaction transac = null; try { transac = protocolProvider.getDefaultJainSipProvider().getNewClientTransaction(request); } catch (TransactionUnavailableException e) { logger.error("can't create the client transaction", e); return false; } try { transac.sendRequest(); } catch (SipException e) { logger.error("can't send the new request", e); return false; } return true; // UNAUTHORIZED (401/407) } else if ((statusCode == Response.UNAUTHORIZED) || (statusCode == Response.PROXY_AUTHENTICATION_REQUIRED)) { try { processAuthenticationChallenge(clientTransaction, response, sourceProvider); } catch (OperationFailedException e) { logger.error("can't handle the challenge", e); removeSubscription(callId, subscription); subscription.processFailureResponse(responseEvent, statusCode); } // 408 480 486 600 603 : non definitive reject // others: definitive reject (or not implemented) } else { if (logger.isDebugEnabled()) logger.debug("error received from the network:\n" + response); removeSubscription(callId, subscription); subscription.processFailureResponse(responseEvent, statusCode); } } return true; }