xref: /aosp_15_r20/external/pigweed/pw_rpc/public/pw_rpc/internal/encoding_buffer.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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