aviorcli/essential/visible/service/helper/codec/codec.go
2026-08-28 12:51:52 +08:00

690 lines
16 KiB
Go

/*
|--------------------------------------------------------------------------
| Description
|--------------------------------------------------------------------------
|
| Name:
| - Codec
|
| Purpose:
| - Provide unified encoding and decoding capabilities using Base8,
| Base10, Base16, Base32, Base64, and Sqids with centralized
| validation, operation dispatching, transformation handling,
| and standardized result generation.
|
|--------------------------------------------------------------------------
*/
/*
|--------------------------------------------------------------------------
| Instruction
|--------------------------------------------------------------------------
|
| Guideline:
| - Use the utility to encode input data using the selected codec format.
| - Use the utility to decode input data using the selected codec format.
| - Configure the codec format using base8, base10, base16, base32,
| base64, or sqids.
| - Provide input data as a string or []byte.
| - Configure charset and salt transformations when required.
| - Configure lower or upper output casing when required.
| - Configure minimum output length for Sqids encoding.
| - Configure length for deterministic Sqids decoding.
|
| Example:
| - ObrimCodec("encode", map[string]any{
| "format": "base64",
| "data": "hello",
| })
|
| - ObrimCodec("decode", map[string]any{
| "format": "base64",
| "data": "aGVsbG8=",
| })
|
| - ObrimCodec("encode", map[string]any{
| "format": "sqids",
| "data": "12345",
| "charset": "abcdefghijklmnopqrstuvwxyz",
| "salt": "example",
| "length": 8,
| })
|
|--------------------------------------------------------------------------
*/
/*
|--------------------------------------------------------------------------
| Credit
|--------------------------------------------------------------------------
|
| Contributor:
| - Rajon Ahmed
| - Blockonite
|
|--------------------------------------------------------------------------
*/
package codec
import (
"bytes"
"encoding/base32"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"strings"
"github.com/sqids/sqids-go"
)
const (
obrimCodecTypeEncode = "encode"
obrimCodecTypeDecode = "decode"
obrimCodecFormatBase8 = "base8"
obrimCodecFormatBase10 = "base10"
obrimCodecFormatBase16 = "base16"
obrimCodecFormatBase32 = "base32"
obrimCodecFormatBase64 = "base64"
obrimCodecFormatSqids = "sqids"
obrimCodecCaseLower = "lower"
obrimCodecCaseUpper = "upper"
obrimCodecSuccessEncoded = "SUCCESS_ENCODED"
obrimCodecSuccessDecoded = "SUCCESS_DECODED"
obrimCodecFailureInvalidType = "FAILURE_INVALID_TYPE"
obrimCodecFailureInvalidConfig = "FAILURE_INVALID_CONFIG"
obrimCodecFailureInvalidFormat = "FAILURE_INVALID_FORMAT"
obrimCodecFailureInvalidData = "FAILURE_INVALID_DATA"
obrimCodecFailureInvalidCharset = "FAILURE_INVALID_CHARSET"
obrimCodecFailureInvalidSalt = "FAILURE_INVALID_SALT"
obrimCodecFailureInvalidCase = "FAILURE_INVALID_CASE"
obrimCodecFailureInvalidLength = "FAILURE_INVALID_LENGTH"
obrimCodecFailureEncoding = "FAILURE_ENCODING"
obrimCodecFailureDecoding = "FAILURE_DECODING"
obrimCodecFailureTransformation = "FAILURE_TRANSFORMATION"
obrimCodecFailureTransformationReversal = "FAILURE_TRANSFORMATION_REVERSAL"
obrimCodecFailureUnsupportedData = "FAILURE_UNSUPPORTED_DATA"
obrimCodecFailureSqids = "FAILURE_SQIDS"
)
type obrimCodecConfig struct {
format string
data []byte
charset string
salt string
caseRule string
length int
}
type obrimCodecResult struct {
operation string
format string
value string
}
type obrimCodecOutput struct {
Status bool `json:"status"`
Code string `json:"code"`
Payload *obrimCodecResult `json:"payload"`
}
func ObrimCodec(typ string, config map[string]any) map[string]any {
normalized, code := obrimCodecValidateInput(typ, config)
if code != "" {
return obrimCodecBuildOutput(false, code, nil)
}
value, code := obrimCodecRouteRequest(typ, normalized)
if code != "" {
return obrimCodecBuildOutput(false, code, nil)
}
if typ == obrimCodecTypeEncode {
return obrimCodecBuildOutput(
true,
obrimCodecSuccessEncoded,
&obrimCodecResult{
operation: typ,
format: normalized.format,
value: value,
},
)
}
return obrimCodecBuildOutput(
true,
obrimCodecSuccessDecoded,
&obrimCodecResult{
operation: typ,
format: normalized.format,
value: value,
},
)
}
func obrimCodecValidateInput(typ string, config map[string]any) (obrimCodecConfig, string) {
var result obrimCodecConfig
switch typ {
case obrimCodecTypeEncode, obrimCodecTypeDecode:
default:
return result, obrimCodecFailureInvalidType
}
formatValue, exists := config["format"]
if !exists {
return result, obrimCodecFailureInvalidConfig
}
format, ok := formatValue.(string)
if !ok || format == "" {
return result, obrimCodecFailureInvalidFormat
}
switch format {
case obrimCodecFormatBase8,
obrimCodecFormatBase10,
obrimCodecFormatBase16,
obrimCodecFormatBase32,
obrimCodecFormatBase64,
obrimCodecFormatSqids:
default:
return result, obrimCodecFailureInvalidFormat
}
dataValue, exists := config["data"]
if !exists {
return result, obrimCodecFailureInvalidData
}
switch value := dataValue.(type) {
case string:
if value == "" {
return result, obrimCodecFailureInvalidData
}
result.data = []byte(value)
case []byte:
if len(value) == 0 {
return result, obrimCodecFailureInvalidData
}
result.data = append([]byte(nil), value...)
default:
return result, obrimCodecFailureUnsupportedData
}
result.format = format
if charsetValue, exists := config["charset"]; exists {
charset, ok := charsetValue.(string)
if !ok || charset == "" {
return result, obrimCodecFailureInvalidCharset
}
result.charset = charset
}
if saltValue, exists := config["salt"]; exists {
salt, ok := saltValue.(string)
if !ok || salt == "" {
return result, obrimCodecFailureInvalidSalt
}
result.salt = salt
}
if caseValue, exists := config["case"]; exists {
caseRule, ok := caseValue.(string)
if !ok {
return result, obrimCodecFailureInvalidCase
}
switch caseRule {
case obrimCodecCaseLower, obrimCodecCaseUpper:
result.caseRule = caseRule
default:
return result, obrimCodecFailureInvalidCase
}
}
if lengthValue, exists := config["length"]; exists {
switch length := lengthValue.(type) {
case int:
result.length = length
case int8:
result.length = int(length)
case int16:
result.length = int(length)
case int32:
result.length = int(length)
case int64:
result.length = int(length)
case uint:
result.length = int(length)
case uint8:
result.length = int(length)
case uint16:
result.length = int(length)
case uint32:
result.length = int(length)
case uint64:
if uint64(int(length)) != length {
return result, obrimCodecFailureInvalidLength
}
result.length = int(length)
case float64:
if length != float64(int(length)) {
return result, obrimCodecFailureInvalidLength
}
result.length = int(length)
default:
return result, obrimCodecFailureInvalidLength
}
if result.length < 0 {
return result, obrimCodecFailureInvalidLength
}
}
if format == obrimCodecFormatSqids && result.length == 0 {
result.length = 0
}
return result, ""
}
func obrimCodecRouteRequest(typ string, config obrimCodecConfig) (string, string) {
switch typ {
case obrimCodecTypeEncode:
return obrimCodecEncode(config)
case obrimCodecTypeDecode:
return obrimCodecDecode(config)
default:
return "", obrimCodecFailureInvalidType
}
}
func obrimCodecBuildOutput(status bool, code string, payload *obrimCodecResult) map[string]any {
if !status {
return map[string]any{
"status": false,
"code": code,
"payload": nil,
}
}
return map[string]any{
"status": true,
"code": code,
"payload": map[string]any{
"operation": payload.operation,
"format": payload.format,
"value": payload.value,
},
}
}
func obrimCodecEncode(config obrimCodecConfig) (string, string) {
data, code := obrimCodecApplyEncodeTransformations(config.data, config)
if code != "" {
return "", code
}
var (
value string
err error
)
switch config.format {
case obrimCodecFormatBase8:
value, err = obrimCodecEncodeBase8(data)
case obrimCodecFormatBase10:
value, err = obrimCodecEncodeBase10(data)
case obrimCodecFormatBase16:
value, err = obrimCodecEncodeBase16(data)
case obrimCodecFormatBase32:
value, err = obrimCodecEncodeBase32(data)
case obrimCodecFormatBase64:
value, err = obrimCodecEncodeBase64(data)
case obrimCodecFormatSqids:
value, err = obrimCodecEncodeSqids(data, config)
default:
return "", obrimCodecFailureInvalidFormat
}
if err != nil {
if config.format == obrimCodecFormatSqids {
return "", obrimCodecFailureSqids
}
return "", obrimCodecFailureEncoding
}
value = obrimCodecApplyCase(value, config.caseRule)
return value, ""
}
func obrimCodecDecode(config obrimCodecConfig) (string, string) {
data := append([]byte(nil), config.data...)
var err error
var value []byte
switch config.format {
case obrimCodecFormatBase8:
value, err = obrimCodecDecodeBase8(string(data))
case obrimCodecFormatBase10:
value, err = obrimCodecDecodeBase10(string(data))
case obrimCodecFormatBase16:
value, err = obrimCodecDecodeBase16(string(data))
case obrimCodecFormatBase32:
value, err = obrimCodecDecodeBase32(string(data))
case obrimCodecFormatBase64:
value, err = obrimCodecDecodeBase64(string(data))
case obrimCodecFormatSqids:
value, err = obrimCodecDecodeSqids(string(data), config)
default:
return "", obrimCodecFailureInvalidFormat
}
if err != nil {
if config.format == obrimCodecFormatSqids {
return "", obrimCodecFailureSqids
}
return "", obrimCodecFailureDecoding
}
value, err = obrimCodecApplyDecodeTransformations(value, config)
if err != nil {
return "", obrimCodecFailureTransformationReversal
}
return string(value), ""
}
func obrimCodecEncodeBase8(data []byte) (string, error) {
if len(data) == 0 {
return "", fmt.Errorf("empty data")
}
number := new(big.Int).SetBytes(data)
return number.Text(8), nil
}
func obrimCodecEncodeBase10(data []byte) (string, error) {
if len(data) == 0 {
return "", fmt.Errorf("empty data")
}
number := new(big.Int).SetBytes(data)
return number.Text(10), nil
}
func obrimCodecEncodeBase16(data []byte) (string, error) {
if len(data) == 0 {
return "", fmt.Errorf("empty data")
}
return hex.EncodeToString(data), nil
}
func obrimCodecEncodeBase32(data []byte) (string, error) {
if len(data) == 0 {
return "", fmt.Errorf("empty data")
}
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(data), nil
}
func obrimCodecEncodeBase64(data []byte) (string, error) {
if len(data) == 0 {
return "", fmt.Errorf("empty data")
}
return base64.StdEncoding.EncodeToString(data), nil
}
func obrimCodecEncodeSqids(data []byte, config obrimCodecConfig) (string, error) {
if len(data) == 0 {
return "", fmt.Errorf("empty data")
}
number := new(big.Int).SetBytes(data)
if !number.IsUint64() {
return "", fmt.Errorf("sqids input exceeds uint64")
}
options := sqids.Options{}
if config.charset != "" {
options.Alphabet = config.charset
}
if config.length > 0 {
options.MinLength = config.length
}
encoder, err := sqids.New(options)
if err != nil {
return "", err
}
return encoder.Encode([]uint64{number.Uint64()}), nil
}
func obrimCodecDecodeBase8(data string) ([]byte, error) {
data = strings.TrimSpace(data)
if data == "" {
return nil, fmt.Errorf("empty data")
}
number := new(big.Int)
if _, ok := number.SetString(data, 8); !ok {
return nil, fmt.Errorf("invalid base8 value")
}
return obrimCodecBigIntBytes(number), nil
}
func obrimCodecDecodeBase10(data string) ([]byte, error) {
data = strings.TrimSpace(data)
if data == "" {
return nil, fmt.Errorf("empty data")
}
number := new(big.Int)
if _, ok := number.SetString(data, 10); !ok {
return nil, fmt.Errorf("invalid base10 value")
}
return obrimCodecBigIntBytes(number), nil
}
func obrimCodecDecodeBase16(data string) ([]byte, error) {
data = strings.TrimSpace(data)
if data == "" {
return nil, fmt.Errorf("empty data")
}
if len(data)%2 != 0 {
data = "0" + data
}
return hex.DecodeString(data)
}
func obrimCodecDecodeBase32(data string) ([]byte, error) {
data = strings.TrimSpace(data)
if data == "" {
return nil, fmt.Errorf("empty data")
}
padding := len(data) % 8
if padding != 0 {
data += strings.Repeat("=", 8-padding)
}
return base32.StdEncoding.DecodeString(data)
}
func obrimCodecDecodeBase64(data string) ([]byte, error) {
data = strings.TrimSpace(data)
if data == "" {
return nil, fmt.Errorf("empty data")
}
return base64.StdEncoding.DecodeString(data)
}
func obrimCodecDecodeSqids(data string, config obrimCodecConfig) ([]byte, error) {
if data == "" {
return nil, fmt.Errorf("empty data")
}
options := sqids.Options{}
if config.charset != "" {
options.Alphabet = config.charset
}
if config.length > 0 {
options.MinLength = config.length
}
encoder, err := sqids.New(options)
if err != nil {
return nil, err
}
numbers := encoder.Decode(data)
if len(numbers) != 1 {
return nil, fmt.Errorf("invalid sqids value")
}
number := new(big.Int).SetUint64(numbers[0])
return obrimCodecBigIntBytes(number), nil
}
func obrimCodecBigIntBytes(number *big.Int) []byte {
if number.Sign() == 0 {
return []byte{0}
}
return number.Bytes()
}
func obrimCodecApplyEncodeTransformations(data []byte, config obrimCodecConfig) ([]byte, string) {
result := append([]byte(nil), data...)
if config.salt != "" {
result = obrimCodecApplySalt(result, []byte(config.salt))
}
if config.charset != "" && config.format != obrimCodecFormatSqids {
transformed, err := obrimCodecApplyCharsetEncode(result, config.charset)
if err != nil {
return nil, obrimCodecFailureTransformation
}
result = transformed
}
return result, ""
}
func obrimCodecApplyDecodeTransformations(data []byte, config obrimCodecConfig) ([]byte, error) {
result := append([]byte(nil), data...)
if config.charset != "" && config.format != obrimCodecFormatSqids {
transformed, err := obrimCodecApplyCharsetDecode(result, config.charset)
if err != nil {
return nil, err
}
result = transformed
}
if config.salt != "" {
result = obrimCodecApplySalt(result, []byte(config.salt))
}
return result, nil
}
func obrimCodecApplyCharsetEncode(data []byte, charset string) ([]byte, error) {
charsetBytes := []byte(charset)
if len(charsetBytes) < 2 {
return nil, fmt.Errorf("charset must contain at least two characters")
}
result := make([]byte, len(data))
for index, value := range data {
result[index] = charsetBytes[int(value)%len(charsetBytes)]
}
return result, nil
}
func obrimCodecApplyCharsetDecode(data []byte, charset string) ([]byte, error) {
charsetBytes := []byte(charset)
if len(charsetBytes) < 2 {
return nil, fmt.Errorf("charset must contain at least two characters")
}
reverse := make(map[byte]byte, len(charsetBytes))
for index, value := range charsetBytes {
if _, exists := reverse[value]; exists {
return nil, fmt.Errorf("charset contains duplicate characters")
}
reverse[value] = byte(index)
}
result := make([]byte, len(data))
for index, value := range data {
decoded, exists := reverse[value]
if !exists {
return nil, fmt.Errorf("character not present in charset")
}
result[index] = decoded
}
return result, nil
}
func obrimCodecApplySalt(data []byte, salt []byte) []byte {
if len(salt) == 0 {
return data
}
result := make([]byte, len(data))
for index, value := range data {
result[index] = value ^ salt[index%len(salt)]
}
return result
}
func obrimCodecApplyCase(value string, caseRule string) string {
switch caseRule {
case obrimCodecCaseLower:
return strings.ToLower(value)
case obrimCodecCaseUpper:
return strings.ToUpper(value)
default:
return value
}
}
var _ = bytes.Compare
var _ = json.Valid