// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html #include "precomp.hpp" #include "persistence_impl.hpp" #include "persistence_base64_encoding.hpp" namespace cv { class base64::Base64ContextEmitter { public: explicit Base64ContextEmitter(cv::FileStorage::Impl& fs, bool needs_indent_) : file_storage(fs) , needs_indent(needs_indent_) , binary_buffer(BUFFER_LEN) , base64_buffer(base64_encode_buffer_size(BUFFER_LEN)) , src_beg(0) , src_cur(0) , src_end(0) { src_beg = binary_buffer.data(); src_end = src_beg + BUFFER_LEN; src_cur = src_beg; CV_Assert(fs.write_mode); if (needs_indent) { file_storage.flush(); } } ~Base64ContextEmitter() { /* cleaning */ if (src_cur != src_beg) flush(); /* encode the rest binary data to base64 buffer */ } Base64ContextEmitter & write(const uchar * beg, const uchar * end) { if (beg >= end) return *this; while (beg < end) { /* collect binary data and copy to binary buffer */ size_t len = std::min(end - beg, src_end - src_cur); std::memcpy(src_cur, beg, len); beg += len; src_cur += len; if (src_cur >= src_end) { /* binary buffer is full. */ /* encode it to base64 and send result to fs */ flush(); } } return *this; } /* * a convertor must provide : * - `operator >> (uchar * & dst)` for writing current binary data to `dst` and moving to next data. * - `operator bool` for checking if current loaction is valid and not the end. */ template inline Base64ContextEmitter & write(_to_binary_convertor_t & convertor) { static const size_t BUFFER_MAX_LEN = 1024U; std::vector buffer(BUFFER_MAX_LEN); uchar * beg = buffer.data(); uchar * end = beg; while (convertor) { convertor >> end; write(beg, end); end = beg; } return *this; } bool flush() { /* control line width, so on. */ size_t len = base64_encode(src_beg, base64_buffer.data(), 0U, src_cur - src_beg); if (len == 0U) return false; src_cur = src_beg; if ( !needs_indent) { file_storage.puts((const char*)base64_buffer.data()); } else { const char newline[] = "\n"; char space[80]; int ident = file_storage.write_stack.back().indent; memset(space, ' ', static_cast(ident)); space[ident] = '\0'; file_storage.puts(space); file_storage.puts((const char*)base64_buffer.data()); file_storage.puts(newline); file_storage.flush(); } return true; } private: /* because of Base64, we must keep its length a multiple of 3 */ static const size_t BUFFER_LEN = 48U; // static_assert(BUFFER_LEN % 3 == 0, "BUFFER_LEN is invalid"); private: cv::FileStorage::Impl& file_storage; bool needs_indent; std::vector binary_buffer; std::vector base64_buffer; uchar * src_beg; uchar * src_cur; uchar * src_end; }; std::string base64::make_base64_header(const char *dt) { std::ostringstream oss; oss << dt << ' '; std::string buffer(oss.str()); CV_Assert(buffer.size() < ::base64::HEADER_SIZE); buffer.reserve(::base64::HEADER_SIZE); while (buffer.size() < ::base64::HEADER_SIZE) buffer += ' '; return buffer; } size_t base64::base64_encode(const uint8_t *src, uint8_t *dst, size_t off, size_t cnt) { if (!src || !dst || !cnt) return 0; /* initialize beginning and end */ uint8_t * dst_beg = dst; uint8_t * dst_cur = dst_beg; uint8_t const * src_beg = src + off; uint8_t const * src_cur = src_beg; uint8_t const * src_end = src_cur + cnt / 3U * 3U; /* integer multiples part */ while (src_cur < src_end) { uint8_t _2 = *src_cur++; uint8_t _1 = *src_cur++; uint8_t _0 = *src_cur++; *dst_cur++ = base64_mapping[ _2 >> 2U]; *dst_cur++ = base64_mapping[(_1 & 0xF0U) >> 4U | (_2 & 0x03U) << 4U]; *dst_cur++ = base64_mapping[(_0 & 0xC0U) >> 6U | (_1 & 0x0FU) << 2U]; *dst_cur++ = base64_mapping[ _0 & 0x3FU]; } /* remainder part */ size_t rst = src_beg + cnt - src_cur; if (rst == 1U) { uint8_t _2 = *src_cur++; *dst_cur++ = base64_mapping[ _2 >> 2U]; *dst_cur++ = base64_mapping[(_2 & 0x03U) << 4U]; } else if (rst == 2U) { uint8_t _2 = *src_cur++; uint8_t _1 = *src_cur++; *dst_cur++ = base64_mapping[ _2 >> 2U]; *dst_cur++ = base64_mapping[(_2 & 0x03U) << 4U | (_1 & 0xF0U) >> 4U]; *dst_cur++ = base64_mapping[(_1 & 0x0FU) << 2U]; } /* padding */ switch (rst) { case 1U: *dst_cur++ = base64_padding; /* fallthrough */ case 2U: *dst_cur++ = base64_padding; /* fallthrough */ default: *dst_cur = 0; break; } return static_cast(dst_cur - dst_beg); } int base64::icvCalcStructSize(const char *dt, int initial_size) { int size = cv::fs::calcElemSize( dt, initial_size ); size_t elem_max_size = 0; for ( const char * type = dt; *type != '\0'; type++ ) { switch ( *type ) { case 'u': { elem_max_size = std::max( elem_max_size, sizeof(uchar ) ); break; } case 'c': { elem_max_size = std::max( elem_max_size, sizeof(schar ) ); break; } case 'w': { elem_max_size = std::max( elem_max_size, sizeof(ushort) ); break; } case 's': { elem_max_size = std::max( elem_max_size, sizeof(short ) ); break; } case 'i': { elem_max_size = std::max( elem_max_size, sizeof(int ) ); break; } case 'f': { elem_max_size = std::max( elem_max_size, sizeof(float ) ); break; } case 'd': { elem_max_size = std::max( elem_max_size, sizeof(double) ); break; } default: break; } } size = cvAlign( size, static_cast(elem_max_size) ); return size; } size_t base64::base64_encode_buffer_size(size_t cnt, bool is_end_with_zero) { size_t additional = static_cast(is_end_with_zero == true); return (cnt + 2U) / 3U * 4U + additional; } base64::Base64Writer::Base64Writer(cv::FileStorage::Impl& fs, bool can_indent) : emitter(new Base64ContextEmitter(fs, can_indent)) , data_type_string() { CV_Assert(fs.write_mode); } void base64::Base64Writer::write(const void* _data, size_t len, const char* dt) { check_dt(dt); RawDataToBinaryConvertor convertor(_data, static_cast(len), data_type_string); emitter->write(convertor); } template inline void base64::Base64Writer::write(_to_binary_convertor_t & convertor, const char* dt) { check_dt(dt); emitter->write(convertor); } base64::Base64Writer::~Base64Writer() { delete emitter; } void base64::Base64Writer::check_dt(const char* dt) { if ( dt == 0 ) CV_Error( cv::Error::StsBadArg, "Invalid \'dt\'." ); else if (data_type_string.empty()) { data_type_string = dt; /* output header */ std::string buffer = make_base64_header(dt); const uchar * beg = reinterpret_cast(buffer.data()); const uchar * end = beg + buffer.size(); emitter->write(beg, end); } else if ( data_type_string != dt ) CV_Error( cv::Error::StsBadArg, "\'dt\' does not match." ); } base64::RawDataToBinaryConvertor::RawDataToBinaryConvertor(const void* src, int len, const std::string & dt) : beg(reinterpret_cast(src)) , cur(0) , end(0) { CV_Assert(src); CV_Assert(!dt.empty()); CV_Assert(len > 0); /* calc step and to_binary_funcs */ step_packed = make_to_binary_funcs(dt); end = beg; cur = beg; step = icvCalcStructSize(dt.c_str(), 0); end = beg + static_cast(len); } inline base64::RawDataToBinaryConvertor& base64::RawDataToBinaryConvertor::operator >>(uchar * & dst) { CV_DbgAssert(*this); for (size_t i = 0U, n = to_binary_funcs.size(); i < n; i++) { elem_to_binary_t & pack = to_binary_funcs[i]; pack.func(cur + pack.offset, dst + pack.offset_packed); } cur += step; dst += step_packed; return *this; } inline base64::RawDataToBinaryConvertor::operator bool() const { return cur < end; } size_t base64::RawDataToBinaryConvertor::make_to_binary_funcs(const std::string &dt) { size_t cnt = 0; size_t offset = 0; size_t offset_packed = 0; char type = '\0'; std::istringstream iss(dt); while (!iss.eof()) { if (!(iss >> cnt)) { iss.clear(); cnt = 1; } CV_Assert(cnt > 0U); if (!(iss >> type)) break; while (cnt-- > 0) { elem_to_binary_t pack; size_t size = 0; switch (type) { case 'u': case 'c': size = sizeof(uchar); pack.func = to_binary; break; case 'w': case 's': size = sizeof(ushort); pack.func = to_binary; break; case 'i': size = sizeof(uint); pack.func = to_binary; break; case 'f': size = sizeof(float); pack.func = to_binary; break; case 'd': size = sizeof(double); pack.func = to_binary; break; case 'r': default: CV_Error(cv::Error::StsError, "type is not supported"); }; offset = static_cast(cvAlign(static_cast(offset), static_cast(size))); pack.offset = offset; offset += size; pack.offset_packed = offset_packed; offset_packed += size; to_binary_funcs.push_back(pack); } } CV_Assert(iss.eof()); return offset_packed; } }