mirror of
https://github.com/schollz/croc.git
synced 2025-10-11 13:21:00 +02:00
use keypair to validate
This commit is contained in:
parent
60e31803c9
commit
30c9c3317f
3 changed files with 174 additions and 31 deletions
80
connect.go
80
connect.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -18,6 +19,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/schollz/messagebox/keypair"
|
||||||
"github.com/schollz/peerdiscovery"
|
"github.com/schollz/peerdiscovery"
|
||||||
"github.com/schollz/progressbar"
|
"github.com/schollz/progressbar"
|
||||||
tarinator "github.com/schollz/tarinator-go"
|
tarinator "github.com/schollz/tarinator-go"
|
||||||
|
@ -43,6 +45,8 @@ type Connection struct {
|
||||||
Wait bool
|
Wait bool
|
||||||
bar *progressbar.ProgressBar
|
bar *progressbar.ProgressBar
|
||||||
rate int
|
rate int
|
||||||
|
keypair keypair.KeyPair
|
||||||
|
encryptedPassword string
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileMetaData struct {
|
type FileMetaData struct {
|
||||||
|
@ -73,6 +77,7 @@ func NewConnection(config *AppConfig) (*Connection, error) {
|
||||||
c.Yes = config.Yes
|
c.Yes = config.Yes
|
||||||
c.rate = config.Rate
|
c.rate = config.Rate
|
||||||
c.Local = config.Local
|
c.Local = config.Local
|
||||||
|
c.keypair, _ = keypair.New()
|
||||||
|
|
||||||
if c.Local {
|
if c.Local {
|
||||||
c.Yes = true
|
c.Yes = true
|
||||||
|
@ -213,6 +218,10 @@ func (c *Connection) Run() error {
|
||||||
if err := SplitFile(c.File.Name+".enc", c.NumberOfConnections); err != nil {
|
if err := SplitFile(c.File.Name+".enc", c.NumberOfConnections); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// remove the file now since we still have pieces
|
||||||
|
if err := os.Remove(c.File.Name + ".enc"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// get file hash
|
// get file hash
|
||||||
var err error
|
var err error
|
||||||
|
@ -221,14 +230,10 @@ func (c *Connection) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// get file size
|
// get file size
|
||||||
c.File.Size, err = FileSize(c.File.Name + ".enc")
|
c.File.Size, err = FileSize(c.File.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// remove the file now since we still have pieces
|
|
||||||
if err := os.Remove(c.File.Name + ".enc"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove compressed archive
|
// remove compressed archive
|
||||||
if c.File.IsDir {
|
if c.File.IsDir {
|
||||||
|
@ -241,16 +246,19 @@ func (c *Connection) Run() error {
|
||||||
fmt.Fprintf(os.Stderr, "Code is: %s\n", c.Code)
|
fmt.Fprintf(os.Stderr, "Code is: %s\n", c.Code)
|
||||||
|
|
||||||
// broadcast local connection from sender
|
// broadcast local connection from sender
|
||||||
log.Debug("settings payload to ", c.Code)
|
if c.Server == "" {
|
||||||
go func() {
|
log.Debug("settings payload to ", c.Code)
|
||||||
go peerdiscovery.Discover(peerdiscovery.Settings{
|
go func() {
|
||||||
Limit: 1,
|
go peerdiscovery.Discover(peerdiscovery.Settings{
|
||||||
TimeLimit: 600 * time.Second,
|
Limit: 1,
|
||||||
Delay: 50 * time.Millisecond,
|
TimeLimit: 600 * time.Second,
|
||||||
Payload: []byte(c.Code),
|
Delay: 50 * time.Millisecond,
|
||||||
})
|
Payload: []byte(c.Code),
|
||||||
runClientError <- c.runClient("localhost")
|
})
|
||||||
}()
|
runClientError <- c.runClient("localhost")
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("checking code validity")
|
log.Debug("checking code validity")
|
||||||
|
@ -330,19 +338,19 @@ func (c *Connection) runClient(serverName string) error {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
encryptedMetaData, salt, iv := Encrypt(metaData, c.Code)
|
encryptedMetaData, salt, iv := Encrypt(metaData, c.Code)
|
||||||
sendMessage("s."+c.HashedCode+"."+hex.EncodeToString(encryptedMetaData)+"-"+salt+"-"+iv, connection)
|
sendMessage("s."+c.keypair.Public+"."+c.HashedCode+"."+hex.EncodeToString(encryptedMetaData)+"-"+salt+"-"+iv, connection)
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("telling relay (%s): %s", c.Server, "r."+c.Code)
|
log.Debugf("telling relay (%s): %s", c.Server, "r."+c.Code)
|
||||||
if c.Wait {
|
if c.Wait {
|
||||||
// tell server to wait for sender
|
// tell server to wait for sender
|
||||||
sendMessage("r."+c.HashedCode+".0.0.0", connection)
|
sendMessage("r."+c.keypair.Public+"."+c.HashedCode+".0.0.0", connection)
|
||||||
} else {
|
} else {
|
||||||
// tell server to cancel if sender doesn't exist
|
// tell server to cancel if sender doesn't exist
|
||||||
sendMessage("c."+c.HashedCode+".0.0.0", connection)
|
sendMessage("c."+c.keypair.Public+"."+c.HashedCode+".0.0.0", connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.IsSender { // this is a sender
|
if c.IsSender { // this is a sender
|
||||||
log.Debug("waiting for ok from relay")
|
log.Debugf("[%d] waiting for ok from relay", id)
|
||||||
message = receiveMessage(connection)
|
message = receiveMessage(connection)
|
||||||
if message == "timeout" {
|
if message == "timeout" {
|
||||||
responses.Lock()
|
responses.Lock()
|
||||||
|
@ -359,10 +367,33 @@ func (c *Connection) runClient(serverName string) error {
|
||||||
responses.gotConnectionInUse = true
|
responses.gotConnectionInUse = true
|
||||||
responses.Unlock()
|
responses.Unlock()
|
||||||
} else {
|
} else {
|
||||||
log.Debug("got ok from relay")
|
// message is IP address, lets check next message
|
||||||
|
log.Debugf("[%d] got ok from relay: %s", id, message)
|
||||||
|
publicKeyRecipient := receiveMessage(connection)
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
fmt.Fprintf(os.Stderr, "\nSending (->%s)..\n", message)
|
fmt.Fprintf(os.Stderr, "\nSending (->%s@%s)..\n", publicKeyRecipient, message)
|
||||||
|
// check if okay again
|
||||||
|
// TODO
|
||||||
|
encryptedPassword, err := c.keypair.Encrypt([]byte(RandStringBytesMaskImprSrc(20)), publicKeyRecipient)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// encrypt files
|
||||||
|
|
||||||
|
c.encryptedPassword = base64.StdEncoding.EncodeToString(encryptedPassword)
|
||||||
}
|
}
|
||||||
|
log.Debugf("[%d] waiting for 0 thread to encrypt", id)
|
||||||
|
for {
|
||||||
|
if c.encryptedPassword != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
log.Debugf("sending encrypted passphrase: %s", c.encryptedPassword)
|
||||||
|
sendMessage(c.encryptedPassword, connection)
|
||||||
|
// wait for relay go
|
||||||
|
receiveMessage(connection)
|
||||||
|
|
||||||
// wait for pipe to be made
|
// wait for pipe to be made
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
// Write data from file
|
// Write data from file
|
||||||
|
@ -399,6 +430,9 @@ func (c *Connection) runClient(serverName string) error {
|
||||||
} else if strings.Split(sendersAddress, ":")[0] == "127.0.0.1" {
|
} else if strings.Split(sendersAddress, ":")[0] == "127.0.0.1" {
|
||||||
sendersAddress = strings.Replace(sendersAddress, "127.0.0.1", c.Server, 1)
|
sendersAddress = strings.Replace(sendersAddress, "127.0.0.1", c.Server, 1)
|
||||||
}
|
}
|
||||||
|
// now get public key
|
||||||
|
publicKeySender := receiveMessage(connection)
|
||||||
|
|
||||||
// have the main thread ask for the okay
|
// have the main thread ask for the okay
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
encryptedBytes, err := hex.DecodeString(encryptedData)
|
encryptedBytes, err := hex.DecodeString(encryptedData)
|
||||||
|
@ -436,6 +470,7 @@ func (c *Connection) runClient(serverName string) error {
|
||||||
fmt.Fprintf(os.Stderr, "Will not overwrite file!")
|
fmt.Fprintf(os.Stderr, "Will not overwrite file!")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "incoming file from "+publicKeySender+"\n")
|
||||||
getOK := "y"
|
getOK := "y"
|
||||||
if !c.Yes {
|
if !c.Yes {
|
||||||
getOK = getInput("ok? (y/n): ")
|
getOK = getInput("ok? (y/n): ")
|
||||||
|
@ -466,6 +501,9 @@ func (c *Connection) runClient(serverName string) error {
|
||||||
if !gotOK {
|
if !gotOK {
|
||||||
sendMessage("not ok", connection)
|
sendMessage("not ok", connection)
|
||||||
} else {
|
} else {
|
||||||
|
sendMessage("ok", connection)
|
||||||
|
c.encryptedPassword = receiveMessage(connection)
|
||||||
|
log.Debugf("[%d] got encrypted passphrase: %s", id, c.encryptedPassword)
|
||||||
sendMessage("ok", connection)
|
sendMessage("ok", connection)
|
||||||
log.Debug("receive file")
|
log.Debug("receive file")
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
|
|
94
relay.go
94
relay.go
|
@ -19,6 +19,10 @@ type connectionMap struct {
|
||||||
sender map[string]net.Conn
|
sender map[string]net.Conn
|
||||||
metadata map[string]string
|
metadata map[string]string
|
||||||
potentialReceivers map[string]struct{}
|
potentialReceivers map[string]struct{}
|
||||||
|
rpublicKey map[string]string
|
||||||
|
spublicKey map[string]string
|
||||||
|
passphrase map[string]string
|
||||||
|
receiverReady map[string]bool
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +68,11 @@ func (r *Relay) Run() {
|
||||||
r.connections.receiver = make(map[string]net.Conn)
|
r.connections.receiver = make(map[string]net.Conn)
|
||||||
r.connections.sender = make(map[string]net.Conn)
|
r.connections.sender = make(map[string]net.Conn)
|
||||||
r.connections.metadata = make(map[string]string)
|
r.connections.metadata = make(map[string]string)
|
||||||
|
r.connections.spublicKey = make(map[string]string)
|
||||||
|
r.connections.rpublicKey = make(map[string]string)
|
||||||
|
r.connections.passphrase = make(map[string]string)
|
||||||
r.connections.potentialReceivers = make(map[string]struct{})
|
r.connections.potentialReceivers = make(map[string]struct{})
|
||||||
|
r.connections.receiverReady = make(map[string]bool)
|
||||||
r.connections.Unlock()
|
r.connections.Unlock()
|
||||||
r.runServer()
|
r.runServer()
|
||||||
}
|
}
|
||||||
|
@ -124,12 +132,13 @@ func (r *Relay) clientCommuncation(id int, connection net.Conn) {
|
||||||
|
|
||||||
sendMessage("who?", connection)
|
sendMessage("who?", connection)
|
||||||
m := strings.Split(receiveMessage(connection), ".")
|
m := strings.Split(receiveMessage(connection), ".")
|
||||||
if len(m) < 3 {
|
if len(m) < 4 {
|
||||||
logger.Debug("exiting, not enough information")
|
logger.Debug("exiting, not enough information")
|
||||||
sendMessage("not enough information", connection)
|
sendMessage("not enough information", connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connectionType, codePhrase, metaData := m[0], m[1], m[2]
|
connectionType, publicKey, codePhrase, metaData := m[0], m[1], m[2], m[3]
|
||||||
|
logger.Debugf("got connection from %s", publicKey)
|
||||||
key := codePhrase + "-" + strconv.Itoa(id)
|
key := codePhrase + "-" + strconv.Itoa(id)
|
||||||
|
|
||||||
switch connectionType {
|
switch connectionType {
|
||||||
|
@ -142,9 +151,11 @@ func (r *Relay) clientCommuncation(id int, connection net.Conn) {
|
||||||
r.connections.Lock()
|
r.connections.Lock()
|
||||||
r.connections.metadata[key] = metaData
|
r.connections.metadata[key] = metaData
|
||||||
r.connections.sender[key] = connection
|
r.connections.sender[key] = connection
|
||||||
|
r.connections.spublicKey[key] = publicKey
|
||||||
r.connections.Unlock()
|
r.connections.Unlock()
|
||||||
// wait for receiver
|
// wait for receiver
|
||||||
receiversAddress := ""
|
receiversAddress := ""
|
||||||
|
receiversPublicKey := ""
|
||||||
isTimeout := time.Duration(0)
|
isTimeout := time.Duration(0)
|
||||||
for {
|
for {
|
||||||
if CONNECTION_TIMEOUT <= isTimeout {
|
if CONNECTION_TIMEOUT <= isTimeout {
|
||||||
|
@ -154,16 +165,38 @@ func (r *Relay) clientCommuncation(id int, connection net.Conn) {
|
||||||
r.connections.RLock()
|
r.connections.RLock()
|
||||||
if _, ok := r.connections.receiver[key]; ok {
|
if _, ok := r.connections.receiver[key]; ok {
|
||||||
receiversAddress = r.connections.receiver[key].RemoteAddr().String()
|
receiversAddress = r.connections.receiver[key].RemoteAddr().String()
|
||||||
logger.Debug("got receiver")
|
}
|
||||||
r.connections.RUnlock()
|
if _, ok := r.connections.rpublicKey[key]; ok {
|
||||||
break
|
receiversPublicKey = r.connections.rpublicKey[key]
|
||||||
}
|
}
|
||||||
r.connections.RUnlock()
|
r.connections.RUnlock()
|
||||||
|
if receiversAddress != "" && receiversPublicKey != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
isTimeout += 100 * time.Millisecond
|
isTimeout += 100 * time.Millisecond
|
||||||
}
|
}
|
||||||
logger.Debug("telling sender ok")
|
logger.Debug("telling sender ok")
|
||||||
sendMessage(receiversAddress, connection)
|
sendMessage(receiversAddress, connection)
|
||||||
|
sendMessage(receiversPublicKey, connection)
|
||||||
|
// TODO ASK FOR OKAY HERE TOO
|
||||||
|
logger.Debug("waiting for encrypted passphrase")
|
||||||
|
encryptedPassphrase := receiveMessage(connection)
|
||||||
|
r.connections.Lock()
|
||||||
|
r.connections.passphrase[key] = encryptedPassphrase
|
||||||
|
r.connections.Unlock()
|
||||||
|
|
||||||
|
// wait for receiver ready
|
||||||
|
for {
|
||||||
|
r.connections.RLock()
|
||||||
|
if _, ok := r.connections.receiverReady[key]; ok {
|
||||||
|
r.connections.RUnlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.connections.RUnlock()
|
||||||
|
}
|
||||||
|
// go reciever ready tell sender to go
|
||||||
|
sendMessage("go", connection)
|
||||||
logger.Debug("preparing pipe")
|
logger.Debug("preparing pipe")
|
||||||
r.connections.Lock()
|
r.connections.Lock()
|
||||||
con1 := r.connections.sender[key]
|
con1 := r.connections.sender[key]
|
||||||
|
@ -192,20 +225,28 @@ func (r *Relay) clientCommuncation(id int, connection net.Conn) {
|
||||||
// add as a potential receiver
|
// add as a potential receiver
|
||||||
r.connections.Lock()
|
r.connections.Lock()
|
||||||
r.connections.potentialReceivers[key] = struct{}{}
|
r.connections.potentialReceivers[key] = struct{}{}
|
||||||
|
r.connections.rpublicKey[key] = publicKey
|
||||||
|
r.connections.receiver[key] = connection
|
||||||
r.connections.Unlock()
|
r.connections.Unlock()
|
||||||
// wait for sender's metadata
|
// wait for sender's metadata
|
||||||
sendersAddress := ""
|
sendersAddress := ""
|
||||||
|
sendersPublicKey := ""
|
||||||
for {
|
for {
|
||||||
r.connections.RLock()
|
r.connections.RLock()
|
||||||
if _, ok := r.connections.metadata[key]; ok {
|
if _, ok := r.connections.metadata[key]; ok {
|
||||||
if _, ok2 := r.connections.sender[key]; ok2 {
|
if _, ok2 := r.connections.sender[key]; ok2 {
|
||||||
sendersAddress = r.connections.sender[key].RemoteAddr().String()
|
sendersAddress = r.connections.sender[key].RemoteAddr().String()
|
||||||
logger.Debug("got sender meta data")
|
logger.Debug("got sender meta data")
|
||||||
r.connections.RUnlock()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := r.connections.spublicKey[key]; ok {
|
||||||
|
sendersPublicKey = r.connections.spublicKey[key]
|
||||||
|
logger.Debugf("got sender public key: %s", sendersPublicKey)
|
||||||
|
}
|
||||||
r.connections.RUnlock()
|
r.connections.RUnlock()
|
||||||
|
if sendersAddress != "" && sendersPublicKey != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
if connectionType == "c" {
|
if connectionType == "c" {
|
||||||
sendMessage("0-0-0-0.0.0.0", connection)
|
sendMessage("0-0-0-0.0.0.0", connection)
|
||||||
// sender is not ready so delete connection
|
// sender is not ready so delete connection
|
||||||
|
@ -219,16 +260,49 @@ func (r *Relay) clientCommuncation(id int, connection net.Conn) {
|
||||||
// send meta data
|
// send meta data
|
||||||
r.connections.RLock()
|
r.connections.RLock()
|
||||||
sendMessage(r.connections.metadata[key]+"-"+sendersAddress, connection)
|
sendMessage(r.connections.metadata[key]+"-"+sendersAddress, connection)
|
||||||
|
sendMessage(sendersPublicKey, connection)
|
||||||
r.connections.RUnlock()
|
r.connections.RUnlock()
|
||||||
|
|
||||||
|
// now get passphrase
|
||||||
|
sendersPassphrase := ""
|
||||||
|
for {
|
||||||
|
r.connections.RLock()
|
||||||
|
if _, ok := r.connections.passphrase[key]; ok {
|
||||||
|
sendersPassphrase = r.connections.passphrase[key]
|
||||||
|
logger.Debugf("got sender passphrase: %s", sendersPassphrase)
|
||||||
|
}
|
||||||
|
r.connections.RUnlock()
|
||||||
|
if sendersPassphrase != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check for receiver's consent
|
// check for receiver's consent
|
||||||
consent := receiveMessage(connection)
|
consent := receiveMessage(connection)
|
||||||
logger.Debugf("consent: %s", consent)
|
logger.Debugf("consent: %s", consent)
|
||||||
if consent == "ok" {
|
if consent == "ok" {
|
||||||
logger.Debug("got consent")
|
logger.Debug("got consent")
|
||||||
r.connections.Lock()
|
// wait for encrypted passphrase
|
||||||
r.connections.receiver[key] = connection
|
encryptedPassphrase := ""
|
||||||
r.connections.Unlock()
|
for {
|
||||||
|
r.connections.RLock()
|
||||||
|
if _, ok := r.connections.passphrase[key]; ok {
|
||||||
|
encryptedPassphrase = r.connections.passphrase[key]
|
||||||
|
logger.Debugf("got passphrase: %s", r.connections.passphrase[key])
|
||||||
|
}
|
||||||
|
r.connections.RUnlock()
|
||||||
|
if encryptedPassphrase != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
sendMessage(encryptedPassphrase, connection)
|
||||||
}
|
}
|
||||||
|
receiveMessage(connection)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
r.connections.Lock()
|
||||||
|
r.connections.receiverReady[key] = true
|
||||||
|
r.connections.Unlock()
|
||||||
default:
|
default:
|
||||||
logger.Debugf("Got unknown protocol: '%s'", connectionType)
|
logger.Debugf("Got unknown protocol: '%s'", connectionType)
|
||||||
}
|
}
|
||||||
|
|
31
utils.go
31
utils.go
|
@ -5,9 +5,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
math_rand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -173,3 +175,32 @@ func GetLocalIP() string {
|
||||||
}
|
}
|
||||||
return bestIP
|
return bestIP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src is seeds the random generator for generating random strings
|
||||||
|
var src = math_rand.NewSource(time.Now().UnixNano())
|
||||||
|
|
||||||
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
const (
|
||||||
|
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||||
|
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||||
|
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandStringBytesMaskImprSrc prints a random string
|
||||||
|
func RandStringBytesMaskImprSrc(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||||
|
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||||
|
if remain == 0 {
|
||||||
|
cache, remain = src.Int63(), letterIdxMax
|
||||||
|
}
|
||||||
|
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||||
|
b[i] = letterBytes[idx]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
cache >>= letterIdxBits
|
||||||
|
remain--
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue