File indexing completed on 2024-09-15 04:36:24

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  *
0006  */
0007 
0008 #pragma once
0009 
0010 #include <chrono>
0011 #include <type_traits>
0012 
0013 #include "akonadiprivate_export.h"
0014 #include "protocol_exception_p.h"
0015 
0016 #include <QByteArray>
0017 #include <QIODevice>
0018 #include <QTimeZone>
0019 
0020 namespace Akonadi::Protocol
0021 {
0022 
0023 class AKONADIPRIVATE_EXPORT DataStream
0024 {
0025 public:
0026     explicit DataStream();
0027     explicit DataStream(QIODevice *device);
0028     ~DataStream();
0029 
0030     static void waitForData(QIODevice *device, int timeoutMs);
0031 
0032     QIODevice *device() const;
0033     void setDevice(QIODevice *device);
0034 
0035     std::chrono::milliseconds waitTimeout() const;
0036     void setWaitTimeout(std::chrono::milliseconds timeout);
0037 
0038     void flush();
0039 
0040     template<typename T>
0041         requires(std::is_integral_v<T>)
0042     inline DataStream &operator<<(T val);
0043     template<typename T>
0044         requires(std::is_enum_v<T>)
0045     inline DataStream &operator<<(T val);
0046 
0047     inline DataStream &operator<<(const QString &str);
0048     inline DataStream &operator<<(const QByteArray &data);
0049     inline DataStream &operator<<(const QDateTime &dt);
0050 
0051     template<typename T>
0052         requires(std::is_integral_v<T>)
0053     inline DataStream &operator>>(T &val);
0054     template<typename T>
0055         requires(std::is_enum_v<T>)
0056     inline DataStream &operator>>(T &val);
0057     inline DataStream &operator>>(QString &str);
0058     inline DataStream &operator>>(QByteArray &data);
0059     inline DataStream &operator>>(QDateTime &dt);
0060 
0061     void writeRawData(const char *data, qsizetype len);
0062     void writeBytes(const char *bytes, qsizetype len);
0063 
0064     [[nodiscard]] qint64 readRawData(char *buffer, qint64 len);
0065 
0066     void waitForData(quint32 size);
0067 
0068 private:
0069     Q_DISABLE_COPY(DataStream)
0070 
0071     inline void checkDevice() const
0072     {
0073         if (Q_UNLIKELY(!mDev)) {
0074             throw ProtocolException("Device does not exist");
0075         }
0076     }
0077 
0078     QIODevice *mDev;
0079     QByteArray mWriteBuffer;
0080     std::chrono::milliseconds mWaitTimeout = std::chrono::seconds{30};
0081 };
0082 
0083 template<typename T>
0084     requires(std::is_integral_v<T>)
0085 inline DataStream &DataStream::operator<<(T val)
0086 {
0087     checkDevice();
0088     writeRawData((char *)&val, sizeof(T));
0089     return *this;
0090 }
0091 
0092 template<typename T>
0093     requires(std::is_enum_v<T>)
0094 inline DataStream &DataStream::operator<<(T val)
0095 {
0096     return *this << (typename std::underlying_type<T>::type)val;
0097 }
0098 
0099 inline DataStream &DataStream::operator<<(const QString &str)
0100 {
0101     if (str.isNull()) {
0102         *this << (quint32)0xffffffff;
0103     } else {
0104         writeBytes(reinterpret_cast<const char *>(str.unicode()), sizeof(QChar) * str.length());
0105     }
0106     return *this;
0107 }
0108 
0109 inline DataStream &DataStream::operator<<(const QByteArray &data)
0110 {
0111     if (data.isNull()) {
0112         *this << (quint32)0xffffffff;
0113     } else {
0114         writeBytes(data.constData(), data.size());
0115     }
0116     return *this;
0117 }
0118 
0119 inline DataStream &DataStream::operator<<(const QDateTime &dt)
0120 {
0121     *this << dt.date().toJulianDay() << dt.time().msecsSinceStartOfDay() << dt.timeSpec();
0122     if (dt.timeSpec() == Qt::OffsetFromUTC) {
0123         *this << dt.offsetFromUtc();
0124     } else if (dt.timeSpec() == Qt::TimeZone) {
0125         *this << dt.timeZone().id();
0126     }
0127     return *this;
0128 }
0129 
0130 template<typename T>
0131     requires(std::is_integral_v<T>)
0132 inline DataStream &DataStream::operator>>(T &val)
0133 {
0134     checkDevice();
0135 
0136     waitForData(sizeof(T));
0137 
0138     if (mDev->read((char *)&val, sizeof(T)) != sizeof(T)) {
0139         throw Akonadi::ProtocolException("Failed to read enough data from stream");
0140     }
0141     return *this;
0142 }
0143 
0144 template<typename T>
0145     requires(std::is_enum_v<T>)
0146 inline DataStream &DataStream::operator>>(T &val)
0147 {
0148     return *this >> reinterpret_cast<typename std::underlying_type<T>::type &>(val);
0149 }
0150 
0151 inline DataStream &DataStream::operator>>(QString &str)
0152 {
0153     str.clear();
0154 
0155     quint32 bytes = 0;
0156     *this >> bytes;
0157     if (bytes == 0xffffffff) {
0158         return *this;
0159     } else if (bytes == 0) {
0160         str = QString(QLatin1StringView(""));
0161         return *this;
0162     }
0163 
0164     if (bytes & 0x1) {
0165         str.clear();
0166         throw Akonadi::ProtocolException("Read corrupt data");
0167     }
0168 
0169     const quint32 step = 1024 * 1024;
0170     const quint32 len = bytes / 2;
0171     quint32 allocated = 0;
0172 
0173     while (allocated < len) {
0174         const int blockSize = qMin(step, len - allocated);
0175         waitForData(blockSize * sizeof(QChar));
0176         str.resize(allocated + blockSize);
0177         if (readRawData(reinterpret_cast<char *>(str.data()) + allocated * sizeof(QChar), blockSize * sizeof(QChar)) != int(blockSize * sizeof(QChar))) {
0178             throw Akonadi::ProtocolException("Failed to read enough data from stream");
0179         }
0180         allocated += blockSize;
0181     }
0182 
0183     return *this;
0184 }
0185 
0186 inline DataStream &DataStream::operator>>(QByteArray &data)
0187 {
0188     data.clear();
0189 
0190     quint32 len = 0;
0191     *this >> len;
0192     if (len == 0xffffffff) {
0193         return *this;
0194     }
0195 
0196     const quint32 step = 1024 * 1024;
0197     quint32 allocated = 0;
0198 
0199     while (allocated < len) {
0200         const int blockSize = qMin(step, len - allocated);
0201         waitForData(blockSize);
0202         data.resize(allocated + blockSize);
0203         if (readRawData(data.data() + allocated, blockSize) != blockSize) {
0204             throw Akonadi::ProtocolException("Failed to read enough data from stream");
0205         }
0206         allocated += blockSize;
0207     }
0208 
0209     return *this;
0210 }
0211 
0212 inline DataStream &DataStream::operator>>(QDateTime &dt)
0213 {
0214     QDate date;
0215     QTime time;
0216     qint64 jd;
0217     int mds;
0218     Qt::TimeSpec spec;
0219 
0220     *this >> jd >> mds >> spec;
0221     date = QDate::fromJulianDay(jd);
0222     time = QTime::fromMSecsSinceStartOfDay(mds);
0223     if (spec == Qt::OffsetFromUTC) {
0224         int offset = 0;
0225         *this >> offset;
0226         dt = QDateTime(date, time, spec, offset);
0227     } else if (spec == Qt::TimeZone) {
0228         QByteArray id;
0229         *this >> id;
0230         dt = QDateTime(date, time, QTimeZone(id));
0231     } else {
0232         dt = QDateTime(date, time, spec);
0233     }
0234 
0235     return *this;
0236 }
0237 
0238 } // namespace Akonadi::Protocol
0239 
0240 // Inline functions
0241 
0242 template<typename T>
0243 inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, QFlags<T> flags)
0244 {
0245     return stream << static_cast<typename QFlags<T>::Int>(flags);
0246 }
0247 
0248 template<typename T>
0249 inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, QFlags<T> &flags)
0250 {
0251     stream >> reinterpret_cast<typename QFlags<T>::Int &>(flags);
0252     return stream;
0253 }
0254 
0255 // Generic streaming for all Qt value-based containers (as well as std containers that
0256 // implement operator<< for appending)
0257 template<typename T, template<typename> class Container>
0258 // typename std::enable_if<is_compatible_value_container<Container>::value, Akonadi::Protocol::DataStream>::type
0259 inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Container<T> &list)
0260 {
0261     stream << (quint32)list.size();
0262     for (auto iter = list.cbegin(), end = list.cend(); iter != end; ++iter) {
0263         stream << *iter;
0264     }
0265     return stream;
0266 }
0267 
0268 template<typename T, template<typename> class Container>
0269 // //typename std::enable_if<is_compatible_container<Container>::value, Akonadi::Protocol::DataStream>::type
0270 inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Container<T> &list)
0271 {
0272     list.clear();
0273     quint32 size = 0;
0274     stream >> size;
0275     list.reserve(size);
0276     for (quint32 i = 0; i < size; ++i) {
0277         T t;
0278         stream >> t;
0279         list << t;
0280     }
0281     return stream;
0282 }
0283 
0284 namespace Akonadi::Protocol::Private
0285 {
0286 
0287 template<typename Key, typename Value, template<typename, typename> class Container>
0288 inline void container_reserve(Container<Key, Value> &container, int size)
0289 {
0290     container.reserve(size);
0291 }
0292 
0293 template<typename Key, typename Value>
0294 inline void container_reserve(QMap<Key, Value> &, int)
0295 {
0296     // noop
0297 }
0298 
0299 } // namespace Akonadi::Protocol::Private
0300 
0301 // Generic streaming for all Qt dictionary-based containers
0302 template<typename Key, typename Value, template<typename, typename> class Container>
0303 // typename std::enable_if<is_compatible_dictionary_container<Container>::value, Akonadi::Protocol::DataStream>::type
0304 inline Akonadi::Protocol::DataStream &operator<<(Akonadi::Protocol::DataStream &stream, const Container<Key, Value> &map)
0305 {
0306     stream << (quint32)map.size();
0307     auto iter = map.cend(), begin = map.cbegin();
0308     while (iter != begin) {
0309         --iter;
0310         stream << iter.key() << iter.value();
0311     }
0312     return stream;
0313 }
0314 
0315 template<typename Key, typename Value, template<typename, typename> class Container>
0316 // typename std::enable_if<is_compatible_dictionary_container<Container>::value, Akonadi::Protocol::DataStream>::type
0317 inline Akonadi::Protocol::DataStream &operator>>(Akonadi::Protocol::DataStream &stream, Container<Key, Value> &map)
0318 {
0319     map.clear();
0320     quint32 size = 0;
0321     stream >> size;
0322     Akonadi::Protocol::Private::container_reserve(map, size);
0323     for (quint32 i = 0; i < size; ++i) {
0324         Key key;
0325         Value value;
0326         stream >> key >> value;
0327         map.insert(key, value);
0328     }
0329     return stream;
0330 }