void push_amf(const SimpleBuffer &amf) { //GNASH_REPORT_FUNCTION; _postdata.append(amf.data(), amf.size()); queued_count++; }
void RTMP::call(const SimpleBuffer& amf) { RTMPPacket p(amf.size()); setupInvokePacket(p); // Copy the data. p.buffer->append(amf.data(), amf.size()); sendPacket(p); }
/// Return the size of the data plus header. size_t size() const { if (!_data) return 0; SimpleBuffer buf; // The header comprises 2 bytes and a length field of 4 bytes. if (encodeData(_name, *_data, buf)) { return buf.size() + 6; } return 0; }
/// Ensure that each buffer has appropriate padding for the decoder. // /// Note: all callers passing a SimpleBuffer should already do this, /// so this is a paranoid check. void ensurePadding(SimpleBuffer& data, media::MediaHandler* m) { const size_t padding = m ? m->getInputPaddingSize() : 0; if (data.capacity() - data.size() < padding) { log_error(_("Sound data creator didn't appropriately pad " "buffer. We'll do so now, but will cost memory copies.")); data.reserve(data.size() + padding); } }
vespalib::string render(const StateExplorer &state, const Url &url) { Slime top; state.get_state(slime::SlimeInserter(top), true); if (top.get().type().getId() == slime::NIX::ID) { top.setObject(); } inject_children(state, url, top.get()); SimpleBuffer buf; slime::JsonFormat::encode(top, buf, true); return buf.get().make_string(); }
void sendCreateStream(rtmp::RTMP& r, FakeNC& nc) { const size_t cn = nc.callNumber(); SimpleBuffer buf; amf::write(buf, "createStream"); amf::write(buf, static_cast<double>(cn)); buf.appendByte(amf::NULL_AMF0); nc.queueCall(cn, "createStream"); r.call(buf); }
void RTMP::play(const SimpleBuffer& buf, int streamID) { RTMPPacket packet(buf.size()); packet.header.channel = CHANNEL_VIDEO; packet.header.packetType = PACKET_TYPE_INVOKE; packet.header._streamID = streamID; packet.buffer->append(buf.data(), buf.size()); sendPacket(packet); }
void replyBWCheck(rtmp::RTMP& r, FakeNC& /*nc*/, double txn) { // Infofield1? SimpleBuffer buf; amf::write(buf, "_result"); amf::write(buf, txn); buf.appendByte(amf::NULL_AMF0); amf::write(buf, 0.0); r.call(buf); }
void sendCheckBW(rtmp::RTMP& r, FakeNC& nc) { SimpleBuffer buf; const size_t cn = nc.callNumber(); amf::write(buf, "_checkbw"); amf::write(buf, static_cast<double>(cn)); buf.appendByte(amf::NULL_AMF0); nc.queueCall(cn, "_checkbw"); r.call(buf); }
void sendFCSubscribe(rtmp::RTMP& r, FakeNC& nc, const std::string& subscribepath) { const size_t cn = nc.callNumber(); SimpleBuffer buf; amf::write(buf, "FCSubscribe"); // What is this? amf::write(buf, static_cast<double>(cn)); buf.appendByte(amf::NULL_AMF0); amf::write(buf, subscribepath); nc.queueCall(cn, "FCSubscribe"); r.call(buf); }
// Which channel to send on? Always video? //ASnative(2101, 202)(this, "play", null, name, start * 1000, len * 1000, reset); // This call is not queued (it's a play call, and doesn't have a callback). void sendPlayPacket(rtmp::RTMP& r, FakeNC& nc) { const int streamid = nc.streamID(); const double seektime = nc.seekTime() * 1000.0; const double length = nc.length() * 1000.0; log_debug("Sending play packet. Stream id: %s, playpath %s", streamid, nc.playpath()); SimpleBuffer buf; amf::write(buf, "play"); // What is this? The play stream? Call number? amf::write(buf, 0.0); buf.appendByte(amf::NULL_AMF0); log_debug( "seekTime=%.2f, dLength=%d, sending play: %s", seektime, length, nc.playpath()); amf::write(buf, nc.playpath()); // Optional parameters start and len. // // start: -2, -1, 0, positive number // -2: looks for a live stream, then a recorded stream, if not found // any open a live stream // -1: plays a live stream // >=0: plays a recorded streams from 'start' milliseconds amf::write(buf, seektime); // len: -1, 0, positive number // -1: plays live or recorded stream to the end (default) // 0: plays a frame 'start' ms away from the beginning // >0: plays a live or recoded stream for 'len' milliseconds //enc += EncodeNumber(enc, -1.0); // len amf::write(buf, length); r.play(buf, streamid); }
void sendPausePacket(rtmp::RTMP& r, FakeNC& nc, bool flag, double time) { const int streamid = nc.streamID(); SimpleBuffer buf; amf::write(buf, "pause"); // What is this? The play stream? Call number? amf::write(buf, 0.0); buf.appendByte(amf::NULL_AMF0); log_debug( "Pause: flag=%s, time=%d", flag, time); amf::write(buf, flag); // "this.time", i.e. NetStream.time. amf::write(buf, time * 1000.0); r.play(buf, streamid); }
/// These functions create an RTMP call buffer and send it. They mimic /// NetConnection.call() methods and replies to server calls. // /// If a call is initiated by us, we send our own call number. /// If we are replying to a server call, we send the server's call number back. void sendConnectPacket(rtmp::RTMP& r, FakeNC& nc, const std::string& app, const std::string& ver, const std::string& swfurl, const std::string& tcurl, const std::string& pageurl) { log_debug("Sending connect packet."); log_debug("app : %s", app); log_debug("flashVer : %s", ver); log_debug("tcURL : %s", tcurl); log_debug("swfURL : %s", swfurl); log_debug("pageURL : %s", pageurl); SimpleBuffer buf; amf::write(buf, "connect"); const size_t cn = nc.callNumber(); /// Call number? amf::write(buf, static_cast<double>(cn)); buf.appendByte(amf::OBJECT_AMF0); if (!app.empty()) amf::writeProperty(buf, "app", app); if (!ver.empty()) amf::writeProperty(buf, "flashVer", ver); if (!swfurl.empty()) amf::writeProperty(buf, "swfUrl", swfurl); if (!tcurl.empty()) amf::writeProperty(buf, "tcUrl", tcurl); amf::writeProperty(buf, "fpad", false); amf::writeProperty(buf, "capabilities", 15.0); amf::writeProperty(buf, "audioCodecs", 3191.0); amf::writeProperty(buf, "videoCodecs", 252.0); amf::writeProperty(buf, "videoFunction", 1.0); if (!pageurl.empty()) amf::writeProperty(buf, "pageUrl", pageurl); buf.appendByte(0); buf.appendByte(0); buf.appendByte(amf::OBJECT_END_AMF0); nc.queueCall(cn, "connect"); r.call(buf); }
bool HTTPRemotingHandler::advance() { #ifdef GNASH_DEBUG_REMOTING log_debug("advancing HTTPRemotingHandler"); #endif if (_connection) { #ifdef GNASH_DEBUG_REMOTING log_debug("have connection"); #endif // Fill last chunk before reading in the next size_t toRead = reply.capacity() - reply.size(); if (! toRead) toRead = NCCALLREPLYCHUNK; #ifdef GNASH_DEBUG_REMOTING log_debug("Attempt to read %d bytes", toRead); #endif // See if we need to allocate more bytes for the next // read chunk if (reply.capacity() < reply.size() + toRead) { // if _connection->size() >= 0, reserve for it, so // if HTTP Content-Length response header is correct // we'll be allocating only once for all. const size_t newCapacity = reply.size() + toRead; #ifdef GNASH_DEBUG_REMOTING log_debug("NetConnection.call: reply buffer capacity (%d) " "is too small to accept next %d bytes of chunk " "(current size is %d). Reserving %d bytes.", reply.capacity(), toRead, reply.size(), newCapacity); #endif reply.reserve(newCapacity); #ifdef GNASH_DEBUG_REMOTING log_debug(" after reserve, new capacity is %d", reply.capacity()); #endif } const int read = _connection->readNonBlocking(reply.data() + reply.size(), toRead); if (read > 0) { #ifdef GNASH_DEBUG_REMOTING log_debug("read '%1%' bytes: %2%", read, hexify(reply.data() + reply.size(), read, false)); #endif reply.resize(reply.size() + read); } // There is no way to tell if we have a whole amf reply without // parsing everything // // The reply format has a header field which specifies the // number of bytes in the reply, but potlatch sends 0xffffffff // and works fine in the proprietary player // // For now we just wait until we have the full reply. // // FIXME make this parse on other conditions, including: 1) when // the buffer is full, 2) when we have a "length in bytes" value // thas is satisfied if (_connection->bad()) { log_debug("connection is in error condition, calling " "NetConnection.onStatus"); reply.resize(0); reply_start = 0; // reset connection before calling the callback _connection.reset(); // This is just a guess, but is better than sending // 'undefined' _nc.notifyStatus(NetConnection_as::CALL_FAILED); } else if (_connection->eof()) { if (reply.size() > 8) { #ifdef GNASH_DEBUG_REMOTING log_debug("hit eof"); #endif boost::uint16_t li; const boost::uint8_t *b = reply.data() + reply_start; const boost::uint8_t *end = reply.data() + reply.size(); amf::Reader rd(b, end, getGlobal(_nc.owner())); // parse header b += 2; // skip version indicator and client id // NOTE: this looks much like parsing of an OBJECT_AMF0 boost::int16_t si = readNetworkShort(b); b += 2; // number of headers uint8_t headers_ok = 1; if (si != 0) { #ifdef GNASH_DEBUG_REMOTING log_debug("NetConnection::call(): amf headers " "section parsing"); #endif as_value tmp; for (size_t i = si; i > 0; --i) { if(b + 2 > end) { headers_ok = 0; break; } si = readNetworkShort(b); b += 2; // name length if(b + si > end) { headers_ok = 0; break; } std::string headerName((char*)b, si); // end-b); #ifdef GNASH_DEBUG_REMOTING log_debug("Header name %s", headerName); #endif b += si; if ( b + 5 > end ) { headers_ok = 0; break; } b += 5; // skip past bool and length long if(!rd(tmp)) { headers_ok = 0; break; } #ifdef GNASH_DEBUG_REMOTING log_debug("Header value %s", tmp); #endif { // method call for each header // FIXME: it seems to me that the call should happen VM& vm = getVM(_nc.owner()); string_table& st = vm.getStringTable(); string_table::key key = st.find(headerName); #ifdef GNASH_DEBUG_REMOTING log_debug("Calling NetConnection.%s(%s)", headerName, tmp); #endif callMethod(&_nc.owner(), key, tmp); } } } if(headers_ok == 1) { si = readNetworkShort(b); b += 2; // number of replies // TODO consider counting number of replies we // actually parse and doing something if it // doesn't match this value (does it matter? if(si > 0) { // parse replies until we get a parse error or we reach the end of the buffer while(b < end) { if(b + 2 > end) break; si = readNetworkShort(b); b += 2; // reply length if(si < 4) { // shorted valid response is '/1/a' log_error("NetConnection::call(): reply message name too short"); break; } if(b + si > end) break; // Reply message is: '/id/methodName' int ns = 1; // next slash position while (ns<si-1 && *(b+ns) != '/') ++ns; if ( ns >= si-1 ) { std::string msg( reinterpret_cast<const char*>(b), si); log_error("NetConnection::call(): invalid " "reply message name (%s)", msg); break; } std::string id(reinterpret_cast<const char*>(b), ns); std::string methodName( reinterpret_cast<const char*>(b+ns+1), si-ns-1); b += si; // parse past unused string in header if(b + 2 > end) break; si = readNetworkShort(b); b += 2; // reply length if(b + si > end) break; b += si; // this field is supposed to hold the // total number of bytes in the rest of // this particular reply value, but // openstreetmap.org (which works great // in the adobe player) sends // 0xffffffff. So we just ignore it if(b + 4 > end) break; li = readNetworkLong(b); b += 4; // reply length #ifdef GNASH_DEBUG_REMOTING log_debug("about to parse amf value"); #endif // this updates b to point to the next unparsed byte as_value replyval; if (!rd(replyval)) { log_error("parse amf failed"); // this will happen if we get // bogus data, or if the data runs // off the end of the buffer // provided, or if we get data we // don't know how to parse break; } #ifdef GNASH_DEBUG_REMOTING log_debug("parsed amf"); #endif // update variable to show how much we've parsed reply_start = b - reply.data(); // if actionscript specified a callback object, // call it as_object* callback = pop_callback(id); if (callback) { string_table::key methodKey; if ( methodName == "onResult" ) { methodKey = NSV::PROP_ON_RESULT; } else if (methodName == "onStatus") { methodKey = NSV::PROP_ON_STATUS; } else { // NOTE: the pp is known to actually // invoke the custom method, but with 7 // undefined arguments (?) log_error("Unsupported HTTP Remoting " "response callback: '%s' " "(size %d)", methodName, methodName.size()); continue; } #ifdef GNASH_DEBUG_REMOTING log_debug("calling onResult callback"); #endif // FIXME check if above line can fail and we // have to react callMethod(callback, methodKey, replyval); #ifdef GNASH_DEBUG_REMOTING log_debug("callback called"); #endif } else { log_error("Unknown HTTP Remoting response identifier '%s'", id); } } } } } else { log_error("Response from remoting service < 8 bytes"); } #ifdef GNASH_DEBUG_REMOTING log_debug("deleting connection"); #endif _connection.reset(); reply.resize(0); reply_start = 0; } } if(!_connection && queued_count > 0) { //#ifdef GNASH_DEBUG_REMOTING log_debug("creating connection"); //#endif // set the "number of bodies" header (reinterpret_cast<boost::uint16_t*>(_postdata.data() + 4))[0] = htons(queued_count); std::string postdata_str(reinterpret_cast<char*>(_postdata.data()), _postdata.size()); #ifdef GNASH_DEBUG_REMOTING log_debug("NetConnection.call(): encoded args from %1% calls: %2%", queued_count, hexify(postdata.data(), postdata.size(), false)); #endif queued_count = 0; // TODO: it might be useful for a Remoting Handler to have a // StreamProvider member const StreamProvider& sp = getRunResources(_nc.owner()).streamProvider(); _connection.reset(sp.getStream(_url, postdata_str, _headers).release()); _postdata.resize(6); #ifdef GNASH_DEBUG_REMOTING log_debug("connection created"); #endif } if (_connection == 0) { // nothing more to do return false; } return true; }
/// Returns false if the data cannot be written to file. // /// If there is no data, the file is removed and the function returns true. bool SharedObject_as::flush(int space) const { /// This is called on on destruction of the SharedObject, or (allegedly) /// on a call to SharedObject.data, so _data is not guaranteed to exist. // /// The function should never be called from SharedObject.flush() when /// _data is 0. if (!_data) return false; if (space > 0) { log_unimpl("SharedObject.flush() called with a minimum disk space " "argument (%d), which is currently ignored", space); } const std::string& filespec = getFilespec(); if (!mkdirRecursive(filespec)) { log_error("Couldn't create dir for flushing SharedObject %s", filespec); return false; } #ifdef USE_SOL_READONLY log_debug(_("SharedObject %s not flushed (compiled as read-only mode)"), filespec); return false; #endif if (rcfile.getSOLReadOnly()) { log_security("Attempting to write object %s when it's SOL " "Read Only is set! Refusing...", filespec); return false; } // Open file std::ofstream ofs(filespec.c_str(), std::ios::binary); if (!ofs) { log_error("SharedObject::flush(): Failed opening file '%s' in " "binary mode", filespec.c_str()); return false; } // Encode data part. SimpleBuffer buf; if (!encodeData(_name, *_data, buf)) { std::remove(filespec.c_str()); return true; } // Encode header part. SimpleBuffer header; encodeHeader(buf.size(), header); // Write header ofs.write(reinterpret_cast<const char*>(header.data()), header.size()); if (!ofs) { log_error("Error writing SOL header"); return false; } // Write AMF data ofs.write(reinterpret_cast<const char*>(buf.data()), buf.size()); if (!ofs) { log_error("Error writing %d bytes to output file %s", buf.size(), filespec.c_str()); return false; } ofs.close(); log_security("SharedObject '%s' written to filesystem.", filespec); return true; }