root/shell/ByteArrayGlue.cpp
/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- ThrowMemoryError
 - Grow
 - Push
 - Push
 - SetLength
 - NotifySubscribers
 - addSubscriber
 - removeSubscriber
 - DataOutput
 - Available
 - SetLength
 - Read
 - Write
 - m_byteArray
 - getUintProperty
 - setUintProperty
 - getAtomProperty
 - setAtomProperty
 - hasAtomProperty
 - setLength
 - get_length
 - set_length
 - get_position
 - get_bytesAvailable
 - set_position
 - _toString
 - readByte
 - readUnsignedByte
 - readShort
 - readUnsignedShort
 - readInt
 - readUnsignedInt
 - readFloat
 - readDouble
 - readBoolean
 - writeBoolean
 - writeByte
 - writeShort
 - writeInt
 - writeUnsignedInt
 - writeFloat
 - writeDouble
 - zlib_compress
 - zlib_uncompress
 - checkNull
 - writeBytes
 - readBytes
 - readUTF
 - readUTFBytes
 - writeUTF
 - writeUTFBytes
 - fill
 - createInstance
 - readFile
 - get_endian
 - set_endian
 - writeFile
 
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is [Open Source Virtual Machine.].
 *
 * The Initial Developer of the Original Code is
 * Adobe System Incorporated.
 * Portions created by the Initial Developer are Copyright (C) 2004-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Adobe AS3 Team
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
#include "avmshell.h"
#include "genericzlib.h"
namespace avmshell
{
        //
        // ByteArray
        //
        
        ByteArray::ByteArray()
        {
                m_subscriberRoot = NULL;
                m_capacity = 0;
                m_length   = 0;
                m_array    = NULL;
        }
        ByteArray::ByteArray(const ByteArray &lhs) 
        // GCC will warn if we don't explicitly init GlobalMemoryProvider, 
        // even though it has no fields or ctor... sigh
        : GlobalMemoryProvider()
        {
                m_subscriberRoot = NULL;
                m_array    = mmfx_new_array(uint8_t, lhs.m_length);
                if (!m_array)
                {
                        ThrowMemoryError();
                        return;
                }
                m_capacity = lhs.m_length;
                m_length   = lhs.m_length;
                VMPI_memcpy(m_array, lhs.m_array, m_length);
        }
        ByteArray::~ByteArray()
        {
                m_subscriberRoot = NULL;
                if (m_array)
                {
                        mmfx_delete_array(m_array);
                        m_array = NULL;
                }
                m_capacity = 0;
                m_length = 0;
        }
        void ByteArray::ThrowMemoryError()
        {
                // todo throw out of memory exception
                // m_toplevel->memoryError->throwError(kOutOfMemoryError);
        }
        bool ByteArray::Grow(uint32_t minimumCapacity)
        {
                if (minimumCapacity > m_capacity)
                {
                        uint32_t newCapacity = m_capacity << 1;                 
                        if (newCapacity < minimumCapacity)
                        {
                                newCapacity = minimumCapacity;
                        }
                        if (newCapacity < kGrowthIncr) 
                        {
                                newCapacity = kGrowthIncr;
                        }
                        U8 *newArray = mmfx_new_array(uint8_t, newCapacity);
                        if (!newArray)
                        {
                                return false;
                        }
                        if (m_array)
                        {
                                VMPI_memcpy(newArray, m_array, m_length);
                                mmfx_delete_array(m_array);
                        }
                        VMPI_memset(newArray+m_length, 0, newCapacity-m_capacity);
                        m_array = newArray;
                        m_capacity = newCapacity;
                        NotifySubscribers();
                }
                return true;
        }
                
        U8 ByteArray::operator[] (uint32_t index) const
        {
                if (m_length <= index)
                {
                        return 0;
                }
                return m_array[index];
        }
        U8& ByteArray::operator[] (uint32_t index)
        {
                if (m_length <= index)
                {
                        Grow(index+1);
                        m_length = index+1;
                        NotifySubscribers();
                }
                return m_array[index];
        }
        void ByteArray::Push(U8 value)
        {
                if (m_length >= m_capacity)
                {
                        Grow(m_length + 1);
                }
                m_array[m_length++] = value;
                NotifySubscribers();
        }
                
