text:
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
using PropertyChanged;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using nanoFramework.Tools.Debugger.Extensions;
using nanoFramework.Tools.Debugger.PortTcpIp;

namespace nanoFramework.Tools.Debugger
{
    [AddINotifyPropertyChangedInterface]
    public abstract class NanoDeviceBase
    {
        /// <summary>
        /// nanoFramework debug engine.
        /// </summary>
        ///
        public Engine DebugEngine { get; set; }

        /// <summary>
        /// Creates a new debug engine for this nanoDevice.
        /// </summary>
        public void CreateDebugEngine()
        {
            DebugEngine = new Engine(this);

            DebugEngine.DefaultTimeout = Transport switch
            {
                TransportType.Serial => NanoSerialDevice.SafeDefaultTimeout,
                TransportType.TcpIp => NanoNetworkDevice.SafeDefaultTimeout,
                _ => throw new NotImplementedException()
            };
        }

        /// <summary>
        /// Creates a new debug engine for this nanoDevice.
        /// </summary>
        /// <param name="timeoutMilliseconds"></param>
        public void CreateDebugEngine(int timeoutMilliseconds)
        {
            DebugEngine = new Engine(this);
            DebugEngine.DefaultTimeout = NanoSerialDevice.SafeDefaultTimeout;
        }

        /// <summary>
        /// Transport to the device.
        /// </summary>
        public TransportType Transport { get; set; }

        /// <summary>
        /// Port here this device is connected.
        /// </summary>
        public IPort ConnectionPort { get; set; }

        /// <summary>
        /// Id of the connection to the device.
        /// </summary>
        public string ConnectionId { get; internal set; }

        /// <summary>
        /// Device description.
        /// </summary>
        public string Description => TargetName + " @ " + ConnectionId;

        /// <summary>
        /// Target name.
        /// </summary>
        public string TargetName { get; internal set; }

        /// <summary>
        /// Target platform.
        /// </summary>
        public string Platform { get; internal set; }

        /// <summary>
        /// Device serial number (if define on the target).
        /// </summary>
        public string SerialNumber { get; internal set; }

        /// <summary>
        /// Unique ID of the NanoDevice.
        /// </summary>
        public Guid DeviceUniqueId { get; internal set; }

        /// <summary>
        /// Version of nanoBooter.
        /// </summary>
        public Version BooterVersion
        {
            get
            {
                try
                {
                    return DebugEngine.TargetInfo.BooterVersion;
                }
                catch
                {
                    return new Version();
                }
            }
        }

        /// <summary>
        /// Version of nanoCLR.
        /// </summary>
        public Version CLRVersion
        {
            get
            {
                try
                {
                    return DebugEngine.TargetInfo.CLRVersion;
                }
                catch
                {
                    return new Version();
                }
            }
        }

        /// <summary>
        /// Detailed info about the NanoFramework device hardware, solution and CLR.
        /// </summary>
        public INanoFrameworkDeviceInfo DeviceInfo { get; internal set; }

        /// <summary>
        /// This indicates if the device has a proprietary bootloader.
        /// </summary>
        public bool HasProprietaryBooter
        {
            get
            {
                return DebugEngine != null && DebugEngine.HasProprietaryBooter;
            }
        }

        /// <summary>
        /// This indicates if the target device has nanoBooter.
        /// </summary>
        public bool HasNanoBooter
        {
            get
            {
                return DebugEngine != null && DebugEngine.HasNanoBooter;
            }
        }

        /// <summary>
        ///  This indicates if the target device is IFU capable.
        /// </summary>
        public bool IsIFUCapable
        {
            get
            {
                return DebugEngine != null && DebugEngine.IsIFUCapable;
            }
        }

        private readonly object m_serverCert = null;
        private readonly Dictionary<uint, string> m_execSrecHash = new Dictionary<uint, string>();
        private readonly Dictionary<uint, int> m_srecHash = new Dictionary<uint, int>();

        private readonly AutoResetEvent m_evtMicroBooter = new AutoResetEvent(false);
        private readonly AutoResetEvent m_evtMicroBooterError = new AutoResetEvent(false);
        private readonly ManualResetEvent m_evtMicroBooterStart = new ManualResetEvent(false);

        protected NanoDeviceBase()
        {
            DeviceInfo = new NanoFrameworkDeviceInfo(this);

            DeviceUniqueId = Guid.NewGuid();
        }

        private bool IsCLRDebuggerEnabled
        {
            get
            {
                try
                {
                    if (DebugEngine.IsConnectedTonanoCLR)
                    {
                        return (DebugEngine.Capabilities.SourceLevelDebugging);
                    }
                }
                catch
                {
                }
                return false;
            }
        }

        public object OnProgress { get; private set; }

        public object DeviceBase { get; internal set; }

        public abstract void Disconnect(bool force = false);

        /// <summary>
        /// Get <see cref="INanoFrameworkDeviceInfo"/> from device.
        /// If the device information has been retrieved before this method returns the cached data, unless the force argument is true.
        /// </summary>
        /// <param name="force">Force retrieving the information from the device.</param>
        /// <returns>Return the <see cref="INanoFrameworkDeviceInfo"/> for this device.</returns>
        public INanoFrameworkDeviceInfo GetDeviceInfo(bool force = true)
        {
            // start by checking if we already have this available
            if (!DeviceInfo.Valid || force)
            {
                // seems to be invalid so get it from device
                NanoFrameworkDeviceInfo nfDeviceInfo = new NanoFrameworkDeviceInfo(this);
                nfDeviceInfo.GetDeviceInfo();

                DeviceInfo = nfDeviceInfo;
            }

            return DeviceInfo;
        }

        /// <summary>
        /// Attempts to communicate with the connected nanoFramework device
        /// </summary>
        /// <returns></returns>
        public ConnectionSource Ping()
        {
            if(DebugEngine == null)
            {
                return ConnectionSource.Unknown;
            }

            return DebugEngine.GetConnectionSource();
        }

