Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of multiple responses in one read #152

Merged
merged 10 commits into from
May 8, 2024
Merged
1 change: 1 addition & 0 deletions include/scip2/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/function.hpp>

#include <scip2/logger.h>

Expand Down
17 changes: 12 additions & 5 deletions include/scip2/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,33 @@ class Protocol
const boost::posix_time::ptime& time_read)
{
std::istream stream(&buf);

std::string echo_back;
if (!std::getline(stream, echo_back))
{
logger::error() << "Failed to get echo back" << std::endl;
return;
}
if (echo_back == "")
{
logger::debug() << "Empty response echo back " << std::endl;
return;
}

std::string status;
if (!std::getline(stream, status))
{
logger::error() << "Failed to get status" << std::endl;
return;
}
if (status == "")
{
logger::debug() << "Empty response status" << std::endl;
return;
}
status.pop_back(); // remove checksum

response_processor_(time_read, echo_back, status, stream);

std::string line;
while (std::getline(stream, line))
{
}
Comment on lines -60 to -63
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continued lines must be processed on the next callback

}

public:
Expand Down
1 change: 1 addition & 0 deletions include/scip2/response.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class ResponseProcessor
if (response == responses_.end())
{
logger::debug() << "Unknown response " << command_code << std::endl;
readUntilEnd(stream);
return;
}
(*(response->second))(time_read, echo_back, status, stream);
Expand Down
8 changes: 8 additions & 0 deletions include/scip2/response/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class Response
std::istream&) = 0;
};

inline void readUntilEnd(std::istream& stream)
{
std::string line;
while (std::getline(stream, line) && line.size() > 0)
{
}
}

} // namespace scip2

#endif // SCIP2_RESPONSE_ABSTRACT_H
3 changes: 3 additions & 0 deletions include/scip2/response/quit.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ class ResponseQT : public Response
std::istream& stream)
{
if (cb_)
{
cb_(time_read, echo_back, status);
}
readUntilEnd(stream);
}
void registerCallback(Callback cb)
{
Expand Down
3 changes: 3 additions & 0 deletions include/scip2/response/reboot.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ class ResponseRB : public Response
std::istream& stream)
{
if (cb_)
{
cb_(time_read, echo_back, status);
}
readUntilEnd(stream);
}
void registerCallback(Callback cb)
{
Expand Down
6 changes: 6 additions & 0 deletions include/scip2/response/reset.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@
std::istream& stream)
{
if (cb_)
{
cb_(time_read, echo_back, status);
}
readUntilEnd(stream);
}
void registerCallback(Callback cb)
{
Expand Down Expand Up @@ -79,7 +82,10 @@
std::istream& stream)
{
if (cb_)
{
cb_(time_read, echo_back, status);
}
readUntilEnd(stream);

Check warning on line 88 in include/scip2/response/reset.h

View check run for this annotation

Codecov / codecov/patch

include/scip2/response/reset.h#L88

Added line #L88 was not covered by tests
}
void registerCallback(Callback cb)
{
Expand Down
7 changes: 7 additions & 0 deletions include/scip2/response/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@
if (!readTimestamp(time_read, echo_back, status, stream, scan))
{
if (cb_)
{
cb_(time_read, echo_back, status, scan);
}
readUntilEnd(stream);

Check warning on line 121 in include/scip2/response/stream.h

View check run for this annotation

Codecov / codecov/patch

include/scip2/response/stream.h#L121

Added line #L121 was not covered by tests
return;
}
scan.ranges_.reserve(512);
Expand Down Expand Up @@ -149,7 +152,9 @@
}
}
if (cb_)
{
cb_(time_read, echo_back, status, scan);
}
}
};

Expand Down Expand Up @@ -206,7 +211,9 @@
}
}
if (cb_)
{
cb_(time_read, echo_back, status, scan);
}
}
};

Expand Down
9 changes: 9 additions & 0 deletions include/scip2/response/time_sync.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@
if (status != "00")
{
if (cb_)
{
cb_(time_read, echo_back, status, timestamp);
}
readUntilEnd(stream);

Check warning on line 71 in include/scip2/response/time_sync.h

View check run for this annotation

Codecov / codecov/patch

include/scip2/response/time_sync.h#L71

Added line #L71 was not covered by tests
return;
}
if (echo_back[2] == '1')
Expand All @@ -74,13 +77,15 @@
if (!std::getline(stream, stamp))
{
logger::error() << "Failed to get timestamp" << std::endl;
readUntilEnd(stream);

Check warning on line 80 in include/scip2/response/time_sync.h

View check run for this annotation

Codecov / codecov/patch

include/scip2/response/time_sync.h#L80

Added line #L80 was not covered by tests
return;
}
const uint8_t checksum = stamp.back();
stamp.pop_back(); // remove checksum
if (stamp.size() < 4)
{
logger::error() << "Wrong timestamp format" << std::endl;
readUntilEnd(stream);

Check warning on line 88 in include/scip2/response/time_sync.h

View check run for this annotation

Codecov / codecov/patch

include/scip2/response/time_sync.h#L88

Added line #L88 was not covered by tests
return;
}

