<commit_before>/*
 * Copyright (c) 2017 Immo Software
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its contributors may
 *   be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "sampler_voice.h"
#include "reader_thread.h"
#include "ui.h"
#include "debug_log.h"
#include "utility.h"

using namespace slab;

//------------------------------------------------------------------------------
// Code
//------------------------------------------------------------------------------

SampleBufferManager::SampleBufferManager()
:   _number(0),
    _fullBuffers(),
    _emptyBuffers(),
    _primeMutex(),
    _currentBuffer(nullptr),
    _activeBufferCount(0),
    _totalSamples(0),
    _startSample(0),
    _samplesPlayed(0),
    _samplesRead(0),
    _samplesQueued(0),
    _didReadFileStart(false),
    _waitingForFileStart(true),
    _isReady(false)
{
}

void SampleBufferManager::init(SamplerVoice * voice, int16_t * buffer)
{
    _voice = voice;
    _number = voice->get_number();
    _primeMutex.init("prime");

    uint32_t i;
    for (i = 0; i < kBufferCount; ++i)
    {
        _buffer[i].number = i;
        _buffer[i].state = SampleBuffer::State::kUnused;
        _buffer[i].data = &buffer[i * kBufferSize];
        _buffer[i].startFrame = 0;
        _buffer[i].frameCount = kBufferSize;
        _buffer[i].readHead = 0;
        _buffer[i].reread = false;
    }
}

void SampleBufferManager::set_file(uint32_t totalFrames)
{
    _startSample = 0;
    _totalSamples = totalFrames;
    _endSample = totalFrames;
    _activeBufferCount = min(round_up_div(_totalSamples, kBufferSize), kBufferCount);
    _samplesPlayed = 0;
    _samplesRead = 0;
    _samplesQueued = 0;
    _didReadFileStart = false;
    _waitingForFileStart = true;
    _isReady = false;
}

void SampleBufferManager::prime()
{
    Ar::Mutex::Guard guard(_primeMutex);
    DEBUG_PRINTF(QUEUE_MASK, "V%lu: start prime\r\n", _number);

    // Reset state.
    _samplesPlayed = _startSample;
    _samplesRead = _didReadFileStart ? kBufferSize : _startSample;
    _samplesQueued = _samplesRead;

    // Clear buffers queues.
    ReaderThread::get()->clear_voice_queue(_voice);
    _fullBuffers.clear();
    _emptyBuffers.clear();

    // Playing will start from the file start buffer.
    _currentBuffer = &_buffer[0];
    _buffer[0].readHead = 0;

    // Queue up the rest of the available buffers to be filled.
    uint32_t i = _didReadFileStart ? 1 : 0;
    for (; i < _activeBufferCount; ++i)
    {
        // If the buffer is currently being filled by the reader thread, then we can't touch
        // it. So just flag it for requeuing when it's finished. This will be handled in
        // enqueue_full_buffer().
        if (_buffer[i].state == SampleBuffer::State::kReading)
        {
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: prime: marking b%lu for reread\r\n", _number, i);
            _buffer[i].reread = true;
        }
        else
        {
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: prime: queuing b%lu for read\r\n", _number, i);
            queue_buffer_for_read(&_buffer[i]);
        }
    }
    DEBUG_PRINTF(QUEUE_MASK, "V%lu: end prime\r\n", _number);
}

SampleBuffer * SampleBufferManager::get_current_buffer()
{
    if (!_currentBuffer)
    {
        _currentBuffer = dequeue_next_buffer();
    }
    return _currentBuffer;
}

void SampleBufferManager::queue_buffer_for_read(SampleBuffer * buffer)
{
    buffer->startFrame = _samplesQueued;
    buffer->frameCount = min(kBufferSize, _endSample - _samplesQueued);
    buffer->reread = false;
    buffer->state = SampleBuffer::State::kFree;

    _emptyBuffers.put(buffer);
    ReaderThread::get()->enqueue(_voice);

    _samplesQueued += buffer->frameCount;
}

SampleBuffer * SampleBufferManager::get_empty_buffer()
{
    Ar::Mutex::Guard guard(_primeMutex);

    SampleBuffer * buffer;
    if (_emptyBuffers.get(buffer))
    {
        buffer->state = SampleBuffer::State::kReading;
        return buffer;
    }
    else
    {
        return nullptr;
    }
}

void SampleBufferManager::retire_buffer(SampleBuffer * buffer)
{
    _samplesPlayed += buffer->frameCount;

    if (_samplesPlayed >= _endSample)
    {
        DEBUG_PRINTF(RETIRE_MASK, "V%lu: retiring b%d; played %lu (done)\r\n", _number, buffer->number, _samplesPlayed);

        _voice->playing_did_finish();
    }
    else
    {
        DEBUG_PRINTF(RETIRE_MASK, "V%lu: retiring b%d; played %lu\r\n", _number, buffer->number, _samplesPlayed);

        // Don't queue up file start buffer for reading, and don't queue more than the active
        // number of samples.
        if (buffer != &_buffer[0] && _samplesQueued < _endSample)
        {
            DEBUG_PRINTF(RETIRE_MASK|QUEUE_MASK, "V%lu: retire: queue b%d to read @ %lu\r\n", _number, buffer->number, _samplesPlayed);
            queue_buffer_for_read(buffer);
        }

        dequeue_next_buffer();
    }
}

SampleBuffer * SampleBufferManager::dequeue_next_buffer()
{
    SampleBuffer * buffer;
    if (_fullBuffers.get(buffer))
    {
        DEBUG_PRINTF(CURBUF_MASK, "V%lu: current buffer = %d\r\n", _number, buffer->number);
        _currentBuffer = buffer;
        buffer->state = SampleBuffer::State::kPlaying;
    }
    else
    {
        DEBUG_PRINTF(ERROR_MASK, "V%lu: *** NO READY BUFFERS ***\r\n", _number);
        Ar::_halt();
        _currentBuffer = nullptr;
    }
    return _currentBuffer;
}

void SampleBufferManager::enqueue_full_buffer(SampleBuffer * buffer)
{
    assert(buffer);
    if (buffer == &_buffer[0])
    {
        _didReadFileStart = true;
        if (_waitingForFileStart)
        {
            _waitingForFileStart = false;
            _isReady = true;
        }
        _samplesRead += buffer->frameCount;
        buffer->state = SampleBuffer::State::kReady;
    }
    else
    {
        if (buffer->reread)
        {
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: queuing b%d for reread\r\n", _number, buffer->number);
            queue_buffer_for_read(buffer);
        }
        else
        {
            _samplesRead += buffer->frameCount;

            buffer->state = SampleBuffer::State::kReady;
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: queuing b%d for play\r\n", _number, buffer->number);
            _fullBuffers.put(buffer);
        }
    }
}

void SampleBufferManager::set_start_sample(uint32_t start)
{
    // Voice must not be playing.
    assert(!_voice->is_playing());

    _isReady = false;
    _didReadFileStart = false;
    _waitingForFileStart = true;
    _startSample = start;
    if (_endSample < _startSample)
    {
        _endSample = _startSample;
    }

    _activeBufferCount = min(round_up_div(get_active_samples(), kBufferSize), kBufferCount);

    prime();
}

void SampleBufferManager::set_end_sample(uint32_t end)
{
    // Voice must not be playing.
    assert(!_voice->is_playing());
    assert(end >= _startSample);

    _endSample = end;

    _activeBufferCount = min(round_up_div(get_active_samples(), kBufferSize), kBufferCount);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

SamplerVoice::SamplerVoice()
:   _wav(),
    _data(),
    _manager(),
    _isPlaying(false),
    _turnOnLedNextBuffer(false),
    _lastBufferLastSample(0),
    _fraction(0.0f),
    _gain(1.0f),
    _pitch(1.0f)
{
}

void SamplerVoice::init(uint32_t n, int16_t * buffer)
{
    _number = n;
    _manager.init(this, buffer);
}

void SamplerVoice::set_file(WaveFile& file)
{
    _wav = file;
    _data = _wav.get_audio_data();
    _isPlaying = false;
    _manager.set_file(_data.get_frames());
}

void SamplerVoice::_reset_voice()
{
    _isPlaying = false;
    _fraction = 0.0f;
    _lastBufferLastSample = 0.0f;
}

void SamplerVoice::prime()
{
    _reset_voice();
    _manager.prime();
}

void SamplerVoice::trigger()
{
    // Ignore the trigger if the manager isn't ready to play.
    if (!_manager.is_ready())
    {
        return;
    }

    // Handle re-triggering while sample is already playing.
    if (_isPlaying)
    {
        DEBUG_PRINTF(RETRIG_MASK, "V%lu: retrigger (@%lu)\r\n", _number, _manager.get_samples_played());

        // Start playing over from file start.
        prime();

        UI::get().set_voice_playing(_number, false);
        _turnOnLedNextBuffer = true;
    }
    else
    {
        UI::get().set_voice_playing(_number, true);
    }
    _isPlaying = true;
}

void SamplerVoice::playing_did_finish()
{
    UI::get().set_voice_playing(_number, false);
    prime();
}

void SamplerVoice::render(int16_t * data, uint32_t frameCount)
{
    uint32_t i;

    // Get the current buffer if playing.
    SampleBuffer * voiceBuffer = nullptr;
    if (is_valid() && is_playing())
    {
        voiceBuffer = _manager.get_current_buffer();
    }

    // If not playing, just fill with silence.
    if (!voiceBuffer)
    {
        for (i = 0; i < frameCount; ++i)
        {
            *data = 0;
            data += 2;
        }
        return;
    }

    float rate = _pitch;
    int16_t * bufferData = voiceBuffer->data;
    uint32_t readHead = voiceBuffer->readHead;
    uint32_t bufferFrameCount = voiceBuffer->frameCount;
    float s0 = 0.0f;
    float s1 = 0.0f;

    // Special case for unmodified pitch.
    if (rate == 1.0f)
    {
        uint32_t framesRemainingInBuffer = bufferFrameCount - readHead;
        uint32_t framesFromBuffer = min(framesRemainingInBuffer, frameCount);

        // Render sample data into the output buffer.
        for (i = 0; i < framesFromBuffer; ++i)
        {
            s1 = float(bufferData[readHead++]);

            // Apply the gain.
            float s = s1 * _gain;
            *data = int16_t(s);
            data += 2;
        }
    }
    else
    {
        float readHeadFraction = _fraction;
        s1 = _lastBufferLastSample;

        // Render sample data into the output buffer at the specified playback rate.
        for (i = 0; i < frameCount; ++i)
        {
            readHeadFraction += rate;
            if (readHeadFraction >= 1.0f)
            {
                uint32_t index = uint32_t(readHeadFraction);
                readHeadFraction -= index;
                readHead += index;

                // Check if we reached the end of this sample buffer.
                if (readHead >= bufferFrameCount)
                {
                    _manager.retire_buffer(voiceBuffer);

                    // If we're no longer playing, exit this loop.
                    if (!_isPlaying)
                    {
                        // Set some variables used outside the loop below.
                        readHead = voiceBuffer->readHead;
                        readHeadFraction = 0.0f;
                        s1 = 0.0f;
                        break;
                    }

                    // Get the next buffer and update locals.
                    voiceBuffer = _manager.get_current_buffer();
                    bufferData = voiceBuffer->data;
                    readHead = voiceBuffer->readHead + (bufferFrameCount - readHead);
                    bufferFrameCount = voiceBuffer->frameCount;
                }

                s0 = s1;
                s1 = float(bufferData[readHead]);
            }

            // Perform simple linear interpolation, then apply the gain.
            float s = (s0 + readHeadFraction * (s1 - s0)) * _gain;
            *data = int16_t(s);
            data += 2;
        }

        _fraction = readHeadFraction;
    }

    voiceBuffer->readHead = readHead;
    _lastBufferLastSample = s1;

    // Fill any leftover frames with silence.
    for (; i < frameCount; ++i)
    {
        *data = 0;
        data += 2;
    }

    // Did we finish this buffer?
    if (readHead >= bufferFrameCount)
    {
        _manager.retire_buffer(voiceBuffer);
    }

    if (_turnOnLedNextBuffer)
    {
        _turnOnLedNextBuffer = false;
        UI::get().set_voice_playing(_number, true);
    }
}

void SamplerVoice::set_sample_start(float start)
{
    // Stop playing and turn off LED.
    _reset_voice();
    UI::get().set_voice_playing(_number, false);

    // Tell sample manager to set and load new start point.
    uint32_t sample = uint32_t(float(_manager.get_total_samples()) * start);
    _manager.set_start_sample(sample);
}

void SamplerVoice::set_sample_end(float end)
{
    // Stop playing and turn off LED.
    _reset_voice();
    UI::get().set_voice_playing(_number, false);

    uint32_t startSample = _manager.get_start_sample();
    uint32_t s = _manager.get_total_samples() - startSample;
    uint32_t sample = startSample + uint32_t(float(s) * end);
    _manager.set_end_sample(sample);
}

//------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------
<commit_msg>Fixed _samplesRead value set in SampleBufferManager::prime().<commit_after>/*
 * Copyright (c) 2017 Immo Software
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its contributors may
 *   be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "sampler_voice.h"
#include "reader_thread.h"
#include "ui.h"
#include "debug_log.h"
#include "utility.h"

using namespace slab;

//------------------------------------------------------------------------------
// Code
//------------------------------------------------------------------------------

SampleBufferManager::SampleBufferManager()
:   _number(0),
    _fullBuffers(),
    _emptyBuffers(),
    _primeMutex(),
    _currentBuffer(nullptr),
    _activeBufferCount(0),
    _totalSamples(0),
    _startSample(0),
    _samplesPlayed(0),
    _samplesRead(0),
    _samplesQueued(0),
    _didReadFileStart(false),
    _waitingForFileStart(true),
    _isReady(false)
{
}

void SampleBufferManager::init(SamplerVoice * voice, int16_t * buffer)
{
    _voice = voice;
    _number = voice->get_number();
    _primeMutex.init("prime");

    uint32_t i;
    for (i = 0; i < kBufferCount; ++i)
    {
        _buffer[i].number = i;
        _buffer[i].state = SampleBuffer::State::kUnused;
        _buffer[i].data = &buffer[i * kBufferSize];
        _buffer[i].startFrame = 0;
        _buffer[i].frameCount = kBufferSize;
        _buffer[i].readHead = 0;
        _buffer[i].reread = false;
    }
}

void SampleBufferManager::set_file(uint32_t totalFrames)
{
    _startSample = 0;
    _totalSamples = totalFrames;
    _endSample = totalFrames;
    _activeBufferCount = min(round_up_div(_totalSamples, kBufferSize), kBufferCount);
    _samplesPlayed = 0;
    _samplesRead = 0;
    _samplesQueued = 0;
    _didReadFileStart = false;
    _waitingForFileStart = true;
    _isReady = false;
}

void SampleBufferManager::prime()
{
    Ar::Mutex::Guard guard(_primeMutex);
    DEBUG_PRINTF(QUEUE_MASK, "V%lu: start prime\r\n", _number);

    // Reset state.
    _samplesPlayed = _startSample;
    _samplesRead = _didReadFileStart ? _startSample + kBufferSize : _startSample;
    _samplesQueued = _samplesRead;

    // Clear buffers queues.
    ReaderThread::get()->clear_voice_queue(_voice);
    _fullBuffers.clear();
    _emptyBuffers.clear();

    // Playing will start from the file start buffer.
    _currentBuffer = &_buffer[0];
    _buffer[0].readHead = 0;

    // Queue up the rest of the available buffers to be filled.
    uint32_t i = _didReadFileStart ? 1 : 0;
    for (; i < _activeBufferCount; ++i)
    {
        // If the buffer is currently being filled by the reader thread, then we can't touch
        // it. So just flag it for requeuing when it's finished. This will be handled in
        // enqueue_full_buffer().
        if (_buffer[i].state == SampleBuffer::State::kReading)
        {
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: prime: marking b%lu for reread\r\n", _number, i);
            _buffer[i].reread = true;
        }
        else
        {
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: prime: queuing b%lu for read\r\n", _number, i);
            queue_buffer_for_read(&_buffer[i]);
        }
    }
    DEBUG_PRINTF(QUEUE_MASK, "V%lu: end prime\r\n", _number);
}

SampleBuffer * SampleBufferManager::get_current_buffer()
{
    if (!_currentBuffer)
    {
        _currentBuffer = dequeue_next_buffer();
    }
    return _currentBuffer;
}

void SampleBufferManager::queue_buffer_for_read(SampleBuffer * buffer)
{
    buffer->startFrame = _samplesQueued;
    buffer->frameCount = min(kBufferSize, _endSample - _samplesQueued);
    buffer->reread = false;
    buffer->state = SampleBuffer::State::kFree;

    _emptyBuffers.put(buffer);
    ReaderThread::get()->enqueue(_voice);

    _samplesQueued += buffer->frameCount;
}

SampleBuffer * SampleBufferManager::get_empty_buffer()
{
    Ar::Mutex::Guard guard(_primeMutex);

    SampleBuffer * buffer;
    if (_emptyBuffers.get(buffer))
    {
        buffer->state = SampleBuffer::State::kReading;
        return buffer;
    }
    else
    {
        return nullptr;
    }
}

void SampleBufferManager::retire_buffer(SampleBuffer * buffer)
{
    _samplesPlayed += buffer->frameCount;

    if (_samplesPlayed >= _endSample)
    {
        DEBUG_PRINTF(RETIRE_MASK, "V%lu: retiring b%d; played %lu (done)\r\n", _number, buffer->number, _samplesPlayed);

        _voice->playing_did_finish();
    }
    else
    {
        DEBUG_PRINTF(RETIRE_MASK, "V%lu: retiring b%d; played %lu\r\n", _number, buffer->number, _samplesPlayed);

        // Don't queue up file start buffer for reading, and don't queue more than the active
        // number of samples.
        if (buffer != &_buffer[0] && _samplesQueued < _endSample)
        {
            DEBUG_PRINTF(RETIRE_MASK|QUEUE_MASK, "V%lu: retire: queue b%d to read @ %lu\r\n", _number, buffer->number, _samplesPlayed);
            queue_buffer_for_read(buffer);
        }

        dequeue_next_buffer();
    }
}

SampleBuffer * SampleBufferManager::dequeue_next_buffer()
{
    SampleBuffer * buffer;
    if (_fullBuffers.get(buffer))
    {
        DEBUG_PRINTF(CURBUF_MASK, "V%lu: current buffer = %d\r\n", _number, buffer->number);
        _currentBuffer = buffer;
        buffer->state = SampleBuffer::State::kPlaying;
    }
    else
    {
        DEBUG_PRINTF(ERROR_MASK, "V%lu: *** NO READY BUFFERS ***\r\n", _number);
        Ar::_halt();
        _currentBuffer = nullptr;
    }
    return _currentBuffer;
}

void SampleBufferManager::enqueue_full_buffer(SampleBuffer * buffer)
{
    assert(buffer);
    if (buffer == &_buffer[0])
    {
        _didReadFileStart = true;
        if (_waitingForFileStart)
        {
            _waitingForFileStart = false;
            _isReady = true;
        }
        _samplesRead += buffer->frameCount;
        buffer->state = SampleBuffer::State::kReady;
    }
    else
    {
        if (buffer->reread)
        {
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: queuing b%d for reread\r\n", _number, buffer->number);
            queue_buffer_for_read(buffer);
        }
        else
        {
            _samplesRead += buffer->frameCount;

            buffer->state = SampleBuffer::State::kReady;
            DEBUG_PRINTF(QUEUE_MASK, "V%lu: queuing b%d for play\r\n", _number, buffer->number);
            _fullBuffers.put(buffer);
        }
    }
}

void SampleBufferManager::set_start_sample(uint32_t start)
{
    // Voice must not be playing.
    assert(!_voice->is_playing());

    _isReady = false;
    _didReadFileStart = false;
    _waitingForFileStart = true;
    _startSample = start;
    if (_endSample < _startSample)
    {
        _endSample = _startSample;
    }

    _activeBufferCount = min(round_up_div(get_active_samples(), kBufferSize), kBufferCount);

    prime();
}

void SampleBufferManager::set_end_sample(uint32_t end)
{
    // Voice must not be playing.
    assert(!_voice->is_playing());
    assert(end >= _startSample);

    _endSample = end;

    _activeBufferCount = min(round_up_div(get_active_samples(), kBufferSize), kBufferCount);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

SamplerVoice::SamplerVoice()
:   _wav(),
    _data(),
    _manager(),
    _isPlaying(false),
    _turnOnLedNextBuffer(false),
    _lastBufferLastSample(0),
    _fraction(0.0f),
    _gain(1.0f),
    _pitch(1.0f)
{
}

void SamplerVoice::init(uint32_t n, int16_t * buffer)
{
    _number = n;
    _manager.init(this, buffer);
}

void SamplerVoice::set_file(WaveFile& file)
{
    _wav = file;
    _data = _wav.get_audio_data();
    _isPlaying = false;
    _manager.set_file(_data.get_frames());
}

void SamplerVoice::_reset_voice()
{
    _isPlaying = false;
    _fraction = 0.0f;
    _lastBufferLastSample = 0.0f;
}

void SamplerVoice::prime()
{
    _reset_voice();
    _manager.prime();
}

void SamplerVoice::trigger()
{
    // Ignore the trigger if the manager isn't ready to play.
    if (!_manager.is_ready())
    {
        return;
    }

    // Handle re-triggering while sample is already playing.
    if (_isPlaying)
    {
        DEBUG_PRINTF(RETRIG_MASK, "V%lu: retrigger (@%lu)\r\n", _number, _manager.get_samples_played());

        // Start playing over from file start.
        prime();

        UI::get().set_voice_playing(_number, false);
        _turnOnLedNextBuffer = true;
    }
    else
    {
        UI::get().set_voice_playing(_number, true);
    }
    _isPlaying = true;
}

void SamplerVoice::playing_did_finish()
{
    UI::get().set_voice_playing(_number, false);
    prime();
}

void SamplerVoice::render(int16_t * data, uint32_t frameCount)
{
    uint32_t i;

    // Get the current buffer if playing.
    SampleBuffer * voiceBuffer = nullptr;
    if (is_valid() && is_playing())
    {
        voiceBuffer = _manager.get_current_buffer();
    }

    // If not playing, just fill with silence.
    if (!voiceBuffer)
    {
        for (i = 0; i < frameCount; ++i)
        {
            *data = 0;
            data += 2;
        }
        return;
    }

    float rate = _pitch;
    int16_t * bufferData = voiceBuffer->data;
    uint32_t readHead = voiceBuffer->readHead;
    uint32_t bufferFrameCount = voiceBuffer->frameCount;
    float s0 = 0.0f;
    float s1 = 0.0f;

    // Special case for unmodified pitch.
    if (rate == 1.0f)
    {
        uint32_t framesRemainingInBuffer = bufferFrameCount - readHead;
        uint32_t framesFromBuffer = min(framesRemainingInBuffer, frameCount);

        // Render sample data into the output buffer.
        for (i = 0; i < framesFromBuffer; ++i)
        {
            s1 = float(bufferData[readHead++]);

            // Apply the gain.
            float s = s1 * _gain;
            *data = int16_t(s);
            data += 2;
        }
    }
    else
    {
        float readHeadFraction = _fraction;
        s1 = _lastBufferLastSample;

        // Render sample data into the output buffer at the specified playback rate.
        for (i = 0; i < frameCount; ++i)
        {
            readHeadFraction += rate;
            if (readHeadFraction >= 1.0f)
            {
                uint32_t index = uint32_t(readHeadFraction);
                readHeadFraction -= index;
                readHead += index;

                // Check if we reached the end of this sample buffer.
                if (readHead >= bufferFrameCount)
                {
                    _manager.retire_buffer(voiceBuffer);

                    // If we're no longer playing, exit this loop.
                    if (!_isPlaying)
                    {
                        // Set some variables used outside the loop below.
                        readHead = voiceBuffer->readHead;
                        readHeadFraction = 0.0f;
                        s1 = 0.0f;
                        break;
                    }

                    // Get the next buffer and update locals.
                    voiceBuffer = _manager.get_current_buffer();
                    bufferData = voiceBuffer->data;
                    readHead = voiceBuffer->readHead + (bufferFrameCount - readHead);
                    bufferFrameCount = voiceBuffer->frameCount;
                }

                s0 = s1;
                s1 = float(bufferData[readHead]);
            }

            // Perform simple linear interpolation, then apply the gain.
            float s = (s0 + readHeadFraction * (s1 - s0)) * _gain;
            *data = int16_t(s);
            data += 2;
        }

        _fraction = readHeadFraction;
    }

    voiceBuffer->readHead = readHead;
    _lastBufferLastSample = s1;

    // Fill any leftover frames with silence.
    for (; i < frameCount; ++i)
    {
        *data = 0;
        data += 2;
    }

    // Did we finish this buffer?
    if (readHead >= bufferFrameCount)
    {
        _manager.retire_buffer(voiceBuffer);
    }

    if (_turnOnLedNextBuffer)
    {
        _turnOnLedNextBuffer = false;
        UI::get().set_voice_playing(_number, true);
    }
}

void SamplerVoice::set_sample_start(float start)
{
    // Stop playing and turn off LED.
    _reset_voice();
    UI::get().set_voice_playing(_number, false);

    // Tell sample manager to set and load new start point.
    uint32_t sample = uint32_t(float(_manager.get_total_samples()) * start);
    _manager.set_start_sample(sample);
}

void SamplerVoice::set_sample_end(float end)
{
    // Stop playing and turn off LED.
    _reset_voice();
    UI::get().set_voice_playing(_number, false);

    uint32_t startSample = _manager.get_start_sample();
    uint32_t s = _manager.get_total_samples() - startSample;
    uint32_t sample = startSample + uint32_t(float(s) * end);
    _manager.set_end_sample(sample);
}

//------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------
<|endoftext|>


<commit_before>// This program is free software licenced under MIT Licence. You can
// find a copy of this licence in LICENCE.txt in the top directory of
// source code.
//

// Self
#include "GreedyRoundStrategy.h"

// Project
#include "globals.h"
#include "World.h"
#include "Region.h"
#include "SuperRegion.h"
#include "common/ScoreComputer.h"

// C++
#include <queue>
#include <limits>
#include <iostream>

namespace warlightAi {

GreedyRoundStrategy::GreedyRoundStrategy(const World &world,
                                         int availableArmies)
    : RoundStrategy(world, availableArmies)
{
    using common::ScoreComputer;

    auto pq_cmp = [](const auto &lhs, const auto &rhs) -> bool {
        return lhs.first < rhs.first;
    };
    std::priority_queue<
                        std::pair<double, SuperRegionPtr>,
                        std::vector<std::pair<double, SuperRegionPtr>>,
                        decltype(pq_cmp)
                       > pq(pq_cmp);
    std::unordered_set<SuperRegionPtr> supers;

    for (auto &myReg : world.getRegionsOwnedBy(Player::ME))
        for (auto &neigh : myReg->getNeighbors()) {
            if (supers.count(neigh->getSuperRegion()))
                continue;

            pq.emplace(
                ScoreComputer::wastelandsBasedScore(neigh->getSuperRegion()),
                neigh->getSuperRegion()
            );
            supers.emplace(neigh->getSuperRegion());
        }

    std::unordered_set<RegionPtr> addedRegs;
    VecOfRegionPtrs neighs;
    while (!pq.empty()) {
        auto superReg = pq.top().second;
        pq.pop();

        for (auto &reg : superReg->getSubRegions()) {
            if (reg->getOwner() == Player::ME || addedRegs.count(reg))
                continue;

            for (auto &regNeigh : reg->getNeighbors())
                if (regNeigh->getOwner() == Player::ME) {
                    addedRegs.emplace(reg);
                    neighs.emplace_back(reg);
                    break;
                }
        }
    }

    for (auto &neigh : neighs) {
        if (availableArmies <= 0)
            break;

        auto biggestReg = static_cast<RegionPtr>(nullptr);
        auto maxArmies = std::numeric_limits<int>::min();
        for (auto &mine : neigh->getNeighbors()) {
            if (mine->getOwner() != Player::ME)
                continue;

            if (mine->getArmies() > maxArmies) {
                maxArmies = mine->getArmies();
                biggestReg = mine;
            }
        }

        auto armiesNeeded = ScoreComputer::armiesNeeded(neigh->getArmies(), 0.7) + 1;
        auto diff = armiesNeeded - maxArmies;
        if (diff > availableArmies) {
            continue;
        } else if (diff > 0) {
            biggestReg->setArmies(biggestReg->getArmies() + diff);
            m_deployments.emplace_back(biggestReg, diff);
            availableArmies -= diff;
        }

        m_attacks.emplace_back(biggestReg, neigh, biggestReg->getArmies() - 1);
        biggestReg->setArmies(1);
    }

    if (availableArmies && m_attacks.size()) {
        m_deployments.emplace_back(std::get<0>(m_attacks.front()), availableArmies);
        std::get<2>(m_attacks.front()) += availableArmies;
    } else if (availableArmies) {
        auto maxArmies = std::numeric_limits<int>::min();
        auto maxReg = static_cast<RegionPtr>(nullptr);
        for (auto &mine : world.getRegionsOwnedBy(Player::ME))
            if (mine->getArmies() > maxArmies) {
                maxArmies = mine->getArmies();
                maxReg = mine;
            }

        m_deployments.emplace_back(maxReg, availableArmies);
    }

    computeMigrations();
}

VecOfPairs GreedyRoundStrategy::getDeployments() const
{
    return m_deployments;
}

VecOfTuples GreedyRoundStrategy::getAttacks() const
{
    return m_attacks;
}

void GreedyRoundStrategy::computeMigrations()
{
    auto visitedRegs = getRegionsOnBorder();
    std::queue<RegionPtr> Q;

    for (auto &reg : visitedRegs)
        Q.emplace(reg);

    while (!Q.empty()) {
        auto nextReg = Q.front();
        Q.pop();

        for (auto &neigh : nextReg->getNeighbors()) {
            if (neigh->getOwner() != Player::ME || visitedRegs.count(neigh))
                continue;

            visitedRegs.emplace(neigh);
            Q.emplace(neigh);
            if (neigh->getArmies() > 1)
                m_attacks.emplace_back(neigh, nextReg, neigh->getArmies() - 1);
        }
    }
}

std::unordered_set<RegionPtr> GreedyRoundStrategy::getRegionsOnBorder()
{
    std::unordered_set<RegionPtr> regsOnBorder;

    for (auto &mine : m_world.getRegionsOwnedBy(Player::ME)) {
        auto isOnBorder = false;
        for (auto &neigh : mine->getNeighbors())
            if (neigh->getOwner() != Player::ME) {
                isOnBorder = true;
                break;
            }

        if (isOnBorder)
            regsOnBorder.emplace(mine);
    }

    return regsOnBorder;
}

} // namespace warlightAi
<commit_msg>Reverse attacks order<commit_after>// This program is free software licenced under MIT Licence. You can
// find a copy of this licence in LICENCE.txt in the top directory of
// source code.
//

// Self
#include "GreedyRoundStrategy.h"

// Project
#include "globals.h"
#include "World.h"
#include "Region.h"
#include "SuperRegion.h"
#include "common/ScoreComputer.h"

// C++
#include <queue>
#include <limits>
#include <iostream>

namespace warlightAi {

GreedyRoundStrategy::GreedyRoundStrategy(const World &world,
                                         int availableArmies)
    : RoundStrategy(world, availableArmies)
{
    using common::ScoreComputer;

    auto pq_cmp = [](const auto &lhs, const auto &rhs) -> bool {
        return lhs.first < rhs.first;
    };
    std::priority_queue<
                        std::pair<double, SuperRegionPtr>,
                        std::vector<std::pair<double, SuperRegionPtr>>,
                        decltype(pq_cmp)
                       > pq(pq_cmp);
    std::unordered_set<SuperRegionPtr> supers;

    for (auto &myReg : world.getRegionsOwnedBy(Player::ME))
        for (auto &neigh : myReg->getNeighbors()) {
            if (supers.count(neigh->getSuperRegion()))
                continue;

            pq.emplace(
                ScoreComputer::wastelandsBasedScore(neigh->getSuperRegion()),
                neigh->getSuperRegion()
            );
            supers.emplace(neigh->getSuperRegion());
        }

    std::unordered_set<RegionPtr> addedRegs;
    VecOfRegionPtrs neighs;
    while (!pq.empty()) {
        auto superReg = pq.top().second;
        pq.pop();

        for (auto &reg : superReg->getSubRegions()) {
            if (reg->getOwner() == Player::ME || addedRegs.count(reg))
                continue;

            for (auto &regNeigh : reg->getNeighbors())
                if (regNeigh->getOwner() == Player::ME) {
                    addedRegs.emplace(reg);
                    neighs.emplace_back(reg);
                    break;
                }
        }
    }

    for (auto &neigh : neighs) {
        if (availableArmies <= 0)
            break;

        auto biggestReg = static_cast<RegionPtr>(nullptr);
        auto maxArmies = std::numeric_limits<int>::min();
        for (auto &mine : neigh->getNeighbors()) {
            if (mine->getOwner() != Player::ME)
                continue;

            if (mine->getArmies() > maxArmies) {
                maxArmies = mine->getArmies();
                biggestReg = mine;
            }
        }

        auto armiesNeeded = ScoreComputer::armiesNeeded(neigh->getArmies(), 0.7) + 1;
        auto diff = armiesNeeded - maxArmies;
        if (diff > availableArmies) {
            continue;
        } else if (diff > 0) {
            biggestReg->setArmies(biggestReg->getArmies() + diff);
            m_deployments.emplace_back(biggestReg, diff);
            availableArmies -= diff;
        }

        m_attacks.emplace_back(biggestReg, neigh, biggestReg->getArmies() - 1);
        biggestReg->setArmies(1);
    }

    if (availableArmies && m_attacks.size()) {
        m_deployments.emplace_back(std::get<0>(m_attacks.front()), availableArmies);
        std::get<2>(m_attacks.front()) += availableArmies;
    } else if (availableArmies) {
        auto maxArmies = std::numeric_limits<int>::min();
        auto maxReg = static_cast<RegionPtr>(nullptr);
        for (auto &mine : world.getRegionsOwnedBy(Player::ME))
            if (mine->getArmies() > maxArmies) {
                maxArmies = mine->getArmies();
                maxReg = mine;
            }

        m_deployments.emplace_back(maxReg, availableArmies);
    }

    computeMigrations();
}

VecOfPairs GreedyRoundStrategy::getDeployments() const
{
    return m_deployments;
}

VecOfTuples GreedyRoundStrategy::getAttacks() const
{
    std::reverse(m_attacks.begin(), m_attacks.end());
    return m_attacks;
}

void GreedyRoundStrategy::computeMigrations()
{
    auto visitedRegs = getRegionsOnBorder();
    std::queue<RegionPtr> Q;

    for (auto &reg : visitedRegs)
        Q.emplace(reg);

    while (!Q.empty()) {
        auto nextReg = Q.front();
        Q.pop();

        for (auto &neigh : nextReg->getNeighbors()) {
            if (neigh->getOwner() != Player::ME || visitedRegs.count(neigh))
                continue;

            visitedRegs.emplace(neigh);
            Q.emplace(neigh);
            if (neigh->getArmies() > 1)
                m_attacks.emplace_back(neigh, nextReg, neigh->getArmies() - 1);
        }
    }
}

std::unordered_set<RegionPtr> GreedyRoundStrategy::getRegionsOnBorder()
{
    std::unordered_set<RegionPtr> regsOnBorder;

    for (auto &mine : m_world.getRegionsOwnedBy(Player::ME)) {
        auto isOnBorder = false;
        for (auto &neigh : mine->getNeighbors())
            if (neigh->getOwner() != Player::ME) {
                isOnBorder = true;
                break;
            }

        if (isOnBorder)
            regsOnBorder.emplace(mine);
    }

    return regsOnBorder;
}

} // namespace warlightAi
<|endoftext|>


<commit_before>#include "Symbol.h"
#include "Shadow.h"
#include "config.h"
#include "FileIO.h"

#include <ee/Sprite.h>

#include <sprite2/RenderParams.h>
#include <gum/FilepathHelper.h>

namespace eshadow
{

Symbol::Symbol()
{
	m_shadow = new Shadow(SOFT_SHADOW_RADIUS);
}

Symbol::~Symbol()
{
	if (m_shadow) {
		m_shadow->RemoveReference();
	}
}

void Symbol::Draw(const s2::RenderParams& params, const s2::Sprite* spr) const
{
	s2::RenderParams p = params;
	if (spr) {
		p.mt = spr->GetLocalMat() * params.mt;
		p.color = spr->GetColor() * params.color;
	}
	if (m_shadow) {
		m_shadow->Draw(p.mt, p.color.mul.a);
	}
}

sm::rect Symbol::GetBounding(const s2::Sprite* spr) const
{
	if (m_shadow) {
		return m_shadow->GetRegion();
	} else {
		return sm::rect(sm::vec2(0, 0), 200, 200);
	}
}

bool Symbol::LoadResources()
{
	if (!gum::FilepathHelper::Exists(m_filepath)) {
		return false;
	}
	FileIO::LoadFromFile(m_filepath.c_str(), this);
	return true;
}

}<commit_msg>[FIXED] compile<commit_after>#include "Symbol.h"
#include "Shadow.h"
#include "config.h"
#include "FileIO.h"

#include <ee/Sprite.h>

#include <sprite2/RenderParams.h>
#include <gum/FilepathHelper.h>

namespace eshadow
{

Symbol::Symbol()
{
	m_shadow = new Shadow(SOFT_SHADOW_RADIUS);
}

Symbol::~Symbol()
{
	if (m_shadow) {
		m_shadow->RemoveReference();
	}
}

void Symbol::Draw(const s2::RenderParams& params, const s2::Sprite* spr) const
{
	s2::RenderParams p = params;
	if (spr) {
		p.mt = spr->GetLocalMat() * params.mt;
		p.color = spr->GetColor() * params.color;
	}
	if (m_shadow) {
		m_shadow->Draw(p.mt, p.color.GetMul().a);
	}
}

sm::rect Symbol::GetBounding(const s2::Sprite* spr) const
{
	if (m_shadow) {
		return m_shadow->GetRegion();
	} else {
		return sm::rect(sm::vec2(0, 0), 200, 200);
	}
}

bool Symbol::LoadResources()
{
	if (!gum::FilepathHelper::Exists(m_filepath)) {
		return false;
	}
	FileIO::LoadFromFile(m_filepath.c_str(), this);
	return true;
}

}<|endoftext|>


<commit_before>#include "cppbencode.h"
#include <sstream>
#include <stdio.h>

void verify(const ben::Value &value, const char *s)
{
	std::ostringstream ss;
	value.write(ss);

	assert(ss.str() == s);

	std::istringstream parser(ss.str());
	ben::Value value2;
	value2.load_all(parser);

	assert(value == value2);
}

void verify_error(const char *s, const char *error)
{
	ben::Value val;
	std::istringstream ss(s);
	try {
		val.load(ss);
		/* should always raise an exception */
		assert(0);
	} catch (const ben::decode_error &e) {
		assert(e.what() == std::string(error));
	}
}

