Skip to content

Commit addadfe

Browse files
committed
uuid: use fstream instead of FILE* and allow uuid-per-room
1 parent e2358a8 commit addadfe

File tree

1 file changed

+195
-51
lines changed

1 file changed

+195
-51
lines changed

apuuid.hpp

Lines changed: 195 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,18 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1010
#ifndef _APUUID_HPP
1111
#define _APUUID_HPP
1212

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+
*/
1319

1420
#include <stdint.h>
1521
#include <stdio.h>
1622
#include <string>
23+
#include <memory>
24+
#include <fstream>
1725

1826

1927
#ifdef __EMSCRIPTEN__
@@ -25,67 +33,203 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
2533
#endif
2634

2735

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+
}
3651

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() {}
4953

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();
5485
}
55-
}
5686

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;
6697
}
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+
}
79145
}
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);
82198
}
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+
});
83215
}
84-
f = nullptr;
85216
#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+
}
87226
#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();
89233
}
90234

91235
#endif // _APUUID_HPP

0 commit comments

Comments
 (0)