<commit_before>// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// 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 "minidump/minidump_memory_writer.h"

#include <dbghelp.h>
#include <stdint.h>

#include "base/basictypes.h"
#include "gtest/gtest.h"
#include "minidump/minidump_extensions.h"
#include "minidump/minidump_file_writer.h"
#include "minidump/minidump_stream_writer.h"
#include "minidump/minidump_test_util.h"
#include "util/file/string_file_writer.h"

namespace {

using namespace crashpad;
using namespace crashpad::test;

const MinidumpStreamType kBogusStreamType =
    static_cast<MinidumpStreamType>(1234);

// expected_streams is the expected number of streams in the file. The memory
// list must be the last stream. If there is another stream, it must come first,
// have stream type kBogusStreamType, and have zero-length data.
void GetMemoryListStream(const std::string& file_contents,
                         const MINIDUMP_MEMORY_LIST** memory_list,
                         const uint32_t expected_streams) {
  const size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER);
  const size_t kMemoryListStreamOffset =
      kDirectoryOffset + expected_streams * sizeof(MINIDUMP_DIRECTORY);
  const size_t kMemoryDescriptorsOffset =
      kMemoryListStreamOffset + sizeof(MINIDUMP_MEMORY_LIST);

  ASSERT_GE(file_contents.size(), kMemoryDescriptorsOffset);

  const MINIDUMP_HEADER* header =
      reinterpret_cast<const MINIDUMP_HEADER*>(&file_contents[0]);

  VerifyMinidumpHeader(header, expected_streams, 0);
  if (testing::Test::HasFatalFailure()) {
    return;
  }

  const MINIDUMP_DIRECTORY* directory =
      reinterpret_cast<const MINIDUMP_DIRECTORY*>(
          &file_contents[kDirectoryOffset]);

  if (expected_streams > 1) {
    ASSERT_EQ(kBogusStreamType, directory->StreamType);
    ASSERT_EQ(0u, directory->Location.DataSize);
    ASSERT_EQ(kMemoryListStreamOffset, directory->Location.Rva);
    ++directory;
  }

  ASSERT_EQ(kMinidumpStreamTypeMemoryList, directory->StreamType);
  ASSERT_GE(directory->Location.DataSize, sizeof(MINIDUMP_MEMORY_LIST));
  ASSERT_EQ(kMemoryListStreamOffset, directory->Location.Rva);

  *memory_list = reinterpret_cast<const MINIDUMP_MEMORY_LIST*>(
      &file_contents[kMemoryListStreamOffset]);

  ASSERT_EQ(sizeof(MINIDUMP_MEMORY_LIST) +
                (*memory_list)->NumberOfMemoryRanges *
                    sizeof(MINIDUMP_MEMORY_DESCRIPTOR),
            directory->Location.DataSize);
}

TEST(MinidumpMemoryWriter, EmptyMemoryList) {
  MinidumpFileWriter minidump_file_writer;
  MinidumpMemoryListWriter memory_list_writer;

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  ASSERT_EQ(sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
                sizeof(MINIDUMP_MEMORY_LIST),
            file_writer.string().size());

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 1);
  if (Test::HasFatalFailure()) {
    return;
  }

  EXPECT_EQ(0u, memory_list->NumberOfMemoryRanges);
}

class TestMemoryWriter final : public MinidumpMemoryWriter {
 public:
  TestMemoryWriter(uint64_t base_address, size_t size, uint8_t value)
      : MinidumpMemoryWriter(),
        base_address_(base_address),
        expected_offset_(-1),
        size_(size),
        value_(value) {}

  ~TestMemoryWriter() {}

 protected:
  // MinidumpMemoryWriter:
  virtual uint64_t MemoryRangeBaseAddress() const override {
    EXPECT_EQ(state(), kStateFrozen);
    return base_address_;
  }

  virtual size_t MemoryRangeSize() const override {
    EXPECT_GE(state(), kStateFrozen);
    return size_;
  }

  // MinidumpWritable:
  virtual bool WillWriteAtOffsetImpl(off_t offset) override {
    EXPECT_EQ(state(), kStateFrozen);
    expected_offset_ = offset;
    bool rv = MinidumpMemoryWriter::WillWriteAtOffsetImpl(offset);
    EXPECT_TRUE(rv);
    return rv;
  }

  virtual bool WriteObject(FileWriterInterface* file_writer) override {
    EXPECT_EQ(state(), kStateWritable);
    EXPECT_EQ(expected_offset_, file_writer->Seek(0, SEEK_CUR));

    bool rv = true;
    if (size_ > 0) {
      std::string data(size_, value_);
      rv = file_writer->Write(&data[0], size_);
      EXPECT_TRUE(rv);
    }

    return rv;
  }

 private:
  uint64_t base_address_;
  off_t expected_offset_;
  size_t size_;
  uint8_t value_;

  DISALLOW_COPY_AND_ASSIGN(TestMemoryWriter);
};

void ExpectMemoryDescriptorAndContents(
    const MINIDUMP_MEMORY_DESCRIPTOR* expected,
    const MINIDUMP_MEMORY_DESCRIPTOR* observed,
    const std::string& file_contents,
    uint8_t value,
    bool at_eof) {
  const uint32_t kMemoryAlignment = 16;

  EXPECT_EQ(expected->StartOfMemoryRange, observed->StartOfMemoryRange);
  EXPECT_EQ(expected->Memory.DataSize, observed->Memory.DataSize);
  EXPECT_EQ(
      (expected->Memory.Rva + kMemoryAlignment - 1) & ~(kMemoryAlignment - 1),
      observed->Memory.Rva);
  if (at_eof) {
    EXPECT_EQ(file_contents.size(),
              observed->Memory.Rva + observed->Memory.DataSize);
  } else {
    EXPECT_GE(file_contents.size(),
              observed->Memory.Rva + observed->Memory.DataSize);
  }

  std::string expected_data(expected->Memory.DataSize, value);
  std::string observed_data(&file_contents[observed->Memory.Rva],
                            observed->Memory.DataSize);
  EXPECT_EQ(expected_data, observed_data);
}

TEST(MinidumpMemoryWriter, OneMemoryRegion) {
  MinidumpFileWriter minidump_file_writer;
  MinidumpMemoryListWriter memory_list_writer;

  const uint64_t kBaseAddress = 0xfedcba9876543210ull;
  const uint64_t kSize = 0x1000;
  const uint8_t kValue = 'm';

  TestMemoryWriter memory_writer(kBaseAddress, kSize, kValue);
  memory_list_writer.AddMemory(&memory_writer);

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 1);
  if (Test::HasFatalFailure()) {
    return;
  }

  MINIDUMP_MEMORY_DESCRIPTOR expected;
  expected.StartOfMemoryRange = kBaseAddress;
  expected.Memory.DataSize = kSize;
  expected.Memory.Rva =
      sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
      sizeof(MINIDUMP_MEMORY_LIST) +
      memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
  ExpectMemoryDescriptorAndContents(&expected,
                                    &memory_list->MemoryRanges[0],
                                    file_writer.string(),
                                    kValue,
                                    true);
}

TEST(MinidumpMemoryWriter, TwoMemoryRegions) {
  MinidumpFileWriter minidump_file_writer;
  MinidumpMemoryListWriter memory_list_writer;

  const uint64_t kBaseAddress1 = 0x00c0ffeeull;
  const uint64_t kSize1 = 0x0100;
  const uint8_t kValue1 = '6';
  const uint64_t kBaseAddress2 = 0xfac00facull;
  const uint64_t kSize2 = 0x0200;
  const uint8_t kValue2 = '!';

  TestMemoryWriter memory_writer_1(kBaseAddress1, kSize1, kValue1);
  memory_list_writer.AddMemory(&memory_writer_1);
  TestMemoryWriter memory_writer_2(kBaseAddress2, kSize2, kValue2);
  memory_list_writer.AddMemory(&memory_writer_2);

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 1);
  if (Test::HasFatalFailure()) {
    return;
  }

  EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges);

  MINIDUMP_MEMORY_DESCRIPTOR expected;

  {
    SCOPED_TRACE("region 0");

    expected.StartOfMemoryRange = kBaseAddress1;
    expected.Memory.DataSize = kSize1;
    expected.Memory.Rva =
        sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
        sizeof(MINIDUMP_MEMORY_LIST) +
        memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[0],
                                      file_writer.string(),
                                      kValue1,
                                      false);
  }

  {
    SCOPED_TRACE("region 1");

    expected.StartOfMemoryRange = kBaseAddress2;
    expected.Memory.DataSize = kSize2;
    expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva +
                          memory_list->MemoryRanges[0].Memory.DataSize;
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[1],
                                      file_writer.string(),
                                      kValue2,
                                      true);
  }
}

class TestMemoryStream final : public internal::MinidumpStreamWriter {
 public:
  TestMemoryStream(uint64_t base_address, size_t size, uint8_t value)
      : MinidumpStreamWriter(), memory_(base_address, size, value) {}

  ~TestMemoryStream() {}

  TestMemoryWriter* memory() { return &memory_; }

  // MinidumpStreamWriter:
  virtual MinidumpStreamType StreamType() const override {
    return kBogusStreamType;
  }

 protected:
  // MinidumpWritable:
  virtual size_t SizeOfObject() override {
    EXPECT_GE(state(), kStateFrozen);
    return 0;
  }

  virtual std::vector<MinidumpWritable*> Children() override {
    EXPECT_GE(state(), kStateFrozen);
    std::vector<MinidumpWritable*> children(1, memory());
    return children;
  }

  virtual bool WriteObject(FileWriterInterface* file_writer) override {
    EXPECT_EQ(kStateWritable, state());
    return true;
  }

 private:
  TestMemoryWriter memory_;

  DISALLOW_COPY_AND_ASSIGN(TestMemoryStream);
};

