root/cygnal/libamf/lcshm.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. _baseaddr
  2. findListener
  3. addListener
  4. removeListener
  5. listListeners
  6. close
  7. parseHeader
  8. formatHeader
  9. connect
  10. connect
  11. send
  12. dump

//
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
//   2011 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 <cerrno>
#include <vector>
#include <string>
#include <cstring>
#include <boost/cstdint.hpp>
#include <boost/shared_ptr.hpp>

#include "log.h"
#include "buffer.h"
//#include "network.h"
#include "amf.h"
#include "SharedMem.h"
#include "element.h"
#include "GnashException.h"
#include "lcshm.h"

#ifndef VECTOR
#include <vector>
#endif

using std::string;
using std::vector;
using gnash::log_debug;
using gnash::log_error;

// Some facts:
//     * The header is 16 bytes,
//     * The message can be up to 40k,
//     * The listeners block starts at 40k+16 = 40976 bytes,
//     * To add a listener, simply append its name in the listeners list (null terminated strings)

/// \namespace cygnal
///
/// This namespace is for all the AMF specific classes in libamf.
namespace cygnal
{

// The maximum 
// although a bool is one byte, it appears to be a short in AMF,
// plus the type byte.
const int AMF_BOOLEAN_SIZE = 3;

/// \var LC_HEADER_SIZE
///     The header size for a memory segment.
const int LC_HEADER_SIZE = 16;

/// \var MAX_LC_HEADER_SIZE
///     The maximum size allowed for the header of a memory segment.
const int MAX_LC_HEADER_SIZE = 40960;

/// \var LC_LISTENERS_START
///     The starting address for the block of Listeners in the memory
///     segment.
const int LC_LISTENERS_START  = MAX_LC_HEADER_SIZE +  LC_HEADER_SIZE;
//LC_LISTENERS_START equals to 40976.

/// \def MAXHOSTNAMELEN
///     This doesn't exist on all systems, but here's the value used on Unix.
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN 64
#endif

/// \define ENSUREBYTES
///
/// @param from The base address to check.
///
/// @param tooFar The ending address that is one byte too many.
///
/// @param size The number of bytes to check for: from to tooFar.
///
/// @remarks May throw an Exception
#define ENSUREBYTES(from, toofar, size) { \
        if ( from+size >= toofar ) \
                throw gnash::ParserException("Premature end of AMF stream"); \
}

/// \brief Construct an uninitialized shared memory segment.
///     Open a connection between two SWF movies so they can send
///     each other Flash Objects, but does not initialize the memory
///     segment.
LcShm::LcShm() 
    :
    SharedMem(64528),
    _baseaddr(0)
{
//    GNASH_REPORT_FUNCTION;
}

/// \brief Construct an initialized shared memory segment.
///
/// @param addr The address to use for the memory segment.
LcShm::LcShm(boost::uint8_t *addr)
    :
    SharedMem(64528)
{
//    GNASH_REPORT_FUNCTION;
    _baseaddr = addr;
}

/// \brief Construct an initialized shared memory segment.
///
/// @param key The SYSV style key to use for the memory segment.
LcShm::LcShm(key_t /*key*/)
    :
    SharedMem(64528)
{
//    GNASH_REPORT_FUNCTION;
}

/// \brief Delete the shared memory segment.
///
/// @remark This does not clear the content of the memory segment.
LcShm::~LcShm()
{
//    GNASH_REPORT_FUNCTION;    
}

/// \brief Construct a block of Listeners.
///     This constructs an uninitialized Listener block.
Listener::Listener()
    : _baseaddr(0)
{
//    GNASH_REPORT_FUNCTION;
}

/// \brief Construct a block Listeners at the specified address.
///
/// @param baseaddr The address to use for the block of
///     Listeners.
Listener::Listener(boost::uint8_t *x)
{
//    GNASH_REPORT_FUNCTION;
    _baseaddr = x;
}

/// \brief Delete the Listener block
Listener::~Listener()
{
//    GNASH_REPORT_FUNCTION;
}

