diff --git a/goextVersion.go b/goextVersion.go index 1e16ad5..5b8aeb2 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.294" +const GoextVersion = "0.0.295" -const GoextVersionTimestamp = "2023-10-31T22:58:28+0100" +const GoextVersionTimestamp = "2023-11-01T00:23:17+0100" diff --git a/langext/baseAny.go b/langext/baseAny.go new file mode 100644 index 0000000..5d11f1e --- /dev/null +++ b/langext/baseAny.go @@ -0,0 +1,178 @@ +package langext + +import ( + "crypto/rand" + "errors" + "math" + "math/big" +) + +type AnyBaseConverter struct { + base uint64 + charset []rune +} + +func NewAnyBaseConverter(cs string) AnyBaseConverter { + rcs := []rune(cs) + return AnyBaseConverter{ + base: uint64(len(rcs)), + charset: rcs, + } +} + +func (bc AnyBaseConverter) Rand(rlen int) string { + biBase := big.NewInt(int64(bc.base)) + + randMax := big.NewInt(math.MaxInt64) + + r := "" + + for i := 0; i < rlen; i++ { + v, err := rand.Int(rand.Reader, randMax) + if err != nil { + panic(err) + } + + r += string(bc.charset[v.Mod(v, biBase).Int64()]) + } + + return r +} + +func (bc AnyBaseConverter) EncodeUInt64(num uint64) string { + if num == 0 { + return "0" + } + + b := "" + + // loop as long the num is bigger than zero + for num > 0 { + r := num % bc.base + + num -= r + num /= base62Base + + b += string(bc.charset[int(r)]) + } + + return b +} + +func (bc AnyBaseConverter) DecodeUInt64(str string) (uint64, error) { + if str == "" { + return 0, errors.New("empty string") + } + + result := uint64(0) + + for _, v := range str { + result *= base62Base + + pos := ArrFirstIndex(bc.charset, v) + if pos == -1 { + return 0, errors.New("invalid character: " + string(v)) + } + + result += uint64(pos) + } + + return result, nil +} + +func (bc AnyBaseConverter) Encode(src []byte) string { + value := new(big.Int) + value.SetBytes(src) + return bc.EncodeBigInt(value) +} + +func (bc AnyBaseConverter) EncodeBigInt(src *big.Int) string { + value := new(big.Int) + value.Set(src) + + isneg := value.Sign() < 0 + + answer := "" + + if isneg { + value.Neg(value) + } + + biBase := big.NewInt(int64(bc.base)) + + rem := new(big.Int) + + for value.Sign() > 0 { + value.QuoRem(value, biBase, rem) + answer = string(bc.charset[rem.Int64()]) + answer + } + + if isneg { + return "-" + answer + } else { + return answer + } +} + +func (bc AnyBaseConverter) Decode(src string) ([]byte, error) { + value, err := bc.DecodeToBigInt(src) + if err != nil { + return nil, err + } + return value.Bytes(), nil +} + +func (bc AnyBaseConverter) DecodeToBigInt(_src string) (*big.Int, error) { + result := new(big.Int) + result.SetInt64(0) + + src := []rune(_src) + + if len(src) == 0 { + return nil, errors.New("string is empty") + } + if bc.base < 2 { + return nil, errors.New("not enough digits") + } + + i := 0 + + sign := new(big.Int) + sign.SetInt64(1) + if src[i] == '+' { + i++ + } else if src[i] == '-' { + i++ + sign.SetInt64(-1) + } + + if i >= len(src) { + return nil, errors.New("no digits in input") + } + + biBase := big.NewInt(int64(bc.base)) + + oldResult := new(big.Int) + + for ; i < len(src); i++ { + n := ArrFirstIndex(bc.charset, src[i]) + if n < 0 { + return nil, errors.New("invalid characters in input") + } + + oldResult.Set(result) + + result.Mul(result, biBase) + result.Add(result, big.NewInt(int64(n))) + + if result.Cmp(oldResult) < 0 { + return nil, errors.New("overflow") + } + } + + if sign.Cmp(big.NewInt(0)) < 0 { + result.Neg(result) + } + + return result, nil +} diff --git a/langext/baseAny_test.go b/langext/baseAny_test.go new file mode 100644 index 0000000..cbe2f86 --- /dev/null +++ b/langext/baseAny_test.go @@ -0,0 +1,80 @@ +package langext + +import ( + "gogs.mikescher.com/BlackForestBytes/goext/tst" + "testing" +) + +func _anyEncStr(bc AnyBaseConverter, v string) string { + vr := bc.Encode([]byte(v)) + return vr +} + +func _anyDecStr(bc AnyBaseConverter, v string) string { + vr, err := bc.Decode(v) + if err != nil { + panic(err) + } + return string(vr) +} + +func TestAnyBase58DefaultEncoding(t *testing.T) { + tst.AssertEqual(t, _anyEncStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "Hello"), "9Ajdvzr") + tst.AssertEqual(t, _anyEncStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in."), "48638SMcJuah5okqPx4kCVf5d8QAdgbdNf28g7ReY13prUENNbMyssjq5GjsrJHF5zeZfqs4uJMUJHr7VbrU4XBUZ2Fw9DVtqtn9N1eXucEWSEZahXV6w4ysGSWqGdpeYTJf1MdDzTg8vfcQViifJjZX") +} + +func TestAnyBase58DefaultDecoding(t *testing.T) { + tst.AssertEqual(t, _anyDecStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "9Ajdvzr"), "Hello") + tst.AssertEqual(t, _anyDecStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "48638SMcJuah5okqPx4kCVf5d8QAdgbdNf28g7ReY13prUENNbMyssjq5GjsrJHF5zeZfqs4uJMUJHr7VbrU4XBUZ2Fw9DVtqtn9N1eXucEWSEZahXV6w4ysGSWqGdpeYTJf1MdDzTg8vfcQViifJjZX"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in.") +} + +func TestAnyBaseDecode(t *testing.T) { + + const ( + Binary = "01" + Decimal = "0123456789" + Hex = "0123456789ABCDEF" + DNA = "ACGT" + Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + Base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + Base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + Base256 = "๐Ÿš€๐Ÿชโ˜„๐Ÿ›ฐ๐ŸŒŒ๐ŸŒ‘๐ŸŒ’๐ŸŒ“๐ŸŒ”๐ŸŒ•๐ŸŒ–๐ŸŒ—๐ŸŒ˜๐ŸŒ๐ŸŒ๐ŸŒŽ๐Ÿ‰โ˜€๐Ÿ’ป๐Ÿ–ฅ๐Ÿ’พ๐Ÿ’ฟ๐Ÿ˜‚โค๐Ÿ˜๐Ÿคฃ๐Ÿ˜Š๐Ÿ™๐Ÿ’•๐Ÿ˜ญ๐Ÿ˜˜๐Ÿ‘๐Ÿ˜…๐Ÿ‘๐Ÿ˜๐Ÿ”ฅ๐Ÿฅฐ๐Ÿ’”๐Ÿ’–๐Ÿ’™๐Ÿ˜ข๐Ÿค”๐Ÿ˜†๐Ÿ™„๐Ÿ’ช๐Ÿ˜‰โ˜บ๐Ÿ‘Œ๐Ÿค—๐Ÿ’œ๐Ÿ˜”๐Ÿ˜Ž๐Ÿ˜‡๐ŸŒน๐Ÿคฆ๐ŸŽ‰๐Ÿ’žโœŒโœจ๐Ÿคท๐Ÿ˜ฑ๐Ÿ˜Œ๐ŸŒธ๐Ÿ™Œ๐Ÿ˜‹๐Ÿ’—๐Ÿ’š๐Ÿ˜๐Ÿ’›๐Ÿ™‚๐Ÿ’“๐Ÿคฉ๐Ÿ˜„๐Ÿ˜€๐Ÿ–ค๐Ÿ˜ƒ๐Ÿ’ฏ๐Ÿ™ˆ๐Ÿ‘‡๐ŸŽถ๐Ÿ˜’๐Ÿคญโฃ๐Ÿ˜œ๐Ÿ’‹๐Ÿ‘€๐Ÿ˜ช๐Ÿ˜‘๐Ÿ’ฅ๐Ÿ™‹๐Ÿ˜ž๐Ÿ˜ฉ๐Ÿ˜ก๐Ÿคช๐Ÿ‘Š๐Ÿฅณ๐Ÿ˜ฅ๐Ÿคค๐Ÿ‘‰๐Ÿ’ƒ๐Ÿ˜ณโœ‹๐Ÿ˜š๐Ÿ˜๐Ÿ˜ด๐ŸŒŸ๐Ÿ˜ฌ๐Ÿ™ƒ๐Ÿ€๐ŸŒท๐Ÿ˜ป๐Ÿ˜“โญโœ…๐Ÿฅบ๐ŸŒˆ๐Ÿ˜ˆ๐Ÿค˜๐Ÿ’ฆโœ”๐Ÿ˜ฃ๐Ÿƒ๐Ÿ’โ˜น๐ŸŽŠ๐Ÿ’˜๐Ÿ˜ โ˜๐Ÿ˜•๐ŸŒบ๐ŸŽ‚๐ŸŒป๐Ÿ˜๐Ÿ–•๐Ÿ’๐Ÿ™Š๐Ÿ˜น๐Ÿ—ฃ๐Ÿ’ซ๐Ÿ’€๐Ÿ‘‘๐ŸŽต๐Ÿคž๐Ÿ˜›๐Ÿ”ด๐Ÿ˜ค๐ŸŒผ๐Ÿ˜ซโšฝ๐Ÿค™โ˜•๐Ÿ†๐Ÿคซ๐Ÿ‘ˆ๐Ÿ˜ฎ๐Ÿ™†๐Ÿป๐Ÿƒ๐Ÿถ๐Ÿ’๐Ÿ˜ฒ๐ŸŒฟ๐Ÿงก๐ŸŽโšก๐ŸŒž๐ŸŽˆโŒโœŠ๐Ÿ‘‹๐Ÿ˜ฐ๐Ÿคจ๐Ÿ˜ถ๐Ÿค๐Ÿšถ๐Ÿ’ฐ๐Ÿ“๐Ÿ’ข๐ŸคŸ๐Ÿ™๐Ÿšจ๐Ÿ’จ๐Ÿคฌโœˆ๐ŸŽ€๐Ÿบ๐Ÿค“๐Ÿ˜™๐Ÿ’Ÿ๐ŸŒฑ๐Ÿ˜–๐Ÿ‘ถ๐Ÿฅดโ–ถโžกโ“๐Ÿ’Ž๐Ÿ’ธโฌ‡๐Ÿ˜จ๐ŸŒš๐Ÿฆ‹๐Ÿ˜ท๐Ÿ•บโš ๐Ÿ™…๐Ÿ˜Ÿ๐Ÿ˜ต๐Ÿ‘Ž๐Ÿคฒ๐Ÿค ๐Ÿคง๐Ÿ“Œ๐Ÿ”ต๐Ÿ’…๐Ÿง๐Ÿพ๐Ÿ’๐Ÿ˜—๐Ÿค‘๐ŸŒŠ๐Ÿคฏ๐Ÿทโ˜Ž๐Ÿ’ง๐Ÿ˜ฏ๐Ÿ’†๐Ÿ‘†๐ŸŽค๐Ÿ™‡๐Ÿ‘โ„๐ŸŒด๐Ÿ’ฃ๐Ÿธ๐Ÿ’Œ๐Ÿ“๐Ÿฅ€๐Ÿคข๐Ÿ‘…๐Ÿ’ก๐Ÿ’ฉ๐Ÿ‘๐Ÿ“ธ๐Ÿ‘ป๐Ÿค๐Ÿคฎ๐ŸŽผ๐Ÿฅต๐Ÿšฉ๐ŸŽ๐ŸŠ๐Ÿ‘ผ๐Ÿ’๐Ÿ“ฃ๐Ÿฅ‚" + ) + + type TestDef struct { + FromCS string + FromVal string + ToCS string + ToVal string + } + + defs := []TestDef{ + {Binary, "10100101011100000101010", Decimal, "5421098"}, + {Decimal, "5421098", DNA, "CCAGGTGAAGGG"}, + {Decimal, "5421098", DNA, "CCAGGTGAAGGG"}, + {Decimal, "80085", Base256, "๐Ÿช๐Ÿ’ž๐Ÿ”ต"}, + {Hex, "48656C6C6C20576F526C5421", Base64, "SGVsbGwgV29SbFQh"}, + {Base64, "SGVsbGw/gV29SbF+Qh", Base32, "CIMVWGY3B7QFO32SNRPZBB"}, + {Base64, "SGVsbGw/gV29SbF+Qh", Base58, "2fUsGKQUcgQcwSqpvy6"}, + {Base64, "SGVsbGw/gV29SbF+Qh", Base62, "V34nvybdQ3m3RHk9Sr"}, + } + + for _, def := range defs { + + d1 := NewAnyBaseConverter(def.FromCS) + d2 := NewAnyBaseConverter(def.ToCS) + + v1 := tst.Must(d1.Decode(def.FromVal))(t) + v2 := tst.Must(d2.Decode(def.ToVal))(t) + + tst.AssertArrayEqual(t, v1, v2) + + str2 := d2.Encode(v1) + tst.AssertEqual(t, str2, def.ToVal) + + str1 := d1.Encode(v2) + tst.AssertEqual(t, str1, def.FromVal) + + } +} diff --git a/tst/assertions.go b/tst/assertions.go index bb13196..5bcfaed 100644 --- a/tst/assertions.go +++ b/tst/assertions.go @@ -14,6 +14,20 @@ func AssertEqual[T comparable](t *testing.T, actual T, expected T) { } } +func AssertArrayEqual[T comparable](t *testing.T, actual []T, expected []T) { + t.Helper() + if len(actual) != len(expected) { + t.Errorf("values differ: Actual: '%v', Expected: '%v' (len %d <> %d)", actual, expected, len(actual), len(expected)) + return + } + for i := 0; i < len(actual); i++ { + if actual[i] != expected[i] { + t.Errorf("values differ: Actual: '%v', Expected: '%v' (at index %d)", actual, expected, i) + return + } + } +} + func AssertNotEqual[T comparable](t *testing.T, actual T, expected T) { t.Helper() if actual == expected {