        /// <summary>
        /// Start address of the deployment block.
        /// Returns (-1) as invalid value if the address can't be retrieved from the device properties.
        /// </summary>
        public int GetDeploymentStartAddress()
        {
            if (DebugEngine != null)
            {
                return (int)DebugEngine.FlashSectorMap.FirstOrDefault(s =>
                {
                    return (s.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_DEPLOYMENT;
                }).StartAddress;
            }

            return -1;
        }

        /// <summary>
        /// Start address of the CLR block.
        /// Returns (-1) as invalid value if the address can't be retrieved from the device properties.
        /// </summary>
        public int GetCLRStartAddress()
        {
            if (DebugEngine != null)
            {
                return (int)DebugEngine.FlashSectorMap.FirstOrDefault(s =>
                {
                    return (s.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_CODE;
                }).StartAddress;
            }

            return -1;
        }

        /// <summary>
        /// Attempt to establish a connection with nanoBooter (with reboot if necessary)
        /// </summary>
        /// <returns>true connection was made, false otherwise</returns>
        public bool ConnectToNanoBooter()
        {
            bool ret = false;

            if (!DebugEngine.Connect(1000, true))
            {
                return false;
            }

            if (DebugEngine != null)
            {
                if (DebugEngine.IsConnectedTonanoBooter) return true;

                try
                {
                    DebugEngine.RebootDevice(RebootOptions.EnterNanoBooter);

                    /////////////////////////////////////////
                    // FIXME
                    /////////////////////////////////////////
                    //// nanoBooter is only com port so
                    //if (Transport == TransportType.TcpIp)
                    //{
                    //    _DBG.PortDefinition pdTmp = m_port;

                    //    Disconnect();

                    //    try
                    //    {
                    //        m_port = m_portNanoBooter;

                    //        // digi takes forever to reset
                    //        if (!Connect(60000, true))
                    //        {
                    //            Console.WriteLine(Properties.Resources.ErrorUnableToConnectToNanoBooterSerial);
                    //            return false;
                    //        }
                    //    }
                    //    finally
                    //    {
                    //        m_port = pdTmp;
                    //    }
                    //}

                    bool fConnected = false;

                    for (int i = 0; i < 40; i++)
                    {
                        if (DebugEngine == null)
                        {
                            CreateDebugEngine();
                        }

                        if (fConnected = DebugEngine.Connect(
                            true))
                        {
                            ret = (DebugEngine.GetConnectionSource() == ConnectionSource.nanoBooter);

                            break;
                        }
                    }

                    if (!fConnected)
                    {
                        //Debug.WriteLine("Unable to connect to NanoBooter");
                    }
                }
                catch
                {
                    // need a catch all here because some targets re-enumerate the USB device and that makes it impossible to catch them here
                }
            }

            return ret;
        }

        /// <summary>
        /// Erases the deployment sectors of the connected nanoDevice
        /// </summary>
        /// <param name="options">Identifies which areas are to be erased</param>
        /// <param name="cancellationToken">Cancellation token to allow caller to cancel task</param>
        /// <param name="log">Progress report of execution</param>
        /// <returns>Returns false if the erase fails, true otherwise
        /// Possible exceptions: MFUserExitException, MFDeviceNoResponseException
        /// </returns>
        public bool Erase(
            EraseOptions options,
            IProgress<MessageWithProgress> progress = null,
            IProgress<string> log = null)
        {
            bool fReset = false;
            bool requestBooter = false;

            if (DebugEngine == null)
            {
                return false;
            }

            if (!IsCLRDebuggerEnabled || 0 != (options & EraseOptions.Firmware))
            {
                log?.Report("Connecting to nanoBooter...");

                fReset = DebugEngine.IsConnectedTonanoCLR;

                if (!ConnectToNanoBooter())
                {
                    log?.Report("*** ERROR: request to connect to nanoBooter failed ***");

                    return false;
                }

                // flag request to launch nanoBooter
                requestBooter = true;
            }

            if (DebugEngine.FlashSectorMap.Count == 0)
            {
                log?.Report("*** ERROR: device flash map not available, aborting ***");

                return false;
            }

            if (requestBooter)
            {
                log?.Report("Getting connection source...");

                DebugEngine.GetConnectionSource();
            }

            long total = 0;

            if (DebugEngine.IsConnectedTonanoCLR)
            {
                var deviceState = DebugEngine.GetExecutionMode();

                if (deviceState == Commands.DebuggingExecutionChangeConditions.State.Unknown)
                {
                    log?.Report("*** ERROR: failed to retrieve device execution state ***");

                    return false;
                }

                if(!deviceState.IsDeviceInStoppedState())
                {
                    log?.Report("Connected to CLR. Pausing execution...");

                    if (!DebugEngine.PauseExecution())
                    {
                        log?.Report("*** ERROR: failed to stop execution ***");

                        return false;
                    }
                }
            }

            List<Commands.Monitor_FlashSectorMap.FlashSectorData> eraseSectors = new List<Commands.Monitor_FlashSectorMap.FlashSectorData>();

            foreach (Commands.Monitor_FlashSectorMap.FlashSectorData flashSectorData in DebugEngine.FlashSectorMap)
            {
                switch (flashSectorData.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK)
                {
                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_DEPLOYMENT:
                        if (EraseOptions.Deployment == (options & EraseOptions.Deployment))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;

                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_UPDATE:
                        if (EraseOptions.UpdateStorage == (options & EraseOptions.UpdateStorage))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;

                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_SIMPLE_A:
                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_SIMPLE_B:
                        if (EraseOptions.SimpleStorage == (options & EraseOptions.SimpleStorage))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;

                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_STORAGE_A:
                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_STORAGE_B:
                        if (EraseOptions.UserStorage == (options & EraseOptions.UserStorage))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;

                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_FS:
                        if (EraseOptions.FileSystem == (options & EraseOptions.FileSystem))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;

                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_CONFIG:
                        if (EraseOptions.Configuration == (options & EraseOptions.Configuration))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;

                    case Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_CODE:
                        if (EraseOptions.Firmware == (options & EraseOptions.Firmware))
                        {
                            eraseSectors.Add(flashSectorData);
                            total++;
                        }
                        break;
                }

            }

            uint totalBytes = (uint)eraseSectors.Sum(s => s.NumBlocks * s.BytesPerBlock);
            uint current = 0;

            foreach (Commands.Monitor_FlashSectorMap.FlashSectorData flashSectorData in eraseSectors)
            {
                var sectorSize = flashSectorData.NumBlocks * flashSectorData.BytesPerBlock;

                progress?.Report(new MessageWithProgress($"Erasing sector @ 0x{flashSectorData.StartAddress:X8}...", current, totalBytes));
                log?.Report($"Erasing sector @ 0x{flashSectorData.StartAddress:X8}...");

                (AccessMemoryErrorCodes ErrorCode, bool Success) = DebugEngine.EraseMemory(
                    flashSectorData.StartAddress,
                    sectorSize);

                if (!Success)
                {
                    log?.Report($"Error erasing sector @ 0x{flashSectorData.StartAddress:X8}.");

                    return false;
                }

                // check the error code returned
                if (ErrorCode != AccessMemoryErrorCodes.NoError)
                {
                    // operation failed
                    log?.Report($"Error erasing sector @ 0x{flashSectorData.StartAddress:X8}. Error code: {ErrorCode}.");

                    // don't bother continuing
                    return false;
                }

                current += sectorSize;
            }

            // reset if we specifically entered nanoBooter to erase
            if (fReset)
            {
                log?.Report("Executing memory...");
                DebugEngine.ExecuteMemory(0);
            }

            // reboot if we are talking to the CLR
            if (DebugEngine.IsConnectedTonanoCLR)
            {
                progress?.Report(new MessageWithProgress("Rebooting..."));

                RebootOptions rebootOptions = RebootOptions.ClrOnly;

                DebugEngine.RebootDevice(rebootOptions, log);
            }

            return true;
        }

        //public bool DeployUpdateAsync(StorageFile comprFilePath, CancellationToken cancellationToken, IProgress<string> progress = null)
        //{
        //    if (DebugEngine.IsConnectedTonanoCLR)
        //    {
        //        if (await DeployMFUpdateAsync(comprFilePath, cancellationToken, progress))
        //        {
        //            return true;
        //        }
        //    }

        //    return false;
        //}

        /// <summary>
        /// Attempts to deploy a binary (.bin) file to the connected nanoFramework device.
        /// </summary>
        /// <param name="binFile">Path to the binary file (.bin).</param>
        /// <param name="address">Address to write to.</param>
        /// <returns>Returns false if the deployment fails, true otherwise.
        /// </returns>
        /// <remarks>
        /// To perform the update the device has to be running:
        /// - nanoCLR if this is meant to update the deployment region.
        /// - nanoBooter if this is meant to update nanoCLR
        /// Failing to meet this condition will abort the operation.
        /// </remarks>
        public bool DeployBinaryFile(
            string binFile,
            uint address,
            IProgress<string> progress = null)
        {
            // validate if file exists
            if(!File.Exists(binFile))
            {
                return false;
            }

            if (DebugEngine == null)
            {
                return false;
            }

            var data = File.ReadAllBytes(binFile);

            if (!PrepareForDeploy(
                address,
                progress))
            {
                return false;
            }

            if(!DeployFile(
                data,
                address,
                0,
                progress))
            {
                return false;
            }

            progress?.Report($"Verifying image...");

            if (!VerifyMemory(
                data,
                address,
                progress))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Attempts to deploy an SREC (.hex) file to the connected nanoFramework device.
        /// </summary>
        /// <param name="srecFile">Path to the SREC file (.hex) file.</param>
        /// <returns>Returns <see langword="false"/> if the deployment fails, <see langword="true"/> otherwise.
        /// Also returns the entry point address for the given SREC file.
        /// </returns>
        // TODO this is not working most likely because of the format or parsing.
        private bool DeploySrecFile(
            string srecFile,
            CancellationToken cancellationToken,
            IProgress<string> progress = null)
        {
            // validate if file exists
            if (!File.Exists(srecFile))
            {
                return false;
            }

            if (DebugEngine == null)
            {
                return false;
            }

            List<SRecordFile.Block> blocks = SRecordFile.Parse(srecFile);

            if (blocks.Count > 0)
            {
                long total = 0;
                long value = 0;

                for (int i = 0; i < blocks.Count; i++)
                {
                    total += blocks[i].data.Length;
                }

                if(!PrepareForDeploy(blocks, progress))
                {
                    return false;
                }

                progress?.Report($"Deploying {Path.GetFileNameWithoutExtension(srecFile)}...");

                foreach (SRecordFile.Block block in blocks)
                {
                    uint addr = block.address;

                    // check if cancellation was requested
                    if (cancellationToken.IsCancellationRequested)
                    {
                        return false;
                    }

                    block.data.Seek(0, SeekOrigin.Begin);

                    byte[] data = new byte[block.data.Length];

                    if (!DeployFile(
                        data,
                        addr,
                        0,
                        progress))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private bool DeployFile(
            byte[] buffer,
            uint address,
            int programAligment = 0,
            IProgress<string> progress = null)
        {
            AccessMemoryErrorCodes errorCode = DebugEngine.WriteMemory(
                address,
                buffer,
                programAligment);

            if (errorCode != AccessMemoryErrorCodes.NoError)
            {
                progress?.Report($"Error writing to device memory @ 0x{address:X8}, error {errorCode}.");

                return false;
            }

            return true;
        }

        private bool VerifyMemory(
            byte[] buffer,
            uint address,
            IProgress<string> progress = null)
        {
            if (!DebugEngine.PerformWriteMemoryCheck(address, buffer))
            {
                progress?.Report($"Verification failed.");

                return false;
            }

            return true;
        }

        /// <summary>
        /// Starts execution on the connected nanoDevice at the supplied address (parameter entrypoint).
        /// This method is generally used after the Deploy method to jump into the code that was deployed.
        /// </summary>
        /// <param name="entrypoint">Entry point address for execution to begin</param>
        /// <returns>Returns false if execution fails, true otherwise
        /// </returns>
        public bool Execute(uint entryPoint)
        {
            if (DebugEngine == null)
            {
                return false;
            }

            if (CheckForMicroBooter())
            {
                if (m_execSrecHash.ContainsKey(entryPoint))
                {
                    string execRec = m_execSrecHash[entryPoint];
                    bool fRet = false;

                    for (int retry = 0; retry < 10; retry++)
                    {
                        try
                        {
                            DebugEngine.SendBuffer(Encoding.UTF8.GetBytes(execRec));

                            DebugEngine.SendBuffer(Encoding.UTF8.GetBytes("\n"));
                        }
                        catch
                        {
                            // catch all, doesn't care about the return
                            return false;
                        }

                        if (m_evtMicroBooter.WaitOne(1000))
                        {
                            fRet = true;
                            break;
                        }
                    }

                    return fRet;
                }

                return false;
            }

            var connectionSource = DebugEngine.GetConnectionSource();

            if (connectionSource == ConnectionSource.Unknown)
            {
                return false;
            }

            // only execute if connected to nanoBooter, otherwise reboot
            if (connectionSource == ConnectionSource.nanoBooter)
            {
                return DebugEngine.ExecuteMemory(entryPoint);
            }
            else
            {
                // if connected to CLR then this was just a deployment update, reboot
                DebugEngine.RebootDevice(RebootOptions.ClrOnly);
            }

            return true;
        }

        internal bool CheckForMicroBooter()
        {
            if (DebugEngine == null) return false;

            try
            {
                m_evtMicroBooterStart.Set();
                m_evtMicroBooterError.Reset();

                // try to see if we are connected to MicroBooter
                for (int retry = 0; retry < 5; retry++)
                {
                    DebugEngine.SendBuffer(Encoding.UTF8.GetBytes("xx\n"));

                    if (m_evtMicroBooterError.WaitOne(100))
                    {
                        return true;
                    }
                }
            }
            finally
            {
                m_evtMicroBooterStart.Reset();
            }

            return false;
        }

        //private bool DeployMFUpdateAsync(StorageFile zipFile, CancellationToken cancellationToken, IProgress<string> progress = null)
        //{
        //    if (zipFile.IsAvailable)
        //    {
        //        byte[] packet = new byte[DebugEngine.WireProtocolPacketSize];
        //        try
        //        {
        //            int handle = -1;
        //            int idx = 0;

        //            Windows.Storage.FileProperties.BasicProperties fileInfo = await zipFile.GetBasicPropertiesAsync();
        //            uint numPkts = (uint)(fileInfo.Size + DebugEngine.WireProtocolPacketSize - 1) / DebugEngine.WireProtocolPacketSize;

        //            byte[] hashData = Encoding.UTF8.GetBytes(zipFile.Name + fileInfo.DateModified.ToString());

        //            uint updateId = CRC.ComputeCRC(hashData, 0, hashData.Length, 0);
        //            uint imageCRC = 0;

        //            byte[] sig = null;

        //            //Debug.WriteLine(updateId);

        //            handle = DebugEngine.StartUpdate("NetMF", 4, 4, updateId, 0, 0, (uint)fileInfo.Size, DebugEngine.WireProtocolPacketSize, 0);
        //            if (handle > -1)
        //            {
        //                uint authType;
        //                IAsyncResult iar = null;

        //                // perform request
        //                (byte[] Response, bool Success) resp = DebugEngine.UpdateAuthCommand(handle, 1, null);

        //                // check result
        //                if (!resp.Success || resp.Response.Length < 4) return false;

        //                using (MemoryStream ms = new MemoryStream(resp.Item1))
        //                using (BinaryReader br = new BinaryReader(ms))
        //                {
        //                    authType = br.ReadUInt32();
        //                }

        //                byte[] pubKey = null;

        //                // FIXME
        //                //if (m_serverCert != null)
        //                //{

        //                //    RSACryptoServiceProvider rsa = m_serverCert.PrivateKey as RSACryptoServiceProvider;

        //                //    if (rsa != null)
        //                //    {
        //                //        pubKey = rsa.ExportCspBlob(false);
        //                //    }
        //                //}

        //                if (!DebugEngine.UpdateAuthenticate(handle, pubKey))
        //                {
        //                    return false;
        //                }

        //                // FIXME
        //                //if (authType == 1 && m_serverCert != null)
        //                //{
        //                //    iar = await DebugEngine.UpgradeConnectionToSsl_Begin(m_serverCert, m_requireClientCert);

        //                //    if (0 == WaitHandle.WaitAny(new WaitHandle[] { iar.AsyncWaitHandle, EventCancel }, 10000))
        //                //    {
        //                //        try
        //                //        {
        //                //            if (!m_eng.UpgradeConnectionToSSL_End(iar))
        //                //            {
        //                //                m_eng.Dispose();
        //                //                m_eng = null;
        //                //                return false;
        //                //            }
        //                //        }
        //                //        catch
        //                //        {
        //                //            m_eng.Dispose();
        //                //            m_eng = null;
        //                //            return false;
        //                //        }
        //                //    }
        //                //    else
        //                //    {
        //                //        return false;
        //                //    }
        //                //}

        //                // FIXME
        //                //RSAPKCS1SignatureFormatter alg = null;
        //                object alg = null;
        //                HashAlgorithmProvider hash = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha1);
        //                byte[] hashValue = null;

        //                try
        //                {
        //                    if (m_serverCert != null)
        //                    {
        //                        //alg = new RSAPKCS1SignatureFormatter(m_serverCert.PrivateKey);
        //                        //alg.SetHashAlgorithm("SHA1");
        //                        hash = HashAlgorithmProvider.OpenAlgorithm("SHA1");
        //                        hashValue = new byte[hash.HashLength / 8];
        //                    }
        //                }
        //                catch
        //                {
        //                }

        //                IBuffer buffer = await FileIO.ReadBufferAsync(zipFile);
        //                using (DataReader dataReader = DataReader.FromBuffer(buffer))
        //                {
        //                    dataReader.ReadBytes(packet);

        //                    uint crc = CRC.ComputeCRC(packet, 0, packet.Length, 0);

        //                    if (!DebugEngine.AddPacket(handle, (uint)idx++, packet, CRC.ComputeCRC(packet, 0, packet.Length, 0))) return false;

        //                    imageCRC = CRC.ComputeCRC(packet, 0, packet.Length, imageCRC);

        //                    progress?.Report($"Deploying {idx}...");
        //                }

        //                if (hash != null)
        //                {
        //                    buffer = await FileIO.ReadBufferAsync(zipFile);
        //                                     // hash it
        //                    IBuffer hashed = hash.HashData(buffer);
        //                    CryptographicBuffer.CopyToByteArray(hashed, out sig);
        //                }

        //                if (alg != null)
        //                {
        //                    //sig = alg.CreateSignature(hash);
        //                    //CryptographicBuffer.CopyToByteArray(sig)
        //                }
        //                else
        //                {
        //                    sig = new byte[4];
        //                    using (MemoryStream ms = new MemoryStream(sig))
        //                    using (BinaryWriter br = new BinaryWriter(ms))
        //                    {
        //                        br.Write(imageCRC);
        //                    }
        //                }

        //                if (DebugEngine.InstallUpdate(handle, sig))
        //                {
        //                    return true;
        //                }
        //            }
        //        }
        //        catch
        //        {
        //        }
        //    }

        //    return false;
        //}

        //private async Task<Tuple<uint, bool>> DeploySRECAsync(StorageFile srecFile, CancellationToken cancellationToken)
        //{
        //    m_srecHash.Clear();
        //    m_execSrecHash.Clear();

        //    // create .EXT file for SREC file
        //    StorageFolder folder = await srecFile.GetParentAsync();

        //    int m_totalSrecs = 0;
        //    uint m_minSrecAddr = uint.MaxValue;
        //    uint m_maxSrecAddr = 0;

        //    if (srecFile.IsAvailable)
        //    {
        //        // check is EXT file exists, if yes delete it
        //        StorageFile srecExtFile = await folder.TryGetItemAsync(Path.GetFileNameWithoutExtension(srecFile.Name) + ".ext") as StorageFile;
        //        if (srecExtFile != null)
        //        {
        //            await srecExtFile.DeleteAsync();
        //        }

        //        if (await PreProcesSrecAsync(srecFile))
        //        {
        //            srecExtFile = await folder.TryGetItemAsync(srecFile.Name.Replace(srecFile.FileType, "") + ".ext") as StorageFile;
        //        }

        //        // check if cancellation was requested
        //        if (cancellationToken.IsCancellationRequested)
        //        {
        //            new Tuple<uint, bool>(0, false);
        //        }

        //        SrecParseResult parsedFile = await ParseSrecFileAsync(srecExtFile);

        //        try
        //        {
        //            int sleepTime = 5000;
        //            UInt32 imageAddr = 0xFFFFFFFF;

        //            m_totalSrecs = parsedFile.Records.Count;

        //            //m_evtMicroBooterStart.Set();
        //            //m_evtMicroBooter.Reset();
        //            //m_evtMicroBooterError.Reset();

        //            while (parsedFile.Records.Count > 0)
        //            {
        //                // check if cancellation was requested
        //                if (cancellationToken.IsCancellationRequested)
        //                {
        //                    new Tuple<uint, bool>(0, false);
        //                }

        //                List<uint> remove = new List<uint>();

        //                const int c_MaxPipeline = 4;
        //                int pipe = c_MaxPipeline;

        //                uint[] keys = new uint[parsedFile.Records.Count];

        //                parsedFile.Records.Keys.CopyTo(keys, 0);

        //                Array.Sort(keys);

        //                if (keys[0] < imageAddr) imageAddr = keys[0];

        //                foreach (uint key in keys)
        //                {
        //                    // check if cancellation was requested
        //                    if (cancellationToken.IsCancellationRequested)
        //                    {
        //                        new Tuple<uint, bool>(0, false);
        //                    }

        //                    if (key < m_minSrecAddr) m_minSrecAddr = key;
        //                    if (key > m_maxSrecAddr) m_maxSrecAddr = key;
        //                    if (m_srecHash.ContainsKey(key))
        //                    {
        //                        remove.Add(key);
        //                        continue;
        //                    }

        //                    await DebugEngine.SendBufferAsync(Encoding.UTF8.GetBytes("\n"), TimeSpan.FromMilliseconds(1000), cancellationToken).ConfigureAwait(true);

        //                    await DebugEngine.SendBufferAsync(Encoding.UTF8.GetBytes(parsedFile.Records[key]), TimeSpan.FromMilliseconds(20000), cancellationToken).ConfigureAwait(true);

        //                    await DebugEngine.SendBufferAsync(Encoding.UTF8.GetBytes("\n"), TimeSpan.FromMilliseconds(1000), cancellationToken).ConfigureAwait(true);

        //                    if (pipe-- <= 0)
        //                    {
        //                        //m_evtMicroBooter.WaitOne(sleepTime);
        //                        pipe = c_MaxPipeline;
        //                    }
        //                }

        //                int cnt = remove.Count;

        //                if (cnt > 0)
        //                {
        //                    for (int i = 0; i < cnt; i++)
        //                    {
        //                        parsedFile.Records.Remove(remove[i]);
        //                    }
        //                }
        //            }

        //            if (imageAddr != 0)
        //            {
        //                string basefile = Path.GetFileNameWithoutExtension(srecFile.Name);

        //                // srecfile might be .bin.ext (for srec updates)
        //                if (!string.IsNullOrEmpty(Path.GetExtension(basefile)))
        //                {
        //                    basefile = Path.GetFileNameWithoutExtension(basefile);
        //                }

        //                string path = folder.Path;
        //                string binFilePath = "";
        //                string symdefFilePath = "";

        //                if (folder.Path.ToLower().EndsWith("\\nanoCLR.hex"))
        //                {
        //                    binFilePath = Path.GetDirectoryName(path) + "\\nanoCLR.bin\\" + basefile;
        //                    symdefFilePath = Path.GetDirectoryName(path) + "\\nanoCLR.symdefs";
        //                }
        //                else
        //                {
        //                    binFilePath = Path.GetDirectoryName(srecFile.Path) + "\\" + basefile + ".bin";
        //                    symdefFilePath = Path.GetDirectoryName(srecFile.Path) + "\\" + basefile + ".symdefs";
        //                }

        //                StorageFile binFile = await folder.TryGetItemAsync(binFilePath) as StorageFile;

        //                StorageFile symdefFile = await folder.TryGetItemAsync(symdefFilePath) as StorageFile;

        //                // check if cancellation was requested
        //                if (cancellationToken.IsCancellationRequested)
        //                {
        //                    new Tuple<uint, bool>(0, false);
        //                }

        //                // send image crc
        //                if (binFile != null && symdefFile != null)
        //                {
        //                    Windows.Storage.FileProperties.BasicProperties fileInfo = await binFile.GetBasicPropertiesAsync();

        //                    UInt32 imageCRC = 0;

        //                    // read lines from SREC file
        //                    IList<string> textLines = await FileIO.ReadLinesAsync(symdefFile);

        //                    foreach (string line in textLines)
        //                    {
        //                        // check if cancellation was requested
        //                        if (cancellationToken.IsCancellationRequested)
        //                        {
        //                            new Tuple<uint, bool>(0, false);
        //                        }

        //                        if (line.Contains("LOAD_IMAGE_CRC"))
        //                        {
        //                            int idxEnd = line.IndexOf(' ', 2);
        //                            imageCRC = UInt32.Parse(line.Substring(2, idxEnd - 2), System.Globalization.NumberStyles.HexNumber);
        //                        }
        //                    }

        //                    m_execSrecHash[parsedFile.EntryPoint] = string.Format("<CRC>{0:X08},{1:X08},{2:X08},{3:X08}</CRC>\n", imageAddr, fileInfo.Size, imageCRC, parsedFile.EntryPoint);
        //                }
        //            }

        //            return new Tuple<uint, bool>(parsedFile.EntryPoint, true);
        //        }
        //        finally
        //        {
        //            //m_evtMicroBooterStart.Reset();
        //        }
        //    }

        //    return new Tuple<uint, bool>(0, false);
        //}

        //private async Task<SrecParseResult> ParseSrecFileAsync(StorageFile srecFile)
        //{
        //    SrecParseResult reply = new SrecParseResult();

        //    Dictionary<uint, string> recs = new Dictionary<uint, string>();

        //    try
        //    {
        //        int total = 0;

        //        IList<string> textLines = await FileIO.ReadLinesAsync(srecFile);

        //        foreach (string line in textLines)
        //        {
        //            string addr = line.Substring(4, 8);

        //            // we only support s7, s3 records
        //            if (line.ToLower().StartsWith("s7"))
        //            {
        //                reply.EntryPoint = uint.Parse(addr, System.Globalization.NumberStyles.HexNumber);
        //            }
        //            else if (line.ToLower().StartsWith("s3"))
        //            {
        //                total += line.Length - 14;
        //                reply.Records[uint.Parse(addr, System.Globalization.NumberStyles.HexNumber)] = line;
        //            }
        //        }

        //        reply.ImageSize = (uint)total;
        //    }
        //    catch
        //    {
        //        return null;
        //    }

        //    return reply;
        //}

        //private bool PreProcesSrecAsync(StorageFile srecFile)
        //{
        //    if (!srecFile.IsAvailable) return false;

        //    // create .EXT file for SREC file
        //    StorageFolder folder = await srecFile.GetParentAsync();

        //    try
        //    {
        //        // read lines from SREC file
        //        IList<string> textLines = await FileIO.ReadLinesAsync(srecFile);

        //        StorageFile srecExtFile = await folder.CreateFileAsync(Path.GetFileNameWithoutExtension(srecFile.Name) + ".ext", CreationCollisionOption.ReplaceExisting);

        //        const int c_MaxRecords = 8;
        //        int iRecord = 0;
        //        int currentCRC = 0;
        //        int iDataLength = 0;
        //        string s7rec = "";
        //        StringBuilder sb = new StringBuilder();

        //        foreach (string line in textLines)
        //        {
        //            // we only support s7, s3 records
        //            if (line.ToLower().StartsWith("s7"))
        //            {
        //                s7rec = line;
        //                continue;
        //            }

        //            if (!line.ToLower().StartsWith("s3")) continue;

        //            string crcData;

        //            if (iRecord == 0)
        //            {
        //                crcData = line.Substring(4, line.Length - 6);
        //            }
        //            else
        //            {
        //                crcData = line.Substring(12, line.Length - 14);
        //            }

        //            iDataLength += crcData.Length / 2; // 2 chars per byte

        //            if (iRecord == 0)
        //            {
        //                sb.Append(line.Substring(0, 2));
        //            }
        //            sb.Append(crcData);

        //            iRecord++;

        //            for (int i = 0; i < crcData.Length - 1; i += 2)
        //            {
        //                currentCRC += Byte.Parse(crcData.Substring(i, 2), System.Globalization.NumberStyles.HexNumber);
        //            }

        //            if (iRecord == c_MaxRecords)
        //            {
        //                iDataLength += 1; // crc

        //                sb = sb.Insert(2, string.Format("{0:X02}", iDataLength));

        //                currentCRC += (iDataLength & 0xFF) + ((iDataLength >> 8) & 0xFF);

        //                // write crc
        //                sb.Append(string.Format("{0:X02}", (0xFF - (0xFF & currentCRC))));

        //                await FileIO.WriteTextAsync(srecExtFile, sb.ToString());

        //                currentCRC = 0;
        //                iRecord = 0;
        //                iDataLength = 0;
        //                sb.Length = 0;
        //            }
        //        }

        //        if (iRecord != 0)
        //        {
        //            iDataLength += 1; // crc

        //            sb = sb.Insert(2, string.Format("{0:X02}", iDataLength));

        //            currentCRC += (iDataLength & 0xFF) + ((iDataLength >> 8) & 0xFF);

        //            // write crc
        //            sb.Append(string.Format("{0:X02}", (0xFF - (0xFF & currentCRC))));

        //            await FileIO.WriteTextAsync(srecExtFile, sb.ToString());
        //        }

        //        if (s7rec != "")
        //        {
        //            await FileIO.WriteTextAsync(srecExtFile, s7rec);
        //        }
        //    }
        //    catch
        //    {
        //        StorageFile thisFile = await folder.TryGetItemAsync(Path.GetFileNameWithoutExtension(srecFile.Name) + ".ext") as StorageFile;

        //        if (thisFile != null)
        //        {
        //            await thisFile.DeleteAsync();
        //        }

        //        return false;
        //    }

        //    return true;
        //}

        private bool PrepareForDeploy(
            uint address,
            IProgress<string> progress = null)
        {
            return PrepareForDeploy(
                address,
                null,
                progress);
        }

        private bool PrepareForDeploy(
            List<SRecordFile.Block> blocks,
            IProgress<string> progress = null)
        {
            return PrepareForDeploy(
                0,
                blocks,
                progress);
        }

        private bool PrepareForDeploy(
            uint address,
            List<SRecordFile.Block> blocks,
            IProgress<string> progress = null)
        {
            // get flash sector map, only if needed
            List<Commands.Monitor_FlashSectorMap.FlashSectorData> flashSectorsMap = DebugEngine.FlashSectorMap;

            // sanity check
            if (flashSectorsMap == null ||
                flashSectorsMap.Count == 0)
            {
                return false;
            }

            // validate deployment
            bool updatesDeployment = false;
            bool updatesClr = false;
            bool updatesBooter = false;

            if (blocks != null)
            {
                foreach (SRecordFile.Block bl in blocks)
                {
                    var startSector = flashSectorsMap.Find(s => s.StartAddress == bl.address);
                    if (startSector.NumBlocks > 0)
                    {
                        updatesDeployment ^= (startSector.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_DEPLOYMENT;
                        updatesClr ^= (startSector.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_CODE;
                        updatesBooter ^= (startSector.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_BOOTSTRAP;
                    }
                }
            }
            else
            {
                var startSector = flashSectorsMap.Find(s => s.StartAddress == address);
                if (startSector.NumBlocks > 0)
                {
                    updatesDeployment = (startSector.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_DEPLOYMENT;
                    updatesClr = (startSector.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_CODE;
                    updatesBooter = (startSector.Flags & Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_MASK) == Commands.Monitor_FlashSectorMap.c_MEMORY_USAGE_BOOTSTRAP;
                }
            }

            // sanity check
            if (updatesBooter)
            {
                // can't handle this update because it touches nanoBooter

                progress?.Report("Can't deploy file because it updates nanoBooter.");

                return false;
            }

            if(
                !updatesDeployment &&
                !updatesClr &&
                !updatesBooter)
            {
                // nothing to update???
                return false;
            }

            if (updatesClr)
            {
                // if this is updating the CLR need to launch nanoBooter
                if (!DebugEngine.IsConnectedTonanoBooter)
                {
                    progress?.Report("Need to launch nanoBooter before updating the firmware.");

                    return false;
                }
            }

            // erase whatever blocks are required
            if (updatesClr)
            {
                if (!Erase(
                    EraseOptions.Firmware,
                    null,
                    progress))
                {
                    return false;
                }
            }

            if (updatesDeployment)
            {
                if (!Erase(
                    EraseOptions.Deployment,
                    null,
                    progress))
                {
                    return false;
                }
            }

            if (DebugEngine.IsConnectedTonanoCLR)
            {
                DebugEngine.PauseExecution();
            }

            return true;
        }
    }
}


text:
@interface RNNNavigationButtons()

@property (weak, nonatomic) UIViewController<RNNLayoutProtocol>* viewController;
@property (strong, nonatomic) RNNButtonOptions* defaultLeftButtonStyle;
@property (strong, nonatomic) RNNButtonOptions* defaultRightButtonStyle;
@property (strong, nonatomic) RNNReactComponentRegistry* componentRegistry;
@end

@implementation RNNNavigationButtons

-(instancetype)initWithViewController:(UIViewController<RNNLayoutProtocol>*)viewController componentRegistry:(id)componentRegistry {
	self = [super init];

	self.viewController = viewController;
	self.componentRegistry = componentRegistry;

	return self;
}

- (void)applyLeftButtons:(NSArray *)leftButtons rightButtons:(NSArray *)rightButtons defaultLeftButtonStyle:(RNNButtonOptions *)defaultLeftButtonStyle defaultRightButtonStyle:(RNNButtonOptions *)defaultRightButtonStyle {
	_defaultLeftButtonStyle = defaultLeftButtonStyle;
	_defaultRightButtonStyle = defaultRightButtonStyle;
	if (leftButtons) {
		[self setButtons:leftButtons side:@"left" animated:NO defaultStyle:_defaultLeftButtonStyle insets:[self leftButtonInsets:_defaultLeftButtonStyle.iconInsets]];
	}

	if (rightButtons) {
		[self setButtons:rightButtons side:@"right" animated:NO defaultStyle:_defaultRightButtonStyle insets:[self rightButtonInsets:_defaultRightButtonStyle.iconInsets]];
	}
}

-(void)setButtons:(NSArray*)buttons side:(NSString*)side animated:(BOOL)animated defaultStyle:(RNNButtonOptions *)defaultStyle insets:(UIEdgeInsets)insets {
	NSMutableArray *barButtonItems = [NSMutableArray new];
	NSArray* resolvedButtons = [self resolveButtons:buttons];
	for (NSDictionary *button in resolvedButtons) {
		RNNUIBarButtonItem* barButtonItem = [self buildButton:button defaultStyle:defaultStyle insets:insets];
		if(barButtonItem) {
			[barButtonItems addObject:barButtonItem];
		}
		UIColor* color = [self color:[RCTConvert UIColor:button[@"color"]] defaultColor:[defaultStyle.color getWithDefaultValue:nil]];
		if (color) {
			self.viewController.navigationController.navigationBar.tintColor = color;
		}
	}

	if ([side isEqualToString:@"left"]) {
		[self.viewController.navigationItem setLeftBarButtonItems:barButtonItems animated:animated];
	}

	if ([side isEqualToString:@"right"]) {
		[self.viewController.navigationItem setRightBarButtonItems:barButtonItems animated:animated];
	}
}

- (NSArray *)resolveButtons:(id)buttons {
	if ([buttons isKindOfClass:[NSArray class]]) {
		return buttons;
	} else {
		return @[buttons];
	}
}

-(RNNUIBarButtonItem*)buildButton: (NSDictionary*)dictionary defaultStyle:(RNNButtonOptions *)defaultStyle insets:(UIEdgeInsets)insets {
	NSString* buttonId = dictionary[@"id"];
	NSString* title = [self getValue:dictionary[@"text"] withDefault:[defaultStyle.text getWithDefaultValue:nil]];
	NSDictionary* component = dictionary[@"component"];
	NSString* systemItemName = dictionary[@"systemItem"];

	UIColor* color = [self color:[RCTConvert UIColor:dictionary[@"color"]] defaultColor:[defaultStyle.color getWithDefaultValue:nil]];
	UIColor* disabledColor = [self color:[RCTConvert UIColor:dictionary[@"disabledColor"]] defaultColor:[defaultStyle.disabledColor getWithDefaultValue:nil]];

	if (!buttonId) {
		@throw [NSException exceptionWithName:@"NSInvalidArgumentException" reason:[@"button id is not specified " stringByAppendingString:title] userInfo:nil];
	}

	UIImage* defaultIcon = [defaultStyle.icon getWithDefaultValue:nil];
	UIImage* iconImage = [self getValue:dictionary[@"icon"] withDefault:defaultIcon];
	if (![iconImage isKindOfClass:[UIImage class]]) {
		iconImage = [RCTConvert UIImage:iconImage];
	}

	if (iconImage) {
		iconImage = [iconImage imageWithInsets:insets];
		if (color) {
			iconImage = [iconImage withTintColor:color];
		}
	}

	RNNUIBarButtonItem *barButtonItem;
	if (component) {
		RNNComponentOptions* componentOptions = [RNNComponentOptions new];
		componentOptions.componentId = [[Text alloc] initWithValue:component[@"componentId"]];
		componentOptions.name = [[Text alloc] initWithValue:component[@"name"]];

		RNNReactView *view = [_componentRegistry createComponentIfNotExists:componentOptions parentComponentId:self.viewController.layoutInfo.componentId reactViewReadyBlock:nil];
		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withCustomView:view];
	} else if (iconImage) {
		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withIcon:iconImage];
	} else if (title) {
		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withTitle:title];

		NSMutableDictionary *buttonTextAttributes = [RCTHelpers textAttributesFromDictionary:dictionary withPrefix:@"button"];
		if (buttonTextAttributes.allKeys.count > 0) {
			[barButtonItem setTitleTextAttributes:buttonTextAttributes forState:UIControlStateNormal];
		}
	} else if (systemItemName) {
		barButtonItem = [[RNNUIBarButtonItem alloc] init:buttonId withSystemItem:systemItemName];
	} else {
		return nil;
	}

	barButtonItem.target = self.viewController;
	barButtonItem.action = @selector(onButtonPress:);

	NSNumber *enabled = [self getValue:dictionary[@"enabled"] withDefault:defaultStyle.enabled.getValue];
	BOOL enabledBool = enabled ? [enabled boolValue] : YES;
	[barButtonItem setEnabled:enabledBool];

	NSMutableDictionary* textAttributes = [[NSMutableDictionary alloc] init];
	NSMutableDictionary* disabledTextAttributes = [[NSMutableDictionary alloc] init];

	if (!enabledBool && disabledColor) {
		color = disabledColor;
		[disabledTextAttributes setObject:disabledColor forKey:NSForegroundColorAttributeName];
	}

	if (color) {
		[textAttributes setObject:color forKey:NSForegroundColorAttributeName];
		[barButtonItem setImage:[[iconImage withTintColor:color] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
		barButtonItem.tintColor = color;
	}

	NSNumber* fontSize = [self fontSize:dictionary[@"fontSize"] defaultFontSize:[defaultStyle.fontSize getWithDefaultValue:nil]];
	NSString* fontFamily = [self fontFamily:dictionary[@"fontFamily"] defaultFontFamily:[defaultStyle.fontFamily getWithDefaultValue:nil]];
	UIFont *font = nil;
	if (fontFamily) {
		font = [UIFont fontWithName:fontFamily size:[fontSize floatValue]];
	} else {
		font = [UIFont systemFontOfSize:[fontSize floatValue]];
	}
	[textAttributes setObject:font forKey:NSFontAttributeName];
	[disabledTextAttributes setObject:font forKey:NSFontAttributeName];

	[barButtonItem setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
	[barButtonItem setTitleTextAttributes:textAttributes forState:UIControlStateHighlighted];
	[barButtonItem setTitleTextAttributes:disabledTextAttributes forState:UIControlStateDisabled];

	NSString *testID = dictionary[@"testID"];
	if (testID)
	{
		barButtonItem.accessibilityIdentifier = testID;
	}

	return barButtonItem;
}

- (UIColor *)color:(UIColor *)color defaultColor:(UIColor *)defaultColor {
	if (color) {
		return color;
	} else if (defaultColor) {
		return defaultColor;
	}

	return nil;
}

- (NSNumber *)fontSize:(NSNumber *)fontSize defaultFontSize:(NSNumber *)defaultFontSize {
	if (fontSize) {
		return fontSize;
	} else if (defaultFontSize) {
		return defaultFontSize;
	}

	return @(17);
}

- (NSString *)fontFamily:(NSString *)fontFamily defaultFontFamily:(NSString *)defaultFontFamily {
	if (fontFamily) {
		return fontFamily;
	} else {
		return defaultFontFamily;
	}
}

- (id)getValue:(id)value withDefault:(id)defaultValue {
	return value ? value : defaultValue;
}

- (UIEdgeInsets)leftButtonInsets:(RNNInsetsOptions *)defaultInsets {
	return UIEdgeInsetsMake([defaultInsets.top getWithDefaultValue:0],
					 [defaultInsets.left getWithDefaultValue:0],
					 [defaultInsets.bottom getWithDefaultValue:0],
					 [defaultInsets.right getWithDefaultValue:15]);
}

- (UIEdgeInsets)rightButtonInsets:(RNNInsetsOptions *)defaultInsets {
	return UIEdgeInsetsMake([defaultInsets.top getWithDefaultValue:0],
					 [defaultInsets.left getWithDefaultValue:15],
					 [defaultInsets.bottom getWithDefaultValue:0],
					 [defaultInsets.right getWithDefaultValue:0]);
}

@end

redpajama_set_name: RedPajamaGithub


text:
{% extends 'dz/tables/base.html' %}
{% load bootstrap i18n %}

{% block title %}{{ verbose_name|title }} | {% trans 'D.Z.' %}{% endblock %}

{% block messages %}
  {% include './_messages.html' %}
{% endblock messages %}

{% block content %}

{% blocktrans %}Edit {{verbose_name}} {{pk}}{% endblocktrans %}

  <form class="form-horizontal" method="post" action="{{ action }}">
    {% csrf_token %}
    {{ form|bootstrap_horizontal:"col-sm-3" }}

        <button type="submit" class="btn btn-primary"{% if not is_admin %} disabled{% endif %}>{% trans 'Submit' %}</button>
        {% trans 'Close' %}

  </form>

{% endblock content %}


text:
from flask_wtf import Form
from wtforms import StringField, TextAreaField
from wtforms import validators

class CreateEntryForm(Form):
    # title = TextField('Title', [validators.Length(min=1, max=70)])
    title = StringField('Title', [validators.Length(min=1, max=70)])
    body = TextAreaField('Body', [validators.Length(min=1, max=300)])

class UpdateEntryForm(Form):
    # title = TextField('Title', [validators.Length(min=1, max=70)])
    title = StringField('Title', [validators.Length(min=1, max=70)])
    body = TextAreaField('Body', [validators.Length(min=1, max=300)])


text:
currentMenu: primitives
---

# Entidades Primitivas

En este capítulo aprenderás sobre los elementos básicos para diseñar algoritmos, como son:

* los **tipos de datos**,
* los **identificadores**,
* las **constantes**,
* las **variables**
* y las **expresiones**.

## Tipos de Datos

Todos los datos en un algoritmo o programa tienen un tipo asociado con ellos. Un dato puede ser un
simple caracter, tal como `'b'`, o un valor entero tal como `35`.

El tipo determina el **conjunto de valores que puede tomar un dato** y el **tipo de
operaciones** que se le pueden aplicar. La siguiente Figura muestra la clasificación general de los
tipos de datos de acuerdo a su complejidad.

![Tipos de datos](images/tipos-de-datos.png)

Algunos tipos de datos estructurados se analizarán en capítulos posteriores, este Capítulo se enfoca
en describir los tipos de datos simples.

### Tipos de Datos Simples

A continuación se detalla el tipo de contenido que corresponde a los tipos de datos simples.

-   **Datos Numéricos**. Permiten representar valores de forma numérica, esto incluye a los
    números **enteros** y los **reales**. Con este tipo de datos se pueden realizar las
    **operaciones aritméticas comunes**.
-   **Datos Alfanuméricos (Cadenas)**. Estos datos son una secuencia de caracteres alfanuméricos,
    en otras palabras **texto**. Esto incluye nombres de personas, direcciones, etc. Es posible
    representar números como alfanuméricos, pero éstos pierden sus propiedades matemáticas, es
    decir, no es posible hacer operaciones aritméticas con datos alfanuméricos. Este tipo de datos
    se representan encerrados entre comillas. Por ejemplo: `"Grupo de Usuarios PHP Puebla"`, `"1997"`.
-   **Datos Lógicos**. Son aquellos que sólo pueden tener dos valores: **cierto** o **falso** ya
    que representan el resultado de una **comparación** entre otros datos (numéricos o
    alfanuméricos).

## Identificadores

Los identificadores representan los datos de un programa. Un identificador es una **secuencia de
caracteres que sirve para identificar un dato en la computadora**.

Ejemplos: `nombre`, `numeroDeHoras`, `calificacion2`

### Reglas para formar un identificador

Los identificadores deben seguir las siguientes reglas:

-   Deben comenzar con una letra (A a Z, mayúsculas o minúsculas) y no deben contener espacios en
    blanco.
-   Letras, dígitos y el guión bajo (`_`) están permitidos después del primer caracter.

## Constantes y Variables

Los identificadores sirven para nombrar los dos tipos de elementos más importantes y más usados en
un programa o algoritmo, las constantes y las variables.

Constante

:   Una constante es un dato numérico o alfanumérico cuyo valor **no cambia** dentro de un
    algoritmo o durante la ejecución de un programa.

    Por ejemplo: `PI = 3.1416`

Variable

:   Es es un valor numérico o alfanumérico cuyo contenido puede **cambiar** durante la ejecución de
    un programa o algoritmo.

    Por ejemplo:

    `perimetro = PI * diametro`

    Las variables son : `diametro` y `perimetro`, la constante es `PI`.

### Notación para Variables y Constantes

Existen varias **formas estándar** de nombrar variables y constantes, a las cuales se les conoce
como **notaciones**.

Una de las más comunes es la notación *Camel*, la cual consiste en escribir los identificadores con
la primera letra de cada palabra en mayúsculas y el resto en minúsculas, por ejemplo: `EndOfFile`.
Se le llama notación "Camel" porque los identificadores semejan las jorobas de un camello.

Existen dos variantes de la notación:

-   `UpperCamelCase`: en esta variante la primera letra también es mayúscula.
-   `lowerCamelCase`: en esta variante la primera letra es minúscula.

En muchos lenguajes de programación, se tiene la costumbre de denotar las constantes mediante
identificadores en mayúsculas. En este caso la notación Camel no es aplicable y solo queda usar la
notación **C**, por ejemplo: `END_OF_FILE`, `PI`, etc.

### Clasificación de las Variables

Las variables se clasifican por su contenido en: **numéricas**, **lógicas** y **alfanuméricas**
(cadenas); y por su uso en: **variables de trabajo**, **contadores** y **acumuladores**. La
siguiente Figura muestra gráficamente esta clasificación.

![Tipos de variables](images/tipos-variables.png)

#### Por su contenido

-   **Variable Numéricas.** Son aquellas en las cuales se almacenan valores numéricos, positivos o
    negativos, es decir almacenan secuencias de dígitos (números del 0 al 9), signos (+ y -) y el
    punto decimal. Ejemplo: `salario = 3500.85`, `costo = 2500`, `temperatura = -2`.
-   **Variables Lógicas.** Son aquellas que sólo pueden tener dos valores: verdadero o falso (`V`
    ó `F`), éstos representan el resultado de una comparación entre otros datos (operación
    relacional).
-   **Variables Alfanuméricas.** Variables formadas por caracteres alfanuméricos (letras, números
    y caracteres especiales). Por ejemplo: `letra = 'a'`, `apellido = "López"`,
    `direccion = "Av. Libertad  #190"`.

#### Por su uso

-   **Variables de Trabajo.** Variables que reciben el resultado de una operación matemática. Por
    ejemplo: `suma = a + b / c`.
-   **Contadores.** Se utilizan para llevar el control del número de ocasiones en que se realiza
    una operación. Generalmente este tipo de variable cuenta de uno en uno. Por ejemplo:
```
contador = 0
contador = contador + 1
contador = contador + 1
contador = contador + 1
```
    La variable `contador` contó 3 veces en este ejemplo.
-   **Acumuladores.** Son variables que sirven para llevar la suma acumulativa de una serie de
    valores que se van leyendo o calculando progresivamente. Por ejemplo:
```
deuda = 0
deuda = deuda + 25
deuda = deuda + 5
deuda = deuda + 10
```
    En este caso la variable `deuda` acumula la cantidad que se debe a alguien
    y su valor final es 40.

### Ejercicios propuestos

1.  ¿Cuál es la clasificación de los tipos de datos de acuerdo a su complejidad?
2.  Describa el tipo de contenido que pueden almacenar los tipos de datos simples.
3.  ¿Qués un identificador y cuáles son las dos reglas básicas para formar un identificador.
4.  Defina que es una variable y qué es una constante.
5.  ¿Cual es la notación que se debe usar para una variable y para una constante? De un ejemplo de
    cada una.
6.  ¿Cuál es la clasificación de las variables por su uso y su contenido?


text:
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2017 Couchbase, Inc
 *
 *   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

#include "checkpoint.h"
#include "collections/manifest.h"
#include "collections/vbucket_manifest.h"
#include "collections/vbucket_serialised_manifest_entry.h"
#include "ep_vb.h"
#include "failover-table.h"
#include "tests/module_tests/test_helpers.h"

#include <cJSON_utils.h>

#include <gtest/gtest.h>

class MockVBManifest : public Collections::VB::Manifest {
public:
    MockVBManifest() : Collections::VB::Manifest({/* no collection data*/}) {
    }

    MockVBManifest(const std::string& json) : Collections::VB::Manifest(json) {
    }

    bool exists(const std::string& collection, uint32_t rev) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        return exists_UNLOCKED(collection, rev);
    }

    bool isOpen(const std::string& collection, uint32_t rev) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        expect_true(exists_UNLOCKED(collection, rev));
        auto itr = map.find(collection);
        return itr->second->isOpen();
    }

    bool isExclusiveOpen(const std::string& collection, uint32_t rev) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        expect_true(exists_UNLOCKED(collection, rev));
        auto itr = map.find(collection);
        return itr->second->isExclusiveOpen();
    }

    bool isDeleting(const std::string& collection, uint32_t rev) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        expect_true(exists_UNLOCKED(collection, rev));
        auto itr = map.find(collection);
        return itr->second->isDeleting();
    }

    bool isExclusiveDeleting(const std::string& collection,
                             uint32_t rev) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        expect_true(exists_UNLOCKED(collection, rev));
        auto itr = map.find(collection);
        return itr->second->isExclusiveDeleting();
    }

    bool isOpenAndDeleting(const std::string& collection, uint32_t rev) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        expect_true(exists_UNLOCKED(collection, rev));
        auto itr = map.find(collection);
        return itr->second->isOpenAndDeleting();
    }

    size_t size() const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        return map.size();
    }

    bool compareEntry(const Collections::VB::ManifestEntry& entry) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        if (exists_UNLOCKED(entry.getCollectionName(), entry.getRevision())) {
            auto itr = map.find(entry.getCollectionName());
            const auto& myEntry = *itr->second;
            return myEntry.getStartSeqno() == entry.getStartSeqno() &&
                   myEntry.getEndSeqno() == entry.getEndSeqno() &&
                   myEntry.getRevision() == entry.getRevision();
        }
        return false;
    }

    bool operator==(const MockVBManifest& rhs) const {
        std::lock_guard<cb::ReaderLock> readLock(rwlock.reader());
        if (rhs.size() != size()) {
            return false;
        }
        // Check all collections match
        for (const auto& e : map) {
            if (!rhs.compareEntry(*e.second)) {
                return false;
            }
        }

        // finally check the separator's match
        return rhs.separator == separator;
    }

    bool operator!=(const MockVBManifest& rhs) const {
        return !(*this == rhs);
    }

protected:
    bool exists_UNLOCKED(const std::string& collection, uint32_t rev) const {
        auto itr = map.find(collection);
        return itr != map.end() && itr->second->getRevision() == rev;
    }

    void expect_true(bool in) const {
        if (!in) {
            throw std::logic_error("expect_true found false");
        }
    }
};

/**
 * Test class that owns an active and replica manifest.
 * Updates applied to the active are applied to the replica by processing
 * the active's checkpoint.
 */
class ActiveReplicaManifest {
public:
    /// Dummy callback to replace the flusher callback so we can create VBuckets
    class DummyCB : public Callback<uint16_t> {
    public:
        DummyCB() {
        }

        void callback(uint16_t& dummy) {
        }
    };

    ActiveReplicaManifest()
        : active(),
          replica(),
          vbA(0,
              vbucket_state_active,
              global_stats,
              checkpoint_config,
              /*kvshard*/ nullptr,
              /*lastSeqno*/ 0,
              /*lastSnapStart*/ 0,
              /*lastSnapEnd*/ 0,
              /*table*/ nullptr,
              std::make_shared<DummyCB>(),
              /*newSeqnoCb*/ nullptr,
              config,
              VALUE_ONLY),
          vbR(1,
              vbucket_state_replica,
              global_stats,
              checkpoint_config,
              /*kvshard*/ nullptr,
              /*lastSeqno*/ 0,
              /*lastSnapStart*/ 0,
              /*lastSnapEnd*/ snapEnd,
              /*table*/ nullptr,
              std::make_shared<DummyCB>(),
              /*newSeqnoCb*/ nullptr,
              config,
              VALUE_ONLY) {
    }

    ::testing::AssertionResult update(const char* json) {
        try {
            active.wlock().update(vbA, {json});
        } catch (std::exception& e) {
            return ::testing::AssertionFailure()
                   << "Exception thrown for update with " << json
                   << ", e.what:" << e.what();
        }
        queued_item manifest;
        try {
            manifest = applyCheckpointEventsToReplica();
        } catch (std::exception& e) {
            return ::testing::AssertionFailure()
                   << "Exception thrown for replica update, e.what:"
                   << e.what();
        }
        if (active != replica) {
            return ::testing::AssertionFailure()
                   << "active doesn't match replica active:\n"
                   << active << " replica:\n"
                   << replica;
        }
        return checkJson(*manifest);
    }

    ::testing::AssertionResult completeDeletion(const std::string& collection,
                                                uint32_t revision) {
        try {
            active.wlock().completeDeletion(vbA, collection, revision);
            replica.wlock().completeDeletion(vbR, collection, revision);
        } catch (std::exception& e) {
            return ::testing::AssertionFailure()
                   << "Exception thrown for completeDeletion with e.what:"
                   << e.what();
        }

        // completeDeletion adds a new item without a seqno, which closes
        // the snapshot, re-open the snapshot so tests can continue.
        vbR.checkpointManager.updateCurrentSnapshotEnd(snapEnd);
        return ::testing::AssertionSuccess();
    }

    ::testing::AssertionResult doesKeyContainValidCollection(DocKey key) {
        if (!active.lock().doesKeyContainValidCollection(key)) {
            return ::testing::AssertionFailure() << "active failed the key";
        } else if (!replica.lock().doesKeyContainValidCollection(key)) {
            return ::testing::AssertionFailure() << "replica failed the key";
        }
        return ::testing::AssertionSuccess();
    }

    bool isExclusiveOpen(const std::string& collection, uint32_t rev) {
        return active.isExclusiveOpen(collection, rev) &&
               replica.isExclusiveOpen(collection, rev);
    }

    bool isExclusiveDeleting(const std::string& collection, uint32_t rev) {
        return active.isExclusiveDeleting(collection, rev) &&
               replica.isExclusiveDeleting(collection, rev);
    }

    bool isOpenAndDeleting(const std::string& collection, uint32_t rev) {
        return active.isOpenAndDeleting(collection, rev) &&
               replica.isOpenAndDeleting(collection, rev);
    }

    bool checkSize(size_t s) {
        return active.size() == s && replica.size() == s;
    }

    VBucket& getActiveVB() {
        return vbA;
    }

    MockVBManifest& getActiveManifest() {
        return active;
    }

private:
    static std::string itemToJson(const Item& item) {
        cb::const_char_buffer buffer(item.getData(), item.getNBytes());
        return Collections::VB::Manifest::serialToJson(
                SystemEvent(item.getFlags()), buffer, item.getBySeqno());
    }

    static void getEventsFromCheckpoint(VBucket& vb,
                                        std::vector<queued_item>& events) {
        std::vector<queued_item> items;
        vb.checkpointManager.getAllItemsForCursor(
                CheckpointManager::pCursorName, items);
        for (const auto& qi : items) {
            if (qi->getOperation() == queue_op::system_event) {
                events.push_back(qi);
            }
        }

        if (events.empty()) {
            throw std::logic_error("getEventsFromCheckpoint: no events in vb:" +
                                   std::to_string(vb.getId()));
        }
    }

    /**
     * 1. scan the VBucketManifestTestVBucket's checkpoint for all system
     * events.
     * 2. for all system-events, pretend to be the DcpConsumer and call
     *    the VBucket's manifest's replica functions on.
     * @param replicaVB A vbucket acting as the replica, we will create/delete
     *        collections against this VB.
     * @param replicaManfiest The replica VB's manifest, we will create/delete
     *         collections against this manifest.
     *
     * @returns the last queued_item (which would be used to create a json
     *          manifest)
     */
    queued_item applyCheckpointEventsToReplica() {
        std::vector<queued_item> events;
        getEventsFromCheckpoint(vbA, events);
        queued_item rv = events.back();
        for (const auto& qi : events) {
            if (qi->getOperation() == queue_op::system_event) {
                auto dcpData = Collections::VB::Manifest::getSystemEventData(
                        {qi->getData(), qi->getNBytes()});

                // Extract the revision to a local
                uint32_t revision = *reinterpret_cast<const uint32_t*>(
                        dcpData.second.data());

                switch (SystemEvent(qi->getFlags())) {
                case SystemEvent::CreateCollection: {
                    replica.wlock().replicaAdd(
                            vbR, dcpData.first, revision, qi->getBySeqno());
                    break;
                }
                case SystemEvent::BeginDeleteCollection: {
                    replica.wlock().replicaBeginDelete(
                            vbR, dcpData.first, revision, qi->getBySeqno());
                    break;
                }
                case SystemEvent::CollectionsSeparatorChanged: {
                    auto dcpData = Collections::VB::Manifest::
                            getSystemEventSeparatorData(
                                    {qi->getData(), qi->getNBytes()});
                    replica.wlock().replicaChangeSeparator(
                            vbR, dcpData.first, revision, qi->getBySeqno());
                    break;
                }
                case SystemEvent::DeleteCollectionSoft:
                case SystemEvent::DeleteCollectionHard:

                    // Nothing todo for these events
                    break;
                }
            }
        }
        return rv;
    }

    /**
     * Take SystemEvent item and obtain the JSON manifest.
     * Next create a new/temp MockVBManifest from the JSON.
     * Finally check that this new object is equal to the test class's active
     *
     * @returns gtest assertion fail (with details) or success
     */
    ::testing::AssertionResult checkJson(const Item& manifest) {
        MockVBManifest newManifest(itemToJson(manifest));
        if (active != newManifest) {
            return ::testing::AssertionFailure() << "manifest mismatch\n"
                                                 << "generated\n"
                                                 << newManifest << "\nvs\n"
                                                 << active;
        }
        return ::testing::AssertionSuccess();
    }

    MockVBManifest active;
    MockVBManifest replica;
    EPStats global_stats;
    CheckpointConfig checkpoint_config;
    Configuration config;
    EPVBucket vbA;
    EPVBucket vbR;

    static const int64_t snapEnd{200};
};

class VBucketManifestTest : public ::testing::Test {
public:
    ActiveReplicaManifest manifest;
};

TEST_F(VBucketManifestTest, collectionExists) {
    EXPECT_TRUE(manifest.update(
            R"({"revision":0,"separator":"::","collections":["vegetable"]})"));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
    EXPECT_TRUE(manifest.isExclusiveOpen("vegetable", 0));
}

TEST_F(VBucketManifestTest, defaultCollectionExists) {
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"anykey", DocNamespace::DefaultCollection}));
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::","collections":[]})"));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"anykey", DocNamespace::DefaultCollection}));
}

TEST_F(VBucketManifestTest, updates) {
    EXPECT_TRUE(manifest.checkSize(1));
    EXPECT_TRUE(manifest.isExclusiveOpen("$default", 0));

    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::",)"
            R"("collections":["$default","vegetable"]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveOpen("vegetable", 1));

    EXPECT_TRUE(manifest.update(
            R"({"revision":2,"separator":"::",)"
            R"("collections":["$default", "vegetable", "fruit"]})"));
    EXPECT_TRUE(manifest.checkSize(3));
    EXPECT_TRUE(manifest.isExclusiveOpen("fruit", 2));

    EXPECT_TRUE(manifest.update(
            R"({"revision":3,"separator":"::",)"
            R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})"));
    EXPECT_TRUE(manifest.checkSize(5));
    EXPECT_TRUE(manifest.isExclusiveOpen("meat", 3));
    EXPECT_TRUE(manifest.isExclusiveOpen("dairy", 3));
}

TEST_F(VBucketManifestTest, updates2) {
    EXPECT_TRUE(manifest.update(
            R"({"revision":0,"separator":"::",)"
            R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})"));
    EXPECT_TRUE(manifest.checkSize(5));

    // Remove meat and dairy, size is not affected because the delete is only
    // starting
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::",)"
            R"("collections":["$default", "vegetable", "fruit"]})"));
    EXPECT_TRUE(manifest.checkSize(5));
    EXPECT_TRUE(manifest.isExclusiveDeleting("meat", 1));
    EXPECT_TRUE(manifest.isExclusiveDeleting("dairy", 1));

    // But vegetable is accessible, the others are locked out
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"anykey", DocNamespace::DefaultCollection}));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"dairy::milk", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"meat::chicken", DocNamespace::Collections}));
}

