Loading...
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- vim: ft=cpp et ts=4 sw=4:
 *
 * Copyright (c) 2023 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * This implements a basic bplist00 (aka Binary Plist version 0) encoder. It supports the following functionality
 *
 * * Basic Types
 *   * Integer
 *   * String
 *   * Data
 * * Collection Types
 *   * Array
 *   * Dictionary
 *
 * Basic types are builtin plist types supported by the plist00 format. This encoder uniques them to save space
 *
 * Collection types are builtin plist types supported by the bplist00 format. This encoder does not bother attempting to unique them
 * as it is computationally expensive and due to the unique addresses of all the images and segments we know a priori the vast
 * majority of them will be unique. In some cases where we know two collections will be identical the encoder explicity reuses the collection.
 *
 * Usage
 *
 * This is not intended to be a general purpose bplist00 encoder, as such it only implements features necessary for dyld.
 *  * All plists generated by this encoder will have a dictionary as their root object
 *  * There is no way to create a detached object, all objects must either be created by explicitly adding an entry to collection
 *  * All dictionary keys must be strings
 *  * There is no validation the dictionaries only have a single instance of a particular key added to them
 *  *  There is no support for reading plists or walking back through the created structure
 */

#ifndef PropertyList_h
#define PropertyList_h

#include <cstdint>
#include <uuid/uuid.h>

#include "Vector.h"
#include "Defines.h"
#include "ByteStream.h"

struct VIS_HIDDEN PropertyList {
    struct Object;
    using Allocator = lsl::Allocator;
    using ObjectVector = lsl::Vector<Object*>;

    PropertyList(Allocator&);
    void encode(ByteStream&);
    struct Object {
        Object() = delete;
        enum Type : uint64_t {
            String      = 0,
            Integer     = 1,
            Array       = 2,
            Dictionary  = 3,
            Data        = 4
        };

        void            convertToRedirect(uint64_t index);
        void            setIndex(uint64_t index);
        uint64_t        index() const;
        Type            type() const;
        bool            processed() const;
        void            setProcessed();
        virtual void    emit(uint8_t objectIndexSize, ByteStream& bytes) = 0;
        virtual void    deallocate() = 0;
    protected:
        Object(Type type) : _type(type) {}
        uint64_t    _index      : 59    = ~1ULL;
        uint64_t    _type       : 3     = 0;
        uint64_t    _processed  : 1     = 0;
        uint64_t    _isRedirect : 1     = 0;
    };

    struct Data : Object {
        Data() = delete;
        ~Data();
        Data(Allocator& allocator,  uint64_t size);
        Data(Allocator& allocator,  std::span<std::byte> value);
        virtual void            emit(uint8_t objectIndexSize, ByteStream& bytes) override;
        virtual void            deallocate() override;
        bool                    operator==(const Data& other) const;
        std::strong_ordering    operator<=>(const Data& other) const;
        std::span<std::byte>    bytes();
    private:
        lsl::Vector<std::byte>       _value;
    };

    struct String : Object {
        String() = delete;
        ~String();
        String(const String&) = delete;
        String(Allocator& allocator, std::string_view value);   // Note: no constructor from "const char*"

        virtual void            emit(uint8_t objectIndexSize, ByteStream& bytes) override;
        bool                    emitUnicode(uint8_t objectIndexSize, uint64_t stringSize, ByteStream& bytes) const;
        virtual void            deallocate() override;
        bool                    operator==(const String& other) const;
        std::strong_ordering    operator<=>(const String& other) const;
    private:
        const char* _value;     //FIXME: should this be a std::string_view ?
    };

    struct Array : Object {
        Array() = delete;
        Array(Allocator& allocator);
        ~Array();

        std::span<Object*>  values();
        virtual void        emit(uint8_t objectIndexSize, ByteStream& bytes) override;
        virtual void        deallocate() override;

        template<typename T, class... Args>
        T& addObject(Args&&... args) {
            Allocator& allocator = *_values.allocator();
            void* storage = allocator.aligned_alloc(alignof(T), sizeof(T));
            T* result = new (storage) T(allocator, std::forward<Args>(args)...);
            _values.push_back(result);
            return *result;
        }
    private:
        ObjectVector  _values;
    };

    struct Dictionary : Object {
        Dictionary()  = delete;
        Dictionary(Allocator& allocator);

        std::span<Object*>  keys();
        std::span<Object*>  values();
        virtual void        emit(uint8_t objectIndexSize, ByteStream& bytes) override;
        virtual void        deallocate() override;

        template<typename T, class... Args>
        T& addObjectForKey(std::string_view key, Args&&... args) {
            Allocator& allocator = *_values.allocator();
            void* keyStorage = allocator.aligned_alloc(alignof(struct String), sizeof(struct String));
            _keys.push_back(new (keyStorage) struct String(allocator, key));
            void* storage = allocator.aligned_alloc(alignof(T), sizeof(T));
            T* result = new (storage) T(allocator, std::forward<Args>(args)...);
            _values.push_back(result);
            return *result;
        }
        void insertObjectForKey(std::string_view key, Object& object) {
            Allocator& allocator = *_values.allocator();
            void* keyStorage = allocator.aligned_alloc(alignof(struct String), sizeof(struct String));
            _keys.push_back(new (keyStorage) struct String(allocator, key));
            _values.push_back(&object);
        }
    private:
        ObjectVector _keys;
        ObjectVector _values;
    };

    struct Integer : Object {
        Integer() = delete;
        Integer(int64_t value);
        Integer(Allocator& allocator, int64_t value);

        virtual void            emit(uint8_t objectIndexSize, ByteStream& bytes) override;
        virtual void            deallocate() override;
        bool                    operator==(const Integer& other) const;
        std::strong_ordering    operator<=>(const Integer& other) const;
    protected:
        int64_t    _value;
    };

    struct UUID : Data {
        UUID(Allocator& allocator, uuid_t uuid);
    };

    struct Bitmap : Data {
        Bitmap(Allocator& allocator, uint64_t size);
        void setBit(uint64_t bit);
    };

    template<typename T>
    struct Flags : PropertyList::Integer {
        Flags(Allocator& allocator) : PropertyList::Integer(allocator, 0) {}
        void setFlag(T flag, bool value = true) {
            assert(flag <= 62); // FIXME: deal with signed integers in PropertyList::Integer
            if (value) {
                _value |= flag;
            } else {
                _value &= ~flag;
            }
        }
    };

    Dictionary& rootDictionary();
private:
    Allocator&  _allocator;
    Dictionary  _rootDictionary;
};

#endif /* PropertyList_h */