        void ByteArray::Push(const U8 *data, uint32_t count)
        {
                Grow(m_length + count);
                VMPI_memcpy(m_array + m_length, data, count);
                m_length += count;
                NotifySubscribers();
        }
        
        void ByteArray::SetLength(uint32_t newLength)
        {
                if(m_subscriberRoot && m_length < Domain::GLOBAL_MEMORY_MIN_SIZE)
                        ThrowMemoryError();
                if (newLength > m_capacity)
                {
                        if (!Grow(newLength))
                        {
                                ThrowMemoryError();
                                return;
                        }
                }
                m_length = newLength;
                NotifySubscribers();
        }
        void ByteArray::NotifySubscribers()
        {
                SubscriberLink *curLink = m_subscriberRoot;
                SubscriberLink **prevNext = &m_subscriberRoot;
 
                while(curLink != NULL) // notify subscribers
                {
                        AvmAssert(m_length >= Domain::GLOBAL_MEMORY_MIN_SIZE);
 
                        GlobalMemorySubscriber* subscriber = (GlobalMemorySubscriber*)curLink->weakSubscriber->get();
 
                        if (subscriber)
                        {
                                subscriber->notifyGlobalMemoryChanged(m_array, m_length);
                                prevNext = &curLink->next;
                        }
                        else
            {
                                // Domain went away? remove link
                                MMgc::GC::WriteBarrier(prevNext, curLink->next);
            }
                        curLink = curLink->next;
                }
        }
 
        bool ByteArray::addSubscriber(GlobalMemorySubscriber* subscriber)
        {
                if(m_length >= Domain::GLOBAL_MEMORY_MIN_SIZE)
                {
                        removeSubscriber(subscriber);
                        SubscriberLink *newLink = new (MMgc::GC::GetGC(subscriber)) SubscriberLink;
                        newLink->weakSubscriber = subscriber->GetWeakRef();
                        MMgc::GC::WriteBarrier(&newLink->next, m_subscriberRoot);
                        MMgc::GC::WriteBarrier(&m_subscriberRoot, newLink);
                        // notify the new "subscriber" of the current state of the world
                        subscriber->notifyGlobalMemoryChanged(m_array, m_length);
                        return true;
                }
                return false;
        }
 
        bool ByteArray::removeSubscriber(GlobalMemorySubscriber* subscriber)
        {
                SubscriberLink **prevNext = &m_subscriberRoot;
                SubscriberLink *curLink = m_subscriberRoot;
 
                while(curLink)
                {
                        if ((GlobalMemorySubscriber*)curLink->weakSubscriber->get() == subscriber)
                        {
                                MMgc::GC::WriteBarrier(prevNext, curLink->next);
                                return true;
                        }
                        prevNext = &curLink->next;
                        curLink = curLink->next;
                }
                return false;
        }
        //
        // ByteArrayFile
        //
        ByteArrayFile::ByteArrayFile(Toplevel *toplevel)
                : DataInput(toplevel),
                  DataOutput(toplevel)
        {
                m_filePointer = 0;
        }
        uint32_t ByteArrayFile::Available()
        {
                if (m_filePointer <= m_length) {
                        return m_length - m_filePointer;
                } else {
                        return 0;
                }
        }
        void ByteArrayFile::SetLength(uint32_t newLength)
        {
                ByteArray::SetLength(newLength);
                if (m_filePointer > newLength) {
                        m_filePointer = newLength;
                }
        }
        
        void ByteArrayFile::Read(void *buffer, uint32_t count)
        {
                CheckEOF(count);
                if (count > 0)
                {
                        VMPI_memcpy(buffer, m_array+m_filePointer, count);
                        m_filePointer += count;
                }
        }
        void ByteArrayFile::Write(const void *buffer, uint32_t count)
        {
                if (m_filePointer+count >= m_length) {
                        Grow(m_filePointer+count);
                        m_length = m_filePointer+count;
                }
                VMPI_memcpy(m_array+m_filePointer, buffer, count);
                m_filePointer += count;
        }
        //
        // ByteArrayObject
        //
        
        ByteArrayObject::ByteArrayObject(VTable *ivtable,
                                                                         ScriptObject *delegate)
                : ScriptObject(ivtable, delegate),
                  m_byteArray(toplevel())
        {
                c.set(&m_byteArray, sizeof(ByteArrayFile));
        }
        Atom ByteArrayObject::getUintProperty(uint32_t i) const
        {
                if (i < (uint32_t)m_byteArray.GetLength()) {
                        return core()->intToAtom(m_byteArray[i]);
                } else {
                        return undefinedAtom;
                }
        }
        
        void ByteArrayObject::setUintProperty(uint32_t i, Atom value)
        {
                m_byteArray[i] = (U8)(AvmCore::integer(value));
        }
        
        Atom ByteArrayObject::getAtomProperty(Atom name) const
        {
                uint32_t index;
                if (AvmCore::getIndexFromAtom(name, &index)) {
                        if (index < (uint32_t) m_byteArray.GetLength()) {
                                return core()->intToAtom(m_byteArray[index]);
                        } else {
                                return undefinedAtom;
                        }
                }
                return ScriptObject::getAtomProperty(name);
        }
        
        void ByteArrayObject::setAtomProperty(Atom name, Atom value)
        {
                uint32_t index;
                if (AvmCore::getIndexFromAtom(name, &index)) {
                        int intValue = AvmCore::integer(value);
                        m_byteArray[index] = (U8)(intValue);
                } else {
                        ScriptObject::setAtomProperty(name, value);
                }
        }
        
        bool ByteArrayObject::hasAtomProperty(Atom name) const
        {
                return ScriptObject::hasAtomProperty(name) || getAtomProperty(name) != undefinedAtom;
        }
        void ByteArrayObject::setLength(uint32_t newLength)
        {
                m_byteArray.SetLength(newLength);
        }
        uint32_t ByteArrayObject::get_length()
        {
                return m_byteArray.GetLength();
        }
        void ByteArrayObject::set_length(uint32_t value)
        {
                setLength(value);
        }
        int ByteArrayObject::get_position()
        {
                return m_byteArray.GetFilePointer();
        }
        int ByteArrayObject::get_bytesAvailable()
        {
                return m_byteArray.Available();
        }
        
        void ByteArrayObject::set_position(int offset)
        {
                if (offset >= 0) {
                        m_byteArray.Seek(offset);
                }
        }
        String* ByteArrayObject::_toString()
        {
                union {
                        uint8_t* c;
                        wchar* c_w;
                };
                c = (uint8_t*)m_byteArray.GetBuffer();
                uint32_t len = m_byteArray.GetLength();
                if (len >= 3)
                {
                        // UTF8 BOM
                        if ((c[0] == 0xef) && (c[1] == 0xbb) && (c[2] == 0xbf))
                        {
                                return core()->newStringUTF8((const char*)c + 3, len - 3);
                        }
                        else if ((c[0] == 0xfe) && (c[1] == 0xff))
                        {
                                //UTF-16 big endian
                                c += 2;
                                len = (len - 2) >> 1;
                                return core()->newStringEndianUTF16(/*littleEndian*/false, c_w, len);
                        }
                        else if ((c[0] == 0xff) && (c[1] == 0xfe))
                        {
                                //UTF-16 little endian
                                return core()->newStringEndianUTF16(/*littleEndian*/true, c_w, len);
                        }
                }
                // Use newStringUTF8() with "strict" explicitly set to false to mimick old,
                // buggy behavior, where malformed UTF-8 sequences are stored as single characters.
                return core()->newStringUTF8((const char*)c, len, false);
        }
        
        int ByteArrayObject::readByte()
        {
                return (signed char)m_byteArray.ReadU8();
        }
        int ByteArrayObject::readUnsignedByte()
        {
                return m_byteArray.ReadU8();
        }
        int ByteArrayObject::readShort()
        {
                return (short)m_byteArray.ReadU16();
        }
        int ByteArrayObject::readUnsignedShort()
        {
                return m_byteArray.ReadU16();
        }
        int ByteArrayObject::readInt()
        {
                return (int)m_byteArray.ReadU32();              
        }
        uint32_t ByteArrayObject::readUnsignedInt()
        {
                return m_byteArray.ReadU32();           
        }
        
