| // 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 |