Expand All @@ -90,11 +95,15 @@
if ((dec.getChecksum() & 0x3F) + 0x30 != checksum)
{
logger::error() << "Checksum mismatch" << std::endl;
readUntilEnd(stream);

Check warning on line 98 in include/scip2/response/time_sync.h

View check run for this annotation

Codecov / codecov/patch

include/scip2/response/time_sync.h#L98

Added line #L98 was not covered by tests
return;
}
}
if (cb_)
{
cb_(time_read, echo_back, status, timestamp);
}
readUntilEnd(stream);
}
void registerCallback(Callback cb)
{
Expand Down
6 changes: 6 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ target_link_libraries(test_timestamp_moving_average ${catkin_LIBRARIES} ${Boost_
catkin_add_gtest(test_timestamp_outlier_remover src/test_timestamp_outlier_remover.cpp)
target_link_libraries(test_timestamp_outlier_remover ${catkin_LIBRARIES} ${Boost_LIBRARIES})

catkin_add_gtest(test_scip2
src/test_scip2.cpp
../src/scip2/logger.cpp
)
target_link_libraries(test_scip2 ${catkin_LIBRARIES} ${Boost_LIBRARIES})

catkin_add_gtest(test_walltime src/test_walltime.cpp ../src/scip2/logger.cpp)
target_link_libraries(test_walltime ${catkin_LIBRARIES} ${Boost_LIBRARIES})

Expand Down
112 changes: 112 additions & 0 deletions test/src/test_scip2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2024 The urg_stamped Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <gtest/gtest.h>

#include <memory>
#include <ostream>
#include <string>
#include <vector>

#include <scip2/protocol.h>
#include <scip2/connection.h>

class ConnectionDummy : public scip2::Connection
{
public:
void spin() final

Check warning on line 30 in test/src/test_scip2.cpp

View check run for this annotation

Codecov / codecov/patch

test/src/test_scip2.cpp#L30

Added line #L30 was not covered by tests
{
}
void stop() final

Check warning on line 33 in test/src/test_scip2.cpp

View check run for this annotation

Codecov / codecov/patch

test/src/test_scip2.cpp#L33

Added line #L33 was not covered by tests
{
}
void send(const std::string&, CallbackSend = CallbackSend()) final

Check warning on line 36 in test/src/test_scip2.cpp

View check run for this annotation

Codecov / codecov/patch

test/src/test_scip2.cpp#L36

Added line #L36 was not covered by tests
{
}
void startWatchdog(const boost::posix_time::time_duration&) final

Check warning on line 39 in test/src/test_scip2.cpp

View check run for this annotation

Codecov / codecov/patch

test/src/test_scip2.cpp#L39

Added line #L39 was not covered by tests
{
}
void feed(boost::asio::streambuf& buf, const boost::posix_time::ptime& time_read)
{
receive(buf, time_read);
}
};

TEST(SCIP2, MultipleResponses)
{
std::shared_ptr<ConnectionDummy> dev(new ConnectionDummy());
scip2::Protocol p(dev);

const auto now = boost::posix_time::microsec_clock::universal_time();
int num_receive = 0;

const auto cb =
[&num_receive, now](
const boost::posix_time::ptime& time_read,
const std::string& echo_back,
const std::string& status)
{
num_receive++;
EXPECT_EQ(now, time_read);
EXPECT_EQ("00", status);
};
p.registerCallback<scip2::ResponseQT>(cb);

/*
Input data:

QT // First QT response
00P

FOO // Unknown response
BAR
QT // This line must be ignored as a part of unknown command

QT // This must be ignored as it doesn't have correct status line

QT // Second QT response
00P

*/
boost::asio::streambuf buf;
std::ostream os(&buf);
os << "QT\n00P\n\nF";
dev->feed(buf, now);
ASSERT_EQ(1, num_receive);

os << "OO\nBAR\nQT\n\n";
dev->feed(buf, now);
ASSERT_EQ(1, num_receive);

os << "QT\n\nQT\n";
dev->feed(buf, now);
ASSERT_EQ(1, num_receive);

os << "00P\n\n";
dev->feed(buf, now);
ASSERT_EQ(2, num_receive);

os << "\n\n";
dev->feed(buf, now);
ASSERT_EQ(2, num_receive);
}

int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();
}