golcg

- Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.acid.vegas/-c.git
Log | Files | Refs | Archive | README | LICENSE

golcg.go (2954B)

      1 package golcg
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"math/rand"
      7 	"net"
      8 	"os"
      9 	"path/filepath"
     10 	"strings"
     11 	"time"
     12 )
     13 
     14 type LCG struct {
     15 	M       uint32
     16 	A       uint32
     17 	C       uint32
     18 	Current uint32
     19 }
     20 
     21 func NewLCG(seed int, m uint32) *LCG {
     22 	return &LCG{
     23 		M:       1<<32 - 1,
     24 		A:       1664525,
     25 		C:       1013904223,
     26 		Current: uint32(seed),
     27 	}
     28 }
     29 
     30 func (l *LCG) Next() uint32 {
     31 	l.Current = (l.A*l.Current + l.C) % l.M
     32 	return l.Current
     33 }
     34 
     35 type IPRange struct {
     36 	Start uint32
     37 	Total uint32
     38 }
     39 
     40 func NewIPRange(cidr string) (*IPRange, error) {
     41 	_, network, err := net.ParseCIDR(cidr)
     42 	if err != nil {
     43 		return nil, err
     44 	}
     45 
     46 	start := ipToUint32(network.IP)
     47 	ones, bits := network.Mask.Size()
     48 	hostBits := uint(bits - ones)
     49 
     50 	var total uint32
     51 	if hostBits == 32 {
     52 		total = 0
     53 	} else {
     54 		total = 1 << hostBits
     55 	}
     56 
     57 	return &IPRange{
     58 		Start: start,
     59 		Total: total,
     60 	}, nil
     61 }
     62 
     63 func (r *IPRange) GetIPAtIndex(index uint32) (string, error) {
     64 	if r.Total > 0 && index >= r.Total {
     65 		return "", errors.New("IP index out of range")
     66 	}
     67 
     68 	ip := uint32ToIP(r.Start + index)
     69 	return ip.String(), nil
     70 }
     71 
     72 func ipToUint32(ip net.IP) uint32 {
     73 	ip = ip.To4()
     74 	return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3])
     75 }
     76 
     77 func uint32ToIP(n uint32) net.IP {
     78 	ip := make(net.IP, 4)
     79 	ip[0] = byte(n >> 24)
     80 	ip[1] = byte(n >> 16)
     81 	ip[2] = byte(n >> 8)
     82 	ip[3] = byte(n)
     83 	return ip
     84 }
     85 
     86 func SaveState(seed int, cidr string, shard int, total int, lcgCurrent uint32) error {
     87 	fileName := fmt.Sprintf("golcg_%d_%s_%d_%d.state", seed, strings.Replace(cidr, "/", "_", -1), shard, total)
     88 	stateFile := filepath.Join(os.TempDir(), fileName)
     89 
     90 	return os.WriteFile(stateFile, []byte(fmt.Sprintf("%d", lcgCurrent)), 0644)
     91 }
     92 
     93 func IPStream(cidr string, shardNum, totalShards, seed int, state *uint32) (<-chan string, error) {
     94 	ipRange, err := NewIPRange(cidr)
     95 	if err != nil {
     96 		return nil, err
     97 	}
     98 
     99 	shardIndex := shardNum - 1
    100 
    101 	if seed == 0 {
    102 		rand.Seed(time.Now().UnixNano())
    103 		seed = rand.Intn(1<<32 - 1)
    104 	}
    105 
    106 	lcg := NewLCG(seed+shardIndex, 1<<32-1)
    107 	if state != nil {
    108 		lcg.Current = *state
    109 	}
    110 
    111 	var shardSize uint32
    112 	if ipRange.Total == 0 {
    113 		shardSize = (1<<32 - 1) / uint32(totalShards)
    114 		if uint32(shardIndex) < uint32(totalShards-1) {
    115 			shardSize++
    116 		}
    117 	} else {
    118 		shardSize = ipRange.Total / uint32(totalShards)
    119 		if uint32(shardIndex) < ipRange.Total%uint32(totalShards) {
    120 			shardSize++
    121 		}
    122 	}
    123 
    124 	out := make(chan string, 1000)
    125 	go func() {
    126 		defer close(out)
    127 		remaining := shardSize
    128 
    129 		for remaining > 0 {
    130 			next := lcg.Next()
    131 			var index uint32
    132 			if ipRange.Total > 0 {
    133 				index = next % ipRange.Total
    134 			} else {
    135 				index = next
    136 			}
    137 
    138 			if totalShards == 1 || index%uint32(totalShards) == uint32(shardIndex) {
    139 				ip, err := ipRange.GetIPAtIndex(index)
    140 				if err != nil {
    141 					continue
    142 				}
    143 				out <- ip
    144 				remaining--
    145 
    146 				if remaining%1000 == 0 {
    147 					SaveState(seed, cidr, shardNum, totalShards, lcg.Current)
    148 				}
    149 			}
    150 		}
    151 	}()
    152 
    153 	return out, nil
    154 }