* @revision 04 * @license http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons Attribution-Share Alike 3.0 Germany * @homepage http://oss.tiggerswelt.net/oscar/ * @copyright Copyright © 2009 tiggersWelt.net */ require_once ("oscar/common.php"); require_once ("oscar/snac.php"); class Oscar_FLAP { /* Frame-Types */ const FRAME_SIGNON = 0x01; const FRAME_DATA = 0x02; const FRAME_ERROR = 0x03; const FRAME_SIGNOFF = 0x04; const FRAME_KEEPALIVE = 0x05; /* General FLAP-Settings */ public $ID = 0x2A; public $Channel = 0; public $Sequence = 0; public $Size = 0; public $Data = ""; protected $Parent = null; /* Version of this stream */ private $Version = -1; /* Socket to OSCAR-Server */ private $Socket = null; private $lastFLAP = 0; /* Packet-Queue */ private $Packets = array (); /* Internal Sequence counter */ private $flapsSent = 0; private $SNACCount = 0; /* Suspend-Information */ private $Suspended = false; private $SuspendFamilies = array (); private $sentVersion = false; private $recvVersion = false; // {{{ __construct public function __construct (&$Socket = null, $Channel = 0, $Parent = null) { $this->Parent = $Parent; $this->sentVersion = false; $this->setSocket ($Socket); $this->setChannel ($Channel); $this->reset (); } // }}} // {{{ reset public function reset () { $this->lastFLAP = time (); $this->flapsSent = rand (0, 0x2000); } // }}} public function setSocket ($Socket) { $this->Socket = $Socket; } // {{{ setChannel /** * Set Channel of this FLAP * * @access public * @return bool */ public function setChannel ($Channel) { switch ($Channel) { case self::FRAME_SIGNON: case self::FRAME_DATA: case self::FRAME_ERROR: case self::FRAME_SIGNOFF: case self::FRAME_KEEPALIVE: $this->Channel = $Channel; return true; default: return false; } } // }}} // {{{ readFLAP /** * Read a FLAP-Object from stream * * @param resource $Socket Socket to read data from * * @access public * @return object */ public static function readFLAP (&$Socket, &$Parent = null) { // Create new Object $Packet = new Oscar_FLAP ($Socket, 0, $Parent); // Try to read data from stream $Packet->read (); // Return the packet return $Packet; } // }}} // {{{ read /** * Read a new FLAP-Packet from stream * * @param bool $Peek (optional) Just try to peek data from stream (don't block) * * @access public * @return bool */ public function read ($Peek = false) { // Check our socket if (!is_resource ($this->Socket) || feof ($this->Socket)) return false; // Try to get data from socket $N = null; $Wait = ($Peek ? 0 : 5); do { $Sockets = array ($this->Socket); if (stream_select ($Sockets, $N, $N, $Wait, 12) < 1) { if ($Peek) return null; usleep (125); } } while (!$Peek); while ($c = fread ($this->Socket, 1)) if ($c == "*") break; else print "Discard: $c\n"; if (!$c) { trigger_error ("FLAP: Read 0x42 failed", E_USER_WARNING); return false; } $Data = $c . fread ($this->Socket, 5); // Occupy Object with values $this->ID = OSCAR_Common::str2int8 ($Data, 0); $this->Channel = OSCAR_Common::str2int8 ($Data, 1); $this->Sequence = OSCAR_Common::str2int16 ($Data, 2); $this->Size = OSCAR_Common::str2int16 ($Data, 4); $this->Data = ""; // Read data of packet if ($this->Size > 0) while (($l = strlen ($this->Data)) < $this->Size) { $this->Data .= fread ($this->Socket, $this->Size - $l); usleep (12); } // Some debugging if (defined ("OSCAR_DEBUG_IN") && (OSCAR_DEBUG_IN == 1)) print "Received Packet\n"; if (defined ("OSCAR_DEBUG_IN_VERBOSE") && (OSCAR_DEBUG_IN_VERBOSE == 1)) self::debugPacket ($Data . $this->Data, "S: "); if (strlen ($this->Data) != $this->Size) print "WARNING: Size differs " . strlen ($this->Data) . " / " . $this->Size . "\n\n"; // This seems to be an "ACK" # TODO: This is FLAP-Version! if (($this->Channel == self::FRAME_SIGNON) && ($this->Length = 4) && (OSCAR_Common::str2int32 ($this->Data) == 0x01)) #return self::read ($Peek); return null; return true; } // }}} // {{{ getSNAC /** * Parse SNAC from this Packet * * @access public * @return object */ public function getSNAC () { // We must be on channel "SNAC" if ($this->Channel != self::FRAME_DATA) return false; // Parse the SNAC return Oscar_SNAC::parseSNAC ($this->Data, $this->Parent); } // }}} // {{{ generate /** * Generate a transmission-string from object * * @access public * @return string */ public function generate () { $this->Sequence = ++$this->flapsSent; return OSCAR_Common::int8tostr ($this->ID) . OSCAR_Common::int8tostr ($this->Channel) . OSCAR_Common::int16tostr ($this->Sequence) . OSCAR_Common::int16tostr (strlen ($this->Data)) . $this->Data; } // }}} // {{{ queue /** * Append packet to our internal write-chain * * @param object $Packet * * @access public * @return void */ public function queue ($Packet) { $this->Packets [] = $Packet; } // }}} // {{{ write /** * Generate FLAP-Packet and write to stream * * @access public * @return bool */ public function write () { if (!is_resource ($this->Socket)) { trigger_error ("FLAP: Don't write to socket, because we are disconnected", E_USER_WARNING); return false; } // Check if we are suspended if (($this->Channel == self::FRAME_DATA) && $this->Suspended) { if (count ($this->SuspendFamilies) == 0) return null; foreach ($this->Packets as $ID=>$Packet) if (in_array ($Packet->ServiceID, $this->SuspendFamilies)) return null; } // Do some debugging if (defined ("OSCAR_DEBUG_OUT") && (OSCAR_DEBUG_OUT == 1)) print "Writing packet to stream\n"; if ($this->Channel == self::FRAME_DATA) $this->Data = ""; // Check wheter to submit Protocol-Version if (!$this->sentVersion) { $this->Data = OSCAR_Common::int32tostr (0x00000001) . $this->Data; $this->sentVersion = true; } // Append packets from our chain foreach ($this->Packets as $ID=>$Packet) { // Set Sequence-Number on SNAC-Packet $Packet->RequestID = ++$this->SNACCount; // Generate stream.data $this->Data .= $Packet->generate (); // Do some debugging if (defined ("OSCAR_DEBUG_OUT") && (OSCAR_DEBUG_OUT == 1) && ($Packet instanceof Oscar_SNAC)) print " SNAC 0x" . dechex ($Packet->ServiceID) . " / 0x" . dechex ($Packet->SubtypeID) . " / " . get_class ($Packet) . "\n\n"; unset ($this->Packets [$ID], $Packet); } // Write to stream if (@fwrite ($this->Socket, $buf = self::generate ()) == false) { $this->reset (); $this->Socket = null; // Tell our parent that we lost our connection if (is_object ($this->Parent)) $this->Parent->clientDisconnected (); return false; } $this->lastFLAP = time (); // Print some debug.info if (defined ("OSCAR_DEBUG_OUT_VERBOSE") && (OSCAR_DEBUG_OUT_VERBOSE == 1)) self::debugPacket ($buf, "C: "); return true; } // }}} // {{{ logoff /** * Close the connection * * @access public * @return void */ public function logoff () { $this->Channel = self::FRAME_SIGNOFF; $this->Data = ""; if (is_resource ($this->Socket)) { $this->write (); @fclose ($this->Socket); $this->Socket = null; } $this->reset (); } // }}} // {{{ keepAlive /** * Send keep-alive signals * * @access public * @return void */ public function keepAlive () { // Don't flood keepalives if (time () - $this->lastFLAP < 90) return; $this->Channel = self::FRAME_KEEPALIVE; $this->Data = ""; $this->write (); } // }}} // {{{ suspend /** * Suspend this class * * @param array $Families (optional) Which families to suspend * @param bool $SendACK (optional) Send ACK for Suspend to server (default) * * @access public * @return void */ public function suspend ($Families = array (), $SendACK = true) { // Send ACK-Packet if ($SendACK) { $SNAC = new Oscar_SNAC_Service_Server_Pause_ACK ($this->Parent, $Families); $SNAC->writeFLAP (); } // Suspend this stream $this->Suspended = true; $this->SuspendFamilies = $Families; } // }}} // {{{ resume /** * Resume handling of packets * * @param array $Families (optional) Which families to resume * * @access public * @return void */ public function resume ($Families = array ()) { if ((count ($Families) > 0) && (count ($this->SuspendFamilies) > 0)) { foreach ($Families as $Family) foreach ($this->SuspendFamilies as $ID=>$SFamily) if ($Family == $SFamily) unset ($$this->SuspendFamilies [$ID]); $this->Suspended = (count ($this->SuspendFamilies) != 0); } else $this->Suspended = false; } // }}} // {{{ debugPacket /** * Dump contents of a packet * * @param string $Data * * @access public * @return void */ public function debugPacket ($Data, $Prefix = "", $f = STDOUT) { $p = 0; $w = 16; $l = strlen ($Data); while ($p < $l) { fwrite ($f, $Prefix); for ($i = $p; $i < min ($l, $p + $w); $i++) { $c = strtoupper (dechex ($v = ord ($Data [$i]))); fwrite ($f, ($v < 16 ? "0" : "") . $c . " "); } fwrite ($f, "\n"); $p += $w; } fwrite ($f, "\n"); } // }}} } ?>