| // 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 |