• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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