int main()
{
	verify(0, "i0e");
	verify(1234, "i1234e");
	verify(-1234, "i-1234e");
	verify(std::string("foobar"), "6:foobar");
	verify(std::string(""), "0:");
	verify(true, "b1");
	verify(false, "b0");

	std::vector<ben::Value> arr;
	arr.push_back(std::string("foo"));
	arr.push_back(1234);
	arr.push_back(true);
	verify(arr, "l3:fooi1234eb1e");

	ben::dict_map_t dict;
	dict.insert(std::make_pair("bar", arr));
	dict.insert(std::make_pair("foo", std::string("test")));
	verify(dict, "d3:barl3:fooi1234eb1e3:foo4:teste");

	verify_error("i1234", "Expected 'e'");
	verify_error("dx", "Expected a digit");
	verify_error("d-5", "Expected a digit");
	verify_error("d123", "Expected ':'");
	verify_error("i", "Unexpected end of input");
	verify_error("i 1e", "Expected a digit or '-'");
	verify_error("i1111111111111111111111e", "Invalid integer");
	verify_error("i- 1e", "Invalid integer");
	verify_error("i-0e", "Zero with a minus sign");
	verify_error("i05e", "Integer has leading zeroes");
	verify_error("06:foobar", "String length has leading zeroes");
	verify_error("123", "Expected ':'");
	verify_error("5:foo", "Unexpected end of input");
	verify_error("l", "Unexpected end of input");

	try {
		ben::Value val;
		std::istringstream ss("d3:bari123ee");
		val.load_all(ss);
		int i = val.get("foo").as_integer();
		(void) i;
	} catch (const ben::type_error &e) {
		assert(e.what() == std::string("Expected type integer, but got null"));
	}

	try {
		ben::Value val;
		std::istringstream ss("d3:bari123e3:foob1e");
		val.load_all(ss);
		int i = val.get("foo").as_integer();
		(void) i;
	} catch (const ben::type_error &e) {
		assert(e.what() == std::string("Expected type integer, but got boolean"));
	}

	printf("ok\n");
	return 0;
}
<commit_msg>test: Add comments [DOCUMENTATION]<commit_after>#include "cppbencode.h"
#include <sstream>
#include <stdio.h>

