KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kiid.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 Ian McInerney <[email protected]>
5 * Copyright (C) 2007-2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
6 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <kiid.h>
23
24#include <boost/random/mersenne_twister.hpp>
25#include <boost/uuid/uuid_generators.hpp>
26#include <boost/uuid/name_generator_sha1.hpp>
27#include <boost/uuid/uuid_io.hpp>
28
29#if BOOST_VERSION >= 106700
30#include <boost/uuid/entropy_error.hpp>
31#endif
32
33#include <json_common.h>
34
35#include <cctype>
36#include <cstdint>
37#include <mutex>
38#include <utility>
39#include <stdlib.h>
40
41#include <wx/log.h>
42#include <wx/string.h>
43
44// Use thread_local because boost:mt19937 is not thread-safe
45// Static rng and generators are used because the overhead of constant seeding is expensive
46// We rely on the default non-arg constructor of basic_random_generator to provide a random seed.
47// We use a separate rng object for cases where we want to control the basic_random_generator
48// initial seed by calling SeedGenerator from unit tests and other special cases.
49static thread_local boost::mt19937 rng;
50static thread_local boost::uuids::basic_random_generator<boost::mt19937> randomGenerator;
51
52// These don't have the same performance penalty, but we might as well be consistent
53static boost::uuids::string_generator stringGenerator;
54static boost::uuids::nil_generator nilGenerator;
55
56
57// Global nil reference
59
60
61// When true, always create nil uuids for performance, when valid ones aren't needed.
62// Thread-local to prevent background library loading from affecting other threads.
63static thread_local bool g_createNilUuids = false;
64
65
66// For static initialization
68{
69 static KIID nil( 0 );
70 return nil;
71}
72
73
75{
76#if BOOST_VERSION >= 106700
77 try
78 {
79#endif
80
82 {
84 }
85 else
86 {
88 }
89
90#if BOOST_VERSION >= 106700
91 }
92 catch( const boost::uuids::entropy_error& )
93 {
94 wxLogFatalError( "A Boost UUID entropy exception was thrown in %s:%s.",
95 __FILE__, __FUNCTION__ );
96 }
97#endif
98}
99
100
101KIID::KIID( int null ) :
103{
104 wxASSERT( null == 0 );
105}
106
107
108KIID::KIID( const std::string& aString ) :
109 m_uuid()
110{
111 if( !aString.empty() && aString.length() <= 8
112 && std::all_of( aString.begin(), aString.end(),
113 []( unsigned char c )
114 {
115 return std::isxdigit( c );
116 } ) )
117 {
118 // A legacy-timestamp-based UUID has only the last 4 octets filled in.
119 // Convert them individually to avoid stepping in the little-endian/big-endian
120 // doo-doo.
121 for( int i = 0; i < 4; i++ )
122 {
123 int start = static_cast<int>( aString.length() ) - 8 + i * 2;
124 int end = start + 2;
125
126 start = std::max( 0, start );
127 int len = std::max( 0, end - start );
128
129 std::string octet = aString.substr( start, len );
130 m_uuid.data[i + 12] = strtol( octet.data(), nullptr, 16 );
131 }
132 }
133 else
134 {
135 try
136 {
137 m_uuid = stringGenerator( aString );
138 }
139 catch( ... )
140 {
141 // Failed to parse string representation; best we can do is assign a new
142 // random one.
143#if BOOST_VERSION >= 106700
144 try
145 {
146#endif
147
148 m_uuid = randomGenerator();
149
150#if BOOST_VERSION >= 106700
151 }
152 catch( const boost::uuids::entropy_error& )
153 {
154 wxLogFatalError( "A Boost UUID entropy exception was thrown in %s:%s.",
155 __FILE__, __FUNCTION__ );
156 }
157#endif
158 }
159 }
160}
161
162
163KIID::KIID( const char* aString ) :
164 KIID( std::string( aString ) )
165{
166}
167
168
169KIID::KIID( const wxString& aString ) :
170 KIID( std::string( aString.ToUTF8() ) )
171{
172}
173
174
175bool KIID::SniffTest( const wxString& aCandidate )
176{
177 static wxString niluuidStr = niluuid.AsString();
178
179 if( aCandidate.Length() != niluuidStr.Length() )
180 return false;
181
182 for( wxChar c : aCandidate )
183 {
184 if( c >= '0' && c <= '9' )
185 continue;
186
187 if( c >= 'a' && c <= 'f' )
188 continue;
189
190 if( c >= 'A' && c <= 'F' )
191 continue;
192
193 if( c == '-' )
194 continue;
195
196 return false;
197 }
198
199 return true;
200}
201
202
204{
205 m_uuid.data[12] = static_cast<uint8_t>( aTimestamp >> 24 );
206 m_uuid.data[13] = static_cast<uint8_t>( aTimestamp >> 16 );
207 m_uuid.data[14] = static_cast<uint8_t>( aTimestamp >> 8 );
208 m_uuid.data[15] = static_cast<uint8_t>( aTimestamp );
209}
210
211
213{
214 return !m_uuid.data[8] && !m_uuid.data[9] && !m_uuid.data[10] && !m_uuid.data[11];
215}
216
217
219{
220 timestamp_t ret = 0;
221
222 ret |= m_uuid.data[12] << 24;
223 ret |= m_uuid.data[13] << 16;
224 ret |= m_uuid.data[14] << 8;
225 ret |= m_uuid.data[15];
226
227 return ret;
228}
229
230
231size_t KIID::Hash() const
232{
233 return boost::uuids::hash_value( m_uuid );
234}
235
236
237KIID KIID::FromName( const std::string& aName )
238{
239 // Arbitrary but fixed namespace, the same name must map to the same KIID forever
240 static const boost::uuids::uuid nsUuid = stringGenerator( "8b8b58e2-3d21-4a24-9dcf-42e0f14001a2" );
241
242 boost::uuids::name_generator_sha1 nameGenerator( nsUuid );
243
244 KIID id( 0 );
245 id.m_uuid = nameGenerator( aName );
246 return id;
247}
248
249
250KIID KIID::FromBytes( const std::array<uint8_t, 16>& aBytes )
251{
252 KIID id( 0 );
253 std::copy( aBytes.begin(), aBytes.end(), id.m_uuid.begin() );
254 return id;
255}
256
257
258void KIID::Clone( const KIID& aUUID )
259{
260 m_uuid = aUUID.m_uuid;
261}
262
263
264wxString KIID::AsString() const
265{
266 return boost::uuids::to_string( m_uuid );
267}
268
269
270std::string KIID::AsStdString() const
271{
272 return boost::uuids::to_string( m_uuid );
273}
274
275
276std::array<uint8_t, 16> KIID::AsBytes() const
277{
278 std::array<uint8_t, 16> result;
279 std::copy( m_uuid.begin(), m_uuid.end(), result.begin() );
280 return result;
281}
282
283
285{
286 return wxString::Format( "%8.8lX", (unsigned long) AsLegacyTimestamp() );
287}
288
289
291{
292 if( !IsLegacyTimestamp() )
293 return;
294
296}
297
298
300{
301 // This obviously destroys uniform distribution, but it can be useful when a
302 // deterministic replacement for a duplicate ID is required.
303
304 for( int i = 15; i >= 0; --i )
305 {
306 m_uuid.data[i]++;
307
308 if( m_uuid.data[i] != 0 )
309 break;
310 }
311}
312
313
314KIID KIID::Combine( const KIID& aFirst, const KIID& aSecond )
315{
316 KIID result( 0 );
317
318 for( int i = 0; i < 16; ++i )
319 result.m_uuid.data[i] = aFirst.m_uuid.data[i] ^ aSecond.m_uuid.data[i];
320
321 return result;
322}
323
324
325KIID KIID::FromDeterministicString( const wxString& aName )
326{
327 // Dual FNV-1a accumulators fold the name into two 64-bit values, which are
328 // then laid out as a v4-shaped UUID string and parsed back into a KIID so
329 // boost's string_generator produces identical bytes on every call. Do not
330 // alter the constants or bit layout: this output is embedded in persisted
331 // diff/merge artifacts and must stay byte-identical.
332 std::uint64_t h1 = 0xcbf29ce484222325ULL;
333 std::uint64_t h2 = 0x84222325cbf29ce4ULL;
334
335 for( wxChar c : aName )
336 {
337 h1 ^= static_cast<std::uint64_t>( c );
338 h1 *= 0x100000001b3ULL;
339 h2 ^= static_cast<std::uint64_t>( c ) * 0x9E3779B97F4A7C15ULL;
340 h2 = ( h2 << 13 ) | ( h2 >> 51 );
341 }
342
343 const wxString uuid = wxString::Format(
344 wxS( "%08x-%04x-%04x-%04x-%012llx" ),
345 static_cast<unsigned>( h1 >> 32 ),
346 static_cast<unsigned>( ( h1 >> 16 ) & 0xffff ),
347 static_cast<unsigned>( h1 & 0xffff ),
348 static_cast<unsigned>( h2 & 0xffff ),
349 static_cast<unsigned long long>( h2 >> 16 ) & 0xffffffffffffULL );
350
351 return KIID( uuid );
352}
353
354
355void KIID::CreateNilUuids( bool aNil )
356{
357 g_createNilUuids = aNil;
358}
359
360
361void KIID::SeedGenerator( unsigned int aSeed )
362{
363 rng.seed( aSeed );
364 randomGenerator = boost::uuids::basic_random_generator<boost::mt19937>( rng );
365}
366
367
368KIID_PATH::KIID_PATH( const wxString& aString )
369{
370 for( const wxString& pathStep : wxSplit( aString, '/' ) )
371 {
372 if( !pathStep.empty() )
373 emplace_back( KIID( pathStep ) );
374 }
375}
376
377
379{
380 KIID_PATH copy = *this;
381 clear();
382
383 if( aPath.size() > copy.size() )
384 return false; // this path is not contained within aPath
385
386 for( size_t i = 0; i < aPath.size(); ++i )
387 {
388 if( copy.at( i ) != aPath.at( i ) )
389 {
390 *this = copy;
391 return false; // this path is not contained within aPath
392 }
393 }
394
395 for( size_t i = aPath.size(); i < copy.size(); ++i )
396 push_back( copy.at( i ) );
397
398 return true;
399}
400
401
402bool KIID_PATH::EndsWith( const KIID_PATH& aPath ) const
403{
404 if( aPath.size() > size() )
405 return false; // this path can not end aPath
406
407 KIID_PATH copyThis = *this;
408 KIID_PATH copyThat = aPath;
409
410 while( !copyThat.empty() )
411 {
412 if( *std::prev( copyThis.end() ) != *std::prev( copyThat.end() ) )
413 return false;
414
415 copyThis.pop_back();
416 copyThat.pop_back();
417 }
418
419 return true;
420}
421
422
423wxString KIID_PATH::AsString() const
424{
425 wxString path;
426
427 for( const KIID& pathStep : *this )
428 path += '/' + pathStep.AsString();
429
430 return path;
431}
432
433
434void to_json( nlohmann::json& aJson, const KIID& aKIID )
435{
436 aJson = aKIID.AsString().ToUTF8();
437}
438
439
440void from_json( const nlohmann::json& aJson, KIID& aKIID )
441{
442 aKIID = KIID( aJson.get<std::string>() );
443}
bool EndsWith(const KIID_PATH &aPath) const
Test if aPath from the last path towards the first path.
Definition kiid.cpp:402
bool MakeRelativeTo(const KIID_PATH &aPath)
Definition kiid.cpp:378
KIID_PATH()
Definition kiid.h:176
wxString AsString() const
Definition kiid.cpp:423
Definition kiid.h:46
KIID()
Definition kiid.cpp:74
static void SeedGenerator(unsigned int aSeed)
Re-initialize the UUID generator with a given seed (for testing or QA purposes)
Definition kiid.cpp:361
size_t Hash() const
Definition kiid.cpp:231
wxString AsString() const
Definition kiid.cpp:264
std::array< uint8_t, 16 > AsBytes() const
Definition kiid.cpp:276
static KIID Combine(const KIID &aFirst, const KIID &aSecond)
Creates a deterministic KIID from two input KIIDs by XORing their underlying UUIDs.
Definition kiid.cpp:314
boost::uuids::uuid m_uuid
Definition kiid.h:165
void Increment()
Generates a deterministic replacement for a given ID.
Definition kiid.cpp:299
std::string AsStdString() const
Definition kiid.cpp:270
wxString AsLegacyTimestampString() const
Definition kiid.cpp:284
timestamp_t AsLegacyTimestamp() const
Definition kiid.cpp:218
static KIID FromBytes(const std::array< uint8_t, 16 > &aBytes)
Definition kiid.cpp:250
static void CreateNilUuids(bool aNil=true)
A performance optimization which disables/enables the generation of pseudo-random UUIDs.
Definition kiid.cpp:355
static bool SniffTest(const wxString &aCandidate)
Returns true if a string has the correct formatting to be a KIID.
Definition kiid.cpp:175
static KIID FromDeterministicString(const wxString &aName)
Build a deterministic UUID from an arbitrary name string.
Definition kiid.cpp:325
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
void Clone(const KIID &aUUID)
Definition kiid.cpp:258
bool IsLegacyTimestamp() const
Definition kiid.cpp:212
void ConvertTimestampToUuid()
Change an existing time stamp based UUID into a true UUID.
Definition kiid.cpp:290
static boost::uuids::nil_generator nilGenerator
Definition kiid.cpp:54
static thread_local boost::uuids::basic_random_generator< boost::mt19937 > randomGenerator
Definition kiid.cpp:50
KIID & NilUuid()
Definition kiid.cpp:67
void from_json(const nlohmann::json &aJson, KIID &aKIID)
Definition kiid.cpp:440
static thread_local boost::mt19937 rng
Definition kiid.cpp:49
static boost::uuids::string_generator stringGenerator
Definition kiid.cpp:53
static thread_local bool g_createNilUuids
Definition kiid.cpp:63
void to_json(nlohmann::json &aJson, const KIID &aKIID)
Definition kiid.cpp:434
KIID niluuid(0)
KICOMMON_API KIID niluuid
uint32_t timestamp_t
timestamp_t is our type to represent unique IDs for all kinds of elements; historically simply the ti...
Definition kiid.h:43
STL namespace.
netlist clear()
std::string path
wxString result
Test unit parsing edge cases and error handling.