Skip to content

Commit c737e99

Browse files
committed
Major changes, no longer uses mac ids to generate ids, instead rely on consumer to provide a machine id or fallbacks to ipv4
1 parent abe3acc commit c737e99

File tree

6 files changed

+149
-50
lines changed

6 files changed

+149
-50
lines changed

binding.gyp

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
"cflags!": [ "-fno-exceptions" ],
55
"cflags_cc!": [ "-fno-exceptions" ],
66
"sources": [
7-
"cppsrc/main.cpp"
7+
"cppsrc/main.cpp",
8+
"cppsrc/generate_hash.cpp",
9+
"cppsrc/ipaddress.cpp"
10+
],
11+
"cflags_cc": [
12+
"-std=c++17"
813
],
914
'include_dirs': [
1015
"<!@(node -p \"require('node-addon-api').include\")"

cppsrc/generate_hash.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "generate_hash.h"
2+
3+
int generate_hash(const char *str, int s)
4+
{
5+
int result = 0;
6+
const int prime = 31;
7+
for (int i = 0; i < s; ++i)
8+
{
9+
result = str[i] + (result * prime);
10+
}
11+
12+
return result;
13+
}

cppsrc/generate_hash.h

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
int generate_hash(const char *str, int s);

cppsrc/ipaddress.cpp

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <arpa/inet.h>
2+
#include <netinet/in.h>
3+
#include <sys/socket.h>
4+
#include <unistd.h>
5+
6+
#include <cstring>
7+
#include <iostream>
8+
9+
#include "ipaddress.h"
10+
11+
GET_IP_RES getIP(char *ip)
12+
{
13+
int sock = socket(PF_INET, SOCK_DGRAM, 0);
14+
sockaddr_in loopback;
15+
16+
if (sock == -1)
17+
{
18+
std::cerr << "Could not socket\n";
19+
return GET_IP_RES::ERR_COULD_NOT_SOCKET;
20+
}
21+
22+
std::memset(&loopback, 0, sizeof(loopback));
23+
loopback.sin_family = AF_INET;
24+
loopback.sin_addr.s_addr = INADDR_LOOPBACK; // using loopback ip address
25+
loopback.sin_port = htons(9); // using debug port
26+
27+
if (connect(sock, reinterpret_cast<sockaddr *>(&loopback), sizeof(loopback)) == -1)
28+
{
29+
close(sock);
30+
std::cerr << "Could not connect\n";
31+
return GET_IP_RES::ERR_COULD_NOT_CONNECT;
32+
}
33+
34+
socklen_t addrlen = sizeof(loopback);
35+
if (getsockname(sock, reinterpret_cast<sockaddr *>(&loopback), &addrlen) == -1)
36+
{
37+
close(sock);
38+
std::cerr << "Could not getsockname\n";
39+
return GET_IP_RES::ERR_COULD_NOT_GET_SOCKETNAME;
40+
}
41+
42+
close(sock);
43+
44+
char buf[INET_ADDRSTRLEN];
45+
if (inet_ntop(AF_INET, &loopback.sin_addr, buf, INET_ADDRSTRLEN) == 0x0)
46+
{
47+
std::cerr << "Could not inet_ntop\n";
48+
return GET_IP_RES::ERR_COULD_NOT_GET_INET_NTOP;
49+
}
50+
else
51+
{
52+
std::strcpy(ip, buf);
53+
return GET_IP_RES::SUCCESS;
54+
}
55+
}

cppsrc/ipaddress.h

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
enum class GET_IP_RES : int
4+
{
5+
SUCCESS,
6+
ERR_COULD_NOT_SOCKET,
7+
ERR_COULD_NOT_CONNECT,
8+
ERR_COULD_NOT_GET_SOCKETNAME,
9+
ERR_COULD_NOT_GET_INET_NTOP
10+
};
11+
12+
// Returns IPv4 of the current machine
13+
GET_IP_RES getIP(char *);

cppsrc/main.cpp

+59-49
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,37 @@
99
#include <chrono>
1010
#include <thread>
1111

12+
#include "ipaddress.h"
13+
#include "generate_hash.h"
14+
1215
// ////////////////////////////////////////////////////////////////////////////////////////
1316

1417
/**
1518
* Total number of bits allocated to an ID
1619
*/
17-
int TOTAL_BITS = 64;
20+
constexpr int TOTAL_BITS = 64;
1821

1922
/**
2023
* Total number of bits allocated to an epoch timestamp
2124
*/
22-
int EPOCH_BITS = 42;
25+
constexpr int EPOCH_BITS = 42;
2326

2427
/**
2528
* Total number of bits allocated to an node/machine id
2629
*/
27-
int NODE_ID_BITS = 10;
30+
constexpr int NODE_ID_BITS = 12;
2831

2932
/**
3033
* Total number of bits allocated to an sequencing
3134
*/
32-
int SEQUENCE_BITS = 12;
35+
constexpr int SEQUENCE_BITS = 10;
3336

3437
/**
3538
* Max node that can be used
3639
*/
37-
uint64_t maxNodeId = std::pow(2, NODE_ID_BITS) - 1;
40+
constexpr uint64_t maxNodeId = (1 << NODE_ID_BITS) - 1;
3841

39-
uint64_t maxSequence = std::pow(2, SEQUENCE_BITS) - 1;
42+
constexpr uint64_t maxSequence = (1 << SEQUENCE_BITS) - 1;
4043

4144
// ////////////////////////////////////////////////////////////////////////////////////////
4245

@@ -46,9 +49,11 @@ uint64_t maxSequence = std::pow(2, SEQUENCE_BITS) - 1;
4649
* so that the hashed value is always smaller than maxNodeID
4750
* which is 10 bits in size
4851
*/
49-
int nodeID(std::string macID)
52+
int nodeID()
5053
{
51-
return std::hash<std::string>()(macID) % maxNodeId;
54+
char ip[16];
55+
getIP(ip);
56+
return generate_hash(ip, 16) & maxNodeId;
5257
}
5358

5459
// ////////////////////////////////////////////////////////////////////////////////////////
@@ -69,19 +74,18 @@ class Snowflake : public Napi::ObjectWrap<Snowflake>
6974
private:
7075
static Napi::FunctionReference constructor;
7176
uint64_t _lastTimestamp;
72-
uint64_t _CUSTOM_EPOCH;
73-
int _sequence;
74-
std::string _macID;
75-
int _nodeID;
77+
uint64_t _CUSTOM_EPOCH = 1546300800000;
78+
uint16_t _sequence;
79+
uint16_t _nodeID;
7680
Napi::Value getUniqueIDBigInt(const Napi::CallbackInfo &info);
77-
Napi::Value getUniqueID(const Napi::CallbackInfo &info);
7881
Napi::Value getTimestampFromID(const Napi::CallbackInfo &info);
82+
Napi::Value getNodeIDFomID(const Napi::CallbackInfo &info);
7983
};
8084

