obrimbasecli/essential/visible/service/helper/codec/codec.go

434 lines
12 KiB
Go

package codec
import (
"encoding/base32"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"math/big"
"strconv"
"strings"
"github.com/sqids/sqids-go"
)
const (
obrimCodecOperationEncode = "encode"
obrimCodecOperationDecode = "decode"
obrimCodecFormatBase8 = "base8"
obrimCodecFormatBase10 = "base10"
obrimCodecFormatBase16 = "base16"
obrimCodecFormatBase32 = "base32"
obrimCodecFormatBase64 = "base64"
obrimCodecFormatSqids = "sqids"
obrimCodecCaseLower = "lower"
obrimCodecCaseUpper = "upper"
)
type obrimCodecConfiguration struct {
Format string
Data interface{}
Charset string
Salt string
Case string
Length int
}
type obrimCodecResult struct {
Status bool `json:"status"`
Code string `json:"code"`
Payload interface{} `json:"payload"`
}
type obrimCodecPayload struct {
Operation string `json:"operation"`
Format string `json:"format"`
Value string `json:"value"`
}
// ObrimCodec validates configuration, dispatches codec operations, and returns structured results.
func ObrimCodec(operation string, config map[string]interface{}) map[string]interface{} {
validatedConfiguration, validationError := obrimCodecValidation(operation, config)
if validationError != nil {
return map[string]interface{}{
"status": false,
"code": "FAILED_VALIDATION",
"payload": nil,
}
}
value, dispatchError := obrimCodecDispatch(operation, validatedConfiguration)
if dispatchError != nil {
return map[string]interface{}{
"status": false,
"code": "FAILED_OPERATION",
"payload": nil,
}
}
return map[string]interface{}{
"status": true,
"code": "SUCCESS_OPERATION",
"payload": map[string]interface{}{
"operation": operation,
"format": validatedConfiguration.Format,
"value": value,
},
}
}
// obrimCodecValidation validates required parameters, supported operations, formats, and parameter types.
func obrimCodecValidation(operation string, config map[string]interface{}) (*obrimCodecConfiguration, error) {
switch operation {
case obrimCodecOperationEncode, obrimCodecOperationDecode:
default:
return nil, errors.New("unsupported operation")
}
formatValue, exists := config["format"]
if !exists {
return nil, errors.New("missing format")
}
format, ok := formatValue.(string)
if !ok {
return nil, errors.New("invalid format type")
}
switch format {
case obrimCodecFormatBase8,
obrimCodecFormatBase10,
obrimCodecFormatBase16,
obrimCodecFormatBase32,
obrimCodecFormatBase64,
obrimCodecFormatSqids:
default:
return nil, errors.New("unsupported format")
}
data, exists := config["data"]
if !exists {
return nil, errors.New("missing data")
}
charsetValue, exists := config["charset"]
if !exists {
return nil, errors.New("missing charset")
}
charset, ok := charsetValue.(string)
if !ok {
return nil, errors.New("invalid charset type")
}
saltValue, exists := config["salt"]
if !exists {
return nil, errors.New("missing salt")
}
salt, ok := saltValue.(string)
if !ok {
return nil, errors.New("invalid salt type")
}
configuration := &obrimCodecConfiguration{
Format: format,
Data: data,
Charset: charset,
Salt: salt,
}
if format == obrimCodecFormatSqids {
lengthValue, exists := config["length"]
if !exists {
return nil, errors.New("missing length")
}
switch value := lengthValue.(type) {
case int:
configuration.Length = value
case float64:
configuration.Length = int(value)
default:
return nil, errors.New("invalid length type")
}
} else {
caseValue, exists := config["case"]
if !exists {
return nil, errors.New("missing case")
}
caseRule, ok := caseValue.(string)
if !ok {
return nil, errors.New("invalid case type")
}
switch caseRule {
case obrimCodecCaseLower, obrimCodecCaseUpper:
default:
return nil, errors.New("invalid case value")
}
configuration.Case = caseRule
}
switch data.(type) {
case string, []byte:
default:
return nil, errors.New("invalid data type")
}
return configuration, nil
}
// obrimCodecDispatch routes validated requests to the appropriate codec implementation.
func obrimCodecDispatch(operation string, configuration *obrimCodecConfiguration) (string, error) {
switch operation {
case obrimCodecOperationEncode:
switch configuration.Format {
case obrimCodecFormatBase8:
return obrimCodecEncodeBase8(configuration)
case obrimCodecFormatBase10:
return obrimCodecEncodeBase10(configuration)
case obrimCodecFormatBase16:
return obrimCodecEncodeBase16(configuration)
case obrimCodecFormatBase32:
return obrimCodecEncodeBase32(configuration)
case obrimCodecFormatBase64:
return obrimCodecEncodeBase64(configuration)
case obrimCodecFormatSqids:
return obrimCodecEncodeSqids(configuration)
}
case obrimCodecOperationDecode:
switch configuration.Format {
case obrimCodecFormatBase8:
return obrimCodecDecodeBase8(configuration)
case obrimCodecFormatBase10:
return obrimCodecDecodeBase10(configuration)
case obrimCodecFormatBase16:
return obrimCodecDecodeBase16(configuration)
case obrimCodecFormatBase32:
return obrimCodecDecodeBase32(configuration)
case obrimCodecFormatBase64:
return obrimCodecDecodeBase64(configuration)
case obrimCodecFormatSqids:
return obrimCodecDecodeSqids(configuration)
}
}
return "", errors.New("dispatch failure")
}
// obrimCodecNormalizeInput converts supported input types into string form.
func obrimCodecNormalizeInput(data interface{}) string {
switch value := data.(type) {
case string:
return value
case []byte:
return string(value)
default:
return ""
}
}
// obrimCodecApplyTransform applies charset and salt transformations.
func obrimCodecApplyTransform(value string, charset string, salt string) string {
return charset + salt + value
}
// obrimCodecReverseTransform reverses charset and salt transformations.
func obrimCodecReverseTransform(value string, charset string, salt string) string {
prefix := charset + salt
return strings.TrimPrefix(value, prefix)
}
// obrimCodecApplyCase applies output casing.
func obrimCodecApplyCase(value string, rule string) string {
switch rule {
case obrimCodecCaseUpper:
return strings.ToUpper(value)
default:
return strings.ToLower(value)
}
}
// obrimCodecEncodeBase8 encodes data using Base8 encoding.
func obrimCodecEncodeBase8(configuration *obrimCodecConfiguration) (string, error) {
input := obrimCodecApplyTransform(
obrimCodecNormalizeInput(configuration.Data),
configuration.Charset,
configuration.Salt,
)
var builder strings.Builder
for _, character := range []byte(input) {
builder.WriteString(fmt.Sprintf("%03o", character))
}
return obrimCodecApplyCase(builder.String(), configuration.Case), nil
}
// obrimCodecDecodeBase8 decodes Base8 encoded data.
func obrimCodecDecodeBase8(configuration *obrimCodecConfiguration) (string, error) {
input := strings.ToLower(obrimCodecNormalizeInput(configuration.Data))
if len(input)%3 != 0 {
return "", errors.New("invalid base8 data")
}
decoded := make([]byte, 0)
for index := 0; index < len(input); index += 3 {
value, errorValue := strconv.ParseUint(input[index:index+3], 8, 8)
if errorValue != nil {
return "", errorValue
}
decoded = append(decoded, byte(value))
}
return obrimCodecReverseTransform(string(decoded), configuration.Charset, configuration.Salt), nil
}
// obrimCodecEncodeBase10 encodes data using Base10 encoding.
func obrimCodecEncodeBase10(configuration *obrimCodecConfiguration) (string, error) {
input := obrimCodecApplyTransform(
obrimCodecNormalizeInput(configuration.Data),
configuration.Charset,
configuration.Salt,
)
value := new(big.Int).SetBytes([]byte(input))
return obrimCodecApplyCase(value.Text(10), configuration.Case), nil
}
// obrimCodecDecodeBase10 decodes Base10 encoded data.
func obrimCodecDecodeBase10(configuration *obrimCodecConfiguration) (string, error) {
value := new(big.Int)
if _, success := value.SetString(obrimCodecNormalizeInput(configuration.Data), 10); !success {
return "", errors.New("invalid base10 data")
}
return obrimCodecReverseTransform(string(value.Bytes()), configuration.Charset, configuration.Salt), nil
}
// obrimCodecEncodeBase16 encodes data using Base16 encoding.
func obrimCodecEncodeBase16(configuration *obrimCodecConfiguration) (string, error) {
input := obrimCodecApplyTransform(
obrimCodecNormalizeInput(configuration.Data),
configuration.Charset,
configuration.Salt,
)
output := hex.EncodeToString([]byte(input))
return obrimCodecApplyCase(output, configuration.Case), nil
}
// obrimCodecDecodeBase16 decodes Base16 encoded data.
func obrimCodecDecodeBase16(configuration *obrimCodecConfiguration) (string, error) {
decoded, err := hex.DecodeString(obrimCodecNormalizeInput(configuration.Data))
if err != nil {
return "", err
}
return obrimCodecReverseTransform(string(decoded), configuration.Charset, configuration.Salt), nil
}
// obrimCodecEncodeBase32 encodes data using Base32 encoding.
func obrimCodecEncodeBase32(configuration *obrimCodecConfiguration) (string, error) {
input := obrimCodecApplyTransform(
obrimCodecNormalizeInput(configuration.Data),
configuration.Charset,
configuration.Salt,
)
output := base32.StdEncoding.EncodeToString([]byte(input))
return obrimCodecApplyCase(output, configuration.Case), nil
}
// obrimCodecDecodeBase32 decodes Base32 encoded data.
func obrimCodecDecodeBase32(configuration *obrimCodecConfiguration) (string, error) {
decoded, err := base32.StdEncoding.DecodeString(strings.ToUpper(obrimCodecNormalizeInput(configuration.Data)))
if err != nil {
return "", err
}
return obrimCodecReverseTransform(string(decoded), configuration.Charset, configuration.Salt), nil
}
// obrimCodecEncodeBase64 encodes data using Base64 encoding.
func obrimCodecEncodeBase64(configuration *obrimCodecConfiguration) (string, error) {
input := obrimCodecApplyTransform(
obrimCodecNormalizeInput(configuration.Data),
configuration.Charset,
configuration.Salt,
)
output := base64.StdEncoding.EncodeToString([]byte(input))
return obrimCodecApplyCase(output, configuration.Case), nil
}
// obrimCodecDecodeBase64 decodes Base64 encoded data.
func obrimCodecDecodeBase64(configuration *obrimCodecConfiguration) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(obrimCodecNormalizeInput(configuration.Data))
if err != nil {
return "", err
}
return obrimCodecReverseTransform(string(decoded), configuration.Charset, configuration.Salt), nil
}
// obrimCodecEncodeSqids encodes data using Sqids encoding with minimum length enforcement.
func obrimCodecEncodeSqids(configuration *obrimCodecConfiguration) (string, error) {
input := obrimCodecApplyTransform(
obrimCodecNormalizeInput(configuration.Data),
configuration.Charset,
configuration.Salt,
)
numbers := make([]uint64, 0)
for _, value := range []byte(input) {
numbers = append(numbers, uint64(value))
}
sqidsInstance, err := sqids.New(sqids.Options{
Alphabet: configuration.Charset,
MinLength: uint8(configuration.Length),
})
if err != nil {
return "", err
}
return sqidsInstance.Encode(numbers)
}
// obrimCodecDecodeSqids decodes Sqids encoded data using deterministic length validation.
func obrimCodecDecodeSqids(configuration *obrimCodecConfiguration) (string, error) {
sqidsInstance, err := sqids.New(sqids.Options{
Alphabet: configuration.Charset,
MinLength: uint8(configuration.Length),
})
if err != nil {
return "", err
}
numbers, err := sqidsInstance.Decode(obrimCodecNormalizeInput(configuration.Data))
if err != nil {
return "", err
}
output := make([]byte, 0)
for _, value := range numbers {
output = append(output, byte(value))
}
return obrimCodecReverseTransform(string(output), configuration.Charset, configuration.Salt), nil
}