aboutsummaryrefslogtreecommitdiff
path: root/pw_transfer
diff options
context:
space:
mode:
Diffstat (limited to 'pw_transfer')
-rw-r--r--pw_transfer/BUILD.bazel40
-rw-r--r--pw_transfer/BUILD.gn93
-rw-r--r--pw_transfer/OWNERS1
-rw-r--r--pw_transfer/api.rst12
-rw-r--r--pw_transfer/chunk.cc55
-rw-r--r--pw_transfer/client.cc6
-rw-r--r--pw_transfer/client_test.cc205
-rw-r--r--pw_transfer/context.cc48
-rw-r--r--pw_transfer/docs.rst41
-rw-r--r--pw_transfer/integration_test/BUILD.bazel35
-rw-r--r--pw_transfer/integration_test/client.cc18
-rw-r--r--pw_transfer/integration_test/config.proto2
-rw-r--r--pw_transfer/integration_test/cross_language_large_read_test.py2
-rw-r--r--pw_transfer/integration_test/cross_language_large_write_test.py2
-rw-r--r--pw_transfer/integration_test/cross_language_medium_read_test.py2
-rw-r--r--pw_transfer/integration_test/cross_language_medium_write_test.py2
-rw-r--r--pw_transfer/integration_test/cross_language_small_test.py2
-rw-r--r--pw_transfer/integration_test/expected_errors_test.py2
-rw-r--r--pw_transfer/integration_test/legacy_binaries_test.py11
-rw-r--r--pw_transfer/integration_test/multi_transfer_test.py2
-rw-r--r--pw_transfer/integration_test/proxy.py4
-rw-r--r--pw_transfer/integration_test/python_client.py183
-rw-r--r--pw_transfer/integration_test/test_fixture.py2
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/BUILD.bazel1
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/ReadTransfer.java8
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/Transfer.java34
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/TransferClient.java19
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/TransferEventHandler.java99
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/TransferService.java4
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/VersionedChunk.java41
-rw-r--r--pw_transfer/java/main/dev/pigweed/pw_transfer/WriteTransfer.java4
-rw-r--r--pw_transfer/java/test/dev/pigweed/pw_transfer/TransferClientTest.java870
-rw-r--r--pw_transfer/public/pw_transfer/atomic_file_transfer_handler.h44
-rw-r--r--pw_transfer/public/pw_transfer/client.h8
-rw-r--r--pw_transfer/public/pw_transfer/internal/chunk.h33
-rw-r--r--pw_transfer/public/pw_transfer/internal/client_context.h7
-rw-r--r--pw_transfer/public/pw_transfer/internal/config.h21
-rw-r--r--pw_transfer/public/pw_transfer/internal/context.h12
-rw-r--r--pw_transfer/public/pw_transfer/internal/event.h2
-rw-r--r--pw_transfer/public/pw_transfer/internal/server_context.h3
-rw-r--r--pw_transfer/public/pw_transfer/transfer.h8
-rw-r--r--pw_transfer/public/pw_transfer/transfer_thread.h39
-rw-r--r--pw_transfer/pw_transfer_private/chunk_testing.h3
-rw-r--r--pw_transfer/py/pw_transfer/chunk.py57
-rw-r--r--pw_transfer/py/pw_transfer/client.py39
-rw-r--r--pw_transfer/py/pw_transfer/transfer.py24
-rw-r--r--pw_transfer/py/tests/transfer_test.py91
-rw-r--r--pw_transfer/test_rpc_server.cc123
-rw-r--r--pw_transfer/test_server.proto24
-rw-r--r--pw_transfer/transfer.cc16
-rw-r--r--pw_transfer/transfer.proto22
-rw-r--r--pw_transfer/transfer_test.cc270
-rw-r--r--pw_transfer/transfer_thread.cc78
-rw-r--r--pw_transfer/transfer_thread_test.cc28
-rw-r--r--pw_transfer/ts/client.ts38
-rw-r--r--pw_transfer/ts/index.ts3
-rw-r--r--pw_transfer/ts/transfer.ts57
-rw-r--r--pw_transfer/ts/transfer_test.ts36
58 files changed, 1710 insertions, 1226 deletions
diff --git a/pw_transfer/BUILD.bazel b/pw_transfer/BUILD.bazel
index 577d658c8..1a8bc16a2 100644
--- a/pw_transfer/BUILD.bazel
+++ b/pw_transfer/BUILD.bazel
@@ -12,10 +12,10 @@
# License for the specific language governing permissions and limitations under
# the License.
-load("//pw_build:pigweed.bzl", "pw_cc_binary", "pw_cc_library", "pw_cc_test")
-load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
+load("//pw_build:pigweed.bzl", "pw_cc_library", "pw_cc_test")
+load("//pw_protobuf_compiler:pw_proto_library.bzl", "pw_proto_library")
package(default_visibility = ["//visibility:public"])
@@ -183,6 +183,10 @@ pw_cc_test(
pw_cc_test(
name = "transfer_thread_test",
srcs = ["transfer_thread_test.cc"],
+ target_compatible_with = select({
+ "//pw_unit_test:light_setting": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
deps = [
":pw_transfer",
":test_helpers",
@@ -223,21 +227,6 @@ pw_cc_test(
],
)
-pw_cc_binary(
- name = "test_rpc_server",
- srcs = ["test_rpc_server.cc"],
- deps = [
- ":atomic_file_transfer_handler",
- ":pw_transfer",
- ":test_server_pwpb.raw_rpc",
- "//pw_log",
- "//pw_rpc/system_server",
- "//pw_stream:std_file_stream",
- "//pw_thread:thread",
- "//pw_work_queue",
- ],
-)
-
proto_library(
name = "transfer_proto",
srcs = [
@@ -259,20 +248,3 @@ java_lite_proto_library(
name = "transfer_proto_java_lite",
deps = [":transfer_proto"],
)
-
-proto_library(
- name = "test_server",
- srcs = [
- "test_server.proto",
- ],
- import_prefix = "pw_transfer_test",
- strip_import_prefix = "/pw_transfer",
- deps = [
- "//pw_protobuf:common_proto",
- ],
-)
-
-pw_proto_library(
- name = "test_server_pwpb",
- deps = [":test_server"],
-)
diff --git a/pw_transfer/BUILD.gn b/pw_transfer/BUILD.gn
index 9b1eb7fff..e4e15d8af 100644
--- a/pw_transfer/BUILD.gn
+++ b/pw_transfer/BUILD.gn
@@ -159,39 +159,37 @@ pw_proto_library("proto") {
}
pw_test_group("tests") {
- tests = []
-
- # pw_transfer requires threading.
- if (pw_thread_THREAD_BACKEND != "") {
- tests += [
- ":chunk_test",
- ":client_test",
- ":transfer_thread_test",
- ]
-
- # TODO(b/235345886): Fix transfer tests on Windows and non-host builds.
- if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
- pw_toolchain_SCOPE.is_host_toolchain && host_os != "win") {
- tests += [
- ":handler_test",
- ":atomic_file_transfer_handler_test",
- ":transfer_test",
- ]
- }
- }
+ tests = [
+ ":chunk_test",
+ ":client_test",
+ ":transfer_thread_test",
+ ":handler_test",
+ ":atomic_file_transfer_handler_test",
+ ":transfer_test",
+ ]
}
+# TODO: b/235345886 - Fix transfer tests on Windows and non-host builds.
+_is_host_toolchain = defined(pw_toolchain_SCOPE.is_host_toolchain) &&
+ pw_toolchain_SCOPE.is_host_toolchain
+not_needed([ "_is_host_toolchain" ])
+
pw_test("chunk_test") {
+ enable_if = pw_thread_THREAD_BACKEND != ""
sources = [ "chunk_test.cc" ]
deps = [ ":core" ]
}
pw_test("handler_test") {
+ enable_if =
+ pw_thread_THREAD_BACKEND != "" && _is_host_toolchain && host_os != "win"
sources = [ "handler_test.cc" ]
deps = [ ":pw_transfer" ]
}
pw_test("atomic_file_transfer_handler_test") {
+ enable_if =
+ pw_thread_THREAD_BACKEND != "" && _is_host_toolchain && host_os != "win"
sources = [ "atomic_file_transfer_handler_test.cc" ]
deps = [
":atomic_file_transfer_handler",
@@ -203,7 +201,8 @@ pw_test("atomic_file_transfer_handler_test") {
}
pw_test("transfer_test") {
- enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread"
+ enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread" &&
+ _is_host_toolchain && host_os != "win"
sources = [ "transfer_test.cc" ]
deps = [
":proto.pwpb",
@@ -218,7 +217,8 @@ pw_test("transfer_test") {
}
pw_test("transfer_thread_test") {
- enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread"
+ enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread" &&
+ pw_unit_test_GOOGLETEST_BACKEND == "$dir_pw_unit_test:light"
sources = [ "transfer_thread_test.cc" ]
deps = [
":core",
@@ -246,7 +246,10 @@ pw_test("client_test") {
}
pw_doc_group("docs") {
- sources = [ "docs.rst" ]
+ sources = [
+ "api.rst",
+ "docs.rst",
+ ]
inputs = [
"transfer.proto",
"read.svg",
@@ -254,26 +257,6 @@ pw_doc_group("docs") {
]
}
-pw_proto_library("test_server_proto") {
- sources = [ "test_server.proto" ]
- prefix = "pw_transfer_test"
- deps = [ "$dir_pw_protobuf:common_protos" ]
-}
-
-pw_executable("test_rpc_server") {
- sources = [ "test_rpc_server.cc" ]
- deps = [
- ":atomic_file_transfer_handler",
- ":pw_transfer",
- ":test_server_proto.raw_rpc",
- "$dir_pw_rpc/system_server",
- "$dir_pw_rpc/system_server:socket",
- "$dir_pw_stream:std_file_stream",
- "$dir_pw_thread:thread",
- dir_pw_log,
- ]
-}
-
pw_executable("integration_test_server") {
sources = [ "integration_test/server.cc" ]
deps = [
@@ -303,62 +286,62 @@ pw_executable("integration_test_client") {
]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("integration_test_python_client") {
sources = [ "integration_test/python_client.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("integration_test_proxy") {
sources = [ "integration_test/proxy.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("integration_test_proxy_test") {
sources = [ "integration_test/proxy_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("integration_test_fixture") {
sources = [ "integration_test/test_fixture.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("cross_language_small_test") {
sources = [ "integration_test/cross_language_small_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("cross_language_medium_read_test") {
sources = [ "integration_test/cross_language_medium_read_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("cross_language_medium_write_test") {
sources = [ "integration_test/cross_language_medium_write_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("cross_language_large_read_test") {
sources = [ "integration_test/cross_language_large_read_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("cross_language_large_write_test") {
sources = [ "integration_test/cross_language_large_write_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("multi_transfer_test") {
sources = [ "integration_test/multi_transfer_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("expected_errors_test") {
sources = [ "integration_test/expected_errors_test.py" ]
}
-# TODO(b/228516801): Make this actually work; this is just a placeholder.
+# TODO: b/228516801 - Make this actually work; this is just a placeholder.
pw_python_script("legacy_binaries_test") {
sources = [ "integration_test/legacy_binaries_test.py" ]
}
diff --git a/pw_transfer/OWNERS b/pw_transfer/OWNERS
index d96cbc68d..34fbf1bf0 100644
--- a/pw_transfer/OWNERS
+++ b/pw_transfer/OWNERS
@@ -1 +1,2 @@
+frolv@google.com
hepler@google.com
diff --git a/pw_transfer/api.rst b/pw_transfer/api.rst
new file mode 100644
index 000000000..33ca6212d
--- /dev/null
+++ b/pw_transfer/api.rst
@@ -0,0 +1,12 @@
+.. _module-pw_transfer-api:
+
+=========================
+pw_transfer API reference
+=========================
+.. note::
+
+ This API reference is a work-in-progress. The full ``pw_transfer`` API is
+ not yet documented on this page.
+
+.. doxygennamespace:: pw::transfer
+ :members:
diff --git a/pw_transfer/chunk.cc b/pw_transfer/chunk.cc
index cf5133ed3..900c66efc 100644
--- a/pw_transfer/chunk.cc
+++ b/pw_transfer/chunk.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -27,9 +27,12 @@ Result<Chunk::Identifier> Chunk::ExtractIdentifier(ConstByteSpan message) {
protobuf::Decoder decoder(message);
uint32_t session_id = 0;
- uint32_t resource_id = 0;
+ uint32_t desired_session_id = 0;
+ bool legacy = true;
- while (decoder.Next().ok()) {
+ Status status;
+
+ while ((status = decoder.Next()).ok()) {
ProtoChunk::Fields field =
static_cast<ProtoChunk::Fields>(decoder.FieldNumber());
@@ -42,19 +45,27 @@ Result<Chunk::Identifier> Chunk::ExtractIdentifier(ConstByteSpan message) {
} else if (field == ProtoChunk::Fields::kSessionId) {
// A session_id field always takes precedence over transfer_id.
PW_TRY(decoder.ReadUint32(&session_id));
- } else if (field == ProtoChunk::Fields::kResourceId) {
- PW_TRY(decoder.ReadUint32(&resource_id));
+ legacy = false;
+ } else if (field == ProtoChunk::Fields::kDesiredSessionId) {
+ PW_TRY(decoder.ReadUint32(&desired_session_id));
}
}
- // Always prioritize a resource_id if one is set. Resource IDs should only be
- // set in cases where the transfer session ID has not yet been negotiated.
- if (resource_id != 0) {
- return Identifier::Resource(resource_id);
+ if (!status.IsOutOfRange()) {
+ return Status::DataLoss();
+ }
+
+ if (desired_session_id != 0) {
+ // Can't have both a desired and regular session_id.
+ if (!legacy && session_id != 0) {
+ return Status::DataLoss();
+ }
+ return Identifier::Desired(desired_session_id);
}
if (session_id != 0) {
- return Identifier::Session(session_id);
+ return legacy ? Identifier::Legacy(session_id)
+ : Identifier::Session(session_id);
}
return Status::DataLoss();
@@ -78,6 +89,8 @@ Result<Chunk> Chunk::Parse(ConstByteSpan message) {
// window_end_offset from it once parsing is complete.
uint32_t pending_bytes = 0;
+ bool has_session_id = false;
+
while ((status = decoder.Next()).ok()) {
ProtoChunk::Fields field =
static_cast<ProtoChunk::Fields>(decoder.FieldNumber());
@@ -99,7 +112,7 @@ Result<Chunk> Chunk::Parse(ConstByteSpan message) {
if (chunk.protocol_version_ == ProtocolVersion::kUnknown) {
chunk.protocol_version_ = ProtocolVersion::kVersionTwo;
}
-
+ has_session_id = true;
PW_TRY(decoder.ReadUint32(&chunk.session_id_));
break;
@@ -164,10 +177,20 @@ Result<Chunk> Chunk::Parse(ConstByteSpan message) {
chunk.protocol_version_ = static_cast<ProtocolVersion>(value);
break;
+ case ProtoChunk::Fields::kDesiredSessionId:
+ PW_TRY(decoder.ReadUint32(&value));
+ chunk.desired_session_id_ = value;
+ break;
+
// Silently ignore any unrecognized fields.
}
}
+ if (chunk.desired_session_id_.has_value() && has_session_id) {
+ // Setting both session_id and desired_session_id is not permitted.
+ return Status::DataLoss();
+ }
+
if (chunk.protocol_version_ == ProtocolVersion::kUnknown) {
// If no fields in the chunk specified its protocol version, assume it is a
// legacy chunk.
@@ -201,9 +224,15 @@ Result<ConstByteSpan> Chunk::Encode(ByteSpan buffer) const {
if (protocol_version_ >= ProtocolVersion::kVersionTwo) {
if (session_id_ != 0) {
+ PW_CHECK(!desired_session_id_.has_value(),
+ "A chunk cannot set both a desired and regular session ID");
encoder.WriteSessionId(session_id_).IgnoreError();
}
+ if (desired_session_id_.has_value()) {
+ encoder.WriteDesiredSessionId(desired_session_id_.value()).IgnoreError();
+ }
+
if (resource_id_.has_value()) {
encoder.WriteResourceId(resource_id_.value()).IgnoreError();
}
@@ -297,6 +326,10 @@ size_t Chunk::EncodedSize() const {
size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kResourceId,
resource_id_.value());
}
+ if (desired_session_id_.has_value()) {
+ size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kDesiredSessionId,
+ desired_session_id_.value());
+ }
}
if (offset_ != 0) {
diff --git a/pw_transfer/client.cc b/pw_transfer/client.cc
index 95abb81a3..bbd3d0de7 100644
--- a/pw_transfer/client.cc
+++ b/pw_transfer/client.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -24,6 +24,7 @@ Status Client::Read(uint32_t resource_id,
stream::Writer& output,
CompletionFunc&& on_completion,
chrono::SystemClock::duration timeout,
+ chrono::SystemClock::duration initial_chunk_timeout,
ProtocolVersion protocol_version) {
if (on_completion == nullptr ||
protocol_version == ProtocolVersion::kUnknown) {
@@ -49,6 +50,7 @@ Status Client::Read(uint32_t resource_id,
max_parameters_,
std::move(on_completion),
timeout,
+ initial_chunk_timeout,
max_retries_,
max_lifetime_retries_);
return OkStatus();
@@ -58,6 +60,7 @@ Status Client::Write(uint32_t resource_id,
stream::Reader& input,
CompletionFunc&& on_completion,
chrono::SystemClock::duration timeout,
+ chrono::SystemClock::duration initial_chunk_timeout,
ProtocolVersion protocol_version) {
if (on_completion == nullptr ||
protocol_version == ProtocolVersion::kUnknown) {
@@ -83,6 +86,7 @@ Status Client::Write(uint32_t resource_id,
max_parameters_,
std::move(on_completion),
timeout,
+ initial_chunk_timeout,
max_retries_,
max_lifetime_retries_);
diff --git a/pw_transfer/client_test.cc b/pw_transfer/client_test.cc
index 1b16d9142..323570dcf 100644
--- a/pw_transfer/client_test.cc
+++ b/pw_transfer/client_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -21,7 +21,6 @@
#include "pw_bytes/array.h"
#include "pw_rpc/raw/client_testing.h"
#include "pw_rpc/test_helpers.h"
-#include "pw_thread/sleep.h"
#include "pw_thread/thread.h"
#include "pw_thread_stl/options.h"
#include "pw_transfer_private/chunk_testing.h"
@@ -611,8 +610,8 @@ TEST_F(ReadTransfer, ResendsParametersIfSentRepeatedChunkDuringRecovery) {
EXPECT_EQ(transfer_status, OkStatus());
}
-constexpr chrono::SystemClock::duration kTestTimeout =
- std::chrono::milliseconds(50);
+// Use a long timeout to avoid accidentally triggering timeouts.
+constexpr chrono::SystemClock::duration kTestTimeout = std::chrono::seconds(30);
constexpr uint8_t kTestRetries = 3;
TEST_F(ReadTransfer, Timeout_ResendsCurrentParameters) {
@@ -780,7 +779,7 @@ TEST_F(ReadTransfer, Timeout_EndsTransferAfterMaxRetries) {
EXPECT_EQ(transfer_status, Status::Unknown());
}
- // Sleep one more time after the final retry. The client should cancel the
+ // Time out one more time after the final retry. The client should cancel the
// transfer at this point. As no packets were received from the server, no
// final status chunk should be sent.
transfer_thread_.SimulateClientTimeout(14);
@@ -788,9 +787,10 @@ TEST_F(ReadTransfer, Timeout_EndsTransferAfterMaxRetries) {
EXPECT_EQ(transfer_status, Status::DeadlineExceeded());
- // After finishing the transfer, nothing else should be sent. Verify this by
- // waiting for a bit.
- this_thread::sleep_for(kTestTimeout * 4);
+ // After finishing the transfer, nothing else should be sent.
+ transfer_thread_.SimulateClientTimeout(14);
+ transfer_thread_.SimulateClientTimeout(14);
+ transfer_thread_.SimulateClientTimeout(14);
ASSERT_EQ(payloads.size(), 4u);
}
@@ -1466,7 +1466,7 @@ TEST_F(WriteTransfer, Timeout_EndsTransferAfterMaxRetries) {
EXPECT_EQ(transfer_status, Status::Unknown());
}
- // Sleep one more time after the final retry. The client should cancel the
+ // Time out one more time after the final retry. The client should cancel the
// transfer at this point. As no packets were received from the server, no
// final status chunk should be sent.
transfer_thread_.SimulateClientTimeout(13);
@@ -1474,9 +1474,10 @@ TEST_F(WriteTransfer, Timeout_EndsTransferAfterMaxRetries) {
EXPECT_EQ(transfer_status, Status::DeadlineExceeded());
- // After finishing the transfer, nothing else should be sent. Verify this by
- // waiting for a bit.
- this_thread::sleep_for(kTestTimeout * 4);
+ // After finishing the transfer, nothing else should be sent.
+ transfer_thread_.SimulateClientTimeout(13);
+ transfer_thread_.SimulateClientTimeout(13);
+ transfer_thread_.SimulateClientTimeout(13);
ASSERT_EQ(payloads.size(), 4u);
// Ensure we don't leave a dangling reference to transfer_status.
@@ -1580,12 +1581,14 @@ TEST_F(WriteTransfer, ManualCancel) {
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
// Get a response from the server, then cancel the transfer.
+ // This must request a smaller chunk than the entire available write data to
+ // prevent the client from trying to send an additional finish chunk.
context_.server().SendServerStream<Transfer::Write>(EncodeChunk(
Chunk(ProtocolVersion::kLegacy, Chunk::Type::kParametersRetransmit)
.set_session_id(15)
.set_offset(0)
- .set_window_end_offset(64)
- .set_max_chunk_size_bytes(32)));
+ .set_window_end_offset(16)
+ .set_max_chunk_size_bytes(16)));
transfer_thread_.WaitUntilEventIsProcessed();
ASSERT_EQ(payloads.size(), 2u);
@@ -1644,6 +1647,7 @@ TEST_F(ReadTransfer, Version2_SingleChunk) {
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1659,17 +1663,16 @@ TEST_F(ReadTransfer, Version2_SingleChunk) {
Chunk chunk = DecodeChunk(payloads[0]);
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
EXPECT_EQ(chunk.max_chunk_size_bytes(), 37u);
- // The server responds with a START_ACK, continuing the version 2 handshake
- // and assigning a session_id to the transfer.
+ // The server responds with a START_ACK, continuing the version 2 handshake.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(29)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1680,7 +1683,8 @@ TEST_F(ReadTransfer, Version2_SingleChunk) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_FALSE(chunk.desired_session_id().has_value());
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1690,7 +1694,7 @@ TEST_F(ReadTransfer, Version2_SingleChunk) {
// transfer.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(29)
+ .set_session_id(1)
.set_offset(0)
.set_payload(kData32)
.set_remaining_bytes(0)));
@@ -1699,7 +1703,7 @@ TEST_F(ReadTransfer, Version2_SingleChunk) {
ASSERT_EQ(payloads.size(), 3u);
chunk = DecodeChunk(payloads.back());
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
ASSERT_TRUE(chunk.status().has_value());
@@ -1711,7 +1715,7 @@ TEST_F(ReadTransfer, Version2_SingleChunk) {
context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(29)));
+ .set_session_id(1)));
}
TEST_F(ReadTransfer, Version2_ServerRunsLegacy) {
@@ -1724,6 +1728,7 @@ TEST_F(ReadTransfer, Version2_ServerRunsLegacy) {
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1739,7 +1744,7 @@ TEST_F(ReadTransfer, Version2_ServerRunsLegacy) {
Chunk chunk = DecodeChunk(payloads[0]);
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1760,6 +1765,7 @@ TEST_F(ReadTransfer, Version2_ServerRunsLegacy) {
ASSERT_EQ(payloads.size(), 2u);
chunk = DecodeChunk(payloads.back());
+ EXPECT_FALSE(chunk.desired_session_id().has_value());
EXPECT_EQ(chunk.session_id(), 3u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kLegacy);
@@ -1781,6 +1787,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1796,7 +1803,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1804,13 +1811,13 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
// Wait for the timeout to expire without doing anything. The client should
// resend the initial chunk.
- transfer_thread_.SimulateClientTimeout(3);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 2u);
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1819,7 +1826,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
// This time, the server responds, continuing the handshake and transfer.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(31)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1828,7 +1835,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 31u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1836,7 +1843,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(31)
+ .set_session_id(1)
.set_offset(0)
.set_payload(kData32)
.set_remaining_bytes(0)));
@@ -1845,7 +1852,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
ASSERT_EQ(payloads.size(), 4u);
chunk = DecodeChunk(payloads.back());
- EXPECT_EQ(chunk.session_id(), 31u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
ASSERT_TRUE(chunk.status().has_value());
@@ -1857,7 +1864,7 @@ TEST_F(ReadTransfer, Version2_TimeoutDuringHandshake) {
context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(31)));
+ .set_session_id(1)));
}
TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
@@ -1870,6 +1877,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1885,7 +1893,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1895,7 +1903,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
// and assigning a session_id to the transfer.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(33)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1906,7 +1914,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1914,13 +1922,13 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
// Wait for the timeout to expire without doing anything. The client should
// resend the confirmation chunk.
- transfer_thread_.SimulateClientTimeout(33);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 3u);
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1929,7 +1937,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
// The server responds and the transfer should continue normally.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(33)
+ .set_session_id(1)
.set_offset(0)
.set_payload(kData32)
.set_remaining_bytes(0)));
@@ -1938,7 +1946,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
ASSERT_EQ(payloads.size(), 4u);
chunk = DecodeChunk(payloads.back());
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
ASSERT_TRUE(chunk.status().has_value());
@@ -1950,7 +1958,7 @@ TEST_F(ReadTransfer, Version2_TimeoutAfterHandshake) {
context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(33)));
+ .set_session_id(1)));
}
TEST_F(ReadTransfer, Version2_ServerErrorDuringHandshake) {
@@ -1963,6 +1971,7 @@ TEST_F(ReadTransfer, Version2_ServerErrorDuringHandshake) {
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1978,7 +1987,7 @@ TEST_F(ReadTransfer, Version2_ServerErrorDuringHandshake) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -1986,7 +1995,7 @@ TEST_F(ReadTransfer, Version2_ServerErrorDuringHandshake) {
// The server responds to the start request with an error.
context_.server().SendServerStream<Transfer::Read>(EncodeChunk(Chunk::Final(
- ProtocolVersion::kVersionTwo, 3, Status::Unauthenticated())));
+ ProtocolVersion::kVersionTwo, 1, Status::Unauthenticated())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_EQ(payloads.size(), 1u);
@@ -2003,6 +2012,7 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2018,17 +2028,16 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
Chunk chunk = DecodeChunk(payloads[0]);
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
EXPECT_EQ(chunk.max_chunk_size_bytes(), 37u);
- // The server responds with a START_ACK, continuing the version 2 handshake
- // and assigning a session_id to the transfer.
+ // The server responds with a START_ACK, continuing the version 2 handshake.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(29)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2039,7 +2048,7 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -2049,7 +2058,7 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
// transfer.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(29)
+ .set_session_id(1)
.set_offset(0)
.set_payload(kData32)
.set_remaining_bytes(0)));
@@ -2058,7 +2067,7 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
ASSERT_EQ(payloads.size(), 3u);
chunk = DecodeChunk(payloads.back());
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
ASSERT_TRUE(chunk.status().has_value());
@@ -2070,14 +2079,14 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
// Time out instead of sending a completion ACK. THe transfer should resend
// its completion chunk.
- transfer_thread_.SimulateClientTimeout(29);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 4u);
// Reset transfer_status to check whether the handler is called again.
transfer_status = Status::Unknown();
chunk = DecodeChunk(payloads.back());
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
ASSERT_TRUE(chunk.status().has_value());
@@ -2090,11 +2099,11 @@ TEST_F(ReadTransfer, Version2_TimeoutWaitingForCompletionAckRetries) {
// Send a completion ACK to end the transfer.
context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(29)));
+ .set_session_id(1)));
transfer_thread_.WaitUntilEventIsProcessed();
// No further chunks should be sent following the ACK.
- transfer_thread_.SimulateClientTimeout(29);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 4u);
}
@@ -2109,6 +2118,7 @@ TEST_F(ReadTransfer,
writer,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2124,7 +2134,7 @@ TEST_F(ReadTransfer,
Chunk chunk = DecodeChunk(payloads[0]);
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -2134,7 +2144,7 @@ TEST_F(ReadTransfer,
// and assigning a session_id to the transfer.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(29)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2145,7 +2155,7 @@ TEST_F(ReadTransfer,
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 64u);
@@ -2155,7 +2165,7 @@ TEST_F(ReadTransfer,
// transfer.
context_.server().SendServerStream<Transfer::Read>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(29)
+ .set_session_id(1)
.set_offset(0)
.set_payload(kData32)
.set_remaining_bytes(0)));
@@ -2164,7 +2174,7 @@ TEST_F(ReadTransfer,
ASSERT_EQ(payloads.size(), 3u);
chunk = DecodeChunk(payloads.back());
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
ASSERT_TRUE(chunk.status().has_value());
@@ -2177,16 +2187,16 @@ TEST_F(ReadTransfer,
// Time out instead of sending a completion ACK. THe transfer should resend
// its completion chunk at first, then terminate after the maximum number of
// retries.
- transfer_thread_.SimulateClientTimeout(29);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 4u); // Retry 1.
- transfer_thread_.SimulateClientTimeout(29);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 5u); // Retry 2.
- transfer_thread_.SimulateClientTimeout(29);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 6u); // Retry 3.
- transfer_thread_.SimulateClientTimeout(29);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 6u); // No more retries; transfer has ended.
}
@@ -2200,6 +2210,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
reader,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2212,14 +2223,13 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
- // The server responds with a START_ACK, continuing the version 2 handshake
- // and assigning a session_id to the transfer.
+ // The server responds with a START_ACK, continuing the version 2 handshake.
context_.server().SendServerStream<Transfer::Write>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(29)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2229,7 +2239,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
// The server can then begin the data transfer by sending its transfer
@@ -2237,7 +2247,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
rpc::test::WaitForPackets(context_.output(), 2, [this] {
context_.server().SendServerStream<Transfer::Write>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kParametersRetransmit)
- .set_session_id(29)
+ .set_session_id(1)
.set_offset(0)
.set_window_end_offset(64)
.set_max_chunk_size_bytes(32)));
@@ -2248,7 +2258,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
chunk = DecodeChunk(payloads[2]);
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_TRUE(chunk.has_payload());
EXPECT_EQ(std::memcmp(
@@ -2258,7 +2268,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
chunk = DecodeChunk(payloads[3]);
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
ASSERT_TRUE(chunk.remaining_bytes().has_value());
EXPECT_EQ(chunk.remaining_bytes().value(), 0u);
@@ -2266,7 +2276,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
// Send the final status chunk to complete the transfer.
context_.server().SendServerStream<Transfer::Write>(
- EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 29, OkStatus())));
+ EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 1, OkStatus())));
transfer_thread_.WaitUntilEventIsProcessed();
// Client should acknowledge the completion of the transfer.
@@ -2275,7 +2285,7 @@ TEST_F(WriteTransfer, Version2_SingleChunk) {
chunk = DecodeChunk(payloads[4]);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletionAck);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 29u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(transfer_status, OkStatus());
}
@@ -2290,6 +2300,7 @@ TEST_F(WriteTransfer, Version2_ServerRunsLegacy) {
reader,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2302,7 +2313,7 @@ TEST_F(WriteTransfer, Version2_ServerRunsLegacy) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
// Instead of continuing the handshake with a START_ACK, the server
@@ -2357,6 +2368,7 @@ TEST_F(WriteTransfer, Version2_RetryDuringHandshake) {
reader,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2369,25 +2381,25 @@ TEST_F(WriteTransfer, Version2_RetryDuringHandshake) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
// Time out waiting for a server response. The client should resend the
// initial packet.
- transfer_thread_.SimulateClientTimeout(3);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 2u);
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
// This time, respond with the correct continuation packet. The transfer
// should resume and complete normally.
context_.server().SendServerStream<Transfer::Write>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(31)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2396,13 +2408,13 @@ TEST_F(WriteTransfer, Version2_RetryDuringHandshake) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 31u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
rpc::test::WaitForPackets(context_.output(), 2, [this] {
context_.server().SendServerStream<Transfer::Write>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kParametersRetransmit)
- .set_session_id(31)
+ .set_session_id(1)
.set_offset(0)
.set_window_end_offset(64)
.set_max_chunk_size_bytes(32)));
@@ -2413,7 +2425,7 @@ TEST_F(WriteTransfer, Version2_RetryDuringHandshake) {
chunk = DecodeChunk(payloads[3]);
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 31u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_TRUE(chunk.has_payload());
EXPECT_EQ(std::memcmp(
@@ -2423,14 +2435,14 @@ TEST_F(WriteTransfer, Version2_RetryDuringHandshake) {
chunk = DecodeChunk(payloads[4]);
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 31u);
+ EXPECT_EQ(chunk.session_id(), 1u);
ASSERT_TRUE(chunk.remaining_bytes().has_value());
EXPECT_EQ(chunk.remaining_bytes().value(), 0u);
EXPECT_EQ(transfer_status, Status::Unknown());
context_.server().SendServerStream<Transfer::Write>(
- EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 31, OkStatus())));
+ EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 1, OkStatus())));
transfer_thread_.WaitUntilEventIsProcessed();
// Client should acknowledge the completion of the transfer.
@@ -2439,7 +2451,7 @@ TEST_F(WriteTransfer, Version2_RetryDuringHandshake) {
chunk = DecodeChunk(payloads[5]);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletionAck);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 31u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(transfer_status, OkStatus());
}
@@ -2454,6 +2466,7 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
reader,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2466,14 +2479,13 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
- // The server responds with a START_ACK, continuing the version 2 handshake
- // and assigning a session_id to the transfer.
+ // The server responds with a START_ACK, continuing the version 2 handshake.
context_.server().SendServerStream<Transfer::Write>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAck)
- .set_session_id(33)
+ .set_session_id(1)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2483,18 +2495,18 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
// Time out waiting for a server response. The client should resend the
// initial packet.
- transfer_thread_.SimulateClientTimeout(33);
+ transfer_thread_.SimulateClientTimeout(1);
ASSERT_EQ(payloads.size(), 3u);
chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAckConfirmation);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_FALSE(chunk.resource_id().has_value());
// This time, respond with the first transfer parameters chunk. The transfer
@@ -2502,7 +2514,7 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
rpc::test::WaitForPackets(context_.output(), 2, [this] {
context_.server().SendServerStream<Transfer::Write>(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kParametersRetransmit)
- .set_session_id(33)
+ .set_session_id(1)
.set_offset(0)
.set_window_end_offset(64)
.set_max_chunk_size_bytes(32)));
@@ -2513,7 +2525,7 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
chunk = DecodeChunk(payloads[3]);
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_TRUE(chunk.has_payload());
EXPECT_EQ(std::memcmp(
@@ -2523,14 +2535,14 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
chunk = DecodeChunk(payloads[4]);
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
ASSERT_TRUE(chunk.remaining_bytes().has_value());
EXPECT_EQ(chunk.remaining_bytes().value(), 0u);
EXPECT_EQ(transfer_status, Status::Unknown());
context_.server().SendServerStream<Transfer::Write>(
- EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 33, OkStatus())));
+ EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 1, OkStatus())));
transfer_thread_.WaitUntilEventIsProcessed();
// Client should acknowledge the completion of the transfer.
@@ -2539,7 +2551,7 @@ TEST_F(WriteTransfer, Version2_RetryAfterHandshake) {
chunk = DecodeChunk(payloads[5]);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletionAck);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 33u);
+ EXPECT_EQ(chunk.session_id(), 1u);
EXPECT_EQ(transfer_status, OkStatus());
}
@@ -2554,6 +2566,7 @@ TEST_F(WriteTransfer, Version2_ServerErrorDuringHandshake) {
reader,
[&transfer_status](Status status) { transfer_status = status; },
cfg::kDefaultChunkTimeout,
+ cfg::kDefaultChunkTimeout,
ProtocolVersion::kVersionTwo));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2566,12 +2579,12 @@ TEST_F(WriteTransfer, Version2_ServerErrorDuringHandshake) {
Chunk chunk = DecodeChunk(payloads.back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStart);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
- EXPECT_EQ(chunk.session_id(), 3u);
+ EXPECT_EQ(chunk.desired_session_id(), 1u);
EXPECT_EQ(chunk.resource_id(), 3u);
// The server responds to the start request with an error.
context_.server().SendServerStream<Transfer::Write>(EncodeChunk(
- Chunk::Final(ProtocolVersion::kVersionTwo, 3, Status::NotFound())));
+ Chunk::Final(ProtocolVersion::kVersionTwo, 1, Status::NotFound())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_EQ(payloads.size(), 1u);
diff --git a/pw_transfer/context.cc b/pw_transfer/context.cc
index 22686398c..8f64488a3 100644
--- a/pw_transfer/context.cc
+++ b/pw_transfer/context.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -33,6 +33,15 @@ void Context::HandleEvent(const Event& event) {
case EventType::kNewClientTransfer:
case EventType::kNewServerTransfer: {
if (active()) {
+ if (event.type == EventType::kNewServerTransfer &&
+ event.new_transfer.session_id == session_id_ &&
+ last_chunk_sent_ == Chunk::Type::kStartAck) {
+ // The client is retrying its initial chunk as the response may not
+ // have made it back. Re-send the handshake response without going
+ // through handler reinitialization.
+ RetryHandshake();
+ return;
+ }
Abort(Status::Aborted());
}
@@ -87,7 +96,7 @@ void Context::HandleEvent(const Event& event) {
void Context::InitiateTransferAsClient() {
PW_DCHECK(active());
- SetTimeout(chunk_timeout_);
+ SetTimeout(initial_chunk_timeout_);
PW_LOG_INFO("Starting transfer for resource %u",
static_cast<unsigned>(resource_id_));
@@ -104,7 +113,7 @@ void Context::InitiateTransferAsClient() {
if (type() == TransferType::kReceive) {
SendTransferParameters(TransmitAction::kBegin);
} else {
- SendInitialTransmitChunk();
+ SendInitialLegacyTransmitChunk();
}
LogTransferConfiguration();
@@ -113,6 +122,7 @@ void Context::InitiateTransferAsClient() {
// In newer protocol versions, begin the initial transfer handshake.
Chunk start_chunk(desired_protocol_version_, Chunk::Type::kStart);
+ start_chunk.set_desired_session_id(session_id_);
start_chunk.set_resource_id(resource_id_);
if (type() == TransferType::kReceive) {
@@ -159,11 +169,11 @@ bool Context::StartTransferAsServer(const NewTransferEvent& new_transfer) {
return true;
}
-void Context::SendInitialTransmitChunk() {
+void Context::SendInitialLegacyTransmitChunk() {
// A transmitter begins a transfer by sending the ID of the resource to which
// it wishes to write.
Chunk chunk(ProtocolVersion::kLegacy, Chunk::Type::kStart);
- chunk.set_session_id(session_id_);
+ chunk.set_session_id(resource_id_);
EncodeAndSendChunk(chunk);
}
@@ -294,6 +304,7 @@ void Context::Initialize(const NewTransferEvent& new_transfer) {
last_chunk_sent_ = Chunk::Type::kStart;
last_chunk_offset_ = 0;
chunk_timeout_ = new_transfer.timeout;
+ initial_chunk_timeout_ = new_transfer.initial_timeout;
interchunk_delay_ = chrono::SystemClock::for_at_least(
std::chrono::microseconds(kDefaultChunkDelayMicroseconds));
next_timeout_ = kNoTimeout;
@@ -348,18 +359,11 @@ void Context::PerformInitialHandshake(const Chunk& chunk) {
break;
}
- // Response packet sent from a server to a client. Contains the assigned
- // session_id of the transfer.
+ // Response packet sent from a server to a client, confirming the protocol
+ // version and session_id of the transfer.
case Chunk::Type::kStartAck: {
UpdateLocalProtocolConfigurationFromPeer(chunk);
- // Accept the assigned session_id and tell the server that the transfer
- // can begin.
- session_id_ = chunk.session_id();
- PW_LOG_DEBUG("Transfer for resource %u was assigned session ID %u",
- static_cast<unsigned>(resource_id_),
- static_cast<unsigned>(session_id_));
-
Chunk start_ack_confirmation(configured_protocol_version_,
Chunk::Type::kStartAckConfirmation);
start_ack_confirmation.set_session_id(session_id_);
@@ -398,8 +402,8 @@ void Context::PerformInitialHandshake(const Chunk& chunk) {
case Chunk::Type::kData:
case Chunk::Type::kParametersRetransmit:
case Chunk::Type::kParametersContinue:
- // Update the local context's session ID in case it was expecting one to
- // be assigned by the server.
+ // Update the local session_id, which will map to the transfer_id of the
+ // legacy chunk.
session_id_ = chunk.session_id();
configured_protocol_version_ = ProtocolVersion::kLegacy;
@@ -944,7 +948,14 @@ void Context::HandleTimeout() {
// A timeout occurring in a transfer or handshake state indicates that no
// chunk has been received from the other side. The transfer should retry
// its previous operation.
- SetTimeout(chunk_timeout_); // Retry() clears the timeout if it fails
+ //
+ // The timeout is set immediately. Retry() will clear it if it fails.
+ if (transfer_state_ == TransferState::kInitiating &&
+ last_chunk_sent_ == Chunk::Type::kStart) {
+ SetTimeout(initial_chunk_timeout_);
+ } else {
+ SetTimeout(chunk_timeout_);
+ }
Retry();
break;
@@ -1004,7 +1015,7 @@ void Context::Retry() {
PW_LOG_DEBUG(
"Transmit transfer %u timed out waiting for initial parameters",
static_cast<unsigned>(session_id_));
- SendInitialTransmitChunk();
+ SendInitialLegacyTransmitChunk();
return;
}
@@ -1032,6 +1043,7 @@ void Context::RetryHandshake() {
// No protocol version is yet configured at the time of sending the start
// chunk, so we use the client's desired version instead.
retry_chunk.set_protocol_version(desired_protocol_version_)
+ .set_desired_session_id(session_id_)
.set_resource_id(resource_id_);
if (type() == TransferType::kReceive) {
SetTransferParameters(retry_chunk);
diff --git a/pw_transfer/docs.rst b/pw_transfer/docs.rst
index 01e1b30f6..d63890c2a 100644
--- a/pw_transfer/docs.rst
+++ b/pw_transfer/docs.rst
@@ -268,6 +268,16 @@ more details.
The default amount of time, in milliseconds, to wait for a chunk to arrive
before retrying. This can later be configured per-transfer.
+.. c:macro:: PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS
+
+ The default amount of time, in milliseconds, to wait for an initial server
+ response to a transfer before retrying. This can later be configured
+ per-transfer.
+
+ This is set separately to PW_TRANSFER_DEFAULT_TIMEOUT_MS as transfers may
+ require additional time for resource initialization (e.g. erasing a flash
+ region before writing to it).
+
.. c:macro:: PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR
The fractional position within a window at which a receive transfer should
@@ -397,8 +407,9 @@ defined by the implementers of the server-side transfer node.
The series of chunks exchanged in an individual transfer operation for a
resource constitute a transfer *session*. The session runs from its opening
chunk until either a terminating chunk is received or the transfer times out.
-Sessions are assigned unique IDs by the transfer server in response to an
-initiating chunk from the client.
+Sessions are assigned IDs by the client that starts them, which are unique over
+the RPC channel between the client and server, allowing the server to identify
+transfers across multiple clients.
Reliability
===========
@@ -421,15 +432,16 @@ resource being transferred, assign a session ID, and synchronize the protocol
version to use.
A read or write transfer for a resource is initiated by a transfer client. The
-client sends the ID of the resource to the server in a ``START`` chunk,
-indicating that it wishes to begin a new transfer. This chunk additionally
-encodes the protocol version which the client is configured to use.
+client sends the ID of the resource to the server alongside a unique session ID
+in a ``START`` chunk, indicating that it wishes to begin a new transfer. This
+chunk additionally encodes the protocol version which the client is configured
+to use.
Upon receiving a ``START`` chunk, the transfer server checks whether the
requested resource is available. If so, it prepares the resource for the
operation, which typically involves opening a data stream, alongside any
-additional user-specified setup. The server generates a session ID, then
-responds to the client with a ``START_ACK`` chunk containing the resource,
+additional user-specified setup. The server accepts the client's session ID,
+then responds to the client with a ``START_ACK`` chunk containing the resource,
session, and configured protocol version for the transfer.
Transfer completion
@@ -618,7 +630,7 @@ correctness of implementations in different languages.
To run the tests on your machine, run
-.. code:: bash
+.. code-block:: bash
$ bazel test --features=c++17 \
pw_transfer/integration_test:cross_language_small_test \
@@ -633,7 +645,7 @@ The integration tests permit injection of client/server/proxy binaries to use
when running the tests. This allows manual testing of older versions of
pw_transfer against newer versions.
-.. code:: bash
+.. code-block:: bash
# Test a newer version of pw_transfer against an old C++ client that was
# backed up to another directory.
@@ -651,7 +663,7 @@ binaries and the latest binaries.
The CIPD package contents can be created with this command:
-.. code::bash
+.. code-block::bash
$ bazel build --features=c++17 pw_transfer/integration_test:server \
pw_transfer/integration_test:cpp_client
@@ -672,6 +684,11 @@ By default, these tests are not run in CQ (on presubmit) because they are too
slow. However, you can request that the tests be run in presubmit on your
change by adding to following line to the commit message footer:
-.. code::
+.. code-block::
+
+ Cq-Include-Trybots: luci.pigweed.try:pigweed-integration-transfer
+
+.. toctree::
+ :hidden:
- Cq-Include-Trybots: luci.pigweed.try:pigweed-integration-transfer
+ API reference <api>
diff --git a/pw_transfer/integration_test/BUILD.bazel b/pw_transfer/integration_test/BUILD.bazel
index 5cb92ee7a..e90d6b067 100644
--- a/pw_transfer/integration_test/BUILD.bazel
+++ b/pw_transfer/integration_test/BUILD.bazel
@@ -12,9 +12,9 @@
# License for the specific language governing permissions and limitations under
# the License.
-load("//pw_build:pigweed.bzl", "pw_cc_binary")
load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
+load("//pw_build:pigweed.bzl", "pw_cc_binary")
pw_cc_binary(
name = "server",
@@ -28,7 +28,6 @@ pw_cc_binary(
"//pw_stream",
"//pw_stream:std_file_stream",
"//pw_thread:thread",
- "//pw_thread_stl:thread_headers",
"//pw_transfer",
"@com_google_protobuf//:protobuf",
],
@@ -52,9 +51,11 @@ py_test(
"proxy.py",
"proxy_test.py",
],
+ imports = ["."],
main = "proxy_test.py",
deps = [
":config_pb2",
+ "//pw_hdlc/py:pw_hdlc",
"//pw_rpc:internal_packet_proto_pb2",
"//pw_transfer:transfer_proto_pb2",
"//pw_transfer/py:pw_transfer",
@@ -110,6 +111,7 @@ py_library(
":python_client",
":server",
],
+ imports = ["."],
deps = [
":config_pb2",
"//pw_protobuf:status_proto_pb2",
@@ -127,8 +129,11 @@ py_test(
srcs = [
"cross_language_large_write_test.py",
],
- # This test is not run in CQ because it's too slow.
- tags = ["manual"],
+ tags = [
+ # This test is not run in CQ because it's too slow.
+ "manual",
+ "integration",
+ ],
deps = [
":integration_test_fixture",
],
@@ -143,8 +148,11 @@ py_test(
srcs = [
"cross_language_large_read_test.py",
],
- # This test is not run in CQ because it's too slow.
- tags = ["manual"],
+ tags = [
+ # This test is not run in CQ because it's too slow.
+ "manual",
+ "integration",
+ ],
deps = [
":integration_test_fixture",
],
@@ -157,10 +165,12 @@ py_test(
srcs = [
"cross_language_medium_read_test.py",
],
+ tags = ["integration"],
deps = [
":config_pb2",
":integration_test_fixture",
"@com_google_protobuf//:protobuf_python",
+ "@python_packages_parameterized//:pkg",
],
)
@@ -171,10 +181,12 @@ py_test(
srcs = [
"cross_language_medium_write_test.py",
],
+ tags = ["integration"],
deps = [
":config_pb2",
":integration_test_fixture",
"@com_google_protobuf//:protobuf_python",
+ "@python_packages_parameterized//:pkg",
],
)
@@ -185,9 +197,11 @@ py_test(
srcs = [
"cross_language_small_test.py",
],
+ tags = ["integration"],
deps = [
":config_pb2",
":integration_test_fixture",
+ "@python_packages_parameterized//:pkg",
],
)
@@ -198,9 +212,11 @@ py_test(
srcs = [
"multi_transfer_test.py",
],
+ tags = ["integration"],
deps = [
":config_pb2",
":integration_test_fixture",
+ "@python_packages_parameterized//:pkg",
],
)
@@ -209,11 +225,13 @@ py_test(
name = "expected_errors_test",
timeout = "moderate",
srcs = ["expected_errors_test.py"],
+ tags = ["integration"],
deps = [
":config_pb2",
":integration_test_fixture",
"//pw_protobuf:status_proto_pb2",
"@com_google_protobuf//:protobuf_python",
+ "@python_packages_parameterized//:pkg",
],
)
@@ -225,10 +243,14 @@ py_test(
data = [
"@pw_transfer_test_binaries//:all",
],
+ tags = ["integration"],
+ # Legacy binaries were only built for linux-x86_64.
+ target_compatible_with = ["@platforms//os:linux"],
deps = [
":config_pb2",
":integration_test_fixture",
"//pw_protobuf:status_proto_pb2",
+ "@python_packages_parameterized//:pkg",
"@rules_python//python/runfiles",
],
)
@@ -259,5 +281,6 @@ py_binary(
"//pw_transfer:transfer_proto_pb2",
"//pw_transfer/py:pw_transfer",
"@com_google_protobuf//:protobuf_python",
+ "@python_packages_pyserial//:pkg",
],
)
diff --git a/pw_transfer/integration_test/client.cc b/pw_transfer/integration_test/client.cc
index 49bb3f267..1d932209c 100644
--- a/pw_transfer/integration_test/client.cc
+++ b/pw_transfer/integration_test/client.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -84,6 +84,15 @@ pw::Status PerformTransferActions(const pw::transfer::ClientConfig& config) {
transfer::Thread<2, 2> transfer_thread(chunk_buffer, encode_buffer);
thread::Thread system_thread(TransferThreadOptions(), transfer_thread);
+ // As much as we don't want to dynamically allocate an array,
+ // variable length arrays (VLA) are nonstandard, and a std::vector could cause
+ // references to go stale if the vector's underlying buffer is resized. This
+ // array of TransferResults needs to outlive the loop that performs the
+ // actual transfer actions due to how some references to TransferResult
+ // may persist beyond the lifetime of a transfer.
+ const int num_actions = config.transfer_actions().size();
+ auto transfer_results = std::make_unique<TransferResult[]>(num_actions);
+
pw::transfer::Client client(rpc::integration_test::client(),
rpc::integration_test::kChannelId,
transfer_thread);
@@ -92,8 +101,9 @@ pw::Status PerformTransferActions(const pw::transfer::ClientConfig& config) {
client.set_max_lifetime_retries(config.max_lifetime_retries());
Status status = pw::OkStatus();
- for (const pw::transfer::TransferAction& action : config.transfer_actions()) {
- TransferResult result;
+ for (int i = 0; i < num_actions; i++) {
+ const pw::transfer::TransferAction& action = config.transfer_actions()[i];
+ TransferResult& result = transfer_results[i];
// If no protocol version is specified, default to the latest version.
pw::transfer::ProtocolVersion protocol_version =
action.protocol_version() ==
@@ -114,6 +124,7 @@ pw::Status PerformTransferActions(const pw::transfer::ClientConfig& config) {
result.completed.release();
},
pw::transfer::cfg::kDefaultChunkTimeout,
+ pw::transfer::cfg::kDefaultInitialChunkTimeout,
protocol_version);
// Wait for the transfer to complete. We need to do this here so that the
// StdFileReader doesn't go out of scope.
@@ -131,6 +142,7 @@ pw::Status PerformTransferActions(const pw::transfer::ClientConfig& config) {
result.completed.release();
},
pw::transfer::cfg::kDefaultChunkTimeout,
+ pw::transfer::cfg::kDefaultInitialChunkTimeout,
protocol_version);
// Wait for the transfer to complete.
result.completed.acquire();
diff --git a/pw_transfer/integration_test/config.proto b/pw_transfer/integration_test/config.proto
index d120fc576..4fc7de336 100644
--- a/pw_transfer/integration_test/config.proto
+++ b/pw_transfer/integration_test/config.proto
@@ -49,7 +49,7 @@ message TransferAction {
// Expected final status of transfer operation.
//
- // TODO(b/241456982): This should be a pw.protobuf.StatusCode, but importing
+ // TODO: b/241456982 - This should be a pw.protobuf.StatusCode, but importing
// other Pigweed protos doesn't work in Bazel.
uint32 expected_status = 4;
diff --git a/pw_transfer/integration_test/cross_language_large_read_test.py b/pw_transfer/integration_test/cross_language_large_read_test.py
index 31d41e82e..37d51d9c2 100644
--- a/pw_transfer/integration_test/cross_language_large_read_test.py
+++ b/pw_transfer/integration_test/cross_language_large_read_test.py
@@ -35,7 +35,7 @@ import random
from google.protobuf import text_format
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import (
TransferConfig,
TransferIntegrationTest,
diff --git a/pw_transfer/integration_test/cross_language_large_write_test.py b/pw_transfer/integration_test/cross_language_large_write_test.py
index 0f195e2da..ee4c31878 100644
--- a/pw_transfer/integration_test/cross_language_large_write_test.py
+++ b/pw_transfer/integration_test/cross_language_large_write_test.py
@@ -35,7 +35,7 @@ import random
from google.protobuf import text_format
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import (
TransferConfig,
TransferIntegrationTest,
diff --git a/pw_transfer/integration_test/cross_language_medium_read_test.py b/pw_transfer/integration_test/cross_language_medium_read_test.py
index 8899d8f78..6adea14cd 100644
--- a/pw_transfer/integration_test/cross_language_medium_read_test.py
+++ b/pw_transfer/integration_test/cross_language_medium_read_test.py
@@ -37,7 +37,7 @@ import random
from google.protobuf import text_format
from pigweed.pw_transfer.integration_test import config_pb2
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import TransferIntegrationTestHarness, TransferConfig
_ALL_LANGUAGES = ("cpp", "java", "python")
diff --git a/pw_transfer/integration_test/cross_language_medium_write_test.py b/pw_transfer/integration_test/cross_language_medium_write_test.py
index 5f8c314dc..a4e33db9b 100644
--- a/pw_transfer/integration_test/cross_language_medium_write_test.py
+++ b/pw_transfer/integration_test/cross_language_medium_write_test.py
@@ -37,7 +37,7 @@ import random
from google.protobuf import text_format
from pigweed.pw_transfer.integration_test import config_pb2
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import TransferIntegrationTestHarness, TransferConfig
_ALL_LANGUAGES = ("cpp", "java", "python")
diff --git a/pw_transfer/integration_test/cross_language_small_test.py b/pw_transfer/integration_test/cross_language_small_test.py
index cfda8f0b4..fd05b7802 100644
--- a/pw_transfer/integration_test/cross_language_small_test.py
+++ b/pw_transfer/integration_test/cross_language_small_test.py
@@ -33,7 +33,7 @@ import itertools
from parameterized import parameterized
from pigweed.pw_transfer.integration_test import config_pb2
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import TransferIntegrationTestHarness
_ALL_LANGUAGES = ("cpp", "java", "python")
diff --git a/pw_transfer/integration_test/expected_errors_test.py b/pw_transfer/integration_test/expected_errors_test.py
index 64d90a390..9a12b6879 100644
--- a/pw_transfer/integration_test/expected_errors_test.py
+++ b/pw_transfer/integration_test/expected_errors_test.py
@@ -41,7 +41,7 @@ from google.protobuf import text_format
from pigweed.pw_transfer.integration_test import config_pb2
from pigweed.pw_protobuf.pw_protobuf_protos import status_pb2
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import TransferIntegrationTestHarness, TransferConfig
diff --git a/pw_transfer/integration_test/legacy_binaries_test.py b/pw_transfer/integration_test/legacy_binaries_test.py
index 7c6b8aa99..671260911 100644
--- a/pw_transfer/integration_test/legacy_binaries_test.py
+++ b/pw_transfer/integration_test/legacy_binaries_test.py
@@ -34,7 +34,7 @@ from parameterized import parameterized
import random
from pigweed.pw_transfer.integration_test import config_pb2
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import TransferIntegrationTestHarness
from rules_python.python.runfiles import runfiles
@@ -254,7 +254,10 @@ class LegacyTransferIntegrationTest(test_fixture.TransferIntegrationTest):
class LegacyClientTransferIntegrationTests(LegacyTransferIntegrationTest):
r = runfiles.Create()
- client_binary = r.Rlocation("pw_transfer_test_binaries/cpp_client_528098d5")
+ client_binary = r.Rlocation(
+ "pw_transfer_test_binaries/cpp_client_528098d5",
+ "pw_transfer_test_binaries",
+ )
HARNESS_CONFIG = TransferIntegrationTestHarness.Config(
cpp_client_binary=client_binary,
server_port=_SERVER_PORT,
@@ -265,7 +268,9 @@ class LegacyClientTransferIntegrationTests(LegacyTransferIntegrationTest):
class LegacyServerTransferIntegrationTests(LegacyTransferIntegrationTest):
r = runfiles.Create()
- server_binary = r.Rlocation("pw_transfer_test_binaries/server_528098d5")
+ server_binary = r.Rlocation(
+ "pw_transfer_test_binaries/server_528098d5", "pw_transfer_test_binaries"
+ )
HARNESS_CONFIG = TransferIntegrationTestHarness.Config(
server_binary=server_binary,
server_port=_SERVER_PORT,
diff --git a/pw_transfer/integration_test/multi_transfer_test.py b/pw_transfer/integration_test/multi_transfer_test.py
index 88ec079c1..90c792466 100644
--- a/pw_transfer/integration_test/multi_transfer_test.py
+++ b/pw_transfer/integration_test/multi_transfer_test.py
@@ -34,7 +34,7 @@ from parameterized import parameterized
import random
from typing import List
-import test_fixture
+from pigweed.pw_transfer.integration_test import test_fixture
from test_fixture import TransferIntegrationTestHarness, BasicTransfer
from pigweed.pw_transfer.integration_test import config_pb2
diff --git a/pw_transfer/integration_test/proxy.py b/pw_transfer/integration_test/proxy.py
index d9df6555c..553518f92 100644
--- a/pw_transfer/integration_test/proxy.py
+++ b/pw_transfer/integration_test/proxy.py
@@ -612,11 +612,11 @@ async def _main(server_port: int, client_port: int) -> None:
config = text_format.Parse(text_config, config_pb2.ProxyConfig())
# Instantiate the TCP server.
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ server_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
server_socket.setsockopt(
socket.SOL_SOCKET, socket.SO_RCVBUF, _RECEIVE_BUFFER_SIZE
)
- server_socket.bind(('localhost', client_port))
+ server_socket.bind(('', client_port))
server = await asyncio.start_server(
lambda reader, writer: _handle_connection(
server_port, config, reader, writer
diff --git a/pw_transfer/integration_test/python_client.py b/pw_transfer/integration_test/python_client.py
index 4c27189b0..2474cf121 100644
--- a/pw_transfer/integration_test/python_client.py
+++ b/pw_transfer/integration_test/python_client.py
@@ -19,7 +19,7 @@ import socket
import sys
from google.protobuf import text_format
-from pw_hdlc.rpc import HdlcRpcClient, default_channels
+from pw_hdlc.rpc import HdlcRpcClient, default_channels, SocketReader
from pw_status import Status
import pw_transfer
from pigweed.pw_transfer import transfer_pb2
@@ -32,6 +32,74 @@ _LOG.addHandler(logging.StreamHandler(sys.stdout))
HOSTNAME: str = "localhost"
+def _perform_transfer_action(
+ action: config_pb2.TransferAction, transfer_manager: pw_transfer.Manager
+) -> bool:
+ """Performs the transfer action and returns Truen on success."""
+ protocol_version = pw_transfer.ProtocolVersion(int(action.protocol_version))
+
+ # Default to the latest protocol version if none is specified.
+ if protocol_version == pw_transfer.ProtocolVersion.UNKNOWN:
+ protocol_version = pw_transfer.ProtocolVersion.LATEST
+
+ if (
+ action.transfer_type
+ == config_pb2.TransferAction.TransferType.WRITE_TO_SERVER
+ ):
+ try:
+ with open(action.file_path, 'rb') as f:
+ data = f.read()
+ except:
+ _LOG.critical("Failed to read input file '%s'", action.file_path)
+ return False
+
+ try:
+ transfer_manager.write(
+ action.resource_id,
+ data,
+ protocol_version=protocol_version,
+ )
+ except pw_transfer.client.Error as e:
+ if e.status != Status(action.expected_status):
+ _LOG.exception(
+ "Unexpected error encountered during write transfer"
+ )
+ return False
+ except:
+ _LOG.exception("Transfer (write to server) failed")
+ return False
+ elif (
+ action.transfer_type
+ == config_pb2.TransferAction.TransferType.READ_FROM_SERVER
+ ):
+ try:
+ data = transfer_manager.read(
+ action.resource_id,
+ protocol_version=protocol_version,
+ )
+ except pw_transfer.client.Error as e:
+ if e.status != Status(action.expected_status):
+ _LOG.exception(
+ "Unexpected error encountered during read transfer"
+ )
+ return False
+ return True
+ except:
+ _LOG.exception("Transfer (read from server) failed")
+ return False
+
+ try:
+ with open(action.file_path, 'wb') as f:
+ f.write(data)
+ except:
+ _LOG.critical("Failed to write output file '%s'", action.file_path)
+ return False
+ else:
+ _LOG.critical("Unknown transfer type: %d", action.transfer_type)
+ return False
+ return True
+
+
def _main() -> int:
if len(sys.argv) != 2:
_LOG.critical("Usage: PORT")
@@ -60,93 +128,36 @@ def _main() -> int:
_LOG.critical("Failed to connect to server at %s:%d", HOSTNAME, port)
return 1
- # Initialize an RPC client over the socket and set up the pw_transfer manager.
- rpc_client = HdlcRpcClient(
- lambda: rpc_socket.recv(4096),
- [transfer_pb2],
- default_channels(lambda data: rpc_socket.sendall(data)),
- lambda data: _LOG.info("%s", str(data)),
- )
- transfer_service = rpc_client.rpcs().pw.transfer.Transfer
- transfer_manager = pw_transfer.Manager(
- transfer_service,
- default_response_timeout_s=config.chunk_timeout_ms / 1000,
- initial_response_timeout_s=config.initial_chunk_timeout_ms / 1000,
- max_retries=config.max_retries,
- max_lifetime_retries=config.max_lifetime_retries,
- default_protocol_version=pw_transfer.ProtocolVersion.LATEST,
- )
-
- transfer_logger = logging.getLogger('pw_transfer')
- transfer_logger.setLevel(logging.DEBUG)
- transfer_logger.addHandler(logging.StreamHandler(sys.stdout))
-
- # Perform the requested transfer actions.
- for action in config.transfer_actions:
- protocol_version = pw_transfer.ProtocolVersion(
- int(action.protocol_version)
+ # Initialize an RPC client using a socket reader and set up the
+ # pw_transfer manager.
+ reader = SocketReader(rpc_socket, 4096)
+ with reader:
+ rpc_client = HdlcRpcClient(
+ reader,
+ [transfer_pb2],
+ default_channels(lambda data: rpc_socket.sendall(data)),
+ lambda data: _LOG.info("%s", str(data)),
)
-
- # Default to the latest protocol version if none is specified.
- if protocol_version == pw_transfer.ProtocolVersion.UNKNOWN:
- protocol_version = pw_transfer.ProtocolVersion.LATEST
-
- if (
- action.transfer_type
- == config_pb2.TransferAction.TransferType.WRITE_TO_SERVER
- ):
- try:
- with open(action.file_path, 'rb') as f:
- data = f.read()
- except:
- _LOG.critical(
- "Failed to read input file '%s'", action.file_path
- )
- return 1
-
- try:
- transfer_manager.write(
- action.resource_id, data, protocol_version=protocol_version
- )
- except pw_transfer.client.Error as e:
- if e.status != Status(action.expected_status):
- _LOG.exception(
- "Unexpected error encountered during write transfer"
- )
+ with rpc_client:
+ transfer_service = rpc_client.rpcs().pw.transfer.Transfer
+ transfer_manager = pw_transfer.Manager(
+ transfer_service,
+ default_response_timeout_s=config.chunk_timeout_ms / 1000,
+ initial_response_timeout_s=config.initial_chunk_timeout_ms
+ / 1000,
+ max_retries=config.max_retries,
+ max_lifetime_retries=config.max_lifetime_retries,
+ default_protocol_version=pw_transfer.ProtocolVersion.LATEST,
+ )
+
+ transfer_logger = logging.getLogger('pw_transfer')
+ transfer_logger.setLevel(logging.DEBUG)
+ transfer_logger.addHandler(logging.StreamHandler(sys.stdout))
+
+ # Perform the requested transfer actions.
+ for action in config.transfer_actions:
+ if not _perform_transfer_action(action, transfer_manager):
return 1
- except:
- _LOG.exception("Transfer (write to server) failed")
- return 1
- elif (
- action.transfer_type
- == config_pb2.TransferAction.TransferType.READ_FROM_SERVER
- ):
- try:
- data = transfer_manager.read(
- action.resource_id, protocol_version=protocol_version
- )
- except pw_transfer.client.Error as e:
- if e.status != Status(action.expected_status):
- _LOG.exception(
- "Unexpected error encountered during read transfer"
- )
- return 1
- continue
- except:
- _LOG.exception("Transfer (read from server) failed")
- return 1
-
- try:
- with open(action.file_path, 'wb') as f:
- f.write(data)
- except:
- _LOG.critical(
- "Failed to write output file '%s'", action.file_path
- )
- return 1
- else:
- _LOG.critical("Unknown transfer type: %d", action.transfer_type)
- return 1
_LOG.info("All transfers completed successfully")
return 0
diff --git a/pw_transfer/integration_test/test_fixture.py b/pw_transfer/integration_test/test_fixture.py
index a7b297c77..60323c223 100644
--- a/pw_transfer/integration_test/test_fixture.py
+++ b/pw_transfer/integration_test/test_fixture.py
@@ -343,7 +343,7 @@ class TransferIntegrationTestHarness:
class BasicTransfer(NamedTuple):
id: int
- type: config_pb2.TransferAction.TransferType.ValueType
+ type: 'config_pb2.TransferAction.TransferType.ValueType'
data: bytes
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/BUILD.bazel b/pw_transfer/java/main/dev/pigweed/pw_transfer/BUILD.bazel
index 03a9a007e..dffe540cd 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/BUILD.bazel
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/BUILD.bazel
@@ -39,6 +39,7 @@ java_library(
"//third_party/google_auto:value",
"@com_google_protobuf//java/lite",
"@maven//:com_google_code_findbugs_jsr305",
+ "@maven//:com_google_guava_failureaccess",
"@maven//:com_google_guava_guava",
],
)
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/ReadTransfer.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/ReadTransfer.java
index 15e3cd321..aa9389b77 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/ReadTransfer.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/ReadTransfer.java
@@ -52,6 +52,7 @@ class ReadTransfer extends Transfer<byte[]> {
private int lastReceivedOffset = 0;
ReadTransfer(int resourceId,
+ int sessionId,
ProtocolVersion desiredProtocolVersion,
TransferInterface transferManager,
TransferTimeoutSettings timeoutSettings,
@@ -59,6 +60,7 @@ class ReadTransfer extends Transfer<byte[]> {
Consumer<TransferProgress> progressCallback,
BooleanSupplier shouldAbortCallback) {
super(resourceId,
+ sessionId,
desiredProtocolVersion,
transferManager,
timeoutSettings,
@@ -68,6 +70,10 @@ class ReadTransfer extends Transfer<byte[]> {
this.windowEndOffset = parameters.maxPendingBytes();
}
+ final TransferParameters getParametersForTest() {
+ return parameters;
+ }
+
@Override
State getWaitingForDataState() {
return new ReceivingData();
@@ -183,7 +189,7 @@ class ReadTransfer extends Transfer<byte[]> {
ByteBuffer result = ByteBuffer.allocate(totalDataSize);
dataChunks.forEach(result::put);
- getFuture().set(result.array());
+ set(result.array());
}
private VersionedChunk prepareTransferParameters(boolean extend) {
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/Transfer.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/Transfer.java
index fef83e59e..8183fb848 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/Transfer.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/Transfer.java
@@ -17,6 +17,7 @@ package dev.pigweed.pw_transfer;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static dev.pigweed.pw_transfer.TransferProgress.UNKNOWN_TRANSFER_SIZE;
+import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.SettableFuture;
import dev.pigweed.pw_log.Logger;
import dev.pigweed.pw_rpc.Status;
@@ -28,7 +29,7 @@ import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
/** Base class for tracking the state of a read or write transfer. */
-abstract class Transfer<T> {
+abstract class Transfer<T> extends AbstractFuture<T> {
private static final Logger logger = Logger.forClass(Transfer.class);
// Largest nanosecond instant. Used to block indefinitely when no transfers are pending.
@@ -38,15 +39,14 @@ abstract class Transfer<T> {
static final boolean VERBOSE_LOGGING = false;
private final int resourceId;
+ private final int sessionId;
private final ProtocolVersion desiredProtocolVersion;
private final TransferEventHandler.TransferInterface eventHandler;
- private final SettableFuture<T> future;
private final TransferTimeoutSettings timeoutSettings;
private final Consumer<TransferProgress> progressCallback;
private final BooleanSupplier shouldAbortCallback;
private final Instant startTime;
- private int sessionId = VersionedChunk.UNASSIGNED_SESSION_ID;
private ProtocolVersion configuredProtocolVersion = ProtocolVersion.UNKNOWN;
private Instant deadline = NO_TIMEOUT;
private State state;
@@ -68,23 +68,24 @@ abstract class Transfer<T> {
* @param shouldAbortCallback BooleanSupplier that returns true if a transfer should be aborted.
*/
Transfer(int resourceId,
+ int sessionId,
ProtocolVersion desiredProtocolVersion,
TransferInterface eventHandler,
TransferTimeoutSettings timeoutSettings,
Consumer<TransferProgress> progressCallback,
BooleanSupplier shouldAbortCallback) {
this.resourceId = resourceId;
+ this.sessionId = sessionId;
this.desiredProtocolVersion = desiredProtocolVersion;
this.eventHandler = eventHandler;
- this.future = SettableFuture.create();
this.timeoutSettings = timeoutSettings;
this.progressCallback = progressCallback;
this.shouldAbortCallback = shouldAbortCallback;
// If the future is cancelled, tell the TransferEventHandler to cancel the transfer.
- future.addListener(() -> {
- if (future.isCancelled()) {
+ addListener(() -> {
+ if (isCancelled()) {
eventHandler.cancelTransfer(this);
}
}, directExecutor());
@@ -92,7 +93,6 @@ abstract class Transfer<T> {
if (desiredProtocolVersion == ProtocolVersion.LEGACY) {
// Legacy transfers skip protocol negotiation stage and use the resource ID as the session ID.
configuredProtocolVersion = ProtocolVersion.LEGACY;
- assignSessionId(resourceId);
state = getWaitingForDataState();
} else {
state = new Initiating();
@@ -119,9 +119,8 @@ abstract class Transfer<T> {
return sessionId;
}
- private void assignSessionId(int newSessionId) {
- sessionId = newSessionId;
- eventHandler.assignSessionId(this);
+ final ProtocolVersion getDesiredProtocolVersionForTest() {
+ return desiredProtocolVersion;
}
/** Terminates the transfer without sending any packets. */
@@ -145,10 +144,6 @@ abstract class Transfer<T> {
deadline = Instant.now().plusNanos((long) timeoutMicros * 1000);
}
- final SettableFuture<T> getFuture() {
- return future;
- }
-
final void start() {
logger.atInfo().log(
"%s starting with parameters: default timeout %d ms, initial timeout %d ms, %d max retires",
@@ -157,7 +152,7 @@ abstract class Transfer<T> {
timeoutSettings.initialTimeoutMillis(),
timeoutSettings.maxRetries());
VersionedChunk.Builder chunk =
- VersionedChunk.createInitialChunk(desiredProtocolVersion, resourceId);
+ VersionedChunk.createInitialChunk(desiredProtocolVersion, resourceId, sessionId);
prepareInitialChunk(chunk);
try {
sendChunk(chunk.build());
@@ -244,7 +239,8 @@ abstract class Transfer<T> {
.setVersion(configuredProtocolVersion != ProtocolVersion.UNKNOWN ? configuredProtocolVersion
: desiredProtocolVersion)
.setType(type)
- .setSessionId(sessionId);
+ .setSessionId(sessionId)
+ .setResourceId(resourceId);
}
final VersionedChunk getLastChunkSent() {
@@ -393,8 +389,6 @@ abstract class Transfer<T> {
private class Initiating extends ActiveState {
@Override
public void handleDataChunk(VersionedChunk chunk) throws TransferAbortedException {
- assignSessionId(chunk.sessionId());
-
if (chunk.version() == ProtocolVersion.UNKNOWN) {
logger.atWarning().log(
"%s aborting due to unsupported protocol version: %s", Transfer.this, chunk);
@@ -495,7 +489,7 @@ abstract class Transfer<T> {
if (status.ok()) {
setFutureResult();
} else {
- future.setException(new TransferError(Transfer.this, status));
+ setException(new TransferError(Transfer.this, status));
}
}
@@ -503,7 +497,7 @@ abstract class Transfer<T> {
Completed(TransferError exception) {
cleanUp();
logger.atWarning().withCause(exception).log("%s terminated with exception", Transfer.this);
- future.setException(exception);
+ setException(exception);
}
private void cleanUp() {
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferClient.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferClient.java
index 248e50ea9..13779005a 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferClient.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferClient.java
@@ -35,7 +35,7 @@ public class TransferClient {
private final TransferEventHandler transferEventHandler;
private final Thread transferEventHandlerThread;
- private ProtocolVersion desiredProtocolVersion = ProtocolVersion.latest();
+ private ProtocolVersion desiredProtocolVersion = ProtocolVersion.LEGACY;
/**
* Creates a new transfer client for sending and receiving data with pw_transfer.
@@ -153,7 +153,22 @@ public class TransferClient {
transferEventHandlerThread.join();
}
- void waitUntilEventsAreProcessedForTest() {
+ // Functions for test use only.
+ // TODO: b/279808806 - These could be annotated with test-only visibility.
+
+ final void waitUntilEventsAreProcessedForTest() {
transferEventHandler.waitUntilEventsAreProcessedForTest();
}
+
+ final int getNextSessionIdForTest() {
+ return transferEventHandler.getNextSessionIdForTest();
+ }
+
+ final WriteTransfer getWriteTransferForTest(ListenableFuture<?> transferFuture) {
+ return (WriteTransfer) transferFuture;
+ }
+
+ final ReadTransfer getReadTransferForTest(ListenableFuture<?> transferFuture) {
+ return (ReadTransfer) transferFuture;
+ }
}
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferEventHandler.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferEventHandler.java
index f4e8189f7..a37d05117 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferEventHandler.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferEventHandler.java
@@ -51,14 +51,18 @@ class TransferEventHandler {
private final BlockingQueue<Event> events = new LinkedBlockingQueue<>();
- // Map resource ID to transfer, and session ID to resource ID.
- private final Map<Integer, Transfer<?>> resourceIdToTransfer = new HashMap<>();
- private final Map<Integer, Integer> sessionToResourceId = new HashMap<>();
+ // Map session ID to transfer.
+ private final Map<Integer, Transfer<?>> sessionIdToTransfer = new HashMap<>();
+ // Legacy transfers only use the resource ID. The client assigns an arbitrary session ID that
+ // legacy servers ignore. The client then maps from the legacy ID to its local session ID.
+ private final Map<Integer, Integer> legacyIdToSessionId = new HashMap<>();
@Nullable private Call.ClientStreaming<Chunk> readStream = null;
@Nullable private Call.ClientStreaming<Chunk> writeStream = null;
private boolean processEvents = true;
+ private int nextSessionId = 1;
+
TransferEventHandler(MethodClient readMethod, MethodClient writeMethod) {
this.readMethod = readMethod;
this.writeMethod = writeMethod;
@@ -70,8 +74,8 @@ class TransferEventHandler {
byte[] data,
Consumer<TransferProgress> progressCallback,
BooleanSupplier shouldAbortCallback) {
- WriteTransfer transfer =
- new WriteTransfer(resourceId, desiredProtocolVersion, new TransferInterface() {
+ WriteTransfer transfer = new WriteTransfer(
+ resourceId, assignSessionId(), desiredProtocolVersion, new TransferInterface() {
@Override
Call.ClientStreaming<Chunk> getStream() throws ChannelOutputException {
if (writeStream == null) {
@@ -86,7 +90,7 @@ class TransferEventHandler {
}
}, settings, data, progressCallback, shouldAbortCallback);
startTransferAsClient(transfer);
- return transfer.getFuture();
+ return transfer;
}
ListenableFuture<byte[]> startReadTransferAsClient(int resourceId,
@@ -95,8 +99,8 @@ class TransferEventHandler {
TransferParameters parameters,
Consumer<TransferProgress> progressCallback,
BooleanSupplier shouldAbortCallback) {
- ReadTransfer transfer =
- new ReadTransfer(resourceId, desiredProtocolVersion, new TransferInterface() {
+ ReadTransfer transfer = new ReadTransfer(
+ resourceId, assignSessionId(), desiredProtocolVersion, new TransferInterface() {
@Override
Call.ClientStreaming<Chunk> getStream() throws ChannelOutputException {
if (readStream == null) {
@@ -111,19 +115,27 @@ class TransferEventHandler {
}
}, settings, parameters, progressCallback, shouldAbortCallback);
startTransferAsClient(transfer);
- return transfer.getFuture();
+ return transfer;
}
private void startTransferAsClient(Transfer<?> transfer) {
enqueueEvent(() -> {
- if (resourceIdToTransfer.containsKey(transfer.getResourceId())) {
- transfer.terminate(new TransferError("A transfer for resource ID "
- + transfer.getResourceId()
- + " is already in progress! Only one read/write transfer per resource is supported at a time",
- Status.ALREADY_EXISTS));
+ if (sessionIdToTransfer.containsKey(transfer.getSessionId())) {
+ throw new AssertionError("Duplicate session ID " + transfer.getSessionId());
+ }
+
+ // The v2 protocol supports multiple transfers for a single resource. For simplicity while
+ // supporting both protocols, only support a single transfer per resource.
+ if (legacyIdToSessionId.containsKey(transfer.getResourceId())) {
+ transfer.terminate(
+ new TransferError("A transfer for resource ID " + transfer.getResourceId()
+ + " is already in progress! Only one read/write transfer per resource is "
+ + "supported at a time",
+ Status.ALREADY_EXISTS));
return;
}
- resourceIdToTransfer.put(transfer.getResourceId(), transfer);
+ sessionIdToTransfer.put(transfer.getSessionId(), transfer);
+ legacyIdToSessionId.put(transfer.getResourceId(), transfer.getSessionId());
transfer.start();
});
}
@@ -159,7 +171,7 @@ class TransferEventHandler {
void stop() {
enqueueEvent(() -> {
logger.atFine().log("Terminating TransferEventHandler");
- resourceIdToTransfer.values().forEach(Transfer::handleTermination);
+ sessionIdToTransfer.values().forEach(Transfer::handleTermination);
processEvents = false;
});
}
@@ -175,6 +187,16 @@ class TransferEventHandler {
}
}
+ /** Generates the session ID to use for the next transfer. */
+ private int assignSessionId() {
+ return nextSessionId++;
+ }
+
+ /** Returns the session ID that will be used for the next transfer. */
+ final int getNextSessionIdForTest() {
+ return nextSessionId;
+ }
+
private void enqueueEvent(Event event) {
while (true) {
try {
@@ -199,14 +221,14 @@ class TransferEventHandler {
}
private void handleTimeouts() {
- for (Transfer<?> transfer : resourceIdToTransfer.values()) {
+ for (Transfer<?> transfer : sessionIdToTransfer.values()) {
transfer.handleTimeoutIfDeadlineExceeded();
}
}
private Instant getNextTimeout() {
Optional<Transfer<?>> transfer =
- resourceIdToTransfer.values().stream().min(Comparator.comparing(Transfer::getDeadline));
+ sessionIdToTransfer.values().stream().min(Comparator.comparing(Transfer::getDeadline));
return transfer.isPresent() ? transfer.get().getDeadline() : Transfer.NO_TIMEOUT;
}
@@ -228,22 +250,16 @@ class TransferEventHandler {
}
/**
- * Associates the transfer's session ID with its resource ID.
- *
- * Must be called on the transfer thread.
- */
- void assignSessionId(Transfer<?> transfer) {
- sessionToResourceId.put(transfer.getSessionId(), transfer.getResourceId());
- }
-
- /**
* Removes this transfer from the list of active transfers.
*
* Must be called on the transfer thread.
*/
+ // TODO(frolv): Investigate why this is occurring -- there shouldn't be any
+ // futures here.
+ @SuppressWarnings("FutureReturnValueIgnored")
void unregisterTransfer(Transfer<?> transfer) {
- resourceIdToTransfer.remove(transfer.getResourceId());
- sessionToResourceId.remove(transfer.getSessionId());
+ sessionIdToTransfer.remove(transfer.getSessionId());
+ legacyIdToSessionId.remove(transfer.getResourceId());
}
/**
@@ -263,25 +279,18 @@ class TransferEventHandler {
private abstract class ChunkHandler implements StreamObserver<Chunk> {
@Override
public final void onNext(Chunk chunkProto) {
- VersionedChunk chunk = VersionedChunk.fromMessage(chunkProto);
+ VersionedChunk chunk = VersionedChunk.fromMessage(chunkProto, legacyIdToSessionId);
enqueueEvent(() -> {
- Transfer<?> transfer = null;
- if (chunk.resourceId().isPresent()) {
- transfer = resourceIdToTransfer.get(chunk.resourceId().getAsInt());
- } else {
- Integer resourceId = sessionToResourceId.get(chunk.sessionId());
- if (resourceId != null) {
- transfer = resourceIdToTransfer.get(resourceId);
- }
- }
-
- if (transfer != null) {
- logger.atFinest().log("%s received chunk: %s", transfer, chunk);
- transfer.handleChunk(chunk);
- } else {
+ Transfer<?> transfer;
+ if (chunk.sessionId() == VersionedChunk.UNKNOWN_SESSION_ID
+ || (transfer = sessionIdToTransfer.get(chunk.sessionId())) == null) {
logger.atInfo().log("Ignoring unrecognized transfer chunk: %s", chunk);
+ return;
}
+
+ logger.atFinest().log("%s received chunk: %s", transfer, chunk);
+ transfer.handleChunk(chunk);
});
}
@@ -296,7 +305,7 @@ class TransferEventHandler {
resetStream();
// The transfers remove themselves from the Map during cleanup, iterate over a copied list.
- List<Transfer<?>> activeTransfers = new ArrayList<>(resourceIdToTransfer.values());
+ List<Transfer<?>> activeTransfers = new ArrayList<>(sessionIdToTransfer.values());
// FAILED_PRECONDITION indicates that the stream packet was not recognized as the stream is
// not open. This could occur if the server resets. Notify pending transfers that this has
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferService.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferService.java
index f2c21b496..c11151fcd 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferService.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/TransferService.java
@@ -21,8 +21,8 @@ import dev.pigweed.pw_rpc.Service;
/** Provides a service definition for the pw_transfer service. */
public class TransferService {
private static final Service SERVICE = new Service("pw.transfer.Transfer",
- bidirectionalStreamingMethod("Read", Chunk.class, Chunk.class),
- bidirectionalStreamingMethod("Write", Chunk.class, Chunk.class));
+ bidirectionalStreamingMethod("Read", Chunk.parser(), Chunk.parser()),
+ bidirectionalStreamingMethod("Write", Chunk.parser(), Chunk.parser()));
public static Service get() {
return SERVICE;
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/VersionedChunk.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/VersionedChunk.java
index 9fdfb4d1e..27261888b 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/VersionedChunk.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/VersionedChunk.java
@@ -17,6 +17,7 @@ package dev.pigweed.pw_transfer;
import com.google.auto.value.AutoValue;
import com.google.protobuf.ByteString;
import dev.pigweed.pw_rpc.Status;
+import java.util.Map;
import java.util.OptionalInt;
import java.util.OptionalLong;
@@ -25,7 +26,8 @@ import java.util.OptionalLong;
*/
@AutoValue
abstract class VersionedChunk {
- public static final int UNASSIGNED_SESSION_ID = 0;
+ // Invalid session ID used for legacy chunks that do not map to a known session ID.
+ public static final int UNKNOWN_SESSION_ID = 0;
public abstract ProtocolVersion version();
@@ -49,9 +51,11 @@ abstract class VersionedChunk {
public abstract OptionalInt status();
+ public abstract OptionalInt desiredSessionId();
+
public static Builder builder() {
return new AutoValue_VersionedChunk.Builder()
- .setSessionId(UNASSIGNED_SESSION_ID)
+ .setSessionId(UNKNOWN_SESSION_ID)
.setOffset(0)
.setWindowEndOffset(0)
.setData(ByteString.EMPTY);
@@ -85,10 +89,12 @@ abstract class VersionedChunk {
abstract Builder setStatus(int statusCode);
+ abstract Builder setDesiredSessionId(int desiredSessionId);
+
public abstract VersionedChunk build();
}
- public static VersionedChunk fromMessage(Chunk chunk) {
+ public static VersionedChunk fromMessage(Chunk chunk, Map<Integer, Integer> legacyIdToSessionId) {
Builder builder = builder();
ProtocolVersion version;
@@ -119,7 +125,8 @@ abstract class VersionedChunk {
// For legacy chunks, use the transfer ID as both the resource and session IDs.
if (version == ProtocolVersion.LEGACY) {
- builder.setSessionId(chunk.getTransferId());
+ builder.setSessionId(
+ legacyIdToSessionId.getOrDefault(chunk.getTransferId(), UNKNOWN_SESSION_ID));
builder.setResourceId(chunk.getTransferId());
if (chunk.hasStatus()) {
builder.setType(Chunk.Type.COMPLETION);
@@ -128,6 +135,10 @@ abstract class VersionedChunk {
builder.setSessionId(chunk.getSessionId());
}
+ if (chunk.hasDesiredSessionId()) {
+ builder.setDesiredSessionId(chunk.getDesiredSessionId());
+ }
+
builder.setOffset((int) chunk.getOffset()).setData(chunk.getData());
if (chunk.hasResourceId()) {
@@ -154,8 +165,15 @@ abstract class VersionedChunk {
}
public static VersionedChunk.Builder createInitialChunk(
- ProtocolVersion desiredVersion, int resourceId) {
- return builder().setVersion(desiredVersion).setType(Chunk.Type.START).setResourceId(resourceId);
+ ProtocolVersion desiredVersion, int resourceId, int sessionId) {
+ VersionedChunk.Builder builder =
+ builder().setVersion(desiredVersion).setType(Chunk.Type.START).setResourceId(resourceId);
+
+ if (desiredVersion != ProtocolVersion.LEGACY) {
+ builder.setDesiredSessionId(sessionId);
+ }
+
+ return builder;
}
public Chunk toMessage() {
@@ -165,19 +183,24 @@ abstract class VersionedChunk {
.setWindowEndOffset(windowEndOffset())
.setData(data());
- resourceId().ifPresent(chunk::setResourceId);
remainingBytes().ifPresent(chunk::setRemainingBytes);
maxChunkSizeBytes().ifPresent(chunk::setMaxChunkSizeBytes);
minDelayMicroseconds().ifPresent(chunk::setMinDelayMicroseconds);
status().ifPresent(chunk::setStatus);
+ desiredSessionId().ifPresent(chunk::setDesiredSessionId);
+
+ // The resourceId is only needed for START chunks.
+ if (type() == Chunk.Type.START) {
+ chunk.setResourceId(resourceId().getAsInt()); // resourceId must be set for start chunks
+ }
// session_id did not exist in the legacy protocol, so don't send it.
- if (version() != ProtocolVersion.LEGACY && sessionId() != UNASSIGNED_SESSION_ID) {
+ if (version() != ProtocolVersion.LEGACY && sessionId() != UNKNOWN_SESSION_ID) {
chunk.setSessionId(sessionId());
}
if (shouldEncodeLegacyFields()) {
- chunk.setTransferId(resourceId().orElse(sessionId()));
+ chunk.setTransferId(resourceId().getAsInt()); // resourceId must be set for legacy transfers
if (chunk.getWindowEndOffset() != 0) {
chunk.setPendingBytes(chunk.getWindowEndOffset() - offset());
diff --git a/pw_transfer/java/main/dev/pigweed/pw_transfer/WriteTransfer.java b/pw_transfer/java/main/dev/pigweed/pw_transfer/WriteTransfer.java
index daf5213f7..d72149f5f 100644
--- a/pw_transfer/java/main/dev/pigweed/pw_transfer/WriteTransfer.java
+++ b/pw_transfer/java/main/dev/pigweed/pw_transfer/WriteTransfer.java
@@ -38,6 +38,7 @@ class WriteTransfer extends Transfer<Void> {
private final byte[] data;
protected WriteTransfer(int resourceId,
+ int sessionId,
ProtocolVersion desiredProtocolVersion,
TransferInterface transferManager,
TransferTimeoutSettings timeoutSettings,
@@ -45,6 +46,7 @@ class WriteTransfer extends Transfer<Void> {
Consumer<TransferProgress> progressCallback,
BooleanSupplier shouldAbortCallback) {
super(resourceId,
+ sessionId,
desiredProtocolVersion,
transferManager,
timeoutSettings,
@@ -124,7 +126,7 @@ class WriteTransfer extends Transfer<Void> {
@Override
void setFutureResult() {
updateProgress(data.length, data.length, data.length);
- getFuture().set(null);
+ set(null);
}
private void updateTransferParameters(VersionedChunk chunk) throws TransferAbortedException {
diff --git a/pw_transfer/java/test/dev/pigweed/pw_transfer/TransferClientTest.java b/pw_transfer/java/test/dev/pigweed/pw_transfer/TransferClientTest.java
index 3a61b362a..898f0e9b0 100644
--- a/pw_transfer/java/test/dev/pigweed/pw_transfer/TransferClientTest.java
+++ b/pw_transfer/java/test/dev/pigweed/pw_transfer/TransferClientTest.java
@@ -92,11 +92,11 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<byte[]> future = transferClient.read(1, TRANSFER_PARAMETERS);
- assertThat(lastChunks()).containsExactly(initialReadChunk(1, ProtocolVersion.LEGACY));
+ assertThat(lastChunks()).containsExactly(initialLegacyReadChunk(1));
receiveReadServerError(Status.FAILED_PRECONDITION);
- assertThat(lastChunks()).containsExactly(initialReadChunk(1, ProtocolVersion.LEGACY));
+ assertThat(lastChunks()).containsExactly(initialLegacyReadChunk(1));
receiveReadChunks(newLegacyChunk(Chunk.Type.DATA, 1)
.setOffset(0)
@@ -112,7 +112,7 @@ public final class TransferClientTest {
TransferParameters params = TransferParameters.create(50, 50, 0);
ListenableFuture<byte[]> future = transferClient.read(1, params);
- assertThat(lastChunks()).containsExactly(initialReadChunk(1, ProtocolVersion.LEGACY, params));
+ assertThat(lastChunks()).containsExactly(initialLegacyReadChunk(1, params));
receiveReadChunks(legacyDataChunk(1, TEST_DATA_100B, 0, 50));
@@ -140,7 +140,7 @@ public final class TransferClientTest {
receiveReadServerError(Status.FAILED_PRECONDITION);
}
- Chunk initialChunk = initialReadChunk(1, ProtocolVersion.LEGACY);
+ Chunk initialChunk = initialLegacyReadChunk(1);
assertThat(lastChunks())
.containsExactlyElementsIn(Collections.nCopies(1 + MAX_RETRIES, initialChunk));
@@ -207,7 +207,7 @@ public final class TransferClientTest {
TransferParameters params = TransferParameters.create(3, 2, 1);
ListenableFuture<byte[]> future = transferClient.read(99, params);
- assertThat(lastChunks()).containsExactly(initialReadChunk(99, ProtocolVersion.LEGACY, params));
+ assertThat(lastChunks()).containsExactly(initialLegacyReadChunk(99, params));
assertThat(future.cancel(true)).isTrue();
}
@@ -216,7 +216,7 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<byte[]> future = transferClient.read(123, TRANSFER_PARAMETERS);
- assertThat(lastChunks()).containsExactly(initialReadChunk(123, ProtocolVersion.LEGACY));
+ assertThat(lastChunks()).containsExactly(initialLegacyReadChunk(123));
receiveReadChunks(newLegacyChunk(Chunk.Type.DATA, 123)
.setOffset(0)
@@ -291,7 +291,7 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<byte[]> future = transferClient.read(123, TRANSFER_PARAMETERS);
- assertThat(lastChunks()).containsExactly(initialReadChunk(123, ProtocolVersion.LEGACY));
+ assertThat(lastChunks()).containsExactly(initialLegacyReadChunk(123));
receiveReadChunks(newLegacyChunk(Chunk.Type.DATA, 123).setOffset(50).setData(range(30, 50)));
@@ -448,9 +448,8 @@ public final class TransferClientTest {
// read should have retried sending the transfer parameters 2 times, for a total of 3
assertThat(lastChunks())
- .containsExactly(initialReadChunk(123, ProtocolVersion.LEGACY),
- initialReadChunk(123, ProtocolVersion.LEGACY),
- initialReadChunk(123, ProtocolVersion.LEGACY));
+ .containsExactly(
+ initialLegacyReadChunk(123), initialLegacyReadChunk(123), initialLegacyReadChunk(123));
}
@Test
@@ -491,13 +490,11 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size()));
+ assertThat(lastChunks()).containsExactly(initialLegacyWriteChunk(2, TEST_DATA_SHORT.size()));
receiveWriteServerError(Status.FAILED_PRECONDITION);
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size()));
+ assertThat(lastChunks()).containsExactly(initialLegacyWriteChunk(2, TEST_DATA_SHORT.size()));
receiveWriteChunks(newLegacyChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 2)
.setOffset(0)
@@ -519,7 +516,7 @@ public final class TransferClientTest {
.setMaxChunkSizeBytes(50));
assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.LEGACY, TEST_DATA_100B.size()),
+ .containsExactly(initialLegacyWriteChunk(2, TEST_DATA_100B.size()),
legacyDataChunk(2, TEST_DATA_100B, 0, 50));
receiveWriteServerError(Status.FAILED_PRECONDITION);
@@ -538,7 +535,7 @@ public final class TransferClientTest {
receiveWriteServerError(Status.FAILED_PRECONDITION);
}
- Chunk initialChunk = initialWriteChunk(2, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size());
+ Chunk initialChunk = initialLegacyWriteChunk(2, TEST_DATA_SHORT.size());
assertThat(lastChunks())
.containsExactlyElementsIn(Collections.nCopies(1 + MAX_RETRIES, initialChunk));
@@ -559,7 +556,7 @@ public final class TransferClientTest {
receiveWriteChunks(legacyFinalChunk(2, Status.OK));
- assertThat(lastChunks()).containsExactly(initialWriteChunk(2, ProtocolVersion.LEGACY, 0));
+ assertThat(lastChunks()).containsExactly(initialLegacyWriteChunk(2, 0));
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
}
@@ -569,8 +566,7 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_100B.toByteArray());
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_100B.size()));
+ assertThat(lastChunks()).containsExactly(initialLegacyWriteChunk(123, TEST_DATA_100B.size()));
receiveWriteChunks(newLegacyChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
.setOffset(0)
@@ -615,8 +611,7 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_100B.toByteArray());
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_100B.size()));
+ assertThat(lastChunks()).containsExactly(initialLegacyWriteChunk(123, TEST_DATA_100B.size()));
receiveWriteChunks(newLegacyChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
.setOffset(0)
@@ -664,8 +659,7 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.LEGACY);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_100B.toByteArray());
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_100B.size()));
+ assertThat(lastChunks()).containsExactly(initialLegacyWriteChunk(123, TEST_DATA_100B.size()));
receiveWriteChunks(newLegacyChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
.setOffset(0)
@@ -734,7 +728,7 @@ public final class TransferClientTest {
.setMaxChunkSizeBytes(25));
assertThat(lastChunks())
- .containsExactly(initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_100B.size()),
+ .containsExactly(initialLegacyWriteChunk(123, TEST_DATA_100B.size()),
newLegacyChunk(Chunk.Type.DATA, 123).setOffset(100).setRemainingBytes(0).build());
assertThat(future.isDone()).isFalse();
}
@@ -800,7 +794,7 @@ public final class TransferClientTest {
.setMaxChunkSizeBytes(25));
assertThat(lastChunks())
- .containsExactly(initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_100B.size()),
+ .containsExactly(initialLegacyWriteChunk(123, TEST_DATA_100B.size()),
legacyFinalChunk(123, Status.OUT_OF_RANGE));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
@@ -874,10 +868,9 @@ public final class TransferClientTest {
// Client should have resent the last chunk (the initial chunk in this case) for each timeout.
assertThat(lastChunks())
- .containsExactly(
- initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size()), // initial
- initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size()), // retry 1
- initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size())); // retry 2
+ .containsExactly(initialLegacyWriteChunk(123, TEST_DATA_SHORT.size()), // initial
+ initialLegacyWriteChunk(123, TEST_DATA_SHORT.size()), // retry 1
+ initialLegacyWriteChunk(123, TEST_DATA_SHORT.size())); // retry 2
}
@Test
@@ -901,8 +894,7 @@ public final class TransferClientTest {
.setRemainingBytes(0)
.build();
assertThat(lastChunks())
- .containsExactly(
- initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_SHORT.size()), // initial
+ .containsExactly(initialLegacyWriteChunk(123, TEST_DATA_SHORT.size()), // initial
data, // data chunk
data, // retry 1
data); // retry 2
@@ -944,7 +936,7 @@ public final class TransferClientTest {
assertThat(lastChunks())
.containsExactly(
// initial
- initialWriteChunk(123, ProtocolVersion.LEGACY, TEST_DATA_100B.size()),
+ initialLegacyWriteChunk(123, TEST_DATA_100B.size()),
// after 2, receive parameters: 40 from 0 by 20
legacyDataChunk(123, TEST_DATA_100B, 0, 20), // data 0-20
legacyDataChunk(123, TEST_DATA_100B, 20, 40), // data 20-40
@@ -966,29 +958,33 @@ public final class TransferClientTest {
public void read_singleChunk_successful() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(3, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
assertThat(future.isDone()).isFalse();
- assertThat(lastChunks()).containsExactly(initialReadChunk(3, ProtocolVersion.VERSION_TWO));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
- receiveReadChunks(newChunk(Chunk.Type.START_ACK, 321)
+ receiveReadChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(3)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
- assertThat(lastChunks()).containsExactly(readStartAckConfirmation(321, TRANSFER_PARAMETERS));
+ assertThat(lastChunks())
+ .containsExactly(readStartAckConfirmation(transfer.getSessionId(), TRANSFER_PARAMETERS));
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 321).setOffset(0).setData(TEST_DATA_SHORT).setRemainingBytes(0));
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(TEST_DATA_SHORT)
+ .setRemainingBytes(0));
assertThat(lastChunks())
.containsExactly(Chunk.newBuilder()
.setType(Chunk.Type.COMPLETION)
- .setSessionId(321)
+ .setSessionId(transfer.getSessionId())
.setStatus(Status.OK.ordinal())
.build());
assertThat(future.isDone()).isFalse();
- receiveReadChunks(newChunk(Chunk.Type.COMPLETION_ACK, 321));
+ receiveReadChunks(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()));
assertThat(future.get()).isEqualTo(TEST_DATA_SHORT.toByteArray());
}
@@ -997,9 +993,10 @@ public final class TransferClientTest {
public void read_requestV2ReceiveLegacy() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(1, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
assertThat(future.isDone()).isFalse();
- assertThat(lastChunks()).containsExactly(initialReadChunk(1, ProtocolVersion.VERSION_TWO));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
receiveReadChunks(newLegacyChunk(Chunk.Type.DATA, 1)
.setOffset(0)
@@ -1016,19 +1013,21 @@ public final class TransferClientTest {
public void read_failedPreconditionError_retriesInitialPacket() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(1, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- assertThat(lastChunks()).containsExactly(initialReadChunk(1, ProtocolVersion.VERSION_TWO));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
for (int i = 0; i < MAX_RETRIES; ++i) {
receiveReadServerError(Status.FAILED_PRECONDITION);
- assertThat(lastChunks()).containsExactly(initialReadChunk(1, ProtocolVersion.VERSION_TWO));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
}
- receiveReadChunks(newChunk(Chunk.Type.START_ACK, 54321)
+ receiveReadChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(1)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
- assertThat(lastChunks()).containsExactly(readStartAckConfirmation(54321, TRANSFER_PARAMETERS));
+ assertThat(lastChunks())
+ .containsExactly(readStartAckConfirmation(transfer.getSessionId(), TRANSFER_PARAMETERS));
}
@Test
@@ -1036,11 +1035,11 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
TransferParameters params = TransferParameters.create(50, 50, 0);
ListenableFuture<byte[]> future = transferClient.read(1, params);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialReadChunk(1, ProtocolVersion.VERSION_TWO, params));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
- receiveReadChunks(newChunk(Chunk.Type.START_ACK, 555)
+ receiveReadChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(1)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
@@ -1056,20 +1055,21 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
TransferParameters params = TransferParameters.create(50, 50, 0);
ListenableFuture<byte[]> future = transferClient.read(1, params);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialReadChunk(1, ProtocolVersion.VERSION_TWO, params));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
- receiveReadChunks(newChunk(Chunk.Type.START_ACK, 555)
+ receiveReadChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(1)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
- assertThat(lastChunks()).containsExactly(readStartAckConfirmation(555, params));
+ assertThat(lastChunks())
+ .containsExactly(readStartAckConfirmation(transfer.getSessionId(), params));
- receiveReadChunks(dataChunk(555, TEST_DATA_100B, 0, 50));
+ receiveReadChunks(dataChunk(transfer.getSessionId(), TEST_DATA_100B, 0, 50));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 555)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(50)
.setWindowEndOffset(100)
.setMaxChunkSizeBytes(50)
@@ -1086,12 +1086,13 @@ public final class TransferClientTest {
public void read_failedPreconditionErrorMaxRetriesTimes_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(1, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
for (int i = 0; i < MAX_RETRIES; ++i) {
receiveReadServerError(Status.FAILED_PRECONDITION);
}
- Chunk initialChunk = initialReadChunk(1, ProtocolVersion.VERSION_TWO);
+ Chunk initialChunk = initialReadChunk(transfer);
assertThat(lastChunks())
.containsExactlyElementsIn(Collections.nCopies(1 + MAX_RETRIES, initialChunk));
@@ -1108,23 +1109,38 @@ public final class TransferClientTest {
public void read_singleChunk_ignoresUnknownIdOrWriteChunks() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(1);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
assertThat(future.isDone()).isFalse();
- performReadStartHandshake(1, 99);
+ performReadStartHandshake(transfer);
- receiveReadChunks(finalChunk(2, Status.OK),
- newChunk(Chunk.Type.DATA, 1).setOffset(0).setData(TEST_DATA_100B).setRemainingBytes(0),
- newChunk(Chunk.Type.DATA, 3).setOffset(0).setData(TEST_DATA_100B).setRemainingBytes(0));
- receiveWriteChunks(finalChunk(99, Status.INVALID_ARGUMENT),
- newChunk(Chunk.Type.DATA, 99).setOffset(0).setData(TEST_DATA_100B).setRemainingBytes(0),
- newChunk(Chunk.Type.DATA, 2).setOffset(0).setData(TEST_DATA_100B).setRemainingBytes(0));
+ receiveReadChunks(finalChunk(transfer.getSessionId() + 1, Status.OK),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId() + 2)
+ .setOffset(0)
+ .setData(TEST_DATA_100B)
+ .setRemainingBytes(0),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId() + 3)
+ .setOffset(0)
+ .setData(TEST_DATA_100B)
+ .setRemainingBytes(0));
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.INVALID_ARGUMENT),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(TEST_DATA_100B)
+ .setRemainingBytes(0),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId() + 1)
+ .setOffset(0)
+ .setData(TEST_DATA_100B)
+ .setRemainingBytes(0));
assertThat(future.isDone()).isFalse();
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 99).setOffset(0).setData(TEST_DATA_SHORT).setRemainingBytes(0));
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(TEST_DATA_SHORT)
+ .setRemainingBytes(0));
- performReadCompletionHandshake(99, Status.OK);
+ performReadCompletionHandshake(transfer.getSessionId(), Status.OK);
assertThat(future.get()).isEqualTo(TEST_DATA_SHORT.toByteArray());
}
@@ -1133,10 +1149,11 @@ public final class TransferClientTest {
public void read_empty() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(2);
- performReadStartHandshake(2, 5678);
- receiveReadChunks(newChunk(Chunk.Type.DATA, 5678).setRemainingBytes(0));
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
+ performReadStartHandshake(transfer);
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId()).setRemainingBytes(0));
- performReadCompletionHandshake(5678, Status.OK);
+ performReadCompletionHandshake(transfer.getSessionId(), Status.OK);
assertThat(future.get()).isEqualTo(new byte[] {});
}
@@ -1146,9 +1163,9 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
TransferParameters params = TransferParameters.create(3, 2, 1);
ListenableFuture<byte[]> future = transferClient.read(99, params);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialReadChunk(99, ProtocolVersion.VERSION_TWO, params));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
assertThat(future.cancel(true)).isTrue();
}
@@ -1156,33 +1173,39 @@ public final class TransferClientTest {
public void read_severalChunks() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(7, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(7, 123, TRANSFER_PARAMETERS);
+ performReadStartHandshake(transfer);
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 20)).setRemainingBytes(70),
- newChunk(Chunk.Type.DATA, 123).setOffset(20).setData(range(20, 40)));
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(range(0, 20))
+ .setRemainingBytes(70),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(20).setData(range(20, 40)));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
.setOffset(40)
.setMaxChunkSizeBytes(30)
.setWindowEndOffset(90)
.build());
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(40).setData(range(40, 70)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(40).setData(range(40, 70)));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
.setOffset(70)
.setMaxChunkSizeBytes(30)
.setWindowEndOffset(120)
.build());
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 123).setOffset(70).setData(range(70, 100)).setRemainingBytes(0));
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(70)
+ .setData(range(70, 100))
+ .setRemainingBytes(0));
- performReadCompletionHandshake(123, Status.OK);
+ performReadCompletionHandshake(transfer.getSessionId(), Status.OK);
assertThat(future.get()).isEqualTo(TEST_DATA_100B.toByteArray());
}
@@ -1192,113 +1215,115 @@ public final class TransferClientTest {
createTransferClientThatMayTimeOut(ProtocolVersion.VERSION_TWO);
TransferParameters params = TransferParameters.create(50, 10, 0);
+ final int id = transferClient.getNextSessionIdForTest();
+
// Handshake
enqueueReadChunks(2, // Wait for read RPC open & START packet
- newChunk(Chunk.Type.START_ACK, 99)
+ newChunk(Chunk.Type.START_ACK, id)
.setResourceId(7)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
enqueueReadChunks(1, // Ignore the first START_ACK_CONFIRMATION
- newChunk(Chunk.Type.START_ACK, 99)
+ newChunk(Chunk.Type.START_ACK, id)
.setResourceId(7)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
// Window 1: server waits for START_ACK_CONFIRMATION, drops 2nd packet
enqueueReadChunks(1,
- newChunk(Chunk.Type.DATA, 99).setOffset(0).setData(range(0, 10)),
- newChunk(Chunk.Type.DATA, 99).setOffset(20).setData(range(20, 30)),
- newChunk(Chunk.Type.DATA, 99).setOffset(30).setData(range(30, 40)),
- newChunk(Chunk.Type.DATA, 99).setOffset(40).setData(range(40, 50)));
+ newChunk(Chunk.Type.DATA, id).setOffset(0).setData(range(0, 10)),
+ newChunk(Chunk.Type.DATA, id).setOffset(20).setData(range(20, 30)),
+ newChunk(Chunk.Type.DATA, id).setOffset(30).setData(range(30, 40)),
+ newChunk(Chunk.Type.DATA, id).setOffset(40).setData(range(40, 50)));
// Window 2: server waits for retransmit, drops 1st packet
enqueueReadChunks(1,
- newChunk(Chunk.Type.DATA, 99).setOffset(20).setData(range(20, 30)),
- newChunk(Chunk.Type.DATA, 99).setOffset(30).setData(range(30, 40)),
- newChunk(Chunk.Type.DATA, 99).setOffset(40).setData(range(40, 50)),
- newChunk(Chunk.Type.DATA, 99).setOffset(50).setData(range(50, 60)));
+ newChunk(Chunk.Type.DATA, id).setOffset(20).setData(range(20, 30)),
+ newChunk(Chunk.Type.DATA, id).setOffset(30).setData(range(30, 40)),
+ newChunk(Chunk.Type.DATA, id).setOffset(40).setData(range(40, 50)),
+ newChunk(Chunk.Type.DATA, id).setOffset(50).setData(range(50, 60)));
// Window 3: server waits for retransmit, drops last packet
enqueueReadChunks(1,
- newChunk(Chunk.Type.DATA, 99).setOffset(10).setData(range(10, 20)),
- newChunk(Chunk.Type.DATA, 99).setOffset(20).setData(range(20, 30)),
- newChunk(Chunk.Type.DATA, 99).setOffset(30).setData(range(30, 40)),
- newChunk(Chunk.Type.DATA, 99).setOffset(40).setData(range(40, 50)));
+ newChunk(Chunk.Type.DATA, id).setOffset(10).setData(range(10, 20)),
+ newChunk(Chunk.Type.DATA, id).setOffset(20).setData(range(20, 30)),
+ newChunk(Chunk.Type.DATA, id).setOffset(30).setData(range(30, 40)),
+ newChunk(Chunk.Type.DATA, id).setOffset(40).setData(range(40, 50)));
// Window 4: server waits for continue and retransmit, normal window.
enqueueReadChunks(2,
- newChunk(Chunk.Type.DATA, 99).setOffset(50).setData(range(50, 60)),
- newChunk(Chunk.Type.DATA, 99).setOffset(60).setData(range(60, 70)),
- newChunk(Chunk.Type.DATA, 99).setOffset(70).setData(range(70, 80)),
- newChunk(Chunk.Type.DATA, 99).setOffset(80).setData(range(80, 90)),
- newChunk(Chunk.Type.DATA, 99).setOffset(90).setData(range(90, 100)));
+ newChunk(Chunk.Type.DATA, id).setOffset(50).setData(range(50, 60)),
+ newChunk(Chunk.Type.DATA, id).setOffset(60).setData(range(60, 70)),
+ newChunk(Chunk.Type.DATA, id).setOffset(70).setData(range(70, 80)),
+ newChunk(Chunk.Type.DATA, id).setOffset(80).setData(range(80, 90)),
+ newChunk(Chunk.Type.DATA, id).setOffset(90).setData(range(90, 100)));
enqueueReadChunks(2, // Ignore continue and retransmit chunks, retry last packet in window
- newChunk(Chunk.Type.DATA, 99).setOffset(90).setData(range(90, 100)),
- newChunk(Chunk.Type.DATA, 99).setOffset(90).setData(range(90, 100)));
+ newChunk(Chunk.Type.DATA, id).setOffset(90).setData(range(90, 100)),
+ newChunk(Chunk.Type.DATA, id).setOffset(90).setData(range(90, 100)));
// Window 5: Final packet
enqueueReadChunks(2, // Receive two retries, then send final packet
- newChunk(Chunk.Type.DATA, 99).setOffset(100).setData(range(100, 110)).setRemainingBytes(0));
+ newChunk(Chunk.Type.DATA, id).setOffset(100).setData(range(100, 110)).setRemainingBytes(0));
enqueueReadChunks(1, // Ignore first COMPLETION packet
- newChunk(Chunk.Type.DATA, 99).setOffset(100).setData(range(100, 110)).setRemainingBytes(0));
- enqueueReadChunks(1, newChunk(Chunk.Type.COMPLETION_ACK, 99));
+ newChunk(Chunk.Type.DATA, id).setOffset(100).setData(range(100, 110)).setRemainingBytes(0));
+ enqueueReadChunks(1, newChunk(Chunk.Type.COMPLETION_ACK, id));
ListenableFuture<byte[]> future = transferClient.read(7, params);
- // assertThat(future.get()).isEqualTo(range(0, 110).toByteArray());
- while (!future.isDone()) {
- }
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
+ assertThat(transfer.getSessionId()).isEqualTo(id);
+ assertThat(future.get()).isEqualTo(range(0, 110).toByteArray());
assertThat(lastChunks())
.containsExactly(
// Handshake
- initialReadChunk(7, ProtocolVersion.VERSION_TWO, params),
- readStartAckConfirmation(99, params),
- readStartAckConfirmation(99, params),
+ initialReadChunk(transfer),
+ readStartAckConfirmation(id, params),
+ readStartAckConfirmation(id, params),
// Window 1: send one transfer parameters update after the drop
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 99)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(10)
.setWindowEndOffset(60)
.setMaxChunkSizeBytes(10)
.build(),
// Window 2: send one transfer parameters update after the drop
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 99)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(10)
.setWindowEndOffset(60)
.setMaxChunkSizeBytes(10)
.build(),
// Window 3: send one transfer parameters update after the drop, then continue packet
- newChunk(Chunk.Type.PARAMETERS_CONTINUE, 99) // Not seen by server
+ newChunk(Chunk.Type.PARAMETERS_CONTINUE, id) // Not seen by server
.setOffset(40)
.setWindowEndOffset(90)
.setMaxChunkSizeBytes(10)
.build(),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 99) // Sent after timeout
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id) // Sent after timeout
.setOffset(50)
.setWindowEndOffset(100)
.setMaxChunkSizeBytes(10)
.build(),
// Window 4: send one transfer parameters update after the drop, then continue packet
- newChunk(Chunk.Type.PARAMETERS_CONTINUE, 99) // Ignored by server
+ newChunk(Chunk.Type.PARAMETERS_CONTINUE, id) // Ignored by server
.setOffset(80)
.setWindowEndOffset(130)
.setMaxChunkSizeBytes(10)
.build(),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 99) // Sent after last packet
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id) // Sent after last packet
.setOffset(100)
.setWindowEndOffset(150)
.setMaxChunkSizeBytes(10)
.build(),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 99) // Sent due to repeated packet
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id) // Sent due to repeated packet
.setOffset(100)
.setWindowEndOffset(150)
.setMaxChunkSizeBytes(10)
.build(),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 99) // Sent due to repeated packet
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id) // Sent due to repeated packet
.setOffset(100)
.setWindowEndOffset(150)
.setMaxChunkSizeBytes(10)
.build(),
// Window 5: final packet and closing handshake
- newChunk(Chunk.Type.COMPLETION, 99).setStatus(Status.OK.ordinal()).build(),
- newChunk(Chunk.Type.COMPLETION, 99).setStatus(Status.OK.ordinal()).build());
+ newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.ordinal()).build(),
+ newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.ordinal()).build());
}
@Test
@@ -1306,21 +1331,31 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future =
transferClient.read(123, TRANSFER_PARAMETERS, progressCallback);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(123, 123, TRANSFER_PARAMETERS);
+ performReadStartHandshake(transfer);
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 30)),
- newChunk(Chunk.Type.DATA, 123).setOffset(30).setData(range(30, 50)),
- newChunk(Chunk.Type.DATA, 123).setOffset(50).setData(range(50, 60)).setRemainingBytes(5),
- newChunk(Chunk.Type.DATA, 123).setOffset(60).setData(range(60, 70)),
- newChunk(Chunk.Type.DATA, 123).setOffset(70).setData(range(70, 80)).setRemainingBytes(20),
- newChunk(Chunk.Type.DATA, 123).setOffset(90).setData(range(90, 100)),
- newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 30)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(0).setData(range(0, 30)),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(30).setData(range(30, 50)),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(50)
+ .setData(range(50, 60))
+ .setRemainingBytes(5),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(60).setData(range(60, 70)),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(70)
+ .setData(range(70, 80))
+ .setRemainingBytes(20),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(90).setData(range(90, 100)),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(0).setData(range(0, 30)));
lastChunks(); // Discard chunks; no need to inspect for this test
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 123).setOffset(80).setData(range(80, 100)).setRemainingBytes(0));
- performReadCompletionHandshake(123, Status.OK);
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(80)
+ .setData(range(80, 100))
+ .setRemainingBytes(0));
+ performReadCompletionHandshake(transfer.getSessionId(), Status.OK);
verify(progressCallback, times(6)).accept(progress.capture());
assertThat(progress.getAllValues())
@@ -1338,51 +1373,59 @@ public final class TransferClientTest {
public void read_rewindWhenPacketsSkipped() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(123, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(123, 123, TRANSFER_PARAMETERS);
+ performReadStartHandshake(transfer);
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(50).setData(range(30, 50)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(50).setData(range(30, 50)));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setWindowEndOffset(50)
.setMaxChunkSizeBytes(30)
.setOffset(0)
.build());
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 30)),
- newChunk(Chunk.Type.DATA, 123).setOffset(30).setData(range(30, 50)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(0).setData(range(0, 30)),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(30).setData(range(30, 50)));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
.setOffset(30)
.setWindowEndOffset(80)
.setMaxChunkSizeBytes(30)
.build());
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 123).setOffset(80).setData(range(80, 100)).setRemainingBytes(0));
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(80)
+ .setData(range(80, 100))
+ .setRemainingBytes(0));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(50)
.setWindowEndOffset(100)
.setMaxChunkSizeBytes(30)
.build());
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(50).setData(range(50, 80)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(50).setData(range(50, 80)));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123)
+ .containsExactly(newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
.setOffset(80)
.setWindowEndOffset(130)
.setMaxChunkSizeBytes(30)
.build());
- receiveReadChunks(
- newChunk(Chunk.Type.DATA, 123).setOffset(80).setData(range(80, 100)).setRemainingBytes(0));
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(80)
+ .setData(range(80, 100))
+ .setRemainingBytes(0));
- performReadCompletionHandshake(123, Status.OK);
+ performReadCompletionHandshake(transfer.getSessionId(), Status.OK);
assertThat(future.get()).isEqualTo(TEST_DATA_100B.toByteArray());
}
@@ -1392,15 +1435,16 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
for (int i = 0; i < 3; ++i) {
ListenableFuture<byte[]> future = transferClient.read(1);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(1, 100 + i);
+ performReadStartHandshake(transfer);
- receiveReadChunks(newChunk(Chunk.Type.DATA, 100 + i)
+ receiveReadChunks(newChunk(Chunk.Type.DATA, transfer.getSessionId())
.setOffset(0)
.setData(TEST_DATA_SHORT)
.setRemainingBytes(0));
- performReadCompletionHandshake(100 + i, Status.OK);
+ performReadCompletionHandshake(transfer.getSessionId(), Status.OK);
assertThat(future.get()).isEqualTo(TEST_DATA_SHORT.toByteArray());
}
@@ -1435,15 +1479,18 @@ public final class TransferClientTest {
public void read_sendErrorOnLaterPacket_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(1024, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(1024, 123, TRANSFER_PARAMETERS);
+ performReadStartHandshake(transfer);
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 20)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(0).setData(range(0, 20)));
ChannelOutputException exception = new ChannelOutputException("blah");
rpcClient.setChannelOutputException(exception);
- receiveReadChunks(newChunk(Chunk.Type.DATA, 123).setOffset(20).setData(range(20, 50)));
+ receiveReadChunks(
+ newChunk(Chunk.Type.DATA, transfer.getSessionId()).setOffset(20).setData(range(20, 50)));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
assertThat(thrown).hasCauseThat().isInstanceOf(TransferError.class);
@@ -1454,21 +1501,22 @@ public final class TransferClientTest {
public void read_cancelFuture_abortsTransfer() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(1, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(1, 123, TRANSFER_PARAMETERS);
+ performReadStartHandshake(transfer);
assertThat(future.cancel(true)).isTrue();
- assertThat(lastChunks()).contains(finalChunk(123, Status.CANCELLED));
+ assertThat(lastChunks()).contains(finalChunk(transfer.getSessionId(), Status.CANCELLED));
}
@Test
public void read_immediateTransferProtocolError_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(123);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- // Resource ID will be set since session ID hasn't been assigned yet.
- receiveReadChunks(newChunk(Chunk.Type.COMPLETION, VersionedChunk.UNASSIGNED_SESSION_ID)
+ receiveReadChunks(newChunk(Chunk.Type.COMPLETION, transfer.getSessionId())
.setResourceId(123)
.setStatus(Status.ALREADY_EXISTS.ordinal()));
@@ -1482,10 +1530,11 @@ public final class TransferClientTest {
public void read_laterTransferProtocolError_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(123);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- performReadStartHandshake(123, 514);
+ performReadStartHandshake(transfer);
- receiveReadChunks(finalChunk(514, Status.ALREADY_EXISTS));
+ receiveReadChunks(finalChunk(transfer.getSessionId(), Status.ALREADY_EXISTS));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
assertThat(thrown).hasCauseThat().isInstanceOf(TransferError.class);
@@ -1508,14 +1557,16 @@ public final class TransferClientTest {
public void read_serverRespondsWithUnknownVersion_invalidArgument() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(2, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialReadChunk(2, ProtocolVersion.VERSION_TWO, TRANSFER_PARAMETERS));
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
- receiveReadChunks(
- newChunk(Chunk.Type.START_ACK, 99).setResourceId(2).setProtocolVersion(600613));
+ receiveReadChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
+ .setResourceId(2)
+ .setProtocolVersion(600613));
- assertThat(lastChunks()).containsExactly(finalChunk(99, Status.INVALID_ARGUMENT));
+ assertThat(lastChunks())
+ .containsExactly(finalChunk(transfer.getSessionId(), Status.INVALID_ARGUMENT));
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) exception.getCause()).status()).isEqualTo(Status.INVALID_ARGUMENT);
@@ -1525,6 +1576,7 @@ public final class TransferClientTest {
public void read_timeout() {
createTransferClientThatMayTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<byte[]> future = transferClient.read(123, TRANSFER_PARAMETERS);
+ ReadTransfer transfer = transferClient.getReadTransferForTest(future);
// Call future.get() without sending any server-side packets.
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
@@ -1532,45 +1584,65 @@ public final class TransferClientTest {
// read should have retried sending the transfer parameters 2 times, for a total of 3
assertThat(lastChunks())
- .containsExactly(initialReadChunk(123, ProtocolVersion.VERSION_TWO),
- initialReadChunk(123, ProtocolVersion.VERSION_TWO),
- initialReadChunk(123, ProtocolVersion.VERSION_TWO));
+ .containsExactly(
+ initialReadChunk(transfer), initialReadChunk(transfer), initialReadChunk(transfer));
+ }
+
+ @Test
+ public void read_generatesUniqueSessionIds() {
+ createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
+
+ int sessionId1 = transferClient.getNextSessionIdForTest();
+ ReadTransfer transfer1 = transferClient.getReadTransferForTest(transferClient.read(123));
+ assertThat(sessionId1).isEqualTo(transfer1.getSessionId());
+
+ int sessionId2 = transferClient.getNextSessionIdForTest();
+ ReadTransfer transfer2 = transferClient.getReadTransferForTest(transferClient.read(456));
+ assertThat(sessionId2).isEqualTo(transfer2.getSessionId());
+
+ int sessionId3 = transferClient.getNextSessionIdForTest();
+ ReadTransfer transfer3 = transferClient.getReadTransferForTest(transferClient.read(789));
+ assertThat(sessionId3).isEqualTo(transfer3.getSessionId());
+
+ assertThat(sessionId1).isNotEqualTo(sessionId2);
+ assertThat(sessionId1).isNotEqualTo(sessionId3);
}
@Test
public void write_singleChunk() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
// Do the start handshake (equivalent to performWriteStartHandshake()).
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()));
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()));
- receiveWriteChunks(newChunk(Chunk.Type.START_ACK, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(2)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.START_ACK_CONFIRMATION, 123)
+ .containsExactly(newChunk(Chunk.Type.START_ACK_CONFIRMATION, transfer.getSessionId())
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(TEST_DATA_SHORT.size())
.build());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(1024)
.setMaxChunkSizeBytes(128));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 123)
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
.setOffset(0)
.setData(TEST_DATA_SHORT)
.setRemainingBytes(0)
.build());
- receiveWriteChunks(finalChunk(123, Status.OK));
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.OK));
- assertThat(lastChunks()).containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ assertThat(lastChunks())
+ .containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()).build());
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
}
@@ -1579,9 +1651,9 @@ public final class TransferClientTest {
public void write_requestV2ReceiveLegacy() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()));
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()));
receiveWriteChunks(newLegacyChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 2)
.setOffset(0)
@@ -1596,10 +1668,11 @@ public final class TransferClientTest {
public void write_platformTransferDisabled_aborted() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
assertThat(future.isDone()).isFalse();
shouldAbortFlag = true;
- receiveWriteChunks(newChunk(Chunk.Type.START_ACK, 3).setResourceId(2));
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId()).setResourceId(2));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
assertThat(thrown).hasCauseThat().isInstanceOf(TransferError.class);
@@ -1610,23 +1683,21 @@ public final class TransferClientTest {
public void write_failedPreconditionError_retriesInitialPacket() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()));
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()));
for (int i = 0; i < MAX_RETRIES; ++i) {
receiveWriteServerError(Status.FAILED_PRECONDITION);
- assertThat(lastChunks())
- .containsExactly(
- initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()));
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()));
}
- receiveWriteChunks(newChunk(Chunk.Type.START_ACK, 54321)
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(2)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.START_ACK_CONFIRMATION, 54321)
+ .containsExactly(newChunk(Chunk.Type.START_ACK_CONFIRMATION, transfer.getSessionId())
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(TEST_DATA_SHORT.size())
.build());
@@ -1636,11 +1707,11 @@ public final class TransferClientTest {
public void write_failedPreconditionError_abortsAfterInitialPacket() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()));
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, TEST_DATA_100B.size()));
- receiveWriteChunks(newChunk(Chunk.Type.START_ACK, 4)
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
.setResourceId(2)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
@@ -1655,12 +1726,13 @@ public final class TransferClientTest {
public void write_failedPreconditionErrorMaxRetriesTimes_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
for (int i = 0; i < MAX_RETRIES; ++i) {
receiveWriteServerError(Status.FAILED_PRECONDITION);
}
- Chunk initialChunk = initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size());
+ Chunk initialChunk = initialWriteChunk(transfer, TEST_DATA_SHORT.size());
assertThat(lastChunks())
.containsExactlyElementsIn(Collections.nCopies(1 + MAX_RETRIES, initialChunk));
@@ -1677,12 +1749,14 @@ public final class TransferClientTest {
public void write_empty() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, new byte[] {});
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(2, 123, 0);
+ performWriteStartHandshake(transfer, 0);
- receiveWriteChunks(finalChunk(123, Status.OK));
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.OK));
- assertThat(lastChunks()).containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ assertThat(lastChunks())
+ .containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()).build());
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
}
@@ -1691,34 +1765,47 @@ public final class TransferClientTest {
public void write_severalChunks() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(500, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(500, 123, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(50)
.setMaxChunkSizeBytes(30)
.setMinDelayMicroseconds(1));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 30)).build(),
- newChunk(Chunk.Type.DATA, 123).setOffset(30).setData(range(30, 50)).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(range(0, 30))
+ .build(),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(30)
+ .setData(range(30, 50))
+ .build());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(50)
.setWindowEndOffset(90)
.setMaxChunkSizeBytes(25));
assertThat(lastChunks())
- .containsExactly(
- newChunk(Chunk.Type.DATA, 123).setOffset(50).setData(range(50, 75)).build(),
- newChunk(Chunk.Type.DATA, 123).setOffset(75).setData(range(75, 90)).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(50)
+ .setData(range(50, 75))
+ .build(),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(75)
+ .setData(range(75, 90))
+ .build());
- receiveWriteChunks(
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123).setOffset(90).setWindowEndOffset(140));
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
+ .setOffset(90)
+ .setWindowEndOffset(140));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 123)
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
.setOffset(90)
.setData(range(90, 100))
.setRemainingBytes(0)
@@ -1726,9 +1813,10 @@ public final class TransferClientTest {
assertThat(future.isDone()).isFalse();
- receiveWriteChunks(finalChunk(123, Status.OK));
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.OK));
- assertThat(lastChunks()).containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ assertThat(lastChunks())
+ .containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()).build());
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
}
@@ -1737,32 +1825,43 @@ public final class TransferClientTest {
public void write_parametersContinue() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(321, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(321, 123, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(50)
.setMaxChunkSizeBytes(30)
.setMinDelayMicroseconds(1));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 123).setOffset(0).setData(range(0, 30)).build(),
- newChunk(Chunk.Type.DATA, 123).setOffset(30).setData(range(30, 50)).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(range(0, 30))
+ .build(),
+ newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(30)
+ .setData(range(30, 50))
+ .build());
- receiveWriteChunks(
- newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123).setOffset(30).setWindowEndOffset(80));
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
+ .setOffset(30)
+ .setWindowEndOffset(80));
// Transfer doesn't roll back to offset 30 but instead continues sending up to 80.
assertThat(lastChunks())
- .containsExactly(
- newChunk(Chunk.Type.DATA, 123).setOffset(50).setData(range(50, 80)).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(50)
+ .setData(range(50, 80))
+ .build());
- receiveWriteChunks(
- newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123).setOffset(80).setWindowEndOffset(130));
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
+ .setOffset(80)
+ .setWindowEndOffset(130));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 123)
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
.setOffset(80)
.setData(range(80, 100))
.setRemainingBytes(0)
@@ -1770,9 +1869,10 @@ public final class TransferClientTest {
assertThat(future.isDone()).isFalse();
- receiveWriteChunks(finalChunk(123, Status.OK));
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.OK));
- assertThat(lastChunks()).containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ assertThat(lastChunks())
+ .containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()).build());
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
}
@@ -1781,33 +1881,42 @@ public final class TransferClientTest {
public void write_continuePacketWithWindowEndBeforeOffsetIsIgnored() throws Exception {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(123, 555, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 555)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(90)
.setMaxChunkSizeBytes(90)
.setMinDelayMicroseconds(1));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 555).setOffset(0).setData(range(0, 90)).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(0)
+ .setData(range(0, 90))
+ .build());
receiveWriteChunks(
// This stale packet with a window end before the offset should be ignored.
- newChunk(Chunk.Type.PARAMETERS_CONTINUE, 555).setOffset(25).setWindowEndOffset(50),
+ newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
+ .setOffset(25)
+ .setWindowEndOffset(50),
// Start from an arbitrary offset before the current, but extend the window to the end.
- newChunk(Chunk.Type.PARAMETERS_CONTINUE, 555).setOffset(80).setWindowEndOffset(100));
+ newChunk(Chunk.Type.PARAMETERS_CONTINUE, transfer.getSessionId())
+ .setOffset(80)
+ .setWindowEndOffset(100));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.DATA, 555)
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
.setOffset(90)
.setData(range(90, 100))
.setRemainingBytes(0)
.build());
- receiveWriteChunks(finalChunk(555, Status.OK));
- assertThat(lastChunks()).containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, 555).build());
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.OK));
+ assertThat(lastChunks())
+ .containsExactly(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()).build());
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
}
@@ -1817,15 +1926,18 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future =
transferClient.write(123, TEST_DATA_100B.toByteArray(), progressCallback);
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(123, 123, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(90)
.setMaxChunkSizeBytes(30),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123).setOffset(50).setWindowEndOffset(100),
- finalChunk(123, Status.OK));
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
+ .setOffset(50)
+ .setWindowEndOffset(100),
+ finalChunk(transfer.getSessionId(), Status.OK));
verify(progressCallback, times(6)).accept(progress.capture());
assertThat(progress.getAllValues())
@@ -1842,17 +1954,20 @@ public final class TransferClientTest {
public void write_asksForFinalOffset_sendsFinalPacket() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(123, 456, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 456)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(100)
.setWindowEndOffset(140)
.setMaxChunkSizeBytes(25));
assertThat(lastChunks())
- .containsExactly(
- newChunk(Chunk.Type.DATA, 456).setOffset(100).setRemainingBytes(0).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setOffset(100)
+ .setRemainingBytes(0)
+ .build());
}
@Test
@@ -1860,17 +1975,21 @@ public final class TransferClientTest {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
for (int i = 0; i < 3; ++i) {
ListenableFuture<Void> future = transferClient.write(6, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(6, 123, TEST_DATA_SHORT.size());
+ performWriteStartHandshake(transfer, TEST_DATA_SHORT.size());
- receiveWriteChunks(
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123).setOffset(0).setWindowEndOffset(50),
- finalChunk(123, Status.OK));
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
+ .setOffset(0)
+ .setWindowEndOffset(50),
+ finalChunk(transfer.getSessionId(), Status.OK));
assertThat(lastChunks())
- .containsExactly(
- newChunk(Chunk.Type.DATA, 123).setData(TEST_DATA_SHORT).setRemainingBytes(0).build(),
- newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ .containsExactly(newChunk(Chunk.Type.DATA, transfer.getSessionId())
+ .setData(TEST_DATA_SHORT)
+ .setRemainingBytes(0)
+ .build(),
+ newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()).build());
future.get();
}
@@ -1905,13 +2024,16 @@ public final class TransferClientTest {
public void write_serviceRequestsNoData_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(7, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(7, 123, TEST_DATA_SHORT.size());
+ performWriteStartHandshake(transfer, TEST_DATA_SHORT.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123).setOffset(0));
+ receiveWriteChunks(
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId()).setOffset(0));
- assertThat(lastChunks()).containsExactly(finalChunk(123, Status.INVALID_ARGUMENT));
- receiveWriteChunks(newChunk(Chunk.Type.COMPLETION_ACK, 123));
+ assertThat(lastChunks())
+ .containsExactly(finalChunk(transfer.getSessionId(), Status.INVALID_ARGUMENT));
+ receiveWriteChunks(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) thrown.getCause()).status()).isEqualTo(Status.INVALID_ARGUMENT);
@@ -1921,16 +2043,18 @@ public final class TransferClientTest {
public void write_invalidOffset_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(7, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(7, 123, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(101)
.setWindowEndOffset(141)
.setMaxChunkSizeBytes(25));
- assertThat(lastChunks()).containsExactly(finalChunk(123, Status.OUT_OF_RANGE));
- receiveWriteChunks(newChunk(Chunk.Type.COMPLETION_ACK, 123));
+ assertThat(lastChunks())
+ .containsExactly(finalChunk(transfer.getSessionId(), Status.OUT_OF_RANGE));
+ receiveWriteChunks(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) thrown.getCause()).status()).isEqualTo(Status.OUT_OF_RANGE);
@@ -1940,13 +2064,14 @@ public final class TransferClientTest {
public void write_sendErrorOnLaterPacket_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(7, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(7, 123, TEST_DATA_SHORT.size());
+ performWriteStartHandshake(transfer, TEST_DATA_SHORT.size());
ChannelOutputException exception = new ChannelOutputException("blah");
rpcClient.setChannelOutputException(exception);
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(50)
.setMaxChunkSizeBytes(30));
@@ -1960,27 +2085,29 @@ public final class TransferClientTest {
public void write_cancelFuture_abortsTransfer() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(7, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(7, 123, TEST_DATA_100B.size());
+ performWriteStartHandshake(transfer, TEST_DATA_100B.size());
assertThat(future.cancel(true)).isTrue();
assertThat(future.isCancelled()).isTrue();
- receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ receiveWriteChunks(newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transfer.getSessionId())
.setOffset(0)
.setWindowEndOffset(50)
.setMaxChunkSizeBytes(50));
- assertThat(lastChunks()).contains(finalChunk(123, Status.CANCELLED));
- receiveWriteChunks(newChunk(Chunk.Type.COMPLETION_ACK, 123));
+ assertThat(lastChunks()).contains(finalChunk(transfer.getSessionId(), Status.CANCELLED));
+ receiveWriteChunks(newChunk(Chunk.Type.COMPLETION_ACK, transfer.getSessionId()));
}
@Test
public void write_immediateTransferProtocolError_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- receiveWriteChunks(newChunk(Chunk.Type.COMPLETION, VersionedChunk.UNASSIGNED_SESSION_ID)
+ receiveWriteChunks(newChunk(Chunk.Type.COMPLETION, transfer.getSessionId())
.setResourceId(123)
.setStatus(Status.NOT_FOUND.ordinal()));
@@ -1994,10 +2121,11 @@ public final class TransferClientTest {
public void write_laterTransferProtocolError_aborts() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- performWriteStartHandshake(123, 123, TEST_DATA_SHORT.size());
+ performWriteStartHandshake(transfer, TEST_DATA_SHORT.size());
- receiveWriteChunks(finalChunk(123, Status.NOT_FOUND));
+ receiveWriteChunks(finalChunk(transfer.getSessionId(), Status.NOT_FOUND));
ExecutionException thrown = assertThrows(ExecutionException.class, future::get);
assertThat(thrown).hasCauseThat().isInstanceOf(TransferError.class);
@@ -2020,29 +2148,34 @@ public final class TransferClientTest {
public void write_unknownVersion_invalidArgument() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- receiveWriteChunks(newChunk(Chunk.Type.START_ACK, 3).setResourceId(2).setProtocolVersion(9));
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
+ .setResourceId(2)
+ .setProtocolVersion(9));
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) exception.getCause()).status()).isEqualTo(Status.INVALID_ARGUMENT);
assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()),
- finalChunk(3, Status.INVALID_ARGUMENT));
+ .containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()),
+ finalChunk(transfer.getSessionId(), Status.INVALID_ARGUMENT));
}
@Test
public void write_serverRespondsWithUnknownVersion_invalidArgument() {
createTransferClientForTransferThatWillNotTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(2, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(2, ProtocolVersion.VERSION_TWO, 100));
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, 100));
- receiveWriteChunks(
- newChunk(Chunk.Type.START_ACK, 99).setResourceId(2).setProtocolVersion(600613));
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
+ .setResourceId(2)
+ .setProtocolVersion(600613));
- assertThat(lastChunks()).containsExactly(finalChunk(99, Status.INVALID_ARGUMENT));
+ assertThat(lastChunks())
+ .containsExactly(finalChunk(transfer.getSessionId(), Status.INVALID_ARGUMENT));
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) exception.getCause()).status()).isEqualTo(Status.INVALID_ARGUMENT);
@@ -2052,6 +2185,7 @@ public final class TransferClientTest {
public void write_timeoutAfterInitialChunk() {
createTransferClientThatMayTimeOut(ProtocolVersion.VERSION_TWO);
ListenableFuture<Void> future = transferClient.write(123, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
// Call future.get() without sending any server-side packets.
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
@@ -2059,10 +2193,9 @@ public final class TransferClientTest {
// Client should have resent the last chunk (the initial chunk in this case) for each timeout.
assertThat(lastChunks())
- .containsExactly(
- initialWriteChunk(123, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()), // initial
- initialWriteChunk(123, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()), // retry 1
- initialWriteChunk(123, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size())); // retry 2
+ .containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()), // initial
+ initialWriteChunk(transfer, TEST_DATA_SHORT.size()), // retry 1
+ initialWriteChunk(transfer, TEST_DATA_SHORT.size())); // retry 2
}
@Test
@@ -2071,25 +2204,25 @@ public final class TransferClientTest {
// Wait for two outgoing packets (Write RPC request and first chunk), then do the handshake.
enqueueWriteChunks(2,
- newChunk(Chunk.Type.START_ACK, 123).setResourceId(9),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.START_ACK, transferClient.getNextSessionIdForTest()).setResourceId(9),
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, transferClient.getNextSessionIdForTest())
.setOffset(0)
.setWindowEndOffset(90)
.setMaxChunkSizeBytes(30));
ListenableFuture<Void> future = transferClient.write(9, TEST_DATA_SHORT.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) exception.getCause()).status()).isEqualTo(Status.DEADLINE_EXCEEDED);
- Chunk data = newChunk(Chunk.Type.DATA, 123)
+ Chunk data = newChunk(Chunk.Type.DATA, transfer.getSessionId())
.setOffset(0)
.setData(TEST_DATA_SHORT)
.setRemainingBytes(0)
.build();
assertThat(lastChunks())
- .containsExactly(
- initialWriteChunk(9, ProtocolVersion.VERSION_TWO, TEST_DATA_SHORT.size()), // initial
- newChunk(Chunk.Type.START_ACK_CONFIRMATION, 123)
+ .containsExactly(initialWriteChunk(transfer, TEST_DATA_SHORT.size()), // initial
+ newChunk(Chunk.Type.START_ACK_CONFIRMATION, transfer.getSessionId())
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(TEST_DATA_SHORT.size())
.build(),
@@ -2103,47 +2236,50 @@ public final class TransferClientTest {
createTransferClientThatMayTimeOut(ProtocolVersion.VERSION_TWO);
assertThat(MAX_RETRIES).isEqualTo(2); // This test assumes 2 retries
+ int id = transferClient.getNextSessionIdForTest();
+
// Wait for four outgoing packets (Write RPC request and START chunk + retry), then handshake.
enqueueWriteChunks(3,
- newChunk(Chunk.Type.START_ACK, 123)
+ newChunk(Chunk.Type.START_ACK, id)
.setResourceId(5)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
// Wait for start ack confirmation + 2 retries, then request three packets.
enqueueWriteChunks(3,
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(0)
.setWindowEndOffset(60)
.setMaxChunkSizeBytes(20));
// After two packets, request the remainder of the packets.
enqueueWriteChunks(
- 2, newChunk(Chunk.Type.PARAMETERS_CONTINUE, 123).setOffset(20).setWindowEndOffset(200));
+ 2, newChunk(Chunk.Type.PARAMETERS_CONTINUE, id).setOffset(20).setWindowEndOffset(200));
// Wait for last 3 data packets, then 2 final packet retries.
enqueueWriteChunks(5,
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(80)
.setWindowEndOffset(200)
.setMaxChunkSizeBytes(20),
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(80)
.setWindowEndOffset(200)
.setMaxChunkSizeBytes(20));
// After the retry, confirm completed multiple times; additional packets should be dropped
enqueueWriteChunks(1,
- newChunk(Chunk.Type.COMPLETION, 123).setStatus(Status.OK.code()),
- newChunk(Chunk.Type.COMPLETION, 123).setStatus(Status.OK.code()),
- newChunk(Chunk.Type.COMPLETION, 123).setStatus(Status.OK.code()),
- newChunk(Chunk.Type.COMPLETION, 123).setStatus(Status.OK.code()));
+ newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.code()),
+ newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.code()),
+ newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.code()),
+ newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.code()));
ListenableFuture<Void> future = transferClient.write(5, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
final Chunk startAckConfirmation =
- newChunk(Chunk.Type.START_ACK_CONFIRMATION, 123)
+ newChunk(Chunk.Type.START_ACK_CONFIRMATION, id)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(TEST_DATA_100B.size())
.build();
@@ -2151,25 +2287,25 @@ public final class TransferClientTest {
assertThat(lastChunks())
.containsExactly(
// initial handshake with retries
- initialWriteChunk(5, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()),
- initialWriteChunk(5, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()),
+ initialWriteChunk(transfer, TEST_DATA_100B.size()),
+ initialWriteChunk(transfer, TEST_DATA_100B.size()),
startAckConfirmation,
startAckConfirmation,
startAckConfirmation,
// send all data
- dataChunk(123, TEST_DATA_100B, 0, 20), // data 0-20
- dataChunk(123, TEST_DATA_100B, 20, 40), // data 20-40
- dataChunk(123, TEST_DATA_100B, 40, 60), // data 40-60
- dataChunk(123, TEST_DATA_100B, 60, 80), // data 60-80
- dataChunk(123, TEST_DATA_100B, 80, 100), // data 80-100 (final)
+ dataChunk(id, TEST_DATA_100B, 0, 20), // data 0-20
+ dataChunk(id, TEST_DATA_100B, 20, 40), // data 20-40
+ dataChunk(id, TEST_DATA_100B, 40, 60), // data 40-60
+ dataChunk(id, TEST_DATA_100B, 60, 80), // data 60-80
+ dataChunk(id, TEST_DATA_100B, 80, 100), // data 80-100 (final)
// retry last packet two times
- dataChunk(123, TEST_DATA_100B, 80, 100), // data 80-100 (final)
- dataChunk(123, TEST_DATA_100B, 80, 100), // data 80-100 (final)
+ dataChunk(id, TEST_DATA_100B, 80, 100), // data 80-100 (final)
+ dataChunk(id, TEST_DATA_100B, 80, 100), // data 80-100 (final)
// respond to two PARAMETERS_RETRANSMIT packets
- dataChunk(123, TEST_DATA_100B, 80, 100), // data 80-100 (final)
- dataChunk(123, TEST_DATA_100B, 80, 100), // data 80-100 (final)
+ dataChunk(id, TEST_DATA_100B, 80, 100), // data 80-100 (final)
+ dataChunk(id, TEST_DATA_100B, 80, 100), // data 80-100 (final)
// respond to OK packet
- newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ newChunk(Chunk.Type.COMPLETION_ACK, id).build());
}
@Test
@@ -2177,98 +2313,105 @@ public final class TransferClientTest {
createTransferClientThatMayTimeOut(ProtocolVersion.VERSION_TWO);
assertThat(MAX_RETRIES).isEqualTo(2); // This test assumes 2 retries
+ int id = transferClient.getNextSessionIdForTest();
+
// Wait for two outgoing packets (Write RPC request and START chunk), then do the handshake.
enqueueWriteChunks(2,
- newChunk(Chunk.Type.START_ACK, 123)
+ newChunk(Chunk.Type.START_ACK, id)
.setResourceId(5)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
// Request two packets.
enqueueWriteChunks(1,
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(0)
.setWindowEndOffset(40)
.setMaxChunkSizeBytes(20));
// After the second retry, send more transfer parameters
enqueueWriteChunks(4,
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(40)
.setWindowEndOffset(120)
.setMaxChunkSizeBytes(40));
// After the first retry, send more transfer parameters
enqueueWriteChunks(3,
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(80)
.setWindowEndOffset(160)
.setMaxChunkSizeBytes(10));
// After the second retry, confirm completed
- enqueueWriteChunks(4, newChunk(Chunk.Type.COMPLETION, 123).setStatus(Status.OK.code()));
- enqueueWriteChunks(1, newChunk(Chunk.Type.COMPLETION_ACK, 123));
+ enqueueWriteChunks(4, newChunk(Chunk.Type.COMPLETION, id).setStatus(Status.OK.code()));
+ enqueueWriteChunks(1, newChunk(Chunk.Type.COMPLETION_ACK, id));
ListenableFuture<Void> future = transferClient.write(5, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
assertThat(future.get()).isNull(); // Ensure that no exceptions are thrown.
assertThat(lastChunks())
.containsExactly(
// initial handshake
- initialWriteChunk(5, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()),
- newChunk(Chunk.Type.START_ACK_CONFIRMATION, 123)
+ initialWriteChunk(transfer, TEST_DATA_100B.size()),
+ newChunk(Chunk.Type.START_ACK_CONFIRMATION, id)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(TEST_DATA_100B.size())
.build(),
// after 2, receive parameters: 40 from 0 by 20
- dataChunk(123, TEST_DATA_100B, 0, 20), // data 0-20
- dataChunk(123, TEST_DATA_100B, 20, 40), // data 20-40
- dataChunk(123, TEST_DATA_100B, 20, 40), // retry 1
- dataChunk(123, TEST_DATA_100B, 20, 40), // retry 2
+ dataChunk(id, TEST_DATA_100B, 0, 20), // data 0-20
+ dataChunk(id, TEST_DATA_100B, 20, 40), // data 20-40
+ dataChunk(id, TEST_DATA_100B, 20, 40), // retry 1
+ dataChunk(id, TEST_DATA_100B, 20, 40), // retry 2
// after 4, receive parameters: 80 from 40 by 40
- dataChunk(123, TEST_DATA_100B, 40, 80), // data 40-80
- dataChunk(123, TEST_DATA_100B, 80, 100), // data 80-100
- dataChunk(123, TEST_DATA_100B, 80, 100), // retry 1
+ dataChunk(id, TEST_DATA_100B, 40, 80), // data 40-80
+ dataChunk(id, TEST_DATA_100B, 80, 100), // data 80-100
+ dataChunk(id, TEST_DATA_100B, 80, 100), // retry 1
// after 3, receive parameters: 80 from 80 by 10
- dataChunk(123, TEST_DATA_100B, 80, 90), // data 80-90
- dataChunk(123, TEST_DATA_100B, 90, 100), // data 90-100
- dataChunk(123, TEST_DATA_100B, 90, 100), // retry 1
- dataChunk(123, TEST_DATA_100B, 90, 100), // retry 2
+ dataChunk(id, TEST_DATA_100B, 80, 90), // data 80-90
+ dataChunk(id, TEST_DATA_100B, 90, 100), // data 90-100
+ dataChunk(id, TEST_DATA_100B, 90, 100), // retry 1
+ dataChunk(id, TEST_DATA_100B, 90, 100), // retry 2
// after 4, receive final OK
- newChunk(Chunk.Type.COMPLETION_ACK, 123).build());
+ newChunk(Chunk.Type.COMPLETION_ACK, id).build());
}
+
@Test
- public void write_maxLifetimeRetries() throws Exception {
+ public void write_maxLifetimeRetries() {
createTransferClientThatMayTimeOut(ProtocolVersion.VERSION_TWO, 5);
assertThat(MAX_RETRIES).isEqualTo(2); // This test assumes 2 retries
+ int id = transferClient.getNextSessionIdForTest();
+
// Wait for four outgoing packets (Write RPC request and START chunk + 2 retries)
enqueueWriteChunks(4, // 2 retries
- newChunk(Chunk.Type.START_ACK, 123)
+ newChunk(Chunk.Type.START_ACK, id)
.setResourceId(5)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
// Wait for start ack confirmation + 2 retries, then request three packets.
enqueueWriteChunks(3, // 2 retries
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123)
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id)
.setOffset(0)
.setWindowEndOffset(60)
.setMaxChunkSizeBytes(20));
// After 3 data packets, wait for two more retries, which should put this over the retry limit.
enqueueWriteChunks(5, // 2 retries
- newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, 123) // This packet should be ignored
+ newChunk(Chunk.Type.PARAMETERS_RETRANSMIT, id) // This packet should be ignored
.setOffset(80)
.setWindowEndOffset(200)
.setMaxChunkSizeBytes(20));
ListenableFuture<Void> future = transferClient.write(5, TEST_DATA_100B.toByteArray());
+ WriteTransfer transfer = transferClient.getWriteTransferForTest(future);
ExecutionException exception = assertThrows(ExecutionException.class, future::get);
assertThat(((TransferError) exception.getCause()).status()).isEqualTo(Status.DEADLINE_EXCEEDED);
final Chunk startAckConfirmation =
- newChunk(Chunk.Type.START_ACK_CONFIRMATION, 123)
+ newChunk(Chunk.Type.START_ACK_CONFIRMATION, id)
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(TEST_DATA_100B.size())
.build();
@@ -2276,19 +2419,19 @@ public final class TransferClientTest {
assertThat(lastChunks())
.containsExactly(
// initial chunk and 2 retries
- initialWriteChunk(5, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()),
- initialWriteChunk(5, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()),
- initialWriteChunk(5, ProtocolVersion.VERSION_TWO, TEST_DATA_100B.size()),
+ initialWriteChunk(transfer, TEST_DATA_100B.size()),
+ initialWriteChunk(transfer, TEST_DATA_100B.size()),
+ initialWriteChunk(transfer, TEST_DATA_100B.size()),
// START_ACK_CONFIRMATION and 2 retries
startAckConfirmation,
startAckConfirmation,
startAckConfirmation,
// send all data
- dataChunk(123, TEST_DATA_100B, 0, 20), // data 0-20
- dataChunk(123, TEST_DATA_100B, 20, 40), // data 20-40
- dataChunk(123, TEST_DATA_100B, 40, 60), // data 40-60
+ dataChunk(id, TEST_DATA_100B, 0, 20), // data 0-20
+ dataChunk(id, TEST_DATA_100B, 20, 40), // data 20-40
+ dataChunk(id, TEST_DATA_100B, 40, 60), // data 40-60
// last packet retry, then hit the lifetime retry limit and abort
- dataChunk(123, TEST_DATA_100B, 40, 60)); // data 40-60
+ dataChunk(id, TEST_DATA_100B, 40, 60)); // data 40-60
}
private static ByteString range(int startInclusive, int endExclusive) {
@@ -2310,21 +2453,35 @@ public final class TransferClientTest {
return Chunk.newBuilder().setType(type).setSessionId(sessionId);
}
- private static Chunk initialReadChunk(int resourceId, ProtocolVersion version) {
- return initialReadChunk(resourceId, version, TRANSFER_PARAMETERS);
+ private static Chunk initialReadChunk(ReadTransfer transfer) {
+ Chunk.Builder chunk =
+ newLegacyChunk(Chunk.Type.START, transfer.getResourceId())
+ .setResourceId(transfer.getResourceId())
+ .setPendingBytes(transfer.getParametersForTest().maxPendingBytes())
+ .setWindowEndOffset(transfer.getParametersForTest().maxPendingBytes())
+ .setMaxChunkSizeBytes(transfer.getParametersForTest().maxChunkSizeBytes())
+ .setOffset(0);
+ if (transfer.getDesiredProtocolVersionForTest() != ProtocolVersion.LEGACY) {
+ chunk.setProtocolVersion(transfer.getDesiredProtocolVersionForTest().ordinal());
+ chunk.setDesiredSessionId(transfer.getSessionId());
+ }
+ if (transfer.getParametersForTest().chunkDelayMicroseconds() > 0) {
+ chunk.setMinDelayMicroseconds(transfer.getParametersForTest().chunkDelayMicroseconds());
+ }
+ return chunk.build();
}
- private static Chunk initialReadChunk(
- int resourceId, ProtocolVersion version, TransferParameters params) {
+ private static Chunk initialLegacyReadChunk(int resourceId) {
+ return initialLegacyReadChunk(resourceId, TRANSFER_PARAMETERS);
+ }
+
+ private static Chunk initialLegacyReadChunk(int resourceId, TransferParameters params) {
Chunk.Builder chunk = newLegacyChunk(Chunk.Type.START, resourceId)
.setResourceId(resourceId)
.setPendingBytes(params.maxPendingBytes())
.setWindowEndOffset(params.maxPendingBytes())
.setMaxChunkSizeBytes(params.maxChunkSizeBytes())
.setOffset(0);
- if (version != ProtocolVersion.LEGACY) {
- chunk.setProtocolVersion(version.ordinal());
- }
if (params.chunkDelayMicroseconds() > 0) {
chunk.setMinDelayMicroseconds(params.chunkDelayMicroseconds());
}
@@ -2343,12 +2500,20 @@ public final class TransferClientTest {
return chunk.build();
}
- private static Chunk initialWriteChunk(int resourceId, ProtocolVersion version, int size) {
- Chunk.Builder chunk = newLegacyChunk(Chunk.Type.START, resourceId)
- .setResourceId(resourceId)
+ private static Chunk initialLegacyWriteChunk(int resourceId, int size) {
+ return newLegacyChunk(Chunk.Type.START, resourceId)
+ .setResourceId(resourceId)
+ .setRemainingBytes(size)
+ .build();
+ }
+
+ private static Chunk initialWriteChunk(WriteTransfer transfer, int size) {
+ Chunk.Builder chunk = newLegacyChunk(Chunk.Type.START, transfer.getResourceId())
+ .setResourceId(transfer.getResourceId())
.setRemainingBytes(size);
- if (version != ProtocolVersion.LEGACY) {
+ if (transfer.getDesiredProtocolVersionForTest() != ProtocolVersion.LEGACY) {
chunk.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal());
+ chunk.setDesiredSessionId(transfer.getSessionId());
}
return chunk.build();
}
@@ -2415,20 +2580,16 @@ public final class TransferClientTest {
}
}
- private void performReadStartHandshake(int resourceId, int sessionId) {
- performReadStartHandshake(
- resourceId, sessionId, TransferClient.DEFAULT_READ_TRANSFER_PARAMETERS);
- }
+ private void performReadStartHandshake(ReadTransfer transfer) {
+ assertThat(lastChunks()).containsExactly(initialReadChunk(transfer));
- private void performReadStartHandshake(int resourceId, int sessionId, TransferParameters params) {
- assertThat(lastChunks())
- .containsExactly(initialReadChunk(resourceId, ProtocolVersion.VERSION_TWO, params));
-
- receiveReadChunks(newChunk(Chunk.Type.START_ACK, sessionId)
- .setResourceId(resourceId)
+ receiveReadChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
+ .setResourceId(transfer.getResourceId())
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
- assertThat(lastChunks()).containsExactly(readStartAckConfirmation(sessionId, params));
+ assertThat(lastChunks())
+ .containsExactly(
+ readStartAckConfirmation(transfer.getSessionId(), transfer.getParametersForTest()));
}
private void performReadCompletionHandshake(int sessionId, Status status) {
@@ -2442,16 +2603,15 @@ public final class TransferClientTest {
receiveReadChunks(newChunk(Chunk.Type.COMPLETION_ACK, sessionId));
}
- private void performWriteStartHandshake(int resourceId, int sessionId, int dataSize) {
- assertThat(lastChunks())
- .containsExactly(initialWriteChunk(resourceId, ProtocolVersion.VERSION_TWO, dataSize));
+ private void performWriteStartHandshake(WriteTransfer transfer, int dataSize) {
+ assertThat(lastChunks()).containsExactly(initialWriteChunk(transfer, dataSize));
- receiveWriteChunks(newChunk(Chunk.Type.START_ACK, sessionId)
- .setResourceId(resourceId)
+ receiveWriteChunks(newChunk(Chunk.Type.START_ACK, transfer.getSessionId())
+ .setResourceId(transfer.getResourceId())
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal()));
assertThat(lastChunks())
- .containsExactly(newChunk(Chunk.Type.START_ACK_CONFIRMATION, sessionId)
+ .containsExactly(newChunk(Chunk.Type.START_ACK_CONFIRMATION, transfer.getSessionId())
.setProtocolVersion(ProtocolVersion.VERSION_TWO.ordinal())
.setRemainingBytes(dataSize)
.build());
diff --git a/pw_transfer/public/pw_transfer/atomic_file_transfer_handler.h b/pw_transfer/public/pw_transfer/atomic_file_transfer_handler.h
index b108c5f52..22eb4c04d 100644
--- a/pw_transfer/public/pw_transfer/atomic_file_transfer_handler.h
+++ b/pw_transfer/public/pw_transfer/atomic_file_transfer_handler.h
@@ -24,12 +24,15 @@
namespace pw::transfer {
-// The AtomicFileTransferHandler is intended to be used as a transfer
-// handler for files. It ensures that the target file of the transfer is always
-// in a correct state. In particular, the transfer is first done to a temporary
-// file and once complete, the original targeted file is updated.
+/// `AtomicFileTransferHandler` is intended to be used as a transfer handler for
+/// files. It ensures that the target file of the transfer is always in a
+/// correct state. In particular, the transfer is first done to a temporary file
+/// and once complete, the original targeted file is updated.
class AtomicFileTransferHandler : public ReadWriteHandler {
public:
+ /// @param[in] resource_id An ID for the resource that's being transferred.
+ ///
+ /// @param[in] file_path The target file to update.
AtomicFileTransferHandler(uint32_t resource_id, std::string_view file_path)
: ReadWriteHandler(resource_id), path_(file_path) {}
@@ -38,15 +41,36 @@ class AtomicFileTransferHandler : public ReadWriteHandler {
delete;
~AtomicFileTransferHandler() override = default;
- // Function called prior to initializing a read transfer.
+ /// Prepares `AtomicFileTransferHandler` for a read transfer.
+ ///
+ /// @pre The read transfer has not been initialized before the call to this
+ /// method.
+ ///
+ /// @returns A `pw::Status` object indicating whether
+ /// `AtomicFileTransferHandler` is ready for the transfer.
Status PrepareRead() override;
- // Function called after a read transfer is done.
- // Status indicates whether transfer was done successfully.
+ /// Handler function that is called by the transfer thread after a read
+ /// transfer completes.
+ ///
+ /// @param[in] Status A `pw::Status` object provided by the transfer thread
+ /// indicating whether the transfer succeeded.
+ ///
+ /// @pre The read transfer is done before the call to this method.
void FinalizeRead(Status) override;
- // Function called prior to initializing a write transfer.
+ /// Prepares `AtomicFileTransferHandler` for a write transfer.
+ ///
+ /// @pre The write transfer has not been initialized before the call to this
+ /// method.
+ ///
+ /// @returns A `pw::Status` object indicating whether
+ /// `AtomicFileTransferHandler` is ready for the transfer.
Status PrepareWrite() override;
- // Function called after a write transfer is done.
- // Status indicates whether transfer was done successfully.
+ /// Indicates whether the write transfer was successful.
+ ///
+ /// @pre The write transfer is done.
+ ///
+ /// @returns A `pw::Status` object indicating whether the transfer data was
+ /// successfully written.
Status FinalizeWrite(Status) override;
private:
diff --git a/pw_transfer/public/pw_transfer/client.h b/pw_transfer/public/pw_transfer/client.h
index d7faa08ed..62931daf8 100644
--- a/pw_transfer/public/pw_transfer/client.h
+++ b/pw_transfer/public/pw_transfer/client.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -77,6 +77,8 @@ class Client {
stream::Writer& output,
CompletionFunc&& on_completion,
chrono::SystemClock::duration timeout = cfg::kDefaultChunkTimeout,
+ chrono::SystemClock::duration initial_chunk_timeout =
+ cfg::kDefaultInitialChunkTimeout,
ProtocolVersion version = kDefaultProtocolVersion);
// Begins a new write transfer for the given resource ID. Data from the
@@ -88,6 +90,8 @@ class Client {
stream::Reader& input,
CompletionFunc&& on_completion,
chrono::SystemClock::duration timeout = cfg::kDefaultChunkTimeout,
+ chrono::SystemClock::duration initial_chunk_timeout =
+ cfg::kDefaultInitialChunkTimeout,
ProtocolVersion version = kDefaultProtocolVersion);
// Terminates an ongoing transfer for the specified resource ID.
@@ -123,7 +127,7 @@ class Client {
private:
static constexpr ProtocolVersion kDefaultProtocolVersion =
- ProtocolVersion::kLatest;
+ ProtocolVersion::kLegacy;
using Transfer = pw_rpc::raw::Transfer;
diff --git a/pw_transfer/public/pw_transfer/internal/chunk.h b/pw_transfer/public/pw_transfer/internal/chunk.h
index ffb556d6e..f2e03c8e0 100644
--- a/pw_transfer/public/pw_transfer/internal/chunk.h
+++ b/pw_transfer/public/pw_transfer/internal/chunk.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -29,7 +29,8 @@ class Chunk {
class Identifier {
public:
constexpr bool is_session() const { return type_ == kSession; }
- constexpr bool is_resource() const { return !is_session(); }
+ constexpr bool is_desired_session() const { return type_ == kDesired; }
+ constexpr bool is_legacy() const { return type_ == kLegacy; }
constexpr uint32_t value() const { return value_; }
@@ -39,13 +40,17 @@ class Chunk {
static constexpr Identifier Session(uint32_t value) {
return Identifier(kSession, value);
}
- static constexpr Identifier Resource(uint32_t value) {
- return Identifier(kResource, value);
+ static constexpr Identifier Desired(uint32_t value) {
+ return Identifier(kDesired, value);
+ }
+ static constexpr Identifier Legacy(uint32_t value) {
+ return Identifier(kLegacy, value);
}
enum IdType {
kSession,
- kResource,
+ kDesired,
+ kLegacy,
};
constexpr Identifier(IdType type, uint32_t value)
@@ -90,6 +95,11 @@ class Chunk {
return *this;
}
+ constexpr Chunk& set_desired_session_id(uint32_t session_id) {
+ desired_session_id_ = session_id;
+ return *this;
+ }
+
constexpr Chunk& set_resource_id(uint32_t resource_id) {
resource_id_ = resource_id;
return *this;
@@ -138,7 +148,16 @@ class Chunk {
return *this;
}
- constexpr uint32_t session_id() const { return session_id_; }
+ constexpr uint32_t session_id() const {
+ if (desired_session_id_.has_value()) {
+ return desired_session_id_.value();
+ }
+ return session_id_;
+ }
+
+ constexpr std::optional<uint32_t> desired_session_id() const {
+ return desired_session_id_;
+ }
constexpr std::optional<uint32_t> resource_id() const {
if (is_legacy()) {
@@ -237,6 +256,7 @@ class Chunk {
private:
constexpr Chunk(ProtocolVersion version, std::optional<Type> type)
: session_id_(0),
+ desired_session_id_(std::nullopt),
resource_id_(std::nullopt),
window_end_offset_(0),
max_chunk_size_bytes_(std::nullopt),
@@ -263,6 +283,7 @@ class Chunk {
}
uint32_t session_id_;
+ std::optional<uint32_t> desired_session_id_;
std::optional<uint32_t> resource_id_;
uint32_t window_end_offset_;
std::optional<uint32_t> max_chunk_size_bytes_;
diff --git a/pw_transfer/public/pw_transfer/internal/client_context.h b/pw_transfer/public/pw_transfer/internal/client_context.h
index 9912aa4ac..aa7763eaa 100644
--- a/pw_transfer/public/pw_transfer/internal/client_context.h
+++ b/pw_transfer/public/pw_transfer/internal/client_context.h
@@ -27,13 +27,6 @@ class ClientContext final : public Context {
on_completion_ = std::move(on_completion);
}
- // In client-side transfer contexts, a session ID may not yet have been
- // assigned by the server, in which case resource_id is used as the context
- // identifier.
- constexpr uint32_t id() const {
- return session_id() == kUnassignedSessionId ? resource_id() : session_id();
- }
-
private:
Status FinalCleanup(Status status) override;
diff --git a/pw_transfer/public/pw_transfer/internal/config.h b/pw_transfer/public/pw_transfer/internal/config.h
index 9410d990b..04bfa78f8 100644
--- a/pw_transfer/public/pw_transfer/internal/config.h
+++ b/pw_transfer/public/pw_transfer/internal/config.h
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2023 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
@@ -54,6 +54,19 @@ static_assert(PW_TRANSFER_DEFAULT_MAX_LIFETIME_RETRIES >
static_assert(PW_TRANSFER_DEFAULT_TIMEOUT_MS > 0);
+// The default amount of time, in milliseconds, to wait for an initial server
+// response to a transfer before retrying. This can later be configured
+// per-transfer.
+//
+// This is set separately to PW_TRANSFER_DEFAULT_TIMEOUT_MS as transfers may
+// require additional time for resource initialization (e.g. erasing a flash
+// region before writing to it).
+#ifndef PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS
+#define PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS PW_TRANSFER_DEFAULT_TIMEOUT_MS
+#endif // PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS
+
+static_assert(PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS > 0);
+
// The fractional position within a window at which a receive transfer should
// extend its window size to minimize the amount of time the transmitter
// spends blocked.
@@ -74,7 +87,11 @@ inline constexpr uint16_t kDefaultMaxLifetimeRetries =
PW_TRANSFER_DEFAULT_MAX_LIFETIME_RETRIES;
inline constexpr chrono::SystemClock::duration kDefaultChunkTimeout =
- std::chrono::milliseconds(PW_TRANSFER_DEFAULT_TIMEOUT_MS);
+ chrono::SystemClock::for_at_least(
+ std::chrono::milliseconds(PW_TRANSFER_DEFAULT_TIMEOUT_MS));
+inline constexpr chrono::SystemClock::duration kDefaultInitialChunkTimeout =
+ chrono::SystemClock::for_at_least(
+ std::chrono::milliseconds(PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS));
inline constexpr uint32_t kDefaultExtendWindowDivisor =
PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR;
diff --git a/pw_transfer/public/pw_transfer/internal/context.h b/pw_transfer/public/pw_transfer/internal/context.h
index d72f81384..b4a80ec98 100644
--- a/pw_transfer/public/pw_transfer/internal/context.h
+++ b/pw_transfer/public/pw_transfer/internal/context.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -129,6 +129,7 @@ class Context {
last_chunk_sent_(Chunk::Type::kData),
last_chunk_offset_(0),
chunk_timeout_(chrono::SystemClock::duration::zero()),
+ initial_chunk_timeout_(chrono::SystemClock::duration::zero()),
interchunk_delay_(chrono::SystemClock::for_at_least(
std::chrono::microseconds(kDefaultChunkDelayMicroseconds))),
next_timeout_(kNoTimeout) {}
@@ -276,8 +277,8 @@ class Context {
// Processes a data chunk in a received while in the kWaiting state.
void HandleReceivedData(const Chunk& chunk);
- // Sends the first chunk in a transmit transfer.
- void SendInitialTransmitChunk();
+ // Sends the first chunk in a legacy transmit transfer.
+ void SendInitialLegacyTransmitChunk();
// Updates the current receive transfer parameters based on the context's
// configuration.
@@ -347,7 +348,7 @@ class Context {
// status chunk will be re-sent for every non-ACK chunk received,
// continually notifying the other end that the transfer is over.
static constexpr chrono::SystemClock::duration kFinalChunkAckTimeout =
- std::chrono::milliseconds(5000);
+ chrono::SystemClock::for_at_least(std::chrono::milliseconds(5000));
static constexpr chrono::SystemClock::time_point kNoTimeout =
chrono::SystemClock::time_point(chrono::SystemClock::duration(0));
@@ -391,6 +392,9 @@ class Context {
// How long to wait for a chunk from the other end.
chrono::SystemClock::duration chunk_timeout_;
+ // How long for a client to wait for an initial server response.
+ chrono::SystemClock::duration initial_chunk_timeout_;
+
// How long to delay between transmitting subsequent data chunks within a
// window.
chrono::SystemClock::duration interchunk_delay_;
diff --git a/pw_transfer/public/pw_transfer/internal/event.h b/pw_transfer/public/pw_transfer/internal/event.h
index 79a7853d1..472bc59bf 100644
--- a/pw_transfer/public/pw_transfer/internal/event.h
+++ b/pw_transfer/public/pw_transfer/internal/event.h
@@ -75,6 +75,7 @@ struct NewTransferEvent {
rpc::Writer* rpc_writer;
const TransferParameters* max_parameters;
chrono::SystemClock::duration timeout;
+ chrono::SystemClock::duration initial_timeout;
uint32_t max_retries;
uint32_t max_lifetime_retries;
TransferThread* transfer_thread;
@@ -109,7 +110,6 @@ struct EndTransferEvent {
struct SendStatusChunkEvent {
uint32_t session_id;
- bool set_resource_id;
ProtocolVersion protocol_version;
Status::Code status;
TransferStream stream;
diff --git a/pw_transfer/public/pw_transfer/internal/server_context.h b/pw_transfer/public/pw_transfer/internal/server_context.h
index 265b75aa9..ce9a78bbb 100644
--- a/pw_transfer/public/pw_transfer/internal/server_context.h
+++ b/pw_transfer/public/pw_transfer/internal/server_context.h
@@ -35,9 +35,6 @@ class ServerContext final : public Context {
// Returns the pointer to the current handler.
const Handler* handler() { return handler_; }
- // In server-side transfer contexts, a session ID always exists.
- constexpr uint32_t id() const { return session_id(); }
-
private:
// Ends the transfer with the given status, calling the handler's Finalize
// method. No chunks are sent.
diff --git a/pw_transfer/public/pw_transfer/transfer.h b/pw_transfer/public/pw_transfer/transfer.h
index 42a9d7bca..93b39abe3 100644
--- a/pw_transfer/public/pw_transfer/transfer.h
+++ b/pw_transfer/public/pw_transfer/transfer.h
@@ -65,8 +65,7 @@ class TransferService : public pw_rpc::raw::Transfer::Service<TransferService> {
thread_(transfer_thread),
chunk_timeout_(chunk_timeout),
max_retries_(max_retries),
- max_lifetime_retries_(max_lifetime_retries),
- next_session_id_(1) {}
+ max_lifetime_retries_(max_lifetime_retries) {}
TransferService(const TransferService&) = delete;
TransferService(TransferService&&) = delete;
@@ -124,17 +123,12 @@ class TransferService : public pw_rpc::raw::Transfer::Service<TransferService> {
private:
void HandleChunk(ConstByteSpan message, internal::TransferType type);
- // TODO(frolv): This could be more sophisticated and less predictable.
- uint32_t GenerateNewSessionId() { return next_session_id_++; }
-
internal::TransferParameters max_parameters_;
TransferThread& thread_;
chrono::SystemClock::duration chunk_timeout_;
uint8_t max_retries_;
uint32_t max_lifetime_retries_;
-
- uint32_t next_session_id_;
};
} // namespace pw::transfer
diff --git a/pw_transfer/public/pw_transfer/transfer_thread.h b/pw_transfer/public/pw_transfer/transfer_thread.h
index 5904c9506..93e724008 100644
--- a/pw_transfer/public/pw_transfer/transfer_thread.h
+++ b/pw_transfer/public/pw_transfer/transfer_thread.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -42,6 +42,7 @@ class TransferThread : public thread::ThreadCore {
ByteSpan encode_buffer)
: client_transfers_(client_transfers),
server_transfers_(server_transfers),
+ next_session_id_(1),
chunk_buffer_(chunk_buffer),
encode_buffer_(encode_buffer) {}
@@ -52,20 +53,19 @@ class TransferThread : public thread::ThreadCore {
const TransferParameters& max_parameters,
Function<void(Status)>&& on_completion,
chrono::SystemClock::duration timeout,
+ chrono::SystemClock::duration initial_timeout,
uint8_t max_retries,
uint32_t max_lifetime_retries) {
- uint32_t session_id = version == ProtocolVersion::kLegacy
- ? resource_id
- : Context::kUnassignedSessionId;
StartTransfer(type,
version,
- session_id,
+ Context::kUnassignedSessionId, // Assigned later.
resource_id,
/*raw_chunk=*/{},
stream,
max_parameters,
std::move(on_completion),
timeout,
+ initial_timeout,
max_retries,
max_lifetime_retries);
}
@@ -88,6 +88,7 @@ class TransferThread : public thread::ThreadCore {
max_parameters,
/*on_completion=*/nullptr,
timeout,
+ timeout,
max_retries,
max_lifetime_retries);
}
@@ -100,6 +101,17 @@ class TransferThread : public thread::ThreadCore {
ProcessChunk(EventType::kServerChunk, chunk);
}
+ void SendServerStatus(TransferType type,
+ uint32_t session_id,
+ ProtocolVersion version,
+ Status status) {
+ SendStatus(type == TransferType::kTransmit ? TransferStream::kServerRead
+ : TransferStream::kServerWrite,
+ session_id,
+ version,
+ status);
+ }
+
void EndClientTransfer(uint32_t session_id,
Status status,
bool send_status_chunk = false) {
@@ -179,7 +191,7 @@ class TransferThread : public thread::ThreadCore {
uint32_t session_id) {
auto transfer =
std::find_if(transfers.begin(), transfers.end(), [session_id](auto& c) {
- return c.initialized() && c.id() == session_id;
+ return c.initialized() && c.session_id() == session_id;
});
return transfer != transfers.end() ? &*transfer : nullptr;
}
@@ -244,6 +256,8 @@ class TransferThread : public thread::ThreadCore {
// Returns the earliest timeout among all active transfers, up to kMaxTimeout.
chrono::SystemClock::time_point GetNextTransferTimeout() const;
+ uint32_t AssignSessionId();
+
void StartTransfer(TransferType type,
ProtocolVersion version,
uint32_t session_id,
@@ -253,11 +267,17 @@ class TransferThread : public thread::ThreadCore {
const TransferParameters& max_parameters,
Function<void(Status)>&& on_completion,
chrono::SystemClock::duration timeout,
+ chrono::SystemClock::duration initial_timeout,
uint8_t max_retries,
uint32_t max_lifetime_retries);
void ProcessChunk(EventType type, ConstByteSpan chunk);
+ void SendStatus(TransferStream stream,
+ uint32_t session_id,
+ ProtocolVersion version,
+ Status status);
+
void EndTransfer(EventType type,
uint32_t session_id,
Status status,
@@ -284,6 +304,13 @@ class TransferThread : public thread::ThreadCore {
span<ClientContext> client_transfers_;
span<ServerContext> server_transfers_;
+ // Identifier to use for the next started transfer, unique over the RPC
+ // channel between the transfer client and server.
+ //
+ // TODO(frolv): If we ever support changing the RPC channel, this should be
+ // reset to 1.
+ uint32_t next_session_id_;
+
// All registered transfer handlers.
IntrusiveList<Handler> handlers_;
diff --git a/pw_transfer/pw_transfer_private/chunk_testing.h b/pw_transfer/pw_transfer_private/chunk_testing.h
index 7347f6b8c..d909ab500 100644
--- a/pw_transfer/pw_transfer_private/chunk_testing.h
+++ b/pw_transfer/pw_transfer_private/chunk_testing.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -13,6 +13,7 @@
// the License.
#pragma once
+#include "gtest/gtest.h"
#include "pw_bytes/span.h"
#include "pw_containers/vector.h"
#include "pw_transfer/internal/chunk.h"
diff --git a/pw_transfer/py/pw_transfer/chunk.py b/pw_transfer/py/pw_transfer/chunk.py
index 0bb0c485a..e527dafa4 100644
--- a/pw_transfer/py/pw_transfer/chunk.py
+++ b/pw_transfer/py/pw_transfer/chunk.py
@@ -1,4 +1,4 @@
-# Copyright 2022 The Pigweed Authors
+# Copyright 2023 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
@@ -52,8 +52,28 @@ class Chunk:
Wraps the generated protobuf Chunk class with protocol-aware field encoding
and decoding.
+
+ Attributes:
+ protocol_version: Version of the transfer protocol with which the chunk
+ is encoded.
+ chunk_type: Type of the chunk within the protocol.
+ session_id: ID for the transfer session to which the chunk belongs.
+ desired_session_id: For a v2 START chunk, the client-assigned session ID
+ to request from the server.
+ resource_id: For a v2 START chunk, ID of the resource to transfer.
+ offset: Offset of the data to be transferred.
+ window_end_offset: In a parameters chunk, end offset of the available
+ window.
+ data: Raw transfer data.
+ remaining_bytes: Optional number of bytes remaining in the transfer.
+ Set to 0 when the data is fully transferred.
+ max_chunk_size_bytes: Maximum number of bytes to send in a single data
+ chunk.
+ min_delay_microseconds: Delay between data chunks to be sent.
"""
+ # pylint: disable=too-many-instance-attributes
+
Type = transfer_pb2.Chunk.Type
# TODO(frolv): Figure out how to make the chunk type annotation work.
@@ -63,6 +83,7 @@ class Chunk:
protocol_version: ProtocolVersion,
chunk_type: Any,
session_id: int = 0,
+ desired_session_id: Optional[int] = None,
resource_id: Optional[int] = None,
offset: int = 0,
window_end_offset: int = 0,
@@ -72,9 +93,31 @@ class Chunk:
min_delay_microseconds: Optional[int] = None,
status: Optional[Status] = None,
):
+ """Creates a new transfer chunk.
+
+ Args:
+ protocol_version: Version of the transfer protocol with which to
+ encode the chunk.
+ chunk_type: Type of the chunk within the protocol.
+ session_id: ID for the transfer session to which the chunk belongs.
+ desired_session_id: For a v2 START chunk, the client-assigned
+ session ID to request from the server.
+ resource_id: For a v2 START chunk, ID of the resource to transfer.
+ offset: Offset of the data to be transferred.
+ window_end_offset: In a parameters chunk, end offset of the
+ available window.
+ data: Raw transfer data.
+ remaining_bytes: Optional number of bytes remaining in the transfer.
+ Set to 0 when the data is fully transferred.
+ max_chunk_size_bytes: Maximum number of bytes to send in a single
+ data chunk.
+ min_delay_microseconds: Delay between data chunks to be sent.
+ status: In a COMPLETION chunk, final status of the transfer.
+ """
self.protocol_version = protocol_version
self.type = chunk_type
self.session_id = session_id
+ self.desired_session_id = desired_session_id
self.resource_id = resource_id
self.offset = offset
self.window_end_offset = window_end_offset
@@ -122,6 +165,10 @@ class Chunk:
chunk.protocol_version = ProtocolVersion.LEGACY
chunk.session_id = message.transfer_id
+ if message.HasField('desired_session_id'):
+ chunk.protocol_version = ProtocolVersion.VERSION_TWO
+ chunk.desired_session_id = message.desired_session_id
+
if message.HasField('resource_id'):
chunk.resource_id = message.resource_id
@@ -165,8 +212,12 @@ class Chunk:
if self.protocol_version is ProtocolVersion.VERSION_TWO:
if self.session_id != 0:
+ assert self.desired_session_id is None
message.session_id = self.session_id
+ if self.desired_session_id is not None:
+ message.desired_session_id = self.desired_session_id
+
if self._should_encode_legacy_fields():
if self.resource_id is not None:
message.transfer_id = self.resource_id
@@ -208,10 +259,6 @@ class Chunk:
Depending on the protocol version and type of chunk, this may correspond
to one of several proto fields.
"""
- if self.resource_id is not None:
- # Always prioritize a resource_id over a session_id.
- return self.resource_id
-
return self.session_id
def requests_transmission_from_offset(self) -> bool:
diff --git a/pw_transfer/py/pw_transfer/client.py b/pw_transfer/py/pw_transfer/client.py
index 85bfc4575..6b2ab6dd2 100644
--- a/pw_transfer/py/pw_transfer/client.py
+++ b/pw_transfer/py/pw_transfer/client.py
@@ -14,6 +14,7 @@
"""Client for the pw_transfer service, which transmits data over pw_rpc."""
import asyncio
+import ctypes
import logging
import threading
from typing import Any, Dict, Optional, Union
@@ -60,7 +61,7 @@ class Manager: # pylint: disable=too-many-instance-attributes
initial_response_timeout_s: float = 4.0,
max_retries: int = 3,
max_lifetime_retries: int = 1500,
- default_protocol_version=ProtocolVersion.LATEST,
+ default_protocol_version=ProtocolVersion.LEGACY,
):
"""Initializes a Manager on top of a TransferService.
@@ -83,6 +84,7 @@ class Manager: # pylint: disable=too-many-instance-attributes
# Ongoing transfers in the service by resource ID.
self._read_transfers: _TransferDict = {}
self._write_transfers: _TransferDict = {}
+ self._next_session_id = ctypes.c_uint32(1)
# RPC streams for read and write transfers. These are shareable by
# multiple transfers of the same type.
@@ -139,7 +141,14 @@ class Manager: # pylint: disable=too-many-instance-attributes
if protocol_version is None:
protocol_version = self._default_protocol_version
+ session_id = (
+ resource_id
+ if protocol_version is ProtocolVersion.LEGACY
+ else self.assign_session_id()
+ )
+
transfer = ReadTransfer(
+ session_id,
resource_id,
self._send_read_chunk,
self._end_read_transfer,
@@ -190,7 +199,14 @@ class Manager: # pylint: disable=too-many-instance-attributes
if protocol_version is None:
protocol_version = self._default_protocol_version
+ session_id = (
+ resource_id
+ if protocol_version is ProtocolVersion.LEGACY
+ else self.assign_session_id()
+ )
+
transfer = WriteTransfer(
+ session_id,
resource_id,
data,
self._send_write_chunk,
@@ -217,6 +233,15 @@ class Manager: # pylint: disable=too-many-instance-attributes
assert self._write_stream is not None
self._write_stream.send(chunk.to_message())
+ def assign_session_id(self) -> int:
+ new_id = self._next_session_id.value
+
+ self._next_session_id = ctypes.c_uint32(self._next_session_id.value + 1)
+ if self._next_session_id.value == 0:
+ self._next_session_id = ctypes.c_uint32(1)
+
+ return new_id
+
def _start_event_loop_thread(self):
"""Entry point for event loop thread that starts an asyncio context."""
asyncio.set_event_loop(self._loop)
@@ -291,15 +316,17 @@ class Manager: # pylint: disable=too-many-instance-attributes
# Find a transfer for the chunk in the list of active transfers.
try:
- if chunk.resource_id is not None:
- # Prioritize a resource_id if one is set.
- transfer = transfers[chunk.resource_id]
+ if chunk.protocol_version is ProtocolVersion.LEGACY:
+ transfer = next(
+ t
+ for t in transfers.values()
+ if t.resource_id == chunk.session_id
+ )
else:
- # Otherwise, match against either resource or session ID.
transfer = next(
t for t in transfers.values() if t.id == chunk.id()
)
- except (KeyError, StopIteration):
+ except StopIteration:
_LOG.error(
'TransferManager received chunk for unknown transfer %d',
chunk.id(),
diff --git a/pw_transfer/py/pw_transfer/transfer.py b/pw_transfer/py/pw_transfer/transfer.py
index 11dc55636..1cbd0d5cd 100644
--- a/pw_transfer/py/pw_transfer/transfer.py
+++ b/pw_transfer/py/pw_transfer/transfer.py
@@ -1,4 +1,4 @@
-# Copyright 2022 The Pigweed Authors
+# Copyright 2023 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
@@ -119,10 +119,9 @@ class Transfer(abc.ABC):
# A transfer has fully completed.
COMPLETE = 5
- _UNASSIGNED_SESSION_ID = 0
-
def __init__( # pylint: disable=too-many-arguments
self,
+ session_id: int,
resource_id: int,
send_chunk: Callable[[Chunk], None],
end_transfer: Callable[['Transfer'], None],
@@ -136,7 +135,7 @@ class Transfer(abc.ABC):
self.status = Status.OK
self.done = threading.Event()
- self._session_id = self._UNASSIGNED_SESSION_ID
+ self._session_id = session_id
self._resource_id = resource_id
self._send_chunk_fn = send_chunk
@@ -187,6 +186,9 @@ class Transfer(abc.ABC):
resource_id=self._resource_id,
)
+ if self._desired_protocol_version is ProtocolVersion.VERSION_TWO:
+ initial_chunk.desired_session_id = self._session_id
+
# Regardless of the desired protocol version, set any additional fields
# on the opening chunk, in case the server only runs legacy.
self._set_initial_chunk_fields(initial_chunk)
@@ -197,9 +199,7 @@ class Transfer(abc.ABC):
@property
def id(self) -> int:
"""Returns the identifier for the active transfer."""
- if self._session_id != self._UNASSIGNED_SESSION_ID:
- return self._session_id
- return self._resource_id
+ return self._session_id
@property
def resource_id(self) -> int:
@@ -278,15 +278,13 @@ class Transfer(abc.ABC):
self._configured_protocol_version = ProtocolVersion.LEGACY
self._state = Transfer._State.WAITING
- # Update the transfer's session ID in case it was expecting one to
- # be assigned by the server.
+ # Update the transfer's session ID, which will map to the
+ # transfer_id of the legacy chunk.
self._session_id = chunk.session_id
await self._handle_data_chunk(chunk)
return
- self._session_id = chunk.session_id
-
self._configured_protocol_version = ProtocolVersion(
min(
self._desired_protocol_version.value,
@@ -426,6 +424,7 @@ class WriteTransfer(Transfer):
def __init__( # pylint: disable=too-many-arguments
self,
+ session_id: int,
resource_id: int,
data: bytes,
send_chunk: Callable[[Chunk], None],
@@ -438,6 +437,7 @@ class WriteTransfer(Transfer):
progress_callback: Optional[ProgressCallback] = None,
):
super().__init__(
+ session_id,
resource_id,
send_chunk,
end_transfer,
@@ -621,6 +621,7 @@ class ReadTransfer(Transfer):
def __init__( # pylint: disable=too-many-arguments
self,
+ session_id: int,
resource_id: int,
send_chunk: Callable[[Chunk], None],
end_transfer: Callable[[Transfer], None],
@@ -635,6 +636,7 @@ class ReadTransfer(Transfer):
progress_callback: Optional[ProgressCallback] = None,
):
super().__init__(
+ session_id,
resource_id,
send_chunk,
end_transfer,
diff --git a/pw_transfer/py/tests/transfer_test.py b/pw_transfer/py/tests/transfer_test.py
index f06bbf508..8b29293f3 100644
--- a/pw_transfer/py/tests/transfer_test.py
+++ b/pw_transfer/py/tests/transfer_test.py
@@ -34,6 +34,7 @@ except ImportError:
from pigweed.pw_transfer import transfer_pb2 # type: ignore
_TRANSFER_SERVICE_ID = ids.calculate('pw.transfer.Transfer')
+_FIRST_SESSION_ID = 1
# If the default timeout is too short, some tests become flaky on Windows.
DEFAULT_TIMEOUT_S = 0.3
@@ -788,14 +789,14 @@ class TransferManagerTest(unittest.TestCase):
(
transfer_pb2.Chunk(
resource_id=39,
- session_id=280,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
),
(
transfer_pb2.Chunk(
- session_id=280,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=0,
data=b'version two',
@@ -804,7 +805,7 @@ class TransferManagerTest(unittest.TestCase):
),
(
transfer_pb2.Chunk(
- session_id=280,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION_ACK,
),
),
@@ -819,6 +820,7 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=39,
resource_id=39,
+ desired_session_id=_FIRST_SESSION_ID,
pending_bytes=8192,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -826,7 +828,7 @@ class TransferManagerTest(unittest.TestCase):
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=280,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK_CONFIRMATION,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -835,7 +837,7 @@ class TransferManagerTest(unittest.TestCase):
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=280,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
@@ -877,6 +879,7 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=40,
resource_id=40,
+ desired_session_id=_FIRST_SESSION_ID,
pending_bytes=8192,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -907,14 +910,14 @@ class TransferManagerTest(unittest.TestCase):
(
transfer_pb2.Chunk(
resource_id=72,
- session_id=880,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
),
(
transfer_pb2.Chunk(
- session_id=880,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.PARAMETERS_RETRANSMIT,
offset=0,
window_end_offset=32,
@@ -924,7 +927,7 @@ class TransferManagerTest(unittest.TestCase):
(), # In response to the first data chunk.
(
transfer_pb2.Chunk(
- session_id=880,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
@@ -940,29 +943,31 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=72,
resource_id=72,
+ desired_session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=880,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK_CONFIRMATION,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=880,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=0,
data=b'write ve',
),
transfer_pb2.Chunk(
- session_id=880,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=8,
data=b'rsion 2',
remaining_bytes=0,
),
transfer_pb2.Chunk(
- session_id=880, type=transfer_pb2.Chunk.Type.COMPLETION_ACK
+ session_id=_FIRST_SESSION_ID,
+ type=transfer_pb2.Chunk.Type.COMPLETION_ACK,
),
],
)
@@ -1010,6 +1015,7 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=76,
resource_id=76,
+ desired_session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
@@ -1032,7 +1038,7 @@ class TransferManagerTest(unittest.TestCase):
self.assertEqual(self._received_data(), b'write v... NOPE')
def test_v2_server_error(self) -> None:
- """Tests a timeout occurring during the opening handshake."""
+ """Tests a server error occurring during the opening handshake."""
manager = pw_transfer.Manager(
self._service,
@@ -1046,14 +1052,14 @@ class TransferManagerTest(unittest.TestCase):
(
transfer_pb2.Chunk(
resource_id=43,
- session_id=680,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
),
(
transfer_pb2.Chunk(
- session_id=680,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.DATA_LOSS.value,
),
@@ -1069,6 +1075,7 @@ class TransferManagerTest(unittest.TestCase):
[
transfer_pb2.Chunk(
transfer_id=43,
+ desired_session_id=_FIRST_SESSION_ID,
resource_id=43,
pending_bytes=8192,
max_chunk_size_bytes=1024,
@@ -1077,7 +1084,7 @@ class TransferManagerTest(unittest.TestCase):
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=680,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK_CONFIRMATION,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -1085,7 +1092,8 @@ class TransferManagerTest(unittest.TestCase):
),
# Client sends a COMPLETION_ACK in response to the server.
transfer_pb2.Chunk(
- session_id=680, type=transfer_pb2.Chunk.Type.COMPLETION_ACK
+ session_id=_FIRST_SESSION_ID,
+ type=transfer_pb2.Chunk.Type.COMPLETION_ACK,
),
],
)
@@ -1110,6 +1118,7 @@ class TransferManagerTest(unittest.TestCase):
start_chunk = transfer_pb2.Chunk(
transfer_id=41,
resource_id=41,
+ desired_session_id=_FIRST_SESSION_ID,
pending_bytes=8192,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -1138,7 +1147,7 @@ class TransferManagerTest(unittest.TestCase):
(
transfer_pb2.Chunk(
resource_id=73,
- session_id=101,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
@@ -1147,7 +1156,7 @@ class TransferManagerTest(unittest.TestCase):
(), # Don't respond to the first START_ACK_CONFIRMATION retry.
(
transfer_pb2.Chunk(
- session_id=101,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.PARAMETERS_RETRANSMIT,
offset=0,
window_end_offset=32,
@@ -1157,7 +1166,7 @@ class TransferManagerTest(unittest.TestCase):
(), # In response to the first data chunk.
(
transfer_pb2.Chunk(
- session_id=101,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
@@ -1168,7 +1177,7 @@ class TransferManagerTest(unittest.TestCase):
manager.write(73, b'write timeout 2')
start_ack_confirmation = transfer_pb2.Chunk(
- session_id=101,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK_CONFIRMATION,
protocol_version=ProtocolVersion.VERSION_TWO.value,
)
@@ -1179,6 +1188,7 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=73,
resource_id=73,
+ desired_session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
@@ -1186,20 +1196,21 @@ class TransferManagerTest(unittest.TestCase):
start_ack_confirmation, # Retry 1
start_ack_confirmation, # Retry 2
transfer_pb2.Chunk(
- session_id=101,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=0,
data=b'write ti',
),
transfer_pb2.Chunk(
- session_id=101,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=8,
data=b'meout 2',
remaining_bytes=0,
),
transfer_pb2.Chunk(
- session_id=101, type=transfer_pb2.Chunk.Type.COMPLETION_ACK
+ session_id=_FIRST_SESSION_ID,
+ type=transfer_pb2.Chunk.Type.COMPLETION_ACK,
),
],
)
@@ -1220,14 +1231,14 @@ class TransferManagerTest(unittest.TestCase):
(
transfer_pb2.Chunk(
resource_id=47,
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
),
(
transfer_pb2.Chunk(
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=0,
data=b'version two',
@@ -1238,7 +1249,7 @@ class TransferManagerTest(unittest.TestCase):
# of a COMPLETION_ACK.
(
transfer_pb2.Chunk(
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=0,
data=b'version two',
@@ -1247,7 +1258,7 @@ class TransferManagerTest(unittest.TestCase):
),
(
transfer_pb2.Chunk(
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION_ACK,
),
),
@@ -1262,6 +1273,7 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=47,
resource_id=47,
+ desired_session_id=_FIRST_SESSION_ID,
pending_bytes=8192,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -1269,20 +1281,20 @@ class TransferManagerTest(unittest.TestCase):
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK_CONFIRMATION,
max_chunk_size_bytes=1024,
window_end_offset=8192,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
# Completion should be re-sent following the repeated chunk.
transfer_pb2.Chunk(
- session_id=580,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
@@ -1305,14 +1317,14 @@ class TransferManagerTest(unittest.TestCase):
(
transfer_pb2.Chunk(
resource_id=47,
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
),
(
transfer_pb2.Chunk(
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.DATA,
offset=0,
data=b'dropped completion',
@@ -1331,6 +1343,7 @@ class TransferManagerTest(unittest.TestCase):
transfer_pb2.Chunk(
transfer_id=47,
resource_id=47,
+ desired_session_id=_FIRST_SESSION_ID,
pending_bytes=8192,
max_chunk_size_bytes=1024,
window_end_offset=8192,
@@ -1338,30 +1351,30 @@ class TransferManagerTest(unittest.TestCase):
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.START_ACK_CONFIRMATION,
max_chunk_size_bytes=1024,
window_end_offset=8192,
protocol_version=ProtocolVersion.VERSION_TWO.value,
),
transfer_pb2.Chunk(
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
# The completion should be retried per the usual retry flow.
transfer_pb2.Chunk(
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
transfer_pb2.Chunk(
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
transfer_pb2.Chunk(
- session_id=980,
+ session_id=_FIRST_SESSION_ID,
type=transfer_pb2.Chunk.Type.COMPLETION,
status=Status.OK.value,
),
@@ -1412,7 +1425,7 @@ class ProgressStatsTest(unittest.TestCase):
if __name__ == '__main__':
- # TODO(b/265975025): Only run this test in upstream Pigweed until the
+ # TODO: b/265975025 - Only run this test in upstream Pigweed until the
# occasional hangs are fixed.
if os.environ.get('PW_ROOT') and os.environ.get(
'PW_ROOT'
diff --git a/pw_transfer/test_rpc_server.cc b/pw_transfer/test_rpc_server.cc
deleted file mode 100644
index fa911b732..000000000
--- a/pw_transfer/test_rpc_server.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2022 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.
-
-// Simple RPC server with the transfer service registered. Reads HDLC frames
-// with RPC packets through a socket. The transfer service reads and writes to
-// files within a given directory. The name of a file is its resource ID.
-
-#include <cstddef>
-#include <filesystem>
-#include <string>
-#include <thread>
-#include <variant>
-#include <vector>
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_rpc_system_server/rpc_server.h"
-#include "pw_rpc_system_server/socket.h"
-#include "pw_stream/std_file_stream.h"
-#include "pw_thread/detached_thread.h"
-#include "pw_thread_stl/options.h"
-#include "pw_transfer/atomic_file_transfer_handler.h"
-#include "pw_transfer/transfer.h"
-#include "pw_transfer_test/test_server.raw_rpc.pb.h"
-
-namespace pw::transfer {
-namespace {
-class TestServerService
- : public pw_rpc::raw::TestServer::Service<TestServerService> {
- public:
- TestServerService(TransferService& transfer_service)
- : transfer_service_(transfer_service) {}
-
- ~TestServerService() { UnregisterHandlers(); }
-
- void UnregisterHandlers() {
- for (auto handler : file_transfer_handlers_) {
- transfer_service_.UnregisterHandler(*handler);
- }
- }
-
- void set_directory(const char* directory) { directory_ = directory; }
-
- void ReloadTransferFiles(ConstByteSpan, rpc::RawUnaryResponder&) {
- LoadFileHandlers();
- }
-
- void LoadFileHandlers() {
- PW_LOG_INFO("Reloading file handlers from %s", directory_.c_str());
- UnregisterHandlers();
- file_transfer_handlers_.clear();
-
- for (const auto& entry : std::filesystem::directory_iterator(directory_)) {
- if (!entry.is_regular_file()) {
- continue;
- }
-
- int resource_id = std::atoi(entry.path().filename().c_str());
- if (resource_id > 0) {
- PW_LOG_DEBUG("Found transfer file %d", resource_id);
- auto handler = std::make_shared<AtomicFileTransferHandler>(
- resource_id, entry.path().c_str());
- transfer_service_.RegisterHandler(*handler);
- file_transfer_handlers_.emplace_back(handler);
- }
- }
- }
-
- private:
- TransferService& transfer_service_;
- std::string directory_;
- std::vector<std::shared_ptr<AtomicFileTransferHandler>>
- file_transfer_handlers_;
-};
-
-constexpr size_t kChunkSizeBytes = 256;
-constexpr size_t kMaxReceiveSizeBytes = 1024;
-
-std::array<std::byte, kChunkSizeBytes> chunk_buffer;
-std::array<std::byte, kChunkSizeBytes> encode_buffer;
-transfer::Thread<4, 4> transfer_thread(chunk_buffer, encode_buffer);
-TransferService transfer_service(transfer_thread, kMaxReceiveSizeBytes);
-TestServerService test_server_service(transfer_service);
-
-void RunServer(int socket_port, const char* directory) {
- rpc::system_server::set_socket_port(socket_port);
-
- test_server_service.set_directory(directory);
- test_server_service.LoadFileHandlers();
-
- rpc::system_server::Init();
- rpc::system_server::Server().RegisterService(test_server_service,
- transfer_service);
-
- thread::DetachedThread(thread::stl::Options(), transfer_thread);
-
- PW_LOG_INFO("Starting pw_rpc server");
- PW_CHECK_OK(rpc::system_server::Start());
-}
-
-} // namespace
-} // namespace pw::transfer
-
-int main(int argc, char* argv[]) {
- if (argc != 3) {
- PW_LOG_ERROR("Usage: %s PORT DIR", argv[0]);
- return 1;
- }
-
- pw::transfer::RunServer(std::atoi(argv[1]), argv[2]);
- return 0;
-}
diff --git a/pw_transfer/test_server.proto b/pw_transfer/test_server.proto
deleted file mode 100644
index c7b658e0d..000000000
--- a/pw_transfer/test_server.proto
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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.
-
-syntax = "proto3";
-
-import 'pw_protobuf_protos/common.proto';
-
-package pw.transfer;
-
-// Manages the transfer service test RPC server (test_rpc_server.cc).
-service TestServer {
- rpc ReloadTransferFiles(pw.protobuf.Empty) returns (pw.protobuf.Empty);
-}
diff --git a/pw_transfer/transfer.cc b/pw_transfer/transfer.cc
index 722ed8217..eb04aeeb1 100644
--- a/pw_transfer/transfer.cc
+++ b/pw_transfer/transfer.cc
@@ -30,11 +30,23 @@ void TransferService::HandleChunk(ConstByteSpan message,
}
if (chunk->IsInitialChunk()) {
- uint32_t session_id =
- chunk->is_legacy() ? chunk->session_id() : GenerateNewSessionId();
uint32_t resource_id =
chunk->is_legacy() ? chunk->session_id() : chunk->resource_id().value();
+ uint32_t session_id;
+ if (chunk->is_legacy()) {
+ session_id = chunk->session_id();
+ } else if (chunk->desired_session_id().has_value()) {
+ session_id = chunk->desired_session_id().value();
+ } else {
+ // Non-legacy start chunks are required to use desired_session_id.
+ thread_.SendServerStatus(type,
+ chunk->session_id(),
+ chunk->protocol_version(),
+ Status::DataLoss());
+ return;
+ }
+
thread_.StartServerTransfer(type,
chunk->protocol_version(),
session_id,
diff --git a/pw_transfer/transfer.proto b/pw_transfer/transfer.proto
index 839eb2cd2..2d3c5d4c3 100644
--- a/pw_transfer/transfer.proto
+++ b/pw_transfer/transfer.proto
@@ -164,14 +164,13 @@ message Chunk {
// TODO(konkers): Implement this behavior.
COMPLETION_ACK = 5;
- // Acknowledges a transfer start request, assigning a session ID for the
+ // Acknowledges a transfer start request, accepting the session ID for the
// transfer and optionally negotiating the protocol version. Sent from
// server to client.
START_ACK = 6;
- // Confirmation of a START_ACK's assigned session ID and negotiated
- // parameters, sent by the client to the server. Initiates the data transfer
- // proper.
+ // Confirmation of a START_ACK's negotiated parameters, sent by the client
+ // to the server. Initiates the data transfer proper.
START_ACK_CONFIRMATION = 7;
};
@@ -195,8 +194,8 @@ message Chunk {
// Write ← ID of transferable resource
optional uint32 resource_id = 11;
- // Unique identifier for a specific transfer session. Assigned by a transfer
- // service during the initial handshake phase, and persists for the remainder
+ // Unique identifier for a specific transfer session. Chosen by the transfer
+ // client during the initial handshake phase, and persists for the remainder
// of that transfer operation.
//
// Read → ID of transfer session
@@ -214,4 +213,15 @@ message Chunk {
// Write → Desired (START) or configured (START_ACK_CONFIRMATION) version.
// Write ← Configured protocol version (START_ACK).
optional uint32 protocol_version = 13;
+
+ // Unique identifier for a specific transfer session. Chosen by the transfer
+ // client during the initial handshake phase. This field is used to request a
+ // session during the handshake, after which the regular session_id field is
+ // used.
+ //
+ // Read → Requested ID of transfer session
+ // Read ← N/A
+ // Write → Requested ID of transfer session
+ // Write ← N/A
+ optional uint32 desired_session_id = 14;
}
diff --git a/pw_transfer/transfer_test.cc b/pw_transfer/transfer_test.cc
index 8b5312107..fcc4e1999 100644
--- a/pw_transfer/transfer_test.cc
+++ b/pw_transfer/transfer_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -106,6 +106,7 @@ class SimpleReadTransfer final : public ReadOnlyHandler {
};
constexpr auto kData = bytes::Initialized<32>([](size_t i) { return i; });
+constexpr uint32_t kArbitrarySessionId = 123;
class ReadTransfer : public ::testing::Test {
protected:
@@ -113,7 +114,10 @@ class ReadTransfer : public ::testing::Test {
: handler_(3, kData),
transfer_thread_(span(data_buffer_).first(max_chunk_size_bytes),
encode_buffer_),
- ctx_(transfer_thread_, 64),
+ ctx_(transfer_thread_,
+ 64,
+ // Use a long timeout to avoid accidentally triggering timeouts.
+ std::chrono::minutes(1)),
system_thread_(TransferThreadOptions(), transfer_thread_) {
ctx_.service().RegisterHandler(handler_);
@@ -393,10 +397,12 @@ TEST_F(ReadTransfer, MaxChunkSize_Client) {
}
TEST_F(ReadTransfer, HandlerIsClearedAfterTransfer) {
+ // Request an end offset smaller than the data size to prevent the server
+ // from sending a final chunk.
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kLegacy, Chunk::Type::kStart)
.set_session_id(3)
- .set_window_end_offset(64)
+ .set_window_end_offset(16)
.set_offset(0)));
ctx_.SendClientStream(
EncodeChunk(Chunk::Final(ProtocolVersion::kLegacy, 3, OkStatus())));
@@ -412,10 +418,12 @@ TEST_F(ReadTransfer, HandlerIsClearedAfterTransfer) {
handler_.prepare_read_called = false;
handler_.finalize_read_called = false;
+ // Request an end offset smaller than the data size to prevent the server
+ // from sending a final chunk.
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kLegacy, Chunk::Type::kStart)
.set_session_id(3)
- .set_window_end_offset(64)
+ .set_window_end_offset(16)
.set_offset(0)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1605,6 +1613,7 @@ TEST_F(WriteTransferMaxBytes16, Service_SetMaxPendingBytes) {
TEST_F(ReadTransfer, Version2_SimpleTransfer) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1612,13 +1621,14 @@ TEST_F(ReadTransfer, Version2_SimpleTransfer) {
EXPECT_TRUE(handler_.prepare_read_called);
EXPECT_FALSE(handler_.finalize_read_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_FALSE(chunk.desired_session_id().has_value());
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 3u);
// Complete the handshake by confirming the server's ACK and sending the first
@@ -1626,7 +1636,7 @@ TEST_F(ReadTransfer, Version2_SimpleTransfer) {
rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(64)
.set_offset(0)));
@@ -1640,7 +1650,7 @@ TEST_F(ReadTransfer, Version2_SimpleTransfer) {
Chunk c1 = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(c1.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c1.type(), Chunk::Type::kData);
- EXPECT_EQ(c1.session_id(), 1u);
+ EXPECT_EQ(c1.session_id(), kArbitrarySessionId);
EXPECT_EQ(c1.offset(), 0u);
ASSERT_TRUE(c1.has_payload());
ASSERT_EQ(c1.payload().size(), kData.size());
@@ -1650,12 +1660,12 @@ TEST_F(ReadTransfer, Version2_SimpleTransfer) {
Chunk c2 = DecodeChunk(ctx_.responses()[2]);
EXPECT_EQ(c2.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c2.type(), Chunk::Type::kData);
- EXPECT_EQ(c2.session_id(), 1u);
+ EXPECT_EQ(c2.session_id(), kArbitrarySessionId);
EXPECT_FALSE(c2.has_payload());
EXPECT_EQ(c2.remaining_bytes(), 0u);
- ctx_.SendClientStream(
- EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 1, OkStatus())));
+ ctx_.SendClientStream(EncodeChunk(Chunk::Final(
+ ProtocolVersion::kVersionTwo, kArbitrarySessionId, OkStatus())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_TRUE(handler_.finalize_read_called);
@@ -1665,6 +1675,7 @@ TEST_F(ReadTransfer, Version2_SimpleTransfer) {
TEST_F(ReadTransfer, Version2_MultiChunk) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1672,13 +1683,13 @@ TEST_F(ReadTransfer, Version2_MultiChunk) {
EXPECT_TRUE(handler_.prepare_read_called);
EXPECT_FALSE(handler_.finalize_read_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 3u);
// Complete the handshake by confirming the server's ACK and sending the first
@@ -1686,7 +1697,7 @@ TEST_F(ReadTransfer, Version2_MultiChunk) {
rpc::test::WaitForPackets(ctx_.output(), 3, [this] {
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(64)
.set_max_chunk_size_bytes(16)
.set_offset(0)));
@@ -1699,7 +1710,7 @@ TEST_F(ReadTransfer, Version2_MultiChunk) {
Chunk c1 = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(c1.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c1.type(), Chunk::Type::kData);
- EXPECT_EQ(c1.session_id(), 1u);
+ EXPECT_EQ(c1.session_id(), kArbitrarySessionId);
EXPECT_EQ(c1.offset(), 0u);
ASSERT_TRUE(c1.has_payload());
ASSERT_EQ(c1.payload().size(), 16u);
@@ -1709,7 +1720,7 @@ TEST_F(ReadTransfer, Version2_MultiChunk) {
Chunk c2 = DecodeChunk(ctx_.responses()[2]);
EXPECT_EQ(c2.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c2.type(), Chunk::Type::kData);
- EXPECT_EQ(c2.session_id(), 1u);
+ EXPECT_EQ(c2.session_id(), kArbitrarySessionId);
EXPECT_EQ(c2.offset(), 16u);
ASSERT_TRUE(c2.has_payload());
ASSERT_EQ(c2.payload().size(), 16u);
@@ -1721,12 +1732,12 @@ TEST_F(ReadTransfer, Version2_MultiChunk) {
Chunk c3 = DecodeChunk(ctx_.responses()[3]);
EXPECT_EQ(c3.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c3.type(), Chunk::Type::kData);
- EXPECT_EQ(c3.session_id(), 1u);
+ EXPECT_EQ(c3.session_id(), kArbitrarySessionId);
EXPECT_FALSE(c3.has_payload());
EXPECT_EQ(c3.remaining_bytes(), 0u);
- ctx_.SendClientStream(
- EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 1, OkStatus())));
+ ctx_.SendClientStream(EncodeChunk(Chunk::Final(
+ ProtocolVersion::kVersionTwo, kArbitrarySessionId, OkStatus())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_TRUE(handler_.finalize_read_called);
@@ -1736,6 +1747,7 @@ TEST_F(ReadTransfer, Version2_MultiChunk) {
TEST_F(ReadTransfer, Version2_MultiParameters) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1743,20 +1755,20 @@ TEST_F(ReadTransfer, Version2_MultiParameters) {
EXPECT_TRUE(handler_.prepare_read_called);
EXPECT_FALSE(handler_.finalize_read_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 3u);
// Complete the handshake by confirming the server's ACK and sending the first
// read transfer parameters.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(16)
.set_offset(0)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1766,7 +1778,7 @@ TEST_F(ReadTransfer, Version2_MultiParameters) {
Chunk c1 = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(c1.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c1.type(), Chunk::Type::kData);
- EXPECT_EQ(c1.session_id(), 1u);
+ EXPECT_EQ(c1.session_id(), kArbitrarySessionId);
EXPECT_EQ(c1.offset(), 0u);
ASSERT_TRUE(c1.has_payload());
ASSERT_EQ(c1.payload().size(), 16u);
@@ -1776,7 +1788,7 @@ TEST_F(ReadTransfer, Version2_MultiParameters) {
rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kParametersContinue)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(64)
.set_offset(16)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1787,7 +1799,7 @@ TEST_F(ReadTransfer, Version2_MultiParameters) {
Chunk c2 = DecodeChunk(ctx_.responses()[2]);
EXPECT_EQ(c2.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c2.type(), Chunk::Type::kData);
- EXPECT_EQ(c2.session_id(), 1u);
+ EXPECT_EQ(c2.session_id(), kArbitrarySessionId);
EXPECT_EQ(c2.offset(), 16u);
ASSERT_TRUE(c2.has_payload());
ASSERT_EQ(c2.payload().size(), 16u);
@@ -1799,12 +1811,12 @@ TEST_F(ReadTransfer, Version2_MultiParameters) {
Chunk c3 = DecodeChunk(ctx_.responses()[3]);
EXPECT_EQ(c3.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c3.type(), Chunk::Type::kData);
- EXPECT_EQ(c3.session_id(), 1u);
+ EXPECT_EQ(c3.session_id(), kArbitrarySessionId);
EXPECT_FALSE(c3.has_payload());
EXPECT_EQ(c3.remaining_bytes(), 0u);
- ctx_.SendClientStream(
- EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo, 1, OkStatus())));
+ ctx_.SendClientStream(EncodeChunk(Chunk::Final(
+ ProtocolVersion::kVersionTwo, kArbitrarySessionId, OkStatus())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_TRUE(handler_.finalize_read_called);
@@ -1814,6 +1826,7 @@ TEST_F(ReadTransfer, Version2_MultiParameters) {
TEST_F(ReadTransfer, Version2_ClientTerminatesDuringHandshake) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1821,18 +1834,19 @@ TEST_F(ReadTransfer, Version2_ClientTerminatesDuringHandshake) {
EXPECT_TRUE(handler_.prepare_read_called);
EXPECT_FALSE(handler_.finalize_read_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 3u);
// Send a terminating chunk instead of the third part of the handshake.
- ctx_.SendClientStream(EncodeChunk(Chunk::Final(
- ProtocolVersion::kVersionTwo, 1, Status::ResourceExhausted())));
+ ctx_.SendClientStream(EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo,
+ kArbitrarySessionId,
+ Status::ResourceExhausted())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_TRUE(handler_.finalize_read_called);
@@ -1842,6 +1856,7 @@ TEST_F(ReadTransfer, Version2_ClientTerminatesDuringHandshake) {
TEST_F(ReadTransfer, Version2_ClientSendsWrongProtocolVersion) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1849,20 +1864,20 @@ TEST_F(ReadTransfer, Version2_ClientSendsWrongProtocolVersion) {
EXPECT_TRUE(handler_.prepare_read_called);
EXPECT_FALSE(handler_.finalize_read_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 3u);
// Complete the handshake by confirming the server's ACK and sending the first
// read transfer parameters.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(16)
.set_offset(0)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1872,7 +1887,7 @@ TEST_F(ReadTransfer, Version2_ClientSendsWrongProtocolVersion) {
Chunk c1 = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(c1.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c1.type(), Chunk::Type::kData);
- EXPECT_EQ(c1.session_id(), 1u);
+ EXPECT_EQ(c1.session_id(), kArbitrarySessionId);
EXPECT_EQ(c1.offset(), 0u);
ASSERT_TRUE(c1.has_payload());
ASSERT_EQ(c1.payload().size(), 16u);
@@ -1883,7 +1898,7 @@ TEST_F(ReadTransfer, Version2_ClientSendsWrongProtocolVersion) {
// server should terminate the transfer.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kLegacy, Chunk::Type::kParametersContinue)
- .set_session_id(1)
+ .set_session_id(3)
.set_window_end_offset(64)
.set_offset(16)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1893,7 +1908,7 @@ TEST_F(ReadTransfer, Version2_ClientSendsWrongProtocolVersion) {
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
ASSERT_TRUE(chunk.status().has_value());
EXPECT_EQ(chunk.status().value(), Status::Internal());
@@ -1904,6 +1919,7 @@ TEST_F(ReadTransfer, Version2_ClientSendsWrongProtocolVersion) {
TEST_F(ReadTransfer, Version2_BadParametersInHandshake) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(3)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1915,14 +1931,14 @@ TEST_F(ReadTransfer, Version2_BadParametersInHandshake) {
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 3u);
// Complete the handshake, but send an invalid parameters chunk. The server
// should terminate the transfer.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(0)
.set_offset(0)));
@@ -1933,7 +1949,7 @@ TEST_F(ReadTransfer, Version2_BadParametersInHandshake) {
Chunk c1 = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(c1.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(c1.type(), Chunk::Type::kCompletion);
- EXPECT_EQ(c1.session_id(), 1u);
+ EXPECT_EQ(c1.session_id(), kArbitrarySessionId);
ASSERT_TRUE(c1.status().has_value());
EXPECT_EQ(c1.status().value(), Status::ResourceExhausted());
}
@@ -1941,6 +1957,7 @@ TEST_F(ReadTransfer, Version2_BadParametersInHandshake) {
TEST_F(ReadTransfer, Version2_InvalidResourceId) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(99)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1949,6 +1966,7 @@ TEST_F(ReadTransfer, Version2_InvalidResourceId) {
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.status().value(), Status::NotFound());
}
@@ -1959,6 +1977,7 @@ TEST_F(ReadTransfer, Version2_PrepareError) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(99)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1966,6 +1985,7 @@ TEST_F(ReadTransfer, Version2_PrepareError) {
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 99u);
EXPECT_EQ(chunk.status().value(), Status::DataLoss());
}
@@ -1973,6 +1993,7 @@ TEST_F(ReadTransfer, Version2_PrepareError) {
TEST_F(WriteTransfer, Version2_SimpleTransfer) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(7)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -1980,19 +2001,19 @@ TEST_F(WriteTransfer, Version2_SimpleTransfer) {
EXPECT_TRUE(handler_.prepare_write_called);
EXPECT_FALSE(handler_.finalize_write_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 7u);
// Complete the handshake by confirming the server's ACK.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
// Server should respond by sending its initial transfer parameters.
@@ -2001,7 +2022,7 @@ TEST_F(WriteTransfer, Version2_SimpleTransfer) {
chunk = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kParametersRetransmit);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 32u);
ASSERT_TRUE(chunk.max_chunk_size_bytes().has_value());
@@ -2010,7 +2031,7 @@ TEST_F(WriteTransfer, Version2_SimpleTransfer) {
// Send all of our data.
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(0)
.set_payload(kData)
.set_remaining_bytes(0)));
@@ -2021,14 +2042,14 @@ TEST_F(WriteTransfer, Version2_SimpleTransfer) {
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
ASSERT_TRUE(chunk.status().has_value());
EXPECT_EQ(chunk.status().value(), OkStatus());
// Send the completion acknowledgement.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
ASSERT_EQ(ctx_.total_responses(), 3u);
@@ -2041,6 +2062,7 @@ TEST_F(WriteTransfer, Version2_SimpleTransfer) {
TEST_F(WriteTransfer, Version2_Multichunk) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(7)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2048,19 +2070,19 @@ TEST_F(WriteTransfer, Version2_Multichunk) {
EXPECT_TRUE(handler_.prepare_write_called);
EXPECT_FALSE(handler_.finalize_write_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 7u);
// Complete the handshake by confirming the server's ACK.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
// Server should respond by sending its initial transfer parameters.
@@ -2069,7 +2091,7 @@ TEST_F(WriteTransfer, Version2_Multichunk) {
chunk = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kParametersRetransmit);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 32u);
ASSERT_TRUE(chunk.max_chunk_size_bytes().has_value());
@@ -2078,12 +2100,12 @@ TEST_F(WriteTransfer, Version2_Multichunk) {
// Send all of our data across two chunks.
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(0)
.set_payload(span(kData).first(8))));
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(8)
.set_payload(span(kData).subspan(8))
.set_remaining_bytes(0)));
@@ -2094,14 +2116,14 @@ TEST_F(WriteTransfer, Version2_Multichunk) {
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
ASSERT_TRUE(chunk.status().has_value());
EXPECT_EQ(chunk.status().value(), OkStatus());
// Send the completion acknowledgement.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
ASSERT_EQ(ctx_.total_responses(), 3u);
@@ -2114,6 +2136,7 @@ TEST_F(WriteTransfer, Version2_Multichunk) {
TEST_F(WriteTransfer, Version2_ContinueParameters) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(7)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2121,19 +2144,19 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
EXPECT_TRUE(handler_.prepare_write_called);
EXPECT_FALSE(handler_.finalize_write_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 7u);
// Complete the handshake by confirming the server's ACK.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
// Server should respond by sending its initial transfer parameters.
@@ -2142,7 +2165,7 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
chunk = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kParametersRetransmit);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 32u);
ASSERT_TRUE(chunk.max_chunk_size_bytes().has_value());
@@ -2151,7 +2174,7 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
// Send all of our data across several chunks.
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(0)
.set_payload(span(kData).first(8))));
@@ -2160,7 +2183,7 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(8)
.set_payload(span(kData).subspan(8, 8))));
@@ -2170,13 +2193,13 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kParametersContinue);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.offset(), 16u);
EXPECT_EQ(chunk.window_end_offset(), 32u);
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(16)
.set_payload(span(kData).subspan(16, 8))));
@@ -2186,13 +2209,13 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kParametersContinue);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.offset(), 24u);
EXPECT_EQ(chunk.window_end_offset(), 32u);
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_offset(24)
.set_payload(span(kData).subspan(24))
.set_remaining_bytes(0)));
@@ -2203,14 +2226,14 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
ASSERT_TRUE(chunk.status().has_value());
EXPECT_EQ(chunk.status().value(), OkStatus());
// Send the completion acknowledgement.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
ASSERT_EQ(ctx_.total_responses(), 5u);
@@ -2223,6 +2246,7 @@ TEST_F(WriteTransfer, Version2_ContinueParameters) {
TEST_F(WriteTransfer, Version2_ClientTerminatesDuringHandshake) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(7)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2230,18 +2254,20 @@ TEST_F(WriteTransfer, Version2_ClientTerminatesDuringHandshake) {
EXPECT_TRUE(handler_.prepare_write_called);
EXPECT_FALSE(handler_.finalize_write_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 7u);
// Send an error chunk instead of completing the handshake.
- ctx_.SendClientStream(EncodeChunk(Chunk::Final(
- ProtocolVersion::kVersionTwo, 1, Status::FailedPrecondition())));
+ ctx_.SendClientStream(
+ EncodeChunk(Chunk::Final(ProtocolVersion::kVersionTwo,
+ kArbitrarySessionId,
+ Status::FailedPrecondition())));
transfer_thread_.WaitUntilEventIsProcessed();
EXPECT_TRUE(handler_.finalize_write_called);
@@ -2251,6 +2277,7 @@ TEST_F(WriteTransfer, Version2_ClientTerminatesDuringHandshake) {
TEST_F(WriteTransfer, Version2_ClientSendsWrongProtocolVersion) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(7)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2258,19 +2285,19 @@ TEST_F(WriteTransfer, Version2_ClientSendsWrongProtocolVersion) {
EXPECT_TRUE(handler_.prepare_write_called);
EXPECT_FALSE(handler_.finalize_write_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 7u);
// Complete the handshake by confirming the server's ACK.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
// Server should respond by sending its initial transfer parameters.
@@ -2279,7 +2306,7 @@ TEST_F(WriteTransfer, Version2_ClientSendsWrongProtocolVersion) {
chunk = DecodeChunk(ctx_.responses()[1]);
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kParametersRetransmit);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.offset(), 0u);
EXPECT_EQ(chunk.window_end_offset(), 32u);
ASSERT_TRUE(chunk.max_chunk_size_bytes().has_value());
@@ -2289,7 +2316,7 @@ TEST_F(WriteTransfer, Version2_ClientSendsWrongProtocolVersion) {
// instead.
ctx_.SendClientStream<64>(
EncodeChunk(Chunk(ProtocolVersion::kLegacy, Chunk::Type::kData)
- .set_session_id(1)
+ .set_session_id(7)
.set_offset(0)
.set_payload(kData)
.set_remaining_bytes(0)));
@@ -2306,7 +2333,7 @@ TEST_F(WriteTransfer, Version2_ClientSendsWrongProtocolVersion) {
// Send the completion acknowledgement.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kCompletionAck)
- .set_session_id(1)));
+ .set_session_id(kArbitrarySessionId)));
transfer_thread_.WaitUntilEventIsProcessed();
ASSERT_EQ(ctx_.total_responses(), 3u);
@@ -2315,6 +2342,7 @@ TEST_F(WriteTransfer, Version2_ClientSendsWrongProtocolVersion) {
TEST_F(WriteTransfer, Version2_InvalidResourceId) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(99)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2323,6 +2351,8 @@ TEST_F(WriteTransfer, Version2_InvalidResourceId) {
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
+ EXPECT_FALSE(chunk.resource_id().has_value());
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.status().value(), Status::NotFound());
}
@@ -2448,6 +2478,7 @@ TEST_F(ReadTransferLowMaxRetries, FailsAfterLifetimeRetryCount) {
TEST_F(ReadTransferLowMaxRetries, Version2_FailsAfterLifetimeRetryCount) {
ctx_.SendClientStream(
EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
.set_resource_id(9)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2455,18 +2486,18 @@ TEST_F(ReadTransferLowMaxRetries, Version2_FailsAfterLifetimeRetryCount) {
EXPECT_TRUE(handler_.prepare_read_called);
EXPECT_FALSE(handler_.finalize_read_called);
- // First, the server responds with a START_ACK, assigning a session ID and
+ // First, the server responds with a START_ACK, accepting the session ID and
// confirming the protocol version.
ASSERT_EQ(ctx_.total_responses(), 1u);
Chunk chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
- EXPECT_EQ(chunk.session_id(), 1u);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
EXPECT_EQ(chunk.resource_id(), 9u);
// Time out twice. Server should retry both times.
- transfer_thread_.SimulateServerTimeout(1);
- transfer_thread_.SimulateServerTimeout(1);
+ transfer_thread_.SimulateServerTimeout(kArbitrarySessionId);
+ transfer_thread_.SimulateServerTimeout(kArbitrarySessionId);
ASSERT_EQ(ctx_.total_responses(), 3u);
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
@@ -2474,7 +2505,7 @@ TEST_F(ReadTransferLowMaxRetries, Version2_FailsAfterLifetimeRetryCount) {
// Complete the handshake, allowing the transfer to continue.
ctx_.SendClientStream(EncodeChunk(
Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStartAckConfirmation)
- .set_session_id(1)
+ .set_session_id(kArbitrarySessionId)
.set_window_end_offset(16)
.set_offset(0)));
transfer_thread_.WaitUntilEventIsProcessed();
@@ -2484,22 +2515,89 @@ TEST_F(ReadTransferLowMaxRetries, Version2_FailsAfterLifetimeRetryCount) {
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
// Time out three more times. The transfer should terminate.
- transfer_thread_.SimulateServerTimeout(1);
+ transfer_thread_.SimulateServerTimeout(kArbitrarySessionId);
ASSERT_EQ(ctx_.total_responses(), 5u);
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
- transfer_thread_.SimulateServerTimeout(1);
+ transfer_thread_.SimulateServerTimeout(kArbitrarySessionId);
ASSERT_EQ(ctx_.total_responses(), 6u);
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.type(), Chunk::Type::kData);
- transfer_thread_.SimulateServerTimeout(1);
+ transfer_thread_.SimulateServerTimeout(kArbitrarySessionId);
ASSERT_EQ(ctx_.total_responses(), 7u);
chunk = DecodeChunk(ctx_.responses().back());
EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
EXPECT_EQ(chunk.status(), Status::DeadlineExceeded());
}
+TEST_F(WriteTransfer, Version2_ClientRetriesOpeningChunk) {
+ ctx_.SendClientStream(
+ EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
+ .set_resource_id(7)));
+
+ transfer_thread_.WaitUntilEventIsProcessed();
+
+ EXPECT_TRUE(handler_.prepare_write_called);
+ EXPECT_FALSE(handler_.finalize_write_called);
+
+ // First, the server responds with a START_ACK, accepting the session ID and
+ // confirming the protocol version.
+ ASSERT_EQ(ctx_.total_responses(), 1u);
+ Chunk chunk = DecodeChunk(ctx_.responses().back());
+ EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
+ EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
+ EXPECT_EQ(chunk.resource_id(), 7u);
+
+ // Reset prepare_write_called to ensure it isn't called again.
+ handler_.prepare_write_called = false;
+
+ // Client re-sends the same chunk instead of finishing the handshake.
+ ctx_.SendClientStream(
+ EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_desired_session_id(kArbitrarySessionId)
+ .set_resource_id(7)));
+
+ transfer_thread_.WaitUntilEventIsProcessed();
+
+ // The server should re-send the same START_ACK without reinitializing the
+ // handler.
+ ASSERT_EQ(ctx_.total_responses(), 2u);
+ chunk = DecodeChunk(ctx_.responses().back());
+ EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
+ EXPECT_EQ(chunk.type(), Chunk::Type::kStartAck);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
+ EXPECT_EQ(chunk.resource_id(), 7u);
+
+ EXPECT_FALSE(handler_.prepare_write_called);
+ EXPECT_FALSE(handler_.finalize_write_called);
+}
+
+TEST_F(WriteTransfer, Version2_RegularSessionIdInStartChunk) {
+ // Client incorrectly sets session_id instead of desired_session_id in its
+ // START chunk. Server should immediately respond with a protocol error.
+ ctx_.SendClientStream(
+ EncodeChunk(Chunk(ProtocolVersion::kVersionTwo, Chunk::Type::kStart)
+ .set_session_id(kArbitrarySessionId)
+ .set_resource_id(99)));
+
+ transfer_thread_.WaitUntilEventIsProcessed();
+
+ EXPECT_FALSE(handler_.prepare_write_called);
+ EXPECT_FALSE(handler_.finalize_write_called);
+
+ ASSERT_EQ(ctx_.total_responses(), 1u);
+
+ Chunk chunk = DecodeChunk(ctx_.responses().back());
+ EXPECT_EQ(chunk.protocol_version(), ProtocolVersion::kVersionTwo);
+ EXPECT_EQ(chunk.session_id(), kArbitrarySessionId);
+ EXPECT_FALSE(chunk.resource_id().has_value());
+ EXPECT_EQ(chunk.type(), Chunk::Type::kCompletion);
+ EXPECT_EQ(chunk.status().value(), Status::DataLoss());
+}
+
} // namespace
} // namespace pw::transfer::test
diff --git a/pw_transfer/transfer_thread.cc b/pw_transfer/transfer_thread.cc
index c0d03efa1..84bf92651 100644
--- a/pw_transfer/transfer_thread.cc
+++ b/pw_transfer/transfer_thread.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2023 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
@@ -98,22 +98,32 @@ chrono::SystemClock::time_point TransferThread::GetNextTransferTimeout() const {
return timeout;
}
-void TransferThread::StartTransfer(TransferType type,
- ProtocolVersion version,
- uint32_t session_id,
- uint32_t resource_id,
- ConstByteSpan raw_chunk,
- stream::Stream* stream,
- const TransferParameters& max_parameters,
- Function<void(Status)>&& on_completion,
- chrono::SystemClock::duration timeout,
- uint8_t max_retries,
- uint32_t max_lifetime_retries) {
+void TransferThread::StartTransfer(
+ TransferType type,
+ ProtocolVersion version,
+ uint32_t session_id,
+ uint32_t resource_id,
+ ConstByteSpan raw_chunk,
+ stream::Stream* stream,
+ const TransferParameters& max_parameters,
+ Function<void(Status)>&& on_completion,
+ chrono::SystemClock::duration timeout,
+ chrono::SystemClock::duration initial_timeout,
+ uint8_t max_retries,
+ uint32_t max_lifetime_retries) {
// Block until the last event has been processed.
next_event_ownership_.acquire();
bool is_client_transfer = stream != nullptr;
+ if (is_client_transfer) {
+ if (version == ProtocolVersion::kLegacy) {
+ session_id = resource_id;
+ } else if (session_id == Context::kUnassignedSessionId) {
+ session_id = AssignSessionId();
+ }
+ }
+
next_event_.type = is_client_transfer ? EventType::kNewClientTransfer
: EventType::kNewServerTransfer;
@@ -128,6 +138,7 @@ void TransferThread::StartTransfer(TransferType type,
.resource_id = resource_id,
.max_parameters = &max_parameters,
.timeout = timeout,
+ .initial_timeout = initial_timeout,
.max_retries = max_retries,
.max_lifetime_retries = max_lifetime_retries,
.transfer_thread = this,
@@ -158,11 +169,7 @@ void TransferThread::StartTransfer(TransferType type,
// No handler exists for the transfer: return a NOT_FOUND.
next_event_.type = EventType::kSendStatusChunk;
next_event_.send_status_chunk = {
- // Identify the status chunk using the requested resource ID rather
- // than the session ID. In legacy, the two are the same, whereas in
- // v2+ the client has not yet been assigned a session.
- .session_id = resource_id,
- .set_resource_id = version == ProtocolVersion::kVersionTwo,
+ .session_id = session_id,
.protocol_version = version,
.status = Status::NotFound().code(),
.stream = type == TransferType::kTransmit
@@ -196,7 +203,7 @@ void TransferThread::ProcessChunk(EventType type, ConstByteSpan chunk) {
next_event_.type = type;
next_event_.chunk = {
.context_identifier = identifier->value(),
- .match_resource_id = identifier->is_resource(),
+ .match_resource_id = identifier->is_legacy(),
.data = chunk_buffer_.data(),
.size = chunk.size(),
};
@@ -204,6 +211,24 @@ void TransferThread::ProcessChunk(EventType type, ConstByteSpan chunk) {
event_notification_.release();
}
+void TransferThread::SendStatus(TransferStream stream,
+ uint32_t session_id,
+ ProtocolVersion version,
+ Status status) {
+ // Block until the last event has been processed.
+ next_event_ownership_.acquire();
+
+ next_event_.type = EventType::kSendStatusChunk;
+ next_event_.send_status_chunk = {
+ .session_id = session_id,
+ .protocol_version = version,
+ .status = status.code(),
+ .stream = stream,
+ };
+
+ event_notification_.release();
+}
+
void TransferThread::EndTransfer(EventType type,
uint32_t session_id,
Status status,
@@ -319,9 +344,7 @@ void TransferThread::HandleEvent(const internal::Event& event) {
} else if (event.type == EventType::kNewServerTransfer) {
// On the server, send a status chunk back to the client.
SendStatusChunk(
- {.session_id = event.new_transfer.resource_id,
- .set_resource_id = event.new_transfer.protocol_version ==
- ProtocolVersion::kVersionTwo,
+ {.session_id = event.new_transfer.session_id,
.protocol_version = event.new_transfer.protocol_version,
.status = Status::ResourceExhausted().code(),
.stream = event.new_transfer.type == TransferType::kTransmit
@@ -394,10 +417,6 @@ void TransferThread::SendStatusChunk(
Chunk chunk =
Chunk::Final(event.protocol_version, event.session_id, event.status);
- if (event.set_resource_id) {
- chunk.set_resource_id(event.session_id);
- }
-
Result<ConstByteSpan> result = chunk.Encode(chunk_buffer_);
if (!result.ok()) {
PW_LOG_ERROR("Failed to encode final chunk for transfer %u",
@@ -412,6 +431,15 @@ void TransferThread::SendStatusChunk(
}
}
+// Should only be called with the `next_event_ownership_` lock held.
+uint32_t TransferThread::AssignSessionId() {
+ uint32_t session_id = next_session_id_++;
+ if (session_id == 0) {
+ session_id = next_session_id_++;
+ }
+ return session_id;
+}
+
} // namespace pw::transfer::internal
PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_transfer/transfer_thread_test.cc b/pw_transfer/transfer_thread_test.cc
index d83f8298d..0df41ec6f 100644
--- a/pw_transfer/transfer_thread_test.cc
+++ b/pw_transfer/transfer_thread_test.cc
@@ -32,6 +32,10 @@ namespace {
using internal::Chunk;
+// Effectively unlimited timeout as these tests should never hit it.
+constexpr chrono::SystemClock::duration kNeverTimeout =
+ std::chrono::seconds(60);
+
// TODO(frolv): Have a generic way to obtain a thread for testing on any system.
thread::Options& TransferThreadOptions() {
static thread::stl::Options options;
@@ -111,7 +115,7 @@ TEST_F(TransferThreadTest, AddTransferHandler) {
3,
{},
max_parameters_,
- std::chrono::seconds(2),
+ kNeverTimeout,
3,
10);
@@ -136,7 +140,7 @@ TEST_F(TransferThreadTest, RemoveTransferHandler) {
3,
{},
max_parameters_,
- std::chrono::seconds(2),
+ kNeverTimeout,
3,
10);
@@ -173,7 +177,7 @@ TEST_F(TransferThreadTest, ProcessChunk_SendsWindow) {
.set_max_chunk_size_bytes(8)
.set_offset(0)),
max_parameters_,
- std::chrono::seconds(2),
+ kNeverTimeout,
3,
10);
});
@@ -220,7 +224,7 @@ TEST_F(TransferThreadTest, StartTransferExhausted_Server) {
.set_max_chunk_size_bytes(8)
.set_offset(0)),
max_parameters_,
- std::chrono::seconds(2),
+ kNeverTimeout,
3,
10);
transfer_thread_.WaitUntilEventIsProcessed();
@@ -244,7 +248,7 @@ TEST_F(TransferThreadTest, StartTransferExhausted_Server) {
.set_max_chunk_size_bytes(8)
.set_offset(0)),
max_parameters_,
- std::chrono::seconds(2),
+ kNeverTimeout,
3,
10);
transfer_thread_.WaitUntilEventIsProcessed();
@@ -279,7 +283,8 @@ TEST_F(TransferThreadTest, StartTransferExhausted_Client) {
&buffer3,
max_parameters_,
[&status3](Status status) { status3 = status; },
- std::chrono::seconds(2),
+ kNeverTimeout,
+ kNeverTimeout,
3,
10);
transfer_thread_.WaitUntilEventIsProcessed();
@@ -296,7 +301,8 @@ TEST_F(TransferThreadTest, StartTransferExhausted_Client) {
&buffer4,
max_parameters_,
[&status4](Status status) { status4 = status; },
- std::chrono::seconds(2),
+ kNeverTimeout,
+ kNeverTimeout,
3,
10);
transfer_thread_.WaitUntilEventIsProcessed();
@@ -322,7 +328,7 @@ TEST_F(TransferThreadTest, VersionTwo_NoHandler) {
/*resource_id=*/7,
{},
max_parameters_,
- std::chrono::seconds(2),
+ kNeverTimeout,
3,
10);
@@ -333,10 +339,10 @@ TEST_F(TransferThreadTest, VersionTwo_NoHandler) {
ASSERT_EQ(ctx_.total_responses(), 1u);
Result<Chunk::Identifier> id = Chunk::ExtractIdentifier(ctx_.response());
ASSERT_TRUE(id.ok());
- EXPECT_EQ(id->value(), 7u);
+ EXPECT_EQ(id->value(), 421u);
auto chunk = DecodeChunk(ctx_.response());
- EXPECT_EQ(chunk.session_id(), 7u);
- EXPECT_EQ(chunk.resource_id(), 7u);
+ EXPECT_EQ(chunk.session_id(), 421u);
+ EXPECT_FALSE(chunk.resource_id().has_value());
ASSERT_TRUE(chunk.status().has_value());
EXPECT_EQ(chunk.status().value(), Status::NotFound());
diff --git a/pw_transfer/ts/client.ts b/pw_transfer/ts/client.ts
index c39e37775..79f397df2 100644
--- a/pw_transfer/ts/client.ts
+++ b/pw_transfer/ts/client.ts
@@ -19,8 +19,8 @@ import {
BidirectionalStreamingMethodStub,
ServiceClient,
} from 'pigweedjs/pw_rpc';
-import {Status} from 'pigweedjs/pw_status';
-import {Chunk} from 'pigweedjs/protos/pw_transfer/transfer_pb';
+import { Status } from 'pigweedjs/pw_status';
+import { Chunk } from 'pigweedjs/protos/pw_transfer/transfer_pb';
import {
ReadTransfer,
@@ -49,8 +49,8 @@ const DEFAULT_INITIAL_RESPONSE_TIMEOUT = 4;
*/
export class Manager {
// Ongoing transfers in the service by ID
- private readTransfers: TransferDict = {};
- private writeTransfers: TransferDict = {};
+ readTransfers: TransferDict = {};
+ writeTransfers: TransferDict = {};
// RPC streams for read and write transfers. These are shareable by
// multiple transfers of the same type.
@@ -73,7 +73,7 @@ export class Manager {
private service: ServiceClient,
private defaultResponseTimeoutS = DEFAULT_RESPONSE_TIMEOUT_S,
private initialResponseTimeoutS = DEFAULT_INITIAL_RESPONSE_TIMEOUT,
- private maxRetries = DEFAULT_MAX_RETRIES
+ private maxRetries = DEFAULT_MAX_RETRIES,
) {}
/**
@@ -83,11 +83,11 @@ export class Manager {
*/
async read(
resourceId: number,
- progressCallback?: ProgressCallback
+ progressCallback?: ProgressCallback,
): Promise<Uint8Array> {
if (resourceId in this.readTransfers) {
throw new Error(
- `Read transfer for resource ${resourceId} already exists`
+ `Read transfer for resource ${resourceId} already exists`,
);
}
const transfer = new ReadTransfer(
@@ -95,7 +95,7 @@ export class Manager {
this.sendReadChunkCallback,
this.defaultResponseTimeoutS,
this.maxRetries,
- progressCallback
+ progressCallback,
);
this.startReadTransfer(transfer);
@@ -129,7 +129,7 @@ export class Manager {
async write(
resourceId: number,
data: Uint8Array,
- progressCallback?: ProgressCallback
+ progressCallback?: ProgressCallback,
): Promise<void> {
const transfer = new WriteTransfer(
resourceId,
@@ -138,7 +138,7 @@ export class Manager {
this.defaultResponseTimeoutS,
this.initialResponseTimeoutS,
this.maxRetries,
- progressCallback
+ progressCallback,
);
this.startWriteTransfer(transfer);
@@ -172,27 +172,31 @@ export class Manager {
private openReadStream(): void {
const readRpc = this.service.method(
- 'Read'
+ 'Read',
)! as BidirectionalStreamingMethodStub;
this.readStream = readRpc.invoke(
(chunk: Chunk) => {
this.handleChunk(this.readTransfers, chunk);
},
- () => {},
- this.onReadError
+ () => {
+ // Do nothing.
+ },
+ this.onReadError,
);
}
private openWriteStream(): void {
const writeRpc = this.service.method(
- 'Write'
+ 'Write',
)! as BidirectionalStreamingMethodStub;
this.writeStream = writeRpc.invoke(
(chunk: Chunk) => {
this.handleChunk(this.writeTransfers, chunk);
},
- () => {},
- this.onWriteError
+ () => {
+ // Do nothing.
+ },
+ this.onWriteError,
);
}
@@ -255,7 +259,7 @@ export class Manager {
const transfer = transfers[chunk.getTransferId()];
if (transfer === undefined) {
console.error(
- `TransferManager received chunk for unknown transfer ${chunk.getTransferId()}`
+ `TransferManager received chunk for unknown transfer ${chunk.getTransferId()}`,
);
return;
}
diff --git a/pw_transfer/ts/index.ts b/pw_transfer/ts/index.ts
index 5ba7e8382..7a193cfa7 100644
--- a/pw_transfer/ts/index.ts
+++ b/pw_transfer/ts/index.ts
@@ -12,4 +12,5 @@
// License for the specific language governing permissions and limitations under
// the License.
-export {Manager} from './client';
+export { Manager } from './client';
+export { ProgressStats, ProgressCallback } from './transfer';
diff --git a/pw_transfer/ts/transfer.ts b/pw_transfer/ts/transfer.ts
index c098a8232..c4eef6b2d 100644
--- a/pw_transfer/ts/transfer.ts
+++ b/pw_transfer/ts/transfer.ts
@@ -12,19 +12,14 @@
// License for the specific language governing permissions and limitations under
// the License.
-import {
- BidirectionalStreamingCall,
- BidirectionalStreamingMethodStub,
- ServiceClient,
-} from 'pigweedjs/pw_rpc';
-import {Status} from 'pigweedjs/pw_status';
-import {Chunk} from 'pigweedjs/protos/pw_transfer/transfer_pb';
+import { Status } from 'pigweedjs/pw_status';
+import { Chunk } from 'pigweedjs/protos/pw_transfer/transfer_pb';
export class ProgressStats {
constructor(
readonly bytesSent: number,
readonly bytesConfirmedReceived: number,
- readonly totalSizeBytes?: number
+ readonly totalSizeBytes?: number,
) {}
get percentReceived(): number {
@@ -55,7 +50,7 @@ class Timer {
constructor(
readonly timeoutS: number,
- private readonly callback: () => any
+ private readonly callback: () => any,
) {}
/**
@@ -89,7 +84,7 @@ class Timer {
export abstract class Transfer {
status: Status = Status.OK;
done: Promise<Status>;
- protected data = new Uint8Array();
+ data = new Uint8Array(0);
private retries = 0;
private responseTimer?: Timer;
@@ -100,10 +95,10 @@ export abstract class Transfer {
protected sendChunk: (chunk: Chunk) => void,
responseTimeoutS: number,
private maxRetries: number,
- private progressCallback?: ProgressCallback
+ private progressCallback?: ProgressCallback,
) {
this.responseTimer = new Timer(responseTimeoutS, this.onTimeout);
- this.done = new Promise<Status>(resolve => {
+ this.done = new Promise<Status>((resolve) => {
this.resolve = resolve!;
});
}
@@ -126,7 +121,7 @@ export abstract class Transfer {
}
console.debug(
- `Received no responses for ${this.responseTimer?.timeoutS}; retrying ${this.retries}/${this.maxRetries}`
+ `Received no responses for ${this.responseTimer?.timeoutS}; retrying ${this.retries}/${this.maxRetries}`,
);
this.retryAfterTimeout();
@@ -145,7 +140,7 @@ export abstract class Transfer {
/** Sends the initial chunk of the transfer. */
begin(): void {
- this.sendChunk(this.initialChunk);
+ this.sendChunk(this.initialChunk as any);
this.responseTimer?.start();
}
@@ -167,12 +162,12 @@ export abstract class Transfer {
updateProgress(
bytesSent: number,
bytesConfirmedReceived: number,
- totalSizeBytes?: number
+ totalSizeBytes?: number,
): void {
const stats = new ProgressStats(
bytesSent,
bytesConfirmedReceived,
- totalSizeBytes
+ totalSizeBytes,
);
console.debug(`Transfer ${this.id} progress: ${stats}`);
@@ -232,8 +227,6 @@ export class ReadTransfer extends Transfer {
// of the window, and so on.
private static EXTEND_WINDOW_DIVISOR = 2;
- data = new Uint8Array();
-
constructor(
id: number,
sendChunk: (chunk: Chunk) => void,
@@ -242,7 +235,7 @@ export class ReadTransfer extends Transfer {
progressCallback?: ProgressCallback,
maxBytesToReceive = 8192,
maxChunkSize = 1024,
- chunkDelayMicroS?: number
+ chunkDelayMicroS?: number,
) {
super(id, sendChunk, responseTimeoutS, maxRetries, progressCallback);
this.maxBytesToReceive = maxBytesToReceive;
@@ -252,12 +245,12 @@ export class ReadTransfer extends Transfer {
this.windowEndOffset = maxBytesToReceive;
}
- protected get initialChunk(): Chunk {
+ protected get initialChunk(): any {
return this.transferParameters(Chunk.Type.START);
}
/** Builds an updated transfer parameters chunk to send the server. */
- private transferParameters(type: Chunk.TypeMap[keyof Chunk.TypeMap]): Chunk {
+ private transferParameters(type: any): Chunk {
this.pendingBytes = this.maxBytesToReceive;
this.windowEndOffset = this.offset + this.maxBytesToReceive;
@@ -328,7 +321,7 @@ export class ReadTransfer extends Transfer {
this.id
}: transmitter sent invalid earlier end offset ${chunk.getWindowEndOffset()} (receiver offset ${
this.offset
- })`
+ })`,
);
this.sendError(Status.INTERNAL);
return;
@@ -340,7 +333,7 @@ export class ReadTransfer extends Transfer {
this.id
}: transmitter sent invalid later end offset ${chunk.getWindowEndOffset()} (receiver end offset ${
this.windowEndOffset
- })`
+ })`,
);
this.sendError(Status.INTERNAL);
return;
@@ -379,7 +372,6 @@ export class ReadTransfer extends Transfer {
* A client => server write transfer.
*/
export class WriteTransfer extends Transfer {
- readonly data: Uint8Array;
private windowId = 0;
offset = 0;
maxChunkSize = 0;
@@ -394,14 +386,14 @@ export class WriteTransfer extends Transfer {
responseTimeoutS: number,
initialResponseTimeoutS: number,
maxRetries: number,
- progressCallback?: ProgressCallback
+ progressCallback?: ProgressCallback,
) {
super(id, sendChunk, responseTimeoutS, maxRetries, progressCallback);
this.data = data;
this.lastChunk = this.initialChunk;
}
- protected get initialChunk(): Chunk {
+ protected get initialChunk(): any {
// TODO(frolv): The session ID should not be set here but assigned by the
// server during an initial handshake.
const chunk = new Chunk();
@@ -429,6 +421,7 @@ export class WriteTransfer extends Transfer {
const bytesAknowledged = chunk.getOffset();
let writeChunk: Chunk;
+ // eslint-disable-next-line no-constant-condition
while (true) {
writeChunk = this.nextChunk();
this.offset += writeChunk.getData().length;
@@ -459,7 +452,7 @@ export class WriteTransfer extends Transfer {
this.id
}: server requested invalid offset ${chunk.getOffset()} (size ${
this.data.length
- })`
+ })`,
);
this.sendError(Status.OUT_OF_RANGE);
@@ -468,7 +461,7 @@ export class WriteTransfer extends Transfer {
if (chunk.getPendingBytes() === 0) {
console.error(
- `Transfer ${this.id}: service requested 0 bytes (invalid); aborting`
+ `Transfer ${this.id}: service requested 0 bytes (invalid); aborting`,
);
this.sendError(Status.INTERNAL);
return false;
@@ -481,7 +474,7 @@ export class WriteTransfer extends Transfer {
console.debug(
`Write transfer ${
this.id
- } rolling back to offset ${chunk.getOffset()} from ${this.offset}`
+ } rolling back to offset ${chunk.getOffset()} from ${this.offset}`,
);
}
@@ -492,14 +485,14 @@ export class WriteTransfer extends Transfer {
// to be set in these version, so it must be calculated.
const maxBytesToSend = Math.min(
chunk.getPendingBytes(),
- this.data.length - this.offset
+ this.data.length - this.offset,
);
this.windowEndOffset = this.offset + maxBytesToSend;
} else {
// Extend the window to the new end offset specified by the server.
this.windowEndOffset = Math.min(
chunk.getWindowEndOffset(),
- this.data.length
+ this.data.length,
);
}
@@ -522,7 +515,7 @@ export class WriteTransfer extends Transfer {
const maxBytesInChunk = Math.min(
this.maxChunkSize,
- this.windowEndOffset - this.offset
+ this.windowEndOffset - this.offset,
);
chunk.setData(this.data.slice(this.offset, this.offset + maxBytesInChunk));
diff --git a/pw_transfer/ts/transfer_test.ts b/pw_transfer/ts/transfer_test.ts
index 148ad44ac..723539dff 100644
--- a/pw_transfer/ts/transfer_test.ts
+++ b/pw_transfer/ts/transfer_test.ts
@@ -21,16 +21,16 @@ import {
MethodStub,
ServiceClient,
} from 'pigweedjs/pw_rpc';
-import {Status} from 'pigweedjs/pw_status';
+import { Status } from 'pigweedjs/pw_status';
import {
PacketType,
RpcPacket,
} from 'pigweedjs/protos/pw_rpc/internal/packet_pb';
-import {ProtoCollection} from 'pigweedjs/protos/collection';
-import {Chunk} from 'pigweedjs/protos/pw_transfer/transfer_pb';
+import { ProtoCollection } from 'pigweedjs/protos/collection';
+import { Chunk } from 'pigweedjs/protos/pw_transfer/transfer_pb';
-import {Manager} from './client';
-import {ProgressStats} from './transfer';
+import { Manager } from './client';
+import { ProgressStats } from './transfer';
const DEFAULT_TIMEOUT_S = 0.3;
@@ -114,7 +114,7 @@ describe('Transfer client', () => {
sessionId: number,
offset: number,
data: string,
- remainingBytes: number
+ remainingBytes: number,
): Chunk {
const chunk = new Chunk();
chunk.setTransferId(sessionId);
@@ -217,7 +217,7 @@ describe('Transfer client', () => {
.then(() => {
fail('Unexpected completed promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(27);
expect(Status[error.status]).toEqual(Status[Status.DEADLINE_EXCEEDED]);
expect(sentChunks).toHaveLength(4);
@@ -237,7 +237,7 @@ describe('Transfer client', () => {
.then(() => {
fail('Unexpected completed promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(31);
expect(Status[error.status]).toEqual(Status[Status.NOT_FOUND]);
});
@@ -249,10 +249,10 @@ describe('Transfer client', () => {
enqueueServerError(service.method('Read')!, Status.NOT_FOUND);
await manager
.read(31)
- .then(data => {
+ .then((data) => {
fail('Unexpected completed promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(31);
expect(Status[error.status]).toEqual(Status[Status.INTERNAL]);
});
@@ -400,7 +400,7 @@ describe('Transfer client', () => {
await manager.write(4, textEncoder.encode('hello this is a message'));
expect(receivedData()).toEqual(
- textEncoder.encode('hello this is a message')
+ textEncoder.encode('hello this is a message'),
);
expect(sentChunks[1].getData()).toEqual(textEncoder.encode('hell'));
expect(sentChunks[2].getData()).toEqual(textEncoder.encode('o th'));
@@ -441,7 +441,7 @@ describe('Transfer client', () => {
textEncoder.encode('data to write'),
(stats: ProgressStats) => {
progress.push(stats);
- }
+ },
);
expect(sentChunks).toHaveLength(3);
expect(receivedData()).toEqual(textEncoder.encode('data to write'));
@@ -533,7 +533,7 @@ describe('Transfer client', () => {
.then(() => {
fail('Unexpected succesful promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(4);
expect(Status[error.status]).toEqual(Status[Status.OUT_OF_RANGE]);
});
@@ -553,7 +553,7 @@ describe('Transfer client', () => {
.then(() => {
fail('Unexpected succesful promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(21);
expect(Status[error.status]).toEqual(Status[Status.UNAVAILABLE]);
});
@@ -573,7 +573,7 @@ describe('Transfer client', () => {
.then(() => {
fail('Unexpected succesful promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(21);
expect(Status[error.status]).toEqual(Status[Status.INTERNAL]);
});
@@ -587,7 +587,7 @@ describe('Transfer client', () => {
.then(() => {
fail('unexpected succesful write');
})
- .catch(error => {
+ .catch((error) => {
expect(sentChunks).toHaveLength(3); // Initial chunk + two retries.
expect(error.id).toEqual(22);
expect(Status[error.status]).toEqual(Status[Status.DEADLINE_EXCEEDED]);
@@ -609,7 +609,7 @@ describe('Transfer client', () => {
.then(() => {
fail('unexpected succesful write');
})
- .catch(error => {
+ .catch((error) => {
const expectedChunk1 = new Chunk();
expectedChunk1.setTransferId(22);
expectedChunk1.setResourceId(22);
@@ -654,7 +654,7 @@ describe('Transfer client', () => {
.then(() => {
fail('Unexpected succesful promise');
})
- .catch(error => {
+ .catch((error) => {
expect(error.id).toEqual(23);
expect(Status[error.status]).toEqual(Status[Status.INTERNAL]);
});