/// \brief See if a connection name exists in our list of Listeners
///
/// @param name An ASCII string that is the name of the Listener
///             to search for.
///
/// @return true if this succeeded. false if it doesn't.
bool
Listener::findListener(const string &name)
{
//    GNASH_REPORT_FUNCTION;

    boost::uint8_t *addr = _baseaddr + LC_LISTENERS_START;
    char *item = reinterpret_cast<char *>(addr);
    // Walk through the list to the end
    while (*item != 0) {
        if (name == item)       {
            return true;
        }
        item += strlen(item)+8+1;
                
                //Si has rewritten thise, please test before using.
                //This version should be right now.
    }
    
    return false;
}

/// \brief Create a new Listener in the memory segment.
///
/// @param name The name for the Listener.
///
/// @return true if this succeeded. false if it doesn't.
bool
Listener::addListener(const string &name)
{
    GNASH_REPORT_FUNCTION;

    boost::uint8_t *addr = _baseaddr + LC_LISTENERS_START;
    char *item = reinterpret_cast<char *>(addr);

         if (findListener(name)) {
        return true;
    }

    // Walk to the end of the list
    while ((item[0] != 0) && (item[1] != 0)) {
        item += strlen(item)+1;
                // This is not the proper way.  But it works now.       
    }
    
    // Add ourselves to the list
    if (memcpy(item, name.c_str(), name.size()) == 0) {
        return false;
    }

    // Add the two mystery two strings or number that follows the name.
    // These vary somewhat, but as test cases produces these values, we'll
    // use them till we're sure what these actually represent.
    
        item += name.size() + 1;
    const char *x1 = "::3";
    if (!memcpy(item, x1, 4)) {
        return false;
    }
    item += 4;
    const char *x2 = "::2";
    if (!memcpy(item, x2, 4)) {
        return false;
    }
        // Si: I have tested more swf files from the internet
        // Not all of them are 3 and 2. 
        // Could be 3 2, 4 1, 3 1, etc.
        
        //Si has rewritten thise, please test before using.
        //This version should be right now.
    
    return true;
}

/// \brief Remove the Listener for this Object.
///
/// @param name An ASCII string that is the name of the Listener
///             to remove from the  memory segment..
///
/// @return true if this succeeded. false if it doesn't.
///
/// @remark
///     I don't believe this function is support by other swf players,
///     but we do, as it's nice to remove oneself from the listeners
///     list so nobody knows we were here listening.
bool
Listener::removeListener(const string &name)
{
    GNASH_REPORT_FUNCTION;

    boost::uint8_t *addr = _baseaddr + LC_LISTENERS_START;

    int len = 0;
        int dest= 0;    
        int source =0;
    char *item = reinterpret_cast<char *>(addr);
    while (*item != 0) {
        if (name == item) {         
                        len =strlen(item) +8+1; //The length of the removed string, including '\n'                              
                        
                        while (*item != 0) {
                
                                // len = strlen(item) + 8 + 1;
                // strcpy(item, item + len);
                // item += len + strlen(item + len);
                                // These old codes are wrong !!!!  
                                // Rewrite them!!!
                                
                                if (source!=0)
                                        dest  += strlen(item+source)+8+1;
                                                                        
                                source+= strlen(item+dest)+8+1;
                                strcpy(item+dest,item+source);          
            }
                                                                                  
            memset(item+dest+strlen(item+source)+8+1, 0, len);
            return true;
        }
        //item += strlen(item) + 1;
                //This is not right.
                
                item += strlen(item) + 8 + 1;
                //You will only know weather this funtion is right or not after you really remove some listeneres.
                //Si has rewritten thise, please test before using.
                //This version should be right now.
                
        }
    
    return false;
}

/// \brief List the Listeners for this memory segment.
///
/// @return A smart pointer to a vector of Listener names.
///
/// @remarks This is only used for debugging
std::auto_ptr< vector<string> >
Listener::listListeners()
{
//    GNASH_REPORT_FUNCTION;    
    std::auto_ptr< vector<string> > listeners ( new vector<string> );
    if (_baseaddr != 0) {
        boost::uint8_t *addr = _baseaddr + LC_LISTENERS_START;
        
        const char *item = reinterpret_cast<const char *>(addr);
        while (*item != 0) {
            if (item[0] != ':') {
                listeners->push_back(item);
            }
            item += strlen(item) + 1;
                        // This is not very effective but looks right.
                        // A better way should be jump to the next listener directly.
        }
    }

    return listeners;
}

