package jpbx.plugins.core; import java.util.*; import javaforce.*; import javaforce.voip.*; import jpbx.core.*; /** Low-level plugin for handling INVITEs from extensions to trunks. */ public class Trunks implements Plugin, DialChain { public final static int pid = 20; //priority private PBXAPI api; //interface Plugin public void init(PBXAPI api) { this.api = api; api.hookDialChain(this); JFLog.log("Trunks plugin init"); } public void uninit(PBXAPI api) { api.unhookDialChain(this); JFLog.log("Trunks plugin uninit"); } public void install(PBXAPI api) { //nothing to do } public void uninstall(PBXAPI api) { //nothing to do } //interface DialChain public int getPriority() {return pid;} public int onInvite(CallDetailsPBX cd, SQL sql, boolean src) { if (cd.invited) { //reINVITE api.connect(cd); if (src) { cd.pbxdst.branch = cd.src.branch; cd.pbxsrc.branch = cd.src.branch; } else { cd.pbxsrc.branch = cd.dst.branch; cd.pbxsrc.branch = cd.dst.branch; } cd.sip.buildsdp(cd, src ? cd.pbxdst : cd.pbxsrc); api.issue(cd, null, true, !src); return pid; } //dial outbound from extension if (!cd.authorized) { if (cd.route) route_call(cd, sql); //TODO : route call from one trunk to another ??? return -1; //??? pid ??? } if ((cd.user == null) || (cd.user.length() == 0)) return -1; String ext = sql.select1value("SELECT ext FROM exts WHERE ext=" + sql.quote(cd.user)); if (ext == null) return -1; //an extension is not dialing if (!api.isRegistered(cd.user)) return -1; if (cd.user.equals(cd.dialed)) return -1; //voicemail may intercept next Extension x = api.getExtension(cd.user); if (x == null) return -1; //unauth trunk access cd.pbxdst.to = cd.src.to.clone(); cd.pbxdst.from = cd.src.from.clone(); cd.pbxdst.contact = cd.src.contact; //BUG : should this be changed to DID from trunk register string ??? cd.pbxdst.branch = cd.src.branch; api.connect(cd); cd.trunks = api.getTrunks(cd.dialed, cd.user, sql); if ((cd.trunks == null) || (cd.trunks.length < 2)) return -1; //no routes on this trunk api.reply(cd, 100, "TRYING", null, false, true); cd.dialed = cd.trunks[0]; //apply new dialed after outroute pattern is applied cd.trunkidx = 1; cd.lastcode = -1; cd.invited = true; tryTrunk(cd, sql); return pid; } public void onRinging(CallDetailsPBX cd, SQL sql, boolean src) { if (src) return; cd.trunkok = true; api.reply(cd, 180, "RINGING", null, false, true); } public void onSuccess(CallDetailsPBX cd, SQL sql, boolean src) { if (!cd.cmd.equals("INVITE")) return; synchronized(cd.lock) { if (cd.cancelled) return; cd.trunkok = true; cd.connected = true; } clearTimer(cd); cd.cmd = "ACK"; api.issue(cd, null, false, src); //send ACK if (src) { cd.pbxdst.to = cd.src.to.clone(); cd.pbxdst.from = cd.src.from.clone(); } else { cd.pbxsrc.to = cd.dst.to.clone(); cd.pbxsrc.from = cd.dst.from.clone(); } api.connect(cd); cd.sip.buildsdp(cd, src ? cd.pbxdst : cd.pbxsrc); cd.cmd = "INVITE"; api.reply(cd, 200, "OK", null, true, !src); //send 200 (NOTE : ACK is ignored (already sent)) } public void onCancel(CallDetailsPBX cd, SQL sql, boolean src) { //extension "CANCEL"ed call if (!src) return; synchronized(cd.lock) { if (cd.connected) return; //TODO : should return error already connected cd.trunkok = true; //stop tryTrunk() cd.cancelled = true; } api.reply(cd, 200, "OK", null, false, true); api.issue(cd, null, false, false); api.disconnect(cd); } public void onError(CallDetailsPBX cd, SQL sql, int code, boolean src) { if (!cd.connected) { cd.lastcode = code; } api.reply(cd, code, "RELAY", null, false, !src); cd.cmd = "ACK"; api.issue(cd, null, false, src); } public void onTrying(CallDetailsPBX cd, SQL sql, boolean src) { if (!src) { cd.trunkdelay = 2; //give trunk 10 more secs } } public void onBye(CallDetailsPBX cd, SQL sql, boolean src) { if (src) { api.log(cd, "TRUNK : src terminated call with BYE"); cd.pbxdst.cseq++; api.issue(cd, null, false, false); api.reply(cd, 200, "OK", null, false, true); } else { api.log(cd, "TRUNK : dst terminated call with BYE"); //NOTE:to/from have been swapped cd.pbxsrc.to = cd.dst.to.clone(); cd.pbxsrc.from = cd.dst.from.clone(); api.issue(cd, null, false, true); cd.pbxdst.cseq++; cd.pbxdst.to = cd.dst.to.clone(); cd.pbxdst.from = cd.dst.from.clone(); api.reply(cd, 200, "OK", null, false, false); } api.disconnect(cd); } public void onFeature(CallDetailsPBX cd, SQL sql, String cmd, String cmddata, boolean src) { } private void tryTrunk(CallDetailsPBX cd, SQL sql) { //try INVITE to cd.trunks[cd.trunkidx] String trunk = cd.trunks[cd.trunkidx]; api.log(cd, "TRUNK : Trying trunk " + trunk); String host_rules[] = sql.select1row("SELECT host,outrules FROM trunks WHERE trunk=" + sql.quote(trunk)); if ((host_rules == null) || (host_rules[0] == null)) return; //ohoh String host = host_rules[0]; int idx = host.indexOf(':'); if (idx == -1) { cd.pbxdst.host = host; cd.pbxdst.port = 5060; } else { cd.pbxdst.host = host.substring(0, idx); cd.pbxdst.port = Integer.valueOf(host.substring(idx+1)); } String rulesStr = host_rules[1]; if (rulesStr != null) { cd.pbxdst.to[1] = null; String rules[] = rulesStr.split(":"); for(int a=0;a<rules.length;a++) { cd.pbxdst.to[1] = api.patternMatches(rules[a], cd.dialed); if (cd.pbxdst.to[1] != null) break; } if (cd.pbxdst.to[1] == null) cd.pbxdst.to[1] = cd.dialed; } else { cd.pbxdst.to[1] = cd.dialed; } cd.pbxdst.to[0] = cd.pbxdst.to[1]; cd.sip.buildsdp(cd, cd.pbxdst); cd.pbxdst.cseq = cd.src.cseq; cd.uri = "sip:" + cd.dialed + "@" + cd.pbxdst.host; cd.pbxdst.to[2] = cd.pbxdst.host; cd.cmd = "INVITE"; cd.pbxdst.to = SIP.removetag(cd.pbxdst.to.clone()); cd.pbxdst.from = cd.pbxdst.from.clone(); cd.trunkok = false; cd.authsent = false; api.issue(cd, null, true, false); setTimer(cd); } private void setTimer(CallDetailsPBX cd) { cd.timer = new Timer(); cd.timer.schedule(new TimerTask() { private CallDetailsPBX cd; public void run() { synchronized (cd.lock) { if (cd.trunkok) return; if (cd.trunkdelay > 0) { cd.trunkdelay--; setTimer(cd); } else { //send CANCEL to last trunk if (cd.lastcode == -1) { cd.cmd = "CANCEL"; api.issue(cd, null, false, false); } cd.trunkidx++; if ((cd.trunks.length == cd.trunkidx) || (cd.lastcode == 486)) { //reply last error (if any) to extension if (cd.lastcode == -1) cd.lastcode = 487; api.log(cd, "TRUNK : call failed : error = " + cd.lastcode); api.reply(cd, cd.lastcode, "ERROR", null, false, true); return; } SQL sql = new SQL(); if (!sql.connect(Service.jdbc)) return; //ohoh tryTrunk(cd, sql); sql.close(); } } } public TimerTask init(CallDetailsPBX cd) { this.cd = cd; return this; } }.init(cd), 5000); //give trunk 5 sec to respond } private void clearTimer(CallDetailsPBX cd) { synchronized (cd.lock) { if (cd.timer == null) return; cd.timer.cancel(); cd.timer = null; } } private void route_call(CallDetailsPBX cd, SQL sql) { //TODO : route call from one trunk to another } }