void verify(const ben::Value &value, const char *encoded)
{
	/* verify that the encoder produces the expected output */
	std::ostringstream ss;
	value.write(ss);
	assert(ss.str() == encoded);

	/* try to load the bencoded string, and check that it is equal */
	std::istringstream parser(ss.str());
	ben::Value value2;
	value2.load_all(parser);
	assert(value == value2);
}

void verify_error(const char *s, const char *error)
{
	ben::Value val;
	std::istringstream ss(s);
	try {
		val.load(ss);
		/* should always raise an exception */
		assert(0);
	} catch (const ben::decode_error &e) {
		assert(e.what() == std::string(error));
	}
}

int main()
{
	verify(0, "i0e");
	verify(1234, "i1234e");
	verify(-1234, "i-1234e");
	verify(std::string("foobar"), "6:foobar");
	verify(std::string(""), "0:");
	verify(true, "b1");
	verify(false, "b0");

	std::vector<ben::Value> arr;
	arr.push_back(std::string("foo"));
	arr.push_back(1234);
	arr.push_back(true);
	verify(arr, "l3:fooi1234eb1e");

	ben::dict_map_t dict;
	dict.insert(std::make_pair("bar", arr));
	dict.insert(std::make_pair("foo", std::string("test")));
	verify(dict, "d3:barl3:fooi1234eb1e3:foo4:teste");

	verify_error("i1234", "Expected 'e'");
	verify_error("dx", "Expected a digit");
	verify_error("d-5", "Expected a digit");
	verify_error("d123", "Expected ':'");
	verify_error("i", "Unexpected end of input");
	verify_error("i 1e", "Expected a digit or '-'");
	verify_error("i1111111111111111111111e", "Invalid integer");
	verify_error("i- 1e", "Invalid integer");
	verify_error("i-0e", "Zero with a minus sign");
	verify_error("i05e", "Integer has leading zeroes");
	verify_error("06:foobar", "String length has leading zeroes");
	verify_error("123", "Expected ':'");
	verify_error("5:foo", "Unexpected end of input");
	verify_error("l", "Unexpected end of input");

	try {
		ben::Value val;
		std::istringstream ss("d3:bari123ee");
		val.load_all(ss);
		int i = val.get("foo").as_integer();
		(void) i;
	} catch (const ben::type_error &e) {
		assert(e.what() == std::string("Expected type integer, but got null"));
	}

	try {
		ben::Value val;
		std::istringstream ss("d3:bari123e3:foob1e");
		val.load_all(ss);
		int i = val.get("foo").as_integer();
		(void) i;
	} catch (const ben::type_error &e) {
		assert(e.what() == std::string("Expected type integer, but got boolean"));
	}

	printf("ok\n");
	return 0;
}
<|endoftext|>


