1 // Copyright 2023 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 // Definitions for the static and dynamic versions of the pw_rpc encoding
16 // buffer. Both version are compiled rot, but only one is instantiated,
17 // depending on the PW_RPC_DYNAMIC_ALLOCATION config option.
18 #pragma once
19
20 #include <array>
21
22 #include "pw_assert/assert.h"
23 #include "pw_bytes/span.h"
24 #include "pw_rpc/channel.h"
25 #include "pw_rpc/internal/config.h"
26 #include "pw_rpc/internal/lock.h"
27 #include "pw_rpc/internal/packet.h"
28 #include "pw_status/status_with_size.h"
29
30 #if PW_RPC_DYNAMIC_ALLOCATION
31
32 #include PW_RPC_DYNAMIC_CONTAINER_INCLUDE
33
34 #endif // PW_RPC_DYNAMIC_ALLOCATION
35
36 namespace pw::rpc::internal {
37
ResizeForPayload(ByteSpan buffer)38 constexpr ByteSpan ResizeForPayload(ByteSpan buffer) {
39 return buffer.subspan(Packet::kMinEncodedSizeWithoutPayload);
40 }
41
42 // Wraps a statically allocated encoding buffer.
43 class StaticEncodingBuffer {
44 public:
StaticEncodingBuffer()45 constexpr StaticEncodingBuffer() : buffer_{} {}
46
AllocatePayloadBuffer(size_t)47 ByteSpan AllocatePayloadBuffer(size_t /*payload_size */) {
48 return ResizeForPayload(buffer_);
49 }
GetPacketBuffer(size_t)50 ByteSpan GetPacketBuffer(size_t /* payload_size */) { return buffer_; }
Release()51 void Release() {}
ReleaseIfAllocated()52 void ReleaseIfAllocated() {}
53
54 private:
55 static_assert(MaxSafePayloadSize() > 0,
56 "pw_rpc's encode buffer is too small to fit any data");
57
58 std::array<std::byte, cfg::kEncodingBufferSizeBytes> buffer_;
59 };
60
61 #if PW_RPC_DYNAMIC_ALLOCATION
62
63 // Wraps a dynamically allocated encoding buffer.
64 class DynamicEncodingBuffer {
65 public:
66 DynamicEncodingBuffer() = default;
67
~DynamicEncodingBuffer()68 ~DynamicEncodingBuffer() { PW_DASSERT(buffer_.empty()); }
69
70 // Allocates a new buffer and returns a portion to use to encode the payload.
AllocatePayloadBuffer(size_t payload_size)71 ByteSpan AllocatePayloadBuffer(size_t payload_size) {
72 Allocate(payload_size);
73 return ResizeForPayload(buffer_);
74 }
75
76 // Returns the buffer into which to encode the packet, allocating a new buffer
77 // if necessary.
GetPacketBuffer(size_t payload_size)78 ByteSpan GetPacketBuffer(size_t payload_size) {
79 if (buffer_.empty()) {
80 Allocate(payload_size);
81 }
82 return buffer_;
83 }
84
85 // Frees the payload buffer, which MUST have been allocated previously.
Release()86 void Release() {
87 PW_DASSERT(!buffer_.empty());
88 buffer_.clear();
89 }
90
91 // Frees the payload buffer, if one was allocated.
ReleaseIfAllocated()92 void ReleaseIfAllocated() {
93 if (!buffer_.empty()) {
94 Release();
95 }
96 }
97
98 private:
Allocate(size_t payload_size)99 void Allocate(size_t payload_size) {
100 const size_t buffer_size =
101 payload_size + Packet::kMinEncodedSizeWithoutPayload;
102 PW_DASSERT(buffer_.empty());
103 buffer_.resize(buffer_size);
104 }
105
106 PW_RPC_DYNAMIC_CONTAINER(std::byte) buffer_;
107 };
108
109 using EncodingBuffer = DynamicEncodingBuffer;
110
111 #else
112
113 using EncodingBuffer = StaticEncodingBuffer;
114
115 #endif // PW_RPC_DYNAMIC_ALLOCATION
116
117 // Instantiate the global encoding buffer variable, depending on whether dynamic
118 // allocation is enabled or not.
119 inline EncodingBuffer encoding_buffer PW_GUARDED_BY(rpc_lock());
120
121 // Successful calls to EncodeToPayloadBuffer MUST send the returned buffer,
122 // without releasing the RPC lock.
123 template <typename Proto, typename Encoder>
EncodeToPayloadBuffer(Proto & payload,const Encoder & encoder)124 static Result<ByteSpan> EncodeToPayloadBuffer(Proto& payload,
125 const Encoder& encoder)
126 PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
127 // If dynamic allocation is enabled, calculate the size of the encoded
128 // protobuf and allocate a buffer for it.
129 #if PW_RPC_DYNAMIC_ALLOCATION
130 StatusWithSize payload_size = encoder.EncodedSizeBytes(payload);
131 if (!payload_size.ok()) {
132 return Status::Internal();
133 }
134
135 ByteSpan buffer = encoding_buffer.AllocatePayloadBuffer(payload_size.size());
136 #else
137 ByteSpan buffer = encoding_buffer.AllocatePayloadBuffer(MaxSafePayloadSize());
138 #endif // PW_RPC_DYNAMIC_ALLOCATION
139
140 StatusWithSize result = encoder.Encode(payload, buffer);
141 if (!result.ok()) {
142 encoding_buffer.ReleaseIfAllocated();
143 return result.status();
144 }
145 return buffer.first(result.size());
146 }
147
148 } // namespace pw::rpc::internal
149