package Connection; use strict; use warnings; use QtCore4; use QtGui4; use QtNetwork4; use QtCore4::isa qw( Qt::TcpSocket ); use QtCore4::signals readyForUse => [], newMessage => ['const QString &', 'const QString &']; use QtCore4::slots processReadyRead => [], sendPing => [], sendGreetingMessage => []; my $MaxBufferSize = 1024000; use constant { WaitingForGreeting => 0, ReadingGreeting => 1, ReadyForUse => 2, }; use constant { PlainText => 0, Ping => 1, Pong => 2, Greeting => 3, Undefined => 4, }; sub greetingMessage() { return this->{greetingMessage}; } sub username() { return this->{username}; } sub pingTimer() { return this->{pingTimer}; } sub pongTime() { return this->{pongTime}; } sub buffer() { return this->{buffer}; } sub state() { return this->{state}; } sub currentDataType() { return this->{currentDataType}; } sub numBytesForCurrentDataType() { return this->{numBytesForCurrentDataType}; } sub transferTimerId() { return this->{transferTimerId}; } sub isGreetingMessageSent() { return this->{isGreetingMessageSent}; } my $TransferTimeout = 30 * 1000; my $PongTimeout = 60 * 1000; my $PingInterval = 5 * 1000; my $SeparatorToken = ' '; sub NEW { my ($class, $parent) = @_; $class->SUPER::NEW($parent); this->{greetingMessage} = this->tr('undefined'); this->{username} = this->tr('unknown'); this->{state} = WaitingForGreeting; this->{currentDataType} = Undefined; this->{numBytesForCurrentDataType} = -1; this->{transferTimerId} = undef; this->{isGreetingMessageSent} = 0; this->{buffer} = Qt::ByteArray(); this->{pingTimer} = Qt::Timer(); this->{pongTime} = Qt::Time(); pingTimer->setInterval($PingInterval); Qt::Object::connect(this, SIGNAL 'readyRead()', this, SLOT 'processReadyRead()'); Qt::Object::connect(this, SIGNAL 'disconnected()', pingTimer, SLOT 'stop()'); Qt::Object::connect(pingTimer, SIGNAL 'timeout()', this, SLOT 'sendPing()'); Qt::Object::connect(this, SIGNAL 'connected()', this, SLOT 'sendGreetingMessage()'); } sub name { return username; } sub setGreetingMessage { my ($message) = @_; this->{greetingMessage} = $message->constData(); } sub sendMessage { my ($message) = @_; if (!defined $message) { return 0; } utf8::decode($message); my $msg = Qt::ByteArray($message); my $data = Qt::ByteArray('MESSAGE ' . $msg->size() . ' ') + $msg; return this->write($data) == $data->size(); } sub timerEvent { my ($timerEvent) = @_; if ($timerEvent->timerId() == transferTimerId) { abort(); killTimer(transferTimerId); this->{transferTimerId} = undef; } } sub processReadyRead { if (state() == WaitingForGreeting) { if (!readProtocolHeader()) { return; } if (currentDataType() != Greeting) { abort(); return; } this->{state} = ReadingGreeting; } if (state() == ReadingGreeting) { if (!hasEnoughData()) { return; } this->{buffer} = this->read(numBytesForCurrentDataType); if (buffer()->size() != numBytesForCurrentDataType) { abort(); return; } this->{username} = buffer->constData . '@' . peerAddress()->toString() . ':' . peerPort(); this->{currentDataType} = Undefined; this->{numBytesForCurrentDataType} = 0; buffer()->clear(); if (!isValid()) { abort(); return; } if (!isGreetingMessageSent()) { sendGreetingMessage(); } pingTimer()->start(); pongTime()->start(); this->{state} = ReadyForUse; emit readyForUse(); } do { if (currentDataType() == Undefined) { if (!readProtocolHeader()) { return; } } if (!hasEnoughData()) { return; } processData(); } while (bytesAvailable() > 0); } sub sendPing { if (pongTime->elapsed() > $PongTimeout) { abort(); return; } this->write('PING 1 p'); } sub sendGreetingMessage { my $greeting = greetingMessage(); utf8::decode($greeting); my $data = Qt::ByteArray('GREETING ' . length($greeting) . ' ' . $greeting); if (this->write($data) == $data->size()) { this->{isGreetingMessageSent} = 1; } } sub readDataIntoBuffer { my ($maxSize) = @_; $maxSize = $MaxBufferSize if !defined $maxSize; if ($maxSize > $MaxBufferSize) { return 0; } my $numBytesBeforeRead = buffer()->size(); if ($numBytesBeforeRead == $MaxBufferSize) { abort(); return 0; } while (bytesAvailable() > 0 && buffer()->size() < $maxSize) { buffer->append(this->read(1)); if (buffer->endsWith($SeparatorToken)) { last; } } return buffer->size() - $numBytesBeforeRead; } sub dataLengthForCurrentDataType { if (bytesAvailable() <= 0 || readDataIntoBuffer() <= 0 || !buffer()->endsWith($SeparatorToken)) { return 0; } buffer()->chop(1); my $number = buffer()->toInt(); buffer()->clear(); return $number; } sub readProtocolHeader { if (transferTimerId()) { killTimer(transferTimerId); this->{transferTimerId} = undef; } if (readDataIntoBuffer() <= 0) { this->{transferTimerId} = startTimer($TransferTimeout); return 0; } if (buffer() == 'PING ') { this->{currentDataType} = Ping; } elsif (buffer() == 'PONG ') { this->{currentDataType} = Pong; } elsif (buffer() == 'MESSAGE ') { this->{currentDataType} = PlainText; } elsif (buffer() == 'GREETING ') { this->{currentDataType} = Greeting; } else { this->{currentDataType} = Undefined; abort(); return 0; } buffer()->clear(); this->{numBytesForCurrentDataType} = dataLengthForCurrentDataType(); return 1; } sub hasEnoughData { if (transferTimerId()) { Qt::Object::killTimer(transferTimerId); this->{transferTimerId} = undef; } if (numBytesForCurrentDataType() <= 0) { this->{numBytesForCurrentDataType} = dataLengthForCurrentDataType(); } if (bytesAvailable() < numBytesForCurrentDataType() || numBytesForCurrentDataType() <= 0) { this->{transferTimerId} = startTimer($TransferTimeout); return 0; } return 1; } sub processData { this->{buffer} = this->read(numBytesForCurrentDataType()); if (buffer()->size() != numBytesForCurrentDataType()) { abort(); return; } if ( currentDataType() == PlainText ) { my $message = buffer->data(); utf8::decode($message); emit newMessage(username(), $message); } elsif ( currentDataType() == Ping ) { this->write('PONG 1 p'); } elsif ( currentDataType() == Pong ) { pongTime->restart(); } this->{currentDataType} = Undefined; this->{numBytesForCurrentDataType} = 0; buffer()->clear(); } 1;