Skip to content

Commit

Permalink
✨ feat: add unify functions uuid #5
Browse files Browse the repository at this point in the history
  • Loading branch information
pnguyen215 committed Nov 7, 2024
1 parent 83e18dd commit 85161fb
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 1 deletion.
87 changes: 87 additions & 0 deletions test/uuid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package example_test

import (
"sync"
"testing"

"github.com/sivaosorg/unify4g"
)

func TestGenerateUUID(t *testing.T) {
var wg sync.WaitGroup
numTests := 100
uniqueIDs := make(map[string]struct{})
mu := sync.Mutex{}

for i := 0; i < numTests; i++ {
wg.Add(1)
go func() {
defer wg.Done()
id, _ := unify4g.GenerateUUID()
mu.Lock()
if _, exists := uniqueIDs[id]; exists {
t.Errorf("Duplicate ID generated: %s", id)
}
uniqueIDs[id] = struct{}{}
mu.Unlock()
}()
}

wg.Wait()
if len(uniqueIDs) != numTests {
t.Errorf("Expected %d unique IDs but got %d", numTests, len(uniqueIDs))
}
}

func TestGenerateTimestampID(t *testing.T) {
var wg sync.WaitGroup
numTests := 100
uniqueIDs := make(map[string]struct{})
mu := sync.Mutex{}

for i := 0; i < numTests; i++ {
wg.Add(1)
go func() {
defer wg.Done()
id := unify4g.GenerateTimestampID()
mu.Lock()
if _, exists := uniqueIDs[id]; exists {
t.Errorf("Duplicate ID generated: %s", id)
}
uniqueIDs[id] = struct{}{}
mu.Unlock()
}()
}

wg.Wait()
if len(uniqueIDs) != numTests {
t.Errorf("Expected %d unique IDs but got %d", numTests, len(uniqueIDs))
}
}

// Recommended
func TestGenerateCryptoID(t *testing.T) {
var wg sync.WaitGroup
numTests := 100
uniqueIDs := make(map[string]struct{})
mu := sync.Mutex{}

for i := 0; i < numTests; i++ {
wg.Add(1)
go func() {
defer wg.Done()
id := unify4g.GenerateCryptoID()
mu.Lock()
if _, exists := uniqueIDs[id]; exists {
t.Errorf("Duplicate ID generated: %s", id)
}
uniqueIDs[id] = struct{}{}
mu.Unlock()
}()
}

wg.Wait()
if len(uniqueIDs) != numTests {
t.Errorf("Expected %d unique IDs but got %d", numTests, len(uniqueIDs))
}
}
126 changes: 125 additions & 1 deletion uuid.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package unify4g

import (
cr "crypto/rand"
"encoding/hex"
"fmt"
"log"
"math/rand"
"os"
"time"
)