<commit_before>///
/// @file   help.cpp
/// @brief  help() and version() functions of the primesieve
///         console application.
///
/// Copyright (C) 2014 Kim Walisch, <kim.walisch@gmail.com>
///
/// This file is distributed under the BSD License. See the COPYING
/// file in the top level directory.
///

#include <primesieve.hpp>

#include <iostream>
#include <cstdlib>
#include <string>

using namespace std;

namespace {

const string helpMenu(
  "Usage: primesieve [START] STOP [OPTION]...\n"
  "Use the segmented sieve of Eratosthenes to generate the primes and\n"
  "prime k-tuplets in the interval [START, STOP] < 2^64\n"
  "\n"
  "Options:\n"
  "  -c<N+>, --count=<N+>     Count primes and prime k-tuplets, 1 <= N <= 7\n"
  "                           <N> 1=primes, 2=twins, 3=triplets, ...\n"
  "  -h,     --help           Print this help menu\n"
  "  -n,     --nthprime       Calculate the nth prime\n"
  "                           e.g. 1 100 -n finds the first prime >= 100\n"
  "  -o<N>,  --offset=<N>     Sieve the interval [START, START + N]\n"
  "  -p<N>,  --print=<N>      Print primes or prime k-tuplets, 1 <= N <= 7\n"
  "                           <N> 1=primes, 2=twins, 3=triplets, ...\n"
  "  -q,     --quiet          Quiet mode, prints less output\n"
  "  -s<N>,  --size=<N>       Set the sieve size in kilobytes, 1 <= N <= 2048\n"
  "          --test           Run various sieving tests and exit\n"
  "  -t<N>,  --threads=<N>    Set the number of threads, 1 <= N <= CPU cores\n"
  "  -v,     --version        Print version and license information\n"
  "\n"
  "Example:\n"
  "  Count the primes and print the twin primes below 1000\n"
  "  > primesieve 1000 --count=1 -p2"
);

const string versionInfo(
  "primesieve " PRIMESIEVE_VERSION ", <http://primesieve.org>\n"
  "Copyright (C) 2014 Kim Walisch\n"
  "BSD 2-Clause License <http://opensource.org/licenses/BSD-2-Clause>"
);

} // end namespace

void help()
{
  cout << helpMenu << endl;
  exit(1);
}

void version()
{
  cout << versionInfo << endl;
  exit(1);
}
<commit_msg>Update help.cpp<commit_after>///
/// @file   help.cpp
/// @brief  help() and version() functions of the primesieve
///         console application.
///
/// Copyright (C) 2014 Kim Walisch, <kim.walisch@gmail.com>
///
/// This file is distributed under the BSD License. See the COPYING
/// file in the top level directory.
///

#include <primesieve.hpp>

#include <iostream>
#include <cstdlib>
#include <string>

using namespace std;

namespace {

const string helpMenu(
  "Usage: primesieve [START] STOP [OPTION]...\n"
  "Use the segmented sieve of Eratosthenes to generate the primes and\n"
  "prime k-tuplets in the interval [START, STOP] < 2^64\n"
  "\n"
  "Options:\n"
  "  -c<N*>, --count=<N*>     Count primes and prime k-tuplets, 1 <= N <= 7\n"
  "                           <N> 1=primes, 2=twins, 3=triplets, ...\n"
  "  -h,     --help           Print this help menu\n"
  "  -n,     --nthprime       Calculate the nth prime\n"
  "                           e.g. 1 100 -n finds the first prime > 100\n"
  "  -o<N>,  --offset=<N>     Sieve the interval [START, START + N]\n"
  "  -p<N?>, --print=<N?>     Print primes or prime k-tuplets, 1 <= N <= 7\n"
  "                           <N> 1=primes, 2=twins, 3=triplets, ...\n"
  "  -q,     --quiet          Quiet mode, prints less output\n"
  "  -s<N>,  --size=<N>       Set the sieve size in kilobytes, 1 <= N <= 2048\n"
  "          --test           Run various sieving tests and exit\n"
  "  -t<N>,  --threads=<N>    Set the number of threads, 1 <= N <= CPU cores\n"
  "  -v,     --version        Print version and license information\n"
  "\n"
  "Example:\n"
  "  Print and count the twin primes below 1000000\n"
  "  > primesieve 10**6 --print=2 -c2"
);

const string versionInfo(
  "primesieve " PRIMESIEVE_VERSION ", <http://primesieve.org>\n"
  "Copyright (C) 2014 Kim Walisch\n"
  "BSD 2-Clause License <http://opensource.org/licenses/BSD-2-Clause>"
);

} // end namespace

void help()
{
  cout << helpMenu << endl;
  exit(1);
}

void version()
{
  cout << versionInfo << endl;
  exit(1);
}
<|endoftext|>


<commit_before>/*
 * This class implmentats callbacks function that are called by ATS
 * AUTHORS:
 *   Vmon: May 2013, moving Bill's code to C++
 */

#include <stdio.h>
#include <ts/ts.h>
#include <regex.h>
#include <string.h>

#include <string>
#include <vector>
#include <list>

#include <zmq.hpp>
using namespace std;

#include <re2/re2.h>
//to retrieve the client ip
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//to run fail2ban-client
#include <stdlib.h>

#include "banjax.h"
#include "banjax_continuation.h"
#include "transaction_muncher.h"
#include "regex_manager.h"
#include "challenge_manager.h"
#include "swabber_interface.h"
#include "ats_event_handler.h"

/*the glabal_cont the global continuation that is generated by
  by the banjax object.*/
//TSCont Banjax::global_contp;
//extern TSMutex Banjax::regex_mutex;
bool ATSEventHandler::banjax_active_queues[BanjaxFilter::TOTAL_NO_OF_QUEUES];