TEST(MinidumpMemoryWriter, ExtraMemory) {
  // This tests MinidumpMemoryListWriter::AddExtraMemory(). That method adds
  // a MinidumpMemoryWriter to the MinidumpMemoryListWriter without making the
  // memory writer a child of the memory list writer.
  MinidumpFileWriter minidump_file_writer;

  const uint64_t kBaseAddress1 = 0x0000000000001000ull;
  const uint64_t kSize1 = 0x0400;
  const uint8_t kValue1 = '1';
  TestMemoryStream test_memory_stream(kBaseAddress1, kSize1, kValue1);

  MinidumpMemoryListWriter memory_list_writer;
  memory_list_writer.AddExtraMemory(test_memory_stream.memory());

  minidump_file_writer.AddStream(&test_memory_stream);

  const uint64_t kBaseAddress2 = 0x0000000000002000ull;
  const uint64_t kSize2 = 0x0400;
  const uint8_t kValue2 = 'm';

  TestMemoryWriter memory_writer(kBaseAddress2, kSize2, kValue2);
  memory_list_writer.AddMemory(&memory_writer);

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 2);
  if (Test::HasFatalFailure()) {
    return;
  }

  EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges);

  MINIDUMP_MEMORY_DESCRIPTOR expected;

  {
    SCOPED_TRACE("region 0");

    expected.StartOfMemoryRange = kBaseAddress1;
    expected.Memory.DataSize = kSize1;
    expected.Memory.Rva =
        sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) +
        sizeof(MINIDUMP_MEMORY_LIST) +
        memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[0],
                                      file_writer.string(),
                                      kValue1,
                                      false);
  }

  {
    SCOPED_TRACE("region 1");

    expected.StartOfMemoryRange = kBaseAddress2;
    expected.Memory.DataSize = kSize2;
    expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva +
                          memory_list->MemoryRanges[0].Memory.DataSize;
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[1],
                                      file_writer.string(),
                                      kValue2,
                                      true);
  }
}

}  // namespace
<commit_msg>MinidumpMemoryWriter test: Don’t suffix uint64_t constants.<commit_after>// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// 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 "minidump/minidump_memory_writer.h"

#include <dbghelp.h>
#include <stdint.h>

#include "base/basictypes.h"
#include "gtest/gtest.h"
#include "minidump/minidump_extensions.h"
#include "minidump/minidump_file_writer.h"
#include "minidump/minidump_stream_writer.h"
#include "minidump/minidump_test_util.h"
#include "util/file/string_file_writer.h"

namespace {

using namespace crashpad;
using namespace crashpad::test;

const MinidumpStreamType kBogusStreamType =
    static_cast<MinidumpStreamType>(1234);

// expected_streams is the expected number of streams in the file. The memory
// list must be the last stream. If there is another stream, it must come first,
// have stream type kBogusStreamType, and have zero-length data.
void GetMemoryListStream(const std::string& file_contents,
                         const MINIDUMP_MEMORY_LIST** memory_list,
                         const uint32_t expected_streams) {
  const size_t kDirectoryOffset = sizeof(MINIDUMP_HEADER);
  const size_t kMemoryListStreamOffset =
      kDirectoryOffset + expected_streams * sizeof(MINIDUMP_DIRECTORY);
  const size_t kMemoryDescriptorsOffset =
      kMemoryListStreamOffset + sizeof(MINIDUMP_MEMORY_LIST);

  ASSERT_GE(file_contents.size(), kMemoryDescriptorsOffset);

  const MINIDUMP_HEADER* header =
      reinterpret_cast<const MINIDUMP_HEADER*>(&file_contents[0]);

  VerifyMinidumpHeader(header, expected_streams, 0);
  if (testing::Test::HasFatalFailure()) {
    return;
  }

  const MINIDUMP_DIRECTORY* directory =
      reinterpret_cast<const MINIDUMP_DIRECTORY*>(
          &file_contents[kDirectoryOffset]);

  if (expected_streams > 1) {
    ASSERT_EQ(kBogusStreamType, directory->StreamType);
    ASSERT_EQ(0u, directory->Location.DataSize);
    ASSERT_EQ(kMemoryListStreamOffset, directory->Location.Rva);
    ++directory;
  }

  ASSERT_EQ(kMinidumpStreamTypeMemoryList, directory->StreamType);
  ASSERT_GE(directory->Location.DataSize, sizeof(MINIDUMP_MEMORY_LIST));
  ASSERT_EQ(kMemoryListStreamOffset, directory->Location.Rva);

  *memory_list = reinterpret_cast<const MINIDUMP_MEMORY_LIST*>(
      &file_contents[kMemoryListStreamOffset]);

  ASSERT_EQ(sizeof(MINIDUMP_MEMORY_LIST) +
                (*memory_list)->NumberOfMemoryRanges *
                    sizeof(MINIDUMP_MEMORY_DESCRIPTOR),
            directory->Location.DataSize);
}

TEST(MinidumpMemoryWriter, EmptyMemoryList) {
  MinidumpFileWriter minidump_file_writer;
  MinidumpMemoryListWriter memory_list_writer;

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  ASSERT_EQ(sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
                sizeof(MINIDUMP_MEMORY_LIST),
            file_writer.string().size());

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 1);
  if (Test::HasFatalFailure()) {
    return;
  }

  EXPECT_EQ(0u, memory_list->NumberOfMemoryRanges);
}

class TestMemoryWriter final : public MinidumpMemoryWriter {
 public:
  TestMemoryWriter(uint64_t base_address, size_t size, uint8_t value)
      : MinidumpMemoryWriter(),
        base_address_(base_address),
        expected_offset_(-1),
        size_(size),
        value_(value) {}

  ~TestMemoryWriter() {}

 protected:
  // MinidumpMemoryWriter:
  virtual uint64_t MemoryRangeBaseAddress() const override {
    EXPECT_EQ(state(), kStateFrozen);
    return base_address_;
  }

  virtual size_t MemoryRangeSize() const override {
    EXPECT_GE(state(), kStateFrozen);
    return size_;
  }

  // MinidumpWritable:
  virtual bool WillWriteAtOffsetImpl(off_t offset) override {
    EXPECT_EQ(state(), kStateFrozen);
    expected_offset_ = offset;
    bool rv = MinidumpMemoryWriter::WillWriteAtOffsetImpl(offset);
    EXPECT_TRUE(rv);
    return rv;
  }

  virtual bool WriteObject(FileWriterInterface* file_writer) override {
    EXPECT_EQ(state(), kStateWritable);
    EXPECT_EQ(expected_offset_, file_writer->Seek(0, SEEK_CUR));

    bool rv = true;
    if (size_ > 0) {
      std::string data(size_, value_);
      rv = file_writer->Write(&data[0], size_);
      EXPECT_TRUE(rv);
    }

    return rv;
  }

 private:
  uint64_t base_address_;
  off_t expected_offset_;
  size_t size_;
  uint8_t value_;

  DISALLOW_COPY_AND_ASSIGN(TestMemoryWriter);
};

void ExpectMemoryDescriptorAndContents(
    const MINIDUMP_MEMORY_DESCRIPTOR* expected,
    const MINIDUMP_MEMORY_DESCRIPTOR* observed,
    const std::string& file_contents,
    uint8_t value,
    bool at_eof) {
  const uint32_t kMemoryAlignment = 16;

  EXPECT_EQ(expected->StartOfMemoryRange, observed->StartOfMemoryRange);
  EXPECT_EQ(expected->Memory.DataSize, observed->Memory.DataSize);
  EXPECT_EQ(
      (expected->Memory.Rva + kMemoryAlignment - 1) & ~(kMemoryAlignment - 1),
      observed->Memory.Rva);
  if (at_eof) {
    EXPECT_EQ(file_contents.size(),
              observed->Memory.Rva + observed->Memory.DataSize);
  } else {
    EXPECT_GE(file_contents.size(),
              observed->Memory.Rva + observed->Memory.DataSize);
  }

  std::string expected_data(expected->Memory.DataSize, value);
  std::string observed_data(&file_contents[observed->Memory.Rva],
                            observed->Memory.DataSize);
  EXPECT_EQ(expected_data, observed_data);
}

TEST(MinidumpMemoryWriter, OneMemoryRegion) {
  MinidumpFileWriter minidump_file_writer;
  MinidumpMemoryListWriter memory_list_writer;

  const uint64_t kBaseAddress = 0xfedcba9876543210;
  const uint64_t kSize = 0x1000;
  const uint8_t kValue = 'm';

  TestMemoryWriter memory_writer(kBaseAddress, kSize, kValue);
  memory_list_writer.AddMemory(&memory_writer);

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 1);
  if (Test::HasFatalFailure()) {
    return;
  }

  MINIDUMP_MEMORY_DESCRIPTOR expected;
  expected.StartOfMemoryRange = kBaseAddress;
  expected.Memory.DataSize = kSize;
  expected.Memory.Rva =
      sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
      sizeof(MINIDUMP_MEMORY_LIST) +
      memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
  ExpectMemoryDescriptorAndContents(&expected,
                                    &memory_list->MemoryRanges[0],
                                    file_writer.string(),
                                    kValue,
                                    true);
}

TEST(MinidumpMemoryWriter, TwoMemoryRegions) {
  MinidumpFileWriter minidump_file_writer;
  MinidumpMemoryListWriter memory_list_writer;

  const uint64_t kBaseAddress1 = 0xc0ffee;
  const uint64_t kSize1 = 0x0100;
  const uint8_t kValue1 = '6';
  const uint64_t kBaseAddress2 = 0xfac00fac;
  const uint64_t kSize2 = 0x0200;
  const uint8_t kValue2 = '!';

  TestMemoryWriter memory_writer_1(kBaseAddress1, kSize1, kValue1);
  memory_list_writer.AddMemory(&memory_writer_1);
  TestMemoryWriter memory_writer_2(kBaseAddress2, kSize2, kValue2);
  memory_list_writer.AddMemory(&memory_writer_2);

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 1);
  if (Test::HasFatalFailure()) {
    return;
  }

  EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges);

  MINIDUMP_MEMORY_DESCRIPTOR expected;

  {
    SCOPED_TRACE("region 0");

    expected.StartOfMemoryRange = kBaseAddress1;
    expected.Memory.DataSize = kSize1;
    expected.Memory.Rva =
        sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
        sizeof(MINIDUMP_MEMORY_LIST) +
        memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[0],
                                      file_writer.string(),
                                      kValue1,
                                      false);
  }

  {
    SCOPED_TRACE("region 1");

    expected.StartOfMemoryRange = kBaseAddress2;
    expected.Memory.DataSize = kSize2;
    expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva +
                          memory_list->MemoryRanges[0].Memory.DataSize;
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[1],
                                      file_writer.string(),
                                      kValue2,
                                      true);
  }
}

class TestMemoryStream final : public internal::MinidumpStreamWriter {
 public:
  TestMemoryStream(uint64_t base_address, size_t size, uint8_t value)
      : MinidumpStreamWriter(), memory_(base_address, size, value) {}

  ~TestMemoryStream() {}

  TestMemoryWriter* memory() { return &memory_; }

  // MinidumpStreamWriter:
  virtual MinidumpStreamType StreamType() const override {
    return kBogusStreamType;
  }

 protected:
  // MinidumpWritable:
  virtual size_t SizeOfObject() override {
    EXPECT_GE(state(), kStateFrozen);
    return 0;
  }

  virtual std::vector<MinidumpWritable*> Children() override {
    EXPECT_GE(state(), kStateFrozen);
    std::vector<MinidumpWritable*> children(1, memory());
    return children;
  }

  virtual bool WriteObject(FileWriterInterface* file_writer) override {
    EXPECT_EQ(kStateWritable, state());
    return true;
  }

 private:
  TestMemoryWriter memory_;

