[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Gnash-commit] /srv/bzr/gnash/trunk r12011: Fix XMLSocket mess up.
From: |
Benjamin Wolsey |
Subject: |
[Gnash-commit] /srv/bzr/gnash/trunk r12011: Fix XMLSocket mess up. |
Date: |
Wed, 10 Mar 2010 20:21:04 +0100 |
User-agent: |
Bazaar (2.0.3) |
------------------------------------------------------------
revno: 12011 [merge]
committer: Benjamin Wolsey <address@hidden>
branch nick: trunk
timestamp: Wed 2010-03-10 20:21:04 +0100
message:
Fix XMLSocket mess up.
Use non-blocking sockets for improved compatibility. Also fixes bug #28638 now
networking is done properly.
Implement XMLSocket.onClose.
Make the XMLSocket test automatic so no one else can break it.
Adapt RTMP to use the non-blocking interface. There are minor but
important changes to the way the RTMP class behaves. These are reflected
in utilities/rtmpget.cpp.
removed:
testsuite/misc-ming.all/XMLSocketTest.c
added:
testsuite/misc-ming.all/XMLSocketTest.as
testsuite/misc-ming.all/XMLSocketTester.sh
modified:
libbase/RTMP.cpp
libbase/RTMP.h
libbase/Socket.cpp
libbase/Socket.h
libcore/asobj/flash/net/XMLSocket_as.cpp
testsuite/XmlSocketServer.pl
testsuite/misc-ming.all/Makefile.am
utilities/rtmpget.cpp
=== modified file 'libbase/RTMP.cpp'
--- a/libbase/RTMP.cpp 2010-02-24 18:00:03 +0000
+++ b/libbase/RTMP.cpp 2010-03-10 13:37:51 +0000
@@ -79,6 +79,45 @@
}
+/// A utility functor for carrying out the handshake.
+class HandShaker
+{
+public:
+
+ static const int sigSize = 1536;
+
+ HandShaker(Socket& s);
+
+ /// Calls the next stage in the handshake process.
+ void call();
+
+ bool success() const {
+ return _complete;
+ }
+
+ bool error() const {
+ return _error || _socket.bad();
+ }
+
+private:
+
+ /// These are the stages of the handshake.
+ //
+ /// If the socket is not ready, they will return false. If the socket
+ /// is in error, they will set _error.
+ bool stage0();
+ bool stage1();
+ bool stage2();
+ bool stage3();
+
+ Socket _socket;
+ std::vector<boost::uint8_t> _sendBuf;
+ std::vector<boost::uint8_t> _recvBuf;
+ bool _error;
+ bool _complete;
+ size_t _stage;
+};
+
RTMPPacket::RTMPPacket(size_t reserve)
:
header(),
@@ -106,7 +145,13 @@
_bytesInSent(0),
_serverBandwidth(2500000),
_bandwidth(2500000),
- _outChunkSize(RTMP_DEFAULT_CHUNKSIZE)
+ _outChunkSize(RTMP_DEFAULT_CHUNKSIZE),
+ _connected(false),
+ _error(false)
+{
+}
+
+RTMP::~RTMP()
{
}
@@ -133,12 +178,6 @@
return stored;
}
-bool
-RTMP::connected() const
-{
- return _socket.connected();
-}
-
void
RTMP::setBufferTime(size_t size, int streamID)
{
@@ -161,18 +200,29 @@
{
log_debug("Connecting to %s", url.str());
+ const std::string& hostname = url.hostname();
+ const std::string& p = url.port();
+
+ // Default port.
+ boost::uint16_t port = 1935;
+ if (!p.empty()) {
+ try {
+ port = boost::lexical_cast<boost::uint16_t>(p);
+ }
+ catch (boost::bad_lexical_cast&) {}
+ }
+
// Basic connection attempt.
- if (!_socket.connect(url)) {
+ if (!_socket.connect(hostname, port)) {
log_error("Initial connection failed");
return false;
}
- if (!handShake()) {
- log_error( "handshake failed.");
- close();
- return false;
- }
-
+ _handShaker.reset(new HandShaker(_socket));
+
+ // Start handshake attempt immediately.
+ _handShaker->call();
+
return true;
}
@@ -180,18 +230,50 @@
RTMP::update()
{
if (!connected()) {
- log_debug("Not connected!");
- return;
+ _handShaker->call();
+ if (_handShaker->error()) {
+ _error = true;
+ }
+ if (!_handShaker->success()) return;
+ _connected = true;
}
const size_t reads = 10;
for (size_t i = 0; i < reads; ++i) {
+ /// No need to continue reading (though it should do no harm).
+ if (error()) return;
+
RTMPPacket p;
- readPacket(p);
-
+
+ // If we haven't finished reading a packet, retrieve it; otherwise
+ // use an empty one.
+ if (_incompletePacket.get()) {
+ log_debug("Doing incomplete packet");
+ p = *_incompletePacket;
+ _incompletePacket.reset();
+ }
+ else {
+ if (!readPacketHeader(p)) continue;
+ }
+
+ // Get the payload if possible.
+ if (hasPayload(p) && !readPacketPayload(p)) {
+ // If the payload is not completely readable, store it and
+ // continue.
+ _incompletePacket.reset(new RTMPPacket(p));
+ continue;
+ }
+
+ // Store a copy of the packet for later additions and as a reference
for
+ // future sends.
+ RTMPPacket& stored = storePacket(CHANNELS_IN, p.header.channel, p);
+
+ // If the packet is complete, the stored packet no longer needs to
+ // keep the data alive.
if (isReady(p)) {
+ clearPayload(stored);
handlePacket(p);
return;
}
@@ -275,28 +357,29 @@
int
RTMP::readSocket(boost::uint8_t* buffer, int n)
{
- int toRead = n;
- while (toRead) {
- const std::streamsize bytesRead = _socket.read(buffer, toRead);
-
- if (bytesRead < 0) return -1;
-
- if (!bytesRead) {
- return -1;
- }
-
- _bytesIn += bytesRead;
- toRead -= bytesRead;
-
- // Report bytes recieved every time we reach half the bandwidth.
- // Doesn't seem very likely to be the way the pp does it.
- if (_bytesIn > _bytesInSent + _bandwidth / 2) {
- sendBytesReceived(this);
- log_debug("Sent bytes received");
- }
- buffer += bytesRead;
- }
- return n - toRead;
+
+ assert(n >= 0);
+
+ const std::streamsize bytesRead = _socket.read(buffer, n);
+
+ if (_socket.bad()) {
+ _error = true;
+ return 0;
+ }
+
+ if (!bytesRead) return 0;
+
+ _bytesIn += bytesRead;
+
+ // Report bytes recieved every time we reach half the bandwidth.
+ // Doesn't seem very likely to be the way the pp does it.
+ if (_bytesIn > _bytesInSent + _bandwidth / 2) {
+ sendBytesReceived(this);
+ log_debug("Sent bytes received");
+ }
+
+ buffer += bytesRead;
+ return bytesRead;
}
void
@@ -322,7 +405,7 @@
/// It seems as if new packets can add to the data of old ones if they have
/// a minimal, small header.
bool
-RTMP::readPacket(RTMPPacket& packet)
+RTMP::readPacketHeader(RTMPPacket& packet)
{
RTMPHeader& hr = packet.header;
@@ -330,8 +413,8 @@
boost::uint8_t hbuf[RTMPHeader::headerSize] = { 0 };
boost::uint8_t* header = hbuf;
+ // The first read may fail, but otherwise we expect a complete header.
if (readSocket(hbuf, 1) == 0) {
- log_error( "%s, failed to read RTMP packet header", __FUNCTION__);
return false;
}
@@ -457,6 +540,13 @@
// Resize anyway. If it's different from what it was before, we should
// already have cleared it.
packet.buffer->resize(bufSize);
+ return true;
+}
+
+bool
+RTMP::readPacketPayload(RTMPPacket& packet)
+{
+ RTMPHeader& hr = packet.header;
const size_t bytesRead = packet.bytesRead;
@@ -465,23 +555,13 @@
const int nChunk = std::min<int>(nToRead, _inChunkSize);
assert(nChunk >= 0);
+ // This is fine. We'll keep trying to read this payload until there
+ // is enough data.
if (readSocket(payloadData(packet) + bytesRead, nChunk) != nChunk) {
- log_error( "Failed to read RTMP packet payload. len: %s", bytesRead);
return false;
}
packet.bytesRead += nChunk;
-
- // Store a copy of the packet for later additions and as a reference for
- // future sends.
- RTMPPacket& storedpacket = storePacket(CHANNELS_IN, hr.channel, packet);
-
- // If the packet is complete, the stored packet no longer needs to
- // keep the data alive.
- if (isReady(packet)) {
- // The timestamp should be absolute by this stage.
- clearPayload(storedpacket);
- }
return true;
}
@@ -766,7 +846,6 @@
return true;
}
-
void
RTMP::close()
{
@@ -782,6 +861,154 @@
_serverBandwidth = 2500000;
}
+
+/////////////////////////////////////
+/// HandShaker implementation
+/////////////////////////////////////
+
+HandShaker::HandShaker(Socket& s)
+ :
+ _socket(s),
+ _sendBuf(sigSize + 1),
+ _recvBuf(sigSize + 1),
+ _error(false),
+ _complete(false),
+ _stage(0)
+{
+ // Not encrypted
+ _sendBuf[0] = 0x03;
+
+ // TODO: do this properly.
+ boost::uint32_t uptime = htonl(getUptime());
+
+ boost::uint8_t* ourSig = &_sendBuf.front() + 1;
+ std::memcpy(ourSig, &uptime, 4);
+ std::fill_n(ourSig + 4, 4, 0);
+
+ // Generate 1536 random bytes.
+ std::generate(ourSig + 8, ourSig + sigSize, RandomByte());
+
+}
+
+
+/// Calls the next stage in the handshake process.
+void
+HandShaker::call()
+{
+ if (error() || !_socket.connected()) return;
+
+ switch (_stage) {
+ case 0:
+ if (!stage0()) return;
+ _stage = 1;
+ case 1:
+ if (!stage1()) return;
+ _stage = 2;
+ case 2:
+ if (!stage2()) return;
+ _stage = 3;
+ case 3:
+ if (!stage3()) return;
+ log_debug("Handshake completed");
+ _complete = true;
+ }
+}
+
+bool
+HandShaker::stage0()
+{
+ std::streamsize sent = _socket.write(&_sendBuf.front(), sigSize + 1);
+
+ // This should probably not happen, but we can try again. An error will
+ // be signalled later if the socket is no longer usable.
+ if (!sent) {
+ log_error("Stage 1 socket not ready. This should not happen.");
+ return false;
+ }
+
+ /// If we sent the wrong amount of data, we can't recover.
+ if (sent != sigSize + 1) {
+ log_error("Could not send stage 1 data");
+ _error = true;
+ return false;
+ }
+ return true;
+}
+
+bool
+HandShaker::stage1()
+{
+
+ std::streamsize read = _socket.read(&_recvBuf.front(), sigSize + 1);
+
+ if (!read) {
+ // If we receive nothing, wait until the next try.
+ return false;
+ }
+
+ // The read should never return anything but 0 or what we asked for.
+ assert (read == sigSize + 1);
+
+ if (_recvBuf[0] != _sendBuf[0]) {
+ log_error( "Type mismatch: client sent %d, server answered %d",
+ _recvBuf[0], _sendBuf[0]);
+ }
+
+ const boost::uint8_t* serverSig = &_recvBuf.front() + 1;
+
+ // decode server response
+ boost::uint32_t suptime;
+ std::memcpy(&suptime, serverSig, 4);
+ suptime = ntohl(suptime);
+
+ log_debug("Server Uptime : %d", suptime);
+ log_debug("FMS Version : %d.%d.%d.%d",
+ +serverSig[4], +serverSig[5], +serverSig[6], +serverSig[7]);
+
+ return true;
+}
+
+bool
+HandShaker::stage2()
+{
+
+ std::streamsize sent = _socket.write(&_recvBuf.front() + 1, sigSize);
+
+ // This should probably not happen.
+ if (!sent) return false;
+
+ if (sent != sigSize) {
+ log_error("Could not send complete signature.");
+ _error = true;
+ return false;
+ }
+
+ return true;
+}
+
+bool
+HandShaker::stage3()
+{
+
+ // Expect it back again.
+ std::streamsize got = _socket.read(&_recvBuf.front(), sigSize);
+
+ if (!got) return false;
+
+ assert (got == sigSize);
+
+ const boost::uint8_t* serverSig = &_recvBuf.front();
+ const boost::uint8_t* ourSig = &_sendBuf.front() + 1;
+
+ const bool match = std::equal(serverSig, serverSig + sigSize, ourSig);
+
+ // Should we set an error here?
+ if (!match) {
+ log_error( "Signatures do not match during handshake!");
+ }
+ return true;
+}
+
/// The type of Ping packet is 0x4 and contains two mandatory parameters
/// and two optional parameters. The first parameter is
/// the type of Ping and in short integer. The second parameter is the
=== modified file 'libbase/RTMP.h'
--- a/libbase/RTMP.h 2010-03-10 16:37:25 +0000
+++ b/libbase/RTMP.h 2010-03-10 19:21:04 +0000
@@ -20,6 +20,7 @@
#include <boost/cstdint.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
#include <deque>
#include <map>
@@ -30,8 +31,14 @@
#define RTMP_DEFAULT_CHUNKSIZE 128
+// Forward declarations.
namespace gnash {
+ namespace rtmp {
+ class HandShaker;
+ }
+}
+namespace gnash {
namespace rtmp {
/// Known control / ping codes
@@ -294,27 +301,45 @@
/// This class is for handling the RTMP protocol.
//
-/// What should happen:
-//
-/// The RTMP object should connect to a server.
-//
-/// Once that is done the caller should call "play" on a stream. This (and
-/// other commands) can be called as long as the connection is connected.
-//
-/// It can be closed and reconnected.
+/// Only the RTMP protocol itself is handled in this class. An RTMP connection
+/// is valid only when connected() is true.
+//
+/// An RTMP object may be closed and reconnected. As soon as connect() returns
+/// true, callers are responsible for calling close().
+//
+/// RTMP has a set of channels for incoming and outgoing packets. Packets
+/// are stored here for two reasons:
+/// 1. The payload size exceeds the chunk size, so a single payload requires
+/// several complete packets. A packet is not 'ready' unless it has a
+/// complete payload, or is the packet that completes the payload of
+/// previous packets.
+/// 2. Subsequent packets sent on the same channel can be compressed if they
+/// have the same header information. The stored packet header is used for
+/// comparison. For this case, the payload is no longer necessary.
+//
+/// A different case applies to incomplete packets. The payload of a single
+/// packet (whether the packet is 'ready' or not) is the smaller of (a) the
+/// advertised data size and (b) the chunk size. Until this much data has
+/// been read, the packet is incomplete. Whereas Gnash always
+/// expects a complete header to be available or none at all, the payload
+/// can be read over several calls to update().
struct DSOEXPORT RTMP
{
- /// Construct an RTMP handler.
+ /// Construct a non-connected RTMP handler.
RTMP();
- ~RTMP() {}
+ ~RTMP();
/// Initiate a network connection.
//
/// Note that this only creates the TCP connection and carries out the
- /// handshake. You must send a "connect" request before doing anything
- /// else.
+ /// handshake. An active data connection needs an AMF connect request,
+ /// which is not part of the RTMP protocol.
+ //
+ /// @return true if the connection attempt starts, otherwise false.
+ /// A return of false means that the RTMP object is in a
+ /// closed state and can be reconnected.
bool connect(const URL& url);
/// This is used for sending call requests from the core.
@@ -344,31 +369,43 @@
/// Whether we have a basic connection to a server.
//
- /// This does not mean we are ready to send or receive streams. It does
- /// mean that messages sent via call() will be transmitted to the server.
- bool connected() const;
-
- /// Called to do receives.
- //
- /// TODO: this is the least satisfactory part. Currently sends are
- /// executed immediately. This is probably correct. Data are only
- /// received when update() is called, and then only one packet at a
- /// time.
+ /// This only means that the handshake is complete and that AMF requests
+ /// can be sent to the server. It does not mean that was can send or
+ /// receive media streams.
+ //
+ /// You should ensure that connected() is true before attempting to send
+ /// or receive data.
+ bool connected() const {
+ return _connected;
+ }
+
+ /// Whether the RTMP connection is in error condition.
+ //
+ /// This is a fatal error.
+ bool error() const {
+ return _error;
+ }
+
+ /// This function handles reading incoming data and filling data queues.
+ //
+ /// You should call this function regularly once the initial connection
+ /// has been initiated.
+ //
+ /// Its tasks involve:
+ /// 1. completing the handshake
+ /// 2. checking for socket errors
+ /// 3. reading incoming data
+ /// 4. filling data queues.
+ //
+ /// None of those things should concern you. Just call the function
+ /// regularly and use connected(), error(), and check the message
+ /// queues.
void update();
- /// Handle an RTMPPacket.
- void handlePacket(const RTMPPacket& packet);
-
/// Close the connection.
//
/// A new connection may now be opened.
void close();
-
- /// Read from the socket.
- int readSocket(boost::uint8_t* dst, int num);
-
- /// Send an RTMPPacket on the connection.
- bool sendPacket(RTMPPacket& packet);
/// Get an AMF message received from the server.
//
@@ -394,6 +431,15 @@
return b;
}
+ /// Handle an RTMPPacket.
+ void handlePacket(const RTMPPacket& packet);
+
+ /// Read from the socket.
+ int readSocket(boost::uint8_t* dst, int num);
+
+ /// Send an RTMPPacket on the connection.
+ bool sendPacket(RTMPPacket& packet);
+
/// Store the server bandwidth
//
/// Not sure why we need this.
@@ -430,7 +476,9 @@
};
/// Read an RTMP packet from the connection.
- bool readPacket(RTMPPacket& packet);
+ bool readPacketHeader(RTMPPacket& packet);
+
+ bool readPacketPayload(RTMPPacket& packet);
/// Check whether a packet exists on a channel.
bool hasPacket(ChannelType t, size_t channel) const;
@@ -481,6 +529,18 @@
/// Chunk size for sending.
size_t _outChunkSize;
+ boost::scoped_ptr<HandShaker> _handShaker;
+
+ bool _connected;
+
+ bool _error;
+
+ /// If a packet could not be read in one go, it is stored here.
+ //
+ /// This is not the same as a non-ready packet. It applies only to packets
+ /// waiting for payload data.
+ boost::scoped_ptr<RTMPPacket> _incompletePacket;
+
};
/// Send a control packet
=== modified file 'libbase/Socket.cpp'
--- a/libbase/Socket.cpp 2010-02-24 11:22:12 +0000
+++ b/libbase/Socket.cpp 2010-03-10 17:31:45 +0000
@@ -25,6 +25,7 @@
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
+#include <fcntl.h>
#include <boost/cstdint.hpp>
#include <cstring>
@@ -41,12 +42,69 @@
Socket::Socket()
:
+ _connected(false),
_socket(0),
_size(0),
_pos(0),
- _timedOut(false)
+ _error(false)
{}
+bool
+Socket::connected() const
+{
+ if (_connected) return true;
+ if (!_socket) return false;
+
+ size_t retries = 10;
+ fd_set fdset;
+ struct timeval tval;
+
+ while (retries-- > 0) {
+
+ FD_ZERO(&fdset);
+ FD_SET(_socket, &fdset);
+
+ tval.tv_sec = 0;
+ tval.tv_usec = 103;
+
+ const int ret = select(_socket + 1, NULL, &fdset, NULL, &tval);
+
+ // Select timeout
+ if (ret == 0) continue;
+
+ if (ret > 0) {
+ boost::uint32_t val = 0;
+ socklen_t len = sizeof(val);
+ if (::getsockopt(_socket, SOL_SOCKET, SO_ERROR, &val, &len) < 0) {
+ log_debug("Error");
+ _error = true;
+ return false;
+ }
+
+ if (!val) {
+ _connected = true;
+ return true;
+ }
+ _error = true;
+ return false;
+ }
+
+ // If interrupted by a system call, try again
+ if (ret == -1) {
+ const int err = errno;
+ if (err == EINTR) {
+ log_debug(_("Socket interrupted by a system call"));
+ continue;
+ }
+
+ log_error(_("XMLSocket: The socket was never available"));
+ _error = true;
+ return false;
+ }
+ }
+ return false;
+}
+
void
Socket::close()
@@ -54,10 +112,11 @@
if (_socket) ::close(_socket);
_socket = 0;
_size = 0;
+ _pos = 0;
}
bool
-Socket::connect(const URL& url)
+Socket::connect(const std::string& hostname, boost::uint16_t port)
{
if (connected()) {
@@ -65,88 +124,117 @@
return false;
}
- const std::string& hostname = url.hostname();
+ assert(!_error);
+ assert(!_socket);
+
if (hostname.empty()) return false;
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = inet_addr(hostname.c_str());
+ addr.sin_addr.s_addr = ::inet_addr(hostname.c_str());
if (addr.sin_addr.s_addr == INADDR_NONE) {
- struct hostent* host = gethostbyname(hostname.c_str());
+ struct hostent* host = ::gethostbyname(hostname.c_str());
if (!host || !host->h_addr) {
return false;
}
addr.sin_addr = *reinterpret_cast<in_addr*>(host->h_addr);
}
- const std::string& port = url.port();
- const int p = port.empty() ? 0 : boost::lexical_cast<int>(port);
- addr.sin_port = htons(p);
-
- _timedOut = false;
-
- _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (_socket != -1) {
- const struct sockaddr* a = reinterpret_cast<struct sockaddr*>(&addr);
-
- if (::connect(_socket, a, sizeof(struct sockaddr)) < 0) {
- const int err = errno;
+ addr.sin_port = htons(port);
+
+ _socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+ if (_socket < 0) {
+ const int err = errno;
+ log_debug("Socket creation failed: %s", std::strerror(err));
+ _socket = 0;
+ return false;
+ }
+
+ // Set non-blocking.
+ const int flag = ::fcntl(_socket, F_GETFL, 0);
+ ::fcntl(_socket, F_SETFL, flag | O_NONBLOCK);
+
+ const struct sockaddr* a = reinterpret_cast<struct sockaddr*>(&addr);
+
+ // Attempt connection
+ if (::connect(_socket, a, sizeof(struct sockaddr)) < 0) {
+ const int err = errno;
+ if (err != EINPROGRESS) {
log_error("Failed to connect socket: %s", std::strerror(err));
- close();
+ _socket = 0;
return false;
}
-
}
// Magic timeout number. Use rcfile ?
struct timeval tv = { 120, 0 };
- if (setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO,
+ if (::setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<unsigned char*>(&tv), sizeof(tv))) {
log_error("Setting socket timeout failed");
}
const boost::int32_t on = 1;
- setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+ ::setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
assert(_socket);
return true;
}
-
-std::streamsize
+void
Socket::fillCache()
{
- // If there are no unprocessed bytes, start from the beginning.
- if (!_size) {
- _pos = _cache;
- }
+ // Read position is always _pos + _size wrapped.
+ const size_t cacheSize = arraySize(_cache);
+ size_t start = (_pos + _size) % cacheSize;
+
+ // Try to fill the whole remaining buffer.
+ const size_t completeRead = cacheSize - _size;
+
+ // End is start + read size, wrapped.
+ size_t end = (start + completeRead) % cacheSize;
+ if (end == 0) end = cacheSize;
+
+ boost::uint8_t* startpos = _cache + start;
while (1) {
-
- // Read up to the end of the cache if possible.
- const int toRead = arraySize(_cache) - _size - (_pos - _cache);
-
- const int bytesRead = recv(_socket, _pos + _size, toRead, 0);
+ // The end pos is either the end of the cache or the first
+ // unprocessed byte.
+ boost::uint8_t* endpos = _cache + ((startpos < _cache + _pos) ?
+ _pos : cacheSize);
+
+ const int thisRead = endpos - startpos;
+ assert(thisRead >= 0);
+
+ const int bytesRead = ::recv(_socket, startpos, thisRead, 0);
if (bytesRead == -1) {
+
const int err = errno;
- log_debug("Socket receive error %s", std::strerror(err));
- //if (err == EINTR) continue;
-
if (err == EWOULDBLOCK || err == EAGAIN) {
- // Nothing read.
- _timedOut = true;
- return 0;
+ // Nothing to read. Carry on.
+ return;
}
+ log_error("Socket receive error %s", std::strerror(err));
+ _error = true;
}
+
+
_size += bytesRead;
- return bytesRead;
+
+ // If there weren't enough bytes, that's it.
+ if (bytesRead < thisRead) break;
+
+ // If we wrote up to the end of the cache, try writing more to the
+ // beginning.
+ startpos = _cache;
}
+
}
@@ -154,39 +242,60 @@
std::streamsize
Socket::read(void* dst, std::streamsize num)
{
+
+ if (num < 0) return 0;
+
+ if (_size < num && !_error) {
+ fillCache();
+ }
+
+ if (_size < num) return 0;
+ return readNonBlocking(dst, num);
+
+}
+
+std::streamsize
+Socket::readNonBlocking(void* dst, std::streamsize num)
+{
+ if (bad()) return 0;
- int toRead = num;
- _timedOut = false;
-
boost::uint8_t* ptr = static_cast<boost::uint8_t*>(dst);
-
- if (!_size) {
- if (fillCache() < 1) {
- if (_timedOut) {
- return -1;
- }
- }
- }
-
- const int thisRead = std::min(_size, toRead);
- if (thisRead > 0) {
- std::copy(_pos, _pos + thisRead, ptr);
- _pos += thisRead;
- _size -= thisRead;
- }
- return thisRead;
-}
-
-std::streamsize
-Socket::readNonBlocking(void* dst, std::streamsize num)
-{
- return read(dst, num);
+
+ if (!_size && !_error) {
+ fillCache();
+ }
+
+ size_t cacheSize = arraySize(_cache);
+
+ // First read from pos to end
+
+ // Maximum bytes available to read.
+ const size_t canRead = std::min<size_t>(_size, num);
+
+ size_t toRead = canRead;
+
+ // Space to the end (for the first read).
+ const int thisRead = std::min<size_t>(canRead, cacheSize - _pos);
+
+ std::copy(_cache + _pos, _cache + _pos + thisRead, ptr);
+ _pos += thisRead;
+ _size -= thisRead;
+ toRead -= thisRead;
+
+ if (toRead) {
+ std::copy(_cache, _cache + toRead, ptr + thisRead);
+ _pos = toRead;
+ _size -= toRead;
+ toRead = 0;
+ }
+
+ return canRead - toRead;
}
std::streamsize
Socket::write(const void* src, std::streamsize num)
{
- assert(!bad());
+ if (bad()) return 0;
int bytesSent = 0;
int toWrite = num;
@@ -195,15 +304,14 @@
while (toWrite > 0) {
bytesSent = ::send(_socket, buf, toWrite, 0);
- //log_debug("Bytes sent %s", bytesSent);
if (bytesSent < 0) {
const int err = errno;
log_error("Socket send error %s", std::strerror(err));
- close();
- return -1;
- break;
+ _error = true;
+ return 0;
}
- if (bytesSent == 0) break;
+
+ if (!bytesSent) break;
toWrite -= bytesSent;
buf += bytesSent;
}
@@ -237,10 +345,4 @@
return false;
}
-bool
-Socket::bad() const
-{
- return !_socket || _timedOut;
-}
-
} // namespace gnash
=== modified file 'libbase/Socket.h'
--- a/libbase/Socket.h 2010-02-23 15:28:45 +0000
+++ b/libbase/Socket.h 2010-03-10 13:05:38 +0000
@@ -33,36 +33,66 @@
/// A simple IOChannel subclass for reading and writing sockets.
//
-/// The only important functions here are read and write.
+/// The Socket class will give you years of satisfaction provided you observe
+/// the following points:
+/// 1. A socket is active only when both connected() and not bad().
+/// 2. The only accurate way of determining an error is to check bad().
+/// 3. read() and write() should not be called until connected() is true.
class DSOEXPORT Socket : public IOChannel
{
public:
+ /// Create a non-connected socket.
Socket();
virtual ~Socket() {}
- bool connect(const URL& url);
+ /// Initiate a connection
+ //
+ /// A return of true does not mean the connection has succeeded, only that
+ /// it did not fail. The connection attempt is only complete when
+ /// connected() returns true.
+ //
+ /// @return false if the connection fails. In this case, the
+ /// Socket is still in a closed state and is ready for
+ /// a new connection attempt. Otherwise true.
+ bool connect(const std::string& hostname, boost::uint16_t port);
+ /// Close the Socket.
+ //
+ /// A closed Socket is in a state where another connection attempt can
+ /// be made. Any errors are reset.
void close();
- bool connected() const {
- return (_socket);
- }
-
- bool timedOut() const {
- return (_timedOut);
- }
-
- /// Read the given number of bytes from the stream
- //
- /// Return the number of bytes actually read.
+ /// Whether a connection attempt is complete.
+ //
+ /// This is true as soon as the socket is ready for reading and writing.
+ /// But beware! This function may still return true if the Socket is
+ /// in error condition. Always check bad() if you care about this.
+ bool connected() const;
+
+ /// True if the Socket is in an error condition.
+ //
+ /// An error condition is fatal and can only be reset when the Socket
+ /// is closed. Any read or write failure other than EAGAIN or EWOULDBLOCK
+ /// causes a fatal error.
+ virtual bool bad() const {
+ return _error;
+ }
+
+ /// Read exactly the given number of bytes from the Socket or none at all.
+ //
virtual std::streamsize read(void* dst, std::streamsize num);
- /// The same as read().
+ /// Read up to the given number of bytes from the Socket.
virtual std::streamsize readNonBlocking(void* dst, std::streamsize num);
/// Write the given number of bytes to the stream
+ //
+ /// If you call write() before connected() is true, it may put the Socket
+ /// into an error condition.
+ //
+ /// Calling write() when the Socket is bad has no effect.
virtual std::streamsize write(const void* src, std::streamsize num);
/// Return current stream position
@@ -84,14 +114,13 @@
//
/// Not implemented for Socket.
virtual bool eof() const;
-
- /// Return true if the stream is in an error state
- virtual bool bad() const;
private:
+ mutable bool _connected;
+
/// Fill the cache.
- std::streamsize fillCache();
+ void fillCache();
/// A cache for received data.
boost::uint8_t _cache[16384];
@@ -103,9 +132,9 @@
int _size;
/// Current read position in cache.
- boost::uint8_t* _pos;
+ size_t _pos;
- bool _timedOut;
+ mutable bool _error;
};
} // namespace gnash
=== modified file 'libcore/asobj/flash/net/XMLSocket_as.cpp'
--- a/libcore/asobj/flash/net/XMLSocket_as.cpp 2010-03-09 21:22:09 +0000
+++ b/libcore/asobj/flash/net/XMLSocket_as.cpp 2010-03-10 17:32:34 +0000
@@ -55,172 +55,6 @@
void attachXMLSocketInterface(as_object& o);
}
-/// Connection object
-//
-/// A wrapper round a Network object that adds specific functions needed
-/// by XMLSocket.
-namespace {
-
-class SocketConnection
-{
-public:
-
- SocketConnection()
- :
- _complete(false)
- {}
-
- /// Initiate a connection.
- void connect(const std::string& host, boost::uint16_t port) {
- _start.reset(new boost::thread(
- boost::bind(&SocketConnection::makeConnection, this, host, port)));
- }
-
- /// The state of the connection.
- //
- /// Until complete() is true, this may change.
- bool connected() const {
- return _socket.connected();
- }
-
- /// Whether an initiated connection is finished
- //
- /// @return true if a connection attempt is complete.
- /// The connection attempt may have failed. Check
- /// connected() to find out.
- bool complete() const {
- return _complete;
- }
-
- void setComplete() {
- _complete = true;
- }
-
- size_t writeMessage(const std::string& str) {
- // We have to write the null terminator as well.
- return write(_socket.connected(), str.c_str(), str.size() + 1);
- }
-
- /// Read from the socket.
- void readMessages(std::vector<std::string>& msgs) {
-
- assert(_socket.connected());
-
- const int fd = _socket.connected();
- assert(fd > 0);
-
- fd_set fdset;
- struct timeval tval;
- size_t retries = 10;
-
- const int bufSize = 10000;
- boost::scoped_array<char> buf(new char[bufSize]);
-
- while (retries-- > 0) {
- FD_ZERO(&fdset);
- FD_SET(fd, &fdset);
-
- tval.tv_sec = 0;
- tval.tv_usec = 103;
-
- const int ret = select(fd + 1, &fdset, NULL, NULL, &tval);
-
- // If interupted by a system call, try again
- if (ret == -1 && errno == EINTR) {
- log_debug(_("The socket for fd #%d was interupted by a "
- "system call"), fd);
- continue;
- }
- if (ret == -1) {
- log_error(_("XMLSocket: The socket for fd #%d was never "
- "available"), fd);
- return;
- }
-
- // Return if timed out.
- if (ret == 0) return;
-
- const size_t bytesRead = read(fd, buf.get(), bufSize - 1);
-
- // Return if there's no data.
- if (!bytesRead) return;
-
- if (buf[bytesRead - 1] != 0) {
- // We received a partial message, so bung
- // a null-terminator on the end.
- buf[bytesRead] = 0;
- }
-
- char* ptr = buf.get();
- while (static_cast<size_t>(ptr - buf.get()) < bytesRead - 1) {
-
-#ifdef GNASH_XMLSOCKET_DEBUG
- log_debug ("read: %d, this string ends: %d", bytesRead,
- ptr + std::strlen(ptr) - buf.get());
-#endif
-
- // If the string reaches to the final byte read, it's
- // incomplete. Store it and continue. The buffer is
- // NULL-terminated, so this cannot read past the end.
- if (static_cast<size_t>(
- ptr + std::strlen(ptr) - buf.get()) == bytesRead) {
-
- _remainder += std::string(ptr);
- break;
- }
-
- if (!_remainder.empty()) {
- msgs.push_back(_remainder + std::string(ptr));
- ptr += std::strlen(ptr) + 1;
- _remainder.clear();
- continue;
- }
-
- // Don't do anything if nothing is received.
- msgs.push_back(ptr);
-
- ptr += std::strlen(ptr) + 1;
- }
- }
- }
-
- /// Close the connection.
- //
- /// This also cancels any connection attempt in progress.
- void close() {
- if (_start) {
- _start.reset();
- }
- _socket.close();
-
- // Reset for next connection.
- _complete = false;
-
- assert(!_socket.connected());
- }
-
-private:
-
- void makeConnection(const std::string& host, boost::uint16_t port) {
- std::string combined = host;
- combined += ":";
- combined += port;
- URL url(combined);
- _socket.connect(url);
- _complete = true;
- }
-
- Socket _socket;
-
- bool _complete;
-
- std::string _remainder;
-
- boost::scoped_ptr<boost::thread> _start;
-
-};
-
-}
class XMLSocket_as : public ActiveRelay
{
@@ -231,21 +65,12 @@
XMLSocket_as(as_object* owner);
~XMLSocket_as();
- /// True when the XMLSocket is not trying to connect.
- //
- /// If this is true but the socket is not connected, the connection
- /// has failed.
+ /// True only when the XMLSocket is ready for read/write.
bool ready() const {
return _ready;
}
- /// Whether a connection exists.
- //
- /// This is not final until ready() is true.
- bool connected() const {
- return _connection.connected();
- }
-
+ /// Attempt a connection.
bool connect(const std::string& host, boost::uint16_t port);
/// Send a string with a null-terminator to the socket.
@@ -265,14 +90,12 @@
void checkForIncomingData();
- /// Return the as_function with given name.
- boost::intrusive_ptr<as_function> getEventHandler(
- const std::string& name);
-
/// The connection
- SocketConnection _connection;
+ Socket _socket;
bool _ready;
+
+ std::string _remainder;
};
@@ -294,26 +117,29 @@
void
XMLSocket_as::update()
{
- // Wait until something has happened with the connection
- if (!_connection.complete()) return;
-
- // If this XMLSocket hadn't finished a connection, check whether it
- // has now.
+
+ // This function should never be called unless a connection is active
+ // or a connection attempt is being made.
+
+ // If the connection was not complete, check to see if it is.
if (!ready()) {
-
- if (!connected()) {
-
- // If connection failed, notify onConnect and stop callback.
- // This means update() will not be called again until
- // XMLSocket.connect() is invoked.
+
+ if (_socket.bad()) {
+ // Connection failed during connect()
+ // Notify onConnect and stop callback. This means update()
+ // will not be called again until XMLSocket.connect() is invoked.
callMethod(&owner(), NSV::PROP_ON_CONNECT, false);
getRoot(owner()).removeAdvanceCallback(this);
- return;
+ return;
}
+ // Not yet ready.
+ if (!_socket.connected()) return;
+
// Connection succeeded.
_ready = true;
callMethod(&owner(), NSV::PROP_ON_CONNECT, true);
+
}
// Now the connection is established we can receive data.
@@ -329,7 +155,7 @@
return false;
}
- _connection.connect(host, port);
+ _socket.connect(host, port);
// Start callbacks on advance.
getRoot(owner()).addAdvanceCallback(this);
@@ -341,62 +167,83 @@
XMLSocket_as::close()
{
getRoot(owner()).removeAdvanceCallback(this);
- _connection.close();
+ _socket.close();
_ready = false;
}
-boost::intrusive_ptr<as_function>
-XMLSocket_as::getEventHandler(const std::string& name)
-{
- boost::intrusive_ptr<as_function> ret;
-
- as_value tmp;
- string_table& st = getStringTable(owner());
- if (!owner().get_member(st.find(name), &tmp) ) return ret;
- ret = tmp.to_function();
- return ret;
-}
-
void
XMLSocket_as::checkForIncomingData()
{
- assert(ready() && connected());
+ assert(ready());
std::vector<std::string> msgs;
- _connection.readMessages(msgs);
+
+ const int bufSize = 10000;
+ boost::scoped_array<char> buf(new char[bufSize]);
+
+ const size_t bytesRead = _socket.readNonBlocking(buf.get(), bufSize - 1);
+
+ // Return if there's no data.
+ if (!bytesRead) return;
+
+ if (buf[bytesRead - 1] != 0) {
+ // We received a partial message, so bung
+ // a null-terminator on the end.
+ buf[bytesRead] = 0;
+ }
+
+ char* ptr = buf.get();
+ while (static_cast<size_t>(ptr - buf.get()) < bytesRead) {
+
+#ifdef GNASH_XMLSOCKET_DEBUG
+ log_debug ("read: %d, this string ends: %d", bytesRead,
+ ptr + std::strlen(ptr) - buf.get());
+#endif
+
+ std::string msg(ptr);
+
+ // If the string reaches to the final byte read, it's
+ // incomplete. Store it and continue. The buffer is
+ // NULL-terminated, so this cannot read past the end.
+ if (static_cast<size_t>(
+ ptr + std::strlen(ptr) - buf.get()) == bytesRead) {
+ _remainder += msg;
+ break;
+ }
+
+ if (!_remainder.empty()) {
+ msgs.push_back(_remainder + msg);
+ ptr += msg.size() + 1;
+ _remainder.clear();
+ continue;
+ }
+
+ // Don't do anything if nothing is received.
+ msgs.push_back(msg);
+
+ ptr += msg.size() + 1;
+ }
if (msgs.empty()) return;
- //log_debug(_("Got %d messages: "), msgs.size());
-
#ifdef GNASH_XMLSOCKET_DEBUG
for (size_t i = 0, e = msgs.size(); i != e; ++i) {
log_debug(_(" Message %d: %s "), i, msgs[i]);
}
#endif
- as_environment env(getVM(owner()));
-
for (XMLSocket_as::MessageList::const_iterator it=msgs.begin(),
itEnd=msgs.end(); it != itEnd; ++it) {
-
- // This should be checked on every iteration in case one call
- // changes the handler.
- boost::intrusive_ptr<as_function> onDataHandler =
- getEventHandler("onData");
-
- if (!onDataHandler) break;
-
- const std::string& s = *it;
-
- fn_call::Args args;
- args += s;
-
- fn_call call(&owner(), env, args);
-
- onDataHandler->call(call);
- }
+ callMethod(&owner(), NSV::PROP_ON_DATA, *it);
+ }
+
+ if (_socket.bad()) {
+ callMethod(&owner(), NSV::PROP_ON_CLOSE);
+ getRoot(owner()).removeAdvanceCallback(this);
+ return;
+ }
+
}
@@ -406,12 +253,12 @@
void
XMLSocket_as::send(std::string str)
{
- if (!ready() || !connected()) {
+ if (!_socket.connected()) {
log_error(_("XMLSocket.send(): socket not initialized"));
return;
}
- _connection.writeMessage(str);
+ _socket.write(str.c_str(), str.size() + 1);
return;
}
=== modified file 'testsuite/XmlSocketServer.pl'
--- a/testsuite/XmlSocketServer.pl 2008-08-20 16:15:55 +0000
+++ b/testsuite/XmlSocketServer.pl 2010-03-10 17:32:12 +0000
@@ -4,8 +4,8 @@
$SIG{PIPE}='IGNORE';
-$m=new IO::Socket::INET(Listen=>1,LocalPort=>2229);
-$O=new IO::Select($m);
+my $m=new IO::Socket::INET(Listen=>1,LocalPort=>2229,Reuse=>1,Proto=>'tcp');
+my $O=new IO::Select($m);
$/ = "\0";
@@ -22,6 +22,12 @@
# Log message received:
print "XmlSocketServer: received \"$i\"\n";
+ if ($i =~ m/closeNow/) {
+ print("Closing...\n");
+ close($m);
+ Time::HiRes::sleep(1);
+ }
+
if ($R==0) {
$T=syswrite($_, "\n", 16000);
if ($T==undef) {
=== modified file 'testsuite/misc-ming.all/Makefile.am'
--- a/testsuite/misc-ming.all/Makefile.am 2010-02-11 16:08:00 +0000
+++ b/testsuite/misc-ming.all/Makefile.am 2010-03-10 17:32:12 +0000
@@ -143,7 +143,6 @@
attachMovieLoopingTestRunner \
registerClassTest \
registerClassTestRunner \
- XMLSocketTest \
goto_frame_test \
consecutive_goto_frame_test \
matrix_test \
@@ -255,6 +254,7 @@
DeviceFontTestRunner \
EmbeddedFontTestRunner \
TextSnapshotTest-Runner \
+ XMLSocketTester \
LCTestRunner \
timeline_var_test-Runner \
place_object_testrunner \
@@ -1517,14 +1517,6 @@
sh $< $(top_builddir) action_execution_order_extend_test.swf > $@
chmod 755 $@
-XMLSocketTest_SOURCES = \
- XMLSocketTest.c \
- $(NULL)
-XMLSocketTest_LDADD = libgnashmingutils.la
-
-XMLSocketTest.swf: XMLSocketTest
- ./XMLSocketTest $(top_srcdir)/testsuite/media
-
BitmapDataTest_SOURCES = \
BitmapDataTest.c \
$(NULL)
@@ -1827,6 +1819,14 @@
sh $< -c "ENDOFTEST" $(top_builddir) LC-Receive.swf LC-Send.swf > $@
chmod 755 $@
+XMLSocketTest.swf: $(srcdir)/XMLSocketTest.as
+ $(MAKESWF) -r 1 -o $@ $(srcdir)/empty.as $(srcdir)/XMLSocketTest.as
+
+XMLSocketTester: $(srcdir)/XMLSocketTester.sh XMLSocketTest.swf
+ sh $< -c "ENDOFTEST" $(top_builddir) $(top_srcdir) $(PERL) \
+ XMLSocketTest.swf > $@
+ chmod 755 $@
+
DrawingApiTest.swf: $(srcdir)/DrawingApiTest.as
$(MAKESWF) -r 1 -o $@ $(srcdir)/empty.as $(srcdir)/DrawingApiTest.as
@@ -2060,6 +2060,7 @@
action_execution_order_extend_testrunner \
simple_loop_testrunner \
loadMovieTestRunner \
+ XMLSocketTester \
LCTestRunner \
DrawingApiTestRunner \
TextSnapshotTest-Runner \
=== added file 'testsuite/misc-ming.all/XMLSocketTest.as'
--- a/testsuite/misc-ming.all/XMLSocketTest.as 1970-01-01 00:00:00 +0000
+++ b/testsuite/misc-ming.all/XMLSocketTest.as 2010-03-10 17:46:27 +0000
@@ -0,0 +1,109 @@
+
+#include "../actionscript.all/check.as"
+
+xmlArray = [];
+xmlArray[0] = 'Plain text';
+xmlArray[1] = 'Plain *NEWLINE* text';
+xmlArray[2] = 'Plain *NULL* text';
+xmlArray[3] = 'Plain *NULL**NEWLINE* text';
+xmlArray[4] = '<xml>Some XML</xml>';
+xmlArray[5] = '<xml>Some XML*NEWLINE*</xml>';
+xmlArray[6] = '<xml>Some XML*NULL*</xml>';
+xmlArray[7] = '<xml>Some XML*NEWLINE**NULL*</xml>';
+xmlArray[8] = undefined;
+xmlArray[9] = 9;
+xmlArray[10] = "";
+a = "";
+for (i = 0; i < 15000; ++i) { a += "a"; };
+xmlArray[11] = a;
+xmlArray[12] = 'Last Item';
+xmlArray[13] = 'closeNow';
+xmlArray[14] = 'moo';
+
+expectedArray = new Array();
+expectedArray[0] = 'Plain text';
+expectedArray[1] = 'Plain \n text';
+expectedArray[2] = 'Plain ';
+expectedArray[3] = ' text';
+expectedArray[4] = 'Plain ';
+expectedArray[5] = '\n text';
+expectedArray[6] = '<xml>Some XML</xml>';
+expectedArray[7] = '<xml>Some XML\n</xml>';
+expectedArray[8] = '<xml>Some XML';
+expectedArray[9] = '</xml>';
+expectedArray[10] = '<xml>Some XML\n';
+expectedArray[11] = '</xml>';
+expectedArray[12] = 'undefined';
+expectedArray[13] = 9;
+expectedArray[14] = '';
+// Don't check 15.
+expectedArray[16] = 'Last Item';
+
+ gc = 0;
+ wait = 0;
+ count = -1;
+ connected = false;
+
+ var myXML;
+
+function handleConnect(connectionStatus) {
+ check_equals(connectionStatus, true);
+ if (connectionStatus) {
+ trace('Connected');
+ connected = true;
+ if (gc < xmlArray.length) {
+ myXML.send(xmlArray[gc++]);
+ }
+ }
+ else { trace('Initial connection failed!'); }
+};
+
+// Store data and send next lot.
+function handleData(data) {
+ receivedArray.push(data);
+ str = xmlArray[gc++];
+ myXML.send(str);
+};
+
+function handleDisconnect() {
+ trace('Connection lost.');
+ checkResults();
+};
+
+myXML = new XMLSocket;
+myXML.onConnect = handleConnect;
+myXML.onData = handleData;
+myXML.onClose = handleDisconnect;
+receivedArray = new Array();
+
+ ret = myXML.connect("localhost", 2229);
+ check_equals(ret, true);
+ stop();
+
+
+function checkResults() {
+
+ check_equals(receivedArray[0], expectedArray[0]);
+ check_equals(receivedArray[1], expectedArray[1]);
+ check_equals(receivedArray[2], expectedArray[2]);
+ check_equals(receivedArray[3], expectedArray[3]);
+ check_equals(receivedArray[4], expectedArray[4]);
+ check_equals(receivedArray[5], expectedArray[5]);
+ check_equals(receivedArray[6], expectedArray[6]);
+ check_equals(receivedArray[7], expectedArray[7]);
+ check_equals(receivedArray[8], expectedArray[8]);
+ check_equals(receivedArray[9], expectedArray[9]);
+ check_equals(receivedArray[10], expectedArray[10]);
+ check_equals(receivedArray[11], expectedArray[11]);
+ check_equals(receivedArray[12], expectedArray[12]);
+ check_equals(receivedArray[13], expectedArray[13]);
+ check_equals(receivedArray[14], expectedArray[14]);
+ check_equals(receivedArray[15].length, 15000);
+ check_equals(receivedArray[15].charAt(0), 'a');
+ check_equals(receivedArray[16], expectedArray[16]);
+
+ trace("ENDOFTEST");
+
+ loadMovie ("FSCommand:quit", "");
+};
+
=== removed file 'testsuite/misc-ming.all/XMLSocketTest.c'
--- a/testsuite/misc-ming.all/XMLSocketTest.c 2010-01-01 17:48:26 +0000
+++ b/testsuite/misc-ming.all/XMLSocketTest.c 1970-01-01 00:00:00 +0000
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2005, 2006, 2009, 2010 Free Software Foundation, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <ming.h>
-
-#include "ming_utils.h"
-
-#define OUTPUT_VERSION 7
-#define OUTPUT_FILENAME "XMLSocketTest.swf"
-
-int
-main(int argc, char** argv)
-{
-
- /* Basic setup */
-
- SWFMovieClip dejagnuclip;
- SWFMovie mo;
- const char* srcdir = ".";
-
- char longString[15001];
-
- if ( argc>1 ) srcdir=argv[1];
- else
- {
- fprintf(stderr, "Usage: %s\n", argv[0]);
- return 1;
- }
-
- Ming_init();
-
- mo = newSWFMovieWithVersion(OUTPUT_VERSION);
- SWFMovie_setDimension(mo, 800, 600);
- SWFMovie_setRate (mo, 10.0);
-
- dejagnuclip = get_dejagnu_clip((SWFBlock)get_default_font(srcdir), 10, 0,
0, 800, 600);
- SWFMovie_add(mo, (SWFBlock)dejagnuclip);
-
- /* Setup XMLsocket */
-
- /* Advance to next frame on connection */
- add_actions(mo,
- "function handleConnect(connectionStatus) { "
- " if (connectionStatus) "
- " { "
- " trace('Connected'); "
- " connected = true; "
- " play(); "
- " } "
- " else { trace('Connection failed.'); } "
- "}; "
- );
-
- /* Add any data recieved to an array, advance to next
- frame */
- add_actions(mo,
- "function handleData(data) { "
- " play(); "
- " trace('Data received: ' + data); "
- " receivedArray.push(data); "
- " trace ('Pushed data');"
- "}; "
- );
-
- /* Advance to next frame on losing connection */
- add_actions(mo,
- "function handleDisconnect() { "
- " trace('Connection lost.'); "
- " connected = false; "
- " play(); "
- "}; "
- );
-
- /* Wait for connection */
- add_actions(mo, "stop();");
-
- /* Check here that a successful XMLSocket.connect() returns
- true */
- add_actions(mo,
- "wait = 0;"
- "count = -1;"
- "connected = false;"
- "myXML = new XMLSocket;"
- "myXML.onConnect = handleConnect;"
- "myXML.onData = handleData;"
- "myXML.onClose = handleDisconnect;"
- "ret = myXML.connect(\"localhost\", 2229);"
- "check_equals(ret, true);"
- );
-
- /* The data we're going to send */
-
- add_actions(mo,
- "xmlArray = new Array();"
- "xmlArray[0] = 'Plain text';"
- "xmlArray[1] = 'Plain *NEWLINE* text';"
- "xmlArray[2] = 'Plain *NULL* text';"
- "xmlArray[3] = 'Plain *NULL**NEWLINE* text';"
- "xmlArray[4] = '<xml>Some XML</xml>';"
- "xmlArray[5] = '<xml>Some XML*NEWLINE*</xml>';"
- "xmlArray[6] = '<xml>Some XML*NULL*</xml>';"
- "xmlArray[7] = '<xml>Some XML*NEWLINE**NULL*</xml>';"
- "xmlArray[8] = undefined;"
- "xmlArray[9] = 9;"
- "xmlArray[10] = '';"
- );
-
- memset(longString, 'a', 15000);
- strncpy(longString, "xmlArray[11] = '", 16);
- longString[14998] = '\'';
- longString[14999] = ';';
- longString[15000] = 0;
-
- add_actions(mo, longString);
-
- add_actions(mo, "xmlArray[12] = 'Last Item';");
-
- /* The data we should get back */
- add_actions(mo,
- "expectedArray = new Array();"
- "expectedArray[0] = 'Plain text';"
- "expectedArray[1] = 'Plain \n text';"
- "expectedArray[2] = 'Plain ';"
- "expectedArray[3] = ' text';"
- "expectedArray[4] = 'Plain ';"
- "expectedArray[5] = '\n text';"
- "expectedArray[6] = '<xml>Some XML</xml>';"
- "expectedArray[7] = '<xml>Some XML\n</xml>';"
- "expectedArray[8] = '<xml>Some XML';"
- "expectedArray[9] = '</xml>';"
- "expectedArray[10] = '<xml>Some XML\n';"
- "expectedArray[11] = '</xml>';"
- "expectedArray[12] = 'undefined';"
- "expectedArray[13] = 9;"
- "expectedArray[14] = '';"
- "expectedArray[15] = 'aaa...';" /* Don't test this, we'll check the
length */
- "expectedArray[16] = 'Last Item';"
- );
-
-
- /* Where we're going to put the data */
- add_actions(mo,
- "receivedArray = new Array();"
- );
-
-
-
- /* Frame 2 */
- SWFMovie_nextFrame(mo);
- /* We should be connected here */
- check_equals(mo, "connected", "true");
-
-
-
-
- /* Frame 3 */
- /* This is where we send the socket data */
- SWFMovie_nextFrame(mo);
-
- add_actions(mo,
- "count++;"
- "if (count >= xmlArray.length) { _root.gotoAndStop(4); };"
- "trace('Frame 3 (iteration ' + count + '): sending ' +
xmlArray[count]);"
- "myXML.send(xmlArray[count]);"
- "play();"
- );
-
-
- /* Frame 4 */
- /* This is here to make a small pause between each send
- (a quarter of a second) */
- SWFMovie_nextFrame(mo);
-
- add_actions(mo,
- "trace ('Frame 4');"
- "play();"
- );
-
- /* Frame 5 */
- /* If we have sent all the data, continue. Otherwise send the next item */
- SWFMovie_nextFrame(mo);
-
- add_actions(mo,
- "trace ('Frame 5');"
- "if (count < xmlArray.length - 1 ) { _root.gotoAndStop(3); };"
- "play();"
- );
-
- /* Frame 6 */
- /* This is a bit of a hackish loop to wait for data */
- SWFMovie_nextFrame(mo);
-
- add_actions(mo,
- "play();"
- );
-
- /* Frame 7 */
- SWFMovie_nextFrame(mo);
-
- add_actions(mo,
- "if (receivedArray[receivedArray.length -1] != 'Last Item' && wait++ <
100)"
- "{ _root.gotoAndStop(6); };"
- "play();"
- );
-
-
- /* Last frame (8) */
- SWFMovie_nextFrame(mo);
-
- /* Close the connection to make sure it has no evil effects */
- add_actions(mo,
- "myXML.close();"
- );
-
- check_equals(mo, "receivedArray.length", "expectedArray.length");
-
- check_equals(mo, "receivedArray[0]", "expectedArray[0]");
- check_equals(mo, "receivedArray[1]", "expectedArray[1]");
- check_equals(mo, "receivedArray[2]", "expectedArray[2]");
- check_equals(mo, "receivedArray[3]", "expectedArray[3]");
- check_equals(mo, "receivedArray[4]", "expectedArray[4]");
- check_equals(mo, "receivedArray[5]", "expectedArray[5]");
- check_equals(mo, "receivedArray[6]", "expectedArray[6]");
- check_equals(mo, "receivedArray[7]", "expectedArray[7]");
- check_equals(mo, "receivedArray[8]", "expectedArray[8]");
- check_equals(mo, "receivedArray[9]", "expectedArray[9]");
- check_equals(mo, "receivedArray[10]", "expectedArray[10]");
- check_equals(mo, "receivedArray[11]", "expectedArray[11]");
- check_equals(mo, "receivedArray[12]", "expectedArray[12]");
- check_equals(mo, "receivedArray[13]", "expectedArray[13]");
- check_equals(mo, "receivedArray[14]", "expectedArray[14]");
- check_equals(mo, "receivedArray[15].length", "14982");
-
-
- check_equals(mo, "receivedArray[16]", "expectedArray[16]");
-
-
-
- add_actions(mo, "totals(); stop();");
-
- /* Output movie */
- puts("Saving " OUTPUT_FILENAME );
- SWFMovie_save(mo, OUTPUT_FILENAME);
-
- return 0;
-}
-
=== added file 'testsuite/misc-ming.all/XMLSocketTester.sh'
--- a/testsuite/misc-ming.all/XMLSocketTester.sh 1970-01-01 00:00:00
+0000
+++ b/testsuite/misc-ming.all/XMLSocketTester.sh 2010-03-10 18:33:52
+0000
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+#
+# Copyright (C) 2005, 2006, 2009, 2010 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+while getopts c:C: name; do
+ case $name in
+ c) endtagpat="$OPTARG" ;;
+ C) endtagpat="$OPTARG"; endtagexp=X ;;
+ ?)
+ {
+ echo "Usage: $0 [-r <runs>] [-f <advances>] [-c <string>]
<swf> ..."
+ echo " -c <pattern> : verify that the test ends with a
trace "
+ echo " matching <pattern>, or print a failure"
+ echo " -C <pattern> : same as -c <pattern> but a
failure is "
+ echo " expected"
+ } >&2
+ exit 1;;
+ esac
+done
+shift $(($OPTIND - 1))
+
+
+top_builddir=$1
+shift
+top_srcdir=$1
+shift
+perl=$1
+shift
+swf=$1
+
+
+echo "#!/bin/sh"
+echo
+
+echo "# Environment"
+env | grep GNASH | while read REPLY; do
+ echo "export ${REPLY}"
+done
+
+timeout=40
+cat << EOF
+
+outlog=${top_builddir}/testoutlog.\$$
+(
+ echo "Running first process"
+ $perl ${top_srcdir}/testsuite/XmlSocketServer.pl
+) &
+(
+ echo "Running second process"
+ ${top_builddir}/gui/gnash -v -r0 ${swf} -t ${timeout} > \${outlog}
+ cat \${outlog}
+ if test "x${endtagpat}" != x; then
+ lasttrace=\`grep TRACE \${outlog} | tail -1 | sed 's/.*TRACE:
//'\`
+ if ! expr "\${lasttrace}" : '${endtagpat}' > /dev/null; then
+ echo "${endtagexp}FAILED: consistency check: last trace
from run of test \${t} (\${lasttrace}) doesn't match pattern (${endtagpat})"
+ else
+ echo "${endtagexp}PASSED: consistency check: last trace
from run of test \${t} (\${lasttrace}) matches pattern (${endtagpat})"
+ fi
+ fi
+ rm \${outlog}
+)
+EOF
=== modified file 'utilities/rtmpget.cpp'
--- a/utilities/rtmpget.cpp 2010-02-24 18:00:03 +0000
+++ b/utilities/rtmpget.cpp 2010-03-10 10:08:21 +0000
@@ -24,6 +24,7 @@
#include "SimpleBuffer.h"
#include "AMF.h"
#include "GnashAlgorithm.h"
+#include "GnashSleep.h"
#include <boost/cstdint.hpp>
#include <iomanip>
@@ -480,6 +481,16 @@
std::exit(EXIT_FAILURE);
}
+ do {
+ r.update();
+ gnashSleep(1000);
+ } while (!r.connected());
+
+ if (r.error()) {
+ log_error("Connection attempt failed");
+ std::exit(EXIT_FAILURE);
+ }
+
/// 1. connect.
sendConnectPacket(r, nc, app, ver, swf, tc, page);
@@ -495,6 +506,10 @@
while (1) {
r.update();
+ if (r.error()) {
+ gnash::log_error("Connection error");
+ break;
+ }
/// Retrieve messages.
boost::shared_ptr<SimpleBuffer> b = r.getMessage();
@@ -514,8 +529,8 @@
}
f = r.getMessage();
}
+ gnashSleep(1000);
- if (!r.connected()) break;
}
}
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Gnash-commit] /srv/bzr/gnash/trunk r12011: Fix XMLSocket mess up.,
Benjamin Wolsey <=