//Banjax* ATSEventHandler::banjax = NULL;

int
ATSEventHandler::banjax_global_eventhandler(TSCont contp, TSEvent event, void *edata)
{
  TSHttpTxn txnp = (TSHttpTxn) edata;
  BanjaxContinuation *cd;

  switch (event) {
  case TS_EVENT_HTTP_TXN_START:
    //If we are here it means this is the global continuation
    //we never subscribe subsequent continuations to this event
    handle_txn_start((TSHttpTxn) edata);
    /*if (banjax_active_queues[HTTP_START])
      handle_task_queue(HTTP_START, (BanjaxContinuation *) TSContDataGet(contp));*/

    return 0;

  case TS_EVENT_HTTP_READ_REQUEST_HDR:
    if(contp != Banjax::global_contp)
      handle_request((BanjaxContinuation *) TSContDataGet(contp));
      return 0;

  case TS_EVENT_HTTP_READ_CACHE_HDR:
    /* on hit we don't do anything for now
       lack of miss means hit to me
       if (contp != Banjax::global_contp) {
       cd = (BanjaxContinuation *) TSContDataGet(contp);
       cd->hit = 1;
       }*/
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    return 0;

  case TS_EVENT_HTTP_SEND_REQUEST_HDR:
    TSDebug(BANJAX_PLUGIN_NAME, "miss");
    if (contp != Banjax::global_contp) {
	    cd = (BanjaxContinuation *) TSContDataGet(contp);
	    cd->transaction_muncher.miss();
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    return 0;

  case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
    //TSDebug(BANJAX_PLUGIN_NAME, "response" );
    if (contp != Banjax::global_contp) {
      cd = (BanjaxContinuation*) TSContDataGet(contp);
      handle_response(cd);
    }
    return 0;

  case TS_EVENT_HTTP_TXN_CLOSE:
    TSDebug(BANJAX_PLUGIN_NAME, "txn close" );
    if (contp != Banjax::global_contp) {
      cd = (BanjaxContinuation *) TSContDataGet(contp);
      if (banjax_active_queues[BanjaxFilter::HTTP_CLOSE])
        handle_task_queue(banjax->task_queues[BanjaxFilter::HTTP_CLOSE], cd);

      //killing the continuation
      cd->~BanjaxContinuation(); //leave mem manage to ATS
      //TSfree(cd); I think TS is taking care of this
      destroy_continuation(contp);
    }
    break;

  case TS_EVENT_TIMEOUT:
    //TODO: This code does not make sense and needs to be revisited
    TSDebug("banjaxtimeout", "timeout" );
    /* when mutex lock is not acquired and continuation is rescheduled,
       the plugin is called back with TS_EVENT_TIMEOUT with a NULL
       edata. We need to decide, in which function did the MutexLock
       failed and call that function again */
    /*if (contp != Banjax::global_contp) {
      cd = (BanjaxContinuation *) TSContDataGet(contp);
      switch (cd->cf) {
        case BanjaxContinuation::HANDLE_REQUEST:
          handle_request(cd);
          return 0;
        default:
          TSDebug(BANJAX_PLUGIN_NAME, "This event was unexpected: %d\n", event);
          break;
	  }
    } else {
      //regardless, it even doesn't make sense to read the list here
      //read_regex_list(contp);
      return 0;
      }*/

    default:
      TSDebug(BANJAX_PLUGIN_NAME, "Unsolicitated event call?" );
      break;
  }

  return 0;

}

void
ATSEventHandler::handle_request(BanjaxContinuation* cd)
{
  //retreiving part of header requested by the filters
  const TransactionParts& cur_trans_parts = cd->transaction_muncher.retrieve_parts(banjax->which_parts_are_requested());

  bool continue_filtering = true;
  for(Banjax::TaskQueue::iterator cur_task = banjax->task_queues[BanjaxFilter::HTTP_REQUEST].begin(); continue_filtering && cur_task != banjax->task_queues[BanjaxFilter::HTTP_REQUEST].end(); cur_task++) {
    FilterResponse cur_filter_result = ((*(cur_task->filter)).*(cur_task->task))(cur_trans_parts);
    switch (cur_filter_result.response_type)
      {
      case FilterResponse::GO_AHEAD_NO_COMMENT:
        continue;

      case FilterResponse::NO_WORRIES_SERVE_IMMIDIATELY:
        //This is when the requester is white listed
        continue_filtering = false;
        break;

      case FilterResponse::I_RESPOND:
        // from here on, cur_filter_result is owned by the continuation data.
        cd->response_info = cur_filter_result;
        cd->responding_filter = cur_task->filter;
        // TODO(oschaaf): commented this. @vmon: we already hook this globally,
        // is there a reason we need to hook it again here?
        //TSHttpTxnHookAdd(cd->txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cd->contp);
        TSHttpTxnReenable(cd->txnp, TS_EVENT_HTTP_ERROR);
        return;

      default:
        //Not implemeneted, hence ignoe
        break;

      }
  }

  //TODO: it is imaginable that a filter needs to be
  //called during response but does not need to influnence
  //the response, e.g. botbanger hence we need to get the
  //response hook while continuing with the flow
  //destroy_continuation(cd->txnp, cd->contp);
  TSHttpTxnReenable(cd->txnp, TS_EVENT_HTTP_CONTINUE);

}

void
ATSEventHandler::handle_response(BanjaxContinuation* cd)
{
  //we need to retrieve response parts for any filter who requested it.
  cd->transaction_muncher.retrieve_response_parts(banjax->which_response_parts_are_requested());

  if (cd->response_info.response_type == FilterResponse::I_RESPOND) {
    cd->transaction_muncher.set_status(TS_HTTP_STATUS_FORBIDDEN);
    std::string buf = (((cd->responding_filter)->*(((FilterExtendedResponse*)cd->response_info.response_data)->response_generator)))(cd->transaction_muncher.retrieve_parts(banjax->all_filters_requested_part), cd->response_info);

    cd->transaction_muncher.set_status(
        (TSHttpStatus)(((FilterExtendedResponse*)cd->response_info.response_data))->response_code);

    if ((((FilterExtendedResponse*)cd->response_info.response_data))->set_cookie_header.size()) {
      cd->transaction_muncher.append_header(
          "Set-Cookie", (((FilterExtendedResponse*)cd->response_info.response_data))->set_cookie_header.c_str());
    }
    if (buf.size() == 0) {
      // When we get here, no valid response body was generated somehow.
      // Insert one, to prevent triggering an assert in TSHttpTxnErrorBodySet
      buf.append("Not authorized");
    }
    char* b = (char*) TSmalloc(buf.size());
    memcpy(b, buf.data(), buf.size());
    TSHttpTxnErrorBodySet(cd->txnp, b, buf.size(),
                          (((FilterExtendedResponse*)cd->response_info.response_data))->get_and_release_content_type());
  }
  //Now we should take care of registerd filters in the queue these are not
  //going to generate the response at least that is the plan
  if (banjax_active_queues[BanjaxFilter::HTTP_START])
    handle_task_queue(banjax->task_queues[BanjaxFilter::HTTP_RESPONSE], cd);

  TSHttpTxnReenable(cd->txnp, TS_EVENT_HTTP_CONTINUE);

}

/**
   @param global_contp contains the global continuation and is sent here
   , so the new continuation gets the main banjax object
 */
void
ATSEventHandler::handle_txn_start(TSHttpTxn txnp)
{
  TSDebug(BANJAX_PLUGIN_NAME, "txn start" );

  TSCont txn_contp;
  BanjaxContinuation *cd;

  //retreive the banjax obej
  txn_contp = TSContCreate((TSEventFunc) banjax_global_eventhandler, TSMutexCreate());
  /* create the data that'll be associated with the continuation */
  cd = (BanjaxContinuation *) TSmalloc(sizeof(BanjaxContinuation));
  cd = new(cd) BanjaxContinuation(txnp);
  //TSDebug(BANJAX_PLUGIN_NAME, "New continuation data at %lu", (unsigned long)cd);
  TSContDataSet(txn_contp, cd);

  cd->contp = txn_contp;

  TSHttpTxnHookAdd(txnp, TS_HTTP_READ_REQUEST_HDR_HOOK, txn_contp);
  TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, txn_contp);
  TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, txn_contp);
  TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp);

  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);

}

/**
   runs if a filter is registered to run a function during an event
*/
void
ATSEventHandler::handle_task_queue(Banjax::TaskQueue& current_queue, BanjaxContinuation* cd)
{
  const TransactionParts& cur_trans_parts = cd->transaction_muncher.retrieve_parts(banjax->which_parts_are_requested());

  for(Banjax::TaskQueue::iterator cur_task = current_queue.begin(); cur_task != current_queue.end(); cur_task++)
    /*For now we have no plan on doing anything with the result
      in future we need to receive the filter instruction for
      the rest of transation but for the moment only it matters
      at the begining of the transaction to interfere with the
      transaction.

      FilterResponse cur_filter_result =
     */
    ((*(cur_task->filter)).*(cur_task->task))(cur_trans_parts);

}

void
ATSEventHandler::destroy_continuation(TSCont contp)
{
  BanjaxContinuation *cd = NULL;

  cd = (BanjaxContinuation *) TSContDataGet(contp);

  //save the txn before destroying the continuation so we can continue
  TSHttpTxn txn_keeper = cd->txnp;
  TSContDestroy(contp);
  TSHttpTxnReenable(txn_keeper, TS_EVENT_HTTP_CONTINUE);

  if (cd != NULL) {
    TSfree(cd);
    // delete cd;
  }
}
<commit_msg>free the cont data before destroying the cont<commit_after>/*
 * This class implmentats callbacks function that are called by ATS
 * AUTHORS:
 *   Vmon: May 2013, moving Bill's code to C++
 */

#include <stdio.h>
#include <ts/ts.h>
#include <regex.h>
#include <string.h>

#include <string>
#include <vector>
#include <list>

#include <zmq.hpp>
using namespace std;

#include <re2/re2.h>
//to retrieve the client ip
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//to run fail2ban-client
#include <stdlib.h>

#include "banjax.h"
#include "banjax_continuation.h"
#include "transaction_muncher.h"
#include "regex_manager.h"
#include "challenge_manager.h"
#include "swabber_interface.h"
#include "ats_event_handler.h"

/*the glabal_cont the global continuation that is generated by
  by the banjax object.*/
//TSCont Banjax::global_contp;
//extern TSMutex Banjax::regex_mutex;
bool ATSEventHandler::banjax_active_queues[BanjaxFilter::TOTAL_NO_OF_QUEUES];

//Banjax* ATSEventHandler::banjax = NULL;

int
ATSEventHandler::banjax_global_eventhandler(TSCont contp, TSEvent event, void *edata)
{
  TSHttpTxn txnp = (TSHttpTxn) edata;
  BanjaxContinuation *cd;

  switch (event) {
  case TS_EVENT_HTTP_TXN_START:
    //If we are here it means this is the global continuation
    //we never subscribe subsequent continuations to this event
    handle_txn_start((TSHttpTxn) edata);
    /*if (banjax_active_queues[HTTP_START])
      handle_task_queue(HTTP_START, (BanjaxContinuation *) TSContDataGet(contp));*/

    return 0;

  case TS_EVENT_HTTP_READ_REQUEST_HDR:
    if(contp != Banjax::global_contp)
      handle_request((BanjaxContinuation *) TSContDataGet(contp));
      return 0;

  case TS_EVENT_HTTP_READ_CACHE_HDR:
    /* on hit we don't do anything for now
       lack of miss means hit to me
       if (contp != Banjax::global_contp) {
       cd = (BanjaxContinuation *) TSContDataGet(contp);
       cd->hit = 1;
       }*/
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    return 0;

  case TS_EVENT_HTTP_SEND_REQUEST_HDR:
    TSDebug(BANJAX_PLUGIN_NAME, "miss");
    if (contp != Banjax::global_contp) {
	    cd = (BanjaxContinuation *) TSContDataGet(contp);
	    cd->transaction_muncher.miss();
    }
    TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
    return 0;

  case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
    //TSDebug(BANJAX_PLUGIN_NAME, "response" );
    if (contp != Banjax::global_contp) {
      cd = (BanjaxContinuation*) TSContDataGet(contp);
      handle_response(cd);
    }
    return 0;

  case TS_EVENT_HTTP_TXN_CLOSE:
    TSDebug(BANJAX_PLUGIN_NAME, "txn close" );
    if (contp != Banjax::global_contp) {
      cd = (BanjaxContinuation *) TSContDataGet(contp);
      if (banjax_active_queues[BanjaxFilter::HTTP_CLOSE])
        handle_task_queue(banjax->task_queues[BanjaxFilter::HTTP_CLOSE], cd);

      //killing the continuation
      cd->~BanjaxContinuation(); //leave mem manage to ATS
      //TSfree(cd); I think TS is taking care of this
      destroy_continuation(contp);
    }
    break;

  case TS_EVENT_TIMEOUT:
    //TODO: This code does not make sense and needs to be revisited
    TSDebug("banjaxtimeout", "timeout" );
    /* when mutex lock is not acquired and continuation is rescheduled,
       the plugin is called back with TS_EVENT_TIMEOUT with a NULL
       edata. We need to decide, in which function did the MutexLock
       failed and call that function again */
    /*if (contp != Banjax::global_contp) {
      cd = (BanjaxContinuation *) TSContDataGet(contp);
      switch (cd->cf) {
        case BanjaxContinuation::HANDLE_REQUEST:
          handle_request(cd);
          return 0;
        default:
          TSDebug(BANJAX_PLUGIN_NAME, "This event was unexpected: %d\n", event);
          break;
	  }
    } else {
      //regardless, it even doesn't make sense to read the list here
      //read_regex_list(contp);
      return 0;
      }*/

    default:
      TSDebug(BANJAX_PLUGIN_NAME, "Unsolicitated event call?" );
      break;
  }

  return 0;

}