/// \brief Close a memory segment.
///             This closes the shared memory segment, but the data
///             remains until the next reboot of the computer.
///
/// @return nothing.    
void
LcShm::close()
{
    GNASH_REPORT_FUNCTION;
}

/// @note
///     From what I can tell by exaimining the memory segment, after
///     the raw 16 bytes is a LocalConnection object. This appears to
///     have the following data types:
/// String - This appears to the connection name, and looks like
///          "localhost:lc_replay"
/// String - This appears to be the hostname of the connection, and at
///          least in my tests, has always been "localhost".
/// Boolean - In all the files I've looked at, this has always been
///           TRUE. I assume this is the domain security flag.
/// Si: This value could be false.
/// Number - No idea what this number represents.
/// Number - No idea what this number represents.
/// NULL terminator
///     AMF objects - this is followed by the AMF objects that have
///     been added to the LocalConnection. This can be up to 40k
///     long. While other web sites have claimed there is a length
///     field in the initial shared memory segment header, I've never
///     seen one in my tests.

/// \brief Parse the header of the memory segment.
///
/// @param data real pointer to start parsing from.
///
/// @param tooFar A pointer to one-byte-past the last valid memory
///             address within the buffer.
///
/// @return A real pointer to the data after the headers has been parsed.
///
/// @remarks May throw a ParserException
boost::uint8_t *
LcShm::parseHeader(boost::uint8_t *data, boost::uint8_t* tooFar)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = data;

    if (data == 0) {
        log_debug("No data pointer to parse!");
        return 0;
    }

#ifndef GNASH_TRUST_AMF
    ENSUREBYTES(ptr, tooFar, LC_HEADER_SIZE);
#endif
    
    memcpy(&_header, ptr, LC_HEADER_SIZE);
//    memcpy(&_object, data + LC_HEADER_SIZE, _header.length);
//    log_debug("Timestamp: %d", _header.timestamp);
//    log_debug("Length: %d", _header.length);
//    log_debug("Connection: %s", _object.connection_name);
//    log_debug("name: %s", _object.hostname);
    ptr += LC_HEADER_SIZE;
    
    AMF amf;
    boost::shared_ptr<Element> el = amf.extractAMF(ptr, tooFar);
    if (el == 0) {
        log_debug("Didn't extract an element from the byte stream!");
        return 0;
    }
    _object.connection_name = el->to_string();
    
    el = amf.extractAMF(ptr, tooFar);
    if (ptr != 0) {
        _object.hostname = el->to_string();
    }
    
//     el = new amf::Element;
//     ptr = amf.extractElement(el, ptr);
//     _object.domain = el->to_bool();
//     delete el;
    
//     el = new amf::Element;
//     ptr = amf.extractElement(el, ptr);
//     _object.unknown_num1 = el->to_number();
//     delete el;
    
//     el = new amf::Element;
//     ptr = amf.extractElement(el, ptr);
//     _object.unknown_num2 = el->to_number();
//     delete el;
    
//    memcpy(&_object, data + LC_HEADER_SIZE, _header.length);
//     log_debug("Connection: %s", _object.connection_name.c_str());
//     log_debug("name: %s", _object.hostname.c_str());
//     log_debug("domain: %s", (_object.domain) ? "true" : "false");
//     log_debug("unknown_num1: %f", _object.unknown_num1);
//     log_debug("unknown_num2: %f", _object.unknown_num2);
    
//    ptr += 3;                   // skip past the NULL terminator
    return ptr;
}