TEST_F(VBucketManifestTest, updates3) {
    EXPECT_TRUE(manifest.update(
            R"({"revision":0,"separator":"::",)"
            R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})"));
    EXPECT_TRUE(manifest.checkSize(5));

    // Remove everything
    EXPECT_TRUE(manifest.update(
            R"({"revision":1, "separator":"::","collections":[]})"));
    EXPECT_TRUE(manifest.checkSize(5));
    EXPECT_TRUE(manifest.isExclusiveDeleting("$default", 1));
    EXPECT_TRUE(manifest.isExclusiveDeleting("vegetable", 1));
    EXPECT_TRUE(manifest.isExclusiveDeleting("fruit", 1));
    EXPECT_TRUE(manifest.isExclusiveDeleting("meat", 1));
    EXPECT_TRUE(manifest.isExclusiveDeleting("dairy", 1));

    // But vegetable is accessible, the others are 'locked' out
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"dairy::milk", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"meat::chicken", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"fruit::apple", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"anykey", DocNamespace::DefaultCollection}));
}

TEST_F(VBucketManifestTest, add_beginDelete_add) {
    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":0,"separator":"::","collections":["vegetable"]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveOpen("vegetable", 0));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));

    // remove vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::","collections":[]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveDeleting("vegetable", 1));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":2,"separator":"::","collections":["vegetable"]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isOpenAndDeleting("vegetable", 2));

    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
}