void
ATSEventHandler::handle_request(BanjaxContinuation* cd)
{
  //retreiving part of header requested by the filters
  const TransactionParts& cur_trans_parts = cd->transaction_muncher.retrieve_parts(banjax->which_parts_are_requested());

  bool continue_filtering = true;
  for(Banjax::TaskQueue::iterator cur_task = banjax->task_queues[BanjaxFilter::HTTP_REQUEST].begin(); continue_filtering && cur_task != banjax->task_queues[BanjaxFilter::HTTP_REQUEST].end(); cur_task++) {
    FilterResponse cur_filter_result = ((*(cur_task->filter)).*(cur_task->task))(cur_trans_parts);
    switch (cur_filter_result.response_type)
      {
      case FilterResponse::GO_AHEAD_NO_COMMENT:
        continue;

      case FilterResponse::NO_WORRIES_SERVE_IMMIDIATELY:
        //This is when the requester is white listed
        continue_filtering = false;
        break;

      case FilterResponse::I_RESPOND:
        // from here on, cur_filter_result is owned by the continuation data.
        cd->response_info = cur_filter_result;
        cd->responding_filter = cur_task->filter;
        // TODO(oschaaf): commented this. @vmon: we already hook this globally,
        // is there a reason we need to hook it again here?
        //TSHttpTxnHookAdd(cd->txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, cd->contp);
        TSHttpTxnReenable(cd->txnp, TS_EVENT_HTTP_ERROR);
        return;

      default:
        //Not implemeneted, hence ignoe
        break;

      }
  }

  //TODO: it is imaginable that a filter needs to be
  //called during response but does not need to influnence
  //the response, e.g. botbanger hence we need to get the
  //response hook while continuing with the flow
  //destroy_continuation(cd->txnp, cd->contp);
  TSHttpTxnReenable(cd->txnp, TS_EVENT_HTTP_CONTINUE);

}

void
ATSEventHandler::handle_response(BanjaxContinuation* cd)
{
  //we need to retrieve response parts for any filter who requested it.
  cd->transaction_muncher.retrieve_response_parts(banjax->which_response_parts_are_requested());

  if (cd->response_info.response_type == FilterResponse::I_RESPOND) {
    cd->transaction_muncher.set_status(TS_HTTP_STATUS_FORBIDDEN);
    std::string buf = (((cd->responding_filter)->*(((FilterExtendedResponse*)cd->response_info.response_data)->response_generator)))(cd->transaction_muncher.retrieve_parts(banjax->all_filters_requested_part), cd->response_info);

    cd->transaction_muncher.set_status(
        (TSHttpStatus)(((FilterExtendedResponse*)cd->response_info.response_data))->response_code);

    if ((((FilterExtendedResponse*)cd->response_info.response_data))->set_cookie_header.size()) {
      cd->transaction_muncher.append_header(
          "Set-Cookie", (((FilterExtendedResponse*)cd->response_info.response_data))->set_cookie_header.c_str());
    }
    if (buf.size() == 0) {
      // When we get here, no valid response body was generated somehow.
      // Insert one, to prevent triggering an assert in TSHttpTxnErrorBodySet
      buf.append("Not authorized");
    }
    char* b = (char*) TSmalloc(buf.size());
    memcpy(b, buf.data(), buf.size());
    TSHttpTxnErrorBodySet(cd->txnp, b, buf.size(),
                          (((FilterExtendedResponse*)cd->response_info.response_data))->get_and_release_content_type());
  }
  //Now we should take care of registerd filters in the queue these are not
  //going to generate the response at least that is the plan
  if (banjax_active_queues[BanjaxFilter::HTTP_START])
    handle_task_queue(banjax->task_queues[BanjaxFilter::HTTP_RESPONSE], cd);

  TSHttpTxnReenable(cd->txnp, TS_EVENT_HTTP_CONTINUE);

}

/**
   @param global_contp contains the global continuation and is sent here
   , so the new continuation gets the main banjax object
 */
void
ATSEventHandler::handle_txn_start(TSHttpTxn txnp)
{
  TSDebug(BANJAX_PLUGIN_NAME, "txn start" );

  TSCont txn_contp;
  BanjaxContinuation *cd;

  //retreive the banjax obej
  txn_contp = TSContCreate((TSEventFunc) banjax_global_eventhandler, TSMutexCreate());
  /* create the data that'll be associated with the continuation */
  cd = (BanjaxContinuation *) TSmalloc(sizeof(BanjaxContinuation));
  cd = new(cd) BanjaxContinuation(txnp);
  //TSDebug(BANJAX_PLUGIN_NAME, "New continuation data at %lu", (unsigned long)cd);
  TSContDataSet(txn_contp, cd);

  cd->contp = txn_contp;

  TSHttpTxnHookAdd(txnp, TS_HTTP_READ_REQUEST_HDR_HOOK, txn_contp);
  TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, txn_contp);
  TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, txn_contp);
  TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp);

  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);

}

/**
   runs if a filter is registered to run a function during an event
*/
void
ATSEventHandler::handle_task_queue(Banjax::TaskQueue& current_queue, BanjaxContinuation* cd)
{
  const TransactionParts& cur_trans_parts = cd->transaction_muncher.retrieve_parts(banjax->which_parts_are_requested());

  for(Banjax::TaskQueue::iterator cur_task = current_queue.begin(); cur_task != current_queue.end(); cur_task++)
    /*For now we have no plan on doing anything with the result
      in future we need to receive the filter instruction for
      the rest of transation but for the moment only it matters
      at the begining of the transaction to interfere with the
      transaction.

      FilterResponse cur_filter_result =
     */
    ((*(cur_task->filter)).*(cur_task->task))(cur_trans_parts);

}

void
ATSEventHandler::destroy_continuation(TSCont contp)
{
  BanjaxContinuation *cd = NULL;

  cd = (BanjaxContinuation *) TSContDataGet(contp);

  //save the txn before destroying the continuation so we can continue
  TSHttpTxn txn_keeper = cd->txnp;
  TSfree(cd);

  TSContDestroy(contp);
  TSHttpTxnReenable(txn_keeper, TS_EVENT_HTTP_CONTINUE);

}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 "src/trace_processor/util/gzip_utils.h"

// For bazel build.
#include "perfetto/base/build_config.h"
#include "perfetto/base/compiler.h"

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>
#else
struct z_stream_s {};
#endif

namespace perfetto {
namespace trace_processor {
namespace util {

bool IsGzipSupported() {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  return true;
#else
  return false;
#endif
}

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
GzipDecompressor::GzipDecompressor() : z_stream_(new z_stream()) {
  z_stream_->zalloc = nullptr;
  z_stream_->zfree = nullptr;
  z_stream_->opaque = nullptr;
  inflateInit2(z_stream_.get(), 32 + 15);
}
#else
GzipDecompressor::GzipDecompressor() = default;
#endif

GzipDecompressor::~GzipDecompressor() {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  // Ensure the call to inflateEnd to prevent leaks of internal state.
  inflateEnd(z_stream_.get());
#endif
}

void GzipDecompressor::Reset() {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  inflateReset(z_stream_.get());
#endif
}

void GzipDecompressor::SetInput(const uint8_t* data, size_t size) {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  // This const_cast is not harmfull as zlib will not modify the data in this
  // pointer. This is only necessary because of the build flags we use to be
  // compatible with other embedders.
  z_stream_->next_in = const_cast<uint8_t*>(data);
  z_stream_->avail_in = static_cast<uInt>(size);
#else
  base::ignore_result(data);
  base::ignore_result(size);
#endif
}

GzipDecompressor::Result GzipDecompressor::Decompress(uint8_t* out,
                                                      size_t out_size) {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  if (z_stream_->avail_in == 0)
    return Result{ResultCode::kNeedsMoreInput, 0};

  z_stream_->next_out = out;
  z_stream_->avail_out = static_cast<uInt>(out_size);

  int ret = inflate(z_stream_.get(), Z_NO_FLUSH);
  switch (ret) {
    case Z_NEED_DICT:
    case Z_DATA_ERROR:
    case Z_MEM_ERROR:
      // Ignore inflateEnd error as we will error out anyway.
      inflateEnd(z_stream_.get());
      return Result{ResultCode::kError, 0};
    case Z_STREAM_END:
      return Result{ResultCode::kEof, out_size - z_stream_->avail_out};
    case Z_BUF_ERROR:
      return Result{ResultCode::kNoProgress, 0};
    default:
      return Result{ResultCode::kOk, out_size - z_stream_->avail_out};
  }
#else
  base::ignore_result(out);
  base::ignore_result(out_size);
  return Result{ResultCode::kError, 0};
#endif
}

}  // namespace util
}  // namespace trace_processor
}  // namespace perfetto
<commit_msg>Improve Readability of gzip_utils.cc<commit_after>/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 "src/trace_processor/util/gzip_utils.h"

// For bazel build.
#include "perfetto/base/build_config.h"
#include "perfetto/base/compiler.h"

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>
#else
struct z_stream_s {};
#endif

namespace perfetto {
namespace trace_processor {
namespace util {

bool IsGzipSupported() {
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  return true;
#else
  return false;
#endif
}

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)  // Real Implementation

GzipDecompressor::GzipDecompressor() : z_stream_(new z_stream()) {
  z_stream_->zalloc = nullptr;
  z_stream_->zfree = nullptr;
  z_stream_->opaque = nullptr;
  // TODO(mohitms): Should we instead use `32 + MAX_WBITS` here to make
  // it more readable ?
  inflateInit2(z_stream_.get(), 32 + 15);
}

GzipDecompressor::~GzipDecompressor() {
  inflateEnd(z_stream_.get());
}

void GzipDecompressor::Reset() {
  inflateReset(z_stream_.get());
}

void GzipDecompressor::SetInput(const uint8_t* data, size_t size) {
  // This const_cast is not harmfull as zlib will not modify the data in this
  // pointer. This is only necessary because of the build flags we use to be
  // compatible with other embedders.
  z_stream_->next_in = const_cast<uint8_t*>(data);
  z_stream_->avail_in = static_cast<uInt>(size);
}

GzipDecompressor::Result GzipDecompressor::Decompress(uint8_t* out,
                                                      size_t out_size) {
  if (z_stream_->avail_in == 0)
    return Result{ResultCode::kNeedsMoreInput, 0};

  z_stream_->next_out = out;
  z_stream_->avail_out = static_cast<uInt>(out_size);

  int ret = inflate(z_stream_.get(), Z_NO_FLUSH);
  switch (ret) {
    case Z_NEED_DICT:
    case Z_DATA_ERROR:
    case Z_MEM_ERROR:
      // Ignore inflateEnd error as we will error out anyway.
      inflateEnd(z_stream_.get());
      return Result{ResultCode::kError, 0};
    case Z_STREAM_END:
      return Result{ResultCode::kEof, out_size - z_stream_->avail_out};
    case Z_BUF_ERROR:
      return Result{ResultCode::kNoProgress, 0};
    default:
      return Result{ResultCode::kOk, out_size - z_stream_->avail_out};
  }
}

#else  // Dummy Implementation

GzipDecompressor::GzipDecompressor() = default;
GzipDecompressor::~GzipDecompressor() = default;
void GzipDecompressor::Reset() {}
void GzipDecompressor::SetInput(const uint8_t*, size_t) {}
GzipDecompressor::Result GzipDecompressor::Decompress(uint8_t*, size_t) {
  Result{ResultCode::kError, 0};
}

#endif  // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)

}  // namespace util
}  // namespace trace_processor
}  // namespace perfetto
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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 "src/trace_processor/util/zip_reader.h"

