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