Discord Snowflake Analysis (Go + Py)

What are Discord Snowflakes?


Discord Snowflakes are a form of unique identifier used by Discord to track and reference various entities such as messages, users, and servers. The system is based on Twitter's Snowflake concept, which generates unique IDs that are also time sortable.

Snowflake Structure


The actual ID (or “snowflake”) is a 64-bit integer, and its structure is important. In the provided format:

  • The first part is the time since the custom epoch, left-shifted 22 bits. This gives the IDs their time-sortable property.
  • The next parts are the worker ID and process ID, which are also left-shifted by 17 and 12 bits, respectively.
  • The last part is the increment for that millisecond.

Code


Python:

from dataclasses import dataclass
from datetime import datetime, timezone, timedelta
from typing import ClassVar, Tuple
from time import time

@dataclass
class SnowflakeGenerator:
    EPOCH: ClassVar[datetime] = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(milliseconds=1420070400000)
    worker_id: int = 1  
    process_id: int = 1 
    increment: int = 0  

    @classmethod
    def generate_snowflake(cls) -> int:
        now = int(time() * 1000)
        elapsed = now - int(cls.EPOCH.timestamp() * 1000)

        # Construct the snowflake ID
        snowflake = (elapsed << 22) | (cls.worker_id << 17) | (cls.process_id << 12) | cls.increment
        cls.increment = (cls.increment + 1) & 0xFFF  # Increment the counter, and wrap it every 4096

        return snowflake

    @classmethod
    def reverse_snowflake(cls, snowflake: int) -> Tuple[datetime, int, int, int]:
        timestamp = (snowflake >> 22) + int(cls.EPOCH.timestamp() * 1000)
        worker_id = (snowflake & 0x3E0000) >> 17
        process_id = (snowflake & 0x1F000) >> 12
        increment = snowflake & 0xFFF

        return (datetime.fromtimestamp(timestamp / 1000, tz=timezone.utc), worker_id, process_id, increment)

Golang:

package main

import (
    "fmt"
    "time"
)

const (
    discordEpoch    int64 = 1420070400000 // Discord epoch (Jan 1, 2015) in milliseconds
    workerIDShift         = 17
    processIDShift        = 12
    incrementMask         = 0xFFF
)

var (
    workerID  int64 = 1 
    processID int64 = 1 
    increment int64 = 0 
)

type SnowflakeInfo struct {
    Time      time.Time 
    WorkerID  int64    
    ProcessID int64     
    Increment int64     
}

func generateSnowflake() int64 {
    now := time.Now().UnixNano() / int64(time.Millisecond)
    elapsed := now - discordEpoch
    snowflake := (elapsed << 22) | (workerID << workerIDShift) | (processID << processIDShift) | increment
    increment = (increment + 1) & incrementMask 

    return snowflake
}

func reverseSnowflake(snowflake int64) SnowflakeInfo {
    timestamp := (snowflake >> 22) + discordEpoch
    workerID := (snowflake & (0x3E0000)) >> workerIDShift
    processID := (snowflake & (0x1F000)) >> processIDShift
    increment := snowflake & incrementMask

    return SnowflakeInfo{
        Time:      time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)),
        WorkerID:  workerID,
        ProcessID: processID,
        Increment: increment,
    }
}

func main() {
    for i := 0; i < 100; i++ {
        snowflakeID := generateSnowflake()
        fmt.Printf("Generated Snowflake ID: %d\n", snowflakeID)
        snowflakeDetails := reverseSnowflake(snowflakeID)
        fmt.Printf("Details of Reversed Snowflake: Timestamp - %s, Worker ID - %d, Process ID - %d, Increment - %d\n",
            snowflakeDetails.Time, snowflakeDetails.WorkerID, snowflakeDetails.ProcessID, snowflakeDetails.Increment)
        time.Sleep(time.Millisecond)
    }
}
3 Likes