/// \brief Format the header for the memory segment.
///
/// @param con The name of the connection.
///
/// @param host The hostname of the connection, often "localhost"
///
/// @param domain The domain the hostname is in.
///
/// @return A real pointer to a header for a memory segment.
boost::uint8_t *
LcShm::formatHeader(const std::string &con, const std::string &host, bool /* domain */ )
{
//  GNASH_REPORT_FUNCTION;
//  boost::uint8_t *ptr = data + LC_HEADER_SIZE;
    int size = con.size() + host.size() + 9;

//    Buffer *buf;
    
//    Si:
//    Assign the value of header and ptr directly.
//    boost::uint8_t *header = new boost::uint8_t[size + 1];
//    boost::uint8_t *ptr = header;

    boost::uint8_t *header    = Listener::getBaseAddress();
    boost::uint8_t *ptr_FH    = Listener::getBaseAddress();
//      log_debug("Base address in 'formatHeader' is: 0x%x, 0x%x",(unsigned int) header, (unsigned int) ptr_FH);

    // This is the initial 16 bytes of the header
    memset(ptr_FH, 0, 16 + size + 1);
    *ptr_FH = 1;
    ptr_FH += 4;  
    //Si changes this value from 3 to 4.
    *ptr_FH = 1;
    ptr_FH = header + LC_HEADER_SIZE;
    //Do you want to assign the value of timestamp and message size?

//  Si has rewritten the following code.
//  The protocol is set to be localhost now. 
//  Make sure it is always right. Probably wrong.

    // Which is then always followed by 3 AMF objects.
    boost::shared_ptr<cygnal::Buffer> buf1 = AMF::encodeString(con);
    memcpy(ptr_FH, buf1->begin(), buf1->size());
    ptr_FH += buf1->size();

    const std::string protocol="localhost";
        // This could equal to the domain name.
    boost::shared_ptr<cygnal::Buffer> buf2 = AMF::encodeString(protocol);
    memcpy(ptr_FH, buf2->begin(), buf2->size());
    ptr_FH += buf2->size();

    boost::shared_ptr<cygnal::Buffer> buf3 = AMF::encodeString(host);
    memcpy(ptr_FH, buf3->begin(), buf3->size());
    ptr_FH += buf3->size();
    
    return ptr_FH;
}

/// \brief Connect to a memory segment.
///     Prepares the LcShm object to receive commands from a
///     LcShm.send() command.
///
/// @param name The name to use for POSIX shared memory, which is not
///             the default type used.
///
/// @return true if this succeeded. false if it doesn't.
///
/// @remarks The name is a symbolic name like "lc_name", that is used
///     by the send() command to signify which local connection to
///     send the object to.
bool
LcShm::connect(const string& names)
{
    //GNASH_REPORT_FUNCTION;

    //log_debug(" The connect function is called");     
        log_debug(" The size of %s is %d ",names, names.size()); 
        
        if (names == "")
                return false;
    
    _name = names;

    // the name here is optional, Gnash will pick a good default.
    // When using sysv shared memory segments in compatibility mode,
    // the name is ignored, and the SHMkey is specified in the user's
    // ~/.gnashrc file.
    if (SharedMem::attach() == false) {
        return false;
    }

    if (SharedMem::begin() <= 0) {
        log_error("Failed to open shared memory segment: \"%s\"", names.c_str());
        return false; 
    }
    
        boost::uint8_t* baseAddress = reinterpret_cast<boost::uint8_t *>(SharedMem::begin());
        
        boost::uint8_t* tooFar = SharedMem::end();
    Listener::setBaseAddress(baseAddress);
    _baseaddr = baseAddress;
    parseHeader(baseAddress, tooFar);
//      log_debug("Base address in 'connect' is: 0x%x, 0x%x",(unsigned int) SharedMem::begin(), (unsigned int) _baseaddr);
//  vector<boost::shared_ptr<Element> > ellist = parseBody(ptr);
//  log_debug("Base address is: 0x%x, 0x%x",
//               (unsigned int)Listener::getBaseAddress(), (unsigned int)_baseaddr);

    addListener(names);
        
//      Make sure this one is already connected
        setconnected(true);
//      system("ipcs");

    return true;
}

/// \brief Connect to a memory segment.
///
/// @param key The SYSV style key for the shared memory segment,
///     which is the default type used.
///
/// @return true if this succeeded. false if it doesn't.
bool
LcShm::connect(key_t key)
{
        boost::mutex::scoped_lock lock(_localconnection_mutex);
   // GNASH_REPORT_FUNCTION;
    
    if (SharedMem::attach() == false) {
        return false;
    }

    if (SharedMem::begin() <= 0) {
        log_error("Failed to open shared memory segment: 0x%x", key);
        return false; 
    }
    
        boost::uint8_t* baseAddress = reinterpret_cast<boost::uint8_t *>(SharedMem::begin());
        boost::uint8_t* tooFar = SharedMem::end();
    Listener::setBaseAddress(baseAddress);
    _baseaddr = baseAddress;
    parseHeader(baseAddress, tooFar);
//    vector<boost::shared_ptr<Element> > ellist = parseBody(ptr);
//     log_debug("Base address is: 0x%x, 0x%x",
//               (unsigned int)Listener::getBaseAddress(), (unsigned int)_baseaddr);
    
    return true;
}