#include <time.h>

#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/utils.h"
#include "src/trace_processor/util/gzip_utils.h"
#include "src/trace_processor/util/streaming_line_reader.h"

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>  // For crc32().
#endif

namespace perfetto {
namespace trace_processor {
namespace util {

namespace {

// Entry signatures.
const uint32_t kFileHeaderSig = 0x04034b50;
const uint32_t kCentralDirectorySig = 0x02014b50;

// Compression flags.
const uint16_t kNoCompression = 0;
const uint16_t kDeflate = 8;

template <typename T>
T ReadAndAdvance(const uint8_t** ptr) {
  T res{};
  memcpy(base::AssumeLittleEndian(&res), *ptr, sizeof(T));
  *ptr += sizeof(T);
  return res;
}

}  // namespace

ZipReader::ZipReader() = default;
ZipReader::~ZipReader() = default;

base::Status ZipReader::Parse(const void* data, size_t len) {
  const uint8_t* input = static_cast<const uint8_t*>(data);
  const uint8_t* const input_begin = input;
  const uint8_t* const input_end = input + len;
  auto input_avail = [&] { return static_cast<size_t>(input_end - input); };

  // .zip file sequence:
  // [ File 1 header (30 bytes) ]
  // [ File 1 name ]
  // [ File 1 extra fields (optional) ]
  // [ File 1 compressed payload ]
  //
  // [ File 2 header (30 bytes) ]
  // [ File 2 name ]
  // [ File 2 extra fields (optional) ]
  // [ File 2 compressed payload ]
  //
  // [ Central directory (ignored) ]
  while (input < input_end) {
    // Initial state, we are building up the file header.
    if (cur_.raw_hdr_size < kZipFileHdrSize) {
      size_t copy_size =
          std::min(input_avail(), kZipFileHdrSize - cur_.raw_hdr_size);
      memcpy(&cur_.raw_hdr[cur_.raw_hdr_size], input, copy_size);
      cur_.raw_hdr_size += copy_size;
      input += copy_size;

      // If we got all the kZipFileHdrSize bytes, parse the zip file header now.
      if (cur_.raw_hdr_size == kZipFileHdrSize) {
        const uint8_t* hdr_it = &cur_.raw_hdr[0];
        cur_.hdr.signature = ReadAndAdvance<uint32_t>(&hdr_it);
        if (cur_.hdr.signature == kCentralDirectorySig) {
          // We reached the central directory at the end of file.
          // We don't make any use here of the central directory, so we just
          // ignore everything else after this point.
          // Here we abuse the ZipFile class a bit. The Central Directory header
          // has a different layout. The first 4 bytes (signature) match, the
          // rest don't but the sizeof(central dir) is >> sizeof(file header) so
          // we are fine.
          // We do this rather than retuning because we could have further
          // Parse() calls (imagine parsing bytes one by one), and we need a way
          // to keep track of the "keep eating input without doing anything".
          cur_.ignore_bytes_after_fname = std::numeric_limits<size_t>::max();
          input = input_end;
          break;
        }
        if (cur_.hdr.signature != kFileHeaderSig) {
          return base::ErrStatus(
              "Invalid signature found at offset 0x%zx. Actual=%x, expected=%x",
              static_cast<size_t>(input - input_begin) - kZipFileHdrSize,
              cur_.hdr.signature, kFileHeaderSig);
        }

        cur_.hdr.version = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.flags = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.compression = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.mtime = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.mdate = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.crc32 = ReadAndAdvance<uint32_t>(&hdr_it);
        cur_.hdr.compressed_size = ReadAndAdvance<uint32_t>(&hdr_it);
        cur_.hdr.uncompressed_size = ReadAndAdvance<uint32_t>(&hdr_it);
        cur_.hdr.fname_len = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.extra_field_len = ReadAndAdvance<uint16_t>(&hdr_it);
        PERFETTO_DCHECK(static_cast<size_t>(hdr_it - cur_.raw_hdr) ==
                        kZipFileHdrSize);

        // We support only up to version 2.0 (20). Higher versions define
        // more advanced features that we don't support (zip64 extensions,
        // encryption).
        // Flag bits 1,2 define the compression strength for deflating (which
        // zlib supports transparently). Other bits define other compression
        // methods that we don't support.
        if ((cur_.hdr.version > 20) || (cur_.hdr.flags & ~3) != 0) {
          return base::ErrStatus(
              "Unsupported zip features at offset 0x%zx. version=%x, flags=%x",
              static_cast<size_t>(input - input_begin) - kZipFileHdrSize,
              cur_.hdr.version, cur_.hdr.flags);
        }
        cur_.compressed_data.reset(new uint8_t[cur_.hdr.compressed_size]);
        cur_.ignore_bytes_after_fname = cur_.hdr.extra_field_len;
      }
      continue;
    }

    // Build up the file name.
    if (cur_.hdr.fname.size() < cur_.hdr.fname_len) {
      size_t name_left = cur_.hdr.fname_len - cur_.hdr.fname.size();
      size_t copy_size = std::min(name_left, input_avail());
      cur_.hdr.fname.append(reinterpret_cast<const char*>(input), copy_size);
      input += copy_size;
      continue;
    }

    // Skip any bytes if extra fields were present.
    if (cur_.ignore_bytes_after_fname > 0) {
      size_t skip_size = std::min(input_avail(), cur_.ignore_bytes_after_fname);
      cur_.ignore_bytes_after_fname -= skip_size;
      input += skip_size;
      continue;
    }

    // Build up the compressed payload
    if (cur_.compressed_data_written < cur_.hdr.compressed_size) {
      size_t needed = cur_.hdr.compressed_size - cur_.compressed_data_written;
      size_t copy_size = std::min(needed, input_avail());
      memcpy(&cur_.compressed_data[cur_.compressed_data_written], input,
             copy_size);
      cur_.compressed_data_written += copy_size;
      input += copy_size;
      continue;
    }

    // We have accumulated the whole header, file name and compressed payload.
    PERFETTO_DCHECK(cur_.raw_hdr_size == kZipFileHdrSize);
    PERFETTO_DCHECK(cur_.hdr.fname.size() == cur_.hdr.fname_len);
    PERFETTO_DCHECK(cur_.compressed_data_written == cur_.hdr.compressed_size);
    PERFETTO_DCHECK(cur_.ignore_bytes_after_fname == 0);

    files_.emplace_back();
    files_.back().hdr_ = std::move(cur_.hdr);
    files_.back().compressed_data_ = std::move(cur_.compressed_data);
    cur_ = FileParseState();  // Reset the parsing state for the next file.

  }  // while (input < input_end)

  // At this point we must have consumed all input.
  PERFETTO_DCHECK(input_avail() == 0);
  return base::OkStatus();
}

ZipFile::ZipFile() = default;
ZipFile::~ZipFile() = default;
ZipFile::ZipFile(ZipFile&& other) noexcept = default;
ZipFile& ZipFile::operator=(ZipFile&& other) noexcept = default;

base::Status ZipFile::Decompress(std::vector<uint8_t>* out_data) const {
  out_data->clear();

  auto res = DoDecompressionChecks();
  if (!res.ok())
    return res;

  if (hdr_.compression == kNoCompression) {
    const uint8_t* data = compressed_data_.get();
    out_data->insert(out_data->end(), data, data + hdr_.compressed_size);
    return base::OkStatus();
  }

  if (hdr_.uncompressed_size == 0)
    return base::OkStatus();

  PERFETTO_DCHECK(hdr_.compression == kDeflate);
  GzipDecompressor dec(GzipDecompressor::InputMode::kRawDeflate);
  dec.Feed(compressed_data_.get(), hdr_.compressed_size);

  out_data->resize(hdr_.uncompressed_size);
  auto dec_res = dec.ExtractOutput(out_data->data(), out_data->size());
  if (dec_res.ret != GzipDecompressor::ResultCode::kEof) {
    return base::ErrStatus("Zip decompression error (%d) on %s (c=%u, u=%u)",
                           static_cast<int>(dec_res.ret), hdr_.fname.c_str(),
                           hdr_.compressed_size, hdr_.uncompressed_size);
  }
  out_data->resize(dec_res.bytes_written);

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  const auto* crc_data = reinterpret_cast<const ::Bytef*>(out_data->data());
  auto crc_len = static_cast<::uInt>(out_data->size());
  auto actual_crc32 = static_cast<uint32_t>(::crc32(0u, crc_data, crc_len));
  if (actual_crc32 != hdr_.crc32) {
    return base::ErrStatus("Zip CRC32 failure on %s (actual: %x, expected: %x)",
                           hdr_.fname.c_str(), actual_crc32, hdr_.crc32);
  }
#endif

  return base::OkStatus();
}

base::Status ZipFile::DecompressLines(LinesCallback callback) const {
  using ResultCode = GzipDecompressor::ResultCode;

  auto res = DoDecompressionChecks();
  if (!res.ok())
    return res;

  StreamingLineReader line_reader(callback);

  if (hdr_.compression == kNoCompression) {
    line_reader.Tokenize(
        base::StringView(reinterpret_cast<const char*>(compressed_data_.get()),
                         hdr_.compressed_size));
    return base::OkStatus();
  }

  PERFETTO_DCHECK(hdr_.compression == kDeflate);
  GzipDecompressor dec(GzipDecompressor::InputMode::kRawDeflate);
  dec.Feed(compressed_data_.get(), hdr_.compressed_size);

  static constexpr size_t kChunkSize = 32768;
  GzipDecompressor::Result dec_res;
  do {
    auto* wptr = reinterpret_cast<uint8_t*>(line_reader.BeginWrite(kChunkSize));
    dec_res = dec.ExtractOutput(wptr, kChunkSize);
    if (dec_res.ret == ResultCode::kError ||
        dec_res.ret == ResultCode::kNeedsMoreInput)
      return base::ErrStatus("zlib decompression error on %s (%d)",
                             name().c_str(), static_cast<int>(dec_res.ret));
    PERFETTO_DCHECK(dec_res.bytes_written <= kChunkSize);
    line_reader.EndWrite(dec_res.bytes_written);
  } while (dec_res.ret == ResultCode::kOk);
  return base::OkStatus();
}

// Common logic for both Decompress() and DecompressLines().
base::Status ZipFile::DoDecompressionChecks() const {
  PERFETTO_DCHECK(compressed_data_);

  if (hdr_.compression == kNoCompression) {
    PERFETTO_CHECK(hdr_.compressed_size == hdr_.uncompressed_size);
    return base::OkStatus();
  }

  if (hdr_.compression != kDeflate) {
    return base::ErrStatus("Zip compression mode not supported (%u)",
                           hdr_.compression);
  }

  if (!IsGzipSupported()) {
    return base::ErrStatus(
        "Cannot open zip file. Gzip is not enabled in the current build. "
        "Rebuild with enable_perfetto_zlib=true");
  }

  return base::OkStatus();
}

// Returns a 64-bit version of time_t, that is, the num seconds since the Epoch.
uint64_t ZipFile::GetDatetime() const {
  // Date: 7 bits year, 4 bits month, 5 bits day.
  // Time: 5 bits hour, 6 bits minute, 5 bits second.
  struct tm mdt {};
  // As per man 3 mktime, `tm_year` is relative to 1900 not Epoch. Go figure.
  mdt.tm_year = 1980 + (hdr_.mdate >> (16 - 7)) - 1900;

  // As per the man page, the month ranges 0 to 11 (Jan = 0).
  mdt.tm_mon = ((hdr_.mdate >> (16 - 7 - 4)) & 0x0f) - 1;

  // However, still according to the same man page, the day starts from 1.
  mdt.tm_mday = hdr_.mdate & 0x1f;

  mdt.tm_hour = hdr_.mtime >> (16 - 5);
  mdt.tm_min = (hdr_.mtime >> (16 - 5 - 6)) & 0x3f;
  mdt.tm_sec = hdr_.mtime & 0x1f;
  return static_cast<uint64_t>(mktime(&mdt));
}

std::string ZipFile::GetDatetimeStr() const {
  char buf[32]{};
  time_t secs = static_cast<time_t>(GetDatetime());
  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", gmtime(&secs));
  buf[sizeof(buf) - 1] = '\0';
  return buf;
}

}  // namespace util
}  // namespace trace_processor
}  // namespace perfetto
<commit_msg>TraceProcessor: use UTC in ZipReader<commit_after>/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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 "src/trace_processor/util/zip_reader.h"

