/*----------------------------------------------------------------------------*/ /* Copyright (c) FIRST 2015. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ /*----------------------------------------------------------------------------*/ #include "WireEncoder.h" #include #include #include #include #include "llvm/MathExtras.h" #include "leb128.h" using namespace nt; WireEncoder::WireEncoder(unsigned int proto_rev) { // Start with a 1024-byte buffer. Use malloc instead of new so we can // realloc. m_start = m_cur = static_cast(std::malloc(1024)); m_end = m_start + 1024; m_proto_rev = proto_rev; m_error = nullptr; } WireEncoder::~WireEncoder() { std::free(m_start); } void WireEncoder::WriteDouble(double val) { Reserve(8); // The highest performance way to do this, albeit non-portable. std::uint64_t v = llvm::DoubleToBits(val); m_cur[7] = (char)(v & 0xff); v >>= 8; m_cur[6] = (char)(v & 0xff); v >>= 8; m_cur[5] = (char)(v & 0xff); v >>= 8; m_cur[4] = (char)(v & 0xff); v >>= 8; m_cur[3] = (char)(v & 0xff); v >>= 8; m_cur[2] = (char)(v & 0xff); v >>= 8; m_cur[1] = (char)(v & 0xff); v >>= 8; m_cur[0] = (char)(v & 0xff); m_cur += 8; } void WireEncoder::ReserveSlow(std::size_t len) { assert(m_end >= m_cur); // should never happen due to checks in Reserve(), but check anyway if (static_cast(m_end - m_cur) >= len) return; // Double current buffer size until we have enough space. Since we're // reserving space, it's likely more will be required soon in any case. std::size_t pos = m_cur - m_start; std::size_t newlen = (m_end - m_start) * 2; while (newlen < (pos + len)) newlen *= 2; m_start = static_cast(std::realloc(m_start, newlen)); m_cur = m_start + pos; m_end = m_start + newlen; } void WireEncoder::WriteUleb128(unsigned long val) { Reserve(SizeUleb128(val)); m_cur += nt::WriteUleb128(m_cur, val); } void WireEncoder::WriteType(NT_Type type) { Reserve(1); // Convert from enum to actual byte value. switch (type) { case NT_BOOLEAN: *m_cur = 0x00; break; case NT_DOUBLE: *m_cur = 0x01; break; case NT_STRING: *m_cur = 0x02; break; case NT_RAW: if (m_proto_rev < 0x0300u) { m_error = "raw type not supported in protocol < 3.0"; return; } *m_cur = 0x03; break; case NT_BOOLEAN_ARRAY: *m_cur = 0x10; break; case NT_DOUBLE_ARRAY: *m_cur = 0x11; break; case NT_STRING_ARRAY: *m_cur = 0x12; break; case NT_RPC: if (m_proto_rev < 0x0300u) { m_error = "RPC type not supported in protocol < 3.0"; return; } *m_cur = 0x20; break; default: m_error = "unrecognized type"; return; } ++m_cur; } std::size_t WireEncoder::GetValueSize(const Value& value) const { switch (value.type()) { case NT_BOOLEAN: return 1; case NT_DOUBLE: return 8; case NT_STRING: return GetStringSize(value.GetString()); case NT_RAW: if (m_proto_rev < 0x0300u) return 0; return GetStringSize(value.GetRaw()); case NT_RPC: if (m_proto_rev < 0x0300u) return 0; return GetStringSize(value.GetRpc()); case NT_BOOLEAN_ARRAY: { // 1-byte size, 1 byte per element std::size_t size = value.GetBooleanArray().size(); if (size > 0xff) size = 0xff; // size is only 1 byte, truncate return 1 + size; } case NT_DOUBLE_ARRAY: { // 1-byte size, 8 bytes per element std::size_t size = value.GetDoubleArray().size(); if (size > 0xff) size = 0xff; // size is only 1 byte, truncate return 1 + size * 8; } case NT_STRING_ARRAY: { auto v = value.GetStringArray(); std::size_t size = v.size(); if (size > 0xff) size = 0xff; // size is only 1 byte, truncate std::size_t len = 1; // 1-byte size for (std::size_t i = 0; i < size; ++i) len += GetStringSize(v[i]); return len; } default: return 0; } } void WireEncoder::WriteValue(const Value& value) { switch (value.type()) { case NT_BOOLEAN: Write8(value.GetBoolean() ? 1 : 0); break; case NT_DOUBLE: WriteDouble(value.GetDouble()); break; case NT_STRING: WriteString(value.GetString()); break; case NT_RAW: if (m_proto_rev < 0x0300u) { m_error = "raw values not supported in protocol < 3.0"; return; } WriteString(value.GetRaw()); break; case NT_RPC: if (m_proto_rev < 0x0300u) { m_error = "RPC values not supported in protocol < 3.0"; return; } WriteString(value.GetRpc()); break; case NT_BOOLEAN_ARRAY: { auto v = value.GetBooleanArray(); std::size_t size = v.size(); if (size > 0xff) size = 0xff; // size is only 1 byte, truncate Reserve(1 + size); Write8(size); for (std::size_t i = 0; i < size; ++i) Write8(v[i] ? 1 : 0); break; } case NT_DOUBLE_ARRAY: { auto v = value.GetDoubleArray(); std::size_t size = v.size(); if (size > 0xff) size = 0xff; // size is only 1 byte, truncate Reserve(1 + size * 8); Write8(size); for (std::size_t i = 0; i < size; ++i) WriteDouble(v[i]); break; } case NT_STRING_ARRAY: { auto v = value.GetStringArray(); std::size_t size = v.size(); if (size > 0xff) size = 0xff; // size is only 1 byte, truncate Write8(size); for (std::size_t i = 0; i < size; ++i) WriteString(v[i]); break; } default: m_error = "unrecognized type when writing value"; return; } } std::size_t WireEncoder::GetStringSize(llvm::StringRef str) const { if (m_proto_rev < 0x0300u) { std::size_t len = str.size(); if (len > 0xffff) len = 0xffff; // Limited to 64K length; truncate return 2 + len; } return SizeUleb128(str.size()) + str.size(); } void WireEncoder::WriteString(llvm::StringRef str) { // length std::size_t len = str.size(); if (m_proto_rev < 0x0300u) { if (len > 0xffff) len = 0xffff; // Limited to 64K length; truncate Write16(len); } else WriteUleb128(len); // contents Reserve(len); std::memcpy(m_cur, str.data(), len); m_cur += len; }