blob: 0032caf77cae2caddb6fa3d9b361e83d8e4f59d1 [file] [log] [blame]
// Copyright 2021 The Pigweed Authors
//
// 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
//
// https://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 "pw_transfer/transfer.h"
#include "gtest/gtest.h"
#include "pw_bytes/array.h"
#include "pw_rpc/raw/test_method_context.h"
#include "pw_transfer/transfer.pwpb.h"
#include "pw_transfer_private/chunk.h"
namespace pw::transfer {
namespace {
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
using internal::Chunk;
Vector<std::byte, 64> EncodeChunk(const Chunk& chunk) {
Vector<std::byte, 64> buffer(64);
auto result = internal::EncodeChunk(chunk, buffer);
EXPECT_EQ(result.status(), OkStatus());
buffer.resize(result.value().size());
return buffer;
}
Chunk DecodeChunk(ConstByteSpan buffer) {
Chunk chunk = {};
EXPECT_EQ(internal::DecodeChunk(buffer, chunk), OkStatus());
return chunk;
}
class SimpleReadTransfer final : public ReadOnlyHandler {
public:
SimpleReadTransfer(uint32_t transfer_id, ConstByteSpan data)
: ReadOnlyHandler(transfer_id),
prepare_read_called(false),
finalize_read_called(false),
finalize_read_status(Status::Unknown()),
reader_(data) {}
Status PrepareRead() final {
// reader_.Seek(0);
set_reader(reader_);
prepare_read_called = true;
return OkStatus();
}
void FinalizeRead(Status status) final {
finalize_read_called = true;
finalize_read_status = status;
}
bool prepare_read_called;
bool finalize_read_called;
Status finalize_read_status;
private:
stream::MemoryReader reader_;
};
TEST(Tranfser, Read_SingleChunk) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SimpleReadTransfer handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx(64, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
EXPECT_FALSE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 64, .offset = 0}));
EXPECT_TRUE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ASSERT_EQ(ctx.total_responses(), 2u);
Chunk c0 = DecodeChunk(ctx.responses()[0]);
Chunk c1 = DecodeChunk(ctx.responses()[1]);
// First chunk should have all the read data.
EXPECT_EQ(c0.transfer_id, 3u);
EXPECT_EQ(c0.offset, 0u);
ASSERT_EQ(c0.data.size(), data.size());
EXPECT_EQ(std::memcmp(c0.data.data(), data.data(), c0.data.size()), 0);
// Second chunk should be empty and set remaining_bytes = 0.
EXPECT_EQ(c1.transfer_id, 3u);
EXPECT_EQ(c1.data.size(), 0u);
ASSERT_TRUE(c1.remaining_bytes.has_value());
EXPECT_EQ(c1.remaining_bytes.value(), 0u);
ctx.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
EXPECT_TRUE(handler.finalize_read_called);
EXPECT_EQ(handler.finalize_read_status, OkStatus());
}
TEST(Tranfser, Read_MultiChunk) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SimpleReadTransfer handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx(64, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
EXPECT_FALSE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 0}));
EXPECT_TRUE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk c0 = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(c0.transfer_id, 3u);
EXPECT_EQ(c0.offset, 0u);
ASSERT_EQ(c0.data.size(), 16u);
EXPECT_EQ(std::memcmp(c0.data.data(), data.data(), c0.data.size()), 0);
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 16}));
ASSERT_EQ(ctx.total_responses(), 2u);
Chunk c1 = DecodeChunk(ctx.responses()[1]);
EXPECT_EQ(c1.transfer_id, 3u);
EXPECT_EQ(c1.offset, 16u);
ASSERT_EQ(c1.data.size(), 16u);
EXPECT_EQ(std::memcmp(c1.data.data(), data.data() + 16, c1.data.size()), 0);
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 32}));
ASSERT_EQ(ctx.total_responses(), 3u);
Chunk c2 = DecodeChunk(ctx.responses()[2]);
EXPECT_EQ(c2.transfer_id, 3u);
EXPECT_EQ(c2.data.size(), 0u);
ASSERT_TRUE(c2.remaining_bytes.has_value());
EXPECT_EQ(c2.remaining_bytes.value(), 0u);
ctx.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
EXPECT_TRUE(handler.finalize_read_called);
EXPECT_EQ(handler.finalize_read_status, OkStatus());
}
TEST(Tranfser, Read_MaxChunkSize_Client) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SimpleReadTransfer handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read, 5, 64) ctx(64, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
EXPECT_FALSE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ctx.SendClientStream(EncodeChunk({.transfer_id = 3,
.pending_bytes = 64,
.max_chunk_size_bytes = 8,
.offset = 0}));
EXPECT_TRUE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ASSERT_EQ(ctx.total_responses(), 5u);
Chunk c0 = DecodeChunk(ctx.responses()[0]);
Chunk c1 = DecodeChunk(ctx.responses()[1]);
Chunk c2 = DecodeChunk(ctx.responses()[2]);
Chunk c3 = DecodeChunk(ctx.responses()[3]);
Chunk c4 = DecodeChunk(ctx.responses()[4]);
EXPECT_EQ(c0.transfer_id, 3u);
EXPECT_EQ(c0.offset, 0u);
ASSERT_EQ(c0.data.size(), 8u);
EXPECT_EQ(std::memcmp(c0.data.data(), data.data(), c0.data.size()), 0);
EXPECT_EQ(c1.transfer_id, 3u);
EXPECT_EQ(c1.offset, 8u);
ASSERT_EQ(c1.data.size(), 8u);
EXPECT_EQ(std::memcmp(c1.data.data(), data.data() + 8, c1.data.size()), 0);
EXPECT_EQ(c2.transfer_id, 3u);
EXPECT_EQ(c2.offset, 16u);
ASSERT_EQ(c2.data.size(), 8u);
EXPECT_EQ(std::memcmp(c2.data.data(), data.data() + 16, c2.data.size()), 0);
EXPECT_EQ(c3.transfer_id, 3u);
EXPECT_EQ(c3.offset, 24u);
ASSERT_EQ(c3.data.size(), 8u);
EXPECT_EQ(std::memcmp(c3.data.data(), data.data() + 24, c3.data.size()), 0);
EXPECT_EQ(c4.transfer_id, 3u);
EXPECT_EQ(c4.data.size(), 0u);
ASSERT_TRUE(c4.remaining_bytes.has_value());
EXPECT_EQ(c4.remaining_bytes.value(), 0u);
ctx.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
EXPECT_TRUE(handler.finalize_read_called);
EXPECT_EQ(handler.finalize_read_status, OkStatus());
}
TEST(Tranfser, Read_MaxChunkSize_Server) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SimpleReadTransfer handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read, 5, 64)
ctx(/*max_chunk_size_bytes=*/8, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
EXPECT_FALSE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
// Client asks for max 16-byte chunks, but service places a limit of 8 bytes.
ctx.SendClientStream(EncodeChunk({.transfer_id = 3,
.pending_bytes = 64,
.max_chunk_size_bytes = 16,
.offset = 0}));
EXPECT_TRUE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ASSERT_EQ(ctx.total_responses(), 5u);
Chunk c0 = DecodeChunk(ctx.responses()[0]);
Chunk c1 = DecodeChunk(ctx.responses()[1]);
Chunk c2 = DecodeChunk(ctx.responses()[2]);
Chunk c3 = DecodeChunk(ctx.responses()[3]);
Chunk c4 = DecodeChunk(ctx.responses()[4]);
EXPECT_EQ(c0.transfer_id, 3u);
EXPECT_EQ(c0.offset, 0u);
ASSERT_EQ(c0.data.size(), 8u);
EXPECT_EQ(std::memcmp(c0.data.data(), data.data(), c0.data.size()), 0);
EXPECT_EQ(c1.transfer_id, 3u);
EXPECT_EQ(c1.offset, 8u);
ASSERT_EQ(c1.data.size(), 8u);
EXPECT_EQ(std::memcmp(c1.data.data(), data.data() + 8, c1.data.size()), 0);
EXPECT_EQ(c2.transfer_id, 3u);
EXPECT_EQ(c2.offset, 16u);
ASSERT_EQ(c2.data.size(), 8u);
EXPECT_EQ(std::memcmp(c2.data.data(), data.data() + 16, c2.data.size()), 0);
EXPECT_EQ(c3.transfer_id, 3u);
EXPECT_EQ(c3.offset, 24u);
ASSERT_EQ(c3.data.size(), 8u);
EXPECT_EQ(std::memcmp(c3.data.data(), data.data() + 24, c3.data.size()), 0);
EXPECT_EQ(c4.transfer_id, 3u);
EXPECT_EQ(c4.data.size(), 0u);
ASSERT_TRUE(c4.remaining_bytes.has_value());
EXPECT_EQ(c4.remaining_bytes.value(), 0u);
ctx.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
EXPECT_TRUE(handler.finalize_read_called);
EXPECT_EQ(handler.finalize_read_status, OkStatus());
}
TEST(Tranfser, Read_ClientError) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SimpleReadTransfer handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx(64, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
EXPECT_FALSE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 0}));
EXPECT_TRUE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
ASSERT_EQ(ctx.total_responses(), 1u);
// Send client error.
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .status = Status::OutOfRange()}));
ASSERT_EQ(ctx.total_responses(), 1u);
EXPECT_TRUE(handler.finalize_read_called);
EXPECT_EQ(handler.finalize_read_status, Status::OutOfRange());
}
TEST(Tranfser, Read_MalformedParametersChunk) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SimpleReadTransfer handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx(64, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
EXPECT_FALSE(handler.prepare_read_called);
EXPECT_FALSE(handler.finalize_read_called);
// pending_bytes is required in a parameters chunk.
ctx.SendClientStream(EncodeChunk({.transfer_id = 3}));
EXPECT_TRUE(handler.prepare_read_called);
EXPECT_TRUE(handler.finalize_read_called);
EXPECT_EQ(handler.finalize_read_status, Status::InvalidArgument());
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 3u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), Status::InvalidArgument());
}
TEST(Tranfser, Read_UnregisteredHandler) {
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx(64, 64);
ctx.call();
ctx.SendClientStream(
EncodeChunk({.transfer_id = 11, .pending_bytes = 32, .offset = 0}));
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 11u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), Status::NotFound());
}
class SimpleWriteTransfer final : public WriteOnlyHandler {
public:
SimpleWriteTransfer(uint32_t transfer_id, ByteSpan data)
: WriteOnlyHandler(transfer_id),
prepare_write_called(false),
finalize_write_called(false),
finalize_write_status(Status::Unknown()),
writer_(data) {}
Status PrepareWrite() final {
// writer_.Seek(0);
set_writer(writer_);
prepare_write_called = true;
return OkStatus();
}
Status FinalizeWrite(Status status) final {
finalize_write_called = true;
finalize_write_status = status;
return OkStatus();
}
bool prepare_write_called;
bool finalize_write_called;
Status finalize_write_status;
private:
stream::MemoryWriter writer_;
};
TEST(Transfer, Write_SingleChunk) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
std::array<std::byte, sizeof(data)> buffer = {};
SimpleWriteTransfer handler(7, buffer);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 64);
ctx.service().RegisterHandler(handler);
EXPECT_FALSE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
EXPECT_TRUE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 32u);
ASSERT_TRUE(chunk.max_chunk_size_bytes.has_value());
EXPECT_EQ(chunk.max_chunk_size_bytes.value(), 42u);
ctx.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
.offset = 0,
.data = std::span(data),
.remaining_bytes = 0}));
ASSERT_EQ(ctx.total_responses(), 2u);
chunk = DecodeChunk(ctx.responses()[1]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), OkStatus());
EXPECT_TRUE(handler.finalize_write_called);
EXPECT_EQ(handler.finalize_write_status, OkStatus());
EXPECT_EQ(std::memcmp(buffer.data(), data.data(), data.size()), 0);
}
TEST(Transfer, Write_MultiChunk) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
std::array<std::byte, sizeof(data)> buffer = {};
SimpleWriteTransfer handler(7, buffer);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 64);
ctx.service().RegisterHandler(handler);
EXPECT_FALSE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
EXPECT_TRUE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 32u);
ctx.SendClientStream<64>(EncodeChunk(
{.transfer_id = 7, .offset = 0, .data = std::span(data).first(16)}));
ASSERT_EQ(ctx.total_responses(), 1u);
ctx.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
.offset = 16,
.data = std::span(data).subspan(16),
.remaining_bytes = 0}));
ASSERT_EQ(ctx.total_responses(), 2u);
chunk = DecodeChunk(ctx.responses()[1]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), OkStatus());
EXPECT_TRUE(handler.finalize_write_called);
EXPECT_EQ(handler.finalize_write_status, OkStatus());
EXPECT_EQ(std::memcmp(buffer.data(), data.data(), data.size()), 0);
}
TEST(Transfer, Write_MultipleParameters) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
std::array<std::byte, sizeof(data)> buffer = {};
SimpleWriteTransfer handler(7, buffer);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 16);
ctx.service().RegisterHandler(handler);
EXPECT_FALSE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
EXPECT_TRUE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 16u);
ctx.SendClientStream<64>(EncodeChunk(
{.transfer_id = 7, .offset = 0, .data = std::span(data).first(8)}));
ASSERT_EQ(ctx.total_responses(), 1u);
ctx.SendClientStream<64>(EncodeChunk(
{.transfer_id = 7, .offset = 8, .data = std::span(data).subspan(8, 8)}));
ASSERT_EQ(ctx.total_responses(), 2u);
chunk = DecodeChunk(ctx.responses()[1]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 16u);
ctx.SendClientStream<64>(
EncodeChunk({.transfer_id = 7,
.offset = 16,
.data = std::span(data).subspan(16, 8)}));
ASSERT_EQ(ctx.total_responses(), 2u);
ctx.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
.offset = 24,
.data = std::span(data).subspan(24),
.remaining_bytes = 0}));
ASSERT_EQ(ctx.total_responses(), 3u);
chunk = DecodeChunk(ctx.responses()[2]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), OkStatus());
EXPECT_TRUE(handler.finalize_write_called);
EXPECT_EQ(handler.finalize_write_status, OkStatus());
EXPECT_EQ(std::memcmp(buffer.data(), data.data(), data.size()), 0);
}
TEST(Transfer, Write_SetsDefaultPendingBytes) {
// Constructor's default max bytes is smaller than buffer.
std::array<std::byte, 32> buffer = {};
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 16);
SimpleWriteTransfer handler(7, buffer);
ctx.service().RegisterHandler(handler);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
EXPECT_EQ(chunk.pending_bytes.value(), 16u);
}
TEST(Transfer, Write_SetsWriterPendingBytes) {
// Buffer is smaller than constructor's default max bytes.
std::array<std::byte, 8> buffer = {};
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 64);
SimpleWriteTransfer handler(7, buffer);
ctx.service().RegisterHandler(handler);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
EXPECT_EQ(chunk.pending_bytes.value(), 8u);
}
TEST(Transfer, Write_UnexpectedOffset) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
std::array<std::byte, sizeof(data)> buffer = {};
SimpleWriteTransfer handler(7, buffer);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 64);
ctx.service().RegisterHandler(handler);
EXPECT_FALSE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
EXPECT_TRUE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
EXPECT_EQ(chunk.offset, 0u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 32u);
ctx.SendClientStream<64>(EncodeChunk(
{.transfer_id = 7, .offset = 0, .data = std::span(data).first(16)}));
ASSERT_EQ(ctx.total_responses(), 1u);
ctx.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
.offset = 8, // incorrect
.data = std::span(data).subspan(16),
.remaining_bytes = 0}));
ASSERT_EQ(ctx.total_responses(), 2u);
chunk = DecodeChunk(ctx.responses()[1]);
EXPECT_EQ(chunk.transfer_id, 7u);
EXPECT_EQ(chunk.offset, 16u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 16u);
ctx.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
.offset = 16, // incorrect
.data = std::span(data).subspan(16),
.remaining_bytes = 0}));
ASSERT_EQ(ctx.total_responses(), 3u);
chunk = DecodeChunk(ctx.responses()[2]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), OkStatus());
EXPECT_TRUE(handler.finalize_write_called);
EXPECT_EQ(handler.finalize_write_status, OkStatus());
EXPECT_EQ(std::memcmp(buffer.data(), data.data(), data.size()), 0);
}
TEST(Transfer, Write_UnregisteredHandler) {
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 64);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), Status::NotFound());
}
TEST(Transfer, Write_ClientError) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
std::array<std::byte, sizeof(data)> buffer = {};
SimpleWriteTransfer handler(7, buffer);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx(64, 64);
ctx.service().RegisterHandler(handler);
EXPECT_FALSE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ctx.call();
ctx.SendClientStream(EncodeChunk({.transfer_id = 7}));
EXPECT_TRUE(handler.prepare_write_called);
EXPECT_FALSE(handler.finalize_write_called);
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 7u);
ASSERT_TRUE(chunk.pending_bytes.has_value());
EXPECT_EQ(chunk.pending_bytes.value(), 32u);
ctx.SendClientStream<64>(
EncodeChunk({.transfer_id = 7, .status = Status::DataLoss()}));
EXPECT_EQ(ctx.total_responses(), 1u);
EXPECT_TRUE(handler.finalize_write_called);
EXPECT_EQ(handler.finalize_write_status, Status::DataLoss());
}
class SometimesUnavailableReadHandler final : public ReadOnlyHandler {
public:
SometimesUnavailableReadHandler(uint32_t transfer_id, ConstByteSpan data)
: ReadOnlyHandler(transfer_id), reader_(data), call_count_(0) {}
Status PrepareRead() final {
if ((call_count_++ % 2) == 0) {
return Status::Unavailable();
}
set_reader(reader_);
return OkStatus();
}
private:
stream::MemoryReader reader_;
int call_count_;
};
TEST(Tranfser, PrepareError) {
constexpr auto data = bytes::Initialized<32>([](size_t i) { return i; });
SometimesUnavailableReadHandler handler(3, data);
PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx(64, 64);
ctx.service().RegisterHandler(handler);
ctx.call();
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 128, .offset = 0}));
ASSERT_EQ(ctx.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx.responses()[0]);
EXPECT_EQ(chunk.transfer_id, 3u);
ASSERT_TRUE(chunk.status.has_value());
EXPECT_EQ(chunk.status.value(), Status::Unavailable());
// Try starting the transfer again. It should work this time.
ctx.SendClientStream(
EncodeChunk({.transfer_id = 3, .pending_bytes = 128, .offset = 0}));
ASSERT_EQ(ctx.total_responses(), 3u);
chunk = DecodeChunk(ctx.responses()[1]);
EXPECT_EQ(chunk.transfer_id, 3u);
ASSERT_EQ(chunk.data.size(), data.size());
EXPECT_EQ(std::memcmp(chunk.data.data(), data.data(), chunk.data.size()), 0);
}
PW_MODIFY_DIAGNOSTICS_POP();
} // namespace
} // namespace pw::transfer