diff --git a/linkgen/linkgen.go b/linkgen/linkgen.go new file mode 100644 index 0000000..5240a54 --- /dev/null +++ b/linkgen/linkgen.go @@ -0,0 +1,125 @@ +package linkgen + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + + "github.com/parkan/go-hauk/config" + "github.com/parkan/go-hauk/store" +) + +const ( + alphaLower = "0123456789abcdefghijklmnopqrstuvwxyz" + alphaMixed = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + alphaUpper = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ" + prefixLocdata = "locdata-" +) + +type Generator struct { + store store.Store + style config.LinkStyle +} + +func New(s store.Store, style config.LinkStyle) *Generator { + return &Generator{store: s, style: style} +} + +func (g *Generator) Generate(ctx context.Context) (string, error) { + for { + s, err := g.generate() + if err != nil { + return "", err + } + exists, err := g.store.Exists(ctx, prefixLocdata+s) + if err != nil { + return "", err + } + if !exists { + return s, nil + } + } +} + +func (g *Generator) generate() (string, error) { + switch g.style { + case config.LinkUUIDv4: + return uuidV4() + case config.Link16Hex: + return hexString(8) + case config.Link16Lower: + return randomString(alphaLower, 16) + case config.Link16Mixed: + return randomString(alphaMixed, 16) + case config.Link16Upper: + return randomString(alphaUpper, 16) + case config.Link32Hex: + return hexString(16) + case config.Link32Lower: + return randomString(alphaLower, 32) + case config.Link32Mixed: + return randomString(alphaMixed, 32) + case config.Link32Upper: + return randomString(alphaUpper, 32) + case config.Link4Plus4Lower: + return fourPlusFour(alphaLower) + case config.Link4Plus4Mixed: + return fourPlusFour(alphaMixed) + case config.Link4Plus4Upper: + fallthrough + default: + return fourPlusFour(alphaUpper) + } +} + +func uuidV4() (string, error) { + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + return "", err + } + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + h := hex.EncodeToString(b) + return fmt.Sprintf("%s-%s-%s-%s-%s", h[0:8], h[8:12], h[12:16], h[16:20], h[20:32]), nil +} + +func hexString(n int) (string, error) { + b := make([]byte, n) + if _, err := rand.Read(b); err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} + +func randomString(alpha string, length int) (string, error) { + b := make([]byte, length) + for i := 0; i < length; i++ { + idx, err := randInt(len(alpha)) + if err != nil { + return "", err + } + b[i] = alpha[idx] + } + return string(b), nil +} + +func fourPlusFour(alpha string) (string, error) { + s, err := randomString(alpha, 8) + if err != nil { + return "", err + } + return s[:4] + "-" + s[4:], nil +} + +func randInt(max int) (int, error) { + b := make([]byte, 1) + for { + if _, err := rand.Read(b); err != nil { + return 0, err + } + if int(b[0]) < 256-(256%max) { + return int(b[0]) % max, nil + } + } +}