代码拉取完成,页面将自动刷新
<?php
/**
* (c) 2007-2009 Chris Maciejewski
* PHP SIP UAC class
* @ingroup API
* @author Chris Maciejewski <chris@wima.co.uk>
* modi by limx 李茂祥@2017:
* 修改重点为1、支持SIPINFO;2、添加支持RTP通讯,支持RFC2833 DTMF
*/
class PhpSIPException extends Exception
{
public function __construct($message, $code = 0)
{
parent::__construct($message,$code);
}
}
function print_rbr ($var, $return = false) {
$r = nl2br(htmlspecialchars(print_r($var, true)));
if ($return) return $r;
else echo $r;
}
class PhpSIP
{
private $debug = false;
private $min_port = 44000;
private $max_port = 48000;
/**
* Final Response timer (in seconds)
*/
private $fr_timer = 20;
/**
* Lock file
*/
private $lock_file = __DIR__.'/PhpSIP.lock';
/**
* Allowed methods array
*/
private $allowed_methods = array(
"CANCEL","NOTIFY", "INVITE","BYE","REFER","OPTIONS","INFO","SUBSCRIBE","MESSAGE"
);
/**
* Dialog established
*/
private $dialog = false;
/**
* The opened socket we listen for incoming SIP messages
*/
private $socket;
/**
* The opened socket we listen for incoming RTP messages
*/
private $RTPsocket;
/**
* Source IP address
*/
private $src_ip;
/**
* Source IP address
*/
private $user_agent = 'PHP DTMF';
/**
* CSeq
*/
private $cseq = 20;
/**
* Source port
*/
private $src_port;
/**
* Call ID
*/
private $call_id;
/**
* RTP ID
*/
private $rtp_id = 100;
/**
* Contact
*/
private $contact;
/**
* Request URI
*/
private $uri;
/**
* Request host
*/
private $host;
/**
* Request port
*/
private $port = 43000;
//rtp 端口
private $rtp_port = 43002;
//rtp 目标端口
private $rtp_to;
/**
* Outboud SIP proxy
*/
private $proxy;
/**
* Method
*/
private $method;
/**
* Auth username
*/
private $username;
/**
* Auth password
*/
private $password;
/**
* To
*/
private $to;
/**
* To tag
*/
private $to_tag;
/**
* From
*/
private $from;
/**
* From User
*/
private $from_user;
/**
* From tag
*/
private $from_tag;
/**
* Via tag
*/
private $via;
/**
* Content type
*/
private $content_type;
/**
* Body
*/
private $body;
/**
* 时间戳
*/
private $timestamp;
/**
* Received Response
*/
private $response; // whole response body
private $res_code;
private $res_contact;
private $res_cseq_method;
private $res_cseq_number;
/**
* Received Request
*/
private $req_method;
private $req_cseq_method;
private $req_cseq_number;
private $req_contact;
/**
* Authentication
*/
private $auth;
/**
* Routes
*/
private $routes = array();
/**
* Request vias
*/
private $request_via = array();
/**
* Additional headers
*/
private $extra_headers = array();
/**
* Constructor
*
* @param $src_ip Ip address to bind (optional)
*/
public function __construct($src_ip = null) {
if (!function_exists('socket_create')) {
throw new PhpSIPException("socket_create() function missing.");
}
if (!$src_ip) {
// running in a web server
if (isset($_SERVER['SERVER_ADDR'])) {
$src_ip = $_SERVER['SERVER_ADDR'];
}
// running from command line
else {
$addr = gethostbynamel(php_uname('n'));
if (!is_array($addr) || !isset($addr[0]) || substr($addr[0],0,3) == '127') {
throw new PhpSIPException("Failed to obtain IP address to bind. Please set bind address manualy.");
}
$src_ip = $addr[0];
}
}
$this->src_ip = $src_ip;
if (!$this->lock_file) {
$this->lock_file = rtrim(sys_get_temp_dir(),DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'phpSIP.lock';
}
$this->createSocket();
}
/**
* Destructor
*/
public function __destruct() {
$this->closeSocket();
}
/**
* Get the ResponseCode
*/
public function getCode() {
return $this->res_code;
}
/**
* Sets debuggin ON/OFF
*
* @param bool $status
*/
public function setDebug($status = false) {
$this->debug = $status;
}
/**
* Gets src IP
*
* @return string
*/
public function getSrcIp() {
return $this->src_ip;
}
/**
* Gets port number to bind
*/
private function getPort() {
if ($this->min_port > $this->max_port) {
throw new PhpSIPException ("Min port is bigger than max port.");
}
$fp = @fopen($this->lock_file, 'a+');
if (!$fp) {
throw new PhpSIPException ("Failed to open lock file ".$this->lock_file);
}
$canWrite = flock($fp, LOCK_EX);
if (!$canWrite) {
throw new PhpSIPException ("Failed to lock a file in 1000 ms.");
}
//file was locked
clearstatcache();
$size = filesize($this->lock_file);
if ($size) {
$contents = fread($fp, $size);
$ports = explode(",",$contents);
}
else {
$ports = false;
}
ftruncate($fp, 0);
rewind($fp);
// we are the first one to run, initialize "PID" => "port number" array
if (!$ports) {
if (!fwrite($fp, $this->min_port)) {
throw new PhpSIPException("Fail to write data to a lock file.");
}
$this->src_port = $this->min_port;
$this->rtp_port = $this->min_port + 2;
}
// there are other programs running now
else {
$src_port = null;
for ($i = $this->min_port; $i <= $this->max_port; $i++) {
if (!in_array($i,$ports)) {
$src_port = $i;
break;
}
}
if (!$src_port) {
throw new PhpSIPException("No more ports left to bind.");
}
$ports[] = $src_port;
$ports[] = $src_port + 2;
if (!fwrite($fp, implode(",",$ports))) {
throw new PhpSIPException("Failed to write data to lock file.");
}
$this->src_port = $src_port;
$this->rtp_port = $src_port + 2;
}
if (!fclose($fp)) {
throw new PhpSIPException("Failed to close lock_file");
}
}
/**
* Releases port
*/
private function releasePort() {
$fp = fopen($this->lock_file, 'r+');
if (!$fp) {
throw new PhpSIPException("Can't open lock file.");
}
$canWrite = flock($fp, LOCK_EX);
if (!$canWrite) {
throw new PhpSIPException("Failed to lock a file in 1000 ms.");
}
clearstatcache();
$size = filesize($this->lock_file);
$content = fread($fp,$size);
$ports = explode(",",$content);
$key = array_search($this->src_port,$ports);
if ($key !== false)
unset($ports[$key]);
$key = array_search($this->src_port+1,$ports);
if ($key !== false)
unset($ports[$key]);
if (count($ports) === 0) {
if (!fclose($fp)) {
throw new PhpSIPException("Failed to close lock_file");
}
if (!unlink($this->lock_file)) {
throw new PhpSIPException("Failed to delete lock_file.");
}
} else {
ftruncate($fp, 0);
rewind($fp);
if (!fwrite($fp, implode(",",$ports))) {
throw new PhpSIPException("Failed to save data in lock_file");
}
flock($fp, LOCK_UN);
if (!fclose($fp)) {
throw new PhpSIPException("Failed to close lock_file");
}
}
}
/**
* Adds aditional header
*
* @param string $header
*/
public function addHeader($header) {
$this->extra_headers[] = $header;
}
/**
* Sets From header
*
* @param string $from
*/
public function setFrom($from) {
if (preg_match('/<.*>$/',$from)) {
$this->from = $from;
}
else {
$this->from = '<'.$from.'>';
}
$m = array();
if (!preg_match('/sip:(.*)@/i',$this->from,$m)) {
throw new PhpSIPException('Failed to parse From username.');
}
$this->from_user = $m[1];
}
/**
* Sets method
*
* @param string $method
*/
public function setMethod($method)
{
if (!in_array($method,$this->allowed_methods)) {
throw new PhpSIPException('Invalid method.');
}
$this->method = $method;
if ($method == 'INVITE') {
$body = "v=0\r\n";
$body.= "o=click2dial 0 0 IN IP4 ".$this->src_ip."\r\n";
$body.= "s=click2dial call\r\n";
$body.= "c=IN IP4 ".$this->src_ip."\r\n";
$body.= "t=0 0\r\n";
$body.= "m=audio ".$this->rtp_port." RTP/AVP 0 18 97 98 101\r\n";
$body.= "a=rtpmap:0 PCMU/8000\r\n";
$body.= "a=rtpmap:18 G729/8000\r\n";
$body.= "a=rtpmap:97 ilbc/8000\r\n";
$body.= "a=rtpmap:98 speex/8000\r\n";
$body.= "a=rtpmap:101 telephone-event/8000\r\n";
$body.= "a=fmtp:101 0-15\r\n";
$this->body = $body;
$this->setContentType(null);
}
if ($method == 'REFER') {
$this->setBody('');
}
if ($method == 'CANCEL') {
$this->setBody('');
$this->setContentType(null);
}
if ($method == 'MESSAGE' || $method == 'INFO') {
$this->setContentType(null);
}
}
/**
* Sets SIP Proxy
*
* @param $proxy
*/
public function setProxy($proxy)
{
$this->proxy = $proxy;
}
/**
* Sets request URI
*
* @param string $uri
*/
public function setUri($uri) {
if (strpos($uri,'sip:') === false) {
throw new PhpSIPException("Only sip: URI supported.");
}
if (strpos($uri,'transport=tcp') !== false) {
throw new PhpSIPException("Only UDP transport supported.");
}
$this->uri = $uri;
$this->to = '<'.$uri.'>';
if ($this->proxy) {
if (strpos($this->proxy,':')) {
$temp = explode(":",$this->proxy);
$this->host = $temp[0];
$this->port = $temp[1];
}
else {
$this->host = $this->proxy;
}
}
else {
$uri = ($t_pos = strpos($uri,";")) ? substr($uri,0,$t_pos) : $uri;
$url = str_replace("sip:","sip://",$uri);
if (!$url = @parse_url($url)) {
throw new PhpSIPException("Failed to parse URI '$url'.");
}
$this->host = $url['host'];
if (isset($url['port'])) {
$this->port = $url['port'];
}
}
}
/**
* Sets username
*
* @param string $username
*/
public function setUsername($username) {
$this->username = $username;
}
/**
* Sets User Agent
*
* @param string $user_agent
*/
public function setUserAgent($user_agent) {
$this->user_agent = $user_agent;
}
/**
* 发送DTMF RTP数据包,
*/
public function sendRTPDTMF($key){
switch (strtolower($key)){
case "0": $event = "0".dechex(0); break;
case "1": $event = "0".dechex(1); break;
case "2": $event = "0".dechex(2); break;
case "3": $event = "0".dechex(3); break;
case "4": $event = "0".dechex(4); break;
case "5": $event = "0".dechex(5); break;
case "6": $event = "0".dechex(6); break;
case "7": $event = "0".dechex(7); break;
case "8": $event = "0".dechex(8); break;
case "9": $event = "0".dechex(9); break;
case "*": $event = "0".dechex(10); break;
case "#": $event = "0".dechex(11); break;
case "a": $event = "0".dechex(12); break;
case "b": $event = "0".dechex(13); break;
case "c": $event = "0".dechex(14); break;
case "d": $event = "0".dechex(15); break;
case "flash": $event = dechex(16); break;
default:return;
}
/*
* V:2位,RTP版本号。为“10”。
* P:1位,填充指示位。为“1”时表示分组结尾含有1个或多个填充字节。
* X:1位,扩展指示位。 X为“1”时,则表示固定头部后还有一个扩展头部,这种情况较复杂,很少使用。
* CC:4位,CSRC计数。指示固定头部后的CSRC的个数
* M: 1位,由应用文档解释,通常不用。
* PT:7位,净荷类型 表示RTP分组的净荷类型。常用的有:“0”:G.711u “8”:G.711A “4”:G.723.1 “18”:G.729 “96”:RFC2833
* 序号:16位,表示RTP分组的次序。初值为随机数,每发送一个增加1。可供接收方检测分组丢失和恢复分组次序。
* 时戳:32位,表示RTP分组第一个字节的取样时刻。其初值为随机数,每个采用周期加1。如每次传送20ms数据,音频采样频率8000Hz,即每20ms160次采样,则每传送20ms的数据,时戳增加160。
* SSRC:32位,同步源标识(Synchronous Source)表示信号的同步源,其值应随机选择,以保证同一个RTP会话中任意两个同步源的SSRC标识不同。
* CSRC:0或多个32位,分信源标识(Contributing Source) 由混合器插入,其值是组成复合信号的各个分信号的SSRC标识,以标识各个组成分信号的信源。RTP分组的头部最多可以包含15个CSRC标识,其数目由CC字段指明。
* events: 事件号,8位,用于说明本数据包的事件。RFC2833除了传送DTMF信号外还能传送传真,调制解调器,MF信号等。
* volume: 音量,6位,用于说明DTMF信号的音频功率级,范围从(0~ -63dbm)。
* E:结束位,1位,设置为1表明数据包中含有事件的结束。通过duration参数即可测定事件的完整宽度。
* R:1位,为以后使用而保留。发送方必须将它设为0,接收端则应忽略它。
* duration:数字信号的宽度,16位,以时戳单元表示。事件从RTP时间戳表示瞬间开始,一直持续到该参数表示的长度。事件可以已经结束也可以没有结束。以8000赫兹取样来说,本字段最长可以表示8秒。
*/
if (empty($this->timestamp))
$this->timestamp = time();
else
$this->timestamp += 160;
$timeHex = dechex($this->timestamp);
$rtpheader = "8065";//指 V 10 P 0 X 0 CC 0000 M 0 PT 1100101 ,PT101(默认用101的多,rfc2833)
$rtpid = str_pad(dechex($this->rtp_id),4,"0",STR_PAD_LEFT);//rtp包序号值,16位
$ssrc = str_pad(dechex(mt_rand()),8,"0",STR_PAD_LEFT);//32位
$preload = '0a00a0'; //指 E 0 R 0 V 001010 Duration 0000000010100000 (160)
$preload_e = '8a00a0';//指 E 1 R 0 V 001010 Duration 0000000010100000 (160)
$char = $rtpheader.$rtpid.$timeHex.$ssrc.$event.$preload;
$out = "";
$sendStrArray = str_split($char, 2);
for ($j = 0; $j < count($sendStrArray); $j++) {
$out .= chr(hexdec($sendStrArray[$j]));
}
$this->sendRTPData($out,$char);
usleep(4000);
//重发----3次--
for ($i=0;$i<4;$i++){
$this->rtp_id ++;//rtp包序号值+1
$rtpid = str_pad(dechex($this->rtp_id),4,"0",STR_PAD_LEFT);//rtp包序号值,16位
$char = $rtpheader.$rtpid.$timeHex.$ssrc.$event.$preload;
$out = "";
$sendStrArray = str_split($char, 2);
for ($j = 0; $j < count($sendStrArray); $j++) {
$out .= chr(hexdec($sendStrArray[$j]));
}
$this->sendRTPData($out,$char);
usleep(4000);
}
//结束---2次---
for ($i=0;$i<2;$i++){
$this->rtp_id ++;//rtp包序号值+1
$rtpid = str_pad(dechex($this->rtp_id),4,"0",STR_PAD_LEFT);//rtp包序号值,16位
$char = $rtpheader.$rtpid.$timeHex.$ssrc.$event.$preload_e;
$out = "";
$sendStrArray = str_split($char, 2);
for ($j = 0; $j < count($sendStrArray); $j++) {
$out .= chr(hexdec($sendStrArray[$j]));
}
$this->sendRTPData($out,$char);
usleep(4000);
}
}
/**
* Sets password
*
* @param string $password
*/
public function setPassword($password) {
$this->password = $password;
}
/**
* Sends SIP request
*
* @return string Reply
*/
public function send()
{
if (!$this->from)
throw new PhpSIPException('Missing From.');
if (!$this->method)
throw new PhpSIPException('Missing Method.');
if (!$this->uri)
throw new PhpSIPException('Missing URI.');
$data = $this->formatRequest();
$this->sendData($data);
$this->readResponse();
if ($this->method == 'CANCEL' && $this->res_code == '200') {
$i = 0;
while (substr($this->res_code,0,1) != '4' && $i < 2) {
$this->readResponse();
$i++;
}
}
if (substr($this->res_code,0,1) == '1') {
$i = 0;
while (substr($this->res_code,0,1) == '1' && $i < 10) {
$this->readResponse();
$i++;
}
}
if ($this->debug) {
echo "<font color=#ff4500>****>-> send {$this->method} >-<-< GetLastResponse: {$this->res_code} <****</font><br/>";
}
if ($this->res_code == '407') {
$this->cseq++;
$this->auth();
$data = $this->formatRequest();
$this->sendData($data);
$this->readResponse();
}
if ($this->res_code == '401') {
$this->cseq++;
$this->authWWW();
$data = $this->formatRequest();
$this->sendData($data);
$this->readResponse();
}
$this->extra_headers = array();
$this->cseq++;
return $this->res_code;
}
/**
* Sends RTPdata
*/
private function sendRTPData($data,$key='') {
try{
if (!@socket_sendto($this->RTPsocket, $data, strlen($data), 0, $this->host, $this->rtp_to)) {
$err_no = socket_last_error($this->RTPsocket);
throw new PhpSIPException("Failed to send data to ".$this->host.":".$this->rtp_to.". ".$err_no);
}
if ($this->debug) {
echo "<font color=#8b4513>>>>>>>>> --> >>>>>>>sendRTPData>>>>>>>>>>start>>><br/>";
if (!empty($key))
$outstr = print_rbr($key,true);
else
$outstr = print_rbr($data,true);
echo $outstr;
echo "------------------------------>sendRTPData>----------------- end>>--</font><br/>";
}
}catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "<br/>";
}
}
/**
* Sends data
*/
private function sendData($data) {
try{
if (!@socket_sendto($this->socket, $data, strlen($data), 0, $this->host, $this->port)) {
$err_no = socket_last_error($this->socket);
throw new PhpSIPException("Failed to send data to ".$this->host.":".$this->port.". ".$err_no);
}
if ($this->debug) {
echo "<font color=blue>>>>>>>>> --> >>>>>>>sendData>>>>>>>>>>start>>><br/>";
$outstr = print_rbr($data,true);
echo $outstr;
echo "---------------------------->sendData>--------------- end>>--</font><br/>";
}
}catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "<br/>";
}
}
/**
* Listen for request, $mode = method or code
*
* @todo This needs to be improved
*/
public function listen($method,$mode='method') {
$i = 0;
if ($mode == 'method'){
while ($this->req_method != $method) {
$this->readResponse();
$i++;
if ($i > 5) {
throw new PhpSIPException("无法获得 ".$this->req_method." 回应.");
}
}
}else{
while ($this->res_code != $method) {
$this->readResponse();
$i++;
if ($i > 5) {
throw new PhpSIPException("无法获得 ".$this->res_code." 回应.");
}
}
}
}
/**
* Reads response
*/
private function readResponse() {
$from = "";
$port = 0;
if (!@socket_recvfrom($this->socket, $this->response, 10000, 0, $from, $port)) {
$this->res_code = "无响应 ".$this->fr_timer." seconds.";
return $this->res_code;
}
if ($this->debug){
echo "<font color=#006400><<<<<<<<<<< --< <<<<<<<<<<<< readResponse<<<<<<<<<<<br/>";
$outstr = print_rbr($this->response,true);
echo $outstr;
echo "----------------------------< readResponse<----------- end<<--</font><br/>";
}
// Response
$result = array();
if (preg_match('/^SIP\/2\.0 ([0-9]{3})/',$this->response,$result)) {
$this->res_code = trim($result[1]);
$res_class = substr($this->res_code,0,1);
if ($res_class == '1' || $res_class == '2')
$this->dialog = true;
$this->parseResponse();
}
// Request
else
$this->parseRequest();
}
/**
* Parse Response
*/
private function parseResponse() {
// To tag
$result = array();
if (preg_match('/^To: .*;tag=(.*)$/im',$this->response,$result)) {
$this->to_tag = trim($result[1]);
}
// Route
$result = array();
if (preg_match_all('/^Record-Route: (.*)$/im',$this->response,$result)) {
foreach ($result[1] as $route) {
if (!in_array(trim($route),$this->routes)) {
$this->routes[] = trim($route);
}
}
}
// Request via
$result = array();
$this->request_via = array();
if (preg_match_all('/^Via: (.*)$/im',$this->response,$result)) {
foreach ($result[1] as $via) {
$this->request_via[] = trim($via);
}
}
// Response contact
$result = array();
if (preg_match('/^Contact:.*<(.*)>/im',$this->response,$result)) {
$this->res_contact = trim($result[1]);
$semicolon = strpos($this->res_contact,";");
if ($semicolon !== false) {
$this->res_contact = substr($this->res_contact,0,$semicolon);
}
}
// Response CSeq method
$result = array();
if (preg_match('/^CSeq: [0-9]+ (.*)$/im',$this->response,$result)) {
$this->res_cseq_method = trim($result[1]);
}
// Response RTP udp port
$result = array();
if (preg_match('/^m=audio ([0-9]+) RTP\/AVP.*$/im',$this->response,$result)) {
$this->rtp_to = trim($result[1]);
}
// ACK 2XX-6XX - only invites - RFC3261 17.1.2.1
if ($this->res_cseq_method == 'INVITE' && in_array(substr($this->res_code,0,1),array('2','3','4','5','6'))) {
$this->ack();
}
return $this->res_code;
}
/**
* Parse Request
*/
private function parseRequest()
{
$temp = explode("\r\n",$this->response);
$temp = explode(" ",$temp[0]);
$this->req_method = trim($temp[0]);
// Route
$result = array();
if (preg_match_all('/^Record-Route: (.*)$/im',$this->response,$result)) {
foreach ($result[1] as $route) {
if (!in_array(trim($route),$this->routes)) {
$this->routes[] = trim($route);
}
}
}
// Request via
$result = array();
$this->request_via = array();
if (preg_match_all('/^Via: (.*)$/im',$this->response,$result)) {
foreach ($result[1] as $via) {
$this->request_via[] = trim($via);
}
}
// Method contact
$result = array();
if (preg_match('/^Contact: <(.*)>/im',$this->response,$result)) {
$this->req_contact = trim($result[1]);
$semicolon = strpos($this->res_contact,";");
if ($semicolon !== false) {
$this->res_contact = substr($this->res_contact,0,$semicolon);
}
}
// Response CSeq method
if (preg_match('/^CSeq: [0-9]+ (.*)$/im',$this->response,$result)) {
$this->req_cseq_method = trim($result[1]);
}
// Response CSeq number
if (preg_match('/^CSeq: ([0-9]+) .*$/im',$this->response,$result)) {
$this->req_cseq_number = trim($result[1]);
}
}
/**
* Send Response
*
* @param int $code Response code
* @param string $text Response text
*/
public function reply($code,$text) {
$r = 'SIP/2.0 '.$code.' '.$text."\r\n";
// Via
foreach ($this->request_via as $via)
{
$r.= 'Via: '.$via."\r\n";
}
// From
$r.= 'From: '.$this->from.';tag='.$this->to_tag."\r\n";
// To
$r.= 'To: '.$this->to.';tag='.$this->from_tag."\r\n";
// Call-ID
$r.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
$r.= 'CSeq: '.$this->req_cseq_number.' '.$this->req_cseq_method."\r\n";
// Max-Forwards
$r.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$r.= 'User-Agent: '.$this->user_agent."\r\n";
// Content-Length
$r.= 'Content-Length: 0'."\r\n";
$r.= "\r\n";
$this->sendData($r);
}
/**
* ACK
*/
private function ack()
{
if ($this->res_cseq_method == 'INVITE' && $this->res_code == '200') {
$a = 'ACK '.$this->res_contact.' SIP/2.0'."\r\n";
}
else {
$a = 'ACK '.$this->uri.' SIP/2.0'."\r\n";
}
// Via
$a.= 'Via: '.$this->via."\r\n";
// Route
if ($this->routes) {
foreach ($this->routes as $route) {
$a.= 'Route: '.$route."\r\n";
}
}
// From
if (!$this->from_tag) $this->setFromTag();
$a.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n";
// To
if ($this->to_tag)
$a.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n";
else
$a.= 'To: '.$this->to."\r\n";
// Call-ID
if (!$this->call_id) $this->setCallId();
$a.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
$a.= 'CSeq: '.$this->cseq.' ACK'."\r\n";
// Authentication
if ($this->res_code == '200' && $this->auth) {
$a.= 'Proxy-Authorization: '.$this->auth."\r\n";
}
// Max-Forwards
$a.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$a.= 'User-Agent: '.$this->user_agent."\r\n";
// Content-Length
$a.= 'Content-Length: 0'."\r\n";
$a.= "\r\n";
$this->sendData($a);
}
/**
* INFO
*/
public function info($info='',$cmd='INFO')
{
$a = $cmd.' '.$this->uri.' SIP/2.0'."\r\n";
// Via
$a.= 'Via: '.$this->via."\r\n";
// Route
if ($this->routes) {
foreach ($this->routes as $route) {
$a.= 'Route: '.$route."\r\n";
}
}
// From
if (!$this->from_tag) $this->setFromTag();
$a.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n";
// To
if ($this->to_tag)
$a.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n";
else
$a.= 'To: '.$this->to."\r\n";
// Call-ID
if (!$this->call_id) $this->setCallId();
$a.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
$a.= 'CSeq: '.$this->cseq." $cmd\r\n";
// Authentication
if ($this->res_code == '200' && $this->auth) {
$a.= 'Proxy-Authorization: '.$this->auth."\r\n";
}
if($cmd=='INFO')
$a.= 'Content-Type: application/dtmf-relay'."\r\n";
// Max-Forwards
$a.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$a.= 'User-Agent: '.$this->user_agent."\r\n";
// Content-Length
if ($info){
$length = strlen($info);
$a.=$info;
$a.= 'Content-Length: '.$length."\r\n";
}else
$a.= 'Content-Length: 0'."\r\n";
$a.= "\r\n";
$this->sendData($a);
$this->readResponse();
if ($this->res_code=='200')
$this->cseq++;
}
/**
* Formats SIP request
* @return string
*/
private function formatRequest()
{
if (in_array($this->method,array('BYE','REFER','SUBSCRIBE'))) {
$r = $this->method.' '.$this->res_contact.' SIP/2.0'."\r\n";
}
else {
$r = $this->method.' '.$this->uri.' SIP/2.0'."\r\n";
}
// Via
if ($this->method != 'CANCEL') {
$this->setVia();
}
$r.= 'Via: '.$this->via."\r\n";
// Route
if ($this->method != 'CANCEL' && $this->routes) {
foreach ($this->routes as $route) {
$r.= 'Route: '.$route."\r\n";
}
}
// From
if (!$this->from_tag) $this->setFromTag();
$r.= 'From: '.$this->from.';tag='.$this->from_tag."\r\n";
// To
if ($this->dialog && !in_array($this->method,array("INVITE","CANCEL","NOTIFY")) && $this->to_tag)
$r.= 'To: '.$this->to.';tag='.$this->to_tag."\r\n";
else
$r.= 'To: '.$this->to."\r\n";
// Authentication
if ($this->auth) {
$r.= $this->auth."\r\n";
$this->auth = null;
}
// Call-ID
if (!$this->call_id) $this->setCallId();
$r.= 'Call-ID: '.$this->call_id."\r\n";
//CSeq
if ($this->method == 'CANCEL') {
$this->cseq--;
}
$r.= 'CSeq: '.$this->cseq.' '.$this->method."\r\n";
// Contact
if ($this->method != 'MESSAGE') {
$r.= 'Contact: <sip:'.$this->from_user.'@'.$this->src_ip.':'.$this->src_port.'>'."\r\n";
}
// Content-Type
if ($this->content_type) {
$r.= 'Content-Type: '.$this->content_type."\r\n";
}
// Max-Forwards
$r.= 'Max-Forwards: 70'."\r\n";
// User-Agent
$r.= 'User-Agent: '.$this->user_agent."\r\n";
// Additional header
foreach ($this->extra_headers as $header) {
$r.= $header."\r\n";
}
// Content-Length
$r.= 'Content-Length: '.strlen($this->body)."\r\n";
$r.= "\r\n";
$r.= $this->body;
return $r;
}
/**
* Sets body
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Sets Content Type
*/
public function setContentType($content_type = null)
{
if ($content_type !== null)
{
$this->content_type = $content_type;
}
else
{
switch ($this->method)
{
case 'INVITE':
$this->content_type = 'application/sdp';
break;
case 'MESSAGE':
$this->content_type = 'text/html; charset=utf-8';
break;
case 'INFO':
$this->content_type = 'application/dtmf-relay';
break;
default:
$this->content_type = null;
}
}
}
/**
* Sets Via header
*/
private function setVia()
{
$rand = rand(100000,999999);
$this->via = 'SIP/2.0/UDP '.$this->src_ip.':'.$this->src_port.';rport;branch=z9hG4bK'.$rand;
}
/**
* Sets from tag
*/
private function setFromTag()
{
$this->from_tag = rand(10000,99999);
}
/**
* Sets call id
*/
private function setCallId()
{
$this->call_id = md5(uniqid()).'@'.$this->src_ip;
}
/**
* Gets value of the header from the previous request
*
* @param string $name Header name
*
* @return string or false
*/
public function getHeader($name)
{
if (preg_match('/^'.$name.': (.*)$/m',$this->response,$result))
{
return trim($result[1]);
}
else
{
return false;
}
}
/**
* Calculates Digest authentication response
*
*/
private function auth()
{
if (!$this->username){
throw new PhpSIPException("Missing username");
}
if (!$this->password){
throw new PhpSIPException("Missing password");
}
// realm
$result = array();
if (!preg_match('/^Proxy-Authenticate: .* realm="(.*)"/imU',$this->response, $result)) {
throw new PhpSIPException("Can't find realm in proxy-auth");
}
$realm = $result[1];
// nonce
$result = array();
if (!preg_match('/^Proxy-Authenticate: .* nonce="(.*)"/imU',$this->response, $result)) {
throw new PhpSIPException("Can't find nonce in proxy-auth");
}
$nonce = $result[1];
$ha1 = md5($this->username.':'.$realm.':'.$this->password);
$ha2 = md5($this->method.':'.$this->uri);
$res = md5($ha1.':'.$nonce.':'.$ha2);
$this->auth = 'Proxy-Authorization: Digest username="'.$this->username.'", realm="'.$realm.'", nonce="'.$nonce.'", uri="'.$this->uri.'", response="'.$res.'", algorithm=MD5';
}
/**
* Calculates WWW authorization response
*
*/
private function authWWW()
{
if (!$this->username)
{
throw new PhpSIPException("Missing auth username");
}
if (!$this->password)
{
throw new PhpSIPException("Missing auth password");
}
$qop_present = false;
if (strpos($this->response,'qop=') !== false)
{
$qop_present = true;
// we can only do qop="auth"
if (strpos($this->response,'qop="auth"') === false)
{
throw new PhpSIPException('Only qop="auth" digest authentication supported.');
}
}
// realm
$result = array();
if (!preg_match('/^WWW-Authenticate: .* realm="(.*)"/imU',$this->response, $result))
{
throw new PhpSIPException("Can't find realm in www-auth");
}
$realm = $result[1];
// nonce
$result = array();
if (!preg_match('/^WWW-Authenticate: .* nonce="(.*)"/imU',$this->response, $result))
{
throw new PhpSIPException("Can't find nonce in www-auth");
}
$nonce = $result[1];
$ha1 = md5($this->username.':'.$realm.':'.$this->password);
$ha2 = md5($this->method.':'.$this->uri);
if ($qop_present)
{
$cnonce = md5(time());
$res = md5($ha1.':'.$nonce.':00000001:'.$cnonce.':auth:'.$ha2);
}
else
{
$res = md5($ha1.':'.$nonce.':'.$ha2);
}
$this->auth = 'Authorization: Digest username="'.$this->username.'", realm="'.$realm.'", nonce="'.$nonce.'", uri="'.$this->uri.'", response="'.$res.'", algorithm=MD5';
if ($qop_present)
{
$this->auth.= ', qop="auth", nc="00000001", cnonce="'.$cnonce.'"';
}
}
/**
* Create network socket
* @return bool True on success
*/
private function createSocket()
{
$this->getPort();
if (!$this->src_ip) {
throw new PhpSIPException("Source IP not defined.");
}
if (!$this->socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) {
$err_no = socket_last_error($this->socket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!@socket_bind($this->socket, $this->src_ip, $this->src_port)) {
$err_no = socket_last_error($this->socket);
throw new PhpSIPException ("Failed to bind ".$this->src_ip.":".$this->src_port." ".socket_strerror($err_no));
}
if (!@socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>$this->fr_timer,"usec"=>0))) {
$err_no = socket_last_error($this->socket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!@socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>5,"usec"=>0))) {
$err_no = socket_last_error($this->socket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!$this->RTPsocket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)) {
$err_no = socket_last_error($this->RTPsocket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!@socket_bind($this->RTPsocket, $this->src_ip, $this->rtp_port)) {
$err_no = socket_last_error($this->RTPsocket);
throw new PhpSIPException ("Failed to bind ".$this->src_ip.":".$this->rtp_port." ".socket_strerror($err_no));
}
if (!@socket_set_option($this->RTPsocket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>$this->fr_timer,"usec"=>0))) {
$err_no = socket_last_error($this->RTPsocket);
throw new PhpSIPException (socket_strerror($err_no));
}
if (!@socket_set_option($this->RTPsocket, SOL_SOCKET, SO_SNDTIMEO, array("sec"=>5,"usec"=>0))) {
$err_no = socket_last_error($this->RTPsocket);
throw new PhpSIPException (socket_strerror($err_no));
}
}
/**
* Close the connection
* @return bool True on success
*/
private function closeSocket()
{
socket_close($this->socket);
socket_close($this->RTPsocket);
$this->releasePort();
}
/**
* Resets callid, to/from tags etc.
*
*/
public function newCall()
{
$this->cseq = 20;
$this->call_id = null;
$this->to_tag = null;;
$this->from_tag = null;;
/**
* Body
*/
$this->body = null;
/**
* Received Response
*/
$this->response = null;
$this->res_code = null;
$this->res_contact = null;
$this->res_cseq_method = null;
$this->res_cseq_number = null;
/**
* Received Request
*/
$this->req_method = null;
$this->req_cseq_method = null;
$this->req_cseq_number = null;
$this->req_contact = null;
$this->routes = array();
$this->request_via = array();
}
}
?>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。