/** * Copyright 2012 Voxbone SA/NV * * 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 org.ifsoft.sip; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.LinkedList; import java.util.ListIterator; import java.util.Timer; import java.util.TimerTask; import javax.sip.ClientTransaction; import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.SipException; import javax.sip.TransactionUnavailableException; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.address.URI; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.EventHeader; import javax.sip.header.ExpiresHeader; import javax.sip.header.FromHeader; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.RecordRouteHeader; import javax.sip.header.SubscriptionStateHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Request; import javax.sip.message.Response; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.slf4j.*; import org.slf4j.Logger; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; public class SipSubscription extends TimerTask { private static final Logger Log = LoggerFactory.getLogger(SipSubscription.class); boolean active = false; // false=pending true=active String localTag; String remoteTag; Address localParty; Address remoteParty; String callId; long cseq; String contact; LinkedList<Address> rl; long expires; private static Timer timer = new Timer("Subscription Thread"); /* * Creates a subscription from information in an xml file */ SipSubscription(String file) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document dom = db.parse(file); Element docEle = dom.getDocumentElement(); NodeList nl = docEle.getChildNodes(); if (nl != null && nl.getLength() > 0) { for (int i = 0 ; i < nl.getLength();i++) { Node n = nl.item(i); if (n instanceof Element) { Element el = (Element) n; Log.debug("Got a node of " + el.getNodeName() + ":" + el.getTextContent()); if (el.getNodeName().equals("active")) { this.active = Boolean.parseBoolean(el.getTextContent()); } else if (el.getNodeName().equals("remote")) { this.remoteTag = el.getAttribute("tag"); this.remoteParty = SipService.addressFactory.createAddress(el.getTextContent()); } else if (el.getNodeName().equals("local")) { this.localTag = el.getAttribute("tag"); this.localParty = SipService.addressFactory.createAddress(el.getTextContent()); } else if (el.getNodeName().equals("callid")) { this.callId = el.getTextContent(); } else if (el.getNodeName().equals("cseq")) { this.cseq = Long.parseLong(el.getTextContent()); } else if (el.getNodeName().equals("contact")) { this.contact = el.getTextContent(); } else if (el.getNodeName().equals("routeset")) { this.rl = new LinkedList<Address>(); NodeList routeList = el.getElementsByTagName("route"); for (int j = 0; j < routeList.getLength(); j++) { Element route = (Element) routeList.item(j); Address addr = SipService.addressFactory.createAddress(route.getTextContent()); this.rl.add(addr); } } else if (el.getNodeName().equals("expires")) { this.expires = Long.parseLong(el.getTextContent()); } } } } } catch (ParserConfigurationException e) { Log.error("Error loading subscriptions from file", e); } catch (SAXException e) { Log.error("Error loading subscriptions from file", e); } catch (IOException e) { Log.error("Error loading subscriptions from file", e); } catch (DOMException e) { Log.error("Error loading subscriptions from file", e); } catch (ParseException e) { Log.error("Error loading subscriptions from file", e); } } /* * Creates an outbound subscription */ SipSubscription(String from, String to) throws ParseException { this.localParty = SipService.addressFactory.createAddress("sip:" + from + "@" + SipService.sipListener.host); this.remoteParty = SipService.addressFactory.createAddress("sip:" + to + "@" + SipService.sipListener.host); this.localTag = Integer.toString((int) (Math.random() * 100000)); this.remoteTag = null; callId = SipService.sipProvider.getNewCallId().getCallId(); this.cseq = 1; @SuppressWarnings("unused") ListeningPoint listeningPoint = SipService.sipProvider.getListeningPoint(); this.contact = "sip:" + to + "@" + SipService.getRemoteIP(); rl = new LinkedList<Address>(); expires = System.currentTimeMillis() + (3600 * 1000); } public void schedule() { timer.schedule(this, 1800 * 1000, 1800 * 1000); } public void schedule(long nextCall) { timer.schedule(this, nextCall, 1800 * 1000); } /* * Creates a Subscription Object based on a received subscribe */ SipSubscription(Request req) { CallIdHeader ch = (CallIdHeader) req.getHeader("Call-ID"); ToHeader th = (ToHeader) req.getHeader("To"); FromHeader fh = (FromHeader) req.getHeader("From"); rl = new LinkedList<Address>(); this.callId = ch.getCallId(); // incoming request from=remote ContactHeader cont = (ContactHeader) req.getHeader(ContactHeader.NAME); if (cont != null) { this.contact = cont.getAddress().getURI().toString(); } // This is a server dialog. The top most record route // header is the one that is closest to us. We extract the // route list in the same order as the addresses in the // incoming request. if (rl.isEmpty()) { ListIterator<?> rrl = req.getHeaders(RecordRouteHeader.NAME); while (rrl.hasNext()) { RecordRouteHeader rrh = (RecordRouteHeader) rrl.next(); rl.add(rrh.getAddress()); } } remoteTag = fh.getTag(); remoteParty = fh.getAddress(); localTag = th.getTag(); localParty = th.getAddress(); if (localTag == null) { localTag = Integer.toString((int) (Math.random() * 100000)); } ExpiresHeader eh = (ExpiresHeader) req.getHeader(ExpiresHeader.NAME); this.expires = System.currentTimeMillis() + (eh.getExpires() * 1000); cseq = 1; } public void makeActive() { this.active = true; } public boolean isActive() { return this.active; } /* public void sendNotify(boolean expire, Presence pres) { FromHeader fromHeader = null; ToHeader toHeader = null; URI requestURI = null; try { requestURI = SipService.addressFactory.createURI(this.contact); toHeader = SipService.headerFactory.createToHeader(this.remoteParty, this.remoteTag); fromHeader = SipService.headerFactory.createFromHeader(this.localParty, this.localTag); ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ViaHeader viaHeader = null; ListeningPoint lp = SipService.sipProvider.getListeningPoint(); viaHeader = SipService.headerFactory.createViaHeader(lp.getIPAddress(), lp.getPort(), lp.getTransport(), null); viaHeaders.add(viaHeader); CallIdHeader callIdHeader = SipService.headerFactory.createCallIdHeader(this.callId); CSeqHeader cSeqHeader = SipService.headerFactory.createCSeqHeader(this.cseq++, Request.NOTIFY); MaxForwardsHeader maxForwards = SipService.headerFactory.createMaxForwardsHeader(70); Request request = null; if (pres != null) { ContentTypeHeader ch = SipService.headerFactory.createContentTypeHeader("application", "pidf+xml"); request = SipService.messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards, ch, pres.buildPidf(((SipURI) this.remoteParty.getURI()).getHost())); } else { request = SipService.messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); } EventHeader eph = SipService.headerFactory.createEventHeader("presence"); request.addHeader(eph); if (expire) { SubscriptionStateHeader ssh = SipService.headerFactory.createSubscriptionStateHeader("terminated;reason=timeout"); request.addHeader(ssh); } else { long duration = (this.expires - System.currentTimeMillis()) / 1000; SubscriptionStateHeader ssh = SipService.headerFactory.createSubscriptionStateHeader("active;expires=" + duration); request.addHeader(ssh); try { SipSubscriptionManager.saveWatcher(this); } catch (IOException e) { Log.error("Error persisting watcher", e); } catch (SAXException e) { Log.error("Error persisting watcher", e); } } String fromUser = ((SipURI) this.localParty.getURI()).getUser(); Address localAddress = SipService.addressFactory.createAddress("sip:" + fromUser + "@" + lp.getIPAddress() + ":" + lp.getPort()); ContactHeader ch = SipService.headerFactory.createContactHeader(localAddress); request.addHeader(ch); if (this.rl != null && !this.rl.isEmpty()) { ListIterator<Address> li = this.rl.listIterator(); while (li.hasNext()) { request.addHeader(SipService.headerFactory.createRouteHeader(li.next())); } } ClientTransaction t = SipService.sipProvider.getNewClientTransaction(request); t.sendRequest(); } catch (ParseException e) { Log.error("Error on SipSubscription:sendNotify", e); } catch (InvalidArgumentException e) { Log.error("Error on SipSubscription:sendNotify", e); } catch (TransactionUnavailableException e) { Log.error("Error on SipSubscription:sendNotify", e); } catch (SipException e) { Log.error("Error on SipSubscription:sendNotify", e); } } */ public void sendSubscribe(boolean expire) { FromHeader fromHeader = null; ToHeader toHeader = null; URI requestURI = null; try { requestURI = SipService.addressFactory.createURI(this.contact); toHeader = SipService.headerFactory.createToHeader(this.remoteParty, this.remoteTag); fromHeader = SipService.headerFactory.createFromHeader(this.localParty, this.localTag); ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ViaHeader viaHeader = null; ListeningPoint lp = SipService.sipProvider.getListeningPoint(); viaHeader = SipService.headerFactory.createViaHeader(lp.getIPAddress(), lp.getPort(), lp.getTransport(), null); viaHeaders.add(viaHeader); CallIdHeader callIdHeader = SipService.headerFactory.createCallIdHeader(this.callId); CSeqHeader cSeqHeader = SipService.headerFactory.createCSeqHeader(this.cseq++, Request.SUBSCRIBE); MaxForwardsHeader maxForwards = SipService.headerFactory.createMaxForwardsHeader(70); Request request = SipService.messageFactory.createRequest(requestURI, "SUBSCRIBE", callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); EventHeader eph = SipService.headerFactory.createEventHeader("presence"); request.addHeader(eph); if (expire) { ExpiresHeader eh = SipService.headerFactory.createExpiresHeader(0); request.addHeader(eh); this.cancel(); } else { ExpiresHeader eh = SipService.headerFactory.createExpiresHeader(3600); request.addHeader(eh); this.expires = System.currentTimeMillis() + (3600 * 1000); try { SipSubscriptionManager.saveSubscription(this); } catch (IOException e) { Log.error("Error persisting subscriber", e); } catch (SAXException e) { Log.error("Error persisting subscriber", e); } } String fromUser = ((SipURI) this.localParty.getURI()).getUser(); Address localAddress = SipService.addressFactory.createAddress("sip:" + fromUser + "@" + lp.getIPAddress() + ":" + lp.getPort()); ContactHeader ch = SipService.headerFactory.createContactHeader(localAddress); request.addHeader(ch); if (this.rl != null && !this.rl.isEmpty()) { ListIterator<Address> li = this.rl.listIterator(); while (li.hasNext()) { request.addHeader(SipService.headerFactory.createRouteHeader(li.next())); } } ClientTransaction t = SipService.sipProvider.getNewClientTransaction(request); t.sendRequest(); } catch (ParseException e) { Log.error("Error on SipSubscription:sendSubscribe", e); } catch (InvalidArgumentException e) { Log.error("Error on SipSubscription:sendSubscribe", e); } catch (TransactionUnavailableException e) { Log.error("Error on SipSubscription:sendSubscribe", e); } catch (SipException e) { Log.error("Error on SipSubscription:sendSubscribe", e); } } // update subscribe on receipt of 200 Ok public void updateSubscription(Response resp) { ContactHeader cont = (ContactHeader) resp.getHeader(ContactHeader.NAME); ToHeader th = (ToHeader) resp.getHeader("To"); FromHeader fh = (FromHeader) resp.getHeader("From"); if (cont != null) { this.contact = cont.getAddress().getURI().toString(); } // This is a client dialog so we extract the record // route from the response and reverse its order to // create a route list. // ignore Record-Route in 1xx messages if (this.rl.isEmpty() && resp.getStatusCode() >= 200) { ListIterator<?> rrl = resp.getHeaders(RecordRouteHeader.NAME); while (rrl.hasNext()) { RecordRouteHeader rrh = (RecordRouteHeader) rrl.next(); this.rl.addFirst(rrh.getAddress()); } } this.remoteTag = th.getTag(); this.localTag = fh.getTag(); try { SipSubscriptionManager.saveSubscription(this); } catch (IOException e) { Log.error("Error persisting subscriber", e); } catch (SAXException e) { Log.error("Error persisting subscriber", e); } } // bug Fix for sip communicator, if the 200ok lacks a contact get it from the first notify public void updateSubscription(Request req) { ContactHeader cont = (ContactHeader) req.getHeader(ContactHeader.NAME); if (cont != null && this.contact.endsWith(SipService.getRemoteIP())) { this.contact = cont.getAddress().getURI().toString(); } } public void buildSubscriptionXML(ContentHandler hd) throws SAXException { AttributesImpl atts = new AttributesImpl(); String activeStr = Boolean.toString(this.active); hd.startElement("", "", "active", atts); hd.characters(activeStr.toCharArray(), 0, activeStr.length()); hd.endElement("", "", "active"); atts.addAttribute("", "", "tag", "", remoteTag); hd.startElement("", "", "remote", atts); String party = remoteParty.toString(); hd.characters(party.toCharArray(), 0, party.length()); hd.endElement("", "", "remote"); atts.clear(); atts.addAttribute("", "", "tag", "", localTag); hd.startElement("", "", "local", atts); party = localParty.toString(); hd.characters(party.toCharArray(), 0, party.length()); hd.endElement("", "", "local"); atts.clear(); hd.startElement("", "", "callid", atts); hd.characters(callId.toCharArray(), 0, callId.length()); hd.endElement("", "", "callid"); String cseqStr = Long.toString(this.cseq); hd.startElement("", "", "cseq", atts); hd.characters(cseqStr.toCharArray(), 0, cseqStr.length()); hd.endElement("", "", "cseq"); hd.startElement("", "", "contact", atts); hd.characters(contact.toCharArray(), 0, contact.length()); hd.endElement("", "", "contact"); hd.startElement("", "", "routeset", atts); ListIterator<Address> li = rl.listIterator(); while(li.hasNext()) { String addr = li.next().toString(); hd.startElement("", "", "route", atts); hd.characters(addr.toCharArray(), 0, addr.length()); hd.endElement("", "", "route"); } hd.endElement("", "", "routeset"); String expireStr = Long.toString(this.expires); hd.startElement("", "", "expires", atts); hd.characters(expireStr.toCharArray(), 0, expireStr.length()); hd.endElement("", "", "expires"); } // This is a task to refresh subscriptions public void run() { // Time's up, refresh the subscription sendSubscribe(false); } }