TEST_F(VBucketManifestTest, add_beginDelete_delete) {
    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":0,"separator":"::","collections":["vegetable"]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveOpen("vegetable", 0));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));

    // remove vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::","collections":[]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveDeleting("vegetable", 1));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));

    // finally remove vegetable
    EXPECT_TRUE(manifest.completeDeletion("vegetable", 1));
    EXPECT_TRUE(manifest.checkSize(1));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
}

TEST_F(VBucketManifestTest, add_beginDelete_add_delete) {
    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":0,"separator":"::","collections":["vegetable"]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveOpen("vegetable", 0));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));

    // remove vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::","collections":[]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isExclusiveDeleting("vegetable", 1));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));

    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":2,"separator":"::","collections":["vegetable"]})"));
    EXPECT_TRUE(manifest.checkSize(2));
    EXPECT_TRUE(manifest.isOpenAndDeleting("vegetable", 2));

    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));

    // finally remove vegetable
    EXPECT_TRUE(manifest.completeDeletion("vegetable", 3));
    EXPECT_TRUE(manifest.checkSize(2));

    // No longer OpenAndDeleting, now ExclusiveOpen
    EXPECT_TRUE(manifest.isExclusiveOpen("vegetable", 2));

    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
}

TEST_F(VBucketManifestTest, invalidDeletes) {
    // add vegetable
    EXPECT_TRUE(manifest.update(R"({"revision":1,"separator":"::",)"
                                R"("collections":["$default","vegetable"]})"));
    // Delete vegetable
    EXPECT_TRUE(manifest.update(R"({"revision":2,"separator":"::",)"
                                R"("collections":["$default"]})"));

    // Invalid.
    EXPECT_FALSE(manifest.completeDeletion("unknown", 1));
    EXPECT_FALSE(manifest.completeDeletion("$default", 1));

    EXPECT_TRUE(manifest.completeDeletion("vegetable", 1));

    // Delete $default
    EXPECT_TRUE(manifest.update(R"({"revision":3,"separator":"::",)"
                                R"("collections":[]})"));
    // Add $default
    EXPECT_TRUE(manifest.update(R"({"revision":4,"separator":"::",)"
                                R"("collections":["$default"]})"));
    EXPECT_TRUE(manifest.completeDeletion("$default", 3));
}