  DISALLOW_COPY_AND_ASSIGN(TestMemoryStream);
};

TEST(MinidumpMemoryWriter, ExtraMemory) {
  // This tests MinidumpMemoryListWriter::AddExtraMemory(). That method adds
  // a MinidumpMemoryWriter to the MinidumpMemoryListWriter without making the
  // memory writer a child of the memory list writer.
  MinidumpFileWriter minidump_file_writer;

  const uint64_t kBaseAddress1 = 0x1000;
  const uint64_t kSize1 = 0x0400;
  const uint8_t kValue1 = '1';
  TestMemoryStream test_memory_stream(kBaseAddress1, kSize1, kValue1);

  MinidumpMemoryListWriter memory_list_writer;
  memory_list_writer.AddExtraMemory(test_memory_stream.memory());

  minidump_file_writer.AddStream(&test_memory_stream);

  const uint64_t kBaseAddress2 = 0x2000;
  const uint64_t kSize2 = 0x0400;
  const uint8_t kValue2 = 'm';

  TestMemoryWriter memory_writer(kBaseAddress2, kSize2, kValue2);
  memory_list_writer.AddMemory(&memory_writer);

  minidump_file_writer.AddStream(&memory_list_writer);

  StringFileWriter file_writer;
  ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer));

  const MINIDUMP_MEMORY_LIST* memory_list;
  GetMemoryListStream(file_writer.string(), &memory_list, 2);
  if (Test::HasFatalFailure()) {
    return;
  }

  EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges);

  MINIDUMP_MEMORY_DESCRIPTOR expected;

  {
    SCOPED_TRACE("region 0");

    expected.StartOfMemoryRange = kBaseAddress1;
    expected.Memory.DataSize = kSize1;
    expected.Memory.Rva =
        sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) +
        sizeof(MINIDUMP_MEMORY_LIST) +
        memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR);
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[0],
                                      file_writer.string(),
                                      kValue1,
                                      false);
  }

  {
    SCOPED_TRACE("region 1");

    expected.StartOfMemoryRange = kBaseAddress2;
    expected.Memory.DataSize = kSize2;
    expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva +
                          memory_list->MemoryRanges[0].Memory.DataSize;
    ExpectMemoryDescriptorAndContents(&expected,
                                      &memory_list->MemoryRanges[1],
                                      file_writer.string(),
                                      kValue2,
                                      true);
  }
}

}  // namespace
<|endoftext|>


<commit_before>// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// 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 "minidump/minidump_string_writer.h"

#include <string>

#include "base/compiler_specific.h"
#include "base/format_macros.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "gtest/gtest.h"
#include "minidump/test/minidump_rva_list_test_util.h"
#include "minidump/test/minidump_string_writer_test_util.h"
#include "minidump/test/minidump_writable_test_util.h"
#include "util/file/string_file.h"

namespace crashpad {
namespace test {
namespace {

TEST(MinidumpStringWriter, MinidumpUTF16StringWriter) {
  StringFile string_file;

  {
    SCOPED_TRACE("unset");
    string_file.Reset();
    crashpad::internal::MinidumpUTF16StringWriter string_writer;
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));
    ASSERT_EQ(string_file.string().size(), 6u);

    const MINIDUMP_STRING* minidump_string =
        MinidumpStringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    EXPECT_EQ(MinidumpStringAtRVAAsString(string_file.string(), 0),
              base::string16());
  }

  static constexpr struct {
    size_t input_length;
    const char* input_string;
    size_t output_length;
    base::char16 output_string[10];
  } kTestData[] = {
      {0, "", 0, {}},
      {1, "a", 1, {'a'}},
      {2, "\0b", 2, {0, 'b'}},
      {3, "cde", 3, {'c', 'd', 'e'}},
      {9, "Hi world!", 9, {'H', 'i', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
      {7, "ret\nurn", 7, {'r', 'e', 't', '\n', 'u', 'r', 'n'}},
      {2, "\303\251", 1, {0x00e9}},  // é

      // oóöőo
      {8, "o\303\263\303\266\305\221o", 5, {'o', 0x00f3, 0x00f6, 0x151, 'o'}},
      {4, "\360\220\204\202", 2, {0xd800, 0xdd02}},  // 𐄂 (non-BMP)
  };

  for (size_t index = 0; index < base::size(kTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf(
        "index %" PRIuS ", input %s", index, kTestData[index].input_string));

    // Make sure that the expected output string with its NUL terminator fits in
    // the space provided.
    ASSERT_EQ(
        kTestData[index]
            .output_string[base::size(kTestData[index].output_string) - 1],
        0);

    string_file.Reset();
    crashpad::internal::MinidumpUTF16StringWriter string_writer;
    string_writer.SetUTF8(std::string(kTestData[index].input_string,
                                      kTestData[index].input_length));
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));

    const size_t expected_utf16_units_with_nul =
        kTestData[index].output_length + 1;
    MINIDUMP_STRING* tmp;
    ALLOW_UNUSED_LOCAL(tmp);
    const size_t expected_utf16_bytes =
        expected_utf16_units_with_nul * sizeof(tmp->Buffer[0]);
    ASSERT_EQ(string_file.string().size(), sizeof(*tmp) + expected_utf16_bytes);

    const MINIDUMP_STRING* minidump_string =
        MinidumpStringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    base::string16 expect_string = base::string16(
        kTestData[index].output_string, kTestData[index].output_length);
    EXPECT_EQ(MinidumpStringAtRVAAsString(string_file.string(), 0),
              expect_string);
  }
}

// Related tracking issues:
// https://fuchsia.atlassian.net/browse/DX-487
// https://bugs.chromium.org/p/chromium/issues/detail?id=872892
// https://bugs.chromium.org/p/chromium/issues/detail?id=889582
// TODO: Re-enable test once LUCI supports invalid UTF8 characters in test logs.
#if !defined(CRASHPAD_IS_IN_FUCHSIA)
TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) {
  StringFile string_file;

  static constexpr const char* kTestData[] = {
      "\200",  // continuation byte
      "\300",  // start byte followed by EOF
      "\310\177",  // start byte without continuation
      "\340\200",  // EOF in middle of 3-byte sequence
      "\340\200\115",  // invalid 3-byte sequence
      "\303\0\251",  // NUL in middle of valid sequence
  };

  for (size_t index = 0; index < base::size(kTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf(
        "index %" PRIuS ", input %s", index, kTestData[index]));
    string_file.Reset();
    crashpad::internal::MinidumpUTF16StringWriter string_writer;
    string_writer.SetUTF8(kTestData[index]);
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));

    // The requirements for conversion of invalid UTF-8 input are lax. Make sure
    // that at least enough data was written for a string that has one unit and
    // a NUL terminator, make sure that the length field matches the length of
    // data written, and make sure that at least one U+FFFD replacement
    // character was written.
    const MINIDUMP_STRING* minidump_string =
        MinidumpStringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    MINIDUMP_STRING* tmp;
    ALLOW_UNUSED_LOCAL(tmp);
    EXPECT_EQ(
        minidump_string->Length,
        string_file.string().size() - sizeof(*tmp) - sizeof(tmp->Buffer[0]));
    base::string16 output_string =
        MinidumpStringAtRVAAsString(string_file.string(), 0);
    EXPECT_FALSE(output_string.empty());
    EXPECT_NE(output_string.find(0xfffd), base::string16::npos);
  }
}
#endif  // !defined(CRASHPAD_IS_IN_FUCHSIA)

TEST(MinidumpStringWriter, MinidumpUTF8StringWriter) {
  StringFile string_file;

  {
    SCOPED_TRACE("unset");
    string_file.Reset();
    crashpad::internal::MinidumpUTF8StringWriter string_writer;
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));
    ASSERT_EQ(string_file.string().size(), 5u);

    const MinidumpUTF8String* minidump_string =
        MinidumpUTF8StringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), 0),
              std::string());
  }

  static constexpr struct {
    size_t length;
    const char* string;
  } kTestData[] = {
      {0, ""},
      {1, "a"},
      {2, "\0b"},
      {3, "cde"},
      {9, "Hi world!"},
      {7, "ret\nurn"},
      {2, "\303\251"},  // é

      // oóöőo
      {8, "o\303\263\303\266\305\221o"},
      {4, "\360\220\204\202"},  // 𐄂 (non-BMP)
  };

  for (size_t index = 0; index < base::size(kTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf(
        "index %" PRIuS ", input %s", index, kTestData[index].string));

    string_file.Reset();
    crashpad::internal::MinidumpUTF8StringWriter string_writer;
    std::string test_string(kTestData[index].string, kTestData[index].length);
    string_writer.SetUTF8(test_string);
    EXPECT_EQ(string_writer.UTF8(), test_string);
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));

    const size_t expected_utf8_bytes_with_nul = kTestData[index].length + 1;
    ASSERT_EQ(string_file.string().size(),
              sizeof(MinidumpUTF8String) + expected_utf8_bytes_with_nul);

    const MinidumpUTF8String* minidump_string =
        MinidumpUTF8StringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), 0),
              test_string);
  }
}

struct MinidumpUTF16StringListWriterTraits {
  using MinidumpStringListWriterType = MinidumpUTF16StringListWriter;
  static base::string16 ExpectationForUTF8(const std::string& utf8) {
    return base::UTF8ToUTF16(utf8);
  }
  static base::string16 ObservationAtRVA(const std::string& file_contents,
                                         RVA rva) {
    return MinidumpStringAtRVAAsString(file_contents, rva);
  }
};

struct MinidumpUTF8StringListWriterTraits {
  using MinidumpStringListWriterType = MinidumpUTF8StringListWriter;
  static std::string ExpectationForUTF8(const std::string& utf8) {
    return utf8;
  }
  static std::string ObservationAtRVA(const std::string& file_contents,
                                      RVA rva) {
    return MinidumpUTF8StringAtRVAAsString(file_contents, rva);
  }
};

template <typename Traits>
void MinidumpStringListTest() {
  std::vector<std::string> strings;
  strings.push_back(std::string("One"));
  strings.push_back(std::string());
  strings.push_back(std::string("3"));
  strings.push_back(std::string("\360\237\222\251"));

  typename Traits::MinidumpStringListWriterType string_list_writer;
  EXPECT_FALSE(string_list_writer.IsUseful());
  string_list_writer.InitializeFromVector(strings);
  EXPECT_TRUE(string_list_writer.IsUseful());

  StringFile string_file;

  ASSERT_TRUE(string_list_writer.WriteEverything(&string_file));

  const MinidumpRVAList* list =
      MinidumpRVAListAtStart(string_file.string(), strings.size());
  ASSERT_TRUE(list);

  for (size_t index = 0; index < strings.size(); ++index) {
    EXPECT_EQ(
        Traits::ObservationAtRVA(string_file.string(), list->children[index]),
        Traits::ExpectationForUTF8(strings[index]));
  }
}

