blob: ad957daba25f6d95a7db7cce556324a7055a89c4 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "libipp/ipp_frame_builder.h"
#include <algorithm>
#include "libipp/ipp_attribute.h"
#include "libipp/ipp_encoding.h"
namespace ipp {
namespace {
// Saves 1-,2- or 4-bytes integer to buf using two's-complement binary encoding.
// The "buf" parameter is always resized to BytesCount. Returns false when
// given integer is out of range. In this case 0 is saved to buf.
template <size_t BytesCount>
bool SaveInteger(int v, std::vector<uint8_t>* buf) {
buf->resize(BytesCount);
uint8_t* ptr = buf->data();
if (!WriteInteger<BytesCount>(&ptr, v)) {
buf->clear();
buf->resize(BytesCount, 0);
return false;
}
return true;
}
// Saves simple string to buf, buf is resized accordingly.
void SaveOctetString(const std::string& s, std::vector<uint8_t>* buf) {
buf->assign(s.begin(), s.end());
}
// Writes textWithLanguage/nameWithLanguage (see [rfc8010]) to buf, which is
// resized accordingly. Returns false if given string(s) is too long. In this
// case an empty string is saved to the buffer.
bool SaveStringWithLanguage(const ipp::StringWithLanguage& s,
std::vector<uint8_t>* buf) {
buf->clear();
buf->resize(4 + s.value.size() + s.language.size());
uint8_t* ptr = buf->data();
if (!WriteInteger<2>(&ptr, s.language.size())) {
std::vector<uint8_t> empty_buffer(4, 0);
buf->swap(empty_buffer);
return false;
}
ptr = std::copy(s.language.begin(), s.language.end(), ptr);
if (!WriteInteger<2>(&ptr, s.value.size())) {
std::vector<uint8_t> empty_buffer(4, 0);
buf->swap(empty_buffer);
return false;
}
std::copy(s.value.begin(), s.value.end(), ptr);
return true;
}
// Saves dateTime (see [rfc8010]) to buf, which is resized accordingly. Returns
// false when the given dateTime is incorrect; in this case |buf| is set to
// 2000/1/1 00:00:00 +00:00.
bool SaveDateTime(const ipp::DateTime& v, std::vector<uint8_t>* buf) {
buf->resize(11);
uint8_t* ptr = buf->data();
if (WriteInteger<2>(&ptr, v.year) && WriteInteger<1>(&ptr, v.month) &&
WriteInteger<1>(&ptr, v.day) && WriteInteger<1>(&ptr, v.hour) &&
WriteInteger<1>(&ptr, v.minutes) && WriteInteger<1>(&ptr, v.seconds) &&
WriteInteger<1>(&ptr, v.deci_seconds) &&
WriteInteger<1>(&ptr, v.UTC_direction) &&
WriteInteger<1>(&ptr, v.UTC_hours) &&
WriteInteger<1>(&ptr, v.UTC_minutes))
return true;
buf->clear();
buf->resize(11, 0);
ptr = buf->data();
WriteInteger<int16_t>(&ptr, 2000);
(*buf)[8] = '+';
return false;
}
// Writes resolution (see [rfc8010]) to buf, which is resized accordingly.
// Returns false when given value is incorrect; in this case |buf| is set to
// 256x256dpi.
bool SaveResolution(const ipp::Resolution& v, std::vector<uint8_t>* buf) {
buf->resize(9);
uint8_t* ptr = buf->data();
if (WriteInteger<4>(&ptr, v.xres) && WriteInteger<4>(&ptr, v.yres) &&
(v.units == ipp::Resolution::kDotsPerCentimeter ||
v.units == ipp::Resolution::kDotsPerInch)) {
WriteInteger<int8_t>(&ptr, v.units);
return true;
}
buf->clear();
// set to 256x256 dpi
buf->resize(9, 0);
(*buf)[2] = (*buf)[6] = 1;
(*buf)[8] = ipp::Resolution::kDotsPerInch;
return false;
}
// Writes rangeOfInteger (see [rfc8010]) to buf, which is resized accordingly.
// Returns false when given value is incorrect; in this case |buf| is set to 0.
bool SaveRangeOfInteger(const ipp::RangeOfInteger& v,
std::vector<uint8_t>* buf) {
buf->resize(8);
uint8_t* ptr = buf->data();
if (WriteInteger<4>(&ptr, v.min_value) && WriteInteger<4>(&ptr, v.max_value))
return true;
buf->clear();
buf->resize(8, 0);
return false;
}
} // namespace
void FrameBuilder::LogFrameBuilderError(const std::string& message) {
Log l;
l.message = "Error when building frame: " + message + ".";
errors_->push_back(l);
}
void FrameBuilder::SaveAttrValue(Attribute* attr,
size_t index,
uint8_t* tag,
std::vector<uint8_t>* buf) {
*tag = static_cast<uint8_t>(attr->GetType());
bool result = true;
switch (attr->GetType()) {
case AttrType::boolean: {
int v = 0;
attr->GetValue(&v, index);
if (v != 0)
v = 1;
result = SaveInteger<1>(v, buf);
break;
}
case AttrType::integer:
case AttrType::enum_: {
int v = 0;
attr->GetValue(&v, index);
result = SaveInteger<4>(v, buf);
break;
}
case AttrType::dateTime: {
DateTime v;
attr->GetValue(&v, index);
result = SaveDateTime(v, buf);
break;
}
case AttrType::resolution: {
Resolution v;
attr->GetValue(&v, index);
result = SaveResolution(v, buf);
break;
}
case AttrType::rangeOfInteger: {
RangeOfInteger v;
attr->GetValue(&v, index);
result = SaveRangeOfInteger(v, buf);
break;
}
case AttrType::text: {
StringWithLanguage s;
attr->GetValue(&s, index);
if (s.language.empty()) {
*tag = textWithoutLanguage_value_tag;
SaveOctetString(s, buf);
} else {
result = SaveStringWithLanguage(s, buf);
}
} break;
case AttrType::name: {
StringWithLanguage s;
attr->GetValue(&s, index);
if (s.language.empty()) {
*tag = nameWithoutLanguage_value_tag;
SaveOctetString(s, buf);
} else {
result = SaveStringWithLanguage(s, buf);
}
} break;
case AttrType::octetString:
case AttrType::keyword:
case AttrType::uri:
case AttrType::uriScheme:
case AttrType::charset:
case AttrType::naturalLanguage:
case AttrType::mimeMediaType: {
std::string s = "";
attr->GetValue(&s, index);
SaveOctetString(s, buf);
break;
}
default:
LogFrameBuilderError(
"Internal error: cannot recognize value type of the attribute " +
attr->GetName());
buf->clear();
break;
}
if (!result)
LogFrameBuilderError("Incorrect value of the attribute " + attr->GetName() +
". Default value was written instead");
}
void FrameBuilder::SaveCollection(Collection* coll,
std::list<TagNameValue>* data_chunks) {
// get list of all attributes
std::vector<Attribute*> attrs = coll->GetAllAttributes();
// save the attributes
for (Attribute* attr : attrs) {
if (attr->GetState() == AttrState::unset)
continue;
TagNameValue tnv;
tnv.tag = memberAttrName_value_tag;
tnv.name.clear();
SaveOctetString(attr->GetName(), &tnv.value);
data_chunks->push_back(tnv);
if (attr->GetState() != AttrState::set) {
// out-of-band value (tag)
tnv.tag = static_cast<uint8_t>(attr->GetState());
tnv.value.clear();
data_chunks->push_back(tnv);
} else {
// standard values (one or more)
for (size_t val_index = 0; val_index < attr->GetSize(); ++val_index) {
if (attr->GetType() == AttrType::collection) {
tnv.tag = begCollection_value_tag;
tnv.value.clear();
data_chunks->push_back(tnv);
SaveCollection(attr->GetCollection(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 FrameBuilder::SaveGroup(Collection* coll,
std::list<TagNameValue>* data_chunks) {
// get list of all attributes
std::vector<Attribute*> attrs = coll->GetAllAttributes();
// save the attributes
for (Attribute* attr : attrs) {
if (attr->GetState() == AttrState::unset)
continue;
TagNameValue tnv;
SaveOctetString(attr->GetName(), &tnv.name);
if (attr->GetState() != AttrState::set) {
tnv.tag = static_cast<uint8_t>(attr->GetState());
tnv.value.clear();
data_chunks->push_back(tnv);
continue;
}
for (size_t val_index = 0; val_index < attr->GetSize(); ++val_index) {
if (attr->GetType() == AttrType::collection) {
tnv.tag = begCollection_value_tag;
tnv.value.clear();
data_chunks->push_back(tnv);
SaveCollection(attr->GetCollection(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();
}
}
}
void FrameBuilder::BuildFrameFromPackage(Package* package) {
frame_->groups_tags_.clear();
frame_->groups_content_.clear();
// save frame data
std::vector<Group*> groups = package->GetAllGroups();
for (Group* grp : groups) {
for (size_t i = 0; true; ++i) {
Collection* coll = grp->GetCollection(i);
if (coll == nullptr)
break;
frame_->groups_tags_.push_back(static_cast<uint8_t>(grp->GetName()));
frame_->groups_content_.resize(frame_->groups_tags_.size());
SaveGroup(coll, &(frame_->groups_content_.back()));
// skip if it this is a single empty group
if (frame_->groups_content_.back().empty() && !grp->IsASet()) {
frame_->groups_tags_.pop_back();
frame_->groups_content_.pop_back();
}
}
}
frame_->data_ = package->Data();
}
bool FrameBuilder::WriteFrameToBuffer(uint8_t* ptr) {
bool error_in_header = true;
if (!WriteInteger<1>(&ptr, frame_->major_version_number_)) {
LogFrameBuilderError("major-version-number is out of range");
} else if (!WriteInteger<1>(&ptr, frame_->minor_version_number_)) {
LogFrameBuilderError("minor-version-number is out of range");
} else if (!WriteInteger<2>(&ptr, frame_->operation_id_or_status_code_)) {
LogFrameBuilderError("operation-id or status-code is out of range");
} else if (!WriteInteger<4>(&ptr, frame_->request_id_)) {
LogFrameBuilderError("request-id is out of range");
} else {
error_in_header = false;
}
if (error_in_header)
return false;
for (size_t i = 0; i < frame_->groups_tags_.size(); ++i) {
if (frame_->groups_tags_[i] > max_begin_attribute_group_tag) {
LogFrameBuilderError("begin-attribute-group-tag is out of range");
return false;
}
// write a group to the buffer
if (!WriteInteger<1>(&ptr, frame_->groups_tags_[i]) ||
!WriteTNVsToBuffer(frame_->groups_content_[i], &ptr))
return false;
}
WriteInteger<int8_t>(&ptr, end_of_attributes_tag);
std::copy(frame_->data_.begin(), frame_->data_.end(), ptr);
return true;
}
bool FrameBuilder::WriteTNVsToBuffer(const std::list<TagNameValue>& tnvs,
uint8_t** ptr) {
for (auto& tnv : tnvs) {
if (!WriteInteger<1>(ptr, tnv.tag)) {
LogFrameBuilderError("value-tag is out of range");
return false;
}
if (!WriteInteger<2>(ptr, tnv.name.size())) {
LogFrameBuilderError("name-length is out of range");
return false;
}
*ptr = std::copy(tnv.name.begin(), tnv.name.end(), *ptr);
if (!WriteInteger<2>(ptr, tnv.value.size())) {
LogFrameBuilderError("value-length is out of range");
return false;
}
*ptr = std::copy(tnv.value.begin(), tnv.value.end(), *ptr);
}
return true;
}
std::size_t FrameBuilder::GetFrameLength() const {
// 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 auto& tnvs : frame_->groups_content_) {
// Each group starts with 1-byte group-tag ...
length += 1;
// ... and consists of list of tag-name-value.
for (const auto& tnv : tnvs)
// 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 ipp