268 lines
10 KiB
C++
268 lines
10 KiB
C++
/*
|
|
* Copyright 2017-2018 Amazon.com, Inc. or its affiliates. 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.
|
|
* A copy of the License is located at
|
|
*
|
|
* http://aws.amazon.com/apache2.0/
|
|
*
|
|
* or in the "license" file accompanying this file. This file 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.
|
|
*/
|
|
|
|
/// @file MimeParserFuzzTest.cpp
|
|
|
|
#include <memory>
|
|
#include <random>
|
|
#include <thread>
|
|
|
|
#include <curl/curl.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "ACL/Transport/MessageConsumerInterface.h"
|
|
#include "ACL/Transport/HTTP2Stream.h"
|
|
|
|
#include <AVSCommon/AVS/Attachment/AttachmentReader.h>
|
|
#include <AVSCommon/AVS/Attachment/AttachmentWriter.h>
|
|
#include "AVSCommon/AVS/Attachment/InProcessAttachment.h"
|
|
#include <AVSCommon/SDKInterfaces/MessageObserverInterface.h>
|
|
#include <AVSCommon/Utils/Logger/Logger.h>
|
|
|
|
#include "Common/TestableAttachmentManager.h"
|
|
#include "Common/Common.h"
|
|
#include "Common/MimeUtils.h"
|
|
#include "Common/TestableMessageObserver.h"
|
|
|
|
#include "MockMessageRequest.h"
|
|
#include "TestableConsumer.h"
|
|
|
|
namespace alexaClientSDK {
|
|
namespace acl {
|
|
namespace test {
|
|
|
|
using namespace avsCommon::sdkInterfaces;
|
|
using namespace avsCommon::avs::attachment;
|
|
|
|
/// String to identify log entries originating from this file.
|
|
static const std::string TAG("MimeParserFuzzTest");
|
|
|
|
/**
|
|
* Create a LogEntry using this file's TAG and the specified event string.
|
|
*
|
|
* @param The event string for this @c LogEntry.
|
|
*/
|
|
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
|
|
|
|
/// The number of feed() calls to be made in @c MimeParserFuzzTest::feed()
|
|
static const int FEED_COUNT = 0x100;
|
|
/// Max pseudo-random buffer size to pass to @c feed() (large enough to assure we trigger attachment buffer full)
|
|
static const size_t MAX_FEED_SIZE = (InProcessAttachment::SDS_BUFFER_DEFAULT_SIZE_IN_BYTES / FEED_COUNT) * 4;
|
|
/// Max buffer size to read from the attachment reader (small enough trigger multiple reads per feed)
|
|
static const size_t MAX_READ_SIZE = MAX_FEED_SIZE / 8;
|
|
/// Consistent seed (for repeatable tests) with which to generate pseudo-random bytes.
|
|
static const unsigned int BYTES_SEED = 1;
|
|
/// Consistent seed (for repeatable tests) with which to generate pseudo-random feed sizes.
|
|
static const unsigned int FEED_SIZE_SEED = 2;
|
|
/// Consistent but different seed (for repeatable tests) with which to generate pseudo-random read sizes.
|
|
static const unsigned int READ_SIZE_SEED = FEED_SIZE_SEED + 1;
|
|
/// Content ID for mime part.
|
|
static const std::string CONTENT_ID = "CONTENT_ID";
|
|
/// Context ID for generating attachment ID.
|
|
static const std::string CONTEXT_ID = "CONTEXT_ID";
|
|
/// CR,LF to separate lines in mime headers and boundaries.
|
|
static const std::string CRLF = "\r\n";
|
|
/// Dashes used as a prefix or suffix to mime boundaries.
|
|
static const std::string DASHES = "--";
|
|
/// Mime Boundary string (without CR,LF or dashes).
|
|
static const std::string BOUNDARY = "BoundaryBoundaryBoundaryBoundaryBoundaryBoundaryBoundary";
|
|
/// Mime prefix to our attachment.
|
|
static const std::string ATTACHMENT_PREFIX = CRLF + DASHES + BOUNDARY + CRLF + "Content-ID: " + CONTENT_ID + CRLF +
|
|
"Content-Type: application/octet-stream" + CRLF + CRLF;
|
|
/// Mime suffix terminating our attachment.
|
|
static const std::string ATTACHMENT_SUFFIX = CRLF + DASHES + BOUNDARY + DASHES;
|
|
|
|
/**
|
|
* Class to generate consistent pseudo-random byte stream.
|
|
*/
|
|
class ByteGenerator {
|
|
public:
|
|
ByteGenerator();
|
|
|
|
/**
|
|
* Generate the next set of bytes in the stream.
|
|
*
|
|
* @param[out] buffer The buffer in which to place the generated bytes.
|
|
* @param size The number of bytes to generate.
|
|
*/
|
|
void generateBytes(std::vector<uint8_t>* buffer, size_t size);
|
|
|
|
private:
|
|
/// The underlying random byte generator.
|
|
std::independent_bits_engine<std::minstd_rand, CHAR_BIT, uint8_t> m_engine;
|
|
};
|
|
|
|
ByteGenerator::ByteGenerator() {
|
|
m_engine.seed(BYTES_SEED);
|
|
}
|
|
|
|
void ByteGenerator::generateBytes(std::vector<uint8_t>* buffer, size_t size) {
|
|
for (size_t ix = 0; ix < size; ix++) {
|
|
(*buffer)[ix] = m_engine();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Our GTest class.
|
|
*/
|
|
class MimeParserFuzzTest : public ::testing::Test {
|
|
public:
|
|
/**
|
|
* Construct the objects we will use across tests.
|
|
*/
|
|
void SetUp() override;
|
|
|
|
/**
|
|
* Feed a pseudo-random stream of bytes to the mime parser, making a number of
|
|
* calls with pseudo-random sizes.
|
|
*/
|
|
void feed();
|
|
|
|
/**
|
|
* Read the attachment the mime parser is rendering to, requesting a pseudo-random
|
|
* number of bytes with each read.
|
|
*/
|
|
void read();
|
|
|
|
/// The AttachmentManager.
|
|
std::shared_ptr<AttachmentManager> m_attachmentManager;
|
|
/// The MimeParser which we will be primarily testing.
|
|
std::shared_ptr<MimeParser> m_parser;
|
|
/// Flag to indicate the test has failed and loops should exit.
|
|
std::atomic<bool> m_failed;
|
|
};
|
|
|
|
void MimeParserFuzzTest::SetUp() {
|
|
m_attachmentManager = std::make_shared<AttachmentManager>(AttachmentManager::AttachmentType::IN_PROCESS);
|
|
auto testableConsumer = std::make_shared<TestableConsumer>();
|
|
testableConsumer->setMessageObserver(std::make_shared<TestableMessageObserver>());
|
|
m_parser = std::make_shared<MimeParser>(testableConsumer, m_attachmentManager);
|
|
m_parser->setAttachmentContextId(CONTEXT_ID);
|
|
m_parser->setBoundaryString(BOUNDARY);
|
|
m_failed = false;
|
|
}
|
|
|
|
void MimeParserFuzzTest::feed() {
|
|
m_parser->feed(const_cast<char*>(ATTACHMENT_PREFIX.c_str()), ATTACHMENT_PREFIX.size());
|
|
|
|
ByteGenerator feedBytesSource;
|
|
|
|
std::default_random_engine feedSizeGenerator;
|
|
feedSizeGenerator.seed(FEED_SIZE_SEED);
|
|
std::uniform_int_distribution<int> feedSizeDistribution(1, MAX_FEED_SIZE);
|
|
auto feedSizeSource = std::bind(feedSizeDistribution, feedSizeGenerator);
|
|
|
|
std::vector<uint8_t> feedBuffer(MAX_FEED_SIZE);
|
|
|
|
size_t totalBytesFed = 0;
|
|
int incompleteCount = 0;
|
|
for (int ix = 0; !m_failed && ix < FEED_COUNT; ix++) {
|
|
auto feedSize = feedSizeSource();
|
|
feedBytesSource.generateBytes(&feedBuffer, feedSize);
|
|
while (true) {
|
|
ACSDK_DEBUG9(LX("callingFeed").d("totalBytesFed", totalBytesFed).d("feedSize", feedSize));
|
|
auto status = m_parser->feed((char*)(&feedBuffer[0]), feedSize);
|
|
if (MimeParser::DataParsedStatus::OK == status) {
|
|
totalBytesFed += feedSize;
|
|
break;
|
|
} else if (MimeParser::DataParsedStatus::INCOMPLETE == status) {
|
|
ACSDK_DEBUG9(LX("feedIncomplete").d("action", "waitAndReDrive"));
|
|
++incompleteCount;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
} else {
|
|
ASSERT_NE(status, status);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_parser->feed(const_cast<char*>(ATTACHMENT_SUFFIX.c_str()), ATTACHMENT_SUFFIX.size());
|
|
|
|
// Make sure there were enough INCOMPLETE return statuses.
|
|
ACSDK_DEBUG9(LX("doneFeeding").d("incompleteCount", incompleteCount).d("FEED_COUNT", FEED_COUNT));
|
|
ASSERT_GT(incompleteCount, FEED_COUNT);
|
|
}
|
|
|
|
void MimeParserFuzzTest::read() {
|
|
ByteGenerator verifyBytesSource;
|
|
|
|
std::default_random_engine readSizeGenerator;
|
|
readSizeGenerator.seed(READ_SIZE_SEED);
|
|
std::uniform_int_distribution<int> readSizeDistribution(1, MAX_READ_SIZE);
|
|
auto readSizeSource = std::bind(readSizeDistribution, readSizeGenerator);
|
|
|
|
auto attachmentId = m_attachmentManager->generateAttachmentId(CONTEXT_ID, CONTENT_ID);
|
|
auto reader = m_attachmentManager->createReader(attachmentId, avsCommon::utils::sds::ReaderPolicy::BLOCKING);
|
|
|
|
std::vector<uint8_t> readBuffer(MAX_READ_SIZE);
|
|
std::vector<uint8_t> verifyBuffer(MAX_READ_SIZE);
|
|
size_t totalBytesRead = 0;
|
|
|
|
while (true) {
|
|
// Delay reads so that AnotherMimeParserTest::feed() blocks on a full attachment buffer
|
|
// in a reliable way. This makes the test results consistent run to run.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
|
auto readSizeIn = readSizeSource();
|
|
auto readStatus = AttachmentReader::ReadStatus::OK;
|
|
ACSDK_DEBUG9(LX("callingRead").d("totalBytesRead", totalBytesRead).d("readSizeIn", readSizeIn));
|
|
auto readSizeOut = reader->read(readBuffer.data(), readSizeIn, &readStatus);
|
|
if (readSizeOut != 0) {
|
|
ACSDK_DEBUG9(LX("readReturned").d("readSizeOut", readSizeOut));
|
|
verifyBytesSource.generateBytes(&verifyBuffer, readSizeOut);
|
|
for (size_t ix = 0; ix < readSizeOut; ix++) {
|
|
auto good = readBuffer[ix] == verifyBuffer[ix];
|
|
if (!good) {
|
|
m_failed = true;
|
|
ASSERT_EQ(readBuffer[ix], verifyBuffer[ix]) << "UnexpectedByte at: " << totalBytesRead + ix;
|
|
}
|
|
}
|
|
totalBytesRead += readSizeOut;
|
|
}
|
|
|
|
switch (readStatus) {
|
|
case AttachmentReader::ReadStatus::OK:
|
|
break;
|
|
|
|
case AttachmentReader::ReadStatus::CLOSED:
|
|
ACSDK_DEBUG9(LX("readClosed"));
|
|
return;
|
|
|
|
case AttachmentReader::ReadStatus::OK_WOULDBLOCK:
|
|
case AttachmentReader::ReadStatus::OK_TIMEDOUT:
|
|
case AttachmentReader::ReadStatus::ERROR_OVERRUN:
|
|
case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE:
|
|
case AttachmentReader::ReadStatus::ERROR_INTERNAL:
|
|
ASSERT_NE(readStatus, readStatus);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Exercise MimeParser's re-drive, parsing, and attachment buffering by feeding in a large attachment
|
|
* consisting of pseudo-random bytes sent in pseudo-random sized chunks. At the same time, read the
|
|
* resulting attachment in pseudo-random sized requests. Expect that the data read matches the data
|
|
* that was fed.
|
|
*/
|
|
TEST_F(MimeParserFuzzTest, testRandomFeedAndReadSizesOfRandomData) {
|
|
auto readThread = std::thread(&MimeParserFuzzTest::read, this);
|
|
feed();
|
|
readThread.join();
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace acl
|
|
} // namespace alexaClientSDK
|