| // 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/transfer.h" |
| #include "pw_transfer_test/test_server.raw_rpc.pb.h" |
| |
| namespace pw::transfer { |
| namespace { |
| |
| class FileTransferHandler final : public ReadWriteHandler { |
| public: |
| FileTransferHandler(TransferService& service, |
| uint32_t resource_id, |
| const char* path) |
| : ReadWriteHandler(resource_id), service_(service), path_(path) { |
| service_.RegisterHandler(*this); |
| } |
| |
| ~FileTransferHandler() { service_.UnregisterHandler(*this); } |
| |
| Status PrepareRead() final { |
| PW_LOG_DEBUG("Preparing read for file %s", path_.c_str()); |
| set_reader(stream_.emplace<stream::StdFileReader>(path_.c_str())); |
| return OkStatus(); |
| } |
| |
| void FinalizeRead(Status) final { |
| std::get<stream::StdFileReader>(stream_).Close(); |
| } |
| |
| Status PrepareWrite() final { |
| PW_LOG_DEBUG("Preparing write for file %s", path_.c_str()); |
| set_writer(stream_.emplace<stream::StdFileWriter>(path_.c_str())); |
| return OkStatus(); |
| } |
| |
| Status FinalizeWrite(Status) final { |
| std::get<stream::StdFileWriter>(stream_).Close(); |
| return OkStatus(); |
| } |
| |
| private: |
| TransferService& service_; |
| std::string path_; |
| std::variant<std::monostate, stream::StdFileReader, stream::StdFileWriter> |
| stream_; |
| }; |
| |
| class TestServerService |
| : public pw_rpc::raw::TestServer::Service<TestServerService> { |
| public: |
| TestServerService(TransferService& transfer_service) |
| : transfer_service_(transfer_service) {} |
| |
| 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()); |
| 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); |
| file_transfer_handlers_.emplace_back( |
| std::make_shared<FileTransferHandler>( |
| transfer_service_, resource_id, entry.path().c_str())); |
| } |
| } |
| } |
| |
| private: |
| TransferService& transfer_service_; |
| std::string directory_; |
| std::vector<std::shared_ptr<FileTransferHandler>> 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; |
| } |