TEST(MinidumpStringWriter, MinidumpUTF16StringList) {
  MinidumpStringListTest<MinidumpUTF16StringListWriterTraits>();
}

TEST(MinidumpStringWriter, MinidumpUTF8StringList) {
  MinidumpStringListTest<MinidumpUTF8StringListWriterTraits>();
}

}  // namespace
}  // namespace test
}  // namespace crashpad
<commit_msg>[fuchsia] Enable ConvertInvalidUTF8ToUTF16<commit_after>// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// 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 "minidump/minidump_string_writer.h"

#include <string>

#include "base/compiler_specific.h"
#include "base/format_macros.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "gtest/gtest.h"
#include "minidump/test/minidump_rva_list_test_util.h"
#include "minidump/test/minidump_string_writer_test_util.h"
#include "minidump/test/minidump_writable_test_util.h"
#include "util/file/string_file.h"

namespace crashpad {
namespace test {
namespace {

TEST(MinidumpStringWriter, MinidumpUTF16StringWriter) {
  StringFile string_file;

  {
    SCOPED_TRACE("unset");
    string_file.Reset();
    crashpad::internal::MinidumpUTF16StringWriter string_writer;
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));
    ASSERT_EQ(string_file.string().size(), 6u);

    const MINIDUMP_STRING* minidump_string =
        MinidumpStringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    EXPECT_EQ(MinidumpStringAtRVAAsString(string_file.string(), 0),
              base::string16());
  }

  static constexpr struct {
    size_t input_length;
    const char* input_string;
    size_t output_length;
    base::char16 output_string[10];
  } kTestData[] = {
      {0, "", 0, {}},
      {1, "a", 1, {'a'}},
      {2, "\0b", 2, {0, 'b'}},
      {3, "cde", 3, {'c', 'd', 'e'}},
      {9, "Hi world!", 9, {'H', 'i', ' ', 'w', 'o', 'r', 'l', 'd', '!'}},
      {7, "ret\nurn", 7, {'r', 'e', 't', '\n', 'u', 'r', 'n'}},
      {2, "\303\251", 1, {0x00e9}},  // é

      // oóöőo
      {8, "o\303\263\303\266\305\221o", 5, {'o', 0x00f3, 0x00f6, 0x151, 'o'}},
      {4, "\360\220\204\202", 2, {0xd800, 0xdd02}},  // 𐄂 (non-BMP)
  };

  for (size_t index = 0; index < base::size(kTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf(
        "index %" PRIuS ", input %s", index, kTestData[index].input_string));

    // Make sure that the expected output string with its NUL terminator fits in
    // the space provided.
    ASSERT_EQ(
        kTestData[index]
            .output_string[base::size(kTestData[index].output_string) - 1],
        0);

    string_file.Reset();
    crashpad::internal::MinidumpUTF16StringWriter string_writer;
    string_writer.SetUTF8(std::string(kTestData[index].input_string,
                                      kTestData[index].input_length));
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));

    const size_t expected_utf16_units_with_nul =
        kTestData[index].output_length + 1;
    MINIDUMP_STRING* tmp;
    ALLOW_UNUSED_LOCAL(tmp);
    const size_t expected_utf16_bytes =
        expected_utf16_units_with_nul * sizeof(tmp->Buffer[0]);
    ASSERT_EQ(string_file.string().size(), sizeof(*tmp) + expected_utf16_bytes);

    const MINIDUMP_STRING* minidump_string =
        MinidumpStringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    base::string16 expect_string = base::string16(
        kTestData[index].output_string, kTestData[index].output_length);
    EXPECT_EQ(MinidumpStringAtRVAAsString(string_file.string(), 0),
              expect_string);
  }
}

TEST(MinidumpStringWriter, ConvertInvalidUTF8ToUTF16) {
  StringFile string_file;

  static constexpr const char* kTestData[] = {
      "\200",  // continuation byte
      "\300",  // start byte followed by EOF
      "\310\177",  // start byte without continuation
      "\340\200",  // EOF in middle of 3-byte sequence
      "\340\200\115",  // invalid 3-byte sequence
      "\303\0\251",  // NUL in middle of valid sequence
  };

  for (size_t index = 0; index < base::size(kTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf(
        "index %" PRIuS ", input %s", index, kTestData[index]));
    string_file.Reset();
    crashpad::internal::MinidumpUTF16StringWriter string_writer;
    string_writer.SetUTF8(kTestData[index]);
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));

    // The requirements for conversion of invalid UTF-8 input are lax. Make sure
    // that at least enough data was written for a string that has one unit and
    // a NUL terminator, make sure that the length field matches the length of
    // data written, and make sure that at least one U+FFFD replacement
    // character was written.
    const MINIDUMP_STRING* minidump_string =
        MinidumpStringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    MINIDUMP_STRING* tmp;
    ALLOW_UNUSED_LOCAL(tmp);
    EXPECT_EQ(
        minidump_string->Length,
        string_file.string().size() - sizeof(*tmp) - sizeof(tmp->Buffer[0]));
    base::string16 output_string =
        MinidumpStringAtRVAAsString(string_file.string(), 0);
    EXPECT_FALSE(output_string.empty());
    EXPECT_NE(output_string.find(0xfffd), base::string16::npos);
  }
}

TEST(MinidumpStringWriter, MinidumpUTF8StringWriter) {
  StringFile string_file;

  {
    SCOPED_TRACE("unset");
    string_file.Reset();
    crashpad::internal::MinidumpUTF8StringWriter string_writer;
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));
    ASSERT_EQ(string_file.string().size(), 5u);

    const MinidumpUTF8String* minidump_string =
        MinidumpUTF8StringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), 0),
              std::string());
  }

  static constexpr struct {
    size_t length;
    const char* string;
  } kTestData[] = {
      {0, ""},
      {1, "a"},
      {2, "\0b"},
      {3, "cde"},
      {9, "Hi world!"},
      {7, "ret\nurn"},
      {2, "\303\251"},  // é

      // oóöőo
      {8, "o\303\263\303\266\305\221o"},
      {4, "\360\220\204\202"},  // 𐄂 (non-BMP)
  };

  for (size_t index = 0; index < base::size(kTestData); ++index) {
    SCOPED_TRACE(base::StringPrintf(
        "index %" PRIuS ", input %s", index, kTestData[index].string));

    string_file.Reset();
    crashpad::internal::MinidumpUTF8StringWriter string_writer;
    std::string test_string(kTestData[index].string, kTestData[index].length);
    string_writer.SetUTF8(test_string);
    EXPECT_EQ(string_writer.UTF8(), test_string);
    EXPECT_TRUE(string_writer.WriteEverything(&string_file));

    const size_t expected_utf8_bytes_with_nul = kTestData[index].length + 1;
    ASSERT_EQ(string_file.string().size(),
              sizeof(MinidumpUTF8String) + expected_utf8_bytes_with_nul);

    const MinidumpUTF8String* minidump_string =
        MinidumpUTF8StringAtRVA(string_file.string(), 0);
    EXPECT_TRUE(minidump_string);
    EXPECT_EQ(MinidumpUTF8StringAtRVAAsString(string_file.string(), 0),
              test_string);
  }
}

struct MinidumpUTF16StringListWriterTraits {
  using MinidumpStringListWriterType = MinidumpUTF16StringListWriter;
  static base::string16 ExpectationForUTF8(const std::string& utf8) {
    return base::UTF8ToUTF16(utf8);
  }
  static base::string16 ObservationAtRVA(const std::string& file_contents,
                                         RVA rva) {
    return MinidumpStringAtRVAAsString(file_contents, rva);
  }
};

struct MinidumpUTF8StringListWriterTraits {
  using MinidumpStringListWriterType = MinidumpUTF8StringListWriter;
  static std::string ExpectationForUTF8(const std::string& utf8) {
    return utf8;
  }
  static std::string ObservationAtRVA(const std::string& file_contents,
                                      RVA rva) {
    return MinidumpUTF8StringAtRVAAsString(file_contents, rva);
  }
};

template <typename Traits>
void MinidumpStringListTest() {
  std::vector<std::string> strings;
  strings.push_back(std::string("One"));
  strings.push_back(std::string());
  strings.push_back(std::string("3"));
  strings.push_back(std::string("\360\237\222\251"));

  typename Traits::MinidumpStringListWriterType string_list_writer;
  EXPECT_FALSE(string_list_writer.IsUseful());
  string_list_writer.InitializeFromVector(strings);
  EXPECT_TRUE(string_list_writer.IsUseful());

  StringFile string_file;

  ASSERT_TRUE(string_list_writer.WriteEverything(&string_file));

  const MinidumpRVAList* list =
      MinidumpRVAListAtStart(string_file.string(), strings.size());
  ASSERT_TRUE(list);

  for (size_t index = 0; index < strings.size(); ++index) {
    EXPECT_EQ(
        Traits::ObservationAtRVA(string_file.string(), list->children[index]),
        Traits::ExpectationForUTF8(strings[index]));
  }
}

TEST(MinidumpStringWriter, MinidumpUTF16StringList) {
  MinidumpStringListTest<MinidumpUTF16StringListWriterTraits>();
}

TEST(MinidumpStringWriter, MinidumpUTF8StringList) {
  MinidumpStringListTest<MinidumpUTF8StringListWriterTraits>();
}

}  // namespace
}  // namespace test
}  // namespace crashpad
<|endoftext|>


