@@ -10,10 +10,18 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
10
10
#ifndef _APUUID_HPP
11
11
#define _APUUID_HPP
12
12
13
+ /*
14
+ * NOTE: this does not produce "real" UUIDs. AP just requires strings that are unlikely to collide.
15
+ * This provides implementation for both browser context via emscripten as well as libc.
16
+ * We use a singleton to properly initialize RNG once.
17
+ * If calling srand() is a problem for the callee, you'll have to provide your own UUID generator.
18
+ */
13
19
14
20
#include < stdint.h>
15
21
#include < stdio.h>
16
22
#include < string>
23
+ #include < memory>
24
+ #include < fstream>
17
25
18
26
19
27
#ifdef __EMSCRIPTEN__
@@ -25,67 +33,203 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
25
33
#endif
26
34
27
35
28
- static void apuuid_init_rng ()
29
- {
30
- #ifdef __EMSCRIPTEN__
31
- /* js crypto needs no init from C */
32
- #else
33
- srand ((unsigned int ) time (NULL ));
34
- #endif
35
- }
36
+ namespace AP {
37
+ class UUID {
38
+ public:
39
+ UUID (uint8_t bytes[16 ])
40
+ {
41
+ const char hex[] = " 0123456789abcdef" ;
42
+ _string.resize (32 );
43
+ char * p = (char *)_string.data ();
44
+ for (size_t i=0 ; i<16 ; i++) {
45
+ *p = hex[(bytes[i] >> 4 ) & 0x0f ];
46
+ p++;
47
+ *p = hex[bytes[i] & 0x0f ];
48
+ p++;
49
+ }
50
+ }
36
51
37
- static uint8_t apuuid_rand_byte ()
38
- {
39
- #ifdef __EMSCRIPTEN__
40
- return (uint8_t )EM_ASM_INT ({
41
- var buf = new Uint8Array (1 );
42
- crypto.getRandomValues (buf);
43
- return buf[0 ];
44
- });
45
- #else
46
- return rand ();
47
- #endif
48
- }
52
+ virtual ~UUID () {}
49
53
50
- static void apuuid_generate (char * out)
51
- {
52
- for (uint8_t i=0 ; i<16 ; i++) {
53
- sprintf (out + 2 *i, " %02hhx" , apuuid_rand_byte ());
54
+ const std::string& string () const
55
+ {
56
+ return _string;
57
+ }
58
+
59
+ private:
60
+ std::string _string;
61
+ };
62
+
63
+ class UUIDFactory {
64
+ public:
65
+ static UUIDFactory* instance ();
66
+ virtual ~UUIDFactory () {}
67
+ void setFilename (const std::string& filename);
68
+ UUID getPersistentUUID (const std::string& hostname_or_url);
69
+
70
+ private:
71
+ UUIDFactory ();
72
+ uint8_t randByte () const ;
73
+ UUID generate () const ;
74
+ void generate (uint8_t bytes[8 ]) const ;
75
+ uint8_t hash (const std::string& name) const ;
76
+
77
+ std::string _filename;
78
+ std::fstream _f;
79
+ };
80
+
81
+ inline UUIDFactory* UUIDFactory::instance ()
82
+ {
83
+ static auto singleton = std::unique_ptr<UUIDFactory>(new UUIDFactory ());
84
+ return singleton.get ();
54
85
}
55
- }
56
86
57
- static std::string ap_get_uuid (const std::string& uuidFile)
58
- {
59
- char uuid[33 ]; uuid[32 ] = 0 ;
60
- #if defined USE_IDBFS || !defined __EMSCRIPTEN__
61
- FILE* f = uuidFile.empty () ? nullptr : fopen (uuidFile.c_str (), " rb" );
62
- size_t n = 0 ;
63
- if (f) {
64
- n = fread (uuid, 1 , 32 , f);
65
- fclose (f);
87
+ void UUIDFactory::setFilename (const std::string& filename)
88
+ {
89
+ #if defined __EMSCRIPTEN__ && !defined USE_IDBFS
90
+ #error "IDBFS is currently required for emscripten"
91
+ #endif
92
+ if (_filename == filename)
93
+ return ;
94
+ if (_f.is_open ())
95
+ _f.close ();
96
+ _filename = filename;
66
97
}
67
- if (!f || n < 32 ) {
68
- apuuid_init_rng ();
69
- apuuid_generate (uuid);
70
- f = uuidFile.empty () ? nullptr : fopen (uuidFile.c_str (), " wb" );
71
- if (f) {
72
- n = fwrite (uuid, 1 , 32 , f);
73
- fclose (f);
74
- #ifdef __EMSCRIPTEN__
75
- EM_ASM (
76
- FS.syncfs (function (err) {}); // cppcheck-suppress unknownMacro
77
- );
78
- #endif
98
+
99
+ UUID UUIDFactory::getPersistentUUID (const std::string& name)
100
+ {
101
+ if (!_f.is_open ()) {
102
+ if (_filename.empty ())
103
+ return generate ();
104
+
105
+ _f.open (_filename, std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::ate);
106
+ if (!_f.is_open ())
107
+ _f.open (_filename, std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::trunc);
108
+ if (!_f.is_open ()) {
109
+ fprintf (stderr, " Could not write persistent UUID!\n " );
110
+ return generate ();
111
+ }
112
+ if (_f.tellp () < 32 ) {
113
+ // insert back compat string
114
+ _f.seekp (0 );
115
+ _f << generate ().string ();
116
+ _f.flush ();
117
+ }
118
+ if (_f.tellp () < 64 ) {
119
+ // insert old UUID into slot 0
120
+ uint64_t state = 1 ;
121
+ uint64_t dummy = 0 ;
122
+ uint8_t bytes[16 ];
123
+ _f.seekg (0 );
124
+ for (uint8_t i=0 ; i<16 ; i++) {
125
+ char c;
126
+ uint8_t b = 0 ;
127
+ for (uint8_t n=0 ; n<2 ; n++) {
128
+ b <<= 4 ;
129
+ _f.read (&c, 1 );
130
+ if (c >= ' 0' && c <= ' 9' )
131
+ b |= (uint8_t )(c - ' 0' );
132
+ if (c >= ' a' && c <= ' f' )
133
+ b |= (uint8_t )(c - ' a' + 0x0a );
134
+ if (c >= ' A' && c <= ' F' )
135
+ b |= (uint8_t )(c - ' A' + 0x0a );
136
+ }
137
+ bytes[i] = b;
138
+ }
139
+ _f.seekp (32 );
140
+ _f.write ((const char *)&state, sizeof (state));
141
+ _f.write ((const char *)&dummy, sizeof (dummy));
142
+ _f.write ((const char *)bytes, sizeof (bytes));
143
+ _f.flush ();
144
+ }
79
145
}
80
- if (!uuidFile.empty () && (!f || n < 32 )) {
81
- fprintf (stderr, " Could not write persistent UUID!\n " );
146
+ // 32 byte at start of file reserved for back compat
147
+ // then each entry is 8 byte state + 8 byte reserved + 16 byte uuid
148
+ size_t offset = 32 + (size_t ) hash (name) * 32 ;
149
+ _f.seekg (offset);
150
+ uint64_t state;
151
+ uint64_t dummy;
152
+ uint8_t bytes[16 ];
153
+
154
+ // load existing UUID if it exists
155
+ if (_f.read ((char *)&state, sizeof (state)) && state
156
+ && _f.read ((char *)&dummy, sizeof (dummy))
157
+ && _f.read ((char *)bytes, sizeof (bytes)))
158
+ return UUID (bytes);
159
+
160
+ // expand file
161
+ _f.clear ();
162
+ _f.seekp (0 , std::fstream::end);
163
+ dummy = 0 ;
164
+ while (_f.tellp () < offset)
165
+ if (!_f.write ((const char *)&dummy, sizeof (dummy)))
166
+ return generate (); // should never happen
167
+
168
+ // write new UUID
169
+ _f.seekp (offset);
170
+ state = 1 ;
171
+ generate (bytes);
172
+ _f.write ((const char *)&state, sizeof (state));
173
+ _f.write ((const char *)&dummy, sizeof (dummy));
174
+ _f.write ((const char *)&bytes, sizeof (bytes));
175
+ _f.flush ();
176
+ return UUID (bytes);
177
+ }
178
+
179
+ void UUIDFactory::generate (uint8_t bytes[8 ]) const
180
+ {
181
+ for (size_t i=0 ; i<8 ; i++)
182
+ bytes[i] = randByte ();
183
+ }
184
+
185
+ UUID UUIDFactory::generate () const
186
+ {
187
+ uint8_t bytes[8 ];
188
+ generate (bytes);
189
+ return UUID (bytes);
190
+ }
191
+
192
+ uint8_t UUIDFactory::hash (const std::string& name) const
193
+ {
194
+ uint8_t res = 0 ;
195
+ for (char c: name) {
196
+ res = (res >> 7 ) | (res << 1 );
197
+ res = (uint8_t )(res + (uint8_t )c);
82
198
}
199
+ return res;
200
+ }
201
+
202
+ #ifdef __EMSCRIPTEN__
203
+ UUIDFactory::UUIDFactory ()
204
+ {
205
+ /* js crypto needs no init from C */
206
+ }
207
+
208
+ uint8_t UUIDFactory::randByte () const
209
+ {
210
+ return (uint8_t )EM_ASM_INT ({
211
+ var buf = new Uint8Array (1 );
212
+ crypto.getRandomValues (buf);
213
+ return buf[0 ];
214
+ });
83
215
}
84
- f = nullptr ;
85
216
#else
86
- #error TODO: implement localStorage API
217
+ UUIDFactory::UUIDFactory ()
218
+ {
219
+ srand ((unsigned int ) time (NULL ));
220
+ }
221
+
222
+ uint8_t UUIDFactory::randByte () const
223
+ {
224
+ return rand ();
225
+ }
87
226
#endif
88
- return uuid;
227
+ }
228
+
229
+ static std::string ap_get_uuid (const std::string& uuidFile, const std::string& host=" " )
230
+ {
231
+ AP::UUIDFactory::instance ()->setFilename (uuidFile);
232
+ return AP::UUIDFactory::instance ()->getPersistentUUID (host).string ();
89
233
}
90
234
91
235
#endif // _APUUID_HPP
0 commit comments