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.

379 lines
8.9 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 Ian McInerney <ian.s.mcinerney@ieee.org>
  5. * Copyright (C) 2007-2014 Jean-Pierre Charras, jp.charras at wanadoo.fr
  6. * Copyright (C) 1992-2022 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, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <kiid.h>
  26. #include <boost/uuid/uuid_generators.hpp>
  27. #include <boost/uuid/uuid_io.hpp>
  28. #include <boost/functional/hash.hpp>
  29. #if BOOST_VERSION >= 106700
  30. #include <boost/uuid/entropy_error.hpp>
  31. #endif
  32. #include <nlohmann/json.hpp>
  33. #include <cctype>
  34. #include <mutex>
  35. #include <wx/log.h>
  36. // boost:mt19937 is not thread-safe
  37. static std::mutex rng_mutex;
  38. // Static rng and generators are used because the overhead of constant seeding is expensive
  39. // We rely on the default non-arg constructor of basic_random_generator to provide a random seed.
  40. // We use a separate rng object for cases where we want to control the basic_random_generator
  41. // initial seed by calling SeedGenerator from unit tests and other special cases.
  42. static boost::mt19937 rng;
  43. static boost::uuids::basic_random_generator<boost::mt19937> randomGenerator;
  44. // These don't have the same performance penalty, but we might as well be consistent
  45. static boost::uuids::string_generator stringGenerator;
  46. static boost::uuids::nil_generator nilGenerator;
  47. // Global nil reference
  48. KIID niluuid( 0 );
  49. // When true, always create nil uuids for performance, when valid ones aren't needed
  50. static bool g_createNilUuids = false;
  51. // For static initialization
  52. KIID& NilUuid()
  53. {
  54. static KIID nil( 0 );
  55. return nil;
  56. }
  57. KIID::KIID()
  58. {
  59. m_cached_timestamp = 0;
  60. #if BOOST_VERSION >= 106700
  61. try
  62. {
  63. #endif
  64. if( g_createNilUuids )
  65. {
  66. m_uuid = nilGenerator();
  67. }
  68. else
  69. {
  70. std::lock_guard<std::mutex> lock( rng_mutex );
  71. m_uuid = randomGenerator();
  72. }
  73. #if BOOST_VERSION >= 106700
  74. }
  75. catch( const boost::uuids::entropy_error& )
  76. {
  77. wxLogFatalError( "A Boost UUID entropy exception was thrown in %s:%s.",
  78. __FILE__, __FUNCTION__ );
  79. }
  80. #endif
  81. }
  82. KIID::KIID( int null ) :
  83. m_uuid( nilGenerator() ),
  84. m_cached_timestamp( 0 )
  85. {
  86. wxASSERT( null == 0 );
  87. }
  88. KIID::KIID( const std::string& aString ) :
  89. m_uuid(),
  90. m_cached_timestamp( 0 )
  91. {
  92. if( aString.length() == 8
  93. && std::all_of( aString.begin(), aString.end(),
  94. []( unsigned char c )
  95. {
  96. return std::isxdigit( c );
  97. } ) )
  98. {
  99. // A legacy-timestamp-based UUID has only the last 4 octets filled in.
  100. // Convert them individually to avoid stepping in the little-endian/big-endian
  101. // doo-doo.
  102. for( int i = 0; i < 4; ++i )
  103. {
  104. std::string octet = aString.substr( i * 2, 2 );
  105. m_uuid.data[i + 12] = strtol( octet.data(), nullptr, 16 );
  106. }
  107. m_cached_timestamp = strtol( aString.c_str(), nullptr, 16 );
  108. }
  109. else
  110. {
  111. try
  112. {
  113. m_uuid = stringGenerator( aString );
  114. if( IsLegacyTimestamp() )
  115. m_cached_timestamp = strtol( aString.substr( 28 ).c_str(), nullptr, 16 );
  116. }
  117. catch( ... )
  118. {
  119. // Failed to parse string representation; best we can do is assign a new
  120. // random one.
  121. #if BOOST_VERSION >= 106700
  122. try
  123. {
  124. #endif
  125. m_uuid = randomGenerator();
  126. #if BOOST_VERSION >= 106700
  127. }
  128. catch( const boost::uuids::entropy_error& )
  129. {
  130. wxLogFatalError( "A Boost UUID entropy exception was thrown in %s:%s.",
  131. __FILE__, __FUNCTION__ );
  132. }
  133. #endif
  134. }
  135. }
  136. }
  137. KIID::KIID( const char* aString ) :
  138. KIID( std::string( aString ) )
  139. {
  140. }
  141. KIID::KIID( const wxString& aString ) :
  142. KIID( std::string( aString.ToUTF8() ) )
  143. {
  144. }
  145. bool KIID::SniffTest( const wxString& aCandidate )
  146. {
  147. static wxString niluuidStr = niluuid.AsString();
  148. if( aCandidate.Length() != niluuidStr.Length() )
  149. return false;
  150. for( wxChar c : aCandidate )
  151. {
  152. if( c >= '0' && c <= '9' )
  153. continue;
  154. if( c >= 'a' && c <= 'f' )
  155. continue;
  156. if( c >= 'A' && c <= 'F' )
  157. continue;
  158. if( c == '-' )
  159. continue;
  160. return false;
  161. }
  162. return true;
  163. }
  164. KIID::KIID( timestamp_t aTimestamp )
  165. {
  166. m_cached_timestamp = aTimestamp;
  167. // A legacy-timestamp-based UUID has only the last 4 octets filled in.
  168. // Convert them individually to avoid stepping in the little-endian/big-endian
  169. // doo-doo.
  170. wxString str = AsLegacyTimestampString();
  171. for( int i = 0; i < 4; ++i )
  172. {
  173. wxString octet = str.substr( i * 2, 2 );
  174. m_uuid.data[i + 12] = strtol( octet.data(), nullptr, 16 );
  175. }
  176. }
  177. bool KIID::IsLegacyTimestamp() const
  178. {
  179. return !m_uuid.data[8] && !m_uuid.data[9] && !m_uuid.data[10] && !m_uuid.data[11];
  180. }
  181. timestamp_t KIID::AsLegacyTimestamp() const
  182. {
  183. return m_cached_timestamp;
  184. }
  185. size_t KIID::Hash() const
  186. {
  187. size_t hash = 0;
  188. // Note: this is NOT little-endian/big-endian safe, but as long as it's just used
  189. // at runtime it won't matter.
  190. for( int i = 0; i < 4; ++i )
  191. boost::hash_combine( hash, reinterpret_cast<const uint32_t*>( m_uuid.data )[i] );
  192. return hash;
  193. }
  194. void KIID::Clone( const KIID& aUUID )
  195. {
  196. m_uuid = aUUID.m_uuid;
  197. m_cached_timestamp = aUUID.m_cached_timestamp;
  198. }
  199. wxString KIID::AsString() const
  200. {
  201. return boost::uuids::to_string( m_uuid );
  202. }
  203. wxString KIID::AsLegacyTimestampString() const
  204. {
  205. return wxString::Format( "%8.8lX", (unsigned long) AsLegacyTimestamp() );
  206. }
  207. void KIID::ConvertTimestampToUuid()
  208. {
  209. if( !IsLegacyTimestamp() )
  210. return;
  211. m_cached_timestamp = 0;
  212. m_uuid = randomGenerator();
  213. }
  214. void KIID::Increment()
  215. {
  216. // This obviously destroys uniform distribution, but it can be useful when a
  217. // deterministic replacement for a duplicate ID is required.
  218. for( int i = 15; i >= 0; --i )
  219. {
  220. m_uuid.data[i]++;
  221. if( m_uuid.data[i] != 0 )
  222. break;
  223. }
  224. }
  225. void KIID::CreateNilUuids( bool aNil )
  226. {
  227. g_createNilUuids = aNil;
  228. }
  229. void KIID::SeedGenerator( unsigned int aSeed )
  230. {
  231. rng.seed( aSeed );
  232. randomGenerator = boost::uuids::basic_random_generator<boost::mt19937>( rng );
  233. }
  234. KIID_PATH::KIID_PATH( const wxString& aString )
  235. {
  236. for( const wxString& pathStep : wxSplit( aString, '/' ) )
  237. {
  238. if( !pathStep.empty() )
  239. emplace_back( KIID( pathStep ) );
  240. }
  241. }
  242. bool KIID_PATH::MakeRelativeTo( const KIID_PATH& aPath )
  243. {
  244. KIID_PATH copy = *this;
  245. clear();
  246. if( aPath.size() > copy.size() )
  247. return false; // this path is not contained within aPath
  248. for( size_t i = 0; i < aPath.size(); ++i )
  249. {
  250. if( copy.at( i ).AsString() != aPath.at( i ).AsString() )
  251. return false; // this path is not contained within aPath
  252. }
  253. for( size_t i = aPath.size(); i < copy.size(); ++i )
  254. push_back( copy.at( i ) );
  255. return true;
  256. }
  257. bool KIID_PATH::EndsWith( const KIID_PATH& aPath ) const
  258. {
  259. if( aPath.size() > size() )
  260. return false; // this path can not end aPath
  261. KIID_PATH copyThis = *this;
  262. KIID_PATH copyThat = aPath;
  263. while( !copyThat.empty() )
  264. {
  265. if( *std::prev( copyThis.end() ) != *std::prev( copyThat.end() ) )
  266. return false;
  267. copyThis.pop_back();
  268. copyThat.pop_back();
  269. }
  270. return true;
  271. }
  272. wxString KIID_PATH::AsString() const
  273. {
  274. wxString path;
  275. for( const KIID& pathStep : *this )
  276. path += '/' + pathStep.AsString();
  277. return path;
  278. }
  279. void to_json( nlohmann::json& aJson, const KIID& aKIID )
  280. {
  281. aJson = aKIID.AsString().ToUTF8();
  282. }
  283. void from_json( const nlohmann::json& aJson, KIID& aKIID )
  284. {
  285. aKIID = KIID( aJson.get<std::string>() );
  286. }