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