mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
11 KiB
458 lines
11 KiB
/********************************************************************\
|
|
* guid.c -- globally unique ID implementation *
|
|
* Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
|
|
* Copyright (C) 2014 Aaron Laws <dartmetrash@gmail.com> *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
* *
|
|
\********************************************************************/
|
|
|
|
#include "guid.hpp"
|
|
#include "guid.h"
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
# include <sys/types.h>
|
|
#endif
|
|
#include <ctype.h>
|
|
#include <stdint.h>
|
|
#ifdef HAVE_DIRENT_H
|
|
# include <dirent.h>
|
|
#endif
|
|
#include <glib.h>
|
|
#include <glib/gstdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_SYS_TIMES_H
|
|
# include <sys/times.h>
|
|
#endif
|
|
#include <time.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include "qof.h"
|
|
|
|
#include <boost/uuid/uuid.hpp>
|
|
#include <boost/uuid/uuid_generators.hpp>
|
|
#include <boost/uuid/uuid_io.hpp>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <algorithm>
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = QOF_MOD_ENGINE;
|
|
|
|
/**
|
|
* gnc_value_get_guid
|
|
*
|
|
* @param value a @c GValue whose value we want to get.
|
|
*
|
|
* @return the value stored in @a value
|
|
*/
|
|
const GncGUID*
|
|
gnc_value_get_guid (const GValue *value)
|
|
{
|
|
if (!value) return nullptr;
|
|
GncGUID *val;
|
|
|
|
g_return_val_if_fail (value && G_IS_VALUE (value), nullptr);
|
|
g_return_val_if_fail (GNC_VALUE_HOLDS_GUID (value), nullptr);
|
|
|
|
val = (GncGUID*) g_value_get_boxed (value);
|
|
|
|
return val;
|
|
}
|
|
|
|
GncGUID * guid_convert_create (gnc::GUID const &);
|
|
|
|
static gnc::GUID s_null_guid {boost::uuids::uuid { {0}}};
|
|
static GncGUID * s_null_gncguid {guid_convert_create (s_null_guid)};
|
|
|
|
static inline int
|
|
char_to_num (unsigned char c) noexcept
|
|
{
|
|
unsigned int digit = c - '0';
|
|
unsigned int alpha = (c | 0x20) - 'a';
|
|
return digit <= 9 ? digit : alpha <= 5 ? alpha + 10 : -1;
|
|
}
|
|
|
|
static inline bool
|
|
fast_string_to_guid (const char* s, uint8_t* out) noexcept
|
|
{
|
|
if (strnlen (s, GUID_ENCODING_LENGTH + 1) != GUID_ENCODING_LENGTH) return false;
|
|
bool all_ok = true;
|
|
for (int i = 0; i < GUID_DATA_SIZE; i++)
|
|
{
|
|
int hi = char_to_num (*s++);
|
|
int lo = char_to_num (*s++);
|
|
all_ok &= (hi >= 0 && lo >= 0);
|
|
out[i] = (unsigned char)(((unsigned)hi << 4) | (unsigned)lo);
|
|
}
|
|
return all_ok;
|
|
}
|
|
|
|
static inline void
|
|
fast_guid_to_string (const uint8_t* src, char* dest) noexcept
|
|
{
|
|
static constexpr char hex[] = "0123456789abcdef";
|
|
for (size_t i = 0; i < 16; i++)
|
|
{
|
|
uint8_t b = src[i];
|
|
*dest++ = hex[b >> 4];
|
|
*dest++ = hex[b & 0x0F];
|
|
}
|
|
}
|
|
|
|
/* Memory management routines ***************************************/
|
|
|
|
/**
|
|
* Allocates and returns a new GncGUID containing the same value as the
|
|
* gnc::GUID passed in.
|
|
*/
|
|
GncGUID *
|
|
guid_convert_create (gnc::GUID const & guid)
|
|
{
|
|
GncGUID temp = guid;
|
|
return guid_copy (&temp);
|
|
}
|
|
|
|
GncGUID *
|
|
guid_malloc (void)
|
|
{
|
|
return new GncGUID;
|
|
}
|
|
|
|
void
|
|
guid_free (GncGUID *guid)
|
|
{
|
|
if (!guid) return;
|
|
if (guid == s_null_gncguid)
|
|
/* Don't delete that! */
|
|
return;
|
|
delete guid;
|
|
}
|
|
|
|
GncGUID *
|
|
guid_copy (const GncGUID *guid)
|
|
{
|
|
if (!guid) return nullptr;
|
|
auto ret = guid_malloc ();
|
|
*ret = *guid;
|
|
return ret;
|
|
}
|
|
|
|
/*It looks like we are expected to provide the same pointer every time from this function*/
|
|
const GncGUID *
|
|
guid_null (void)
|
|
{
|
|
return s_null_gncguid;
|
|
}
|
|
|
|
static void
|
|
guid_assign (GncGUID & target, gnc::GUID const & source)
|
|
{
|
|
std::copy (source.begin(), source.end(), target.reserved);
|
|
}
|
|
|
|
/*Takes an allocated guid pointer and constructs it in place*/
|
|
void
|
|
guid_replace (GncGUID *guid)
|
|
{
|
|
if (!guid) return;
|
|
gnc::GUID temp_random {gnc::GUID::create_random ()};
|
|
guid_assign (*guid, temp_random);
|
|
}
|
|
|
|
GncGUID *
|
|
guid_new (void)
|
|
{
|
|
auto ret = guid_new_return ();
|
|
return guid_copy (&ret);
|
|
}
|
|
|
|
GncGUID
|
|
guid_new_return (void)
|
|
{
|
|
return gnc::GUID::create_random ();
|
|
}
|
|
|
|
gchar *
|
|
guid_to_string (const GncGUID * guid)
|
|
{
|
|
if (!guid) return nullptr;
|
|
char* buffer = g_new (char, GUID_ENCODING_LENGTH + 1);
|
|
guid_to_string_buff (guid, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
gchar *
|
|
guid_to_string_buff (const GncGUID * guid, gchar *str)
|
|
{
|
|
if (!str || !guid) return nullptr;
|
|
fast_guid_to_string (guid->reserved, str);
|
|
str[GUID_ENCODING_LENGTH] = '\0';
|
|
return str;
|
|
}
|
|
|
|
gboolean
|
|
string_to_guid (const char * str, GncGUID * guid)
|
|
{
|
|
if (!guid || !str || !*str) return false;
|
|
|
|
if (fast_string_to_guid (str, guid->reserved))
|
|
return true;
|
|
|
|
try
|
|
{
|
|
guid_assign (*guid, gnc::GUID::from_string (str));
|
|
}
|
|
catch (...)
|
|
{
|
|
PINFO("Failed to construct a GUID from %s", str);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
gboolean
|
|
guid_equal (const GncGUID *guid_1, const GncGUID *guid_2)
|
|
{
|
|
return guid_compare (guid_1, guid_2) == 0;
|
|
}
|
|
|
|
gint
|
|
guid_compare (const GncGUID *guid_1, const GncGUID *guid_2)
|
|
{
|
|
if (guid_1 == guid_2) return 0;
|
|
if (!guid_1) return -1;
|
|
if (!guid_2) return 1;
|
|
return std::memcmp (guid_1->reserved, guid_2->reserved, GUID_DATA_SIZE);
|
|
}
|
|
|
|
// returns a 32-bit hash from 32-byte guid. since guid are generated
|
|
// randomly, this is not expected to cause hash collisions. use memcpy
|
|
// to avoid alignment issues; memcpy likely to be optimised away.
|
|
guint
|
|
guid_hash_to_guint (gconstpointer ptr)
|
|
{
|
|
if (!ptr)
|
|
{
|
|
PERR ("received nullptr guid pointer.");
|
|
return 0;
|
|
}
|
|
const GncGUID* g = static_cast<const GncGUID*>(ptr);
|
|
guint rv;
|
|
memcpy (&rv, &g->reserved[12], sizeof (guint));
|
|
return rv;
|
|
}
|
|
|
|
gint
|
|
guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
|
|
{
|
|
return guid_equal (reinterpret_cast<const GncGUID*> (guid_a),
|
|
reinterpret_cast<const GncGUID*> (guid_b));
|
|
}
|
|
|
|
GHashTable *
|
|
guid_hash_table_new (void)
|
|
{
|
|
return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
|
|
}
|
|
|
|
/***************************/
|
|
static void
|
|
gnc_string_to_guid (const GValue *src, GValue *dest)
|
|
{
|
|
/* FIXME: add more checks*/
|
|
GncGUID *guid;
|
|
const gchar *as_string;
|
|
|
|
g_return_if_fail (G_VALUE_HOLDS_STRING (src) &&
|
|
GNC_VALUE_HOLDS_GUID (dest));
|
|
|
|
as_string = g_value_get_string (src);
|
|
|
|
guid = g_new0 (GncGUID, 1);
|
|
string_to_guid (as_string, guid);
|
|
|
|
g_value_take_boxed (dest, guid);
|
|
}
|
|
|
|
static void
|
|
gnc_guid_to_string (const GValue *src, GValue *dest)
|
|
{
|
|
const gchar *str;
|
|
|
|
g_return_if_fail (G_VALUE_HOLDS_STRING (dest) &&
|
|
GNC_VALUE_HOLDS_GUID (src));
|
|
|
|
str = guid_to_string (gnc_value_get_guid (src));
|
|
|
|
g_value_set_string (dest, str);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE_WITH_CODE (GncGUID, gnc_guid, guid_copy, guid_free,
|
|
g_value_register_transform_func (G_TYPE_STRING,
|
|
g_define_type_id,
|
|
gnc_string_to_guid);
|
|
|
|
g_value_register_transform_func (g_define_type_id,
|
|
G_TYPE_STRING,
|
|
gnc_guid_to_string);
|
|
)
|
|
|
|
namespace gnc
|
|
{
|
|
|
|
GUID
|
|
GUID::create_random () noexcept
|
|
{
|
|
static boost::uuids::random_generator gen;
|
|
return {gen ()};
|
|
}
|
|
|
|
GUID::GUID (boost::uuids::uuid const & other) noexcept
|
|
: implementation (other)
|
|
{
|
|
}
|
|
|
|
GUID const &
|
|
GUID::null_guid () noexcept
|
|
{
|
|
return s_null_guid;
|
|
}
|
|
|
|
std::string
|
|
GUID::to_string () const noexcept
|
|
{
|
|
std::string out;
|
|
out.resize (implementation.size() * 2);
|
|
fast_guid_to_string (implementation.data, out.data());
|
|
return out;
|
|
}
|
|
|
|
GUID
|
|
GUID::from_string (const char* str)
|
|
{
|
|
if (!str)
|
|
throw guid_syntax_exception {};
|
|
|
|
if (boost::uuids::uuid u; fast_string_to_guid(str, u.data))
|
|
return u;
|
|
try
|
|
{
|
|
static boost::uuids::string_generator strgen;
|
|
return strgen (str);
|
|
}
|
|
catch (...)
|
|
{
|
|
throw guid_syntax_exception {};
|
|
}
|
|
}
|
|
|
|
bool
|
|
GUID::is_valid_guid (const char* str)
|
|
{
|
|
uint8_t bytes[16];
|
|
if (fast_string_to_guid(str, bytes))
|
|
return true;
|
|
try
|
|
{
|
|
static boost::uuids::string_generator strgen;
|
|
strgen (str);
|
|
return true;
|
|
}
|
|
catch (...)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
guid_syntax_exception::guid_syntax_exception () noexcept
|
|
: invalid_argument {"Invalid syntax for guid."}
|
|
{
|
|
}
|
|
|
|
GUID::GUID (GncGUID const & other) noexcept
|
|
: implementation {{other.reserved[0] , other.reserved[1]
|
|
, other.reserved[2], other.reserved[3]
|
|
, other.reserved[4], other.reserved[5]
|
|
, other.reserved[6], other.reserved[7]
|
|
, other.reserved[8], other.reserved[9]
|
|
, other.reserved[10], other.reserved[11]
|
|
, other.reserved[12], other.reserved[13]
|
|
, other.reserved[14], other.reserved[15]}
|
|
}
|
|
{
|
|
|
|
}
|
|
|
|
auto
|
|
GUID::end () const noexcept -> decltype (implementation.end ())
|
|
{
|
|
return implementation.end ();
|
|
}
|
|
|
|
auto
|
|
GUID::begin () const noexcept -> decltype (implementation.begin ())
|
|
{
|
|
return implementation.begin ();
|
|
}
|
|
|
|
bool
|
|
GUID::operator < (GUID const & other) noexcept
|
|
{
|
|
return implementation < other.implementation;
|
|
}
|
|
|
|
bool operator == (GUID const & lhs, GncGUID const & rhs) noexcept
|
|
{
|
|
return lhs.implementation == GUID(rhs).implementation;
|
|
}
|
|
|
|
bool
|
|
operator != (GUID const & one, GUID const & two) noexcept
|
|
{
|
|
return one.implementation != two.implementation;
|
|
}
|
|
|
|
GUID & GUID::operator = (GUID && other) noexcept
|
|
{
|
|
boost::uuids::swap (other.implementation, implementation);
|
|
return *this;
|
|
}
|
|
|
|
GUID::operator GncGUID () const noexcept
|
|
{
|
|
GncGUID ret;
|
|
guid_assign (ret, *this);
|
|
return ret;
|
|
}
|
|
|
|
} // namespace gnc
|
|
|
|
bool
|
|
operator==(const GncGUID& lhs, const GncGUID& rhs)
|
|
{
|
|
return gnc::GUID{lhs} == gnc::GUID{rhs};
|
|
}
|