// GenerateUUID generates a new universally unique identifier (UUID) using random data from /dev/urandom (Unix-based systems).
Expand Down Expand Up @@ -30,6 +35,37 @@ import (
// - This function is designed for Unix-based systems. On non-Unix systems, this may not work because /dev/urandom
// may not be available.
func GenerateUUID() (string, error) {
dash := "-"
return GenerateUUIDDelimiter(dash)
}

// GenerateUUIDDelimiter generates a new universally unique identifier (UUID) using random data from /dev/urandom
// (Unix-based systems) with a customizable delimiter.
//
// This function is similar to GenerateUUID but allows the user to specify a custom delimiter to separate
// different sections of the UUID. It opens the special file /dev/urandom to read 16 random bytes,
// which are then used to construct a UUID. The UUID is returned as a string in the format:
// XXXXXXXX<delimiter>XXXX<delimiter>XXXX<delimiter>XXXX<delimiter>XXXXXXXXXXXX, where X is a hexadecimal digit.
//
// Parameters:
// - delimiter: A string used to separate sections of the UUID. Common choices are "-" or "" (no delimiter).
//
// Returns:
// - A string representing the newly generated UUID with the specified delimiter.
// - An error if there is an issue opening or reading from /dev/urandom.
//
// Example:
//
// uuid, err := GenerateUUIDDelimiter("-")
// if err != nil {
// log.Fatalf("Failed to generate UUID: %v", err)
// }
// fmt.Println("Generated UUID:", uuid)
//
// Notes:
// - This function is designed for Unix-based systems. On non-Unix systems, it may not work because /dev/urandom
// may not be available.
func GenerateUUIDDelimiter(delimiter string) (string, error) {
file, err := os.Open("/dev/urandom")
if err != nil {
return "", fmt.Errorf("open /dev/urandom error:[%v]", err)
Expand All @@ -44,6 +80,94 @@ func GenerateUUID() (string, error) {
if err != nil {
return "", err
}
uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
// Format the bytes as a UUID string with the specified delimiter.
// The UUID is structured as XXXXXXXX<delimiter>XXXX<delimiter>XXXX<delimiter>XXXX<delimiter>XXXXXXXXXXXX.
uuid := fmt.Sprintf("%x%s%x%s%x%s%x%s%x", b[0:4], delimiter, b[4:6], delimiter, b[6:8], delimiter, b[8:10], delimiter, b[10:])
return uuid, nil
}

// GenerateRandomID generates a random alphanumeric string of the specified length.
// This string includes uppercase letters, lowercase letters, and numbers, making it
// suitable for use as unique IDs or tokens.
//
// Parameters:
// - length: The length of the random ID to generate. Must be a positive integer.
//
// Returns:
// - A string of random alphanumeric characters with the specified length.
//
// The function uses a custom random source seeded with the current Unix timestamp
// in nanoseconds to ensure that each call produces a unique sequence.
// This function is intended to generate random strings quickly and is not
// cryptographically secure.
//
// Example:
//
// id := GenerateRandomID(16)
// fmt.Println("Generated Random ID:", id)
//
// Notes:
// - This function is suitable for use cases where simple random IDs are needed.
// However, for cryptographic purposes, consider using more secure random generation.
func GenerateRandomID(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) // Create a seeded random generator for unique results each call
// Allocate a byte slice for the generated ID and populate it with random characters
id := make([]byte, length)
for i := range id {
id[i] = charset[seededRand.Intn(len(charset))]
}
return string(id)
}

// GenerateCryptoID generates a cryptographically secure random ID as a hexadecimal string.
// It uses 16 random bytes, which are then encoded to a hexadecimal string for easy representation.
//
// Returns:
// - A string representing a secure random hexadecimal ID of 32 characters (since 16 bytes are used, and each byte
// is represented by two hexadecimal characters).
//
// The function uses crypto/rand.Read to ensure cryptographic security in the generated ID, making it suitable for
// sensitive use cases such as API keys, session tokens, or any security-critical identifiers.
//
// Example:
//
// id := GenerateCryptoID()
// fmt.Println("Generated Crypto ID:", id)
//
// Notes:
// - This function is suitable for use cases where high security is required in the generated ID.
// - It is not recommended for use cases where deterministic or non-cryptographic IDs are preferred.
func GenerateCryptoID() string {
bytes := make([]byte, 16)
// Use crypto/rand.Read for cryptographically secure random byte generation.
if _, err := cr.Read(bytes); err != nil {
log.Fatalf("Failed to generate secure random bytes: %v", err)
return ""
}
return hex.EncodeToString(bytes)
}

// GenerateTimestampID generates a unique identifier based on the current Unix timestamp in nanoseconds,
// with an additional random integer to enhance uniqueness.
//
// This function captures the current time in nanoseconds since the Unix epoch and appends a random integer
// to ensure additional randomness and uniqueness, even if called in rapid succession. The result is returned
// as a string. This type of ID is well-suited for time-based ordering and can be useful for generating
// unique identifiers for logs, events, or non-cryptographic applications.
//
// Returns:
// - A string representing the current Unix timestamp in nanoseconds, concatenated with a random integer.
//
// Example:
//
// id := GenerateTimestampID()
// fmt.Println("Generated Timestamp ID:", id)
//
// Notes:
// - This function provides a unique, time-ordered identifier, but it is not suitable for cryptographic use.
// - The combination of the current time and a random integer is best suited for applications requiring
// uniqueness and ordering, rather than secure identifiers.
func GenerateTimestampID() string {
return fmt.Sprintf("%d%d", time.Now().UnixNano(), nextInt())
}

0 comments on commit 85161fb

Please sign in to comment.