        double ByteArrayObject::readFloat()
        {
                return m_byteArray.ReadFloat();
        }
        double ByteArrayObject::readDouble()
        {
                return m_byteArray.ReadDouble();
        }
        bool ByteArrayObject::readBoolean()
        {
                return m_byteArray.ReadBoolean();
        }
        void ByteArrayObject::writeBoolean(bool value)
        {
                m_byteArray.WriteBoolean(value);
        }
        void ByteArrayObject::writeByte(int value)
        {
                m_byteArray.WriteU8((U8)value);
        }
        void ByteArrayObject::writeShort(int value)
        {
                m_byteArray.WriteU16((unsigned short)value);
        }
        void ByteArrayObject::writeInt(int value)
        {
                m_byteArray.WriteU32((uint32_t)value);
        }
        void ByteArrayObject::writeUnsignedInt(uint32_t value)
        {
                m_byteArray.WriteU32(value);
        }
        
        void ByteArrayObject::writeFloat(double value)
        {
                m_byteArray.WriteFloat((float)value);
        }
        void ByteArrayObject::writeDouble(double value)
        {
                m_byteArray.WriteDouble(value);
        }
        void ByteArrayObject::zlib_compress()
        {
                int len = m_byteArray.GetLength();
                if (!len) // empty buffer should give us a empty result
                        return; 
                unsigned long gzlen = len * 3/2 + 32; // enough for growth plus zlib headers
                U8 *gzdata = mmfx_new_array( uint8_t, gzlen );
                // Use zlib to compress the data
                compress2((uint8_t*)gzdata, (unsigned long*)&gzlen,
                                m_byteArray.GetBuffer(), len, 9);
                // Replace the byte array with the compressed data
                m_byteArray.SetLength(0);
                //m_byteArray.WriteU32((U32)len);
                m_byteArray.Write(gzdata, gzlen);
                mmfx_delete_array( gzdata );
        }
    void ByteArrayObject::zlib_uncompress()
    {
        // Snapshot the compressed data.
        unsigned long gzlen = m_byteArray.GetLength();
                if (!gzlen) // empty buffer should give us a empty result
                        return; 
                uint8_t *gzdata = mmfx_new_array( uint8_t, gzlen );
        VMPI_memcpy(gzdata, m_byteArray.GetBuffer(), gzlen);
        // Clear the buffer
        m_byteArray.Seek(0);
        m_byteArray.SetLength(0);
        // The following block is to force destruction
        // of zstream before potential exception throw.
        int error = Z_OK;
        {
            // Decompress the data
            PlatformZlibStream zstream;
            zstream.SetNextIn(gzdata);
            zstream.SetAvailIn(gzlen);
            const int kBufferSize = 8192;
            uint8_t *buffer = mmfx_new_array( uint8_t, kBufferSize );
            do {
                zstream.SetNextOut(buffer);
                zstream.SetAvailOut(kBufferSize);
                error = zstream.InflateWithStatus();
                m_byteArray.Write(buffer, kBufferSize-zstream.AvailOut());
            } while (error == Z_OK);
            mmfx_delete_array( buffer );
            mmfx_delete_array( gzdata );
        }
                // position byte array at the beginning
                m_byteArray.Seek(0);
        if (error != Z_OK && error != Z_STREAM_END) {
            toplevel()->throwError(kShellCompressedDataError);
        }
    }
        void ByteArrayObject::checkNull(void *instance, const char *name)
        {
                if (instance == NULL) {
                        toplevel()->throwTypeError(kNullArgumentError, core()->toErrorString(name));
                }
        }       
        void ByteArrayObject::writeBytes(ByteArrayObject *bytes,
                                                                         uint32_t offset,
                                                                         uint32_t length)
        {
                checkNull(bytes, "bytes");
                if (length == 0) {
                        length = bytes->getLength() - offset;
                }
                
                m_byteArray.WriteByteArray(bytes->GetByteArray(),
                                                                   offset,
                                                                   length);
        }
        void ByteArrayObject::readBytes(ByteArrayObject *bytes,
                                                                        uint32_t offset,
                                                                        uint32_t length)
        {
                checkNull(bytes, "bytes");
                if (length == 0) {
                        length = m_byteArray.Available();
                }
                
                m_byteArray.ReadByteArray(bytes->GetByteArray(),
                                                                  offset,
                                                                  length);
        }
        
        String* ByteArrayObject::readUTF()
        {
                return m_byteArray.ReadUTF();
        }
        String* ByteArrayObject::readUTFBytes(uint32_t length)
        {
                return m_byteArray.ReadUTFBytes(length);
        }
        
