80c663882a
Summary: First draft. Unit tests pass. Test Plan: unit tests attached Reviewers: heyongqiang Reviewed By: heyongqiang Differential Revision: https://reviews.facebook.net/D3969
358 lines
9.3 KiB
C++
358 lines
9.3 KiB
C++
/**
|
|
* Copyright 2012 Facebook
|
|
* @author Tudor Bosman (tudorb@fb.com)
|
|
*/
|
|
|
|
#ifndef THRIFT_LIB_CPP_PROTOCOL_NEUTRONIUM_ENCODER_INL_H_
|
|
#define THRIFT_LIB_CPP_PROTOCOL_NEUTRONIUM_ENCODER_INL_H_
|
|
|
|
#ifndef THRIFT_INCLUDE_ENCODER_INL
|
|
#error This file may only be included from Encoder.h
|
|
#endif
|
|
|
|
#include "folly/Conv.h"
|
|
|
|
namespace apache {
|
|
namespace thrift {
|
|
namespace protocol {
|
|
namespace neutronium {
|
|
|
|
inline void Encoder::writeFieldBegin(const char* name, TType fieldType,
|
|
int16_t fieldId) {
|
|
auto& s = top();
|
|
DCHECK(s.state == IN_STRUCT);
|
|
s.field = s.dataType->fields.at(fieldId);
|
|
s.tag = fieldId;
|
|
s.state = IN_FIELD;
|
|
}
|
|
|
|
inline void Encoder::writeFieldEnd() {
|
|
auto& s = top();
|
|
DCHECK(s.state == DONE_FIELD);
|
|
s.tag = 0;
|
|
s.field.clear();
|
|
s.state = IN_STRUCT;
|
|
}
|
|
|
|
inline void Encoder::writeFieldStop() {
|
|
auto& s = top();
|
|
DCHECK(s.state == IN_STRUCT);
|
|
flush();
|
|
}
|
|
|
|
inline bool Encoder::EncoderState::inDataState() const {
|
|
return (state == IN_FIELD ||
|
|
state == IN_MAP_KEY ||
|
|
state == IN_MAP_VALUE ||
|
|
state == IN_LIST_VALUE ||
|
|
state == IN_SET_VALUE);
|
|
}
|
|
|
|
inline bool Encoder::EncoderState::inFlushableState() const {
|
|
return (state == IN_STRUCT ||
|
|
state == IN_MAP_KEY ||
|
|
state == IN_LIST_VALUE ||
|
|
state == IN_SET_VALUE);
|
|
}
|
|
|
|
inline void Encoder::EncoderState::dataWritten() {
|
|
switch (state) {
|
|
case IN_FIELD:
|
|
state = DONE_FIELD;
|
|
break;
|
|
case IN_MAP_KEY:
|
|
state = IN_MAP_VALUE;
|
|
field.type = dataType->valueType;
|
|
break;
|
|
case IN_MAP_VALUE:
|
|
state = IN_MAP_KEY;
|
|
field.type = dataType->mapKeyType;
|
|
break;
|
|
case IN_LIST_VALUE:
|
|
break;
|
|
case IN_SET_VALUE:
|
|
break;
|
|
default:
|
|
LOG(FATAL) << "Invalid state " << state;
|
|
}
|
|
}
|
|
|
|
template <class C, class T>
|
|
inline size_t findIndex(const C& container, T val) {
|
|
auto pos = container.find(val);
|
|
if (pos == container.end()) throw std::out_of_range("not found");
|
|
return pos - container.begin();
|
|
}
|
|
|
|
inline void Encoder::EncoderState::markFieldSet() {
|
|
// only useful for structs
|
|
if (state != IN_FIELD || field.isRequired) return;
|
|
optionalSet[findIndex(dataType->optionalFields, tag)] = true;
|
|
}
|
|
|
|
template <typename... Args>
|
|
inline void Encoder::EncoderState::checkType() const {
|
|
throw TLibraryException(folly::to<std::string>("Invalid type ", field.type));
|
|
}
|
|
|
|
template <typename... Args>
|
|
inline void Encoder::EncoderState::checkType(reflection::Type t,
|
|
Args... tail) const {
|
|
if (t == reflection::getType(field.type)) return;
|
|
checkType(tail...);
|
|
}
|
|
|
|
template <class Vec>
|
|
inline void Encoder::EncoderState::appendToOutput(const Vec& vec) {
|
|
size_t bytes = vec.size() * sizeof(typename Vec::value_type::second_type);
|
|
for (auto& p : vec) {
|
|
appender.writeBE(p.second);
|
|
}
|
|
bytesWritten += bytes;
|
|
}
|
|
|
|
inline void Encoder::writeMapBegin(TType keyType, TType valType,
|
|
uint32_t size) {
|
|
push(reflection::TYPE_MAP, topType(), size);
|
|
}
|
|
|
|
inline void Encoder::writeMapEnd() {
|
|
DCHECK_EQ(top().state, IN_MAP_KEY);
|
|
flush();
|
|
pop();
|
|
}
|
|
|
|
inline void Encoder::writeListBegin(TType elemType, uint32_t size) {
|
|
push(reflection::TYPE_LIST, topType(), size);
|
|
}
|
|
|
|
inline void Encoder::writeListEnd() {
|
|
DCHECK_EQ(top().state, IN_LIST_VALUE);
|
|
flush();
|
|
pop();
|
|
}
|
|
|
|
inline void Encoder::writeSetBegin(TType elemType, uint32_t size) {
|
|
push(reflection::TYPE_SET, topType(), size);
|
|
}
|
|
|
|
inline void Encoder::writeSetEnd() {
|
|
DCHECK_EQ(top().state, IN_SET_VALUE);
|
|
flush();
|
|
pop();
|
|
}
|
|
|
|
inline void Encoder::writeStructBegin(const char* name) {
|
|
push(reflection::TYPE_STRUCT, topType(), 0);
|
|
}
|
|
|
|
inline void Encoder::writeStructEnd() {
|
|
DCHECK_EQ(top().state, FLUSHED);
|
|
// writeFieldStop() called flush()
|
|
pop();
|
|
}
|
|
|
|
inline void Encoder::push(reflection::Type rtype, int64_t type,
|
|
uint32_t size) {
|
|
DCHECK(stack_.empty() || top().inDataState());
|
|
if (reflection::getType(type) != rtype) {
|
|
throw TLibraryException(folly::to<std::string>(
|
|
"Invalid aggregate type ", reflection::getType(type),
|
|
" expected ", rtype));
|
|
}
|
|
|
|
const DataType* dt = &(schema_->map().at(type));
|
|
stack_.emplace_back(new EncoderState(type, dt, size));
|
|
}
|
|
|
|
inline void Encoder::pop() {
|
|
auto& s = top();
|
|
DCHECK_EQ(s.state, FLUSHED);
|
|
int64_t size = s.dataType->fixedSize;
|
|
DCHECK(size == -1 || size == s.buf->computeChainDataLength());
|
|
size_t bytesWritten = s.bytesWritten;
|
|
DCHECK_EQ(bytesWritten, s.buf->computeChainDataLength());
|
|
auto buf = std::move(s.buf);
|
|
stack_.pop_back();
|
|
if (stack_.empty()) {
|
|
outputBuf_->prependChain(std::move(buf));
|
|
bytesWritten_ = bytesWritten;
|
|
} else {
|
|
writeData(std::move(buf));
|
|
top().bytesWritten += bytesWritten;
|
|
}
|
|
}
|
|
|
|
inline Encoder::EncoderState& Encoder::top() {
|
|
DCHECK(!stack_.empty());
|
|
DCHECK(stack_.back());
|
|
return *stack_.back();
|
|
}
|
|
|
|
inline const Encoder::EncoderState& Encoder::top() const {
|
|
DCHECK(!stack_.empty());
|
|
DCHECK(stack_.back());
|
|
return *stack_.back();
|
|
}
|
|
|
|
inline int64_t Encoder::topType() const {
|
|
return (stack_.empty() ? rootType_ : top().field.type);
|
|
}
|
|
|
|
inline void Encoder::writeBool(bool v) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.checkType(reflection::TYPE_BOOL);
|
|
s.markFieldSet();
|
|
s.bools.emplace_back(s.tag, v);
|
|
s.dataWritten();
|
|
}
|
|
|
|
inline void Encoder::writeByte(int8_t v) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.markFieldSet();
|
|
s.checkType(reflection::TYPE_BYTE);
|
|
s.bytes.emplace_back(s.tag, v);
|
|
s.dataWritten();
|
|
}
|
|
|
|
inline void Encoder::writeI16(int16_t v) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.checkType(reflection::TYPE_I16);
|
|
s.markFieldSet();
|
|
if (s.field.isFixed) {
|
|
s.fixedInt16s.emplace_back(s.tag, v);
|
|
} else {
|
|
s.varInts.emplace_back(s.tag, v);
|
|
}
|
|
s.dataWritten();
|
|
}
|
|
|
|
inline void Encoder::writeI32(int32_t v) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.checkType(reflection::TYPE_I32, reflection::TYPE_ENUM);
|
|
s.markFieldSet();
|
|
if (reflection::getType(s.field.type) == reflection::TYPE_ENUM &&
|
|
s.field.isStrictEnum) {
|
|
auto& dt = schema_->map().at(s.field.type);
|
|
uint8_t nbits = dt.enumBits();
|
|
uint32_t value = findIndex(dt.enumValues, v);
|
|
s.strictEnums.push_back({s.tag, {nbits, value}});
|
|
s.totalStrictEnumBits += nbits;
|
|
} else {
|
|
if (s.field.isFixed) {
|
|
s.fixedInt32s.emplace_back(s.tag, v);
|
|
} else {
|
|
s.varInts.emplace_back(s.tag, v);
|
|
}
|
|
}
|
|
s.dataWritten();
|
|
}
|
|
|
|
inline void Encoder::writeI64(int64_t v) {
|
|
innerWriteI64(v, reflection::TYPE_I64);
|
|
}
|
|
|
|
inline void Encoder::writeDouble(double v) {
|
|
innerWriteI64(bitwise_cast<int64_t>(v), reflection::TYPE_DOUBLE);
|
|
}
|
|
|
|
inline void Encoder::innerWriteI64(int64_t v, reflection::Type expected) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.markFieldSet();
|
|
s.checkType(expected);
|
|
if (s.field.isFixed) {
|
|
s.fixedInt64s.emplace_back(s.tag, v);
|
|
} else {
|
|
s.varInt64s.emplace_back(s.tag, v);
|
|
}
|
|
s.dataWritten();
|
|
}
|
|
|
|
namespace {
|
|
std::unique_ptr<folly::IOBuf> copyBufferToSize(
|
|
const void* data, size_t dataSize, size_t outSize, char pad) {
|
|
auto buf = folly::IOBuf::create(outSize);
|
|
dataSize = std::min(dataSize, outSize);
|
|
memcpy(buf->writableData(), data, dataSize);
|
|
if (dataSize < outSize) {
|
|
memset(buf->writableData() + dataSize, pad, outSize - dataSize);
|
|
}
|
|
buf->append(outSize);
|
|
return buf;
|
|
}
|
|
|
|
std::unique_ptr<folly::IOBuf> copyBufferAndTerminate(
|
|
const void* data, size_t dataSize, char terminator) {
|
|
if (memchr(data, terminator, dataSize)) {
|
|
throw TProtocolException("terminator found in terminated string");
|
|
}
|
|
// 1 byte of tailroom for the terminator
|
|
auto buf = folly::IOBuf::copyBuffer(data, dataSize, 0, 1);
|
|
buf->writableTail()[0] = terminator;
|
|
buf->append(1);
|
|
return buf;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
// TODO(tudorb): Zero-copy version
|
|
inline void Encoder::writeBytes(folly::StringPiece data) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.checkType(reflection::TYPE_STRING);
|
|
s.markFieldSet();
|
|
if (s.field.isInterned) {
|
|
CHECK(internTable_);
|
|
s.varInternIds.emplace_back(s.tag, internTable_->add(data));
|
|
} else if (s.field.isFixed) {
|
|
s.strings.emplace_back(
|
|
s.tag, copyBufferToSize(data.data(), data.size(),
|
|
s.field.fixedStringSize, s.field.pad));
|
|
s.bytesWritten += s.field.fixedStringSize;
|
|
} else if (s.field.isTerminated) {
|
|
s.strings.emplace_back(
|
|
s.tag, copyBufferAndTerminate(data.data(), data.size(),
|
|
s.field.terminator));
|
|
s.bytesWritten += data.size() + 1;
|
|
} else {
|
|
s.varLengths.emplace_back(s.tag, data.size());
|
|
s.strings.emplace_back(
|
|
s.tag, folly::IOBuf::copyBuffer(data.data(), data.size()));
|
|
s.bytesWritten += data.size();
|
|
}
|
|
s.dataWritten();
|
|
}
|
|
|
|
inline void Encoder::writeData(std::unique_ptr<folly::IOBuf>&& data) {
|
|
auto& s = top();
|
|
DCHECK(s.inDataState());
|
|
s.markFieldSet();
|
|
s.strings.emplace_back(s.tag, std::move(data));
|
|
s.dataWritten();
|
|
}
|
|
|
|
inline void Encoder::flush() {
|
|
auto& s = top();
|
|
DCHECK(s.inFlushableState());
|
|
if (s.state == IN_STRUCT) {
|
|
// TODO(tudorb): Check that all required fields were actually specified.
|
|
flushStruct();
|
|
}
|
|
flushData(s.state == IN_STRUCT);
|
|
s.state = FLUSHED;
|
|
}
|
|
|
|
} // namespace neutronium
|
|
} // namespace protocol
|
|
} // namespace thrift
|
|
} // namespace apache
|
|
|
|
#endif /* THRIFT_LIB_CPP_PROTOCOL_NEUTRONIUM_ENCODER_INL_H_ */
|
|
|