// Check that a deleting collection doesn't keep adding system events
TEST_F(VBucketManifestTest, doubleDelete) {
    auto seqno = manifest.getActiveVB().getHighSeqno();
    // add vegetable
    EXPECT_TRUE(manifest.update(R"({"revision":1,"separator":"::",)"
                                R"("collections":["$default","vegetable"]})"));
    EXPECT_LT(seqno, manifest.getActiveVB().getHighSeqno());
    seqno = manifest.getActiveVB().getHighSeqno();

    // Apply same manifest (different revision). Nothing will be created or
    // deleted. Apply direct to vbm, not via manifest.update as that would
    // complain about the lack of events
    manifest.getActiveManifest().wlock().update(
            manifest.getActiveVB(),
            {R"({"revision":2,"separator":"::",)"
             R"("collections":["$default","vegetable"]})"});

    EXPECT_EQ(seqno, manifest.getActiveVB().getHighSeqno());
    seqno = manifest.getActiveVB().getHighSeqno();

    // Now delete vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":3,"separator":"::","collections":["$default"]})"));

    EXPECT_LT(seqno, manifest.getActiveVB().getHighSeqno());
    seqno = manifest.getActiveVB().getHighSeqno();

    // same again, should have be nothing created or deleted
    manifest.getActiveManifest().wlock().update(
            manifest.getActiveVB(),
            {R"({"revision":4,"separator":"::",)"
             R"("collections":["$default"]})"});

    EXPECT_EQ(seqno, manifest.getActiveVB().getHighSeqno());
}