        void ByteArrayObject::writeUTF(String *value)
        {
                checkNull(value, "value");
                m_byteArray.WriteUTF(value);
        }
        void ByteArrayObject::writeUTFBytes(String *value)
        {
                checkNull(value, "value");
                m_byteArray.WriteUTFBytes(value);
        }
        void ByteArrayObject::fill(const void *b, uint32_t len)
        {
                m_byteArray.Write(b, len);
        }
        
        //
        // ByteArrayClass
        //
        ByteArrayClass::ByteArrayClass(VTable *vtable)
                : ClassClosure(vtable)
    {
        prototype = toplevel()->objectClass->construct();
        }
        ScriptObject* ByteArrayClass::createInstance(VTable *ivtable,
                                                                                                 ScriptObject *prototype)
    {
        return new (core()->GetGC(), ivtable->getExtraSize()) ByteArrayObject(ivtable, prototype);
    }
        ByteArrayObject * ByteArrayClass::readFile(Stringp filename)
        {
                Toplevel* toplevel = this->toplevel();
                if (!filename) {
                        toplevel->throwArgumentError(kNullArgumentError, "filename");
                }
                StUTF8String filenameUTF8(filename);
                File* fp = Platform::GetInstance()->createFile();
                if (fp == NULL || !fp->open(filenameUTF8.c_str(), File::OPEN_READ_BINARY)) 
                {
                        if(fp)
                        {
                                Platform::GetInstance()->destroyFile(fp);
                        }
                        toplevel->throwError(kFileOpenError, filename);
                }
                int64_t len = fp->size();
                if((uint64_t)len >= UINT32_T_MAX) //ByteArray APIs cannot handle files > 4GB
                {
                        toplevel->throwRangeError(kOutOfRangeError, filename);
                }
                uint32_t readCount = (uint32_t)len;
                unsigned char *c = mmfx_new_array( unsigned char, readCount+1);
                Atom args[1] = {nullObjectAtom};
                ByteArrayObject *b = (ByteArrayObject*)AvmCore::atomToScriptObject(construct(0,args));
                b->setLength(0);
                while (readCount > 0)
                {
                        uint32_t actual = (uint32_t) fp->read(c, readCount);
                        if (actual > 0)
                        {
                                b->fill(c, actual);
                                readCount -= readCount;
                        }
                        else
                        {
                                break;
                        }
                }
                b->seek(0);
                mmfx_delete_array( c );
                fp->close();
                Platform::GetInstance()->destroyFile(fp);
                return b;
        }
        Stringp ByteArrayObject::get_endian()
        {
                return (m_byteArray.GetEndian() == kBigEndian) ? core()->internConstantStringLatin1("bigEndian") : core()->internConstantStringLatin1("littleEndian");
        }
        void ByteArrayObject::set_endian(Stringp type)
        {
                AvmCore* core = this->core();
                type = core->internString(type);
                if (type == core->internConstantStringLatin1("bigEndian"))
                {
                        m_byteArray.SetEndian(kBigEndian);
                }
                else if (type == core->internConstantStringLatin1("littleEndian"))
                {
                        m_byteArray.SetEndian(kLittleEndian);
                }
                else
                {
                        toplevel()->throwArgumentError(kInvalidArgumentError, "type");
                }
        }
        void ByteArrayObject::writeFile(Stringp filename)
        {
                Toplevel* toplevel = this->toplevel();
                if (!filename) {
                        toplevel->throwArgumentError(kNullArgumentError, "filename");
                }
                StUTF8String filenameUTF8(filename);
                File* fp = Platform::GetInstance()->createFile();
                if (fp == NULL || !fp->open(filenameUTF8.c_str(), File::OPEN_WRITE_BINARY)) 
                {
                        if(fp)
                        {
                                Platform::GetInstance()->destroyFile(fp);
                        }
                        toplevel->throwError(kFileWriteError, filename);
                }
                int32_t len = this->get_length();
                bool success = (int32_t)fp->write(&(this->GetByteArray())[0], len) == len;
                
                fp->close();
                Platform::GetInstance()->destroyFile(fp);
                
                if (!success) {
                        toplevel->throwError(kFileWriteError, filename);
                }
        }
}