package jpbx.core; import java.util.*; import java.io.*; import javaforce.*; import javaforce.voip.*; import javaforce.jbus.*; /** Handles main SIP messages and dispatches them to low-level plugins. */ public class Service implements SIPServerInterface, PBXAPI { public static String dbPath; public static String jdbc = "jdbc:derby:jpbxDB"; public static String getVersion() { return "0.26"; } private SIPServer ss; private String cfg[][]; private PluginsClassLoader pcl; private Vector<DialChain> dialChainList; private Hashtable<String, Extension> extList; private JBusClient jbusClient; private boolean relayAudio, relayVideo; private Timer timer; public static int sip_port; public boolean init() { //setup derby.system.home if (JF.isWindows()) { dbPath = System.getenv("ProgramData"); //Win Vista/7/8 if (dbPath == null) { dbPath = System.getenv("AllUsersProfile"); //Win 2000/XP/2003 if (dbPath == null) { JFLog.log("Failed to find Program Data folder"); System.exit(1); } } dbPath += "/jpbxlite/"; } else { dbPath = "/var/lib"; } System.setProperty("derby.system.home", dbPath); if (JF.isUnix()) { jbusClient = new JBusClient("org.jflinux.service.jpbxlite", new JBusMethods()); jbusClient.start(); } SQL sql = new SQL(); if (!sql.connect(jdbc)) { JFLog.log("Failed to connect to database"); return false; } //read config cfg = sql.select("SELECT id,value FROM config"); //update config sql.execute("UPDATE config SET value='" + JF.getCurrentPath() + "' WHERE id='binpath'"); //start service ss = new SIPServer(); int rtpmin, rtpmax; try { sip_port = Integer.valueOf(getCfg("port")); rtpmin = Integer.valueOf(getCfg("rtpmin")); rtpmax = Integer.valueOf(getCfg("rtpmax")); if ((rtpmin < 1024) || (rtpmin > 65535-1000)) rtpmin = 32768; if ((rtpmax < 1024) || (rtpmax > 65535)) rtpmax = 65535; if ((rtpmin > rtpmax) || (rtpmax - rtpmin < 1000)) { rtpmin = 32768; rtpmax = 65535; } } catch (Exception e) { sip_port = 5060; rtpmin = 32768; rtpmax = 65535; } JFLog.log("Setting RTP Range:" + rtpmin + "-" + rtpmax); RTP.setPortRange(rtpmin, rtpmax); pcl = new PluginsClassLoader(); dialChainList = new Vector<DialChain>(); extList = new Hashtable<String, Extension>(); relayAudio = getCfg("relayAudio").equals("true"); relayVideo = getCfg("relayVideo").equals("true"); String moh = getCfg("moh"); if (moh.length() > 0) { MusicOnHold.loadWav(Paths.sounds + Paths.lang + "/" + moh + ".wav"); } WebConfig.http_port = JF.atoi(getCfg("http")); if (WebConfig.http_port == 0) WebConfig.http_port = 80; WebConfig.https_port = JF.atoi(getCfg("https")); if (WebConfig.https_port == 0) WebConfig.https_port = 443; WebConfig.hideAdmin = getCfg("hideAdmin").equals("true"); WebConfig.disableWebRTC = getCfg("disableWebRTC").equals("true"); WebConfig.sip_port = sip_port; new File(Paths.lib + "voicemail").mkdirs(); new File(Paths.logs).mkdirs(); loadPlugins(sql); ss.init(sip_port, this, SIP.Transport.UDP); // ss.enableQOP(true); //test! //create timer to register trunks (every 110 seconds) timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() {public void run() {registerTrunks();}}, 0, 110 * 1000); sql.close(); return true; } public void uninit() { if (pcl != null) { pcl.unloadPlugins(); pcl = null; } if (ss != null) { ss.uninit(); ss = null; } if (timer != null) { timer.cancel(); timer = null; } } public void restart() { uninit(); JF.sleep(1000); init(); } public String getCfg(String id) { if (cfg == null) return ""; for(int a=0;a<cfg.length;a++) { if (cfg[a][0].equalsIgnoreCase(id)) return cfg[a][1]; } return ""; } public boolean loadPlugins(SQL sql) { JFLog.log("loading plugins"); String plugins[][] = sql.select("SELECT jar,cls FROM svcplugins"); if (plugins == null) {JFLog.log("no plugins loaded?"); return false;} for(int a=0;a<plugins.length;a++) { if (pcl.isLoaded(plugins[a][0])) continue; try { pcl.loadPlugin(plugins[a][0]); Class cls = pcl.findClass("jpbx.plugins." + plugins[a][1]); Object inst = cls.newInstance(); /* //this works too Method method = cls.getMethod("init", PBXAPI.class); method.invoke(inst, (PBXAPI)this); */ Plugin plugin = (Plugin)inst; plugin.init(this); } catch (Exception e) { JFLog.log("Failed to init plugin:" + plugins[a][0]); JFLog.log(e); } } JFLog.log("loading plugins complete"); return true; } private long getNow() { return System.currentTimeMillis() / 1000; } //interface SIPServerInterface public CallDetailsServer createCallDetailsServer() { return new CallDetailsPBX(); } public String getPassword(String ext) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return null; String password = sql.select1value("SELECT pass FROM exts WHERE ext=" + sql.quote(ext)); sql.close(); return password; } public void onRegister(String ext, int expires, String remoteip, int remoteport) { if (expires == 0) { extList.remove(ext); } else { extList.put(ext, new Extension(ext, getNow() + expires, remoteip, remoteport)); } } public void onInvite(CallDetailsServer cd, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; cd.pid = onInvite(cd, sql, src, cd.pid); sql.close(); } public int onInvite(CallDetailsServer cd, SQL sql, boolean src, int pid) { DialChain chain; int newpid; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if ((pid == 0) || (pid == chain.getPriority())) { newpid = chain.onInvite((CallDetailsPBX)cd, sql, src); if (newpid != -1) { return newpid; } } } JFLog.log("Dest not found:" + cd.dialed); reply(cd, 404, "NOT ONLINE", null, false, true); return -1; } public void onCancel(CallDetailsServer cd, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onCancel(cd, sql, src, cd.pid); sql.close(); } public void onCancel(CallDetailsServer cd, SQL sql, boolean src, int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) { chain.onCancel((CallDetailsPBX)cd, sql, src); return; } } } public void onBye(CallDetailsServer cd, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onBye(cd, sql, src, cd.pid); sql.close(); } public void onBye(CallDetailsServer cd, SQL sql, boolean src, int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) { chain.onBye((CallDetailsPBX)cd, sql, src); return; } } } public void onSuccess(CallDetailsServer cd, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onSuccess(cd,sql,src,cd.pid); sql.close(); } public void onSuccess(CallDetailsServer cd, SQL sql, boolean src, int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) { chain.onSuccess((CallDetailsPBX)cd, sql, src); return; } } } public void onRinging(CallDetailsServer cd, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onRinging(cd, sql, src, cd.pid); sql.close(); } public void onRinging(CallDetailsServer cd, SQL sql, boolean src, int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) { chain.onRinging((CallDetailsPBX)cd, sql, src); return; } } } public void onError(CallDetailsServer cd, int code, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onError(cd, sql, code, src, cd.pid); sql.close(); } public void onError(CallDetailsServer cd, SQL sql, int code, boolean src, int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) { chain.onError((CallDetailsPBX)cd, sql, code, src); return; } } } public void onTrying(CallDetailsServer cd, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onTrying(cd, sql, src, cd.pid); sql.close(); } public void onTrying(CallDetailsServer cd, SQL sql, boolean src, int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) { chain.onTrying((CallDetailsPBX)cd, sql, src); return; } } } public void onFeature(CallDetailsServer cd, String cmd, String cmddata, boolean src) { SQL sql = new SQL(); if (!sql.connect(jdbc)) return; onFeature(cd, sql, cmd, cmddata, src, cd.pid); sql.close(); } public void onFeature(CallDetailsServer cd, SQL sql, String cmd, String cmddata, boolean src, int pid) { DialChain chain; if (cmd.equalsIgnoreCase("REFER")) { if (src) { reply(cd, 202, "REFER OK", null, false, true); cd.cmd = "NOTIFY"; cd.sdp = "SIP/2.0 200 OK"; issue(cd, "Event: refer\r\n", true, true); cd.cmd = "BYE"; issue(cd, null, false, true); String tmp[] = SIP.split(cmddata); transfer_dst((CallDetailsPBX)cd, tmp[1]); } else { reply(cd, 202, "REFER OK", null, false, false); cd.cmd = "NOTIFY"; cd.sdp = "SIP/2.0 200 OK"; issue(cd, "Event: refer\r\n", true, false); cd.cmd = "BYE"; issue(cd, null, false, false); String tmp[] = SIP.split(cmddata); transfer_src((CallDetailsPBX)cd, tmp[1]); } return; } for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if ((pid == 0) || (pid == chain.getPriority())) { chain.onFeature((CallDetailsPBX)cd, sql, cmd, cmddata, src); return; } } } //interface PBXAPI public void hookDialChain(DialChain chain) { DialChain chainEntry; int priority = chain.getPriority(); int idx = 0; for(;idx < dialChainList.size();) { chainEntry = dialChainList.get(idx); if (chainEntry.getPriority() < priority) {idx++; continue;} dialChainList.add(idx, chain); return; } dialChainList.add(idx, chain); } public void unhookDialChain(DialChain chain) { dialChainList.remove(chain); } public void schedule(Runnable plugin, int minutes) {} public void unschedule(Runnable plugin) {} public SIPClient createClient() {return null;} public boolean isRegistered(String ext) { if (extList.get(ext) != null) return true; return false; } public boolean reply(CallDetailsServer cd, int code, String msg, String header, boolean sdp, boolean src) { if (cd.xfer_dst && src) { JFLog.log("reply:refer(to src):" + code); switch (code) { case 200: //now send reinvite back to src (if PBX doesn't relay data) cd.xfer_dst = false; if (!relayAudio || !relayVideo) { cd.xfer_src = true; cd.cmd = "INVITE"; connect((CallDetailsPBX)cd); ss.buildsdp(cd, cd.pbxsrc); issue(cd, null, true, true); } return true; case 100: case 180: case 183: return true; default: //xfer failed - term call cd.xfer_dst = false; cd.cmd = "BYE"; issue(cd, null, false, true); disconnect((CallDetailsPBX)cd); return true; } } if (cd.xfer_src && !src) { cd.xfer_src = false; //BUG : What is src changes SDP details??? Do "another" reinvite to dst??? return true; } if (src) { cd.pbxsrc.host = cd.src.host; cd.pbxsrc.port = cd.src.port; } else { cd.pbxdst.host = cd.dst.host; cd.pbxdst.port = cd.dst.port; } JFLog.log("reply:" + cd.cmd + ":" + code + " to:" + (src ? cd.src.host : cd.dst.host) + ":" + (src ? cd.src.port : cd.dst.port)); return ss.reply(cd,code,msg,header,sdp,src); } public boolean issue(CallDetailsServer cd, String header, boolean sdp, boolean src) { /* if ((cd.xfer_src) && (src)) { return true; } */ if (cd.cmd.equals("INVITE") && !sdp) { try { throw new Exception("error"); } catch (Exception e) { JFLog.log(e); return false; } } if (src) { cd.src.host = cd.pbxsrc.host; cd.src.port = cd.pbxsrc.port; } else { cd.dst.host = cd.pbxdst.host; cd.dst.port = cd.pbxdst.port; } JFLog.log("issue:" + cd.cmd + " to:" + (src ? cd.src.host : cd.dst.host) + ":" + (src ? cd.src.port : cd.dst.port)); return ss.issue(cd,header,sdp,src); } /** transfers source (call originator) to a new dest */ public boolean transfer_src(CallDetailsPBX cd, String number) { JFLog.log("transfer_src"); cd.dialed = number; cd.src.to[0] = number; cd.src.to[1] = number; cd.xfer_dst = true; cd.cmd = "INVITE"; //reset values cd.invited = false; cd.connected = false; cd.cancelled = false; cd.pid = 0; cd.dst = new CallDetails.SideDetails(); cd.pbxdst = new CallDetails.SideDetails(); JFLog.log("transfer_src:from="+cd.src.from[0]+":"+cd.src.from[1]+":"+cd.src.contact+":"+cd.uri); //do reinvites onInvite(cd, true); return true; } /** transfers dest (call terminator) to a new dest */ public boolean transfer_dst(CallDetailsPBX cd, String number) { JFLog.log("transfer_dst"); //copy dst to src cd.src = cd.dst; cd.pbxsrc = cd.pbxdst; //swap RTP connections if (cd.audioRelay != null) { cd.audioRelay.swap(); } if (cd.videoRelay != null) { cd.videoRelay.swap(); } //now do a normal transfer transfer_src(cd, number); return true; } public Extension getExtension(String ext) { if (ext == null) { JFLog.log("getExtension:null"); return null; } return extList.get(ext); } public String getlocalhost(CallDetailsPBX cd) { return cd.localhost; } public int getlocalport() { return sip_port; } /** Returns new dial string in [0] and trunks in [1-n] */ public String[] getTrunks(String dialed, String ext, SQL sql) { //look for a route that matches dialed and return its trunks String routetable = null; if (ext != null) { routetable = sql.select1value("SELECT value FROM extopts WHERE id='routetable' AND ext=" + sql.quote(ext)); if (routetable == null) { routetable = sql.select1value("SELECT defaultroutetable FROM config"); } } if (routetable == null) routetable = "default"; String routes[][] = sql.select("SELECT patterns,trunks FROM outroutes WHERE routetable=" + sql.quote(routetable) + " ORDER BY priority"); if (routes == null) return null; String patterns[]; String newdialed; for(int a=0;a<routes.length;a++) { patterns = routes[a][0].split(":"); for(int b=0;b<patterns.length;b++) { newdialed = patternMatches(patterns[b], dialed); if (newdialed == null) continue; newdialed += ":"; newdialed += routes[a][1]; return newdialed.split(":"); } } return null; } /** Resolve hostname to IP address. */ public String resolve(String host) { return ss.resolve(host); } public void releaseCallDetails(CallDetails cd) { ss.setCallDetailsServer(cd.callid, null); } /** Returns new dial number if it matches pattern, else returns null. */ public String patternMatches(String pattern, String dialed) { /* pattern: #=match exact digit (0-9) X=0-9 Z=1-9 N=2-9 [123-9]=range (TODO) .=wildcard (last char only) x|y = removes 'x' if 'xy' matches x+y = add 'x' if 'y' matches */ char cap[] = pattern.toCharArray(); char cad[] = dialed.toCharArray(); int plus = pattern.indexOf('+'); int pipe = pattern.indexOf('|'); int pi = 0; int di = 0; if ((plus != -1) && (pipe != -1)) { if (plus > pipe) { //swap around plus/pipe strings pattern = pattern.substring(0, pipe+1) + pattern.substring(pipe+1, plus+1) + pattern.substring(plus+1); plus = pattern.indexOf('+'); pipe = pattern.indexOf('|'); } } if (plus != -1) { pi = plus+1; } int pl = pattern.length(); int dl = dialed.length(); while (pi < pl) { if ((cap[pi] >= '0') && (cap[pi] <= '9')) { if (di >= dl) return null; if (cap[pi] != cad[di]) return null; pi++; di++; continue; } switch (Character.toUpperCase(cap[pi])) { case 'X': if (di >= dl) return null; if ((cad[di] < '0') || (cad[di] > '9')) return null; break; case 'Z': if (di >= dl) return null; if ((cad[di] < '1') || (cad[di] > '9')) return null; break; case 'N': if (di >= dl) return null; if ((cad[di] < '2') || (cad[di] > '9')) return null; break; case '.': pi = pl-1; di = dl-1; break; case '|': pi++; continue; } pi++; di++; } if (di < dl) return null; if ((plus != -1) && (pipe != -1)) { return pattern.substring(0, plus) + dialed.substring(pipe-1-plus); } else if (plus != -1) { return pattern.substring(0, plus) + dialed; } else if (pipe != -1) { return dialed.substring(pipe); } return dialed; } public void log(CallDetailsPBX cd, String msg) { JFLog.log("To:" + cd.src.to[1] + " From:" + cd.src.from[1] + " " + msg); } public void log(CallDetailsPBX cd, Exception e) { JFLog.log("To:" + cd.src.to[1] + " From:" + cd.src.from[1] + " exception:"); JFLog.log(e); } public DialChain getDialChain(int pid) { DialChain chain; for(int a=0;a<dialChainList.size();a++) { chain = dialChainList.get(a); if (pid == chain.getPriority()) return chain; } return null; } public void makePath(String path) { try { File file = new File(path); file.mkdirs(); } catch (Exception e) { } } public String convertString(String instr) { //convert WebString to normal strings (expand %## codes, '+'->' ') String outstr = ""; char ca[] = instr.toCharArray(); int h1, h2; char ch; for(int a=0;a<ca.length;a++) { switch (ca[a]) { case '%': if (a+2>=ca.length) return outstr; if ((ca[a+1] >= '0') && (ca[a+1] <= '9')) h1 = ca[a+1] - '0'; else if ((ca[a+1] >= 'a') && (ca[a+1] <= 'f')) h1 = ca[a+1] - ('a' - 10); else if ((ca[a+1] >= 'A') && (ca[a+1] <= 'F')) h1 = ca[a+1] - ('A' - 10); else h1 = 0; if ((ca[a+2] >= '0') && (ca[a+2] <= '9')) h2 = ca[a+2] - '0'; else if ((ca[a+2] >= 'a') && (ca[a+2] <= 'f')) h2 = ca[a+2] - ('a' - 10); else if ((ca[a+2] >= 'A') && (ca[a+2] <= 'F')) h2 = ca[a+2] - ('A' - 10); else h2 = 0; ch = (char)(h1 * 0x10 + h2); if (ch == '\"') ch = '\''; outstr += ch; a+=2; break; case '+': outstr += " "; break; default: outstr += ca[a]; break; } } return outstr; } private void registerTrunks() { SQL sql = new SQL(); if (!sql.connect(jdbc)) { JFLog.log("registerTrunks() SQL connect failed"); return; } try { String trunks[] = sql.select1col("SELECT register FROM trunks WHERE LENGTH(register) > 0"); if (trunks == null) { sql.close(); return; } for(int trunk=0;trunk<trunks.length;trunk++) { String register = trunks[trunk]; //user : password @ domain[:sip_port] [/ did] int idx = register.indexOf(":"); if (idx == -1) continue; //bad string String user = register.substring(0, idx); register = register.substring(idx+1); idx = register.indexOf("@"); if (idx == -1) continue; //bad string String pass = register.substring(0, idx); register = register.substring(idx+1); idx = register.indexOf("/"); String host = null; String did = "s"; if (idx == -1) { host = register; } else { host = register.substring(0, idx); did = register.substring(idx+1); } int port = 5060; idx = host.indexOf(":"); if (idx != -1) { port = Integer.valueOf(host.substring(idx+1)); host = host.substring(0, idx); } // JFLog.log("Register Trunk:" + trunks[trunk]); ss.register(user, pass, host, port, 120, did, host + "---jpbxlite---"); } } catch (Exception e) { JFLog.log(e); } sql.close(); } public String getTrunkRegister(String ip) { SQL sql = new SQL(); if (!sql.connect(jdbc)) { JFLog.log("registerTrunks() SQL connect failed"); return null; } String reg = null; try { String trunks[] = sql.select1col("SELECT register FROM trunks WHERE LENGTH(register) > 0"); if (trunks == null) { sql.close(); return null; } //user : pass @ host [:sip_port] / did int idx; String host; for(int a=0;a<trunks.length;a++) { idx = trunks[a].indexOf("@"); if (idx == -1) continue; host = trunks[a].substring(idx+1); idx = host.indexOf(":"); if (idx == -1) { idx = host.indexOf("/"); } if (idx != -1) { host = host.substring(0, idx); } String trunkip = resolve(host); if (trunkip == null) continue; if (trunkip.equals(ip)) { reg = trunks[a]; break; } } } catch (Exception e) { JFLog.log(e); } sql.close(); return reg; } public SIPServerInterface getSIPServerInterface() { return this; } public static void test() { Service s = new Service(); JFLog.log(s.patternMatches("123", "123")); JFLog.log(s.patternMatches("1+23", "23")); JFLog.log(s.patternMatches("1|123", "1123")); JFLog.log(s.patternMatches("1+1|23", "123")); JFLog.log(s.patternMatches("NXXNXXX", "2342345")); JFLog.log(s.patternMatches("011.", "0111231234")); } public class JBusMethods { public void stop() { System.exit(0); } public void status(String pack) { jbusClient.call(pack, "serviceStatus", "\"jpbxlite running\""); } } /** * Connects two end-points by creating and changing the RTPRelay's as needed. */ public void connect(CallDetailsPBX cd) { SDP.Stream pbx_astream, pbx_vstream; SDP.Stream other_astream, other_vstream; //determine what needs to be connected and do it if (cd.audioRelay == null && relayAudio) { cd.audioRelay = new RTPRelay(); cd.audioRelay.init(); cd.audioRelay.setRawMode(true); } if (cd.videoRelay == null && relayVideo) { cd.videoRelay = new RTPRelay(); cd.videoRelay.init(); cd.videoRelay.setRawMode(true); } JFLog.log("connect:" + relayAudio +","+ relayVideo); //copy src -> pbxdst if (cd.src.sdp != null) { cd.pbxdst.sdp = new SDP(); //copy SRTP-DTLS details cd.pbxdst.sdp.fingerprint = cd.src.sdp.fingerprint; cd.pbxdst.sdp.iceufrag = cd.src.sdp.iceufrag; cd.pbxdst.sdp.icepwd = cd.src.sdp.icepwd; other_astream = cd.src.sdp.getFirstAudioStream(); if (relayAudio) { cd.pbxdst.sdp.ip = getlocalhost(cd); pbx_astream = cd.pbxdst.sdp.addStream(SDP.Type.audio); pbx_astream.codecs = other_astream.codecs; pbx_astream.port = cd.audioRelay.getPort_dst(); } else { cd.pbxdst.sdp.ip = other_astream.getIP(); pbx_astream = cd.pbxdst.sdp.addStream(SDP.Type.audio); pbx_astream.ip = other_astream.getIP(); pbx_astream.codecs = other_astream.codecs; pbx_astream.port = other_astream.port; pbx_astream.mode = other_astream.mode; } //copy SRTP-SDP details pbx_astream.profile = other_astream.profile; pbx_astream.keyExchange = other_astream.keyExchange; pbx_astream.keys = other_astream.keys; if (cd.src.sdp.hasVideo()) { other_vstream = cd.src.sdp.getFirstVideoStream(); if (relayVideo) { pbx_vstream = cd.pbxdst.sdp.addStream(SDP.Type.video); if (!relayAudio) { pbx_vstream.ip = getlocalhost(cd); } pbx_vstream.codecs = other_vstream.codecs; pbx_vstream.port = cd.videoRelay.getPort_dst(); } else { pbx_vstream = cd.pbxdst.sdp.addStream(SDP.Type.video); pbx_vstream.ip = other_vstream.getIP(); pbx_vstream.codecs = other_vstream.codecs; pbx_vstream.port = other_vstream.port; pbx_vstream.mode = other_vstream.mode; } //copy SRTP-SDP details pbx_vstream.profile = other_vstream.profile; pbx_vstream.keyExchange = other_vstream.keyExchange; pbx_vstream.keys = other_vstream.keys; } } //copy dst -> pbxsrc if (cd.dst.sdp != null) { cd.pbxsrc.sdp = new SDP(); //copy SRTP-DTLS details cd.pbxsrc.sdp.fingerprint = cd.dst.sdp.fingerprint; cd.pbxsrc.sdp.iceufrag = cd.dst.sdp.iceufrag; cd.pbxsrc.sdp.icepwd = cd.dst.sdp.icepwd; other_astream = cd.dst.sdp.getFirstAudioStream(); if (relayAudio) { cd.pbxsrc.sdp.ip = getlocalhost(cd); pbx_astream = cd.pbxsrc.sdp.addStream(SDP.Type.audio); pbx_astream.codecs = other_astream.codecs; pbx_astream.port = cd.audioRelay.getPort_src(); } else { cd.pbxsrc.sdp.ip = other_astream.getIP(); pbx_astream = cd.pbxsrc.sdp.addStream(SDP.Type.audio); pbx_astream.ip = other_astream.getIP(); pbx_astream.codecs = other_astream.codecs; pbx_astream.port = other_astream.port; pbx_astream.mode = other_astream.mode; } //copy SRTP-SDP details pbx_astream.profile = other_astream.profile; pbx_astream.keyExchange = other_astream.keyExchange; pbx_astream.keys = other_astream.keys; if (cd.dst.sdp.hasVideo()) { other_vstream = cd.dst.sdp.getFirstVideoStream(); if (relayVideo) { pbx_vstream = cd.pbxsrc.sdp.addStream(SDP.Type.video); if (!relayAudio) { pbx_vstream.ip = getlocalhost(cd); } pbx_vstream.codecs = other_vstream.codecs; pbx_vstream.port = cd.videoRelay.getPort_src(); } else { pbx_vstream = cd.pbxsrc.sdp.addStream(SDP.Type.video); pbx_vstream.ip = other_vstream.getIP(); //TODO : check if needed pbx_vstream.codecs = other_vstream.codecs; pbx_vstream.port = other_vstream.port; pbx_vstream.mode = other_vstream.mode; } //copy SRTP-SDP details pbx_vstream.profile = other_vstream.profile; pbx_vstream.keyExchange = other_vstream.keyExchange; pbx_vstream.keys = other_vstream.keys; } } if (cd.src.sdp == null || cd.dst.sdp == null) return; //both ends have offers, try to connect if (relayAudio) { //start audio cd.audioRelay.start(cd.src.sdp.getFirstAudioStream(), cd.dst.sdp.getFirstAudioStream()); } if (relayVideo && cd.src.sdp.hasVideo() && cd.dst.sdp.hasVideo()) { //start video cd.videoRelay.start(cd.src.sdp.getFirstVideoStream(), cd.dst.sdp.getFirstVideoStream()); } } /** * Connects two ACTIVE end-points. * Swaps the dst RTP in each audioRelay. */ public void connect(CallDetailsPBX cd1, CallDetailsPBX cd2) { RTPRelay.swap(cd1.audioRelay, cd2.audioRelay); } public void disconnect(CallDetailsPBX cd) { if (cd.audioRelay != null) { cd.audioRelay.uninit(); cd.audioRelay = null; } if (cd.videoRelay != null) { cd.videoRelay.uninit(); cd.videoRelay = null; } } }