8185
Napi::Object Snowflake::Init(Napi::Env env, Napi::Object exports)
8286
{
8387
// This method is used to hook the accessor and method callbacks
84-
Napi::Function func = DefineClass(env, "Snowflake", {InstanceMethod("getUniqueIDBigInt", &Snowflake::getUniqueIDBigInt), InstanceMethod("getUniqueID", &Snowflake::getUniqueID), InstanceMethod("getTimestampFromID", &Snowflake::getTimestampFromID)});
88+
auto func = DefineClass(env, "Snowflake", {InstanceMethod("getUniqueID", &Snowflake::getUniqueIDBigInt), InstanceMethod("getTimestampFromID", &Snowflake::getTimestampFromID), InstanceMethod("getNodeIDFromID", &Snowflake::getNodeIDFomID)});
8589

8690
// Create a peristent reference to the class constructor. This will allow
8791
// a function called on a class prototype and a function
@@ -97,14 +101,20 @@ Napi::Object Snowflake::Init(Napi::Env env, Napi::Object exports)
97101

98102
Snowflake::Snowflake(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Snowflake>(info)
99103
{
100-
Napi::String value = info[0].As<Napi::String>();
101-
Napi::Number EPOCH = info[1].As<Napi::Number>();
104+
auto argLen = info.Length();
105+
this->_CUSTOM_EPOCH = info[0].As<Napi::Number>().Int64Value();
106+
switch (argLen)
107+
{
108+
case 2:
109+
this->_nodeID = info[1].As<Napi::Number>().Int32Value() & maxNodeId;
110+
break;
111+
default:
112+
this->_nodeID = nodeID();
113+
break;
114+
}
102115

103-
this->_macID = value.Utf8Value();
104-
this->_nodeID = nodeID(this->_macID);
105116
this->_lastTimestamp = 0;
106117
this->_sequence = 0;
107-
this->_CUSTOM_EPOCH = EPOCH.Int64Value();
108118
}
109119

110120
Napi::FunctionReference Snowflake::constructor;
@@ -113,25 +123,25 @@ Napi::FunctionReference Snowflake::constructor;
113123
* Takes the current timestamp, last timestamp, sequence, and macID
114124
* and generates a 64 bit long integer by performing bitwise operations
115125
*
116-
* First 42 bits are filled with current timestamp
117-
* Next 10 bits are filled with the node/machine id (max size can be 1024)
126+
* First 41 bits are filled with current timestamp
127+
* Next 10 bits are filled with the node/machine id (max size can be 4096)
118128
* Next 12 bits are filled with sequence which ensures that even if timestamp didn't change the value will be generated
119129
*
120-
* Function can theorotically generate 4096 unique values within a millisecond without repeating values
130+
* Function can theorotically generate 1024 unique values within a millisecond without repeating values
121131
*/
122132
Napi::Value Snowflake::getUniqueIDBigInt(const Napi::CallbackInfo &info)
123133
{
124-
Napi::Env env = info.Env();
134+
auto env = info.Env();
125135

126136
uint64_t currentTimestamp = getCurrentTime() - this->_CUSTOM_EPOCH;
127137

128138
if (currentTimestamp == this->_lastTimestamp)
129139
{
130140
this->_sequence = (this->_sequence + 1) & maxSequence;
131-
if (this->_sequence == 0)
141+
if (!this->_sequence)
132142
{
133143
std::this_thread::sleep_for(std::chrono::milliseconds(1));
134-
currentTimestamp++;
144+
++currentTimestamp;
135145
}
136146
}
137147
else
@@ -141,58 +151,57 @@ Napi::Value Snowflake::getUniqueIDBigInt(const Napi::CallbackInfo &info)
141151

142152
this->_lastTimestamp = currentTimestamp;
143153

144-
uint64_t id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS);
154+
uint64_t id{};
155+
id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS);
145156
id |= (this->_nodeID << (TOTAL_BITS - EPOCH_BITS - NODE_ID_BITS));
146157
id |= this->_sequence;
147158

148159
return Napi::BigInt::New(env, id);
149160
}
150161

