summaryrefslogtreecommitdiff
path: root/asdcplib/src/KM_util.cpp
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2015-01-14 17:39:32 +0000
committerCarl Hetherington <cth@carlh.net>2015-01-20 11:20:25 +0000
commit3f630fb8334238ab8a58fbe1a0f513ae2c00de80 (patch)
tree4b773b91029d6374bfd4f2194053d3e249d597cd /asdcplib/src/KM_util.cpp
parent49cafda01b3e07c47e3b20dd5ee91e1426446aea (diff)
Simplify time representation; better in-tree DCP subtitle parser.
Diffstat (limited to 'asdcplib/src/KM_util.cpp')
-rwxr-xr-xasdcplib/src/KM_util.cpp1141
1 files changed, 1141 insertions, 0 deletions
diff --git a/asdcplib/src/KM_util.cpp b/asdcplib/src/KM_util.cpp
new file mode 100755
index 0000000..b181484
--- /dev/null
+++ b/asdcplib/src/KM_util.cpp
@@ -0,0 +1,1141 @@
+/*
+Copyright (c) 2005-2012, 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.cpp
+ \version $Id: KM_util.cpp,v 1.40 2012/02/22 19:20:33 jhurst Exp $
+ \brief Utility functions
+ */
+
+#include <KM_util.h>
+#include <KM_prng.h>
+#include <KM_memio.h>
+#include <KM_fileio.h>
+#include <KM_log.h>
+#include <KM_mutex.h>
+#include <ctype.h>
+#include <list>
+#include <map>
+#include <string>
+
+bool Kumu::libdcp_test = false;
+
+const char*
+Kumu::Version()
+{
+ return PACKAGE_VERSION;
+}
+
+
+//------------------------------------------------------------------------------------------
+
+// Result_t Internals
+
+struct map_entry_t
+{
+ int rcode;
+ Kumu::Result_t* result;
+};
+
+
+// WIN32 does not init this in time for use with Result_t(...) below, so it is
+// now a pointer that Result_t(...) fills in when it needs it.
+static Kumu::Mutex* s_MapLock = 0;
+
+static ui32_t s_MapSize = 0;
+static const ui32_t MapMax = 2048;
+static struct map_entry_t s_ResultMap[MapMax];
+
+
+//
+const Kumu::Result_t&
+Kumu::Result_t::Find(int v)
+{
+ if ( v == 0 )
+ return RESULT_OK;
+
+ assert(s_MapLock);
+ AutoMutex L(*s_MapLock);
+
+ for ( ui32_t i = 0; i < s_MapSize; ++i )
+ {
+ if ( s_ResultMap[i].rcode == v )
+ return *s_ResultMap[i].result;
+ }
+
+ return RESULT_UNKNOWN;
+}
+
+//
+Kumu::Result_t
+Kumu::Result_t::Delete(int v)
+{
+ if ( v < -99 || v > 99 )
+ {
+ DefaultLogSink().Error("Cannot delete core result code: %ld\n", v);
+ return RESULT_FAIL;
+ }
+
+ assert(s_MapLock);
+ AutoMutex L(*s_MapLock);
+
+ for ( ui32_t i = 0; i < s_MapSize; ++i )
+ {
+ if ( s_ResultMap[i].rcode == v )
+ {
+ for ( ++i; i < s_MapSize; ++i )
+ s_ResultMap[i-1] = s_ResultMap[i];
+
+ --s_MapSize;
+ return RESULT_OK;
+ }
+ }
+
+ return RESULT_FALSE;
+}
+
+//
+unsigned int
+Kumu::Result_t::End()
+{
+ return s_MapSize;
+}
+
+//
+const Kumu::Result_t&
+Kumu::Result_t::Get(unsigned int i)
+{
+ return *s_ResultMap[i].result;
+}
+
+//
+Kumu::Result_t::Result_t(int v, const char* s, const char* l) : value(v), label(l), symbol(s)
+{
+ assert(l);
+ assert(s);
+
+ if ( v == 0 )
+ return;
+
+ // This may seem tricky, but it is certain that the static values declared in KM_error.h will
+ // be created (and thus this method will be called) before main(...) is called. It is not
+ // until then that threads could be created, thus the mutex will exist before multi-threaded
+ // access could occur.
+ if ( s_MapLock == 0 )
+ s_MapLock = new Kumu::Mutex;
+
+ assert(s_MapLock);
+ AutoMutex L(*s_MapLock);
+
+ for ( ui32_t i = 0; i < s_MapSize; ++i )
+ {
+ if ( s_ResultMap[i].rcode == v )
+ return;
+ }
+
+ assert(s_MapSize+1 < MapMax);
+
+ s_ResultMap[s_MapSize].rcode = v;
+ s_ResultMap[s_MapSize].result = this;
+ ++s_MapSize;
+
+ return;
+}
+
+Kumu::Result_t::~Result_t() {}
+
+
+//------------------------------------------------------------------------------------------
+// DTrace internals
+
+static int s_DTraceSequence = 0;
+
+Kumu::DTrace_t::DTrace_t(const char* Label, Kumu::Result_t* Watch, int Line, const char* File)
+ : m_Label(Label), m_Watch(Watch), m_Line(Line), m_File(File)
+{
+ m_Sequence = s_DTraceSequence++;
+ DefaultLogSink().Debug("@enter %s[%d] (%s at %d)\n", m_Label, m_Sequence, m_File, m_Line);
+}
+
+Kumu::DTrace_t::~DTrace_t()
+{
+ if ( m_Watch != 0 )
+ DefaultLogSink().Debug("@exit %s[%d]: %s\n", m_Label, m_Sequence, m_Watch->Label());
+ else
+ DefaultLogSink().Debug("@exit %s[%d]\n", m_Label, m_Sequence);
+}
+
+//------------------------------------------------------------------------------------------
+
+
+const char fill = '=';
+const char* base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+const byte_t decode_map[] =
+{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 62, 0xff, 0xff, 0xff, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff,
+ 0xff, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+
+// Convert a binary string to NULL-terminated UTF-8 hexadecimal, returns the buffer
+// if the binary 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*
+Kumu::base64encode(const byte_t* buf, ui32_t buf_len, char* strbuf, ui32_t strbuf_len)
+{
+ ui32_t out_char = 0;
+ ui32_t i, block_len, diff;
+
+ if ( buf == 0 || strbuf == 0 )
+ return 0;
+
+ if ( strbuf_len < base64_encode_length(buf_len) + 1 )
+ return 0;
+
+ block_len = buf_len;
+
+ while ( block_len % 3 )
+ block_len--;
+
+ for ( i = 0; i < block_len; i += 3 )
+ {
+ strbuf[out_char++] = base64_chars[( buf[0] >> 2 )];
+ strbuf[out_char++] = base64_chars[( ( ( buf[0] & 0x03 ) << 4 ) | ( buf[1] >> 4 ) )];
+ strbuf[out_char++] = base64_chars[( ( ( buf[1] & 0x0f ) << 2 ) | ( buf[2] >> 6 ) )];
+ strbuf[out_char++] = base64_chars[( buf[2] & 0x3f )];
+ buf += 3;
+ }
+
+ if ( i < buf_len )
+ {
+ diff = buf_len - i;
+ assert(diff > 0);
+ assert(diff < 3);
+
+ strbuf[out_char++] = base64_chars[( buf[0] >> 2 )];
+
+ if ( diff == 1 )
+ {
+ strbuf[out_char++] = base64_chars[( ( ( buf[0] & 0x03 ) << 4 ) )];
+ strbuf[out_char++] = fill;
+ }
+ else if ( diff == 2 )
+ {
+ strbuf[out_char++] = base64_chars[( ( ( buf[0] & 0x03 ) << 4 ) | ( buf[1] >> 4 ) )];
+ strbuf[out_char++] = base64_chars[( ( ( buf[1] & 0x0f ) << 2 ) )];
+ }
+
+ strbuf[out_char++] = fill;
+ }
+
+ strbuf[out_char] = 0;
+ return strbuf;;
+}
+
+
+
+
+// Convert NULL-terminated UTF-8 Base64 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
+Kumu::base64decode(const char* str, byte_t* buf, ui32_t buf_len, ui32_t* char_count)
+{
+ register byte_t c = 0, d = 0;
+ register ui32_t phase = 0, i = 0;
+
+ if ( str == 0 || buf == 0 || char_count == 0 )
+ return -1;
+
+ while ( *str != 0 && i < buf_len )
+ {
+ c = decode_map[(int)*str++];
+ if ( c == 0xff ) continue;
+ if ( c == 0xfe ) break;
+
+ switch ( phase++ )
+ {
+ case 0:
+ buf[i++] = c << 2;
+ break;
+
+ case 1:
+ buf[i - 1] |= c >> 4;
+ d = c;
+ break;
+
+ case 2:
+ buf[i++] = ( d << 4 ) | ( c >> 2 );
+ d = c;
+ break;
+
+ case 3:
+ buf[i++] = ( d << 6 ) | c;
+ phase = 0;
+ break;
+ }
+ }
+
+ *char_count = i;
+ return 0;
+}
+
+//------------------------------------------------------------------------------------------
+
+// convert utf-8 hext string to bin
+i32_t
+Kumu::hex2bin(const char* str, byte_t* buf, ui32_t buf_len, ui32_t* conv_size)
+{
+ KM_TEST_NULL_L(str);
+ KM_TEST_NULL_L(buf);
+ KM_TEST_NULL_L(conv_size);
+
+ *conv_size = 0;
+
+ if ( str[0] == 0 ) // nothing to convert
+ return 0;
+
+ for ( int j = 0; str[j]; j++ )
+ {
+ if ( isxdigit(str[j]) )
+ (*conv_size)++;
+ }
+
+ if ( *conv_size & 0x01 ) (*conv_size)++;
+ *conv_size /= 2;
+
+ if ( *conv_size > buf_len )// maximum possible data size
+ return -1;
+
+ *conv_size = 0;
+
+ int phase = 0; // track high/low nybble
+
+ // for each character, fill in the high nybble then the low
+ for ( int i = 0; str[i]; i++ )
+ {
+ if ( ! isxdigit(str[i]) )
+ continue;
+
+ byte_t val = str[i] - ( isdigit(str[i]) ? 0x30 : ( isupper(str[i]) ? 0x37 : 0x57 ) );
+
+ if ( phase == 0 )
+ {
+ buf[*conv_size] = val << 4;
+ phase++;
+ }
+ else
+ {
+ buf[*conv_size] |= val;
+ phase = 0;
+ (*conv_size)++;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_RANDOM_UUID
+
+// convert a memory region to a NULL-terminated hexadecimal string
+//
+static const char*
+bin2hex_rand(const byte_t* bin_buf, ui32_t bin_len, char* str_buf, ui32_t str_len)
+{
+ if ( bin_buf == 0
+ || str_buf == 0
+ || ((bin_len * 2) + 1) > str_len )
+ return 0;
+
+ char* p = str_buf;
+ Kumu::mem_ptr<byte_t> rand_buf = new byte_t[bin_len];
+ Kumu::FortunaRNG RNG;
+ RNG.FillRandom(rand_buf, bin_len);
+
+ for ( ui32_t i = 0; i < bin_len; i++ )
+ {
+ *p = (bin_buf[i] >> 4) & 0x0f;
+ *p += *p < 10 ? 0x30 : (( ((rand_buf[i] & 0x01) == 0) ? 0x61 : 0x41 ) - 10);
+ p++;
+
+ *p = bin_buf[i] & 0x0f;
+ *p += *p < 10 ? 0x30 : (( (((rand_buf[i] >> 1) & 0x01) == 0) ? 0x61 : 0x41 ) - 10);
+ p++;
+ }
+
+ *p = '\0';
+ return str_buf;
+}
+#endif
+
+// convert a memory region to a NULL-terminated hexadecimal string
+//
+const char*
+Kumu::bin2hex(const byte_t* bin_buf, ui32_t bin_len, char* str_buf, ui32_t str_len)
+{
+ if ( bin_buf == 0
+ || str_buf == 0
+ || ((bin_len * 2) + 1) > str_len )
+ return 0;
+
+#ifdef CONFIG_RANDOM_UUID
+ const char* use_random_uuid = getenv("KM_USE_RANDOM_UUID");
+ if ( use_random_uuid != 0 && use_random_uuid[0] != 0 && use_random_uuid[0] != '0' )
+ return bin2hex_rand(bin_buf, bin_len, str_buf, str_len);
+#endif
+
+ char* p = str_buf;
+
+ for ( ui32_t i = 0; i < bin_len; i++ )
+ {
+ *p = (bin_buf[i] >> 4) & 0x0f;
+ *p += *p < 10 ? 0x30 : 0x61 - 10;
+ p++;
+
+ *p = bin_buf[i] & 0x0f;
+ *p += *p < 10 ? 0x30 : 0x61 - 10;
+ p++;
+ }
+
+ *p = '\0';
+ return str_buf;
+}
+
+
+// spew a range of bin data as hex
+void
+Kumu::hexdump(const byte_t* buf, ui32_t dump_len, FILE* stream)
+{
+ if ( buf == 0 )
+ return;
+
+ if ( stream == 0 )
+ stream = stderr;
+
+ static ui32_t row_len = 16;
+ const byte_t* p = buf;
+ const byte_t* end_p = p + dump_len;
+
+ for ( ui32_t line = 0; p < end_p; line++ )
+ {
+ fprintf(stream, " %06x: ", line);
+ ui32_t i;
+ const byte_t* pp;
+
+ for ( pp = p, i = 0; i < row_len && pp < end_p; i++, pp++ )
+ fprintf(stream, "%02x ", *pp);
+
+ while ( i++ < row_len )
+ fputs(" ", stream);
+
+ for ( pp = p, i = 0; i < row_len && pp < end_p; i++, pp++ )
+ fputc((isprint(*pp) ? *pp : '.'), stream);
+
+ fputc('\n', stream);
+ p += row_len;
+ }
+}
+
+//
+const char*
+Kumu::bin2UUIDhex(const byte_t* bin_buf, ui32_t bin_len, char* str_buf, ui32_t str_len)
+{
+ ui32_t i, j, k;
+
+ if ( str_len < 34 || bin_len != UUID_Length )
+ return 0;
+
+ if ( bin2hex(bin_buf, bin_len, str_buf, str_len) == 0 )
+ return 0;
+
+ // shift the node id
+ for ( k = 19, i = 12; i > 0; i-- )
+ str_buf[k+i+4] = str_buf[k+i];
+
+ // shift the time (mid+hi+clk)
+ for ( k = 15, j = 3; k > 6; k -= 4, j-- )
+ {
+ for ( i = 4; i > 0; i-- )
+ str_buf[k+i+j] = str_buf[k+i];
+ }
+
+ // add in the hyphens and trainling null
+ for ( i = 8; i < 24; i += 5 )
+ str_buf[i] = '-';
+
+ str_buf[36] = 0;
+ return str_buf;
+}
+
+//
+void
+Kumu::GenRandomValue(UUID& ID)
+{
+ byte_t tmp_buf[UUID_Length];
+ GenRandomUUID(tmp_buf);
+ ID.Set(tmp_buf);
+}
+
+#ifdef LIBDCP_POSIX
+void
+Kumu::ResetTestRNG()
+{
+ FortunaRNG RNG;
+ RNG.Reset();
+}
+#endif
+
+//
+void
+Kumu::GenRandomUUID(byte_t* buf)
+{
+ FortunaRNG RNG;
+ RNG.FillRandom(buf, UUID_Length);
+ buf[6] &= 0x0f; // clear bits 4-7
+ buf[6] |= 0x40; // set UUID version
+ buf[8] &= 0x3f; // clear bits 6&7
+ buf[8] |= 0x80; // set bit 7
+}
+
+//
+void
+Kumu::GenRandomValue(SymmetricKey& Key)
+{
+ byte_t tmp_buf[SymmetricKey_Length];
+ FortunaRNG RNG;
+ RNG.FillRandom(tmp_buf, SymmetricKey_Length);
+ Key.Set(tmp_buf);
+}
+
+
+//------------------------------------------------------------------------------------------
+// read a ber value from the buffer and compare with test value.
+// Advances buffer to first character after BER value.
+//
+bool
+Kumu::read_test_BER(byte_t **buf, ui64_t test_value)
+{
+ if ( buf == 0 )
+ return false;
+
+ if ( ( **buf & 0x80 ) == 0 )
+ return false;
+
+ ui64_t val = 0;
+ ui8_t ber_size = ( **buf & 0x0f ) + 1;
+
+ if ( ber_size > 9 )
+ return false;
+
+ for ( ui8_t i = 1; i < ber_size; i++ )
+ {
+ if ( (*buf)[i] > 0 )
+ val |= (ui64_t)((*buf)[i]) << ( ( ( ber_size - 1 ) - i ) * 8 );
+ }
+
+ *buf += ber_size;
+ return ( val == test_value );
+}
+
+
+//
+bool
+Kumu::read_BER(const byte_t* buf, ui64_t* val)
+{
+ ui8_t ber_size, i;
+
+ if ( buf == 0 || val == 0 )
+ return false;
+
+ if ( ( *buf & 0x80 ) == 0 )
+ return false;
+
+ *val = 0;
+ ber_size = ( *buf & 0x0f ) + 1;
+
+ if ( ber_size > 9 )
+ return false;
+
+ for ( i = 1; i < ber_size; i++ )
+ {
+ if ( buf[i] > 0 )
+ *val |= (ui64_t)buf[i] << ( ( ( ber_size - 1 ) - i ) * 8 );
+ }
+
+ return true;
+}
+
+
+static const ui64_t ber_masks[9] =
+ { ui64_C(0xffffffffffffffff), ui64_C(0xffffffffffffff00),
+ ui64_C(0xffffffffffff0000), ui64_C(0xffffffffff000000),
+ ui64_C(0xffffffff00000000), ui64_C(0xffffff0000000000),
+ ui64_C(0xffff000000000000), ui64_C(0xff00000000000000),
+ 0
+ };
+
+//
+ui32_t
+Kumu::get_BER_length_for_value(ui64_t val)
+{
+ for ( ui32_t i = 0; i < 9; i++ )
+ {
+ if ( ( val & ber_masks[i] ) == 0 )
+ return i + 1;
+ }
+
+ ui64Printer tmp_i(val);
+ DefaultLogSink().Error("BER integer encoding not supported for large value %s\n", tmp_i.c_str());
+ return 0;
+}
+
+//
+bool
+Kumu::write_BER(byte_t* buf, ui64_t val, ui32_t ber_len)
+{
+ if ( buf == 0 )
+ return false;
+
+ if ( ber_len == 0 )
+ { // calculate default length
+ if ( val < 0x01000000L )
+ ber_len = 4;
+ else if ( val < ui64_C(0x0100000000000000) )
+ ber_len = 8;
+ else
+ ber_len = 9;
+ }
+ else
+ { // sanity check BER length
+ if ( ber_len > 9 )
+ {
+ DefaultLogSink().Error("BER integer length %u exceeds maximum size of 9\n", ber_len);
+ return false;
+ }
+
+ if ( ( val & ber_masks[ber_len - 1] ) != 0 )
+ {
+ ui64Printer tmp_i(val);
+ DefaultLogSink().Error("BER integer length %u too small for value %s\n", ber_len, tmp_i.c_str());
+ return false;
+ }
+ }
+
+ buf[0] = 0x80 + ( ber_len - 1 );
+
+ for ( ui32_t i = ber_len - 1; i > 0; i-- )
+ {
+ buf[i] = (ui8_t)(val & 0xff);
+ val >>= 8;
+ }
+
+ return true;
+}
+
+
+//------------------------------------------------------------------------------------------
+
+#ifndef KM_WIN32
+#include <time.h>
+#endif
+
+//
+Kumu::Timestamp::Timestamp() : m_TZOffsetMinutes(0) {
+ if (libdcp_test)
+ {
+ m_Timestamp.x = 42;
+ }
+ else
+ {
+ m_Timestamp.now();
+ }
+}
+
+Kumu::Timestamp::Timestamp(const Timestamp& rhs) {
+ m_Timestamp = rhs.m_Timestamp;
+ m_TZOffsetMinutes = rhs.m_TZOffsetMinutes;
+}
+
+Kumu::Timestamp::Timestamp(const char* datestr) : m_TZOffsetMinutes(0) {
+ DecodeString(datestr);
+}
+
+Kumu::Timestamp::~Timestamp() {
+}
+
+//
+const Kumu::Timestamp&
+Kumu::Timestamp::operator=(const Timestamp& rhs)
+{
+ m_Timestamp = rhs.m_Timestamp;
+ m_TZOffsetMinutes = rhs.m_TZOffsetMinutes;
+ return *this;
+}
+
+bool Kumu::Timestamp::operator<(const Timestamp& rhs) const {
+ return m_Timestamp.x < rhs.m_Timestamp.x;
+}
+
+bool Kumu::Timestamp::operator>(const Timestamp& rhs) const {
+ return m_Timestamp.x > rhs.m_Timestamp.x;
+}
+
+bool Kumu::Timestamp::operator==(const Timestamp& rhs) const {
+ return m_Timestamp.x == rhs.m_Timestamp.x;
+}
+
+bool Kumu::Timestamp::operator!=(const Timestamp& rhs) const {
+ return m_Timestamp.x != rhs.m_Timestamp.x;
+}
+
+//
+void
+Kumu::Timestamp::GetComponents(ui16_t& Year, ui8_t& Month, ui8_t& Day,
+ ui8_t& Hour, ui8_t& Minute, ui8_t& Second) const
+{
+ TAI::caltime ct;
+ ct = m_Timestamp;
+ Year = ct.date.year;
+ Month = ct.date.month;
+ Day = ct.date.day;
+ Hour = ct.hour;
+ Minute = ct.minute;
+ Second = ct.second;
+}
+
+//
+void
+Kumu::Timestamp::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)
+{
+ TAI::caltime ct;
+ ct.date.year = Year;
+ ct.date.month = Month;
+ ct.date.day = Day;
+ ct.hour = Hour;
+ ct.minute = Minute;
+ ct.second = Second;
+ ct.offset = 0;
+ m_Timestamp = ct;
+ m_TZOffsetMinutes = 0;
+}
+
+// returns false if the requested adjustment is out of range
+bool
+Kumu::Timestamp::SetTZOffsetMinutes(const i32_t& minutes)
+{
+ static const i32_t tz_limit = 14 * 60 * 60;
+
+ if ( minutes < ( - tz_limit) || minutes > tz_limit )
+ return false;
+
+ m_TZOffsetMinutes = minutes;
+ return true;
+}
+
+//
+const char*
+Kumu::Timestamp::EncodeString(char* str_buf, ui32_t buf_len) const
+{
+ if ( buf_len < ( DateTimeLen + 1 ) )
+ return 0;
+
+ ui16_t year;
+ ui8_t month, day, hour, minute, second;
+ ui32_t ofst_hours = 0, ofst_minutes = 0;
+ char direction = '+';
+
+ if ( m_TZOffsetMinutes == 0 )
+ {
+ GetComponents(year, month, day, hour, minute, second);
+ }
+ else
+ {
+ // calculate local time
+ Kumu::Timestamp tmp_t(*this);
+ tmp_t.AddMinutes(m_TZOffsetMinutes);
+ tmp_t.GetComponents(year, month, day, hour, minute, second);
+
+ ofst_hours = abs(m_TZOffsetMinutes) / 60;
+ ofst_minutes = abs(m_TZOffsetMinutes) % 60;
+
+ if ( m_TZOffsetMinutes < 0 )
+ direction = '-';
+ }
+
+ // 2004-05-01T13:20:00+00:00
+ snprintf(str_buf, buf_len,
+ "%04hu-%02hu-%02huT%02hu:%02hu:%02hu%c%02hu:%02hu",
+ year, month, day, hour, minute, second,
+ direction, ofst_hours, ofst_minutes);
+
+ return str_buf;
+}
+
+//
+bool
+Kumu::Timestamp::DecodeString(const char* datestr)
+{
+ if ( ! ( isdigit(datestr[0]) && isdigit(datestr[1]) && isdigit(datestr[2]) && isdigit(datestr[3]) )
+ || datestr[4] != '-'
+ || ! ( isdigit(datestr[5]) && isdigit(datestr[6]) )
+ || datestr[7] != '-'
+ || ! ( isdigit(datestr[8]) && isdigit(datestr[9]) ) )
+ return false;
+
+ ui32_t char_count = 10;
+ TAI::caltime YMDhms;
+ YMDhms.offset = 0;
+ YMDhms.date.year = atoi(datestr);
+ YMDhms.date.month = atoi(datestr + 5);
+ YMDhms.date.day = atoi(datestr + 8);
+
+ if ( datestr[10] == 'T' )
+ {
+ if ( ! ( isdigit(datestr[11]) && isdigit(datestr[12]) )
+ || datestr[13] != ':'
+ || ! ( isdigit(datestr[14]) && isdigit(datestr[15]) ) )
+ return false;
+
+ char_count += 6;
+ YMDhms.hour = atoi(datestr + 11);
+ YMDhms.minute = atoi(datestr + 14);
+
+ if ( datestr[16] == ':' )
+ {
+ if ( ! ( isdigit(datestr[17]) && isdigit(datestr[18]) ) )
+ return false;
+
+ char_count += 3;
+ YMDhms.second = atoi(datestr + 17);
+ }
+
+ if ( datestr[19] == '.' )
+ {
+ if ( ! ( isdigit(datestr[20]) && isdigit(datestr[21]) && isdigit(datestr[22]) ) )
+ return false;
+
+ // we don't carry the ms value
+ datestr += 4;
+ }
+
+ if ( datestr[19] == '-' || datestr[19] == '+' )
+ {
+ if ( ! ( isdigit(datestr[20]) && isdigit(datestr[21]) )
+ || datestr[22] != ':'
+ || ! ( isdigit(datestr[23]) && isdigit(datestr[24]) ) )
+ return false;
+
+ char_count += 6;
+
+ ui32_t TZ_hh = atoi(datestr + 20);
+ ui32_t TZ_mm = atoi(datestr + 23);
+ if ((TZ_hh > 14) || (TZ_mm > 59) || ((TZ_hh == 14) && (TZ_mm > 0)))
+ return false;
+
+ i32_t TZ_offset = 60 * TZ_hh + TZ_mm;
+ if (datestr[19] == '-')
+ TZ_offset = -TZ_offset;
+ /* at this point, TZ_offset reflects the contents of the string */
+
+ /* a negative offset is behind UTC and so needs to increment to
+ * convert, while a positive offset must do the reverse */
+ YMDhms.offset = TZ_offset;
+ }
+ else if (datestr[19] == 'Z')
+ {
+ /* act as if the offset were +00:00 */
+ char_count++;
+ }
+ }
+
+ if ( datestr[char_count] != 0 )
+ {
+ Kumu::DefaultLogSink().Error("Unexpected extra characters in string: %s (%ld)\n",
+ datestr, char_count);
+ return false;
+ }
+
+ m_Timestamp = YMDhms;
+ m_TZOffsetMinutes = YMDhms.offset;
+ return true;
+}
+
+//
+bool
+Kumu::Timestamp::HasValue() const
+{
+ return true;
+}
+
+//
+bool
+Kumu::Timestamp::Unarchive(MemIOReader* Reader)
+{
+ ui16_t year;
+ ui8_t month, day, hour, minute, second, tick;
+
+ assert(Reader);
+ if ( ! Reader->ReadUi16BE(&year) ) return false;
+ if ( ! Reader->ReadUi8(&month) ) return false;
+ if ( ! Reader->ReadUi8(&day) ) return false;
+ if ( ! Reader->ReadUi8(&hour) ) return false;
+ if ( ! Reader->ReadUi8(&minute) ) return false;
+ if ( ! Reader->ReadUi8(&second) ) return false;
+ if ( ! Reader->ReadUi8(&tick) ) return false;
+ SetComponents(year, month, day, hour, minute, second);
+ return true;
+}
+
+//
+bool
+Kumu::Timestamp::Archive(MemIOWriter* Writer) const
+{
+ assert(Writer);
+
+ ui16_t year;
+ ui8_t month, day, hour, minute, second, tick = 0;
+ GetComponents(year, month, day, hour, minute, second);
+
+ if ( ! Writer->WriteUi16BE(year) ) return false;
+ if ( ! Writer->WriteUi8(month) ) return false;
+ if ( ! Writer->WriteUi8(day) ) return false;
+ if ( ! Writer->WriteUi8(hour) ) return false;
+ if ( ! Writer->WriteUi8(minute) ) return false;
+ if ( ! Writer->WriteUi8(second) ) return false;
+ if ( ! Writer->WriteUi8(tick) ) return false;
+ return true;
+}
+
+//
+ui64_t
+Kumu::Timestamp::GetCTime() const
+{
+ return m_Timestamp.x - ui64_C(4611686018427387914);
+}
+
+
+//------------------------------------------------------------------------------------------
+
+Kumu::MemIOWriter::MemIOWriter(ByteString* Buf)
+ : m_p(0), m_capacity(0), m_size(0)
+{
+ m_p = Buf->Data();
+ m_capacity = Buf->Capacity();
+ assert(m_p); assert(m_capacity);
+}
+
+bool
+Kumu::MemIOWriter:: WriteBER(ui64_t i, ui32_t ber_len)
+{
+ if ( ( m_size + ber_len ) > m_capacity )
+ return false;
+
+ if ( ! write_BER(m_p + m_size, i, ber_len) )
+ return false;
+
+ m_size += ber_len;
+ return true;
+}
+
+
+Kumu::MemIOReader::MemIOReader(const ByteString* Buf)
+ : m_p(0), m_capacity(0), m_size(0)
+{
+ m_p = Buf->RoData();
+ m_capacity = Buf->Length();
+ assert(m_p); assert(m_capacity);
+}
+
+bool
+Kumu::MemIOReader::ReadBER(ui64_t* i, ui32_t* ber_len)
+{
+ if ( i == 0 || ber_len == 0 ) return false;
+
+ if ( ( *ber_len = BER_length(m_p + m_size) ) == 0 )
+ return false;
+
+ if ( ( m_size + *ber_len ) > m_capacity )
+ return false;
+
+ if ( ! read_BER(m_p + m_size, i) )
+ return false;
+
+ m_size += *ber_len;
+ return true;
+}
+
+//------------------------------------------------------------------------------------------
+
+Kumu::ByteString::ByteString() : m_Data(0), m_Capacity(0), m_Length(0) {}
+
+Kumu::ByteString::ByteString(ui32_t cap) : m_Data(0), m_Capacity(0), m_Length(0)
+{
+ Capacity(cap);
+}
+
+Kumu::ByteString::~ByteString()
+{
+ if ( m_Data != 0 )
+ free(m_Data);
+}
+
+
+// copy the given data into the ByteString, set Length value.
+// Returns error if the ByteString is too small.
+Kumu::Result_t
+Kumu::ByteString::Set(const byte_t* buf, ui32_t buf_len)
+{
+ if ( m_Capacity < buf_len )
+ return RESULT_ALLOC;
+
+ memcpy(m_Data, buf, buf_len);
+ m_Length = buf_len;
+ return RESULT_OK;
+}
+
+
+// copy the given data into the ByteString, set Length value.
+// Returns error if the ByteString is too small.
+Kumu::Result_t
+Kumu::ByteString::Set(const ByteString& Buf)
+{
+ if ( m_Capacity < Buf.m_Capacity )
+ return RESULT_ALLOC;
+
+ memcpy(m_Data, Buf.m_Data, Buf.m_Length);
+ m_Length = Buf.m_Length;
+ return RESULT_OK;
+}
+
+
+// Sets the size of the internally allocate buffer.
+Kumu::Result_t
+Kumu::ByteString::Capacity(ui32_t cap_size)
+{
+ if ( m_Capacity >= cap_size )
+ return RESULT_OK;
+
+ byte_t* tmp_data = 0;
+ if ( m_Data != 0 )
+ {
+ if ( m_Length > 0 )
+ tmp_data = m_Data;
+ else
+ free(m_Data);
+ }
+
+ if ( ( m_Data = (byte_t*)malloc(cap_size) ) == 0 )
+ return RESULT_ALLOC;
+
+ if ( tmp_data != 0 )
+ {
+ assert(m_Length > 0);
+ memcpy(m_Data, tmp_data, m_Length);
+ free(tmp_data);
+ }
+
+ m_Capacity = cap_size;
+ return RESULT_OK;
+}
+
+//
+Kumu::Result_t
+Kumu::ByteString::Append(const ByteString& Buf)
+{
+ Result_t result = RESULT_OK;
+ ui32_t diff = m_Capacity - m_Length;
+
+ if ( diff < Buf.Length() )
+ result = Capacity(m_Capacity + Buf.Length());
+
+ if ( KM_SUCCESS(result) )
+ {
+ memcpy(m_Data + m_Length, Buf.RoData(), Buf.Length());
+ m_Length += Buf.Length();
+ }
+
+ return result;
+}
+
+//
+Kumu::Result_t
+Kumu::ByteString::Append(const byte_t* buf, ui32_t buf_len)
+{
+ Result_t result = RESULT_OK;
+ ui32_t diff = m_Capacity - m_Length;
+
+ if ( diff < buf_len )
+ result = Capacity(m_Capacity + buf_len);
+
+ if ( KM_SUCCESS(result) )
+ {
+ memcpy(m_Data + m_Length, buf, buf_len);
+ m_Length += buf_len;
+ }
+
+ return result;
+}
+
+
+//
+// end KM_util.cpp
+//