1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include <mutex>
16 #define PW_LOG_MODULE_NAME "TRN"
17 #define PW_LOG_LEVEL PW_TRANSFER_CONFIG_LOG_LEVEL
18
19 #include "pw_assert/check.h"
20 #include "pw_log/log.h"
21 #include "pw_status/try.h"
22 #include "pw_transfer/internal/chunk.h"
23 #include "pw_transfer/internal/config.h"
24 #include "pw_transfer/transfer.h"
25
26 namespace pw::transfer {
27
HandleChunk(ConstByteSpan message,internal::TransferType type)28 void TransferService::HandleChunk(ConstByteSpan message,
29 internal::TransferType type) {
30 Result<internal::Chunk> chunk = internal::Chunk::Parse(message);
31 if (!chunk.ok()) {
32 PW_LOG_ERROR("Failed to decode transfer chunk: %d", chunk.status().code());
33 return;
34 }
35
36 if (chunk->IsInitialChunk()) {
37 uint32_t resource_id =
38 chunk->is_legacy() ? chunk->session_id() : chunk->resource_id().value();
39
40 uint32_t session_id;
41 if (chunk->is_legacy()) {
42 session_id = chunk->session_id();
43 } else if (chunk->desired_session_id().has_value()) {
44 session_id = chunk->desired_session_id().value();
45 } else {
46 // Non-legacy start chunks are required to use desired_session_id.
47 thread_.SendServerStatus(type,
48 chunk->session_id(),
49 chunk->protocol_version(),
50 Status::DataLoss());
51 return;
52 }
53
54 uint32_t initial_offset;
55
56 if (chunk->is_legacy()) {
57 initial_offset = 0;
58 } else {
59 initial_offset = chunk->initial_offset();
60 }
61
62 thread_.StartServerTransfer(type,
63 chunk->protocol_version(),
64 session_id,
65 resource_id,
66 message,
67 max_parameters_,
68 chunk_timeout_,
69 max_retries_,
70 max_lifetime_retries_,
71 initial_offset);
72 } else {
73 thread_.ProcessServerChunk(message);
74 }
75 }
76
GetResourceStatus(pw::ConstByteSpan request,pw::rpc::RawUnaryResponder & responder)77 void TransferService::GetResourceStatus(pw::ConstByteSpan request,
78 pw::rpc::RawUnaryResponder& responder) {
79 uint32_t resource_id;
80 Status status;
81 std::array<std::byte, pwpb::ResourceStatus::kMaxEncodedSizeBytes> buffer = {};
82 pwpb::ResourceStatus::MemoryEncoder encoder(buffer);
83
84 protobuf::Decoder decoder(request);
85 if (status = decoder.Next(); status.IsOutOfRange()) {
86 resource_id = 0;
87 } else if (!status.ok()) {
88 responder.Finish({}, Status::DataLoss()).IgnoreError();
89 return;
90 } else if (static_cast<pwpb::ResourceStatusRequest::Fields>(
91 decoder.FieldNumber()) ==
92 pwpb::ResourceStatusRequest::Fields::kResourceId) {
93 if (status = decoder.ReadUint32(&resource_id); !status.ok()) {
94 responder.Finish({}, Status::DataLoss()).IgnoreError();
95 return;
96 }
97 } else {
98 responder.Finish({}, Status::DataLoss()).IgnoreError();
99 return;
100 }
101
102 encoder.WriteResourceId(resource_id).IgnoreError();
103
104 {
105 std::lock_guard lock(resource_responder_mutex_);
106 if (TransferService::resource_responder_.active()) {
107 PW_LOG_ERROR("Previous GetResourceStatus still being handled!");
108 responder.Finish(ConstByteSpan(encoder), Status::Unavailable())
109 .IgnoreError();
110 return;
111 }
112
113 TransferService::resource_responder_ = std::move(responder);
114 }
115
116 thread_.EnqueueResourceEvent(
117 resource_id,
118 [this](Status call_status, const internal::ResourceStatus stats) {
119 this->ResourceStatusCallback(call_status, stats);
120 });
121 }
122
ResourceStatusCallback(Status status,const internal::ResourceStatus & stats)123 void TransferService::ResourceStatusCallback(
124 Status status, const internal::ResourceStatus& stats) {
125 std::lock_guard lock(resource_responder_mutex_);
126
127 if (!resource_responder_.active()) {
128 PW_LOG_ERROR("ResourceStatusCallback invoked without an active responder");
129 return;
130 }
131
132 std::array<std::byte, pwpb::ResourceStatus::kMaxEncodedSizeBytes> buffer = {};
133 pwpb::ResourceStatus::MemoryEncoder encoder(buffer);
134
135 encoder.WriteResourceId(stats.resource_id).IgnoreError();
136 encoder.WriteStatus(status.code()).IgnoreError();
137
138 if (!status.ok()) {
139 resource_responder_.Finish(ConstByteSpan(encoder), status).IgnoreError();
140 return;
141 }
142
143 encoder.WriteReadableOffset(stats.readable_offset).IgnoreError();
144 encoder.WriteReadChecksum(stats.read_checksum).IgnoreError();
145 encoder.WriteWriteableOffset(stats.writeable_offset).IgnoreError();
146 encoder.WriteWriteChecksum(stats.write_checksum).IgnoreError();
147
148 if (!encoder.status().ok()) {
149 resource_responder_.Finish(ConstByteSpan(encoder), encoder.status())
150 .IgnoreError();
151 return;
152 }
153
154 resource_responder_.Finish(ConstByteSpan(encoder), status).IgnoreError();
155 }
156
157 } // namespace pw::transfer
158