Line data Source code
1 : /*
2 : ___________________________________________
3 : | _ ___ _ |
4 : | | | |__ \ | | |
5 : | | |__ ) |__ _ __ _ ___ _ __ | |_ |
6 : | | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
7 : | | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
8 : | |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
9 : | __/ | |
10 : | |___/ |
11 : |___________________________________________|
12 :
13 : Licensed under the MIT License <http://opensource.org/licenses/MIT>.
14 : SPDX-License-Identifier: MIT
15 : Copyright (c) 2021 Eduardo Ramos
16 :
17 : Permission is hereby granted, free of charge, to any person obtaining a copy
18 : of this software and associated documentation files (the "Software"), to deal
19 : in the Software without restriction, including without limitation the rights
20 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 : copies of the Software, and to permit persons to whom the Software is
22 : furnished to do so, subject to the following conditions:
23 :
24 : The above copyright notice and this permission notice shall be included in all
25 : copies or substantial portions of the Software.
26 :
27 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 : SOFTWARE.
34 : */
35 :
36 : #include <string>
37 : #include <algorithm>
38 :
39 : #include <ert/tracing/Logger.hpp>
40 :
41 : #include <MockClientData.hpp>
42 : #include <MockClientEvent.hpp>
43 :
44 :
45 : namespace h2agent
46 : {
47 : namespace model
48 : {
49 :
50 230 : void MockClientData::loadEvent(const DataKey &dataKey, const std::string &clientProvisionId, const std::string &previousState, const std::string &state, const std::chrono::microseconds &sendingTimestampUs, const std::chrono::microseconds &receptionTimestampUs, int responseStatusCode, const nghttp2::asio_http2::header_map &requestHeaders, const nghttp2::asio_http2::header_map &responseHeaders, const std::string &requestBody, DataPart &responseBodyDataPart, std::uint64_t sendSeq, std::int64_t sequence, unsigned int requestDelayMs, unsigned int timeoutMs, bool historyEnabled) {
51 :
52 230 : modifyOrInsert(dataKey.getKey(), [&](std::shared_ptr<MockEventsHistory> &entry) {
53 230 : if (!entry) entry = std::make_shared<MockClientEventsHistory>(dataKey);
54 230 : std::static_pointer_cast<MockClientEventsHistory>(entry)->loadEvent(clientProvisionId, previousState, state, sendingTimestampUs, receptionTimestampUs, responseStatusCode, requestHeaders, responseHeaders, requestBody, responseBodyDataPart, sendSeq, sequence, requestDelayMs, timeoutMs, historyEnabled);
55 230 : });
56 230 : }
57 :
58 0 : bool MockClientData::removeEventBySendSeq(const DataKey &dataKey, std::uint64_t sendSeq) {
59 :
60 0 : bool exists{};
61 0 : auto events = std::static_pointer_cast<MockClientEventsHistory>(get(dataKey.getKey(), exists));
62 0 : if (!exists) return false;
63 :
64 0 : bool deleted = events->removeEventBySendSeq(sendSeq);
65 :
66 : // Cleanup empty map entry:
67 0 : if (deleted && events->size() == 0) {
68 0 : bool aux{};
69 0 : remove(dataKey.getKey(), aux);
70 : }
71 :
72 0 : return deleted;
73 0 : }
74 :
75 0 : std::shared_ptr<MockEvent> MockClientData::getEventBySendSeq(const DataKey &dataKey, std::uint64_t sendSeq) {
76 :
77 0 : bool exists{};
78 0 : auto events = std::static_pointer_cast<MockClientEventsHistory>(get(dataKey.getKey(), exists));
79 0 : if (!exists) return nullptr;
80 :
81 0 : return events->getEventBySendSeq(sendSeq);
82 0 : }
83 :
84 11 : std::shared_ptr<MockEvent> MockClientData::getEventByUriStartsWith(const std::string &clientEndpointId, const std::string &requestMethod, const std::string &uriPrefix, const std::string &eventNumber) const {
85 :
86 : // Decode the requested position (1..N, or -1 for the latest):
87 11 : std::uint64_t uNumber{};
88 11 : bool reverse{};
89 11 : if (!h2agent::model::string2uint64andSign(eventNumber, uNumber, reverse) || uNumber == 0) {
90 2 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Invalid event number '%s' for URI-prefix client event addressing", eventNumber.c_str()), ERT_FILE_LOCATION));
91 2 : return nullptr;
92 : }
93 :
94 : // Collect all events whose key matches 'clientEndpointId'+'method' exactly and whose URI starts with
95 : // 'uriPrefix' (literal), tagging each with its sending timestamp (always present for client events) to
96 : // build a single chronologically ordered set across keys:
97 : struct Entry {
98 : std::uint64_t timestampUs;
99 : std::shared_ptr<MockEvent> event;
100 : };
101 9 : std::vector<Entry> entries;
102 :
103 9 : forEach([&](const mock_events_key_t&, const std::shared_ptr<MockEventsHistory> &history) {
104 18 : const auto &key = history->getKey();
105 27 : if (key.getClientEndpointId() != clientEndpointId) return;
106 16 : if (key.getMethod() != requestMethod) return;
107 : // Literal 'startsWith' (no regex): the prefix is compared verbatim against the normalized URI:
108 14 : if (key.getUri().rfind(uriPrefix, 0) != 0) return;
109 :
110 7 : read_guard_t guard(history->getMutex());
111 14 : for (const auto &ev : history->getEvents()) {
112 21 : std::uint64_t sendTs = std::static_pointer_cast<MockClientEvent>(ev)->getJson()["sendingTimestampUs"].get<std::uint64_t>();
113 7 : entries.push_back({sendTs, ev});
114 : }
115 14 : });
116 :
117 9 : if (entries.empty() || uNumber > entries.size()) return nullptr;
118 :
119 3 : std::sort(entries.begin(), entries.end(), [](const Entry &a, const Entry &b) {
120 2 : return a.timestampUs < b.timestampUs;
121 : });
122 :
123 : // 'reverse' selects from the tail (most recent); otherwise from the head:
124 3 : return reverse ? entries[entries.size() - uNumber].event : entries[uNumber - 1].event;
125 9 : }
126 :
127 4 : std::string MockClientData::getSequence(std::uint64_t fromTimestampUs, std::uint64_t toTimestampUs, const std::string &requestMethod, const std::regex *uriRegex) const {
128 :
129 : struct Entry {
130 : std::uint64_t timestampUs;
131 : std::string direction;
132 : std::string method;
133 : std::string uri;
134 : std::string clientProvisionId;
135 : };
136 4 : std::vector<Entry> entries;
137 :
138 4 : forEach([&](const mock_events_key_t&, const std::shared_ptr<MockEventsHistory> &history) {
139 6 : const auto &key = history->getKey();
140 6 : const std::string &method = key.getMethod();
141 6 : const std::string &uri = key.getUri();
142 :
143 7 : if (!requestMethod.empty() && method != requestMethod) return;
144 4 : if (uriRegex && !std::regex_search(uri, *uriRegex)) return;
145 :
146 3 : read_guard_t guard(history->getMutex());
147 6 : for (const auto &ev : history->getEvents()) {
148 3 : auto clientEv = std::static_pointer_cast<MockClientEvent>(ev);
149 6 : const auto &json = clientEv->getJson();
150 3 : std::uint64_t sendTs = json["sendingTimestampUs"].get<std::uint64_t>();
151 3 : std::uint64_t recvTs = json.contains("receptionTimestampUs") ? json["receptionTimestampUs"].get<std::uint64_t>() : 0;
152 3 : std::string provId = json.contains("clientProvisionId") ? json["clientProvisionId"].get<std::string>() : "";
153 :
154 : // Send entry:
155 3 : if ((!fromTimestampUs || sendTs >= fromTimestampUs) && (!toTimestampUs || sendTs <= toTimestampUs)) {
156 3 : entries.push_back({sendTs, "send", method, uri, provId});
157 : }
158 : // Recv entry:
159 3 : if (recvTs && (!fromTimestampUs || recvTs >= fromTimestampUs) && (!toTimestampUs || recvTs <= toTimestampUs)) {
160 3 : entries.push_back({recvTs, "recv", method, uri, provId});
161 : }
162 3 : }
163 21 : });
164 :
165 4 : std::sort(entries.begin(), entries.end(), [](const Entry &a, const Entry &b) {
166 8 : return a.timestampUs < b.timestampUs;
167 : });
168 :
169 4 : nlohmann::json result = nlohmann::json::array();
170 10 : for (const auto &e : entries) {
171 6 : nlohmann::json obj;
172 6 : obj["direction"] = e.direction;
173 6 : obj["method"] = e.method;
174 6 : obj["uri"] = e.uri;
175 6 : obj["timestampUs"] = e.timestampUs;
176 6 : if (!e.clientProvisionId.empty()) obj["clientProvisionId"] = e.clientProvisionId;
177 6 : result.push_back(std::move(obj));
178 6 : }
179 :
180 8 : return result.dump();
181 4 : }
182 :
183 : }
184 : }
185 :
|