• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 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 // The classes in this file facilitate testing RPC services. The main class is
16 // PayloadsView, which iterates over the payloads sent by an RPC service or
17 // client. This allows verifying that code that invokes RPCs or an RPC service
18 // implementation sends the expected requests or responses.
19 //
20 // This code is inteded for testing, not for deployment.
21 #pragma once
22 
23 #include <tuple>
24 
25 #include "pw_containers/filtered_view.h"
26 #include "pw_containers/vector.h"
27 #include "pw_containers/wrapped_iterator.h"
28 #include "pw_rpc/channel.h"
29 #include "pw_rpc/internal/method_info.h"
30 #include "pw_rpc/internal/packet.h"
31 #include "pw_rpc/method_type.h"
32 
33 namespace pw::rpc {
34 namespace internal::test {
35 
36 class FakeChannelOutput;
37 
38 // Finds packets of a specified type for a particular method.
39 class PacketFilter {
40  public:
41   // Use Channel::kUnassignedChannelId to ignore the channel.
PacketFilter(PacketType packet_type_1,PacketType packet_type_2,uint32_t channel_id,uint32_t service_id,uint32_t method_id)42   constexpr PacketFilter(PacketType packet_type_1,
43                          PacketType packet_type_2,
44                          uint32_t channel_id,
45                          uint32_t service_id,
46                          uint32_t method_id)
47       : packet_type_1_(packet_type_1),
48         packet_type_2_(packet_type_2),
49         channel_id_(channel_id),
50         service_id_(service_id),
51         method_id_(method_id) {}
52 
operator()53   constexpr bool operator()(const Packet& packet) const {
54     return (packet.type() == packet_type_1_ ||
55             packet.type() == packet_type_2_) &&
56            (channel_id_ == Channel::kUnassignedChannelId ||
57             packet.channel_id() == channel_id_) &&
58            packet.service_id() == service_id_ &&
59            packet.method_id() == method_id_;
60   }
61 
62  private:
63   // Support filtering on two packet types to handle reading both client and
64   // server streams for bidirectional streams.
65   PacketType packet_type_1_;
66   PacketType packet_type_2_;
67   uint32_t channel_id_;
68   uint32_t service_id_;
69   uint32_t method_id_;
70 };
71 
72 using PacketsView = containers::FilteredView<Vector<Packet>, PacketFilter>;
73 
74 }  // namespace internal::test
75 
76 // Returns the payloads for a particular RPC in a Vector of RPC packets.
77 //
78 // Adapts a FilteredView of packets to return payloads instead of packets.
79 class PayloadsView {
80  public:
81   class iterator : public containers::WrappedIterator<
82                        iterator,
83                        internal::test::PacketsView::iterator,
84                        ConstByteSpan> {
85    public:
86     constexpr iterator() = default;
87 
88     // Access the payload (rather than packet) with operator* and operator->.
89     const ConstByteSpan& operator*() const { return value().payload(); }
90     const ConstByteSpan* operator->() const { return &value().payload(); }
91 
92    private:
93     friend class PayloadsView;
94 
iterator(const internal::test::PacketsView::iterator & it)95     constexpr iterator(const internal::test::PacketsView::iterator& it)
96         : containers::WrappedIterator<iterator,
97                                       internal::test::PacketsView::iterator,
98                                       ConstByteSpan>(it) {}
99   };
100 
101   using const_iterator = iterator;
102 
103   const ConstByteSpan& operator[](size_t index) const {
104     auto it = begin();
105     std::advance(it, index);
106     return *it;
107   }
108 
109   // Number of payloads for the specified RPC.
size()110   size_t size() const { return view_.size(); }
111 
empty()112   bool empty() const { return begin() == end(); }
113 
114   // Returns the first/last payload for the RPC. size() must be > 0.
front()115   const ConstByteSpan& front() const { return *begin(); }
back()116   const ConstByteSpan& back() const { return *std::prev(end()); }
117 
begin()118   iterator begin() const { return iterator(view_.begin()); }
end()119   iterator end() const { return iterator(view_.end()); }
120 
121  private:
122   friend class internal::test::FakeChannelOutput;
123 
124   template <typename>
125   friend class NanopbPayloadsView;
126 
127   template <auto kMethod>
128   using MethodInfo = internal::MethodInfo<kMethod>;
129 
130   using PacketType = internal::PacketType;
131 
132   template <auto kMethod>
For(const Vector<internal::Packet> & packets,uint32_t channel_id)133   static constexpr PayloadsView For(const Vector<internal::Packet>& packets,
134                                     uint32_t channel_id) {
135     constexpr auto kTypes = PacketTypesWithPayload(MethodInfo<kMethod>::kType);
136     return PayloadsView(packets,
137                         std::get<0>(kTypes),
138                         std::get<1>(kTypes),
139                         channel_id,
140                         MethodInfo<kMethod>::kServiceId,
141                         MethodInfo<kMethod>::kMethodId);
142   }
143 
PayloadsView(const Vector<internal::Packet> & packets,MethodType method_type,uint32_t channel_id,uint32_t service_id,uint32_t method_id)144   constexpr PayloadsView(const Vector<internal::Packet>& packets,
145                          MethodType method_type,
146                          uint32_t channel_id,
147                          uint32_t service_id,
148                          uint32_t method_id)
149       : PayloadsView(packets,
150                      std::get<0>(PacketTypesWithPayload(method_type)),
151                      std::get<1>(PacketTypesWithPayload(method_type)),
152                      channel_id,
153                      service_id,
154                      method_id) {}
155 
PayloadsView(const Vector<internal::Packet> & packets,PacketType packet_type_1,PacketType packet_type_2,uint32_t channel_id,uint32_t service_id,uint32_t method_id)156   constexpr PayloadsView(const Vector<internal::Packet>& packets,
157                          PacketType packet_type_1,
158                          PacketType packet_type_2,
159                          uint32_t channel_id,
160                          uint32_t service_id,
161                          uint32_t method_id)
162       : view_(packets,
163               internal::test::PacketFilter(packet_type_1,
164                                            packet_type_2,
165                                            channel_id,
166                                            service_id,
167                                            method_id)) {}
168 
PacketTypesWithPayload(MethodType method_type)169   static constexpr std::tuple<PacketType, PacketType> PacketTypesWithPayload(
170       MethodType method_type) {
171     switch (method_type) {
172       case MethodType::kUnary:
173         return {PacketType::REQUEST, PacketType::RESPONSE};
174       case MethodType::kServerStreaming:
175         return {PacketType::REQUEST, PacketType::SERVER_STREAM};
176       case MethodType::kClientStreaming:
177         return {PacketType::CLIENT_STREAM, PacketType::RESPONSE};
178       case MethodType::kBidirectionalStreaming:
179         return {PacketType::CLIENT_STREAM, PacketType::SERVER_STREAM};
180     }
181 
182 // Workaround for GCC 8 bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678
183 #if defined(__GNUC__) && __GNUC__ < 9
184     return {};
185 #else
186     PW_ASSERT(false);
187 #endif  // defined(__GNUC__) && __GNUC__ < 9
188   }
189 
190   internal::test::PacketsView view_;
191 };
192 
193 // Class for iterating over RPC statuses associated witha particular RPC. This
194 // is used to iterate over the user RPC statuses and or protocol errors for a
195 // particular RPC.
196 class StatusView {
197  public:
198   class iterator : public containers::WrappedIterator<
199                        iterator,
200                        internal::test::PacketsView::iterator,
201                        Status> {
202    public:
203     constexpr iterator() = default;
204 
205     // Access the status (rather than packet) with operator* and operator->.
206     const Status& operator*() const { return value().status(); }
207     const Status* operator->() const { return &value().status(); }
208 
209    private:
210     friend class StatusView;
211 
iterator(const internal::test::PacketsView::iterator & it)212     constexpr iterator(const internal::test::PacketsView::iterator& it)
213         : containers::WrappedIterator<iterator,
214                                       internal::test::PacketsView::iterator,
215                                       Status>(it) {}
216   };
217 
218   using const_iterator = iterator;
219 
220   const Status& operator[](size_t index) const {
221     auto it = begin();
222     std::advance(it, index);
223     return *it;
224   }
225 
226   // Number of statuses in this view.
size()227   size_t size() const { return view_.size(); }
empty()228   bool empty() const { return begin() == end(); }
229 
230   // Returns the first/last payload for the RPC. size() must be > 0.
front()231   const Status& front() const { return *begin(); }
back()232   const Status& back() const { return *std::prev(end()); }
233 
begin()234   iterator begin() const { return iterator(view_.begin()); }
end()235   iterator end() const { return iterator(view_.end()); }
236 
237  private:
238   friend class internal::test::FakeChannelOutput;
239 
240   template <auto kMethod>
241   using MethodInfo = internal::MethodInfo<kMethod>;
242 
243   using PacketType = internal::PacketType;
244 
StatusView(const Vector<internal::Packet> & packets,PacketType packet_type_1,PacketType packet_type_2,uint32_t channel_id,uint32_t service_id,uint32_t method_id)245   constexpr StatusView(const Vector<internal::Packet>& packets,
246                        PacketType packet_type_1,
247                        PacketType packet_type_2,
248                        uint32_t channel_id,
249                        uint32_t service_id,
250                        uint32_t method_id)
251       : view_(packets,
252               internal::test::PacketFilter(packet_type_1,
253                                            packet_type_2,
254                                            channel_id,
255                                            service_id,
256                                            method_id)) {}
257 
258   internal::test::PacketsView view_;
259 };
260 
261 }  // namespace pw::rpc
262