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(internal::pwpb::PacketType packet_type_1,internal::pwpb::PacketType packet_type_2,uint32_t channel_id,uint32_t service_id,uint32_t method_id)42 constexpr PacketFilter(internal::pwpb::PacketType packet_type_1, 43 internal::pwpb::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 internal::pwpb::PacketType packet_type_1_; 66 internal::pwpb::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 <typename> 128 friend class PwpbPayloadsView; 129 130 template <auto kMethod> 131 using MethodInfo = internal::MethodInfo<kMethod>; 132 133 using PacketType = internal::pwpb::PacketType; 134 135 template <auto kMethod> For(const Vector<internal::Packet> & packets,uint32_t channel_id)136 static constexpr PayloadsView For(const Vector<internal::Packet>& packets, 137 uint32_t channel_id) { 138 constexpr auto kTypes = PacketTypesWithPayload(MethodInfo<kMethod>::kType); 139 return PayloadsView(packets, 140 std::get<0>(kTypes), 141 std::get<1>(kTypes), 142 channel_id, 143 MethodInfo<kMethod>::kServiceId, 144 MethodInfo<kMethod>::kMethodId); 145 } 146 PayloadsView(const Vector<internal::Packet> & packets,MethodType method_type,uint32_t channel_id,uint32_t service_id,uint32_t method_id)147 constexpr PayloadsView(const Vector<internal::Packet>& packets, 148 MethodType method_type, 149 uint32_t channel_id, 150 uint32_t service_id, 151 uint32_t method_id) 152 : PayloadsView(packets, 153 std::get<0>(PacketTypesWithPayload(method_type)), 154 std::get<1>(PacketTypesWithPayload(method_type)), 155 channel_id, 156 service_id, 157 method_id) {} 158 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)159 constexpr PayloadsView(const Vector<internal::Packet>& packets, 160 PacketType packet_type_1, 161 PacketType packet_type_2, 162 uint32_t channel_id, 163 uint32_t service_id, 164 uint32_t method_id) 165 : view_(packets, 166 internal::test::PacketFilter(packet_type_1, 167 packet_type_2, 168 channel_id, 169 service_id, 170 method_id)) {} 171 PacketTypesWithPayload(MethodType method_type)172 static constexpr std::tuple<PacketType, PacketType> PacketTypesWithPayload( 173 MethodType method_type) { 174 switch (method_type) { 175 case MethodType::kUnary: 176 return {PacketType::REQUEST, PacketType::RESPONSE}; 177 case MethodType::kServerStreaming: 178 return {PacketType::REQUEST, PacketType::SERVER_STREAM}; 179 case MethodType::kClientStreaming: 180 return {PacketType::CLIENT_STREAM, PacketType::RESPONSE}; 181 case MethodType::kBidirectionalStreaming: 182 return {PacketType::CLIENT_STREAM, PacketType::SERVER_STREAM}; 183 } 184 185 // Workaround for GCC 8 bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678 186 #if defined(__GNUC__) && __GNUC__ < 9 187 return {}; 188 #else 189 PW_ASSERT(false); 190 #endif // defined(__GNUC__) && __GNUC__ < 9 191 } 192 193 internal::test::PacketsView view_; 194 }; 195 196 // Class for iterating over RPC statuses associated witha particular RPC. This 197 // is used to iterate over the user RPC statuses and or protocol errors for a 198 // particular RPC. 199 class StatusView { 200 public: 201 class iterator : public containers::WrappedIterator< 202 iterator, 203 internal::test::PacketsView::iterator, 204 Status> { 205 public: 206 constexpr iterator() = default; 207 208 // Access the status (rather than packet) with operator* and operator->. 209 const Status& operator*() const { return value().status(); } 210 const Status* operator->() const { return &value().status(); } 211 212 private: 213 friend class StatusView; 214 iterator(const internal::test::PacketsView::iterator & it)215 constexpr iterator(const internal::test::PacketsView::iterator& it) 216 : containers::WrappedIterator<iterator, 217 internal::test::PacketsView::iterator, 218 Status>(it) {} 219 }; 220 221 using const_iterator = iterator; 222 223 const Status& operator[](size_t index) const { 224 auto it = begin(); 225 std::advance(it, index); 226 return *it; 227 } 228 229 // Number of statuses in this view. size()230 size_t size() const { return view_.size(); } empty()231 bool empty() const { return begin() == end(); } 232 233 // Returns the first/last payload for the RPC. size() must be > 0. front()234 const Status& front() const { return *begin(); } back()235 const Status& back() const { return *std::prev(end()); } 236 begin()237 iterator begin() const { return iterator(view_.begin()); } end()238 iterator end() const { return iterator(view_.end()); } 239 240 private: 241 friend class internal::test::FakeChannelOutput; 242 243 template <auto kMethod> 244 using MethodInfo = internal::MethodInfo<kMethod>; 245 246 using PacketType = internal::pwpb::PacketType; 247 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)248 constexpr StatusView(const Vector<internal::Packet>& packets, 249 PacketType packet_type_1, 250 PacketType packet_type_2, 251 uint32_t channel_id, 252 uint32_t service_id, 253 uint32_t method_id) 254 : view_(packets, 255 internal::test::PacketFilter(packet_type_1, 256 packet_type_2, 257 channel_id, 258 service_id, 259 method_id)) {} 260 261 internal::test::PacketsView view_; 262 }; 263 264 } // namespace pw::rpc 265