/// \brief Put data in the memory segment
///             This puts data into the memory segment
///
/// @param name The connection name for this connection
///
/// @param dataname The name of the data to send.
///
/// @param data A vector of smart pointers to the AMF0 Elements
///             contaiing the data for this memory segment.
///
/// @return nothing.

// Si have rewrittten all of these!
// We test several test cases, and it looks like the memory can be written in the right way.
// Please make further check after 'receiving' is completed.

void
LcShm::send(const string&  name , const string&  domainname ,
            vector<cygnal::Element* >& data )
{
    //GNASH_REPORT_FUNCTION;
    boost::mutex::scoped_lock lock(_localconnection_mutex);
        
        std::vector<cygnal::Element* >::iterator iter;
                   
        if (!Listener::getBaseAddress()) return;

    //The base address
     boost::uint8_t *baseptr = Listener::getBaseAddress();         
     boost::uint8_t *ptr = baseptr;     

// Compute the time
// Please check before use.
// Put this value into the header if necessary.
//      int timestamp=GetTickCount();
        
// Compute the size of the message.
// Please check before use.
// Put this value into the header if necessary.
      int message_size=0;
      if (data.size()!=0){      
                   for(iter = data.begin(); iter != data.end(); iter++){
                            boost::shared_ptr<Buffer> buf = AMF::encodeElement(*iter);                                                                  
                                message_size+=buf->size();
                        }
        }       
         

// This function write the first 16 bytes and the following three messages into the memory.
// ptr should be moved
// ptr=formatHeader(name, domainname, _object.domain);
// The ptr is now pointing to the start of the message
        
        int size = name.size() + domainname.size() + 9;
    // This is the initial 16 bytes of the header
    memset(ptr, 0, 16 + size + 1);
    *ptr = 1;
    ptr += 4;
    //Si changes this value from 3 to 4.
    *ptr = 1;
    ptr = baseptr + LC_HEADER_SIZE;

//  Si has rewritten the following code.
//  duplicate as in formatheader
//  The protocol is set to be localhost now. 
//  Make sure it is right later.

    // Which is then always followed by 3 AMF objects.
    boost::shared_ptr<cygnal::Buffer> buf1 = AMF::encodeString(name);
    memcpy(ptr, buf1->begin(), buf1->size());
    ptr += buf1->size();
        
    const std::string protocol="localhostf";
    boost::shared_ptr<cygnal::Buffer> buf2 = AMF::encodeString(protocol);
    memcpy(ptr, buf2->begin(), buf2->size());
    ptr += buf2->size();

    boost::shared_ptr<cygnal::Buffer> buf3 = AMF::encodeString(domainname);
    memcpy(ptr, buf3->begin(), buf3->size());
    ptr += buf3->size();
        
//Put the date into memory when it is not empty

        log_debug(_(" ***** The size of the data is %s *****"),data.size() ); 
      if (data.size()==0){                
                   for(iter = data.begin(); iter != data.end(); iter++){
                                // temporary buf for element
                                boost::shared_ptr<Buffer> buf = AMF::encodeElement(*iter);              
                                memcpy(ptr, buf->begin(), buf->size() );
                                ptr+= buf->size();              
                        }
        }       
        
// Update the connection name
           
#if 0
//     boost::uint8_t *tmp = AMF::encodeElement(name.c_str());
//     memcpy(ptr, tmp, name.size());
//     ptr +=  name.size() + AMF_HEADER_SIZE;
//     delete[] tmp;

//     tmp = AMF::encodeElement(domainname.c_str());
//     memcpy(ptr, tmp, domainname.size());
//     ptr +=  domainname.size() + AMF_HEADER_SIZE;

//    ptr += LC_HEADER_SIZE;
//    boost::uint8_t *x = ptr;    // just for debugging from gdb. temporary

    // This is the initial 16 bytes of the header
    memset(ptr, 0, LC_HEADER_SIZE + 200);
    *buf->at(0) = 1;
//    *ptr = 1;
    ptr += 4;
    *buf->at(4) = 1;
//    *ptr = 1;
    ptr += LC_HEADER_SIZE - 4;
    // Which is then always followed by 3 AMF objects.
    
    Buffer *tmp = AMF::encodeElement(name.c_str());
    memcpy(ptr, tmp, name.size() + AMF_HEADER_SIZE);
    delete[] tmp;

    ptr += name.size() + AMF_HEADER_SIZE;

    // Update the host on the other end of the connection.
    tmp = AMF::encodeElement(domainname.c_str());
    memcpy(ptr, tmp, domainname.size() + AMF_HEADER_SIZE );
    delete[] tmp;

    ptr += domainname.size() + AMF_HEADER_SIZE;

// //  Set the domain flag to whatever it's current value is.
// //  Element domain(_object.domain);
//     tmp = AMF::encodeBoolean(_object.domain);
//     memcpy(ptr, tmp, AMF_BOOLEAN_SIZE);
// //  delete[] tmp;
    
//     ptr += AMF_BOOLEAN_SIZE;
    
    vector<boost::uint8_t> *vec = AMF::encodeElement(data);
    vector<boost::uint8_t>::iterator vit;
    // Can't do a memcpy with a std::vector
//    log_debug("Number of bytes in the vector: %x", vec->size());
    for (vit = vec->begin(); vit != vec->end(); vit++) {
        *ptr = *vit;
#if 0                           // debugging crapola
        if (isalpha(*ptr))
            printf("%c ", *ptr);
        else
            printf("0x%x ", *ptr);
#endif
        ptr++;
    }
//    delete[] tmp;
#endif
    
//      system("ipcs");
       return;
}