#include <time.h>

#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/utils.h"
#include "src/trace_processor/util/gzip_utils.h"
#include "src/trace_processor/util/streaming_line_reader.h"

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>  // For crc32().
#endif

namespace perfetto {
namespace trace_processor {
namespace util {

namespace {

// Entry signatures.
const uint32_t kFileHeaderSig = 0x04034b50;
const uint32_t kCentralDirectorySig = 0x02014b50;

// Compression flags.
const uint16_t kNoCompression = 0;
const uint16_t kDeflate = 8;

template <typename T>
T ReadAndAdvance(const uint8_t** ptr) {
  T res{};
  memcpy(base::AssumeLittleEndian(&res), *ptr, sizeof(T));
  *ptr += sizeof(T);
  return res;
}

}  // namespace

ZipReader::ZipReader() = default;
ZipReader::~ZipReader() = default;

base::Status ZipReader::Parse(const void* data, size_t len) {
  const uint8_t* input = static_cast<const uint8_t*>(data);
  const uint8_t* const input_begin = input;
  const uint8_t* const input_end = input + len;
  auto input_avail = [&] { return static_cast<size_t>(input_end - input); };

  // .zip file sequence:
  // [ File 1 header (30 bytes) ]
  // [ File 1 name ]
  // [ File 1 extra fields (optional) ]
  // [ File 1 compressed payload ]
  //
  // [ File 2 header (30 bytes) ]
  // [ File 2 name ]
  // [ File 2 extra fields (optional) ]
  // [ File 2 compressed payload ]
  //
  // [ Central directory (ignored) ]
  while (input < input_end) {
    // Initial state, we are building up the file header.
    if (cur_.raw_hdr_size < kZipFileHdrSize) {
      size_t copy_size =
          std::min(input_avail(), kZipFileHdrSize - cur_.raw_hdr_size);
      memcpy(&cur_.raw_hdr[cur_.raw_hdr_size], input, copy_size);
      cur_.raw_hdr_size += copy_size;
      input += copy_size;

      // If we got all the kZipFileHdrSize bytes, parse the zip file header now.
      if (cur_.raw_hdr_size == kZipFileHdrSize) {
        const uint8_t* hdr_it = &cur_.raw_hdr[0];
        cur_.hdr.signature = ReadAndAdvance<uint32_t>(&hdr_it);
        if (cur_.hdr.signature == kCentralDirectorySig) {
          // We reached the central directory at the end of file.
          // We don't make any use here of the central directory, so we just
          // ignore everything else after this point.
          // Here we abuse the ZipFile class a bit. The Central Directory header
          // has a different layout. The first 4 bytes (signature) match, the
          // rest don't but the sizeof(central dir) is >> sizeof(file header) so
          // we are fine.
          // We do this rather than retuning because we could have further
          // Parse() calls (imagine parsing bytes one by one), and we need a way
          // to keep track of the "keep eating input without doing anything".
          cur_.ignore_bytes_after_fname = std::numeric_limits<size_t>::max();
          input = input_end;
          break;
        }
        if (cur_.hdr.signature != kFileHeaderSig) {
          return base::ErrStatus(
              "Invalid signature found at offset 0x%zx. Actual=%x, expected=%x",
              static_cast<size_t>(input - input_begin) - kZipFileHdrSize,
              cur_.hdr.signature, kFileHeaderSig);
        }

        cur_.hdr.version = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.flags = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.compression = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.mtime = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.mdate = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.crc32 = ReadAndAdvance<uint32_t>(&hdr_it);
        cur_.hdr.compressed_size = ReadAndAdvance<uint32_t>(&hdr_it);
        cur_.hdr.uncompressed_size = ReadAndAdvance<uint32_t>(&hdr_it);
        cur_.hdr.fname_len = ReadAndAdvance<uint16_t>(&hdr_it);
        cur_.hdr.extra_field_len = ReadAndAdvance<uint16_t>(&hdr_it);
        PERFETTO_DCHECK(static_cast<size_t>(hdr_it - cur_.raw_hdr) ==
                        kZipFileHdrSize);

        // We support only up to version 2.0 (20). Higher versions define
        // more advanced features that we don't support (zip64 extensions,
        // encryption).
        // Flag bits 1,2 define the compression strength for deflating (which
        // zlib supports transparently). Other bits define other compression
        // methods that we don't support.
        if ((cur_.hdr.version > 20) || (cur_.hdr.flags & ~3) != 0) {
          return base::ErrStatus(
              "Unsupported zip features at offset 0x%zx. version=%x, flags=%x",
              static_cast<size_t>(input - input_begin) - kZipFileHdrSize,
              cur_.hdr.version, cur_.hdr.flags);
        }
        cur_.compressed_data.reset(new uint8_t[cur_.hdr.compressed_size]);
        cur_.ignore_bytes_after_fname = cur_.hdr.extra_field_len;
      }
      continue;
    }

    // Build up the file name.
    if (cur_.hdr.fname.size() < cur_.hdr.fname_len) {
      size_t name_left = cur_.hdr.fname_len - cur_.hdr.fname.size();
      size_t copy_size = std::min(name_left, input_avail());
      cur_.hdr.fname.append(reinterpret_cast<const char*>(input), copy_size);
      input += copy_size;
      continue;
    }

    // Skip any bytes if extra fields were present.
    if (cur_.ignore_bytes_after_fname > 0) {
      size_t skip_size = std::min(input_avail(), cur_.ignore_bytes_after_fname);
      cur_.ignore_bytes_after_fname -= skip_size;
      input += skip_size;
      continue;
    }

    // Build up the compressed payload
    if (cur_.compressed_data_written < cur_.hdr.compressed_size) {
      size_t needed = cur_.hdr.compressed_size - cur_.compressed_data_written;
      size_t copy_size = std::min(needed, input_avail());
      memcpy(&cur_.compressed_data[cur_.compressed_data_written], input,
             copy_size);
      cur_.compressed_data_written += copy_size;
      input += copy_size;
      continue;
    }

    // We have accumulated the whole header, file name and compressed payload.
    PERFETTO_DCHECK(cur_.raw_hdr_size == kZipFileHdrSize);
    PERFETTO_DCHECK(cur_.hdr.fname.size() == cur_.hdr.fname_len);
    PERFETTO_DCHECK(cur_.compressed_data_written == cur_.hdr.compressed_size);
    PERFETTO_DCHECK(cur_.ignore_bytes_after_fname == 0);

    files_.emplace_back();
    files_.back().hdr_ = std::move(cur_.hdr);
    files_.back().compressed_data_ = std::move(cur_.compressed_data);
    cur_ = FileParseState();  // Reset the parsing state for the next file.

  }  // while (input < input_end)

  // At this point we must have consumed all input.
  PERFETTO_DCHECK(input_avail() == 0);
  return base::OkStatus();
}

ZipFile::ZipFile() = default;
ZipFile::~ZipFile() = default;
ZipFile::ZipFile(ZipFile&& other) noexcept = default;
ZipFile& ZipFile::operator=(ZipFile&& other) noexcept = default;

base::Status ZipFile::Decompress(std::vector<uint8_t>* out_data) const {
  out_data->clear();

  auto res = DoDecompressionChecks();
  if (!res.ok())
    return res;

  if (hdr_.compression == kNoCompression) {
    const uint8_t* data = compressed_data_.get();
    out_data->insert(out_data->end(), data, data + hdr_.compressed_size);
    return base::OkStatus();
  }

  if (hdr_.uncompressed_size == 0)
    return base::OkStatus();

  PERFETTO_DCHECK(hdr_.compression == kDeflate);
  GzipDecompressor dec(GzipDecompressor::InputMode::kRawDeflate);
  dec.Feed(compressed_data_.get(), hdr_.compressed_size);

  out_data->resize(hdr_.uncompressed_size);
  auto dec_res = dec.ExtractOutput(out_data->data(), out_data->size());
  if (dec_res.ret != GzipDecompressor::ResultCode::kEof) {
    return base::ErrStatus("Zip decompression error (%d) on %s (c=%u, u=%u)",
                           static_cast<int>(dec_res.ret), hdr_.fname.c_str(),
                           hdr_.compressed_size, hdr_.uncompressed_size);
  }
  out_data->resize(dec_res.bytes_written);

#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
  const auto* crc_data = reinterpret_cast<const ::Bytef*>(out_data->data());
  auto crc_len = static_cast<::uInt>(out_data->size());
  auto actual_crc32 = static_cast<uint32_t>(::crc32(0u, crc_data, crc_len));
  if (actual_crc32 != hdr_.crc32) {
    return base::ErrStatus("Zip CRC32 failure on %s (actual: %x, expected: %x)",
                           hdr_.fname.c_str(), actual_crc32, hdr_.crc32);
  }
#endif

  return base::OkStatus();
}

base::Status ZipFile::DecompressLines(LinesCallback callback) const {
  using ResultCode = GzipDecompressor::ResultCode;

  auto res = DoDecompressionChecks();
  if (!res.ok())
    return res;

  StreamingLineReader line_reader(callback);

  if (hdr_.compression == kNoCompression) {
    line_reader.Tokenize(
        base::StringView(reinterpret_cast<const char*>(compressed_data_.get()),
                         hdr_.compressed_size));
    return base::OkStatus();
  }

  PERFETTO_DCHECK(hdr_.compression == kDeflate);
  GzipDecompressor dec(GzipDecompressor::InputMode::kRawDeflate);
  dec.Feed(compressed_data_.get(), hdr_.compressed_size);

  static constexpr size_t kChunkSize = 32768;
  GzipDecompressor::Result dec_res;
  do {
    auto* wptr = reinterpret_cast<uint8_t*>(line_reader.BeginWrite(kChunkSize));
    dec_res = dec.ExtractOutput(wptr, kChunkSize);
    if (dec_res.ret == ResultCode::kError ||
        dec_res.ret == ResultCode::kNeedsMoreInput)
      return base::ErrStatus("zlib decompression error on %s (%d)",
                             name().c_str(), static_cast<int>(dec_res.ret));
    PERFETTO_DCHECK(dec_res.bytes_written <= kChunkSize);
    line_reader.EndWrite(dec_res.bytes_written);
  } while (dec_res.ret == ResultCode::kOk);
  return base::OkStatus();
}

// Common logic for both Decompress() and DecompressLines().
base::Status ZipFile::DoDecompressionChecks() const {
  PERFETTO_DCHECK(compressed_data_);

  if (hdr_.compression == kNoCompression) {
    PERFETTO_CHECK(hdr_.compressed_size == hdr_.uncompressed_size);
    return base::OkStatus();
  }

  if (hdr_.compression != kDeflate) {
    return base::ErrStatus("Zip compression mode not supported (%u)",
                           hdr_.compression);
  }

  if (!IsGzipSupported()) {
    return base::ErrStatus(
        "Cannot open zip file. Gzip is not enabled in the current build. "
        "Rebuild with enable_perfetto_zlib=true");
  }

  return base::OkStatus();
}

// Returns a 64-bit version of time_t, that is, the num seconds since the Epoch.
uint64_t ZipFile::GetDatetime() const {
  // Date: 7 bits year, 4 bits month, 5 bits day.
  // Time: 5 bits hour, 6 bits minute, 5 bits second.
  struct tm mdt {};
  // As per man 3 mktime, `tm_year` is relative to 1900 not Epoch. Go figure.
  mdt.tm_year = 1980 + (hdr_.mdate >> (16 - 7)) - 1900;

  // As per the man page, the month ranges 0 to 11 (Jan = 0).
  mdt.tm_mon = ((hdr_.mdate >> (16 - 7 - 4)) & 0x0f) - 1;

  // However, still according to the same man page, the day starts from 1.
  mdt.tm_mday = hdr_.mdate & 0x1f;

  mdt.tm_hour = hdr_.mtime >> (16 - 5);
  mdt.tm_min = (hdr_.mtime >> (16 - 5 - 6)) & 0x3f;
  mdt.tm_sec = hdr_.mtime & 0x1f;
  return static_cast<uint64_t>(timegm(&mdt));
}

std::string ZipFile::GetDatetimeStr() const {
  char buf[32]{};
  time_t secs = static_cast<time_t>(GetDatetime());
  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", gmtime(&secs));
  buf[sizeof(buf) - 1] = '\0';
  return buf;
}

}  // namespace util
}  // namespace trace_processor
}  // namespace perfetto
<|endoftext|>