xref: /aosp_15_r20/external/pigweed/pw_transfer/integration_test/client.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker // Copyright 2024 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker //
3*61c4878aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker // use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker // the License at
6*61c4878aSAndroid Build Coastguard Worker //
7*61c4878aSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker //
9*61c4878aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker // License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker // the License.
14*61c4878aSAndroid Build Coastguard Worker 
15*61c4878aSAndroid Build Coastguard Worker // Client binary for the cross-language integration test.
16*61c4878aSAndroid Build Coastguard Worker //
17*61c4878aSAndroid Build Coastguard Worker // Usage:
18*61c4878aSAndroid Build Coastguard Worker //  bazel-bin/pw_transfer/integration_test_client 3300 <<< "resource_id: 12
19*61c4878aSAndroid Build Coastguard Worker //  file: '/tmp/myfile.txt'"
20*61c4878aSAndroid Build Coastguard Worker //
21*61c4878aSAndroid Build Coastguard Worker // WORK IN PROGRESS, SEE b/228516801
22*61c4878aSAndroid Build Coastguard Worker #include "pw_transfer/client.h"
23*61c4878aSAndroid Build Coastguard Worker 
24*61c4878aSAndroid Build Coastguard Worker #include <sys/socket.h>
25*61c4878aSAndroid Build Coastguard Worker 
26*61c4878aSAndroid Build Coastguard Worker #include <cstddef>
27*61c4878aSAndroid Build Coastguard Worker #include <cstdio>
28*61c4878aSAndroid Build Coastguard Worker 
29*61c4878aSAndroid Build Coastguard Worker #include "google/protobuf/text_format.h"
30*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/check.h"
31*61c4878aSAndroid Build Coastguard Worker #include "pw_log/log.h"
32*61c4878aSAndroid Build Coastguard Worker #include "pw_rpc/channel.h"
33*61c4878aSAndroid Build Coastguard Worker #include "pw_rpc/integration_testing.h"
34*61c4878aSAndroid Build Coastguard Worker #include "pw_status/status.h"
35*61c4878aSAndroid Build Coastguard Worker #include "pw_status/try.h"
36*61c4878aSAndroid Build Coastguard Worker #include "pw_stream/std_file_stream.h"
37*61c4878aSAndroid Build Coastguard Worker #include "pw_sync/binary_semaphore.h"
38*61c4878aSAndroid Build Coastguard Worker #include "pw_thread/thread.h"
39*61c4878aSAndroid Build Coastguard Worker #include "pw_thread_stl/options.h"
40*61c4878aSAndroid Build Coastguard Worker #include "pw_transfer/integration_test/config.pb.h"
41*61c4878aSAndroid Build Coastguard Worker #include "pw_transfer/transfer_thread.h"
42*61c4878aSAndroid Build Coastguard Worker 
43*61c4878aSAndroid Build Coastguard Worker namespace pw::transfer::integration_test {
44*61c4878aSAndroid Build Coastguard Worker namespace {
45*61c4878aSAndroid Build Coastguard Worker 
46*61c4878aSAndroid Build Coastguard Worker // This is the maximum size of the socket send buffers. Ideally, this is set
47*61c4878aSAndroid Build Coastguard Worker // to the lowest allowed value to minimize buffering between the proxy and
48*61c4878aSAndroid Build Coastguard Worker // clients so rate limiting causes the client to block and wait for the
49*61c4878aSAndroid Build Coastguard Worker // integration test proxy to drain rather than allowing OS buffers to backlog
50*61c4878aSAndroid Build Coastguard Worker // large quantities of data.
51*61c4878aSAndroid Build Coastguard Worker //
52*61c4878aSAndroid Build Coastguard Worker // Note that the OS may chose to not strictly follow this requested buffer size.
53*61c4878aSAndroid Build Coastguard Worker // Still, setting this value to be as small as possible does reduce bufer sizes
54*61c4878aSAndroid Build Coastguard Worker // significantly enough to better reflect typical inter-device communication.
55*61c4878aSAndroid Build Coastguard Worker //
56*61c4878aSAndroid Build Coastguard Worker // For this to be effective, servers should also configure their sockets to a
57*61c4878aSAndroid Build Coastguard Worker // smaller receive buffer size.
58*61c4878aSAndroid Build Coastguard Worker constexpr int kMaxSocketSendBufferSize = 1;
59*61c4878aSAndroid Build Coastguard Worker 
60*61c4878aSAndroid Build Coastguard Worker constexpr size_t kDefaultMaxWindowSizeBytes = 16384;
61*61c4878aSAndroid Build Coastguard Worker 
TransferThreadOptions()62*61c4878aSAndroid Build Coastguard Worker thread::Options& TransferThreadOptions() {
63*61c4878aSAndroid Build Coastguard Worker   static thread::stl::Options options;
64*61c4878aSAndroid Build Coastguard Worker   return options;
65*61c4878aSAndroid Build Coastguard Worker }
66*61c4878aSAndroid Build Coastguard Worker 
67*61c4878aSAndroid Build Coastguard Worker // Transfer status, valid only after semaphore is acquired.
68*61c4878aSAndroid Build Coastguard Worker //
69*61c4878aSAndroid Build Coastguard Worker // We need to bundle the status and semaphore together because a pw_function
70*61c4878aSAndroid Build Coastguard Worker // callback can at most capture the reference to one variable (and we need to
71*61c4878aSAndroid Build Coastguard Worker // both set the status and release the semaphore).
72*61c4878aSAndroid Build Coastguard Worker struct TransferResult {
73*61c4878aSAndroid Build Coastguard Worker   Status status = Status::Unknown();
74*61c4878aSAndroid Build Coastguard Worker   sync::BinarySemaphore completed;
75*61c4878aSAndroid Build Coastguard Worker };
76*61c4878aSAndroid Build Coastguard Worker 
77*61c4878aSAndroid Build Coastguard Worker // Create a pw_transfer client and perform the transfer actions.
PerformTransferActions(const pw::transfer::ClientConfig & config)78*61c4878aSAndroid Build Coastguard Worker pw::Status PerformTransferActions(const pw::transfer::ClientConfig& config) {
79*61c4878aSAndroid Build Coastguard Worker   constexpr size_t kMaxPayloadSize = rpc::MaxSafePayloadSize();
80*61c4878aSAndroid Build Coastguard Worker   std::byte chunk_buffer[kMaxPayloadSize];
81*61c4878aSAndroid Build Coastguard Worker   std::byte encode_buffer[kMaxPayloadSize];
82*61c4878aSAndroid Build Coastguard Worker   transfer::Thread<2, 2> transfer_thread(chunk_buffer, encode_buffer);
83*61c4878aSAndroid Build Coastguard Worker   pw::Thread system_thread(TransferThreadOptions(), transfer_thread);
84*61c4878aSAndroid Build Coastguard Worker 
85*61c4878aSAndroid Build Coastguard Worker   // As much as we don't want to dynamically allocate an array,
86*61c4878aSAndroid Build Coastguard Worker   // variable length arrays (VLA) are nonstandard, and a std::vector could cause
87*61c4878aSAndroid Build Coastguard Worker   // references to go stale if the vector's underlying buffer is resized. This
88*61c4878aSAndroid Build Coastguard Worker   // array of TransferResults needs to outlive the loop that performs the
89*61c4878aSAndroid Build Coastguard Worker   // actual transfer actions due to how some references to TransferResult
90*61c4878aSAndroid Build Coastguard Worker   // may persist beyond the lifetime of a transfer.
91*61c4878aSAndroid Build Coastguard Worker   const int num_actions = config.transfer_actions().size();
92*61c4878aSAndroid Build Coastguard Worker   auto transfer_results = std::make_unique<TransferResult[]>(num_actions);
93*61c4878aSAndroid Build Coastguard Worker 
94*61c4878aSAndroid Build Coastguard Worker   pw::transfer::Client client(rpc::integration_test::client(),
95*61c4878aSAndroid Build Coastguard Worker                               rpc::integration_test::kChannelId,
96*61c4878aSAndroid Build Coastguard Worker                               transfer_thread,
97*61c4878aSAndroid Build Coastguard Worker                               kDefaultMaxWindowSizeBytes);
98*61c4878aSAndroid Build Coastguard Worker 
99*61c4878aSAndroid Build Coastguard Worker   // TODO: https://pwbug.dev/357145010 - Don't IgnoreError here.
100*61c4878aSAndroid Build Coastguard Worker   client.set_max_retries(config.max_retries()).IgnoreError();
101*61c4878aSAndroid Build Coastguard Worker   client.set_max_lifetime_retries(config.max_lifetime_retries()).IgnoreError();
102*61c4878aSAndroid Build Coastguard Worker 
103*61c4878aSAndroid Build Coastguard Worker   Status status = pw::OkStatus();
104*61c4878aSAndroid Build Coastguard Worker   for (int i = 0; i < num_actions; i++) {
105*61c4878aSAndroid Build Coastguard Worker     const pw::transfer::TransferAction& action = config.transfer_actions()[i];
106*61c4878aSAndroid Build Coastguard Worker     TransferResult& result = transfer_results[i];
107*61c4878aSAndroid Build Coastguard Worker     // If no protocol version is specified, default to the latest version.
108*61c4878aSAndroid Build Coastguard Worker     pw::transfer::ProtocolVersion protocol_version =
109*61c4878aSAndroid Build Coastguard Worker         action.protocol_version() ==
110*61c4878aSAndroid Build Coastguard Worker                 pw::transfer::TransferAction::ProtocolVersion::
111*61c4878aSAndroid Build Coastguard Worker                     TransferAction_ProtocolVersion_UNKNOWN_VERSION
112*61c4878aSAndroid Build Coastguard Worker             ? pw::transfer::ProtocolVersion::kLatest
113*61c4878aSAndroid Build Coastguard Worker             : static_cast<pw::transfer::ProtocolVersion>(
114*61c4878aSAndroid Build Coastguard Worker                   action.protocol_version());
115*61c4878aSAndroid Build Coastguard Worker     if (action.transfer_type() ==
116*61c4878aSAndroid Build Coastguard Worker         pw::transfer::TransferAction::TransferType::
117*61c4878aSAndroid Build Coastguard Worker             TransferAction_TransferType_WRITE_TO_SERVER) {
118*61c4878aSAndroid Build Coastguard Worker       pw::stream::StdFileReader input(action.file_path().c_str());
119*61c4878aSAndroid Build Coastguard Worker       pw::Result<pw::transfer::Client::Handle> handle = client.Write(
120*61c4878aSAndroid Build Coastguard Worker           action.resource_id(),
121*61c4878aSAndroid Build Coastguard Worker           input,
122*61c4878aSAndroid Build Coastguard Worker           [&result](Status status) {
123*61c4878aSAndroid Build Coastguard Worker             result.status = status;
124*61c4878aSAndroid Build Coastguard Worker             result.completed.release();
125*61c4878aSAndroid Build Coastguard Worker           },
126*61c4878aSAndroid Build Coastguard Worker           protocol_version,
127*61c4878aSAndroid Build Coastguard Worker           pw::transfer::cfg::kDefaultClientTimeout,
128*61c4878aSAndroid Build Coastguard Worker           pw::transfer::cfg::kDefaultInitialChunkTimeout,
129*61c4878aSAndroid Build Coastguard Worker           action.initial_offset());
130*61c4878aSAndroid Build Coastguard Worker       if (handle.ok()) {
131*61c4878aSAndroid Build Coastguard Worker         // Wait for the transfer to complete. We need to do this here so that
132*61c4878aSAndroid Build Coastguard Worker         // the StdFileReader doesn't go out of scope.
133*61c4878aSAndroid Build Coastguard Worker         result.completed.acquire();
134*61c4878aSAndroid Build Coastguard Worker       } else {
135*61c4878aSAndroid Build Coastguard Worker         result.status = handle.status();
136*61c4878aSAndroid Build Coastguard Worker       }
137*61c4878aSAndroid Build Coastguard Worker 
138*61c4878aSAndroid Build Coastguard Worker       input.Close();
139*61c4878aSAndroid Build Coastguard Worker 
140*61c4878aSAndroid Build Coastguard Worker     } else if (action.transfer_type() ==
141*61c4878aSAndroid Build Coastguard Worker                pw::transfer::TransferAction::TransferType::
142*61c4878aSAndroid Build Coastguard Worker                    TransferAction_TransferType_READ_FROM_SERVER) {
143*61c4878aSAndroid Build Coastguard Worker       pw::stream::StdFileWriter output(action.file_path().c_str());
144*61c4878aSAndroid Build Coastguard Worker       pw::Result<pw::transfer::Client::Handle> handle = client.Read(
145*61c4878aSAndroid Build Coastguard Worker           action.resource_id(),
146*61c4878aSAndroid Build Coastguard Worker           output,
147*61c4878aSAndroid Build Coastguard Worker           [&result](Status status) {
148*61c4878aSAndroid Build Coastguard Worker             result.status = status;
149*61c4878aSAndroid Build Coastguard Worker             result.completed.release();
150*61c4878aSAndroid Build Coastguard Worker           },
151*61c4878aSAndroid Build Coastguard Worker           protocol_version,
152*61c4878aSAndroid Build Coastguard Worker           pw::transfer::cfg::kDefaultClientTimeout,
153*61c4878aSAndroid Build Coastguard Worker           pw::transfer::cfg::kDefaultInitialChunkTimeout,
154*61c4878aSAndroid Build Coastguard Worker           action.initial_offset());
155*61c4878aSAndroid Build Coastguard Worker       if (handle.ok()) {
156*61c4878aSAndroid Build Coastguard Worker         // Wait for the transfer to complete.
157*61c4878aSAndroid Build Coastguard Worker         result.completed.acquire();
158*61c4878aSAndroid Build Coastguard Worker       } else {
159*61c4878aSAndroid Build Coastguard Worker         result.status = handle.status();
160*61c4878aSAndroid Build Coastguard Worker       }
161*61c4878aSAndroid Build Coastguard Worker 
162*61c4878aSAndroid Build Coastguard Worker       output.Close();
163*61c4878aSAndroid Build Coastguard Worker     } else {
164*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("Unrecognized transfer action type %d",
165*61c4878aSAndroid Build Coastguard Worker                    action.transfer_type());
166*61c4878aSAndroid Build Coastguard Worker       status = pw::Status::InvalidArgument();
167*61c4878aSAndroid Build Coastguard Worker       break;
168*61c4878aSAndroid Build Coastguard Worker     }
169*61c4878aSAndroid Build Coastguard Worker 
170*61c4878aSAndroid Build Coastguard Worker     if (int(result.status.code()) != int(action.expected_status())) {
171*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("Failed to perform action:\n%s",
172*61c4878aSAndroid Build Coastguard Worker                    action.DebugString().c_str());
173*61c4878aSAndroid Build Coastguard Worker       status = result.status.ok() ? Status::Unknown() : result.status;
174*61c4878aSAndroid Build Coastguard Worker       break;
175*61c4878aSAndroid Build Coastguard Worker     }
176*61c4878aSAndroid Build Coastguard Worker   }
177*61c4878aSAndroid Build Coastguard Worker 
178*61c4878aSAndroid Build Coastguard Worker   transfer_thread.Terminate();
179*61c4878aSAndroid Build Coastguard Worker 
180*61c4878aSAndroid Build Coastguard Worker   system_thread.join();
181*61c4878aSAndroid Build Coastguard Worker 
182*61c4878aSAndroid Build Coastguard Worker   // The RPC thread must join before destroying transfer objects as the transfer
183*61c4878aSAndroid Build Coastguard Worker   // service may still reference the transfer thread or transfer client objects.
184*61c4878aSAndroid Build Coastguard Worker   pw::rpc::integration_test::TerminateClient();
185*61c4878aSAndroid Build Coastguard Worker   return status;
186*61c4878aSAndroid Build Coastguard Worker }
187*61c4878aSAndroid Build Coastguard Worker 
188*61c4878aSAndroid Build Coastguard Worker }  // namespace
189*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::transfer::integration_test
190*61c4878aSAndroid Build Coastguard Worker 
main(int argc,char * argv[])191*61c4878aSAndroid Build Coastguard Worker int main(int argc, char* argv[]) {
192*61c4878aSAndroid Build Coastguard Worker   if (argc < 2) {
193*61c4878aSAndroid Build Coastguard Worker     PW_LOG_INFO("Usage: %s PORT <<< config textproto", argv[0]);
194*61c4878aSAndroid Build Coastguard Worker     return 1;
195*61c4878aSAndroid Build Coastguard Worker   }
196*61c4878aSAndroid Build Coastguard Worker 
197*61c4878aSAndroid Build Coastguard Worker   const int port = std::atoi(argv[1]);
198*61c4878aSAndroid Build Coastguard Worker 
199*61c4878aSAndroid Build Coastguard Worker   std::string config_string;
200*61c4878aSAndroid Build Coastguard Worker   std::string line;
201*61c4878aSAndroid Build Coastguard Worker   while (std::getline(std::cin, line)) {
202*61c4878aSAndroid Build Coastguard Worker     config_string = config_string + line + '\n';
203*61c4878aSAndroid Build Coastguard Worker   }
204*61c4878aSAndroid Build Coastguard Worker   pw::transfer::ClientConfig config;
205*61c4878aSAndroid Build Coastguard Worker 
206*61c4878aSAndroid Build Coastguard Worker   bool ok =
207*61c4878aSAndroid Build Coastguard Worker       google::protobuf::TextFormat::ParseFromString(config_string, &config);
208*61c4878aSAndroid Build Coastguard Worker   if (!ok) {
209*61c4878aSAndroid Build Coastguard Worker     PW_LOG_INFO("Failed to parse config: %s", config_string.c_str());
210*61c4878aSAndroid Build Coastguard Worker     PW_LOG_INFO("Usage: %s PORT <<< config textproto", argv[0]);
211*61c4878aSAndroid Build Coastguard Worker     return 1;
212*61c4878aSAndroid Build Coastguard Worker   } else {
213*61c4878aSAndroid Build Coastguard Worker     PW_LOG_INFO("Client loaded config:\n%s", config.DebugString().c_str());
214*61c4878aSAndroid Build Coastguard Worker   }
215*61c4878aSAndroid Build Coastguard Worker 
216*61c4878aSAndroid Build Coastguard Worker   if (!pw::rpc::integration_test::InitializeClient(port).ok()) {
217*61c4878aSAndroid Build Coastguard Worker     return 1;
218*61c4878aSAndroid Build Coastguard Worker   }
219*61c4878aSAndroid Build Coastguard Worker 
220*61c4878aSAndroid Build Coastguard Worker   int retval = pw::rpc::integration_test::SetClientSockOpt(
221*61c4878aSAndroid Build Coastguard Worker       SOL_SOCKET,
222*61c4878aSAndroid Build Coastguard Worker       SO_SNDBUF,
223*61c4878aSAndroid Build Coastguard Worker       &pw::transfer::integration_test::kMaxSocketSendBufferSize,
224*61c4878aSAndroid Build Coastguard Worker       sizeof(pw::transfer::integration_test::kMaxSocketSendBufferSize));
225*61c4878aSAndroid Build Coastguard Worker   PW_CHECK_INT_EQ(retval,
226*61c4878aSAndroid Build Coastguard Worker                   0,
227*61c4878aSAndroid Build Coastguard Worker                   "Failed to configure socket send buffer size with errno=%d",
228*61c4878aSAndroid Build Coastguard Worker                   errno);
229*61c4878aSAndroid Build Coastguard Worker 
230*61c4878aSAndroid Build Coastguard Worker   if (!pw::transfer::integration_test::PerformTransferActions(config).ok()) {
231*61c4878aSAndroid Build Coastguard Worker     PW_LOG_INFO("Failed to transfer!");
232*61c4878aSAndroid Build Coastguard Worker     return 1;
233*61c4878aSAndroid Build Coastguard Worker   }
234*61c4878aSAndroid Build Coastguard Worker   return 0;
235*61c4878aSAndroid Build Coastguard Worker }
236