/// \brief Read the date from the memory
///
/// @param dataname The name of the data to read.
///
/// @param data A vector of smart pointers to the AMF0 Elements in
///             this memory segment.
///
/// @return nothing.
/// We may only need a connection name for the receive function.
///void recv(std::string &name, std::string &dataname, boost::shared_ptr<cygnal::Element> data)
//{
         //GNASH_REPORT_FUNCTION;

///     log_debug(_(" ***** The recv function is called *****") );

///  TODO:
///  This function should at do the following work:
///  1: Lock the shared memory
///                     boost::mutex::scoped_lock lock(_localconnection_mutex);
///  2: Check if the current object is the listener
///         if findListener()
///             Make sure the object is the listener for certain connection name
///  2: Parse the header
///         boost::uint8_t *parseHeader(boost::uint8_t *data, boost::uint8_t* tooFar);
///             This should be easy if parseHeader function has been finished.
///  3: Parse the body of the shared memory
///         std::vector<boost::shared_ptr<cygnal::Element> > parseBody(boost::uint8_t *data);
///             This should be easy if parseHeader function has been finished.
///  4: The listened should implement these commands somehow automatically .
///         Handler?

///     return; 
//      }


///  \brief Dump the internal data of this class in a human readable form.
/// @remarks This should only be used for debugging purposes.
void
LcShm::dump()
{
//    GNASH_REPORT_FUNCTION;

//     cerr <<"Timestamp: " << _header.timestamp << endl;
//     cerr << "Length: " << _header.length << endl;
    using namespace std;
    cerr << "Connection Name:\t" << _object.connection_name << endl;
    cerr << "Hostname Name:\t\t" << _object.hostname << endl;
    cerr << "Domain Allowed:\t\t" << ((_object.domain) ? "true" : "false") << endl;
    vector<boost::shared_ptr<Element> >::iterator ait;
    cerr << "# of Elements in file: " << _amfobjs.size() << endl;
    for (ait = _amfobjs.begin(); ait != _amfobjs.end(); ait++) {
        boost::shared_ptr<Element> el = (*(ait));
        el->dump();
    }

    vector<string>::const_iterator lit;
    auto_ptr< vector<string> > listeners ( listListeners() );
    cerr << "# of Listeners in file: " << listeners->size() << endl;
    for (lit=listeners->begin(); lit!=listeners->end(); lit++) {
        string str = *lit;
        if (str[0] != ':') {
            cerr << "Listeners:\t" << str << endl;
        }
    }
}

} // end of amf namespace

// local Variables:
// mode: C++
// indent-tabs-mode: t
// End:

/* [<][>][^][v][top][bottom][index][help] */