Skip to content

Commit efa3221

Browse files
authored
Merge pull request #60 from tstenner/checkquery
Raise an error when attempting stream resolution with an malformed query
2 parents 406b5de + b43eaac commit efa3221

File tree

5 files changed

+99
-65
lines changed

5 files changed

+99
-65
lines changed

include/lsl_cpp.h

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ namespace lsl {
2626
#include "lsl_c.h"
2727
#endif
2828

29+
/// Assert that no error happened; throw appropriate exception otherwise
30+
int32_t check_error(int32_t ec);
31+
2932
/// Constant to indicate that a stream has variable sampling rate.
3033
const double IRREGULAR_RATE = 0.0;
3134

@@ -649,7 +652,11 @@ namespace lsl {
649652
* @return A vector of stream info objects (excluding their desc field), any of which can
650653
* subsequently be used to open an inlet. The full info can be retrieve from the inlet.
651654
*/
652-
inline std::vector<stream_info> resolve_streams(double wait_time=1.0) { lsl_streaminfo buffer[1024]; return std::vector<stream_info>(&buffer[0],&buffer[lsl_resolve_all(buffer,sizeof(buffer),wait_time)]); }
655+
inline std::vector<stream_info> resolve_streams(double wait_time = 1.0) {
656+
lsl_streaminfo buffer[1024];
657+
int nres = check_error(lsl_resolve_all(buffer, sizeof(buffer), wait_time));
658+
return std::vector<stream_info>(&buffer[0], &buffer[nres]);
659+
}
653660

654661
/** Resolve all streams with a specific value for a given property.
655662
* If the goal is to resolve a specific stream, this method is preferred over resolving all streams and then selecting the desired one.
@@ -661,7 +668,14 @@ namespace lsl {
661668
* @return A vector of matching stream info objects (excluding their meta-data), any of
662669
* which can subsequently be used to open an inlet.
663670
*/
664-
inline std::vector<stream_info> resolve_stream(const std::string &prop, const std::string &value, int32_t minimum=1, double timeout=FOREVER) { lsl_streaminfo buffer[1024]; return std::vector<stream_info>(&buffer[0],&buffer[lsl_resolve_byprop(buffer,sizeof(buffer),(prop.c_str()),(value.c_str()),minimum,timeout)]); }
671+
inline std::vector<stream_info> resolve_stream(const std::string &prop,
672+
const std::string &value, int32_t minimum = 1, double timeout = FOREVER) {
673+
lsl_streaminfo buffer[1024];
674+
int nres = check_error(lsl_resolve_byprop(
675+
buffer, sizeof(buffer), prop.c_str(), value.c_str(), minimum, timeout));
676+
return std::vector<stream_info>(
677+
&buffer[0], &buffer[nres]);
678+
}
665679

666680
/** Resolve all streams that match a given predicate.
667681
*
@@ -677,7 +691,13 @@ namespace lsl {
677691
* @return A vector of matching stream info objects (excluding their meta-data), any of
678692
* which can subsequently be used to open an inlet.
679693
*/
680-
inline std::vector<stream_info> resolve_stream(const std::string &pred, int32_t minimum=1, double timeout=FOREVER) { lsl_streaminfo buffer[1024]; return std::vector<stream_info>(&buffer[0],&buffer[lsl_resolve_bypred(buffer,sizeof(buffer),(pred.c_str()),minimum,timeout)]); }
694+
inline std::vector<stream_info> resolve_stream(
695+
const std::string &pred, int32_t minimum = 1, double timeout = FOREVER) {
696+
lsl_streaminfo buffer[1024];
697+
int nres =
698+
check_error(lsl_resolve_bypred(buffer, sizeof(buffer), pred.c_str(), minimum, timeout));
699+
return std::vector<stream_info>(&buffer[0], &buffer[nres]);
700+
}
681701

682702

683703
// ======================
@@ -687,7 +707,6 @@ namespace lsl {
687707
/** A stream inlet.
688708
* Inlets are used to receive streaming data (and meta-data) from the lab network.
689709
*/
690-
void check_error(int32_t ec);
691710
class stream_inlet {
692711
public:
693712
/** Construct a new stream inlet from a resolved stream info.
@@ -1307,7 +1326,7 @@ namespace lsl {
13071326
* Check error codes returned from the C interface
13081327
* and translate into appropriate exceptions.
13091328
*/
1310-
inline void check_error(int32_t ec) {
1329+
inline int32_t check_error(int32_t ec) {
13111330
if (ec<0) {
13121331
switch(ec) {
13131332
case lsl_timeout_error:
@@ -1322,6 +1341,7 @@ namespace lsl {
13221341
throw std::runtime_error("An unknown error has occurred.");
13231342
}
13241343
}
1344+
return ec;
13251345
}
13261346

13271347
} // end namespace

src/lsl_resolver_c.cpp

Lines changed: 10 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,7 @@ using namespace lsl;
1717
* The recommended default value is 5.0.
1818
*/
1919
LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver(double forget_after) {
20-
try {
21-
// create a new resolver
22-
resolver_impl *resolver = new resolver_impl();
23-
// start it with the given query
24-
std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "'";
25-
resolver->resolve_continuous(os.str(),forget_after);
26-
return resolver;
27-
} catch(std::exception &e) {
28-
LOG_F(ERROR, "Error while creating a continuous_resolver: %s", e.what());
29-
return nullptr;
30-
}
20+
return resolver_impl::create_resolver(forget_after);
3121
}
3222

3323
/**
@@ -40,17 +30,7 @@ LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver(double forge
4030
* The recommended default value is 5.0.
4131
*/
4232
LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_byprop(const char *prop, const char *value, double forget_after) {
43-
try {
44-
// create a new resolver
45-
resolver_impl *resolver = new resolver_impl();
46-
// start it with the given query
47-
std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << prop << "='" << value << "'";
48-
resolver->resolve_continuous(os.str(),forget_after);
49-
return resolver;
50-
} catch(std::exception &e) {
51-
LOG_F(ERROR, "Error while creating a continuous_resolver: %s", e.what());
52-
return nullptr;
53-
}
33+
return resolver_impl::create_resolver(forget_after, prop, value);
5434
}
5535

5636
/**
@@ -62,17 +42,7 @@ LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_byprop(const
6242
* The recommended default value is 5.0.
6343
*/
6444
LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_bypred(const char *pred, double forget_after) {
65-
try {
66-
// create a new resolver
67-
resolver_impl *resolver = new resolver_impl();
68-
// start it with the given query
69-
std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << pred;
70-
resolver->resolve_continuous(os.str(),forget_after);
71-
return resolver;
72-
} catch(std::exception &e) {
73-
LOG_F(ERROR, "Error while creating a continuous_resolver: %s", e.what());
74-
return nullptr;
75-
}
45+
return resolver_impl::create_resolver(forget_after, pred, nullptr);
7646
}
7747

7848
/**
@@ -86,13 +56,10 @@ LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_bypred(const
8656
*/
8757
LIBLSL_C_API int32_t lsl_resolver_results(lsl_continuous_resolver res, lsl_streaminfo *buffer, uint32_t buffer_elements) {
8858
try {
89-
// query it
90-
resolver_impl *resolver = res;
91-
std::vector<stream_info_impl> tmp = resolver->results();
59+
std::vector<stream_info_impl> tmp = res->results(buffer_elements);
9260
// allocate new stream_info_impl's and assign to the buffer
93-
uint32_t result = buffer_elements<tmp.size() ? buffer_elements : (uint32_t)tmp.size();
94-
for (uint32_t k = 0; k < result; k++) buffer[k] = new stream_info_impl(tmp[k]);
95-
return result;
61+
for (uint32_t k = 0; k < tmp.size(); k++) buffer[k] = new stream_info_impl(tmp[k]);
62+
return tmp.size();
9663
} catch(std::exception &e) {
9764
LOG_F(WARNING, "Unexpected error querying lsl_resolver_results: %s", e.what());
9865
return lsl_internal_error;
@@ -162,12 +129,8 @@ LIBLSL_C_API int32_t lsl_resolve_all(lsl_streaminfo *buffer, uint32_t buffer_ele
162129
*/
163130
LIBLSL_C_API int32_t lsl_resolve_byprop(lsl_streaminfo *buffer, uint32_t buffer_elements, const char *prop, const char *value, int32_t minimum, double timeout) {
164131
try {
165-
// create a new resolver
166-
resolver_impl resolver;
167-
// build a new query.
168-
std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << prop << "='" << value << "'";
169-
// invoke it
170-
std::vector<stream_info_impl> tmp = resolver.resolve_oneshot(os.str(),minimum,timeout);
132+
std::string query{resolver_impl::build_query(prop, value)};
133+
auto tmp = resolver_impl().resolve_oneshot(query, minimum, timeout);
171134
// allocate new stream_info_impl's and assign to the buffer
172135
uint32_t result = buffer_elements<tmp.size() ? buffer_elements : (uint32_t)tmp.size();
173136
for (uint32_t k = 0; k < result; k++) buffer[k] = new stream_info_impl(tmp[k]);
@@ -195,12 +158,8 @@ LIBLSL_C_API int32_t lsl_resolve_byprop(lsl_streaminfo *buffer, uint32_t buffer_
195158
*/
196159
LIBLSL_C_API int32_t lsl_resolve_bypred(lsl_streaminfo *buffer, uint32_t buffer_elements, const char *pred, int32_t minimum, double timeout) {
197160
try {
198-
// create a new resolver
199-
resolver_impl resolver;
200-
// build a new query.
201-
std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << pred;
202-
// invoke it
203-
std::vector<stream_info_impl> tmp = resolver.resolve_oneshot(os.str(),minimum,timeout);
161+
std::string query{resolver_impl::build_query(pred)};
162+
auto tmp = resolver_impl().resolve_oneshot(query, minimum, timeout);
204163
// allocate new stream_info_impl's and assign to the buffer
205164
uint32_t result = buffer_elements<tmp.size() ? buffer_elements : (uint32_t)tmp.size();
206165
for (uint32_t k = 0; k < result; k++) buffer[k] = new stream_info_impl(tmp[k]);

src/resolver_impl.cpp

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,35 @@ resolver_impl::resolver_impl()
6363
}
6464
}
6565

66+
void check_query(const std::string& query) {
67+
try {
68+
pugi::xpath_query(query.c_str());
69+
} catch (std::exception &e) {
70+
throw std::invalid_argument((("Invalid query '" + query) += "': ") += e.what());
71+
}
72+
}
73+
74+
std::string resolver_impl::build_query(const char* pred_or_prop, const char* value)
75+
{
76+
std::string query("session_id='");
77+
query += api_config::get_instance()->session_id();
78+
query += '\'';
79+
if (pred_or_prop) (query += " and ") += pred_or_prop;
80+
if (value) ((query += "='") += value) += '\'';
81+
return query;
82+
}
83+
84+
resolver_impl *resolver_impl::create_resolver(
85+
double forget_after, const char *pred_or_prop, const char *value) noexcept {
86+
try {
87+
auto *resolver = new resolver_impl();
88+
resolver->resolve_continuous(build_query(pred_or_prop, value), forget_after);
89+
return resolver;
90+
} catch (std::exception &e) {
91+
LOG_F(ERROR, "Error while creating a continuous_resolver: %s", e.what());
92+
return nullptr;
93+
}
94+
}
6695

6796
// === resolve functions ===
6897

@@ -71,6 +100,7 @@ resolver_impl::resolver_impl()
71100
* Blocks until at least the minimum number of streams has been resolved, or the timeout fires, or the resolve has been cancelled.
72101
*/
73102
std::vector<stream_info_impl> resolver_impl::resolve_oneshot(const std::string &query, int minimum, double timeout, double minimum_time) {
103+
check_query(query);
74104
// reset the IO service & set up the query parameters
75105
io_->restart();
76106
query_ = query;
@@ -104,6 +134,7 @@ std::vector<stream_info_impl> resolver_impl::resolve_oneshot(const std::string &
104134
}
105135

106136
void resolver_impl::resolve_continuous(const std::string &query, double forget_after) {
137+
check_query(query);
107138
// reset the IO service & set up the query parameters
108139
io_->restart();
109140
query_ = query;
@@ -120,17 +151,18 @@ void resolver_impl::resolve_continuous(const std::string &query, double forget_a
120151
}
121152

122153
/// Get the current set of results (e.g., during continuous operation).
123-
std::vector<stream_info_impl> resolver_impl::results() {
154+
std::vector<stream_info_impl> resolver_impl::results(uint32_t max_results) {
124155
std::vector<stream_info_impl> output;
125156
lslboost::lock_guard<lslboost::mutex> lock(results_mut_);
126157
double expired_before = lsl_clock() - forget_after_;
127-
for(result_container::iterator i=results_.begin(); i!=results_.end();) {
128-
if (i->second.second < expired_before) {
129-
result_container::iterator tmp = i++;
130-
results_.erase(tmp);
131-
} else {
132-
output.push_back(i->second.first);
133-
i++;
158+
159+
for (auto it = results_.begin(); it != results_.end();) {
160+
if (it->second.second < expired_before)
161+
it = results_.erase(it);
162+
else {
163+
if (output.size() < max_results)
164+
output.push_back(it->second.first);
165+
it++;
134166
}
135167
}
136168
return output;

src/resolver_impl.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ namespace lsl {
4444
*/
4545
resolver_impl();
4646

47+
/** Build a query string
48+
*
49+
* @param pred_or_prop an entire predicate if value isn't set or the
50+
* name of the property, e.g. "foo='bar'" / "foo" (+value set as "bar")
51+
* @param value the value for the property parameter
52+
*/
53+
static std::string build_query(
54+
const char *pred_or_prop = nullptr, const char *value = nullptr);
55+
56+
/** Create a resolver object with optionally a predicate or property + value
57+
*
58+
* @param pred_or_prop an entire predicate of value isn't set or the
59+
* name of the property, e.g. "foo='bar'" / "foo" (+value set as "bar")
60+
* @param value the value for the property parameter
61+
* @return A pointer to the resolver on success or nullptr on error
62+
*/
63+
static resolver_impl *create_resolver(double forget_after,
64+
const char *pred_or_prop = nullptr, const char *value = nullptr) noexcept;
65+
4766
/// Destructor.
4867
/// Cancels any ongoing processes and waits until they finish.
4968
~resolver_impl();
@@ -75,7 +94,7 @@ namespace lsl {
7594
void resolve_continuous(const std::string &query, double forget_after=5.0);
7695

7796
/// Get the current set of results (e.g., during continuous operation).
78-
std::vector<stream_info_impl> results();
97+
std::vector<stream_info_impl> results(uint32_t max_results = 4294967295);
7998

8099
/// Tear down any ongoing operations and render the resolver unusable.
81100
/// This can be used to cancel a blocking resolve_oneshot() from another thread (e.g., to initiate teardown of the object).

testing/discovery.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@ TEST_CASE("resolve from streaminfo", "[resolver][streaminfo][basic]") {
2323
lsl::stream_inlet(outlet.info());
2424
}
2525

26+
TEST_CASE("Invalid queries are caught before sending the query", "[resolver][streaminfo][basic]") {
27+
REQUIRE_THROWS(lsl::resolve_stream("invalid'query", 0, 0.1));
28+
}
29+
2630
} // namespace

0 commit comments

Comments
 (0)