<commit_before>/*
  This file is part of Pack My Sprites.

  Pack My Sprites is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as published by
  the Free Software Foundation, either version 3 of the License.

  Pack My Sprites is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#pragma once

#define PMS_TO_STR_BIS(v) # v
#define PMS_TO_STR(v) PMS_TO_STR_BIS(v)

#define PMS_MAJOR_VERSION 1
#define PMS_MINOR_VERSION 0
#define PMS_RELEASE_NUMBER 3
#define PMS_VERSION_STRING "Pack My Sprites, "                          \
  PMS_TO_STR(PMS_MAJOR_VERSION) "." PMS_TO_STR(PMS_MINOR_VERSION)       \
  "." PMS_TO_STR(PMS_RELEASE_NUMBER)
<commit_msg>Version is 1.0.4<commit_after>/*
  This file is part of Pack My Sprites.

  Pack My Sprites is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as published by
  the Free Software Foundation, either version 3 of the License.

  Pack My Sprites is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#pragma once

#define PMS_TO_STR_BIS(v) # v
#define PMS_TO_STR(v) PMS_TO_STR_BIS(v)

#define PMS_MAJOR_VERSION 1
#define PMS_MINOR_VERSION 0
#define PMS_RELEASE_NUMBER 4
#define PMS_VERSION_STRING "Pack My Sprites, "                          \
  PMS_TO_STR(PMS_MAJOR_VERSION) "." PMS_TO_STR(PMS_MINOR_VERSION)       \
  "." PMS_TO_STR(PMS_RELEASE_NUMBER)
<|endoftext|>


<commit_before>/*
 * Chunk.cpp
 *****************************************************************************
 * Copyright (C) 2010 - 2011 Klagenfurt University
 *
 * Created on: Aug 10, 2010
 * Authors: Christopher Mueller <christopher.mueller@itec.uni-klu.ac.at>
 *          Christian Timmerer  <christian.timmerer@itec.uni-klu.ac.at>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "Chunk.h"
#include "HTTPConnection.hpp"
#include "HTTPConnectionManager.h"
#include "Downloader.hpp"

#include <vlc_common.h>
#include <vlc_url.h>
#include <vlc_block.h>

using namespace adaptative::http;

AbstractChunkSource::AbstractChunkSource()
{
    contentLength = 0;
}

AbstractChunkSource::~AbstractChunkSource()
{

}

void AbstractChunkSource::setBytesRange(const BytesRange &range)
{
    bytesRange = range;
    if(bytesRange.isValid() && bytesRange.getEndByte())
        contentLength = bytesRange.getEndByte() - bytesRange.getStartByte();
}

const BytesRange & AbstractChunkSource::getBytesRange() const
{
    return bytesRange;
}

AbstractChunk::AbstractChunk(AbstractChunkSource *source_)
{
    bytesRead = 0;
    source = source_;
}

AbstractChunk::~AbstractChunk()
{
    delete source;
}

size_t AbstractChunk::getBytesRead() const
{
    return this->bytesRead;
}

block_t * AbstractChunk::doRead(size_t size, bool b_block)
{
    if(!source)
        return NULL;

    block_t *block = (b_block) ? source->readBlock() : source->read(size);
    if(block)
    {
        if(bytesRead == 0)
            block->i_flags |= BLOCK_FLAG_HEADER;
        bytesRead += block->i_buffer;
        onDownload(&block);
        block->i_flags ^= BLOCK_FLAG_HEADER;
    }

    return block;
}

bool AbstractChunk::isEmpty() const
{
    return !source->hasMoreData();
}

block_t * AbstractChunk::readBlock()
{
    return doRead(0, true);
}

block_t * AbstractChunk::read(size_t size)
{
    return doRead(size, false);
}

HTTPChunkSource::HTTPChunkSource(const std::string& url, HTTPConnectionManager *manager) :
    AbstractChunkSource(),
    connection   (NULL),
    connManager  (manager),
    consumed     (0),
    port         (0)
{
    prepared = false;
    eof = false;
    if(!init(url))
        throw VLC_EGENERIC;
}

HTTPChunkSource::~HTTPChunkSource()
{
    if(connection)
        connection->setUsed(false);
}

bool HTTPChunkSource::init(const std::string &url)
{
    this->url = url;

    std::size_t pos = url.find("://");
    if(pos != std::string::npos)
    {
        scheme = url.substr(0, pos);
    }

    if(scheme != "http" && scheme != "https")
        return false;

    vlc_url_t url_components;
    vlc_UrlParse(&url_components, url.c_str());

    if(url_components.psz_path)
        path = url_components.psz_path;
    if(url_components.psz_option)
    {
        path += "?";
        path += url_components.psz_option;
    }
    port = url_components.i_port ? url_components.i_port :
                         ((scheme == "https") ? 443 : 80);
    if(url_components.psz_host)
        hostname = url_components.psz_host;

    vlc_UrlClean(&url_components);

    if(path.empty() || hostname.empty())
        return false;

    return true;
}

bool HTTPChunkSource::hasMoreData() const
{
    if(eof)
        return false;
    else if(contentLength)
        return consumed < contentLength;
    else return true;
}

block_t * HTTPChunkSource::read(size_t readsize)
{
    if(!prepare())
    {
        eof = true;
        return NULL;
    }

    if(consumed == contentLength && consumed > 0)
    {
        eof = true;
        return NULL;
    }

    if(contentLength && readsize > contentLength - consumed)
        readsize = contentLength - consumed;

    block_t *p_block = block_Alloc(readsize);
    if(!p_block)
    {
        eof = true;
        return NULL;
    }

    mtime_t time = mdate();
    ssize_t ret = connection->read(p_block->p_buffer, readsize);
    time = mdate() - time;
    if(ret < 0)
    {
        block_Release(p_block);
        p_block = NULL;
        eof = true;
    }
    else
    {
        p_block->i_buffer = (size_t) ret;
        consumed += p_block->i_buffer;
        if((size_t)ret < readsize)
            eof = true;
        connManager->updateDownloadRate(p_block->i_buffer, time);
    }

    return p_block;
}

bool HTTPChunkSource::prepare()
{
    if(prepared)
        return true;

    if(!connManager)
        return false;

    if(!connection)
    {
        connection = connManager->getConnection(scheme, hostname, port);
        if(!connection)
            return false;
    }

    if( connection->query(path, bytesRange) != VLC_SUCCESS )
        return false;
    /* Because we don't know Chunk size at start, we need to get size
           from content length */
    contentLength = connection->getContentLength();
    prepared = true;

    return true;
}

block_t * HTTPChunkSource::readBlock()
{
    return read(HTTPChunkSource::CHUNK_SIZE);
}

HTTPChunkBufferedSource::HTTPChunkBufferedSource(const std::string& url, HTTPConnectionManager *manager) :
    HTTPChunkSource(url, manager),
    p_head     (NULL),
    pp_tail    (&p_head),
    buffered     (0)
{
    vlc_mutex_init(&lock);
    vlc_cond_init(&avail);
    done = false;
    eof = false;
    downloadstart = 0;
}

HTTPChunkBufferedSource::~HTTPChunkBufferedSource()
{
    vlc_mutex_lock(&lock);
    if(p_head)
    {
        block_ChainRelease(p_head);
        p_head = NULL;
        pp_tail = &p_head;
    }
    done = true;
    buffered = 0;
    vlc_mutex_unlock(&lock);

    connManager->downloader->cancel(this);

    vlc_cond_destroy(&avail);
    vlc_mutex_destroy(&lock);
}

bool HTTPChunkBufferedSource::isDone() const
{
    bool b_done;
    vlc_mutex_lock(const_cast<vlc_mutex_t *>(&lock));
    b_done = done;
    vlc_mutex_unlock(const_cast<vlc_mutex_t *>(&lock));
    return b_done;
}

void HTTPChunkBufferedSource::bufferize(size_t readsize)
{
    vlc_mutex_lock(&lock);
    if(!prepare())
    {
        done = true;
        vlc_cond_signal(&avail);
        vlc_mutex_unlock(&lock);
        return;
    }

    if(readsize < HTTPChunkSource::CHUNK_SIZE)
        readsize = HTTPChunkSource::CHUNK_SIZE;

    if(contentLength && readsize > contentLength - buffered)
        readsize = contentLength - buffered;

    vlc_mutex_unlock(&lock);

    block_t *p_block = block_Alloc(readsize);
    if(!p_block)
        return;

    struct
    {
        size_t size;
        mtime_t time;
    } rate = {0,0};

    ssize_t ret = connection->read(p_block->p_buffer, readsize);
    if(ret <= 0)
    {
        block_Release(p_block);
        vlc_mutex_lock(&lock);
        done = true;
        rate.size = buffered + consumed;
        rate.time = mdate() - downloadstart;
        downloadstart = 0;
        vlc_mutex_unlock(&lock);
    }
    else
    {
        p_block->i_buffer = (size_t) ret;
        vlc_mutex_lock(&lock);
        buffered += p_block->i_buffer;
        block_ChainLastAppend(&pp_tail, p_block);
        if((size_t) ret < readsize)
        {
            done = true;
            rate.size = buffered + consumed;
            rate.time = mdate() - downloadstart;
            downloadstart = 0;
        }
        vlc_mutex_unlock(&lock);
    }

    if(rate.size)
    {
        connManager->updateDownloadRate(rate.size, rate.time);
    }

    vlc_cond_signal(&avail);
}

bool HTTPChunkBufferedSource::prepare()
{
    if(!prepared)
    {
        downloadstart = mdate();
        return HTTPChunkSource::prepare();
    }
    return true;
}

bool HTTPChunkBufferedSource::hasMoreData() const
{
    bool b_hasdata;
    vlc_mutex_lock(const_cast<vlc_mutex_t *>(&lock));
    b_hasdata = !eof;
    vlc_mutex_unlock(const_cast<vlc_mutex_t *>(&lock));
    return b_hasdata;
}

block_t * HTTPChunkBufferedSource::readBlock()
{
    block_t *p_block = NULL;

    vlc_mutex_lock(&lock);

    while(!p_head && !done)
        vlc_cond_wait(&avail, &lock);

    if(!p_head && done)
    {
        if(!eof)
            p_block = block_Alloc(0);
        eof = true;
        vlc_mutex_unlock(&lock);
        return p_block;
    }

    /* dequeue */
    p_block = p_head;
    p_head = p_head->p_next;
    if(p_head == NULL)
    {
        pp_tail = &p_head;
        if(done)
            eof = true;
    }
    p_block->p_next = NULL;

    consumed += p_block->i_buffer;
    buffered -= p_block->i_buffer;

    vlc_mutex_unlock(&lock);

    return p_block;
}

block_t * HTTPChunkBufferedSource::read(size_t readsize)
{
    vlc_mutex_lock(&lock);

    while(readsize > buffered && !done)
        vlc_cond_wait(&avail, &lock);

    block_t *p_block = NULL;
    if(!readsize || !buffered || !(p_block = block_Alloc(readsize)) )
    {
        eof = true;
        vlc_mutex_unlock(&lock);
        return NULL;
    }

    size_t copied = 0;
    while(buffered && readsize)
    {
        const size_t toconsume = std::min(p_head->i_buffer, readsize);
        memcpy(&p_block->p_buffer[copied], p_head->p_buffer, toconsume);
        copied += toconsume;
        readsize -= toconsume;
        buffered -= toconsume;
        p_head->i_buffer -= toconsume;
        p_head->p_buffer += toconsume;
        if(p_head->i_buffer == 0)
        {
            block_t *next = p_head->p_next;
            p_head->p_next = NULL;
            block_Release(p_head);
            p_head = next;
            if(next == NULL)
                pp_tail = &p_head;
        }
    }

    consumed += copied;
    p_block->i_buffer = copied;

    if(copied < readsize)
        eof = true;

    vlc_mutex_unlock(&lock);

    return p_block;
}

HTTPChunk::HTTPChunk(const std::string &url, HTTPConnectionManager *manager):
    AbstractChunk(new HTTPChunkSource(url, manager))
{

}

