| /** @file |
| |
| Chunk decoding. |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you 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 <algorithm> |
| #include <cassert> |
| #include <limits> |
| |
| #include "chunk-decoder.h" |
| |
| namespace |
| { |
| int |
| parse_hex_digit(const char a) |
| { |
| if (a >= '0' && a <= '9') { |
| return a - '0'; |
| } |
| if (a >= 'A' && a <= 'F') { |
| return a - 'A' + 10; |
| } |
| if (a >= 'a' && a <= 'f') { |
| return a - 'a' + 10; |
| } |
| return -1; |
| } |
| } // namespace |
| |
| void |
| ChunkDecoder::parseSizeCharacter(const char a) |
| { |
| assert(state_ == State::kSize); |
| const int digit = parse_hex_digit(a); |
| if (digit >= 0) { |
| constexpr int64_t max_chunk_size = std::numeric_limits<int64_t>::max(); |
| if (size_ > (max_chunk_size - digit) / 16) { |
| state_ = State::kInvalid; |
| size_ = 0; |
| return; |
| } |
| size_ = size_ * 16 + digit; |
| } else if (a == '\r') { |
| state_ = size_ == 0 ? State::kEndN : State::kDataN; |
| } else { |
| state_ = State::kInvalid; |
| return; |
| } |
| return; |
| } |
| |
| int64_t |
| ChunkDecoder::parseSize(const char *p, const int64_t s) |
| { |
| assert(p != nullptr); |
| assert(s > 0); |
| int64_t length = 0; |
| while (state_ != State::kData && state_ != State::kInvalid && length < s) { |
| assert(state_ < State::kUpperBound); // VALID RANGE |
| switch (state_) { |
| case State::kData: |
| case State::kEnd: |
| case State::kUpperBound: |
| assert(false); |
| break; |
| |
| case State::kInvalid: |
| break; |
| |
| case State::kDataN: |
| state_ = (*p == '\n') ? State::kData : State::kInvalid; |
| break; |
| |
| case State::kEndN: |
| state_ = (*p == '\n') ? State::kEnd : State::kInvalid; |
| if (state_ == State::kEnd) { |
| return length; |
| } |
| break; |
| |
| case State::kSizeR: |
| state_ = (*p == '\r') ? State::kSizeN : State::kInvalid; |
| break; |
| |
| case State::kSizeN: |
| state_ = (*p == '\n') ? State::kSize : State::kInvalid; |
| break; |
| |
| case State::kSize: |
| parseSizeCharacter(*p); |
| break; |
| } |
| ++length; |
| ++p; |
| } |
| return length; |
| } |
| |
| bool |
| ChunkDecoder::isSizeState() const |
| { |
| return state_ == State::kDataN || state_ == State::kEndN || state_ == State::kSize || state_ == State::kSizeN || |
| state_ == State::kSizeR; |
| } |
| |
| int64_t |
| ChunkDecoder::decode(const TSIOBufferReader &r) |
| { |
| assert(r != nullptr); |
| |
| if (state_ == State::kInvalid) { |
| return -1; |
| } |
| |
| if (state_ == State::kEnd) { |
| return 0; |
| } |
| |
| { |
| const int64_t l = TSIOBufferReaderAvail(r); |
| if (l == 0) { |
| return 0; |
| } else if (l < size_) { |
| size_ -= l; |
| return l; |
| } |
| } |
| |
| int64_t size; |
| TSIOBufferBlock block = TSIOBufferReaderStart(r); |
| |
| // Trying to parse a size. |
| if (isSizeState()) { |
| while (block != nullptr && size_ == 0 && !isInvalid()) { |
| const char *p = TSIOBufferBlockReadStart(block, r, &size); |
| if (size == 0) { |
| block = TSIOBufferBlockNext(block); |
| continue; |
| } |
| assert(p != nullptr); |
| const int64_t i = parseSize(p, size); |
| size -= i; |
| TSIOBufferReaderConsume(r, i); |
| if (isInvalid()) { |
| return -1; |
| } |
| if (state_ == State::kEnd) { |
| assert(size_ == 0); |
| return 0; |
| } |
| if (isSizeState()) { |
| assert(size == 0); |
| block = TSIOBufferBlockNext(block); |
| } |
| } |
| } |
| |
| int64_t length = 0; |
| |
| while (block != nullptr && state_ == State::kData) { |
| assert(size_ > 0); |
| const char *p = TSIOBufferBlockReadStart(block, r, &size); |
| if (p != nullptr) { |
| if (size >= size_) { |
| length += size_; |
| size_ = 0; |
| state_ = State::kSizeR; |
| break; |
| } else { |
| length += size; |
| size_ -= size; |
| } |
| } |
| block = TSIOBufferBlockNext(block); |
| } |
| |
| return length; |
| } |