blob: da6fc149ec0bfb19e250a30e7145d5330721b1d1 [file] [log] [blame] [edit]
// Copyright 2021 The Verible 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 "common/lsp/message-stream-splitter.h"
namespace verible {
namespace lsp {
absl::Status MessageStreamSplitter::PullFrom(const ReadFun &read_fun) {
if (!message_processor_) {
return absl::FailedPreconditionError(
"MessageStreamSplitter: Message processor not yet set, needed "
"before PullFrom() called");
}
return ReadInput(read_fun);
}
// Return -1 if header is incomplete (not enough data yet).
// Return -2 if header complete, but does not contain a valid
// Content-Length header (i.e. an actual problem)
// On success, returns the offset to the body and its size in "body_size"
static constexpr int kIncompleteHeader = -1;
static constexpr int kGarbledHeader = -2;
int MessageStreamSplitter::ParseHeaderGetBodyOffset(absl::string_view data,
int *body_size) {
static constexpr absl::string_view kEndHeaderMarker = "\r\n\r\n";
static constexpr absl::string_view kLenientEndHeaderMarker = "\n\n";
static constexpr absl::string_view kContentLengthHeader = "Content-Length: ";
int header_marker_len = kEndHeaderMarker.length();
auto end_of_header = data.find(kEndHeaderMarker);
if (end_of_header == absl::string_view::npos && lenient_lf_separation_) {
end_of_header = data.find(kLenientEndHeaderMarker);
header_marker_len = kLenientEndHeaderMarker.length();
}
if (end_of_header == absl::string_view::npos) return kIncompleteHeader;
// Very dirty search for header - we don't check if starts with line.
const absl::string_view header_content(data.data(), end_of_header);
auto found_ContentLength_header = header_content.find(kContentLengthHeader);
if (found_ContentLength_header == absl::string_view::npos) {
return kGarbledHeader;
}
size_t end_key = found_ContentLength_header + kContentLengthHeader.size();
if (!absl::SimpleAtoi(header_content.substr(end_key), body_size)) {
return kGarbledHeader;
}
return end_of_header + header_marker_len;
}
// Read from data and process all fully available messages found in data.
// Updates "data" to return the remaining unprocessed data.
// Returns ok() status encountered a corrupted header.
absl::Status MessageStreamSplitter::ProcessContainedMessages(
absl::string_view *data) {
while (!data->empty()) {
int body_size = 0;
const int body_offset = ParseHeaderGetBodyOffset(*data, &body_size);
if (body_offset == kGarbledHeader) {
absl::string_view limited_view(
data->data(), std::min(data->size(), static_cast<size_t>(256)));
return absl::InvalidArgumentError(
absl::StrCat("No `Content-Length:` header. '", limited_view, "...'"));
}
const int message_size = body_offset + body_size;
if (body_offset == kIncompleteHeader ||
message_size > static_cast<int>(data->size())) {
return absl::OkStatus(); // Only insufficient partial buffer available.
}
absl::string_view header(data->data(), body_offset);
absl::string_view body(data->data() + body_offset, body_size);
message_processor_(header, body);
stats_largest_body_ = std::max(stats_largest_body_, body.size());
*data = {data->data() + message_size, data->size() - message_size};
}
return absl::OkStatus();
}
// Read from "read_fun", fill internal buffer and call all available
// complete messages in it.
absl::Status MessageStreamSplitter::ReadInput(const ReadFun &read_fun) {
size_t write_offset = 0;
// Move all we had left from last time to the beginning of the buffer.
// This is in the same buffer, so we need to memmove()
if (!pending_data_.empty()) {
memmove(read_buffer_.data(), pending_data_.data(), pending_data_.size());
write_offset = pending_data_.size();
}
if (write_offset == read_buffer_.size()) {
read_buffer_.resize(2 * read_buffer_.size());
}
const int free_space = read_buffer_.size() - write_offset;
int bytes_read = read_fun(read_buffer_.data() + write_offset, free_space);
if (bytes_read <= 0) {
// Got EOF.
// If we still have data pending, regard this as data loss situation, as
// we were never able to fully read the last message and send to process.
// Otherwise, report 'Unavailable' to indicate EOF (meh, this should
// be a better message).
if (!pending_data_.empty()) {
return absl::DataLossError(
absl::StrCat("Got EOF, but still have incomplete message with ",
pending_data_.size(), " bytes read so far."));
}
return absl::UnavailableError(absl::StrCat("read() returned ", bytes_read));
}
stats_total_bytes_read_ += bytes_read;
absl::string_view data(read_buffer_.data(), write_offset + bytes_read);
if (auto status = ProcessContainedMessages(&data); !status.ok()) {
return status;
}
pending_data_ = data; // Remember for next round.
return absl::OkStatus();
}
} // namespace lsp
} // namespace verible