HTTPChunk::~HTTPChunk()
{

}
<commit_msg>Chunk: we need <algorithm> for std::min()<commit_after>/*
 * Chunk.cpp
 *****************************************************************************
 * Copyright (C) 2010 - 2011 Klagenfurt University
 *
 * Created on: Aug 10, 2010
 * Authors: Christopher Mueller <christopher.mueller@itec.uni-klu.ac.at>
 *          Christian Timmerer  <christian.timmerer@itec.uni-klu.ac.at>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "Chunk.h"
#include "HTTPConnection.hpp"
#include "HTTPConnectionManager.h"
#include "Downloader.hpp"

#include <vlc_common.h>
#include <vlc_url.h>
#include <vlc_block.h>

#include <algorithm>

using namespace adaptative::http;

AbstractChunkSource::AbstractChunkSource()
{
    contentLength = 0;
}

AbstractChunkSource::~AbstractChunkSource()
{

}

void AbstractChunkSource::setBytesRange(const BytesRange &range)
{
    bytesRange = range;
    if(bytesRange.isValid() && bytesRange.getEndByte())
        contentLength = bytesRange.getEndByte() - bytesRange.getStartByte();
}

const BytesRange & AbstractChunkSource::getBytesRange() const
{
    return bytesRange;
}

AbstractChunk::AbstractChunk(AbstractChunkSource *source_)
{
    bytesRead = 0;
    source = source_;
}

AbstractChunk::~AbstractChunk()
{
    delete source;
}

size_t AbstractChunk::getBytesRead() const
{
    return this->bytesRead;
}

block_t * AbstractChunk::doRead(size_t size, bool b_block)
{
    if(!source)
        return NULL;

    block_t *block = (b_block) ? source->readBlock() : source->read(size);
    if(block)
    {
        if(bytesRead == 0)
            block->i_flags |= BLOCK_FLAG_HEADER;
        bytesRead += block->i_buffer;
        onDownload(&block);
        block->i_flags ^= BLOCK_FLAG_HEADER;
    }

    return block;
}

bool AbstractChunk::isEmpty() const
{
    return !source->hasMoreData();
}

block_t * AbstractChunk::readBlock()
{
    return doRead(0, true);
}

block_t * AbstractChunk::read(size_t size)
{
    return doRead(size, false);
}

HTTPChunkSource::HTTPChunkSource(const std::string& url, HTTPConnectionManager *manager) :
    AbstractChunkSource(),
    connection   (NULL),
    connManager  (manager),
    consumed     (0),
    port         (0)
{
    prepared = false;
    eof = false;
    if(!init(url))
        throw VLC_EGENERIC;
}

HTTPChunkSource::~HTTPChunkSource()
{
    if(connection)
        connection->setUsed(false);
}

bool HTTPChunkSource::init(const std::string &url)
{
    this->url = url;

    std::size_t pos = url.find("://");
    if(pos != std::string::npos)
    {
        scheme = url.substr(0, pos);
    }

    if(scheme != "http" && scheme != "https")
        return false;

    vlc_url_t url_components;
    vlc_UrlParse(&url_components, url.c_str());

    if(url_components.psz_path)
        path = url_components.psz_path;
    if(url_components.psz_option)
    {
        path += "?";
        path += url_components.psz_option;
    }
    port = url_components.i_port ? url_components.i_port :
                         ((scheme == "https") ? 443 : 80);
    if(url_components.psz_host)
        hostname = url_components.psz_host;

    vlc_UrlClean(&url_components);

    if(path.empty() || hostname.empty())
        return false;

    return true;
}

bool HTTPChunkSource::hasMoreData() const
{
    if(eof)
        return false;
    else if(contentLength)
        return consumed < contentLength;
    else return true;
}

block_t * HTTPChunkSource::read(size_t readsize)
{
    if(!prepare())
    {
        eof = true;
        return NULL;
    }

    if(consumed == contentLength && consumed > 0)
    {
        eof = true;
        return NULL;
    }

    if(contentLength && readsize > contentLength - consumed)
        readsize = contentLength - consumed;

    block_t *p_block = block_Alloc(readsize);
    if(!p_block)
    {
        eof = true;
        return NULL;
    }

    mtime_t time = mdate();
    ssize_t ret = connection->read(p_block->p_buffer, readsize);
    time = mdate() - time;
    if(ret < 0)
    {
        block_Release(p_block);
        p_block = NULL;
        eof = true;
    }
    else
    {
        p_block->i_buffer = (size_t) ret;
        consumed += p_block->i_buffer;
        if((size_t)ret < readsize)
            eof = true;
        connManager->updateDownloadRate(p_block->i_buffer, time);
    }

    return p_block;
}

bool HTTPChunkSource::prepare()
{
    if(prepared)
        return true;

    if(!connManager)
        return false;

    if(!connection)
    {
        connection = connManager->getConnection(scheme, hostname, port);
        if(!connection)
            return false;
    }

    if( connection->query(path, bytesRange) != VLC_SUCCESS )
        return false;
    /* Because we don't know Chunk size at start, we need to get size
           from content length */
    contentLength = connection->getContentLength();
    prepared = true;

    return true;
}

block_t * HTTPChunkSource::readBlock()
{
    return read(HTTPChunkSource::CHUNK_SIZE);
}

HTTPChunkBufferedSource::HTTPChunkBufferedSource(const std::string& url, HTTPConnectionManager *manager) :
    HTTPChunkSource(url, manager),
    p_head     (NULL),
    pp_tail    (&p_head),
    buffered     (0)
{
    vlc_mutex_init(&lock);
    vlc_cond_init(&avail);
    done = false;
    eof = false;
    downloadstart = 0;
}

HTTPChunkBufferedSource::~HTTPChunkBufferedSource()
{
    vlc_mutex_lock(&lock);
    if(p_head)
    {
        block_ChainRelease(p_head);
        p_head = NULL;
        pp_tail = &p_head;
    }
    done = true;
    buffered = 0;
    vlc_mutex_unlock(&lock);

    connManager->downloader->cancel(this);

    vlc_cond_destroy(&avail);
    vlc_mutex_destroy(&lock);
}

bool HTTPChunkBufferedSource::isDone() const
{
    bool b_done;
    vlc_mutex_lock(const_cast<vlc_mutex_t *>(&lock));
    b_done = done;
    vlc_mutex_unlock(const_cast<vlc_mutex_t *>(&lock));
    return b_done;
}

void HTTPChunkBufferedSource::bufferize(size_t readsize)
{
    vlc_mutex_lock(&lock);
    if(!prepare())
    {
        done = true;
        vlc_cond_signal(&avail);
        vlc_mutex_unlock(&lock);
        return;
    }

    if(readsize < HTTPChunkSource::CHUNK_SIZE)
        readsize = HTTPChunkSource::CHUNK_SIZE;

    if(contentLength && readsize > contentLength - buffered)
        readsize = contentLength - buffered;

    vlc_mutex_unlock(&lock);

    block_t *p_block = block_Alloc(readsize);
    if(!p_block)
        return;

    struct
    {
        size_t size;
        mtime_t time;
    } rate = {0,0};

    ssize_t ret = connection->read(p_block->p_buffer, readsize);
    if(ret <= 0)
    {
        block_Release(p_block);
        vlc_mutex_lock(&lock);
        done = true;
        rate.size = buffered + consumed;
        rate.time = mdate() - downloadstart;
        downloadstart = 0;
        vlc_mutex_unlock(&lock);
    }
    else
    {
        p_block->i_buffer = (size_t) ret;
        vlc_mutex_lock(&lock);
        buffered += p_block->i_buffer;
        block_ChainLastAppend(&pp_tail, p_block);
        if((size_t) ret < readsize)
        {
            done = true;
            rate.size = buffered + consumed;
            rate.time = mdate() - downloadstart;
            downloadstart = 0;
        }
        vlc_mutex_unlock(&lock);
    }

    if(rate.size)
    {
        connManager->updateDownloadRate(rate.size, rate.time);
    }

    vlc_cond_signal(&avail);
}

bool HTTPChunkBufferedSource::prepare()
{
    if(!prepared)
    {
        downloadstart = mdate();
        return HTTPChunkSource::prepare();
    }
    return true;
}

bool HTTPChunkBufferedSource::hasMoreData() const
{
    bool b_hasdata;
    vlc_mutex_lock(const_cast<vlc_mutex_t *>(&lock));
    b_hasdata = !eof;
    vlc_mutex_unlock(const_cast<vlc_mutex_t *>(&lock));
    return b_hasdata;
}

block_t * HTTPChunkBufferedSource::readBlock()
{
    block_t *p_block = NULL;

    vlc_mutex_lock(&lock);

    while(!p_head && !done)
        vlc_cond_wait(&avail, &lock);

    if(!p_head && done)
    {
        if(!eof)
            p_block = block_Alloc(0);
        eof = true;
        vlc_mutex_unlock(&lock);
        return p_block;
    }

    /* dequeue */
    p_block = p_head;
    p_head = p_head->p_next;
    if(p_head == NULL)
    {
        pp_tail = &p_head;
        if(done)
            eof = true;
    }
    p_block->p_next = NULL;

    consumed += p_block->i_buffer;
    buffered -= p_block->i_buffer;

    vlc_mutex_unlock(&lock);

    return p_block;
}

block_t * HTTPChunkBufferedSource::read(size_t readsize)
{
    vlc_mutex_lock(&lock);

    while(readsize > buffered && !done)
        vlc_cond_wait(&avail, &lock);

    block_t *p_block = NULL;
    if(!readsize || !buffered || !(p_block = block_Alloc(readsize)) )
    {
        eof = true;
        vlc_mutex_unlock(&lock);
        return NULL;
    }

    size_t copied = 0;
    while(buffered && readsize)
    {
        const size_t toconsume = std::min(p_head->i_buffer, readsize);
        memcpy(&p_block->p_buffer[copied], p_head->p_buffer, toconsume);
        copied += toconsume;
        readsize -= toconsume;
        buffered -= toconsume;
        p_head->i_buffer -= toconsume;
        p_head->p_buffer += toconsume;
        if(p_head->i_buffer == 0)
        {
            block_t *next = p_head->p_next;
            p_head->p_next = NULL;
            block_Release(p_head);
            p_head = next;
            if(next == NULL)
                pp_tail = &p_head;
        }
    }

    consumed += copied;
    p_block->i_buffer = copied;

    if(copied < readsize)
        eof = true;

    vlc_mutex_unlock(&lock);

    return p_block;
}

HTTPChunk::HTTPChunk(const std::string &url, HTTPConnectionManager *manager):
    AbstractChunk(new HTTPChunkSource(url, manager))
{

}

HTTPChunk::~HTTPChunk()
{

}
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkottieAnimator.h"

#include "SkCubicMap.h"
#include "SkottieJson.h"
#include "SkottieValue.h"
#include "SkString.h"
#include "SkTArray.h"

#include <memory>

