/* Copyright (c) 2005-2015, John Hurst All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*! \file KM_util.h \version $Id$ \brief Utility functions */ #ifndef _KM_UTIL_H_ #define _KM_UTIL_H_ #include "KM_memio.h" #include "KM_error.h" #include "KM_tai.h" #include #include namespace Kumu { extern bool dcpomatic_test; // The version number declaration and explanation are in ../configure.ac const char* Version(); // a class that represents the string form of a value template class IntPrinter : public std::string { KM_NO_COPY_CONSTRUCT(IntPrinter); IntPrinter(); protected: const char* m_format; char m_strbuf[SIZE]; public: IntPrinter(const char* format, T value) { assert(format); m_format = format; snprintf(m_strbuf, SIZE, m_format, value); } inline operator const char*() { return m_strbuf; } inline const char* c_str() { return m_strbuf; } inline const char* set_value(T value) { snprintf(m_strbuf, SIZE, m_format, value); return m_strbuf; } }; struct i8Printer : public IntPrinter { i8Printer(i8_t value) : IntPrinter("%hd", value) {} }; struct ui8Printer : public IntPrinter { ui8Printer(ui8_t value) : IntPrinter("%hu", value) {} }; struct i16Printer : public IntPrinter { i16Printer(i16_t value) : IntPrinter("%hd", value) {} }; struct ui16Printer : public IntPrinter { ui16Printer(ui16_t value) : IntPrinter("%hu", value) {} }; struct i32Printer : public IntPrinter { i32Printer(i32_t value) : IntPrinter("%d", value) {} }; struct ui32Printer : public IntPrinter { ui32Printer(ui32_t value) : IntPrinter("%u", value) {} }; #ifdef KM_WIN32 struct i64Printer : public IntPrinter { i64Printer(i64_t value) : IntPrinter("%I64d", value) {} }; struct ui64Printer : public IntPrinter { ui64Printer(ui64_t value) : IntPrinter("%I64u", value) {} }; #else struct i64Printer : public IntPrinter { i64Printer(i64_t value) : IntPrinter("%qd", value) {} }; struct ui64Printer : public IntPrinter { ui64Printer(ui64_t value) : IntPrinter("%qu", value) {} }; #endif // Convert NULL-terminated UTF-8 hexadecimal string to binary, returns 0 if // the binary buffer was large enough to hold the result. The output parameter // 'char_count' will contain the length of the converted string. If the output // buffer is too small or any of the pointer arguments are NULL, the subroutine // will return -1 and set 'char_count' to the required buffer size. No data will // be written to 'buf' if the subroutine fails. i32_t hex2bin(const char* str, byte_t* buf, ui32_t buf_len, ui32_t* char_count); // Convert a binary string to NULL-terminated UTF-8 hexadecimal, returns the buffer // if the output buffer was large enough to hold the result. If the output buffer // is too small or any of the pointer arguments are NULL, the subroutine will // return 0. // const char* bin2hex(const byte_t* bin_buf, ui32_t bin_len, char* str_buf, ui32_t str_len); const char* bin2UUIDhex(const byte_t* bin_buf, ui32_t bin_len, char* str_buf, ui32_t str_len); // same as above for base64 text i32_t base64decode(const char* str, byte_t* buf, ui32_t buf_len, ui32_t* char_count); const char* base64encode(const byte_t* bin_buf, ui32_t bin_len, char* str_buf, ui32_t str_len); // returns the length of a Base64 encoding of a buffer of the given length inline ui32_t base64_encode_length(ui32_t length) { while ( ( length % 3 ) != 0 ) length++; return ( length / 3 ) * 4; } // print buffer contents to a stream as hexadecimal values in numbered // rows of 16-bytes each. // void hexdump(const byte_t* buf, ui32_t dump_len, FILE* stream = 0); // Return the length in bytes of a BER encoded value inline ui32_t BER_length(const byte_t* buf) { if ( buf == 0 || (*buf & 0xf0) != 0x80 ) return 0; return (*buf & 0x0f) + 1; } // Return the BER length required to encode value. A return value of zero // indicates a value too large for this library. ui32_t get_BER_length_for_value(ui64_t valuse); // read a BER value bool read_BER(const byte_t* buf, ui64_t* val); // decode a ber value and compare it to a test value bool read_test_BER(byte_t **buf, ui64_t test_value); // create BER encoding of integer value bool write_BER(byte_t* buf, ui64_t val, ui32_t ber_len = 0); //---------------------------------------------------------------- // // an abstract base class that objects implement to serialize state // to and from a binary stream. class IArchive { public: virtual ~IArchive(){} virtual bool HasValue() const = 0; virtual ui32_t ArchiveLength() const = 0; virtual bool Archive(MemIOWriter* Writer) const = 0; virtual bool Unarchive(MemIOReader* Reader) = 0; }; // template class ArchivableList : public std::list, public IArchive { public: ArchivableList() {} virtual ~ArchivableList() {} bool HasValue() const { return ! this->empty(); } ui32_t ArchiveLength() const { ui32_t arch_size = sizeof(ui32_t); typename ArchivableList::const_iterator i = this->begin(); for ( ; i != this->end(); i++ ) arch_size += i->ArchiveLength(); return arch_size; } bool Unarchive(Kumu::MemIOReader* Reader) { if ( Reader == 0 ) return false; ui32_t read_size = 0; if ( ! Reader->ReadUi32BE(&read_size) ) return false; for ( ui32_t i = 0; i < read_size; i++ ) { T TmpTP; if ( ! TmpTP.Unarchive(Reader) ) return false; this->push_back(TmpTP); } return true; } bool Archive(Kumu::MemIOWriter* Writer) const { if ( Writer == 0 ) return false; if ( ! Writer->WriteUi32BE(static_cast(this->size())) ) return false; typename ArchivableList::const_iterator i = this->begin(); for ( ; i != this->end(); i++ ) if ( ! i->Archive(Writer) ) return false; return true; } }; // archivable version of std::string // class ArchivableString : public std::string, public Kumu::IArchive { public: ArchivableString() {} ArchivableString(const char* sz) : std::string(sz) {} ArchivableString(const std::string& s) : std::string(s) {} virtual ~ArchivableString() {} bool HasValue() const { return ! this->empty(); } ui32_t ArchiveLength() const { return sizeof(ui32_t) + static_cast(this->size()); } bool Archive(MemIOWriter* Writer) const { if ( Writer == 0 ) return false; return Writer->WriteString(*this); } bool Unarchive(MemIOReader* Reader) { if ( Reader == 0 ) return false; return Reader->ReadString(*this); } }; // class ArchivableUi16 : public Kumu::IArchive { public: ui16_t value; ArchivableUi16() : value(0) {} ArchivableUi16(const ui16_t& val) : value(val) {} virtual ~ArchivableUi16() {} bool HasValue() const { return true; } ui32_t ArchiveLength() const { return sizeof(ui16_t); } operator ui16_t() const { return value; } bool Archive(MemIOWriter* Writer) const { if ( Writer == 0 ) return false; return Writer->WriteUi16BE(value); } bool Unarchive(MemIOReader* Reader) { if ( Reader == 0 ) return false; return Reader->ReadUi16BE(&value); } const char* EncodeString(char* str_buf, ui32_t buf_len) const { snprintf(str_buf, buf_len, "%hu", value); return str_buf; } }; // typedef Kumu::ArchivableList StringList; // // the base of all identifier classes, Identifier is not usually used directly // see UUID and SymmetricKey below for more detail. // template class Identifier : public IArchive { protected: bool m_HasValue; byte_t m_Value[SIZE]; public: Identifier() : m_HasValue(false) { memset(m_Value, 0, SIZE); } Identifier(const byte_t* value) : m_HasValue(true) { memcpy(m_Value, value, SIZE); } Identifier(const Identifier& rhs) : IArchive() { m_HasValue = rhs.m_HasValue; memcpy(m_Value, rhs.m_Value, SIZE); } virtual ~Identifier() {} const Identifier& operator=(const Identifier& rhs) { m_HasValue = rhs.m_HasValue; memcpy(m_Value, rhs.m_Value, SIZE); return *this; } inline void Set(const byte_t* value) { m_HasValue = true; memcpy(m_Value, value, SIZE); } inline void Reset() { m_HasValue = false; memset(m_Value, 0, SIZE); } inline const byte_t* Value() const { return m_Value; } inline ui32_t Size() const { return SIZE; } inline bool operator<(const Identifier& rhs) const { ui32_t test_size = xmin(rhs.Size(), SIZE); for ( ui32_t i = 0; i < test_size; i++ ) { if ( m_Value[i] != rhs.m_Value[i] ) return m_Value[i] < rhs.m_Value[i]; } return false; } inline bool operator>(const Identifier& rhs) const { ui32_t test_size = xmin(rhs.Size(), SIZE); for ( ui32_t i = 0; i < test_size; i++ ) { if ( m_Value[i] != rhs.m_Value[i] ) return m_Value[i] > rhs.m_Value[i]; } return false; } inline bool operator==(const Identifier& rhs) const { if ( rhs.Size() != SIZE ) return false; return ( memcmp(m_Value, rhs.m_Value, SIZE) == 0 ); } inline bool operator!=(const Identifier& rhs) const { if ( rhs.Size() != SIZE ) return true; return ( memcmp(m_Value, rhs.m_Value, SIZE) != 0 ); } inline bool DecodeHex(const char* str) { ui32_t char_count; m_HasValue = ( hex2bin(str, m_Value, SIZE, &char_count) == 0 ); if ( m_HasValue && char_count != SIZE ) m_HasValue = false; return m_HasValue; } inline const char* EncodeHex(char* buf, ui32_t buf_len) const { return bin2hex(m_Value, SIZE, buf, buf_len); } inline const char* EncodeString(char* str_buf, ui32_t buf_len) const { return EncodeHex(str_buf, buf_len); } inline bool DecodeBase64(const char* str) { ui32_t char_count; m_HasValue = ( base64decode(str, m_Value, SIZE, &char_count) == 0 ); if ( m_HasValue && char_count != SIZE ) m_HasValue = false; return m_HasValue; } inline const char* EncodeBase64(char* buf, ui32_t buf_len) const { return base64encode(m_Value, SIZE, buf, buf_len); } inline bool HasValue() const { return m_HasValue; } inline ui32_t ArchiveLength() const { return SIZE; } inline bool Unarchive(Kumu::MemIOReader* Reader) { m_HasValue = Reader->ReadRaw(m_Value, SIZE); return m_HasValue; } inline bool Archive(Kumu::MemIOWriter* Writer) const { return Writer->WriteRaw(m_Value, SIZE); } }; // UUID // const ui32_t UUID_Length = 16; class UUID : public Identifier { public: UUID() {} UUID(const byte_t* value) : Identifier(value) {} UUID(const UUID& rhs) : Identifier(rhs) {} virtual ~UUID() {} inline const char* EncodeString(char* buf, ui32_t buf_len) const { return bin2UUIDhex(m_Value, Size(), buf, buf_len); } inline const char* EncodeHex(char* buf, ui32_t buf_len) const { return bin2UUIDhex(m_Value, Size(), buf, buf_len); } }; void GenRandomUUID(byte_t* buf); // buf must be UUID_Length or longer void GenRandomValue(UUID&); void ResetTestRNG(); typedef ArchivableList UUIDList; // a self-wiping key container // const ui32_t SymmetricKey_Length = 16; const byte_t NilKey[SymmetricKey_Length] = { 0xfa, 0xce, 0xfa, 0xce, 0xfa, 0xce, 0xfa, 0xce, 0xfa, 0xce, 0xfa, 0xce, 0xfa, 0xce, 0xfa, 0xce }; class SymmetricKey : public Identifier { public: SymmetricKey() {} SymmetricKey(const byte_t* value) : Identifier(value) {} SymmetricKey(const UUID& rhs) : Identifier(rhs) {} virtual ~SymmetricKey() { memcpy(m_Value, NilKey, 16); m_HasValue = false; } }; void GenRandomValue(SymmetricKey&); // // 2004-05-01T13:20:00+00:00 const ui32_t DateTimeLen = 25; // the number of chars in the xs:dateTime format (sans milliseconds) // UTC time+date representation class Timestamp : public IArchive { TAI::tai m_Timestamp; // always UTC i32_t m_TZOffsetMinutes; public: Timestamp(); Timestamp(const Timestamp& rhs); Timestamp(const char* datestr); Timestamp(const ui16_t& Year, const ui8_t& Month, const ui8_t& Day); Timestamp(const ui16_t& Year, const ui8_t& Month, const ui8_t& Day, const ui8_t& Hour, const ui8_t& Minute, const ui8_t& Second); virtual ~Timestamp(); const Timestamp& operator=(const Timestamp& rhs); bool operator<(const Timestamp& rhs) const; bool operator>(const Timestamp& rhs) const; bool operator==(const Timestamp& rhs) const; bool operator!=(const Timestamp& rhs) const; // always UTC void GetComponents(ui16_t& Year, ui8_t& Month, ui8_t& Day, ui8_t& Hour, ui8_t& Minute, ui8_t& Second) const; void SetComponents(const ui16_t& Year, const ui8_t& Month, const ui8_t& Day, const ui8_t& Hour, const ui8_t& Minute, const ui8_t& Second); // Write the timestamp value to the given buffer in the form 2004-05-01T13:20:00+00:00 // returns 0 if the buffer is smaller than DateTimeLen const char* EncodeString(char* str_buf, ui32_t buf_len) const; // decode and set value from string formatted by EncodeString bool DecodeString(const char* datestr); // Add the given number of days, hours, minutes, or seconds to the timestamp value. // Values less than zero will cause the timestamp to decrease inline void AddDays(const i32_t& d) { m_Timestamp.add_days(d); } inline void AddHours(const i32_t& h) { m_Timestamp.add_hours(h); } inline void AddMinutes(const i32_t& m) { m_Timestamp.add_minutes(m); } inline void AddSeconds(const i32_t& s) { m_Timestamp.add_seconds(s); } // returns false if the requested adjustment is out of range bool SetTZOffsetMinutes(const i32_t& minutes); inline i32_t GetTZOffsetMinutes() const { return m_TZOffsetMinutes; } // Return the number of seconds since the Unix epoch UTC (1970-01-01T00:00:00+00:00) ui64_t GetCTime() const; // Set internal time to the number of seconds since the Unix epoch UTC void SetCTime(const ui64_t& ctime); // Read and write the timestamp (always UTC) value as a byte string having // the following format: // | 16 bits int, big-endian | 8 bits | 8 bits | 8 bits | 8 bits | 8 bits | // | Year A.D | Month(1-12) | Day(1-31) | Hour(0-23) | Minute(0-59) | Second(0-59) | // virtual bool HasValue() const; virtual ui32_t ArchiveLength() const { return 8L; } virtual bool Archive(MemIOWriter* Writer) const; virtual bool Unarchive(MemIOReader* Reader); }; // class ByteString : public IArchive { protected: byte_t* m_Data; // pointer to memory area containing frame data ui32_t m_Capacity; // size of memory area pointed to by m_Data ui32_t m_Length; // length of byte string in memory area pointed to by m_Data public: ByteString(); ByteString(const ByteString& rhs) { Copy(rhs); } ByteString(ui32_t cap); virtual ~ByteString(); const ByteString& operator=(const ByteString& rhs) { Copy(rhs); return *this; } void Copy(const ByteString& rhs) { if ( KM_SUCCESS(Capacity(rhs.Length())) ) { Set(rhs); } } // Sets or resets the size of the internally allocated buffer. Result_t Capacity(ui32_t cap); Result_t Append(const ByteString&); Result_t Append(const byte_t* buf, ui32_t buf_len); // returns the size of the buffer inline ui32_t Capacity() const { return m_Capacity; } // returns a const pointer to the essence data inline const byte_t* RoData() const { assert(m_Data); return m_Data; } // returns a non-const pointer to the essence data inline byte_t* Data() { assert(m_Data); return m_Data; } // set the length of the buffer's contents inline ui32_t Length(ui32_t l) { return m_Length = l; } // returns the length of the buffer's contents inline ui32_t Length() const { return m_Length; } // copy the given data into the ByteString, set Length value. // Returns error if the ByteString is too small. Result_t Set(const byte_t* buf, ui32_t buf_len); Result_t Set(const ByteString& Buf); inline virtual bool HasValue() const { return m_Length > 0; } inline virtual ui32_t ArchiveLength() const { return sizeof(ui32_t) + m_Length; } inline virtual bool Archive(MemIOWriter* Writer) const { assert(Writer); if ( ! Writer->WriteUi32BE(m_Length) ) return false; if ( ! Writer->WriteRaw(m_Data, m_Length) ) return false; return true; } inline virtual bool Unarchive(MemIOReader* Reader) { assert(Reader); ui32_t tmp_len; if ( ! Reader->ReadUi32BE(&tmp_len) ) return false; if ( KM_FAILURE(Capacity(tmp_len)) ) return false; if ( ! Reader->ReadRaw(m_Data, tmp_len) ) return false; m_Length = tmp_len; return true; } }; inline void hexdump(const ByteString& buf, FILE* stream = 0) { hexdump(buf.RoData(), buf.Length(), stream); } // Locates the first occurrence of the null-terminated string s2 in the string s1, where not more // than n characters are searched. Characters that appear after a `\0' character are not searched. // Reproduced here from BSD for portability. const char *km_strnstr(const char *s1, const char *s2, size_t n); // Split the input string into tokens using the given separator. If the separator is not found the // entire string will be returned as a single-item list. Empty items will be recorded for // adjacent instances of the separator. E.g., "/foo//bar/" will return ["", "foo", "", "bar", ""]. std::list km_token_split(const std::string& str, const std::string& separator); // Join the tokens in the given list using delimiter. If prefix is defined then each token // will be concatenated with the prefix before being added to the composite string. template std::string km_join(const T& list, const std::string& delimiter, const std::string& prefix = "") { std::string result; for ( typename T::const_iterator i = list.begin(); i != list.end(); ++i ) { if ( i != list.begin() ) { result += delimiter; } result += prefix + *i; } return result; } } // namespace Kumu #endif // _KM_UTIL_H_ // // end KM_util.h //