blob: 6b8c7faa209cb99215aa58d41cc10fcc807313cb [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "builder.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <list>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "frame.h"
#include "ipp_attribute.h"
#include "ipp_encoding.h"
#include "ipp_frame.h"
namespace ipp {
namespace {
struct GroupAsTNVs {
GroupTag tag;
std::list<TagNameValue> content;
};
// Saves boolean to buf.
void SaveBoolean(bool v, std::vector<uint8_t>* buf) {
buf->resize(1);
*(buf->data()) = v ? 1 : 0;
}
// Saves 4-bytes integer to buf using two's-complement binary encoding.
void SaveInt32(int32_t v, std::vector<uint8_t>* buf) {
buf->resize(4);
uint8_t* ptr = buf->data();
WriteInteger<4>(&ptr, v);
}
// Saves simple string to buf, buf is resized accordingly.
// If `s` is too long, the written content is silently truncated.
// The length of values of this type is verified when they are passed to a
// Frame object, so the truncation here should never occur.
void SaveOctetString(std::string_view s, std::vector<uint8_t>* buf) {
static constexpr size_t max_size = std::numeric_limits<int16_t>::max();
const size_t value_size = std::min(s.size(), max_size);
buf->resize(value_size);
std::copy_n(s.begin(), value_size, buf->begin());
}
// Writes textWithLanguage/nameWithLanguage (see [rfc8010]) to buf, which is
// resized accordingly.
// If `s` is too long, the written content is silently truncated.
// The length of values of this type is verified when they are passed to a
// Frame object, so the truncation here should never occur.
void SaveStringWithLanguage(const ipp::StringWithLanguage& s,
std::vector<uint8_t>* buf) {
// StringWithLanguage is used to save nameWithLanguage and textWithLanguage
// values. They are saved as a sequence of the following fields (see section
// 3.9 from rfc8010):
// * int16_t (2 bytes) - length of the language field = L
// * string (L bytes) - content of the language field
// * int16_t (2 bytes) - length of the value field = V
// * string (V bytes) - content of the value field
// The total size (2 + L + 2 + V) cannot exceed the threshold.
static constexpr size_t max_size = std::numeric_limits<int16_t>::max();
const size_t lang_size = std::min(s.language.size(), max_size - 4);
const size_t value_size = std::min(s.value.size(), max_size - 4 - lang_size);
buf->resize(4 + lang_size + value_size);
uint8_t* ptr = buf->data();
WriteInteger<2>(&ptr, static_cast<int16_t>(lang_size));
ptr = std::copy_n(s.language.begin(), lang_size, ptr);
WriteInteger<2>(&ptr, static_cast<int16_t>(value_size));
std::copy_n(s.value.begin(), value_size, ptr);
}
// Saves dateTime (see [rfc8010,rfc2579]) to buf, which is resized accordingly.
void SaveDateTime(const ipp::DateTime& v, std::vector<uint8_t>* buf) {
buf->resize(11);
uint8_t* ptr = buf->data();
WriteUnsigned<2>(&ptr, v.year);
WriteUnsigned<1>(&ptr, v.month);
WriteUnsigned<1>(&ptr, v.day);
WriteUnsigned<1>(&ptr, v.hour);
WriteUnsigned<1>(&ptr, v.minutes);
WriteUnsigned<1>(&ptr, v.seconds);
WriteUnsigned<1>(&ptr, v.deci_seconds);
WriteUnsigned<1>(&ptr, v.UTC_direction);
WriteUnsigned<1>(&ptr, v.UTC_hours);
WriteUnsigned<1>(&ptr, v.UTC_minutes);
}
// Writes resolution (see [rfc8010]) to buf, which is resized accordingly.
void SaveResolution(const ipp::Resolution& v, std::vector<uint8_t>* buf) {
buf->resize(9);
uint8_t* ptr = buf->data();
WriteInteger<4>(&ptr, v.xres);
WriteInteger<4>(&ptr, v.yres);
WriteInteger<1>(&ptr, static_cast<int8_t>(v.units));
}
// Writes rangeOfInteger (see [rfc8010]) to buf, which is resized accordingly.
void SaveRangeOfInteger(const ipp::RangeOfInteger& v,
std::vector<uint8_t>* buf) {
buf->resize(8);
uint8_t* ptr = buf->data();
WriteInteger<4>(&ptr, v.min_value);
WriteInteger<4>(&ptr, v.max_value);
}
// Helpers for converting individual values to binary form.
void SaveAttrValue(const Attribute* attr,
size_t index,
uint8_t* tag,
std::vector<uint8_t>* buf) {
*tag = static_cast<uint8_t>(attr->Tag());
switch (attr->Tag()) {
case ValueTag::boolean: {
int v = 0;
attr->GetValue(index, v);
SaveBoolean((v != 0), buf);
break;
}
case ValueTag::integer:
case ValueTag::enum_: {
int v = 0;
attr->GetValue(index, v);
SaveInt32(v, buf);
break;
}
case ValueTag::dateTime: {
DateTime v;
attr->GetValue(index, v);
SaveDateTime(v, buf);
break;
}
case ValueTag::resolution: {
Resolution v;
attr->GetValue(index, v);
SaveResolution(v, buf);
break;
}
case ValueTag::rangeOfInteger: {
RangeOfInteger v;
attr->GetValue(index, v);
SaveRangeOfInteger(v, buf);
break;
}
case ValueTag::textWithLanguage: {
StringWithLanguage s;
attr->GetValue(index, s);
if (s.language.empty()) {
*tag = static_cast<uint8_t>(ValueTag::textWithoutLanguage);
SaveOctetString(s.value, buf);
} else {
SaveStringWithLanguage(s, buf);
}
break;
}
case ValueTag::nameWithLanguage: {
StringWithLanguage s;
attr->GetValue(index, s);
if (s.language.empty()) {
*tag = static_cast<uint8_t>(ValueTag::nameWithoutLanguage);
SaveOctetString(s.value, buf);
} else {
SaveStringWithLanguage(s, buf);
}
break;
}
default:
if (IsString(attr->Tag()) || attr->Tag() == ValueTag::octetString) {
std::string s = "";
attr->GetValue(index, s);
SaveOctetString(s, buf);
} else {
// attr->Tag() must be out-of-band or unsupported value.
// Both of these should be handled earlier.
buf->clear();
}
break;
}
}
void SaveCollection(const Collection& coll,
std::list<TagNameValue>* data_chunks) {
// save the attributes
for (const Attribute& attr : coll) {
TagNameValue tnv;
tnv.tag = memberAttrName_value_tag;
tnv.name.clear();
SaveOctetString(attr.Name(), &tnv.value);
data_chunks->push_back(tnv);
if (IsOutOfBand(attr.Tag())) {
tnv.tag = static_cast<uint8_t>(attr.Tag());
tnv.value.clear();
data_chunks->push_back(tnv);
} else {
// standard values (one or more)
for (size_t val_index = 0; val_index < attr.Size(); ++val_index) {
if (attr.Tag() == ValueTag::collection) {
tnv.tag = begCollection_value_tag;
tnv.value.clear();
data_chunks->push_back(tnv);
SaveCollection(attr.Colls()[val_index], data_chunks);
tnv.tag = endCollection_value_tag;
tnv.value.clear();
} else {
SaveAttrValue(&attr, val_index, &tnv.tag, &tnv.value);
}
data_chunks->push_back(tnv);
}
}
}
}
void SaveGroup(const Collection* coll, std::list<TagNameValue>* data_chunks) {
// save the attributes
for (const Attribute& attr : *coll) {
TagNameValue tnv;
SaveOctetString(attr.Name(), &tnv.name);
if (IsOutOfBand(attr.Tag())) {
tnv.tag = static_cast<uint8_t>(attr.Tag());
tnv.value.clear();
data_chunks->push_back(tnv);
continue;
}
for (size_t val_index = 0; val_index < attr.Size(); ++val_index) {
if (attr.Tag() == ValueTag::collection) {
tnv.tag = begCollection_value_tag;
tnv.value.clear();
data_chunks->push_back(tnv);
SaveCollection(attr.Colls()[val_index], data_chunks);
tnv.tag = endCollection_value_tag;
tnv.name.clear();
tnv.value.clear();
} else {
SaveAttrValue(&attr, val_index, &tnv.tag, &tnv.value);
}
data_chunks->push_back(tnv);
tnv.name.clear();
}
}
}
// Write data to buffer (ptr is updated, whole list is written).
void WriteTNVsToBuffer(const std::list<TagNameValue>& tnvs, uint8_t** ptr) {
for (auto& tnv : tnvs) {
WriteUnsigned<1>(ptr, tnv.tag);
WriteInteger<2>(ptr, static_cast<int16_t>(tnv.name.size()));
*ptr = std::copy(tnv.name.begin(), tnv.name.end(), *ptr);
WriteInteger<2>(ptr, static_cast<int16_t>(tnv.value.size()));
*ptr = std::copy(tnv.value.begin(), tnv.value.end(), *ptr);
}
}
std::vector<GroupAsTNVs> PreprocessFrame(const Frame& frame) {
std::vector<GroupAsTNVs> groups_tnvs;
// save frame data
std::vector<std::pair<GroupTag, const Collection*>> groups =
frame.GetGroups();
for (std::pair<GroupTag, const Collection*> grp : groups) {
groups_tnvs.emplace_back(GroupAsTNVs{.tag = grp.first});
SaveGroup(grp.second, &(groups_tnvs.back().content));
}
return groups_tnvs;
}
void WriteFrameToBuffer(const Frame& frame,
const std::vector<GroupAsTNVs>& groups,
uint8_t* ptr) {
WriteUnsigned<2>(&ptr, static_cast<uint16_t>(frame.VersionNumber()));
WriteInteger<2>(&ptr, frame.OperationIdOrStatusCode());
WriteInteger<4>(&ptr, frame.RequestId());
for (size_t i = 0; i < groups.size(); ++i) {
// write a group to the buffer
WriteUnsigned<1>(&ptr, static_cast<uint8_t>(groups[i].tag));
WriteTNVsToBuffer(groups[i].content, &ptr);
}
WriteUnsigned<1>(&ptr, end_of_attributes_tag);
std::copy(frame.Data().begin(), frame.Data().end(), ptr);
}
std::size_t GetFrameLength(const Frame& frame,
const std::vector<GroupAsTNVs>& groups) {
// Header has always 8 bytes (ipp_version + operation_id/status + request_id).
size_t length = 8;
// The header is followed by a list of groups.
for (const GroupAsTNVs& grp : groups) {
// Each group starts with 1-byte group-tag ...
length += 1;
// ... and consists of list of tag-name-value.
for (const auto& tnv : grp.content)
// Tag + name_size + name + value_size + value.
length += (1 + 2 + tnv.name.size() + 2 + tnv.value.size());
}
// end-of-attributes-tag + blob_with_data.
length += 1 + frame.Data().size();
return length;
}
} // namespace
size_t CalculateLengthOfBinaryFrame(const Frame& frame) {
std::vector<GroupAsTNVs> tnvs = PreprocessFrame(frame);
return GetFrameLength(frame, tnvs);
}
size_t BuildBinaryFrame(const Frame& frame,
uint8_t* buffer,
size_t buffer_length) {
std::vector<GroupAsTNVs> tnvs = PreprocessFrame(frame);
const size_t length = GetFrameLength(frame, tnvs);
if (length > buffer_length) {
return 0;
}
WriteFrameToBuffer(frame, tnvs, buffer);
return length;
}
std::vector<uint8_t> BuildBinaryFrame(const Frame& frame) {
std::vector<GroupAsTNVs> tnvs = PreprocessFrame(frame);
std::vector<uint8_t> buffer(GetFrameLength(frame, tnvs));
WriteFrameToBuffer(frame, tnvs, buffer.data());
return buffer;
}
} // namespace ipp