namespace skottie {

namespace {

#define LOG SkDebugf

bool LogFail(const skjson::Value& json, const char* msg) {
    const auto dump = json.toString();
    LOG("!! %s: %s\n", msg, dump.c_str());
    return false;
}

class KeyframeAnimatorBase : public sksg::Animator {
public:
    int count() const { return fRecs.count(); }

protected:
    KeyframeAnimatorBase() = default;

    struct KeyframeRec {
        float t0, t1;
        int   vidx0, vidx1, // v0/v1 indices
              cmidx;        // cubic map index

        bool contains(float t) const { return t0 <= t && t <= t1; }
        bool isConstant() const { return vidx0 == vidx1; }
        bool isValid() const {
            SkASSERT(t0 <= t1);
            // Constant frames don't need/use t1 and vidx1.
            return t0 < t1 || this->isConstant();
        }
    };

    const KeyframeRec& frame(float t) {
        if (!fCachedRec || !fCachedRec->contains(t)) {
            fCachedRec = findFrame(t);
        }
        return *fCachedRec;
    }

    float localT(const KeyframeRec& rec, float t) const {
        SkASSERT(rec.isValid());
        SkASSERT(!rec.isConstant());
        SkASSERT(t > rec.t0 && t < rec.t1);

        auto lt = (t - rec.t0) / (rec.t1 - rec.t0);

        return rec.cmidx < 0
            ? lt
            : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
    }

    virtual int parseValue(const skjson::Value&) = 0;

    void parseKeyFrames(const skjson::ArrayValue& jframes) {
        for (const skjson::ObjectValue* jframe : jframes) {
            if (!jframe) continue;

            float t0;
            if (!Parse<float>((*jframe)["t"], &t0))
                continue;

            if (!fRecs.empty()) {
                if (fRecs.back().t1 >= t0) {
                    LOG("!! Ignoring out-of-order key frame (t:%f < t:%f)\n", t0, fRecs.back().t1);
                    continue;
                }
                // Back-fill t1 in prev interval.  Note: we do this even if we end up discarding
                // the current interval (to support "t"-only final frames).
                fRecs.back().t1 = t0;
            }

            const auto vidx0 = this->parseValue((*jframe)["s"]);
            if (vidx0 < 0)
                continue;

            // Defaults for constant frames.
            int vidx1 = vidx0, cmidx = -1;

            if (!ParseDefault<bool>((*jframe)["h"], false)) {
                // Regular frame, requires an end value.
                vidx1 = this->parseValue((*jframe)["e"]);
                if (vidx1 < 0)
                    continue;

                // default is linear lerp
                static constexpr SkPoint kDefaultC0 = { 0, 0 },
                                         kDefaultC1 = { 1, 1 };
                const auto c0 = ParseDefault<SkPoint>((*jframe)["i"], kDefaultC0),
                           c1 = ParseDefault<SkPoint>((*jframe)["o"], kDefaultC1);

                if (c0 != kDefaultC0 || c1 != kDefaultC1) {
                    // TODO: is it worth de-duping these?
                    cmidx = fCubicMaps.count();
                    fCubicMaps.emplace_back();
                    // TODO: why do we have to plug these inverted?
                    fCubicMaps.back().setPts(c1, c0);
                }
            }

            fRecs.push_back({t0, t0, vidx0, vidx1, cmidx });
        }

        // If we couldn't determine a valid t1 for the last frame, discard it.
        if (!fRecs.empty() && !fRecs.back().isValid()) {
            fRecs.pop_back();
        }

        SkASSERT(fRecs.empty() || fRecs.back().isValid());
    }

private:
    const KeyframeRec* findFrame(float t) const {
        SkASSERT(!fRecs.empty());

        auto f0 = &fRecs.front(),
             f1 = &fRecs.back();

        SkASSERT(f0->isValid());
        SkASSERT(f1->isValid());

        if (t < f0->t0) {
            return f0;
        }

        if (t > f1->t1) {
            return f1;
        }

        while (f0 != f1) {
            SkASSERT(f0 < f1);
            SkASSERT(t >= f0->t0 && t <= f1->t1);

            const auto f = f0 + (f1 - f0) / 2;
            SkASSERT(f->isValid());

            if (t > f->t1) {
                f0 = f + 1;
            } else {
                f1 = f;
            }
        }

        SkASSERT(f0 == f1);
        SkASSERT(f0->contains(t));

        return f0;
    }

    SkTArray<KeyframeRec> fRecs;
    SkTArray<SkCubicMap>  fCubicMaps;
    const KeyframeRec*    fCachedRec = nullptr;

    using INHERITED = sksg::Animator;
};

template <typename T>
class KeyframeAnimator final : public KeyframeAnimatorBase {
public:
    static std::unique_ptr<KeyframeAnimator> Make(const skjson::ArrayValue* jv,
                                                  std::function<void(const T&)>&& apply) {
        if (!jv) return nullptr;

        std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(*jv, std::move(apply)));
        if (!animator->count())
            return nullptr;

        return animator;
    }

protected:
    void onTick(float t) override {
        T val;
        this->eval(this->frame(t), t, &val);

        fApplyFunc(val);
    }

private:
    KeyframeAnimator(const skjson::ArrayValue& jframes,
                     std::function<void(const T&)>&& apply)
        : fApplyFunc(std::move(apply)) {
        this->parseKeyFrames(jframes);
    }

    int parseValue(const skjson::Value& jv) override {
        T val;
        if (!Parse<T>(jv, &val) || (!fVs.empty() &&
                ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) {
            return -1;
        }

        // TODO: full deduping?
        if (fVs.empty() || val != fVs.back()) {
            fVs.push_back(std::move(val));
        }
        return fVs.count() - 1;
    }

    void eval(const KeyframeRec& rec, float t, T* v) const {
        SkASSERT(rec.isValid());
        if (rec.isConstant() || t <= rec.t0) {
            *v = fVs[rec.vidx0];
        } else if (t >= rec.t1) {
            *v = fVs[rec.vidx1];
        } else {
            const auto lt = this->localT(rec, t);
            const auto& v0 = fVs[rec.vidx0];
            const auto& v1 = fVs[rec.vidx1];
            *v = ValueTraits<T>::Lerp(v0, v1, lt);
        }
    }

    const std::function<void(const T&)> fApplyFunc;
    SkTArray<T>                         fVs;

    using INHERITED = KeyframeAnimatorBase;
};

template <typename T>
static inline bool BindPropertyImpl(const skjson::ObjectValue* jprop,
                                    sksg::AnimatorList* animators,
                                    std::function<void(const T&)>&& apply,
                                    const T* noop = nullptr) {
    if (!jprop) return false;

    const auto& jpropA = (*jprop)["a"];
    const auto& jpropK = (*jprop)["k"];

    // Older Json versions don't have an "a" animation marker.
    // For those, we attempt to parse both ways.
    if (!ParseDefault<bool>(jpropA, false)) {
        T val;
        if (Parse<T>(jpropK, &val)) {
            // Static property.
            if (noop && val == *noop)
                return false;

            apply(val);
            return true;
        }

        if (!jpropA.is<skjson::NullValue>()) {
            return LogFail(*jprop, "Could not parse (explicit) static property");
        }
    }

    // Keyframe property.
    auto animator = KeyframeAnimator<T>::Make(jpropK, std::move(apply));

    if (!animator) {
        return LogFail(*jprop, "Could not parse keyframed property");
    }

    animators->push_back(std::move(animator));

    return true;
}

class SplitPointAnimator final : public sksg::Animator {
public:
    static std::unique_ptr<SplitPointAnimator> Make(const skjson::ObjectValue* jprop,
                                                    std::function<void(const VectorValue&)>&& apply,
                                                    const VectorValue*) {
        if (!jprop) return nullptr;

        std::unique_ptr<SplitPointAnimator> split_animator(
            new SplitPointAnimator(std::move(apply)));

        // This raw pointer is captured in lambdas below. But the lambdas are owned by
        // the object itself, so the scope is bound to the life time of the object.
        auto* split_animator_ptr = split_animator.get();

        if (!BindPropertyImpl<ScalarValue>((*jprop)["x"], &split_animator->fAnimators,
                [split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) ||
            !BindPropertyImpl<ScalarValue>((*jprop)["y"], &split_animator->fAnimators,
                [split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) {
            LogFail(*jprop, "Could not parse split property");
            return nullptr;
        }

        if (split_animator->fAnimators.empty()) {
            // Static split property, no need to hold on to the split animator.
            return nullptr;
        }

        return split_animator;
    }

    void onTick(float t) override {
        for (const auto& animator : fAnimators) {
            animator->tick(t);
        }

        const VectorValue vec = { fX, fY };
        fApplyFunc(vec);
    }

    void setX(const ScalarValue& x) { fX = x; }
    void setY(const ScalarValue& y) { fY = y; }

private:
    explicit SplitPointAnimator(std::function<void(const VectorValue&)>&& apply)
        : fApplyFunc(std::move(apply)) {}

    const std::function<void(const VectorValue&)> fApplyFunc;
    sksg::AnimatorList                            fAnimators;

    ScalarValue                                   fX = 0,
                                                  fY = 0;

    using INHERITED = sksg::Animator;
};

bool BindSplitPositionProperty(const skjson::Value& jv,
                               sksg::AnimatorList* animators,
                               std::function<void(const VectorValue&)>&& apply,
                               const VectorValue* noop) {
    if (auto split_animator = SplitPointAnimator::Make(jv, std::move(apply), noop)) {
        animators->push_back(std::unique_ptr<sksg::Animator>(split_animator.release()));
        return true;
    }

    return false;
}

} // namespace

template <>
bool BindProperty(const skjson::Value& jv,
                  sksg::AnimatorList* animators,
                  std::function<void(const ScalarValue&)>&& apply,
                  const ScalarValue* noop) {
    return BindPropertyImpl(jv, animators, std::move(apply), noop);
}

template <>
bool BindProperty(const skjson::Value& jv,
                  sksg::AnimatorList* animators,
                  std::function<void(const VectorValue&)>&& apply,
                  const VectorValue* noop) {
    if (!jv.is<skjson::ObjectValue>())
        return false;

    return ParseDefault<bool>(jv.as<skjson::ObjectValue>()["s"], false)
        ? BindSplitPositionProperty(jv, animators, std::move(apply), noop)
        : BindPropertyImpl(jv, animators, std::move(apply), noop);
}

template <>
bool BindProperty(const skjson::Value& jv,
                  sksg::AnimatorList* animators,
                  std::function<void(const ShapeValue&)>&& apply,
                  const ShapeValue* noop) {
    return BindPropertyImpl(jv, animators, std::move(apply), noop);
}

} // namespace skottie
<commit_msg>[skottie] Ensure static split-position properties are committed<commit_after>/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkottieAnimator.h"

#include "SkCubicMap.h"
#include "SkottieJson.h"
#include "SkottieValue.h"
#include "SkString.h"
#include "SkTArray.h"

#include <memory>

namespace skottie {

namespace {

#define LOG SkDebugf

bool LogFail(const skjson::Value& json, const char* msg) {
    const auto dump = json.toString();
    LOG("!! %s: %s\n", msg, dump.c_str());
    return false;
}

class KeyframeAnimatorBase : public sksg::Animator {
public:
    int count() const { return fRecs.count(); }

protected:
    KeyframeAnimatorBase() = default;