151162
/**
152-
* Convert generated number 64 bit integer to a string
163+
* Returns timestamp at which the id was generated by retreiving
164+
* the first 41 bits of the id, which are filled with current timestamp
165+
* bits
153166
*/
154-
Napi::Value Snowflake::getUniqueID(const Napi::CallbackInfo &info)
167+
Napi::Value Snowflake::getTimestampFromID(const Napi::CallbackInfo &info)
155168
{
156-
Napi::Env env = info.Env();
169+
auto env = info.Env();
170+
uint64_t uniqueID{};
157171

158-
uint64_t currentTimestamp = getCurrentTime() - this->_CUSTOM_EPOCH;
172+
if (info[0].IsString())
173+
{
174+
auto first = info[0].As<Napi::String>();
159175

160-
if (currentTimestamp == this->_lastTimestamp)
176+
uniqueID = std::stoull(first.Utf8Value());
177+
}
178+
else if (info[0].IsNumber())
161179
{
162-
this->_sequence = (this->_sequence + 1) & maxSequence;
163-
if (this->_sequence == 0)
164-
{
165-
std::this_thread::sleep_for(std::chrono::milliseconds(1));
166-
currentTimestamp++;
167-
}
180+
uniqueID = info[0].As<Napi::Number>().Int64Value();
168181
}
169182
else
170183
{
171-
this->_sequence = 0;
184+
Napi::TypeError::New(env, "Number or string expected").ThrowAsJavaScriptException();
172185
}
173186

174-
this->_lastTimestamp = currentTimestamp;
175-
176-
uint64_t id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS);
177-
id |= (this->_nodeID << (TOTAL_BITS - EPOCH_BITS - NODE_ID_BITS));
178-
id |= this->_sequence;
187+
uint64_t timestamp = uniqueID >> (TOTAL_BITS - EPOCH_BITS);
179188

180-
return Napi::String::New(env, std::to_string(id));
189+
return Napi::Number::New(env, timestamp + _CUSTOM_EPOCH);
181190
}
182191

183192
/**
184193
* Returns timestamp at which the id was generated by retreiving
185194
* the first 42 bits of the id, which are filled with current timestamp
186195
* bits
187196
*/
188-
Napi::Value Snowflake::getTimestampFromID(const Napi::CallbackInfo &info)
197+
Napi::Value Snowflake::getNodeIDFomID(const Napi::CallbackInfo &info)
189198
{
190-
Napi::Env env = info.Env();
199+
auto env = info.Env();
191200
uint64_t uniqueID = 0;
192201

193202
if (info[0].IsString())
194203
{
195-
Napi::String first = info[0].As<Napi::String>();
204+
auto first = info[0].As<Napi::String>();
196205

197206
uniqueID = std::stoull(first.Utf8Value());
198207
}
@@ -205,9 +214,10 @@ Napi::Value Snowflake::getTimestampFromID(const Napi::CallbackInfo &info)
205214
Napi::TypeError::New(env, "Number or string expected").ThrowAsJavaScriptException();
206215
}
207216

208-
uint64_t timestamp = uniqueID >> (TOTAL_BITS - EPOCH_BITS);
217+
int BITS = TOTAL_BITS - NODE_ID_BITS - SEQUENCE_BITS;
218+
uint16_t machineID = (uniqueID << BITS) >> (BITS + SEQUENCE_BITS);
209219

210-
return Napi::Number::New(env, timestamp);
220+
return Napi::Number::New(env, machineID);
211221
}
212222

213223
// ////////////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)