// This test changes the separator and propagates to the replica (all done
// via the noThrow helper functions).
TEST_F(VBucketManifestTest, active_replica_separatorChanges) {
    // Can change separator to @ as only default exists
    EXPECT_TRUE(manifest.update(
            R"({"revision":1, "separator":"@", "collections":["$default"]})"));

    // Can change separator to / and add first collection
    EXPECT_TRUE(manifest.update(
            R"({"revision":2, "separator":"/", "collections":["$default", "vegetable"]})"));

    // Cannot change separator to ## because non-default collections exist
    EXPECT_FALSE(manifest.update(
            R"({"revision":3, "separator":"##", "collections":["$default", "vegetable"]})"));

    // Now just remove vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":3, "separator":"/", "collections":["$default"]})"));

    // vegetable still exists (isDeleting), but change to ##
    EXPECT_TRUE(manifest.update(
            R"({"revision":4, "separator":"##", "collections":["$default"]})"));

    // Finish removal of vegetable
    EXPECT_TRUE(manifest.completeDeletion("vegetable", 4));

    // Can change separator as only default exists
    EXPECT_TRUE(manifest.update(
            R"({"revision":5, "separator":"@", "collections":["$default"]})"));

    // Remove default
    EXPECT_TRUE(manifest.update(
            R"({"revision":6, "separator":"/", "collections":[]})"));

    // $default still exists (isDeleting), so cannot change to ##
    EXPECT_TRUE(manifest.update(
            R"({"revision":7, "separator":"##", "collections":["$default"]})"));

    EXPECT_TRUE(manifest.completeDeletion("$default", 5));

    // Can change separator as no collection exists
    EXPECT_TRUE(manifest.update(
            R"({"revision":8, "separator":"-=-=-=-", "collections":[]})"));

    // Add a collection and check the new separator
    EXPECT_TRUE(manifest.update(
            R"({"revision":9, "separator":"-=-=-=-", "collections":["meat"]})"));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"meat-=-=-=-bacon", DocNamespace::Collections}));
}

TEST_F(VBucketManifestTest, replica_add_remove) {
    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::","collections":)"
            R"(["$default","vegetable"]})"));

    // add meat & dairy
    EXPECT_TRUE(manifest.update(
            R"({"revision":2,"separator":"::","collections":)"
            R"(["$default","vegetable","meat","dairy"]})"));

    // remove vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":3,"separator":"::","collections":)"
            R"(["$default","meat","dairy"]})"));

    // remove $default
    EXPECT_TRUE(manifest.update(
            R"({"revision":4,"separator":"::","collections":)"
            R"(["meat","dairy"]})"));

    // Check we can access the remaining collections
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"vegetable::carrot", DocNamespace::Collections}));
    EXPECT_FALSE(manifest.doesKeyContainValidCollection(
            {"anykey", DocNamespace::DefaultCollection}));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"meat::sausage", DocNamespace::Collections}));
    EXPECT_TRUE(manifest.doesKeyContainValidCollection(
            {"dairy::butter", DocNamespace::Collections}));
}