    struct KeyframeRec {
        float t0, t1;
        int   vidx0, vidx1, // v0/v1 indices
              cmidx;        // cubic map index

        bool contains(float t) const { return t0 <= t && t <= t1; }
        bool isConstant() const { return vidx0 == vidx1; }
        bool isValid() const {
            SkASSERT(t0 <= t1);
            // Constant frames don't need/use t1 and vidx1.
            return t0 < t1 || this->isConstant();
        }
    };

    const KeyframeRec& frame(float t) {
        if (!fCachedRec || !fCachedRec->contains(t)) {
            fCachedRec = findFrame(t);
        }
        return *fCachedRec;
    }

    float localT(const KeyframeRec& rec, float t) const {
        SkASSERT(rec.isValid());
        SkASSERT(!rec.isConstant());
        SkASSERT(t > rec.t0 && t < rec.t1);

        auto lt = (t - rec.t0) / (rec.t1 - rec.t0);

        return rec.cmidx < 0
            ? lt
            : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
    }

    virtual int parseValue(const skjson::Value&) = 0;

    void parseKeyFrames(const skjson::ArrayValue& jframes) {
        for (const skjson::ObjectValue* jframe : jframes) {
            if (!jframe) continue;

            float t0;
            if (!Parse<float>((*jframe)["t"], &t0))
                continue;

            if (!fRecs.empty()) {
                if (fRecs.back().t1 >= t0) {
                    LOG("!! Ignoring out-of-order key frame (t:%f < t:%f)\n", t0, fRecs.back().t1);
                    continue;
                }
                // Back-fill t1 in prev interval.  Note: we do this even if we end up discarding
                // the current interval (to support "t"-only final frames).
                fRecs.back().t1 = t0;
            }

            const auto vidx0 = this->parseValue((*jframe)["s"]);
            if (vidx0 < 0)
                continue;

            // Defaults for constant frames.
            int vidx1 = vidx0, cmidx = -1;

            if (!ParseDefault<bool>((*jframe)["h"], false)) {
                // Regular frame, requires an end value.
                vidx1 = this->parseValue((*jframe)["e"]);
                if (vidx1 < 0)
                    continue;

                // default is linear lerp
                static constexpr SkPoint kDefaultC0 = { 0, 0 },
                                         kDefaultC1 = { 1, 1 };
                const auto c0 = ParseDefault<SkPoint>((*jframe)["i"], kDefaultC0),
                           c1 = ParseDefault<SkPoint>((*jframe)["o"], kDefaultC1);

                if (c0 != kDefaultC0 || c1 != kDefaultC1) {
                    // TODO: is it worth de-duping these?
                    cmidx = fCubicMaps.count();
                    fCubicMaps.emplace_back();
                    // TODO: why do we have to plug these inverted?
                    fCubicMaps.back().setPts(c1, c0);
                }
            }

            fRecs.push_back({t0, t0, vidx0, vidx1, cmidx });
        }

        // If we couldn't determine a valid t1 for the last frame, discard it.
        if (!fRecs.empty() && !fRecs.back().isValid()) {
            fRecs.pop_back();
        }

        SkASSERT(fRecs.empty() || fRecs.back().isValid());
    }

private:
    const KeyframeRec* findFrame(float t) const {
        SkASSERT(!fRecs.empty());

        auto f0 = &fRecs.front(),
             f1 = &fRecs.back();

        SkASSERT(f0->isValid());
        SkASSERT(f1->isValid());

        if (t < f0->t0) {
            return f0;
        }

        if (t > f1->t1) {
            return f1;
        }

        while (f0 != f1) {
            SkASSERT(f0 < f1);
            SkASSERT(t >= f0->t0 && t <= f1->t1);

            const auto f = f0 + (f1 - f0) / 2;
            SkASSERT(f->isValid());

            if (t > f->t1) {
                f0 = f + 1;
            } else {
                f1 = f;
            }
        }

        SkASSERT(f0 == f1);
        SkASSERT(f0->contains(t));

        return f0;
    }

    SkTArray<KeyframeRec> fRecs;
    SkTArray<SkCubicMap>  fCubicMaps;
    const KeyframeRec*    fCachedRec = nullptr;

    using INHERITED = sksg::Animator;
};

template <typename T>
class KeyframeAnimator final : public KeyframeAnimatorBase {
public:
    static std::unique_ptr<KeyframeAnimator> Make(const skjson::ArrayValue* jv,
                                                  std::function<void(const T&)>&& apply) {
        if (!jv) return nullptr;

        std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(*jv, std::move(apply)));
        if (!animator->count())
            return nullptr;

        return animator;
    }

protected:
    void onTick(float t) override {
        T val;
        this->eval(this->frame(t), t, &val);

        fApplyFunc(val);
    }

private:
    KeyframeAnimator(const skjson::ArrayValue& jframes,
                     std::function<void(const T&)>&& apply)
        : fApplyFunc(std::move(apply)) {
        this->parseKeyFrames(jframes);
    }

    int parseValue(const skjson::Value& jv) override {
        T val;
        if (!Parse<T>(jv, &val) || (!fVs.empty() &&
                ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) {
            return -1;
        }

        // TODO: full deduping?
        if (fVs.empty() || val != fVs.back()) {
            fVs.push_back(std::move(val));
        }
        return fVs.count() - 1;
    }

    void eval(const KeyframeRec& rec, float t, T* v) const {
        SkASSERT(rec.isValid());
        if (rec.isConstant() || t <= rec.t0) {
            *v = fVs[rec.vidx0];
        } else if (t >= rec.t1) {
            *v = fVs[rec.vidx1];
        } else {
            const auto lt = this->localT(rec, t);
            const auto& v0 = fVs[rec.vidx0];
            const auto& v1 = fVs[rec.vidx1];
            *v = ValueTraits<T>::Lerp(v0, v1, lt);
        }
    }

    const std::function<void(const T&)> fApplyFunc;
    SkTArray<T>                         fVs;

    using INHERITED = KeyframeAnimatorBase;
};

template <typename T>
static inline bool BindPropertyImpl(const skjson::ObjectValue* jprop,
                                    sksg::AnimatorList* animators,
                                    std::function<void(const T&)>&& apply,
                                    const T* noop = nullptr) {
    if (!jprop) return false;

    const auto& jpropA = (*jprop)["a"];
    const auto& jpropK = (*jprop)["k"];

    // Older Json versions don't have an "a" animation marker.
    // For those, we attempt to parse both ways.
    if (!ParseDefault<bool>(jpropA, false)) {
        T val;
        if (Parse<T>(jpropK, &val)) {
            // Static property.
            if (noop && val == *noop)
                return false;

            apply(val);
            return true;
        }

        if (!jpropA.is<skjson::NullValue>()) {
            return LogFail(*jprop, "Could not parse (explicit) static property");
        }
    }

    // Keyframe property.
    auto animator = KeyframeAnimator<T>::Make(jpropK, std::move(apply));

    if (!animator) {
        return LogFail(*jprop, "Could not parse keyframed property");
    }

    animators->push_back(std::move(animator));

    return true;
}

class SplitPointAnimator final : public sksg::Animator {
public:
    static std::unique_ptr<SplitPointAnimator> Make(const skjson::ObjectValue* jprop,
                                                    std::function<void(const VectorValue&)>&& apply,
                                                    const VectorValue*) {
        if (!jprop) return nullptr;

        std::unique_ptr<SplitPointAnimator> split_animator(
            new SplitPointAnimator(std::move(apply)));

        // This raw pointer is captured in lambdas below. But the lambdas are owned by
        // the object itself, so the scope is bound to the life time of the object.
        auto* split_animator_ptr = split_animator.get();

        if (!BindPropertyImpl<ScalarValue>((*jprop)["x"], &split_animator->fAnimators,
                [split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) ||
            !BindPropertyImpl<ScalarValue>((*jprop)["y"], &split_animator->fAnimators,
                [split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) {
            LogFail(*jprop, "Could not parse split property");
            return nullptr;
        }

        if (split_animator->fAnimators.empty()) {
            // Static split property: commit the (buffered) value and discard.
            split_animator->onTick(0);
            return nullptr;
        }

        return split_animator;
    }

    void onTick(float t) override {
        for (const auto& animator : fAnimators) {
            animator->tick(t);
        }

        const VectorValue vec = { fX, fY };
        fApplyFunc(vec);
    }

    void setX(const ScalarValue& x) { fX = x; }
    void setY(const ScalarValue& y) { fY = y; }

private:
    explicit SplitPointAnimator(std::function<void(const VectorValue&)>&& apply)
        : fApplyFunc(std::move(apply)) {}

    const std::function<void(const VectorValue&)> fApplyFunc;
    sksg::AnimatorList                            fAnimators;

    ScalarValue                                   fX = 0,
                                                  fY = 0;

    using INHERITED = sksg::Animator;
};

bool BindSplitPositionProperty(const skjson::Value& jv,
                               sksg::AnimatorList* animators,
                               std::function<void(const VectorValue&)>&& apply,
                               const VectorValue* noop) {
    if (auto split_animator = SplitPointAnimator::Make(jv, std::move(apply), noop)) {
        animators->push_back(std::unique_ptr<sksg::Animator>(split_animator.release()));
        return true;
    }

    return false;
}

} // namespace

template <>
bool BindProperty(const skjson::Value& jv,
                  sksg::AnimatorList* animators,
                  std::function<void(const ScalarValue&)>&& apply,
                  const ScalarValue* noop) {
    return BindPropertyImpl(jv, animators, std::move(apply), noop);
}

template <>
bool BindProperty(const skjson::Value& jv,
                  sksg::AnimatorList* animators,
                  std::function<void(const VectorValue&)>&& apply,
                  const VectorValue* noop) {
    if (!jv.is<skjson::ObjectValue>())
        return false;

    return ParseDefault<bool>(jv.as<skjson::ObjectValue>()["s"], false)
        ? BindSplitPositionProperty(jv, animators, std::move(apply), noop)
        : BindPropertyImpl(jv, animators, std::move(apply), noop);
}

template <>
bool BindProperty(const skjson::Value& jv,
                  sksg::AnimatorList* animators,
                  std::function<void(const ShapeValue&)>&& apply,
                  const ShapeValue* noop) {
    return BindPropertyImpl(jv, animators, std::move(apply), noop);
}

} // namespace skottie
<|endoftext|>