gt:     if (req.equals("CANCEL")) {
context: package javaforce.voip;
  
  import java.net.*;
  import java.util.*;
  import java.util.stream.Collectors;
  
  import javaforce.*;
  
  /**
   * Handles the client end of a SIP link.
   */
  
  public class SIPClient extends SIP implements SIPInterface, STUN.Listener {
  
    public enum NAT {None, STUN, TURN, ICE};
  
    private String remotehost, remoteip;
    private InetAddress remoteaddr;
    private int remoteport;
    private String user, auth;
    private String pass;
    private SIPClientInterface iface;
    private String localhost;
    private int localport, rport = -1;
    private static boolean use_received = true;
    private static boolean use_rport = true;
    private Hashtable<String, CallDetails> cdlist;
    private boolean registered;
    private static NAT nat = NAT.None;
    private static boolean useNATOnPrivateNetwork = false;  //do not use NATing techniques on private network servers
    private static String stunHost, stunUser, stunPass;
  
    public Object rtmp;  //used by RTMP2SIPServer
    public Object userobj;  //user definable
    public int expires;  //expires
  
    /**
     * Returns the registered user name.
     */
    public String getUser() {
      return user;
    }
  
    /**
     * Returns the remote host.
     */
    public String getRemoteHost() {
      return remotehost;
    }
  
    /**
     * Determines if the SIP call is currently on hold.
     */
    public boolean isHold(String callid) {
      CallDetails cd = getCallDetails(callid);
      if (cd.dst.sdp == null) return false;
      return cd.dst.sdp.getFirstAudioStream().canSend();
    }
  
    /**
     * Returns the registration status.
     */
    public boolean isRegistered() {
      return registered;
    }
  
    /**
     * Initialize this instance.<br>
     *
     * @param remotehost,remoteport is the SIP Server/Proxy address.<br>
     * @param localport is the UDP port to bind to locally.<br>
     * @param iface must be a SIPClientInterface where SIP events are dispatched
     * to.<br>
     */
    public boolean init(String remotehost, int remoteport, int localport, SIPClientInterface iface, Transport type) {
      this.iface = iface;
      this.localport = localport;
      this.remoteport = remoteport;
      this.remotehost = remotehost;
      this.remoteip = resolve(remotehost);
      cdlist = new Hashtable<String, CallDetails>();
      try {
        this.remoteaddr = InetAddress.getByName(remoteip);
        if (nat == NAT.STUN || nat == NAT.ICE) {
          if (!startSTUN()) return false;
        }
        findlocalhost();
        JFLog.log("localhost = " + localhost + " for remotehost = " + remotehost);
        if (this.remotehost.equals("127.0.0.1")) {
          this.remotehost = localhost;
          remoteip = resolve(this.remotehost);
          JFLog.log("changed 127.0.0.1 to " + this.remotehost + " " + this.remoteip);
        }
        if (nat == NAT.STUN || nat == NAT.ICE) {
          stopSTUN();
        }
        return super.init(localport, this, false, type);
      } catch (Exception e) {
        if (stun != null) stopSTUN();
        JFLog.log(e);
        return false;
      }
    }
  
    /**
     * Free all resources.
     */
    public void uninit() {
      super.uninit();
    }
  
    /**
     * Sets the type of NAT traversal type (global setting).
     */
    public static void setNAT(NAT nat, String host, String user, String pass) {
      SIPClient.nat = nat;
      stunHost = host;
      stunUser = user;
      stunPass = pass;
    }
  
    /**
     * Disable/enable use of NAT traversal on private networks (global setting)
     * Private networks : 192.168.x.x , 10.x.x.x , 172.[16-31].x.x
     */
  
    public static void useNATOnPrivateNetwork(boolean state) {
      useNATOnPrivateNetwork = state;
    }
  
    /**
     * Registers this client with the SIP server/proxy. <br>
     *
     * @param user : username<br>
     * @param auth : authorization name (optional, default=user)<br>
     * @param pass : password<br>
     * @return : if message was sent to server successfully<br> This function does
     * not block waiting for a reply. You should receive onRegister() thru the
     * SIPClientInterface when a reply is returned from server.<br>
     */
    public boolean register(String user, String auth, String pass) {
      return register(user, auth, pass, 3600);
    }
  
    /**
     * Registers this client with the SIP server/proxy. <br>
     *
     * @param user : username<br>
     * @param auth : authorization name (optional, default=user)<br>
     * @param pass : password<br>
     * @param expires : number seconds to register for (0=unregisters)<br>
     * @return : if message was sent to server<br> This function does not block
     * waiting for a reply. You should receive either registered() or
     * unauthorized() thru the SIPClientInterface when a reply is returned from
     * server.<br>
     *
     */
    public boolean register(String user, String auth, String pass, int expires) {
      String regcallid = getcallid();
      CallDetails cd = getCallDetails(regcallid);  //new CallDetails
      if ((auth == null) || (auth.length() == 0)) {
        this.auth = user;
      } else {
        this.auth = auth;
      }
      this.user = user;
      this.pass = pass;
      this.expires = expires;
      cd.src.expires = expires;
      cd.src.to = new String[]{user, user, remotehost + ":" + remoteport, ":"};
      cd.src.from = new String[]{user, user, remotehost + ":" + remoteport, ":"};
      cd.src.from = replacetag(cd.src.from, generatetag());
      cd.src.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
      cd.src.uri = "sip:" + remotehost;  // + ";rinstance=" + getrinstance();
      cd.src.branch = getbranch();
      cd.src.cseq++;
      if ((pass == null) || (pass.length() == 0)) {
        return true;  //non-register mode
      }
      cd.authsent = false;
      cd.src.extra = null;
      cd.src.epass = null;
      boolean ret = issue(cd, "REGISTER", false, true);
      return ret;
    }
  
    /**
     * Reregister with the server.
     */
    public boolean reregister() {
      return register(user, auth, pass, expires);
    }
  
    /**
     * Reregister with the server using an expiration of 0 (zero). Effectively
     * unregisters.
     */
    public boolean unregister() {
      return register(user, auth, pass, 0);
    }
  
    /**
     * Publishes Presence to server. (not tested since Asterisk doesn't support it)
     */
    public boolean publish(String state) {
      String pubcallid = getcallid();
      CallDetails cd = getCallDetails(pubcallid);  //new CallDetails
      cd.src.to = new String[]{user, user, remotehost + ":" + remoteport, ":"};
      cd.src.from = new String[]{user, user, remotehost + ":" + remoteport, ":"};
      cd.src.from = replacetag(cd.src.from, generatetag());
      cd.src.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
      cd.src.uri = "sip:" + user + "@" + remotehost;
      cd.src.branch = getbranch();
      cd.src.cseq++;
      cd.sdp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"pres:" + user + "@" + remotehost
              + "\">" + "<tuple id=\"" + gettupleid() + "\"><status><basic>" + state + "</basic></status></tuple></presence>";
      cd.authsent = false;
      cd.src.extra = "Event: presence\r\n";
      cd.src.epass = null;
      return issue(cd, "PUBLISH", true, true);
    }
  
    /**
     * Subscribe to a user's presence on server.
     */
    public boolean subscribe(String subuser, String event, int expires) {
      String subcallid = getcallid();
      CallDetails cd = getCallDetails(subcallid);  //new CallDetails
      cd.src.to = new String[]{subuser, subuser, remotehost + ":" + remoteport, ":"};
      cd.src.from = new String[]{user, user, remotehost + ":" + remoteport, ":"};
      cd.src.from = replacetag(cd.src.from, generatetag());
      cd.src.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
      cd.src.uri = "sip:" + subuser + "@" + remotehost;
      cd.src.branch = getbranch();
      cd.src.cseq++;
      cd.src.expires = expires;
      cd.src.extra = "Accept: multipart/related, application/rlmi+xml, application/pidf+xml\r\nEvent: " + event + "\r\n";
      cd.src.epass = null;
      return issue(cd, "SUBSCRIBE", false, true);
    }
  
    /**
     * Send an empty SIP message to server. This should be done periodically to
     * keep firewalls open. Most routers close UDP connections after 60 seconds.
     * Not sure if needed with TCP/TLS but is done anyways.
     */
    public void keepalive() {
      send(remoteaddr, remoteport, "\r\n\r\n");  //must resemble a complete packet
    }
  
    /**
     * Determine if server is on a local private network.
     */
    public static boolean isPrivateNetwork(String ip) {
      //in case your PBX is on your own local IP network
      //see http://en.wikipedia.org/wiki/Private_network
      if (ip.startsWith("192.168.")) {
        return true;
      }
      if (ip.startsWith("10.")) {
        return true;
      }
      if (ip.startsWith("169.254.")) {
        return true;
      }
      for(int a=16;a<=31;a++) {
        if (ip.startsWith("172." + a + ".")) return true;
      }
      return false;
    }
  
    /**
     * Returns local SIP IP address.
     */
    public String getlocalhost(String host) {
      return localhost;
    }
  
    /**
     * Returns local RTP IP address.
     */
    public String getlocalRTPhost(String host) {
      if (RTP.useTURN)
        return RTP.getTurnIP();
      else
        return getlocalhost(host);
    }
  
    private boolean findlocalhost_webserver(String host) {
      //this returns your local ip (more accurate on multi-homed systems than java api)
      Socket s;
      try {
        s = new Socket();
        s.connect(new InetSocketAddress(host, 80), 1000);
        localhost = s.getLocalAddress().getHostAddress();
        try { s.close(); } catch (Exception e) {}
        JFLog.log("Detected IP connecting to WebServer at " + host);
        return true;
      } catch (Exception e) {
        return false;
      }
    }
  
    private STUN stun;
    private final Object stunLock = new Object();
    private volatile boolean stunWaiting = false;
    private volatile boolean stunResponse = false;
    private boolean startSTUN() {
      stun = new STUN();
      if (!stun.start(localport, stunHost, stunUser, stunPass, this)) return false;
      return true;
    }
  
    private void stopSTUN() {
      if (stun == null) return;
      stun.close();
      stun = null;
    }
  
    //interface STUN.Listener
    public void stunPublicIP(STUN stun, String ip, int port) {
      synchronized(stunLock) {
        if (stunWaiting) {
          localhost = ip;
          rport = port;  //NOTE : this port may be wrong if router is symmetrical
          stunResponse = true;
          stunLock.notify();
        }
      }
    }
    public void turnAlloc(STUN stun, String ip, int port, byte token[], int lifetime) {}
    public void turnBind(STUN stun) {}
    public void turnRefresh(STUN stun, int lifetime) {}
    public void turnFailed(STUN stun) {}
    public void turnData(STUN stun, byte data[], int offset, int length, short channel) {}
  
    private boolean findlocalhost_stun() {
      stunResponse = false;
      stunWaiting = true;
      synchronized(stunLock) {
        stun.requestPublicIP();
        try {stunLock.wait(1000);} catch (Exception e) {JFLog.log(e);}
        stunWaiting = false;
      }
      if (stunResponse) {
        JFLog.log("Detected IP using STUN");
      }
      return stunResponse;
    }
  
    private boolean findlocalhost_java() {
      //this only detects your local IP, not your internet ip
      //not accurate on multi-homed systems
      try {
        InetAddress local = InetAddress.getLocalHost();
        localhost = local.getHostAddress();
        JFLog.log("Detected IP using Java:" + localhost);
        return true;
      } catch (Exception e) {
        return false;
      }
    }
  
    /**
     * Determines local IP address. Method depends on NAT traversal type selected.
     */
    private void findlocalhost() {
      JFLog.log("Detecting localhost for remotehost = " + remotehost);
      if (useNATOnPrivateNetwork || !isPrivateNetwork(remoteip)) {
        if (nat == NAT.STUN || nat == NAT.ICE) {
          if (findlocalhost_stun()) return;
          JFLog.log("SIP:STUN:Failed");
        }
      }
      //try connecting to remotehost on webserver port
      if (findlocalhost_webserver(remotehost)) return;
      //use java (returns local ip, not internet ip) (not reliable on multi-homed systems)
      if (findlocalhost_java()) return;
      //if all else fails (use a dummy DHCP failure IP)
      Random r = new Random();
      localhost = "169.254." + r.nextInt(256) + "." + r.nextInt(256);
    }
  
    private synchronized CallDetails getCallDetails(String callid) {
      CallDetails cd = cdlist.get(callid);
      if (cd == null) {
        cd = new CallDetails();
        JFLog.log("Create CallDetails:" + callid);
        cd.callid = callid;
        setCallDetails(callid, cd);
      }
      return cd;
    }
  
    private void setCallDetails(String callid, CallDetails cd) {
      if (cd == null) {
        JFLog.log("Delete CallDetails:" + callid);
        cdlist.remove(callid);
      } else {
        cdlist.put(callid, cd);
      }
    }
  
    /**
     * Issues a command to the SIP server.
     */
    private boolean issue(CallDetails cd, String cmd, boolean sdp, boolean src) {
      CallDetails.SideDetails cdsd = (src ? cd.src : cd.dst);
      JFLog.log("callid:" + cd.callid + "\r\nissue command : " + cmd + " from : " + user + " to : " + remotehost);
      cd.dst.host = remoteip;
      cd.dst.port = remoteport;
      StringBuilder req = new StringBuilder();
      req.append(cmd + " " + cdsd.uri + " SIP/2.0\r\n");
      req.append("Via: SIP/2.0/" + transport.getName() + " " + getlocalhost(null) + ":" + getlocalport() + ";branch=" + cdsd.branch + (use_rport ? ";rport" : "") + "\r\n");
      req.append("Max-Forwards: 70\r\n");
      req.append("Contact: " + cdsd.contact + "\r\n");
      req.append("To: " + join(cdsd.to) + "\r\n");
      req.append("From: " + join(cdsd.from) + "\r\n");
      req.append("Call-ID: " + cd.callid + "\r\n");
      req.append("Cseq: " + cdsd.cseq + " " + cmd + "\r\n");
      if ((cmd.equals("REGISTER")) || (cmd.equals("PUBLISH")) || (cmd.equals("SUBSCRIBE"))) {
        req.append("Expires: " + cdsd.expires + "\r\n");
      }
      req.append("Allow: INVITE, ACK, CANCEL, BYE, REFER, NOTIFY, OPTIONS\r\n");
      req.append("User-Agent: " + useragent + "\r\n");
      if (cdsd.extra != null) {
        req.append(cdsd.extra);
      }
      if (cdsd.epass != null) {
        req.append(cdsd.epass);
      }
      if ((cd.sdp != null) && (sdp)) {
        if (cd.sdp.startsWith("<?xml")) {
          req.append("Content-Type: application/pidf+xml\r\n");
        } else if (cd.sdp.startsWith("SIP/2.0")) {
          req.append("Content-Type: message/sipfrag;version=2.0\r\n");
        } else {
          req.append("Content-Type: application/sdp\r\n");
        }
        req.append("Content-Length: " + cd.sdp.length() + "\r\n\r\n");
        req.append(cd.sdp);
      } else {
        req.append("Content-Length: 0\r\n\r\n");
      }
      return send(remoteaddr, remoteport, req.toString());
    }
  
    /**
     * Sends a reply to a SIP server.
     */
    public boolean reply(CallDetails cd, String cmd, int code, String msg, boolean sdp, boolean src) {
      JFLog.log("callid:" + cd.callid + "\r\nissue reply : " + code + " to : " + remotehost);
      CallDetails.SideDetails cdsd = (src ? cd.src : cd.dst);
      StringBuilder req = new StringBuilder();
      req.append("SIP/2.0 " + code + " " + msg + "\r\n");
      if (cdsd.vialist != null) {
        for (int a = 0; a < cdsd.vialist.length; a++) {
          req.append(cdsd.vialist[a]);
          req.append("\r\n");
        }
      }
      if ((cdsd.vialist == null) || (cdsd.vialist.length == 0)) {
        req.append("Via: SIP/2.0/" + transport.getName() + " " + getlocalhost(null) + ":" + getlocalport() + ";branch=" + cdsd.branch + (use_rport ? ";rport" : "") + "\r\n");
      }
      req.append("Contact: " + cdsd.contact + "\r\n");
      req.append("To: " + join(cdsd.to) + "\r\n");
      req.append("From: " + join(cdsd.from) + "\r\n");
      req.append("Call-ID: " + cd.callid + "\r\n");
      req.append("Cseq: " + cdsd.cseq + " " + cmd + "\r\n");
      req.append("Allow: INVITE, ACK, CANCEL, BYE, REFER, NOTIFY, OPTIONS\r\n");
      req.append("User-Agent: " + useragent + "\r\n");
      if ((cd.sdp != null) && (sdp)) {
        req.append("Content-Type: application/sdp\r\n");
        req.append("Content-Length: " + cd.sdp.length() + "\r\n\r\n");
        req.append(cd.sdp);
      } else {
        req.append("Content-Length: 0\r\n\r\n");
      }
      send(remoteaddr, remoteport, req.toString());
      return true;
    }
  
    /**
     * Send an invite to server.<br>
     *
     * @param to : number to dial
     * @param sdp : SDP (only stream types/modes/codecs are needed) (ip not needed)
     *
     * @return unique Call-ID (not caller id)<br>
     */
    public String invite(String to, SDP sdp) {
      String callid = getcallid();
      CallDetails cd = getCallDetails(callid);  //new CallDetails
      cd.src.to = new String[]{to, to, remotehost + ":" + remoteport, ":"};
      cd.src.from = new String[]{user, user, remotehost + ":" + remoteport, ":"};
      cd.src.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
      cd.src.uri = "sip:" + to + "@" + remotehost + ":" + remoteport;
      cd.src.from = replacetag(cd.src.from, generatetag());
      cd.src.branch = getbranch();
      cd.src.o1 = 256;
      cd.src.o2 = 256;
      cd.src.sdp = sdp;
      buildsdp(cd, cd.src);
      cd.src.cseq++;
      cd.authsent = false;
      cd.src.extra = null;
      cd.src.epass = null;
      if (!issue(cd, "INVITE", true, true)) {
        return null;
      }
      return callid;
    }
  
    /**
     * Send a refer command to server (blind transfer)
     */
    public boolean refer(String callid, String to) {
      String headers = "Refer-To: <sip:" + to + "@" + remotehost + ">\r\nReferred-By: <sip:" + user + "@" + remotehost + ":" + getlocalport() + ">\r\n";
      CallDetails cd = getCallDetails(callid);
      cd.src.cseq++;
      cd.authsent = false;
      cd.src.extra = headers;
      cd.src.epass = null;
      boolean ret = issue(cd, "REFER", false, true);
      return ret;
    }
  
    /**
     * Send a refer command to server (non-blind transfer)
     */
    public boolean referLive(String callid, String othercallid) {
      //see RFC5589 part 7.2
      //this MUST be sent to the transfer target from the transferor (not to the transferee) [we are actually transfering the target to the tranferee - kinda backwards thinking]
      //callid = transfer target call leg
      //othercallid = transferee call leg
      CallDetails cd = getCallDetails(callid);
      CallDetails othercd = getCallDetails(othercallid);
      String headers = "Refer-To: <sip:" + othercd.src.to[1] + "@" + remotehost + "?Replaces=" + othercallid
        + "%3Bto-tag%3D" + gettag(othercd.src.to) + "%3Bfrom-tag%3D" + gettag(othercd.src.from) + ">\r\n";
  //    headers += "Referred-By: <sip:" + user + "@" + remotehost + ":" + getlocalport() + ">\r\n";
      headers += "Supported: gruu, replaces, tdialog\r\n";
      headers += "Require: tdialog\r\n";
      headers += "Target-Dialog: " + callid + ";local-tag=" + gettag(cd.src.to) + ";remote-tag=" + gettag(cd.src.from) + "\r\n";
      cd.src.cseq++;
      cd.authsent = false;
      cd.src.extra = headers;
      cd.src.epass = null;
      boolean ret = issue(cd, "REFER", false, true);
      return ret;
    }
  
    /**
     * Set/clear hold state, must call reinvite() after to notify server.
     */
    public boolean setHold(String callid, boolean state) {
      CallDetails cd = getCallDetails(callid);
      cd.src.sdp.getFirstAudioStream().mode = (state ? SDP.Mode.inactive : SDP.Mode.sendrecv);
      return true;
    }
  
    /**
     * Sends a reINVITE to server with a new SDP packet.
     *
     * @param callid : id of call to reinvite
     * @param sdp : SDP (only stream types/modes/codecs are needed) (ip not needed)
     */
    public boolean reinvite(String callid, SDP sdp) {
      CallDetails cd = getCallDetails(callid);
      cd.src.o2++;
      cd.src.sdp = sdp;
      buildsdp(cd, cd.src);
      cd.src.cseq++;
      cd.authsent = false;
      cd.src.extra = null;
      cd.src.epass = null;
      if (!issue(cd, "INVITE", true, true)) {
        return false;
      }
      return true;
    }
  
    /**
     * Sends a reINVITE to server using previous SDP packet.
     *
     * @param callid : id of call to reinvite
     */
    public boolean reinvite(String callid) {
      CallDetails cd = getCallDetails(callid);
      return reinvite(callid, cd.src.sdp);
    }
  
    /**
     * Cancels an INVITE request. Usually done while phone is ringing.
     */
    public boolean cancel(String callid) {
      CallDetails cd = getCallDetails(callid);
      String epass;
      if (cd.authstr != null) {
        epass = getAuthResponse(cd.authstr, auth, pass, remotehost, "BYE", "Proxy-Authorization:");
      } else {
        epass = null;
      }
      cd.authsent = false;
      if (cd.dst.to != null) cd.src.to = cd.dst.to;  //update tag if avail
      if (cd.dst.from != null) cd.src.from = cd.dst.from;  //is this line even needed???
      cd.src.extra = null;
      cd.src.epass = epass;
      boolean ret = issue(cd, "CANCEL", false, true);
      return ret;
    }
  
    /**
     * Send a request to terminate a call in progress.
     */
    public boolean bye(String callid) {
      CallDetails cd = getCallDetails(callid);
      String epass;
      if (cd.authstr != null) {
        epass = getAuthResponse(cd.authstr, auth, pass, remotehost, "BYE", "Proxy-Authorization:");
      } else {
        epass = null;
      }
      cd.src.cseq++;
      cd.authsent = false;
      cd.src.extra = null;
      cd.src.epass = epass;
      boolean ret = issue(cd, "BYE", false, true);
      return ret;
    }
  
    /**
     * Sends a reply to accept an inbound (re)INVITE.
     * @param sdp : SDP (only stream types/modes/codecs are needed) (ip not needed)
     */
    public boolean accept(String callid, SDP sdp) {
      CallDetails cd = getCallDetails(callid);
      cd.src.sdp = sdp;
      buildsdp(cd, cd.src);
      reply(cd, "INVITE", 200, "OK", true, false);
      //need to swap to/from for BYE (only if accept()ing a call)
      cd.src.to = cd.dst.from.clone();
      cd.src.from = cd.dst.to.clone();
      //copy all other needed fields
      cd.src.uri = cd.dst.uri;
      cd.src.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
      cd.src.branch = cd.dst.branch;
      return true;
    }
  
    /**
     * Sends a reply to accept an inbound REINVITE.
     * @param sdp : SDP (only stream types/modes/codecs are needed) (ip not needed)
     */
    public boolean reaccept(String callid, SDP sdp) {
      CallDetails cd = getCallDetails(callid);
      cd.src.sdp = sdp;
      buildsdp(cd, cd.src);
      reply(cd, "INVITE", 200, "OK", true, false);
      //do NOT swap to/from in this case
      //copy all other needed fields
      cd.src.uri = cd.dst.uri;
      cd.src.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
      cd.src.branch = cd.dst.branch;
      return true;
    }
  
    /**
     * Denies an INVITE for whatever reason.
     */
    public boolean deny(String callid, String msg, int code) {
      CallDetails cd = getCallDetails(callid);
      reply(cd, "INVITE", code, msg, false, false);
      return true;
    }
  
    /**
     * Processes SIP messages sent from the SIP server.
     */
    public void packet(String msg[], String remoteip, int remoteport) {
      try {
        JFLog.trace(String.format("%nReceived from: %s:%d%n<-----<%n%s%n------", remoteip, remoteport, Arrays.stream(msg).collect(Collectors.joining("\r\n"))));
        if (remoteip.equals("127.0.0.1")) {
          remoteip = localhost;
        }
        if (!remoteip.equals(this.remoteip) || remoteport != this.remoteport) {
          JFLog.log("Ignoring packet from unknown host:" + remoteip + ":" + remoteport);
          return;
        }
        String tmp, req = null, epass;
        String callid = getHeader("Call-ID:", msg);
        if (callid == null) callid = getHeader("i:", msg);
        if (callid == null) {
          JFLog.log("Bad packet (no Call-ID) from:" + remoteip + ":" + remoteport);
          return;
        }
        CallDetails cd = getCallDetails(callid);
        String[] previousTo = cd.dst.to;
        cd.dst.host = remoteip;
        cd.dst.port = remoteport;
        cd.dst.cseq = getcseq(msg);
        cd.dst.branch = getbranch(msg);
        cd.headers = msg;
        //get cd.dst.to
        tmp = getHeader("To:", msg);
        if (tmp == null) {
          tmp = getHeader("t:", msg);
        }
        cd.dst.to = split(tmp);
        //get cd.dst.from
        tmp = getHeader("From:", msg);
        if (tmp == null) {
          tmp = getHeader("f:", msg);
        }
  
        //RFC 3581 - rport
        String via = getHeader("Via:", msg);
        if (via == null) {
          via = getHeader("v:", msg);
        }
        if (via != null) {
          String f[] = via.split(";");
          if (use_received) {
            //check for received in via header which equals my IP as seen by server
            String received = getHeader("received=", f);
            if (received != null) {
              if (!localhost.equals(received)) {
                localhost = received;
                JFLog.log("received ip=" + received + " for remotehost = " + remotehost);
              }
            }
          }
          if (use_rport) {
            //check for rport in via header which equals my port as seen by server
            String rportstr = getHeader("rport=", f);
            if (rportstr != null && rportstr.length() > 0) {
              int newrport = JF.atoi(rportstr);
              if (rport != newrport) {
                rport = newrport;
                JFLog.log("received port=" + rport + " for remotehost = " + remotehost);
              }
            }
          }
        }
  
        cd.dst.from = split(tmp);
        //get via list
        cd.dst.vialist = getvialist(msg);
        //set contact
        cd.dst.contact = "<sip:" + user + "@" + getlocalhost(null) + ":" + getlocalport() + ">";
        //get uri (it must equal the Contact field)
        cd.dst.uri = getHeader("Contact:", msg);
        if (cd.dst.uri == null) {
          cd.dst.uri = getHeader("m:", msg);
        }
        if (cd.dst.uri != null) {
          cd.dst.uri = cd.dst.uri.substring(1, cd.dst.uri.length() - 1);  //remove < > brackets
        }
        String cmd = getcseqcmd(msg);
        int type = getResponseType(msg);
        if (type != -1) {
          JFLog.log("callid:" + callid + "\r\nreply=" + type);
          //RFC 3261 : 8.1.3.3 - Vias
          if (cd.dst.vialist.length > 1) {
            JFLog.log("Multiple Via:s detected in reply, discarding reply");
            return;
          }
        } else {
          req = getRequest(msg);
          JFLog.log("callid:" + callid + "\r\nrequest=" + req);
        }
        switch (type) {
          case -1:
            try {
              handleRequest(msg, req, callid, cd, cmd, previousTo);
            } catch (SipFailureException e) {
              reply(cd, cmd, e.getCode(), e.getReason(), false, false);
            }
            break;
          case 100:
            iface.onTrying(this, callid);
            break;
          case 180:
            iface.onRinging(this, callid);
            break;
          case 183:
          case 200:
            if (cmd.equals("REGISTER")) {
              if (type == 183) break;  //not used in REGISTER command
              if (cd.src.expires > 0) {
                registered = true;
                iface.onRegister(this, true);
              } else {
                registered = false;
                //iface.onRegister() ???
              }
            } else if (cmd.equals("INVITE")) {
              cd.dst.sdp = getSDP(msg);
  
              //update cd.src.to tag value
              cd.src.to = cd.dst.to;
  
              iface.onSuccess(this, callid, cd.dst.sdp, type == 200);
              if (type == 200) issue(cd, "ACK", false, true);
            } else if (cmd.equals("BYE")) {
              if (type == 183) break;  //not used in BYE command
              //call leg ended
              setCallDetails(callid, null);
            }
            break;
          case 202:
            if (cmd.equals("REFER")) {
              iface.onRefer(this, callid);
            }
            break;
          case 401:
          case 407:
            if (cd.authsent) {
              JFLog.log("Server Error : Double " + type);
            } else {
              issue(cd, "ACK", false, true);
              cd.authstr = getHeader("WWW-Authenticate:", msg);
              if (cd.authstr == null) {
                cd.authstr = getHeader("Proxy-Authenticate:", msg);
              }
              if (cd.authstr == null) {
                JFLog.log("err:401/407 without Authenticate tag");
                break;
              }
              epass = getAuthResponse(cd.authstr, auth, pass, remotehost, cmd, (type == 401 ? "Authorization:" : "Proxy-Authorization:"));
              if (epass == null) {
                JFLog.log("err:gen auth failed");
                break;
              }
              cd.src.cseq++;
              cd.src.epass = epass;
              issue(cd, cmd, cmd.equals("INVITE"), true);
              cd.authsent = true;
            }
            break;
          case 403:
            if (cmd.equals("REGISTER")) {
              //bad password
              iface.onRegister(this, false);
            } else {
              iface.onCancel(this, callid, type);
            }
            issue(cd, "ACK", false, true);
            break;
          case 404:  //no one there
          case 486:  //busy
            if (cd.dst.to != null) cd.src.to = cd.dst.to;
            iface.onCancel(this, callid, type);
            issue(cd, "ACK", false, true);
            setCallDetails(callid, null);
            break;
          default:
            //treat all other codes as a cancel
            if (cd.dst.to != null) cd.src.to = cd.dst.to;
            iface.onCancel(this, callid, type);
            issue(cd, "ACK", false, true);
  //          setCallDetails(callid, null);  //might not be done
            break;
        }
      } catch (Exception e) {
        JFLog.log(e);
      }
    }
  
    private void handleRequest(String[] msg, String req, String callid, CallDetails cd, String cmd, String[] previousTo) {
      if (req.equals("INVITE")) {
        //generate toTag
        if (gettag(cd.dst.to) == null) {
          String previousTag = previousTo == null ? null : gettag(previousTo);
          cd.dst.to = replacetag(cd.dst.to, previousTag == null ? generatetag() : previousTag);
        }
  
        if (cd.dst.to[1] != null && !user.equals(cd.dst.to[1])) {
          throw new SipFailureException(404, "Not Found");
        }
  
        //get o1/o2
        cd.dst.o1 = geto(msg, 1) + 1;
        cd.dst.o2 = geto(msg, 2) + 1;
        cd.dst.sdp = getSDP(msg);
  
        switch (iface.onInvite(this, callid, cd.dst.from[0], cd.dst.from[1], cd.dst.sdp)) {
          case 180:  //this is the normal return
            reply(cd, cmd, 180, "RINGING", false, false);
            break;
          case 200:  //this was usually a reINVITE to change sdp details
            buildsdp(cd, cd.src);
            reply(cd, cmd, 200, "OK", true, false);
            break;
          case 486:
            reply(cd, cmd, 486, "BUSY HERE", false, false);
            break;
          case -1:
            //do nothing
            break;
        }
      }
  