TEST_F(VBucketManifestTest, replica_add_remove_completeDelete) {
    // add vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":1,"separator":"::","collections":["$default","vegetable"]})"));

    // remove vegetable
    EXPECT_TRUE(manifest.update(
            R"({"revision":2,"separator":"::","collections":["$default"]})"));

    // Finish removal of vegetable
    EXPECT_TRUE(manifest.completeDeletion("vegetable", 2));
}


text:
module CleverElements
  class Client
    API_VERSION = "1.0"

    attr_reader :savon, :user_id, :api_key, :mode

    def initialize user_id, api_key, mode = 'test'
      @savon = Savon::Client.new "http://api.sendcockpit.com/server.php?wsdl"
      @user_id, @api_key, @mode = user_id, api_key, mode
    end

    def request method, arguments = {}
      savon.request method do
        soap.body = arguments
        soap.header = authentication_header
      end
    end

    def authentication_header
      {
        :validate => {
          :userid => user_id,
          :apikey => api_key,
          :version => API_VERSION,
          :mode => mode
        }
      }
    end

    def proxy
      @proxy ||= Proxy.new self
    end
  end
end


text:
<?php namespace App\Providers;

use View;
use Illuminate\Support\ServiceProvider;
use App\Http\ViewComposers\SiteComposer;

class ComposerServiceProvider extends ServiceProvider {

    /**
     * Register bindings in the container.
     *
     * @return void
     */
    public function boot()
    {
        View::composer('*', 'App\Http\ViewComposers\SiteComposer');
    }

    /**
     * Register
     *
     * @return void
     */
    public function register()
    {
        //
    }

}