1 // NetConnection_as.cpp: Open local connections for FLV files or URLs.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
4 // 2011 Free Software Foundation, Inc
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "gnashconfig.h"
25 #include "NetConnection_as.h"
29 #include <boost/scoped_ptr.hpp>
30 #include <boost/lexical_cast.hpp>
31 #include <boost/noncopyable.hpp>
32 #include <boost/mem_fn.hpp>
35 #include "GnashSystemNetHeaders.h"
37 #include "GnashException.h"
38 #include "movie_root.h"
39 #include "StreamProvider.h"
42 #include "SimpleBuffer.h"
43 #include "namedStrings.h"
44 #include "GnashAlgorithm.h"
46 #include "Global_as.h"
47 #include "AMFConverter.h"
49 #include "as_function.h"
50 #include "RunResources.h"
51 #include "IOChannel.h"
54 //#define GNASH_DEBUG_REMOTING
58 // Forward declarations.
60 void attachProperties(as_object
& o
);
61 void attachNetConnectionInterface(as_object
& o
);
62 as_value
netconnection_isConnected(const fn_call
& fn
);
63 as_value
netconnection_uri(const fn_call
& fn
);
64 as_value
netconnection_connect(const fn_call
& fn
);
65 as_value
netconnection_close(const fn_call
& fn
);
66 as_value
netconnection_call(const fn_call
& fn
);
67 as_value
netconnection_addHeader(const fn_call
& fn
);
68 as_value
netconnection_new(const fn_call
& fn
);
69 as_value
local_onResult(const fn_call
& fn
);
70 std::pair
<std::string
, std::string
>
71 getStatusCodeInfo(NetConnection_as::StatusCode code
);
73 /// Parse and send any invoke messages from an HTTP connection.
74 void handleAMFInvoke(amf::Reader
& rd
, const boost::uint8_t*& b
,
75 const boost::uint8_t* end
, as_object
& owner
);
77 void replyBWCheck(rtmp::RTMP
& r
, double txn
);
81 /// Abstract connection handler class
83 /// This class abstract operations on network connections,
84 /// specifically RPC and streams fetching.
85 class Connection
: boost::noncopyable
89 typedef std::map
<size_t, as_object
*> CallbacksMap
;
91 /// @param methodName A string identifying the remote procedure to call
92 /// @param responseHandler Object to invoke response methods on.
93 /// @param args A vector of arguments
94 virtual void call(as_object
* asCallback
, const std::string
& methodName
,
95 const std::vector
<as_value
>& args
) = 0;
97 /// Get an stream by name
99 /// @param name Stream identifier
100 virtual std::auto_ptr
<IOChannel
> getStream(const std::string
& /*name*/) {
101 log_unimpl("%s doesn't support fetching streams", typeName(*this));
102 return std::auto_ptr
<IOChannel
>(0);
105 /// Process pending traffic, out or in bound
107 /// Handles all networking for NetConnection::call() and dispatches
108 /// callbacks when needed.
110 /// @return false if no further advance is needed. The only
111 /// case for this is an error.
112 virtual bool advance() = 0;
114 /// Connections may store references to as_objects
115 void setReachable() const {
116 foreachSecond(_callbacks
.begin(), _callbacks
.end(),
117 std::mem_fun(&as_object::setReachable
));
120 /// Return true if the connection has pending calls
122 /// This will be used on NetConnection.close(): if current
123 /// connection has pending calls to process it will be
124 /// queued and only really dropped when advance returns false
125 virtual bool hasPendingCalls() const = 0;
131 virtual ~Connection() {}
133 as_object
* popCallback(size_t id
) {
134 CallbacksMap::iterator it
= _callbacks
.find(id
);
135 if (it
!= _callbacks
.end()) {
136 as_object
* callback
= it
->second
;
137 _callbacks
.erase(it
);
145 /// Construct a connection handler bound to the given NetConnection object
147 /// The binding is used to notify statuses and errors
149 /// The NetConnection_as owns all Connections, so there is no
150 /// need to mark it reachable.
151 Connection(NetConnection_as
& nc
)
158 void pushCallback(size_t id
, as_object
* callback
) {
159 _callbacks
[id
] = callback
;
162 // Object handling connection status messages
163 NetConnection_as
& _nc
;
167 CallbacksMap _callbacks
;
178 HTTPRequest(Connection
& h
)
183 // leave space for header
184 _data
.append("\000\000\000\000\000\000", 6);
185 _headers
["Content-Type"] = "application/x-amf";
188 /// Add AMF data to this request.
189 void addData(const SimpleBuffer
& amf
) {
190 _data
.append(amf
.data(), amf
.size());
194 void send(const URL
& url
, NetConnection_as
& nc
);
196 bool process(NetConnection_as
& nc
);
200 static const size_t NCCALLREPLYCHUNK
= 1024 * 200;
202 /// Handle replies to server functions we invoked with a callback.
204 /// This needs access to the stored callbacks.
205 void handleAMFReplies(amf::Reader
& rd
, const boost::uint8_t*& b
,
206 const boost::uint8_t* end
);
208 Connection
& _handler
;
210 /// The data to be sent by POST with this request.
213 /// A buffer for the reply.
216 /// The number of separate remoting calls to be encoded in this request.
219 /// A single HTTP request.
220 boost::scoped_ptr
<IOChannel
> _connection
;
222 /// Headers to be sent with this request.
223 NetworkAdapter::RequestHeaders _headers
;
227 /// Queue of remoting calls
229 /// This is a single conception HTTP remoting connection, which in reality
230 /// comprises a queue of separate HTTP requests.
231 class HTTPConnection
: public Connection
234 /// Create a handler for HTTP remoting
236 /// @param nc The NetConnection AS object to send status/error events to
237 /// @param url URL to post calls to
238 HTTPConnection(NetConnection_as
& nc
, const URL
& url
)
245 virtual bool hasPendingCalls() const {
246 return _currentRequest
.get() || !_requestQueue
.empty();
249 /// Queue current request, process all live requests.
250 virtual bool advance();
252 /// Check if there is a current request. If not, make one.
253 virtual void call(as_object
* asCallback
, const std::string
& methodName
,
254 const std::vector
<as_value
>& args
);
260 /// The queue of sent requests.
261 std::vector
<boost::shared_ptr
<HTTPRequest
> > _requestQueue
;
263 /// The current request.
264 boost::shared_ptr
<HTTPRequest
> _currentRequest
;
268 class RTMPConnection
: public Connection
272 RTMPConnection(NetConnection_as
& nc
, const URL
& url
)
275 _connectionComplete(false),
278 // Throw exception if this fails.
279 const bool ret
= _rtmp
.connect(url
);
280 if (!ret
) throw GnashException("Connection failed");
283 virtual void call(as_object
* asCallback
, const std::string
& methodName
,
284 const std::vector
<as_value
>& args
)
288 aw
.writeString(methodName
);
289 const size_t id
= asCallback
? callNo() : 0;
292 for (size_t i
= 0; i
< args
.size(); ++i
) {
293 args
[i
].writeAMF0(aw
);
297 pushCallback(id
, asCallback
);
301 bool hasPendingCalls() const {
305 virtual bool advance() {
309 if (_rtmp
.error() && !_connectionComplete
) {
310 _nc
.notifyStatus(NetConnection_as::CONNECT_FAILED
);
313 if (_connectionComplete
&& _rtmp
.error()) {
314 _nc
.notifyStatus(NetConnection_as::CONNECT_CLOSED
);
318 // Nothing to do yet, but we don't want to be dropped.
319 if (!_connectionComplete
) {
321 if (!_rtmp
.connected()) return true;
323 _connectionComplete
= true;
324 log_debug("Initial connection complete");
326 const RunResources
& r
= getRunResources(_nc
.owner());
327 Global_as
& gl
= getGlobal(_nc
.owner());
330 as_object
* o
= createObject(gl
);
333 o
->init_member("app", _url
.path().substr(1), flags
);
335 // TODO: check where it gets these data from.
336 o
->init_member("flashVer", getVM(_nc
.owner()).getPlayerVersion(),
338 o
->init_member("swfUrl", r
.streamProvider().baseURL().str(),
340 o
->init_member("tcUrl", _url
.str(), flags
);
342 // TODO: implement this properly
343 o
->init_member("fpad", false, flags
);
344 o
->init_member("capabilities", 15.0, flags
);
345 o
->init_member("audioCodecs", 3191.0, flags
);
346 o
->init_member("videoCodecs", 252.0, flags
);
347 o
->init_member("videoFunction", 1.0, flags
);
348 o
->init_member("pageUrl", as_value(), flags
);
350 // Set up the callback object.
351 as_object
* cb
= createObject(getGlobal(_nc
.owner()));
352 cb
->init_member(NSV::PROP_ON_RESULT
,
353 gl
.createFunction(local_onResult
), 0);
355 cb
->init_member("_conn", &_nc
.owner(), 0);
357 std::vector
<as_value
> args
;
360 call(cb
, "connect", args
);
362 // Send bandwidth check; the pp appears to do this
368 boost::shared_ptr
<SimpleBuffer
> b
= _rtmp
.getMessage();
370 if (b
&& !_nc
.isConnected()) {
374 /// Retrieve messages.
376 handleInvoke(b
->data() + rtmp::RTMPHeader::headerSize
,
377 b
->data() + b
->size());
378 b
= _rtmp
.getMessage();
386 void handleInvoke(const boost::uint8_t* payload
, const boost::uint8_t* end
);
389 bool _connectionComplete
;
394 } // anonymous namespace
396 NetConnection_as::NetConnection_as(as_object
* owner
)
403 // here to have HTTPConnection definition available
404 NetConnection_as::~NetConnection_as()
408 // extern (used by Global.cpp)
410 netconnection_class_init(as_object
& where
, const ObjectURI
& uri
)
412 registerBuiltinClass(where
, netconnection_new
,
413 attachNetConnectionInterface
, 0, uri
);
417 NetConnection_as::markReachableResources() const
419 owner().setReachable();
420 std::for_each(_oldConnections
.begin(), _oldConnections
.end(),
421 boost::mem_fn(&Connection::setReachable
));
422 if (_currentConnection
.get()) _currentConnection
->setReachable();
425 /// FIXME: this should not use _uri, but rather take a URL argument.
426 /// Validation should probably be done on connect() only and return a
427 /// bool indicating validity. That can be used to return a failure
428 /// for invalid or blocked URLs.
430 NetConnection_as::validateURL() const
432 const RunResources
& r
= getRunResources(owner());
433 URL
uri(_uri
, r
.streamProvider().baseURL());
435 std::string
uriStr(uri
.str());
436 assert(uriStr
.find("://") != std::string::npos
);
438 // Check if we're allowed to open url
439 if (!r
.streamProvider().allow(uri
)) {
440 log_security(_("Gnash is not allowed to open this url: %s"), uriStr
);
444 log_debug(_("Connection to movie: %s"), uriStr
);
450 NetConnection_as::notifyStatus(StatusCode code
)
452 std::pair
<std::string
, std::string
> info
= getStatusCodeInfo(code
);
454 /// This is a new normal object each time (see NetConnection.as)
455 as_object
* o
= createObject(getGlobal(owner()));
459 o
->init_member("code", info
.first
, flags
);
460 o
->init_member("level", info
.second
, flags
);
462 callMethod(&owner(), NSV::PROP_ON_STATUS
, o
);
466 /// Called on NetConnection.connect(null).
468 /// The status notification happens immediately, isConnected becomes true.
470 NetConnection_as::connect()
472 // Close any current connections.
475 notifyStatus(CONNECT_SUCCESS
);
480 NetConnection_as::connect(const std::string
& uri
)
482 // Close any current connections.
484 assert(!_isConnected
);
486 // TODO: check for other kind of invalidities here...
488 notifyStatus(CONNECT_FAILED
);
492 const RunResources
& r
= getRunResources(owner());
493 URL
url(_uri
, r
.streamProvider().baseURL());
495 if (!r
.streamProvider().allow(url
)) {
496 log_security(_("Gnash is not allowed to connect " "to %s"), url
);
497 notifyStatus(CONNECT_FAILED
);
501 // Attempt connection.
502 if (url
.protocol() == "https" || url
.protocol() == "http") {
503 _currentConnection
.reset(new HTTPConnection(*this, url
));
505 else if (url
.protocol() == "rtmp") {
507 _currentConnection
.reset(new RTMPConnection(*this, url
));
509 catch (const GnashException
&) {
510 // This happens if the connect cannot even be attempted.
511 notifyStatus(CONNECT_FAILED
);
516 else if (url
.protocol() == "rtmpt" || url
.protocol() == "rtmpts") {
517 log_unimpl("NetConnection.connect(%s): unsupported connection "
519 notifyStatus(CONNECT_FAILED
);
523 log_error("NetConnection.connect(%s): unknown connection "
525 notifyStatus(CONNECT_FAILED
);
532 /// FIXME: This should close an active connection as well as setting the
533 /// appropriate properties.
535 NetConnection_as::close()
537 // Send close event if a connection is in progress or connected is true.
538 const bool needSendClosedStatus
= _currentConnection
.get() || _isConnected
;
540 /// Queue the current call queue if it has pending calls
541 if (_currentConnection
.get() && _currentConnection
->hasPendingCalls()) {
542 boost::shared_ptr
<Connection
> c(_currentConnection
.release());
543 _oldConnections
.push_back(c
);
546 /// TODO: what should actually happen here? Should an attached
547 /// NetStream object be interrupted?
548 _isConnected
= false;
550 if (needSendClosedStatus
) {
551 notifyStatus(CONNECT_CLOSED
);
557 NetConnection_as::setURI(const std::string
& uri
)
559 owner().init_readonly_property("uri", &netconnection_uri
);
564 NetConnection_as::call(as_object
* asCallback
, const std::string
& methodName
,
565 const std::vector
<as_value
>& args
)
567 if (!_currentConnection
.get()) {
568 IF_VERBOSE_ASCODING_ERRORS(
569 log_aserror("NetConnection.call: can't call while not connected");
574 _currentConnection
->call(asCallback
, methodName
, args
);
579 std::auto_ptr
<IOChannel
>
580 NetConnection_as::getStream(const std::string
& name
)
582 const RunResources
& ri
= getRunResources(owner());
584 const StreamProvider
& streamProvider
= ri
.streamProvider();
586 // Construct URL with base URL (assuming not connected to RTMP server..)
587 // TODO: For RTMP return the named stream from an existing RTMP connection.
588 // If name is a full or relative URL passed from NetStream.play(), it
589 // must be constructed against the base URL, not the NetConnection uri,
590 // which should always be null in this case.
591 const RcInitFile
& rcfile
= RcInitFile::getDefaultInstance();
593 URL
url(name
, streamProvider
.baseURL());
595 return streamProvider
.getStream(url
, rcfile
.saveStreamingMedia());
600 NetConnection_as::startAdvanceTimer()
602 getRoot(owner()).addAdvanceCallback(this);
606 NetConnection_as::stopAdvanceTimer()
608 getRoot(owner()).removeAdvanceCallback(this);
612 NetConnection_as::update()
615 // Handle unfinished actions in any closed connections. Currently we
616 // only expect HTTP connections here, as RTMP is closed immediately.
618 for (Connections::iterator i
= _oldConnections
.begin();
619 i
!= _oldConnections
.end(); ) {
621 Connection
& ch
= **i
;
622 // Remove on error or if there are no more actions.
623 if (!ch
.advance() || !ch
.hasPendingCalls()) {
624 i
= _oldConnections
.erase(i
);
629 // Advance current connection, but reset if there's an error.
631 // TODO: notify relevant status.
632 if (_currentConnection
.get()) {
633 if (!_currentConnection
->advance()) {
634 _currentConnection
.reset();
638 /// If there are no connections we can stop the timer.
639 if (_oldConnections
.empty() && !_currentConnection
.get()) {
644 // Anonymous namespace for NetConnection interface implementation.
647 /// NetConnection.call()
649 /// Documented to return void, and current tests suggest this might be
650 /// correct, though they don't test with any calls that might succeed.
652 netconnection_call(const fn_call
& fn
)
654 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
657 IF_VERBOSE_ASCODING_ERRORS(
658 log_aserror(_("NetConnection.call(): needs at least one argument"));
663 const as_value
& methodName_as
= fn
.arg(0);
664 std::string methodName
= methodName_as
.to_string();
666 #ifdef GNASH_DEBUG_REMOTING
667 std::stringstream ss
; fn
.dump_args(ss
);
668 log_debug("NetConnection.call(%s)", ss
.str());
671 // TODO: arg(1) is the response object. let it know when data comes back
672 as_object
* asCallback(0);
675 if (fn
.arg(1).is_object()) {
676 asCallback
= (toObject(fn
.arg(1), getVM(fn
)));
679 IF_VERBOSE_ASCODING_ERRORS(
680 std::stringstream ss
; fn
.dump_args(ss
);
681 log_aserror("NetConnection.call(%s): second argument must be "
682 "an object", ss
.str());
687 std::vector
<as_value
> args
;
689 args
= std::vector
<as_value
>(fn
.getArgs().begin() + 2,
692 ptr
->call(asCallback
, methodName
, args
);
698 netconnection_close(const fn_call
& fn
)
700 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
707 netconnection_isConnected(const fn_call
& fn
)
709 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
710 return as_value(ptr
->isConnected());
714 netconnection_uri(const fn_call
& fn
)
716 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
717 return as_value(ptr
->getURI());
721 attachNetConnectionInterface(as_object
& o
)
723 Global_as
& gl
= getGlobal(o
);
725 o
.init_member("connect", gl
.createFunction(netconnection_connect
));
726 o
.init_member("addHeader", gl
.createFunction(netconnection_addHeader
));
727 o
.init_member("call", gl
.createFunction(netconnection_call
));
728 o
.init_member("close", gl
.createFunction(netconnection_close
));
732 attachProperties(as_object
& o
)
734 o
.init_readonly_property("isConnected", &netconnection_isConnected
);
737 /// \brief callback to instantiate a new NetConnection object.
738 /// \param fn the parameters from the Flash movie
739 /// \return nothing from the function call.
740 /// \note The return value is returned through the fn.result member.
742 netconnection_new(const fn_call
& fn
)
744 as_object
* obj
= ensure
<ValidThis
>(fn
);
745 obj
->setRelay(new NetConnection_as(obj
));
746 attachProperties(*obj
);
751 /// For remoting, NetConnection.connect() takes a URL. For all other streams,
754 /// For non-remoting streams:
756 /// Returns undefined if there are no arguments, true if the first
757 /// argument is null, otherwise whether the connection is allowed. The
758 /// actual result of the connection is sent with an onStatus call later.
760 /// Undefined is also a valid argument for SWF7 and above.
762 /// The isConnected property is set to the result of connect().
764 netconnection_connect(const fn_call
& fn
)
767 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
770 IF_VERBOSE_ASCODING_ERRORS(
771 log_aserror(_("NetConnection.connect(): needs at least "
777 const as_value
& uri
= fn
.arg(0);
779 const VM
& vm
= getVM(fn
);
780 const std::string
& uriStr
= uri
.to_string(vm
.getSWFVersion());
782 // This is always set without validification.
785 // Check first arg for validity
786 if (uri
.is_null() || (getSWFVersion(fn
) > 6 && uri
.is_undefined())) {
788 return as_value(true);
792 std::stringstream ss
; fn
.dump_args(ss
);
793 log_unimpl("NetConnection.connect(%s): args after the first are "
794 "not supported", ss
.str());
797 return as_value(ptr
->connect(uriStr
));
803 netconnection_addHeader(const fn_call
& fn
)
805 NetConnection_as
* ptr
= ensure
<ThisIsNative
<NetConnection_as
> >(fn
);
808 log_unimpl("NetConnection.addHeader()");
812 /// This creates a local callback function to handle the return from connect()
814 /// NetStream does this using a builtin function, but this is more complicated
815 /// because it needs the native NetConnection type.
817 /// This stores the NetConnection object so that it can be updated when
820 /// We don't know if this is the best way to do it, but:
822 /// 1. the connect call *does* return a callback ID.
824 local_onResult(const fn_call
& fn
)
826 as_object
* obj
= fn
.this_ptr
;
829 const ObjectURI conn
= getURI(getVM(fn
), "_conn");
830 as_value f
= getMember(*obj
, conn
);
831 as_object
* nc
= toObject(f
, getVM(fn
));
834 const as_value arg
= fn
.nargs
? fn
.arg(0) : as_value();
835 callMethod(nc
, NSV::PROP_ON_STATUS
, arg
);
841 std::pair
<std::string
, std::string
>
842 getStatusCodeInfo(NetConnection_as::StatusCode code
)
844 /// The Call statuses do exist, but this implementation is a guess.
846 case NetConnection_as::CONNECT_SUCCESS
:
847 return std::make_pair("NetConnection.Connect.Success", "status");
848 case NetConnection_as::CONNECT_FAILED
:
849 return std::make_pair("NetConnection.Connect.Failed", "error");
850 case NetConnection_as::CONNECT_APPSHUTDOWN
:
851 return std::make_pair("NetConnection.Connect.AppShutdown", "error");
852 case NetConnection_as::CONNECT_REJECTED
:
853 return std::make_pair("NetConnection.Connect.Rejected", "error");
854 case NetConnection_as::CONNECT_CLOSED
:
855 return std::make_pair("NetConnection.Connect.Closed", "status");
856 case NetConnection_as::CALL_FAILED
:
857 return std::make_pair("NetConnection.Call.Failed", "error");
858 case NetConnection_as::CALL_BADVERSION
:
859 return std::make_pair("NetConnection.Call.BadVersion", "status");
867 handleAMFInvoke(amf::Reader
& rd
, const boost::uint8_t*& b
,
868 const boost::uint8_t* end
, as_object
& owner
)
871 const boost::uint16_t invokecount
= amf::readNetworkShort(b
);
874 if (!invokecount
) return;
876 for (size_t i
= invokecount
; i
> 0; --i
) {
878 throw amf::AMFException("Invoke buffer too short");
880 const boost::uint16_t namelength
= amf::readNetworkShort(b
);
882 if (b
+ namelength
> end
) {
883 throw amf::AMFException("Invoke buffer too short");
885 std::string
headerName((char*)b
, namelength
);
887 #ifdef GNASH_DEBUG_REMOTING
888 log_debug("Invoke name %s", headerName
);
892 throw amf::AMFException("Invoke buffer too short");
894 b
+= 5; // skip past bool and length long
896 // It seems there must be exactly one argument.
899 throw amf::AMFException("Invoke argument not present");
902 VM
& vm
= getVM(owner
);
903 ObjectURI key
= getURI(vm
, headerName
);
904 #ifdef GNASH_DEBUG_REMOTING
905 log_debug("Invoking %s(%s)", headerName
, arg
);
907 callMethod(&owner
, key
, arg
);
912 /// Process any replies to server functions we invoked.
914 /// Note that fatal errors will throw an amf::AMFException.
916 HTTPRequest::handleAMFReplies(amf::Reader
& rd
, const boost::uint8_t*& b
,
917 const boost::uint8_t* end
)
919 const boost::uint16_t numreplies
= amf::readNetworkShort(b
);
920 b
+= 2; // number of replies
922 // TODO: test if this value is relevant at all.
923 if (!numreplies
) return;
925 // There should be only three loop control mechanisms in this loop:
926 // 1. continue: there was an error, but parsing is still okay.
927 // 2. return: we've finished, but there was no problem.
928 // 3. amf::AMFException: parsing failed and we can do nothing more.
930 // We haven't tested this very rigorously.
933 if (b
+ 2 > end
) return;
935 const boost::uint16_t replylength
= amf::readNetworkShort(b
);
938 if (replylength
< 4 || b
+ replylength
> end
) {
939 throw amf::AMFException("Reply message too short");
942 // Reply message is: '/id/methodName'
943 int ns
= 1; // next slash position
944 while (ns
< replylength
- 1 && *(b
+ ns
) != '/') ++ns
;
945 if (ns
>= replylength
- 1) {
946 throw amf::AMFException("Invalid reply message name");
949 std::string
id(reinterpret_cast<const char*>(b
+ 1), ns
- 1);
950 size_t callbackID
= 0;
952 callbackID
= boost::lexical_cast
<size_t>(id
);
954 catch (const boost::bad_lexical_cast
&) {
955 // Do we need to abort parsing here?
956 throw amf::AMFException("Invalid callback ID");
959 const std::string
methodName(reinterpret_cast<const char*>(b
+ ns
+ 1),
960 replylength
- ns
- 1);
964 // parse past unused string in header
965 if (b
+ 2 > end
) return;
966 const boost::uint16_t unusedlength
= amf::readNetworkShort(b
);
969 if (b
+ unusedlength
> end
) return;
972 // this field is supposed to hold the total number of bytes in the
973 // rest of this particular reply value, but openstreetmap.org
974 // (which works great in the adobe player) sends 0xffffffff.
975 // So we just ignore it.
976 if (b
+ 4 > end
) break;
979 // this updates b to point to the next unparsed byte
982 throw amf::AMFException("Could not parse argument value");
985 // if actionscript specified a callback object,
987 as_object
* callback
= _handler
.popCallback(callbackID
);
990 log_error("Unknown HTTP Remoting response identifier '%s'", id
);
991 // There's no parsing error, so continue.
996 if (methodName
== "onResult") {
997 methodKey
= NSV::PROP_ON_RESULT
;
999 else if (methodName
== "onStatus") {
1000 methodKey
= NSV::PROP_ON_STATUS
;
1003 // NOTE: the pp is known to actually
1004 // invoke the custom method, but with 7
1005 // undefined arguments (?)
1006 log_error("Unsupported HTTP Remoting response callback: '%s' "
1007 "(size %d)", methodName
, methodName
.size());
1011 #ifdef GNASH_DEBUG_REMOTING
1012 log_debug("callback called");
1015 callMethod(callback
, methodKey
, replyval
);
1021 HTTPConnection::advance()
1023 // If there is data waiting to be sent, send it and push it
1025 if (_currentRequest
.get()) {
1026 _currentRequest
->send(_url
, _nc
);
1027 _requestQueue
.push_back(_currentRequest
);
1029 // Clear the current request for the next go.
1030 _currentRequest
.reset();
1033 // Process all replies and clear finished requests.
1034 for (std::vector
<boost::shared_ptr
<HTTPRequest
> >::iterator i
=
1035 _requestQueue
.begin(); i
!= _requestQueue
.end();) {
1036 if (!(*i
)->process(_nc
)) i
= _requestQueue
.erase(i
);
1044 HTTPRequest::send(const URL
& url
, NetConnection_as
& nc
)
1046 // We should never have a request without any calls.
1048 log_debug("creating connection");
1051 (reinterpret_cast<boost::uint16_t*>(_data
.data() + 4))[0] = htons(_calls
);
1052 std::string
postdata(reinterpret_cast<char*>(_data
.data()), _data
.size());
1054 #ifdef GNASH_DEBUG_REMOTING
1055 log_debug("NetConnection.call(): encoded args from %1% calls: %2%",
1056 _calls
, hexify(_data
.data(), _data
.size(), false));
1059 const StreamProvider
& sp
= getRunResources(nc
.owner()).streamProvider();
1060 _connection
.reset(sp
.getStream(url
, postdata
, _headers
).release());
1064 /// An AMF remoting reply comprises two main sections: first the invoke
1065 /// commands to be called on the NetConnection object, and second the
1066 /// replies to any client invoke messages that requested a callback.
1068 HTTPRequest::process(NetConnection_as
& nc
)
1070 assert(_connection
);
1072 // Fill last chunk before reading in the next
1073 size_t toRead
= _reply
.capacity() - _reply
.size();
1074 if (!toRead
) toRead
= NCCALLREPLYCHUNK
;
1076 #ifdef GNASH_DEBUG_REMOTING
1077 log_debug("Attempt to read %d bytes", toRead
);
1080 // See if we need to allocate more bytes for the next
1082 if (_reply
.capacity() < _reply
.size() + toRead
) {
1083 const size_t newCapacity
= _reply
.size() + toRead
;
1085 #ifdef GNASH_DEBUG_REMOTING
1086 log_debug("NetConnection.call: reply buffer capacity (%d) "
1087 "is too small to accept next %d bytes of chunk "
1088 "(current size is %d). Reserving %d bytes.",
1089 _reply
.capacity(), toRead
, _reply
.size(), newCapacity
);
1092 _reply
.reserve(newCapacity
);
1095 const int read
= _connection
->readNonBlocking(_reply
.data() + _reply
.size(),
1099 #ifdef GNASH_DEBUG_REMOTING
1100 log_debug("read '%1%' bytes: %2%", read
,
1101 hexify(_reply
.data() + _reply
.size(), read
, false));
1103 _reply
.resize(_reply
.size() + read
);
1106 // There is no way to tell if we have a whole amf reply without
1107 // parsing everything
1109 // The reply format has a header field which specifies the
1110 // number of bytes in the reply, but potlatch sends 0xffffffff
1111 // and works fine in the proprietary player
1113 // For now we just wait until we have the full reply.
1115 // FIXME make this parse on other conditions, including: 1) when
1116 // the buffer is full, 2) when we have a "length in bytes" value
1117 // thas is satisfied
1118 if (_connection
->bad()) {
1119 log_debug("connection is in error condition, calling "
1120 "NetConnection.onStatus");
1122 // If the connection fails, it is manually verified
1123 // that the pp calls onStatus with 1 undefined argument.
1124 callMethod(&nc
.owner(), NSV::PROP_ON_STATUS
, as_value());
1128 // Not all data was received, so carry on.
1129 if (!_connection
->eof()) return true;
1131 // If it's less than 8 we didn't expect a response, so just ignore
1133 if (_reply
.size() > 8) {
1135 #ifdef GNASH_DEBUG_REMOTING
1136 log_debug("hit eof");
1138 const boost::uint8_t *b
= _reply
.data();
1139 const boost::uint8_t *end
= _reply
.data() + _reply
.size();
1141 amf::Reader
rd(b
, end
, getGlobal(nc
.owner()));
1143 // skip version indicator and client id
1147 handleAMFInvoke(rd
, b
, end
, nc
.owner());
1148 handleAMFReplies(rd
, b
, end
);
1150 catch (const amf::AMFException
& e
) {
1152 // Any fatal error should be signalled by throwing an
1153 // exception. In this case onStatus is called with an
1154 // undefined argument.
1155 log_error("Error parsing server AMF: %s", e
.what());
1156 callMethod(&nc
.owner(), NSV::PROP_ON_STATUS
, as_value());
1160 // We've finished with this connection.
1165 HTTPConnection::call(as_object
* asCallback
, const std::string
& methodName
,
1166 const std::vector
<as_value
>& args
)
1168 if (!_currentRequest
.get()) {
1169 _currentRequest
.reset(new HTTPRequest(*this));
1172 // Create AMF buffer for this call.
1173 SimpleBuffer
buf(32);
1175 amf::writePlainString(buf
, methodName
, amf::STRING_AMF0
);
1177 const size_t callID
= callNo();
1179 // client id (result number) as counted string
1180 // the convention seems to be / followed by a unique (ascending) number
1181 std::ostringstream os
;
1183 // Call number is not used if the callback is undefined
1184 if (asCallback
) os
<< callID
;
1186 // Encode callback number.
1187 amf::writePlainString(buf
, os
.str(), amf::STRING_AMF0
);
1189 size_t total_size_offset
= buf
.size();
1190 buf
.append("\000\000\000\000", 4); // total size to be filled in later
1192 // encode array of arguments to remote method
1193 buf
.appendByte(amf::STRICT_ARRAY_AMF0
);
1194 buf
.appendNetworkLong(args
.size());
1196 // STRICT_ARRAY encoding is allowed for remoting
1197 amf::Writer
w(buf
, true);
1199 for (size_t i
= 0; i
< args
.size(); ++i
) {
1200 const as_value
& arg
= args
[i
];
1201 if (!arg
.writeAMF0(w
)) {
1202 log_error("Could not serialize NetConnection.call argument %d", i
);
1206 // Set the "total size" parameter.
1207 *(reinterpret_cast<uint32_t*>(buf
.data() + total_size_offset
)) =
1208 htonl(buf
.size() - 4 - total_size_offset
);
1210 // Add data to the current HTTPRequest.
1211 _currentRequest
->addData(buf
);
1213 // Remember the callback object.
1215 pushCallback(callID
, asCallback
);
1220 RTMPConnection::handleInvoke(const boost::uint8_t* payload
,
1221 const boost::uint8_t* end
)
1223 // TODO: clean up the logic in this function to reduce duplication.
1225 assert(payload
!= end
);
1227 // make sure it is a string method name we start with
1228 if (payload
[0] != 0x02) {
1229 log_error( "Sanity failed. no string method in invoke packet");
1234 std::string method
= amf::readString(payload
, end
);
1236 log_debug("Invoke: read method string %s", method
);
1237 if (*payload
!= amf::NUMBER_AMF0
) return;
1240 log_debug( "Server invoking <%s>", method
);
1242 const ObjectURI methodname
= getURI(getVM(_nc
.owner()), method
);
1244 // _result means it's the answer to a remote method call initiated
1246 if (method
== "_result") {
1247 const double id
= amf::readNumber(payload
, end
);
1248 log_debug("Received result for method call %s",
1249 boost::io::group(std::setprecision(15), id
));
1253 amf::Reader
rd(payload
, end
, getGlobal(_nc
.owner()));
1254 // TODO: use all args and check the order! We currently only use
1257 log_debug("Value: %s", arg
);
1260 as_object
* o
= popCallback(id
);
1261 callMethod(o
, NSV::PROP_ON_RESULT
, arg
);
1265 /// These are remote function calls initiated by the server.
1266 const double id
= amf::readNumber(payload
, end
);
1267 log_debug("Received server call %s %s",
1268 boost::io::group(std::setprecision(15), id
),
1269 id
? "" : "(no reply expected)");
1271 /// If the server sends this, we reply (the call should contain a
1272 /// callback object!).
1273 if (method
== "_onbwcheck") {
1274 if (id
) replyBWCheck(_rtmp
, id
);
1276 log_error("Server called _onbwcheck without a callback");
1281 if (method
== "_onbwdone") {
1283 if (*payload
!= amf::NULL_AMF0
) return;
1285 #ifdef GNASH_DEBUG_REMOTING
1286 log_debug("AMF buffer for _onbwdone: %s\n",
1287 hexify(payload
, end
- payload
, false));
1289 double latency
= amf::readNumber(payload
, end
);
1290 double bandwidth
= amf::readNumber(payload
, end
);
1291 log_debug("Latency: %s, bandwidth %s", latency
, bandwidth
);
1295 if (method
== "_error") {
1299 amf::Reader
rd(payload
, end
, getGlobal(_nc
.owner()));
1300 // TODO: use all args and check the order! We currently only use
1303 log_debug("Value: %s", arg
);
1306 log_error( "rtmp server sent error");
1308 callMethod(&_nc
.owner(), NSV::PROP_ON_STATUS
, arg
);
1312 // Parse any arguments.
1315 amf::Reader
rd(payload
, end
, getGlobal(_nc
.owner()));
1316 // TODO: use all args and check the order! We currently only use
1319 log_debug("Value: %s", arg
);
1322 // Call method on the NetConnection object.
1323 callMethod(&_nc
.owner(), methodname
, arg
);
1328 replyBWCheck(rtmp::RTMP
& r
, double txn
)
1331 amf::write(buf
, "_result");
1332 amf::write(buf
, txn
);
1333 buf
.appendByte(amf::NULL_AMF0
);
1334 amf::write(buf
, 0.0);
1338 } // anonymous namespace
1339 } // end of gnash namespace
1343 // indent-tabs-mode: t