525 lines
14 KiB
Go
525 lines
14 KiB
Go
/*
|
|
|--------------------------------------------------------------------------
|
|
| Manifest
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Name:
|
|
| - Codec
|
|
|
|
|
| Purpose:
|
|
| - Provide unified encoding and decoding capabilities using Base8,
|
|
| Base10, Base16, Base32, Base64, and Sqids with centralized
|
|
| validation, dispatching, transformation management, and
|
|
| structured result handling.
|
|
|
|
|
| Guideline:
|
|
| - Use ObrimCodec() as the public entry point.
|
|
| - Validate all parameters before dispatching operations.
|
|
| - Use supported formats only.
|
|
| - Handle all failures through structured responses.
|
|
|
|
|
| Example:
|
|
| - ObrimCodec("encode", config)
|
|
| - ObrimCodec("decode", config)
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Credit
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Contributor:
|
|
| - Rajon Ahmed
|
|
| - Scionite
|
|
|
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
package codec
|
|
|
|
import (
|
|
"encoding/base32"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"go/module/essential/visible/service/helper/log"
|
|
"go/module/essential/visible/service/helper/status"
|
|
|
|
"github.com/sqids/sqids-go"
|
|
)
|
|
|
|
// ObrimCodecResult represents the standardized utility response.
|
|
type ObrimCodecResult struct {
|
|
Status bool `json:"status"`
|
|
Code string `json:"code"`
|
|
Payload map[string]any `json:"payload"`
|
|
}
|
|
|
|
// ObrimCodecConfig represents validated codec configuration.
|
|
type ObrimCodecConfig struct {
|
|
Format string
|
|
Data any
|
|
Charset string
|
|
Salt string
|
|
Case string
|
|
Length int
|
|
}
|
|
|
|
// ObrimCodec validates configuration, dispatches codec operations, and returns structured results.
|
|
func ObrimCodec(operation string, config map[string]any) map[string]any {
|
|
log.ObrimLog("INIT", "Codec operation initialized.")
|
|
|
|
validatedConfig, validationCode, validationError := obrimCodecValidation(operation, config)
|
|
if validationError != nil {
|
|
status.ObrimStatus("ERROR", validationError.Error())
|
|
log.ObrimLog("ERROR", validationError.Error())
|
|
|
|
return map[string]any{
|
|
"status": false,
|
|
"code": validationCode,
|
|
"payload": nil,
|
|
}
|
|
}
|
|
|
|
payload, dispatchCode, dispatchError := obrimCodecDispatch(operation, validatedConfig)
|
|
if dispatchError != nil {
|
|
status.ObrimStatus("ERROR", dispatchError.Error())
|
|
log.ObrimLog("ERROR", dispatchError.Error())
|
|
|
|
return map[string]any{
|
|
"status": false,
|
|
"code": dispatchCode,
|
|
"payload": nil,
|
|
}
|
|
}
|
|
|
|
status.ObrimStatus("SUCCESS", "Codec operation completed.")
|
|
log.ObrimLog("SUCCESS", "Codec operation completed.")
|
|
|
|
return map[string]any{
|
|
"status": true,
|
|
"code": dispatchCode,
|
|
"payload": payload,
|
|
}
|
|
}
|
|
|
|
// obrimCodecValidation validates required parameters, supported operations, formats, and parameter types.
|
|
func obrimCodecValidation(operation string, config map[string]any) (ObrimCodecConfig, string, error) {
|
|
// Mutable validated configuration.
|
|
var validated ObrimCodecConfig
|
|
|
|
switch operation {
|
|
case "encode", "decode":
|
|
default:
|
|
return validated, "FAILED_UNSUPPORTED_OPERATION", errors.New("unsupported operation")
|
|
}
|
|
|
|
// Mutable format value.
|
|
formatValue, exists := config["format"]
|
|
if !exists {
|
|
return validated, "FAILED_MISSING_FORMAT", errors.New("missing format")
|
|
}
|
|
|
|
format, ok := formatValue.(string)
|
|
if !ok {
|
|
return validated, "FAILED_INVALID_FORMAT_TYPE", errors.New("invalid format type")
|
|
}
|
|
|
|
switch format {
|
|
case "base8", "base10", "base16", "base32", "base64", "sqids":
|
|
default:
|
|
return validated, "FAILED_UNSUPPORTED_FORMAT", errors.New("unsupported format")
|
|
}
|
|
|
|
// Mutable data value.
|
|
dataValue, exists := config["data"]
|
|
if !exists {
|
|
return validated, "FAILED_MISSING_DATA", errors.New("missing data")
|
|
}
|
|
|
|
switch dataValue.(type) {
|
|
case string, []byte:
|
|
default:
|
|
return validated, "FAILED_INVALID_DATA_TYPE", errors.New("invalid data type")
|
|
}
|
|
|
|
// Mutable charset value.
|
|
charsetValue, exists := config["charset"]
|
|
if !exists {
|
|
return validated, "FAILED_MISSING_CHARSET", errors.New("missing charset")
|
|
}
|
|
|
|
charset, ok := charsetValue.(string)
|
|
if !ok {
|
|
return validated, "FAILED_INVALID_CHARSET_TYPE", errors.New("invalid charset type")
|
|
}
|
|
|
|
// Mutable salt value.
|
|
saltValue, exists := config["salt"]
|
|
if !exists {
|
|
return validated, "FAILED_MISSING_SALT", errors.New("missing salt")
|
|
}
|
|
|
|
salt, ok := saltValue.(string)
|
|
if !ok {
|
|
return validated, "FAILED_INVALID_SALT_TYPE", errors.New("invalid salt type")
|
|
}
|
|
|
|
validated.Format = format
|
|
validated.Data = dataValue
|
|
validated.Charset = charset
|
|
validated.Salt = salt
|
|
|
|
if format == "sqids" {
|
|
// Mutable length value.
|
|
lengthValue, exists := config["length"]
|
|
if !exists {
|
|
return validated, "FAILED_MISSING_LENGTH", errors.New("missing length")
|
|
}
|
|
|
|
switch value := lengthValue.(type) {
|
|
case int:
|
|
validated.Length = value
|
|
case int64:
|
|
validated.Length = int(value)
|
|
case float64:
|
|
validated.Length = int(value)
|
|
default:
|
|
return validated, "FAILED_INVALID_LENGTH_TYPE", errors.New("invalid length type")
|
|
}
|
|
} else {
|
|
// Mutable case value.
|
|
caseValue, exists := config["case"]
|
|
if !exists {
|
|
return validated, "FAILED_MISSING_CASE", errors.New("missing case")
|
|
}
|
|
|
|
caseRule, ok := caseValue.(string)
|
|
if !ok {
|
|
return validated, "FAILED_INVALID_CASE_TYPE", errors.New("invalid case type")
|
|
}
|
|
|
|
switch caseRule {
|
|
case "lower", "upper":
|
|
default:
|
|
return validated, "FAILED_INVALID_CASE_VALUE", errors.New("invalid case value")
|
|
}
|
|
|
|
validated.Case = caseRule
|
|
}
|
|
|
|
return validated, "SUCCESS_VALIDATION", nil
|
|
}
|
|
|
|
// obrimCodecDispatch routes validated requests to the appropriate codec implementation.
|
|
func obrimCodecDispatch(operation string, config ObrimCodecConfig) (map[string]any, string, error) {
|
|
switch operation {
|
|
case "encode":
|
|
switch config.Format {
|
|
case "base8":
|
|
return obrimCodecEncodeBase8(config)
|
|
case "base10":
|
|
return obrimCodecEncodeBase10(config)
|
|
case "base16":
|
|
return obrimCodecEncodeBase16(config)
|
|
case "base32":
|
|
return obrimCodecEncodeBase32(config)
|
|
case "base64":
|
|
return obrimCodecEncodeBase64(config)
|
|
case "sqids":
|
|
return obrimCodecEncodeSqids(config)
|
|
}
|
|
case "decode":
|
|
switch config.Format {
|
|
case "base8":
|
|
return obrimCodecDecodeBase8(config)
|
|
case "base10":
|
|
return obrimCodecDecodeBase10(config)
|
|
case "base16":
|
|
return obrimCodecDecodeBase16(config)
|
|
case "base32":
|
|
return obrimCodecDecodeBase32(config)
|
|
case "base64":
|
|
return obrimCodecDecodeBase64(config)
|
|
case "sqids":
|
|
return obrimCodecDecodeSqids(config)
|
|
}
|
|
}
|
|
|
|
return nil, "FAILED_DISPATCH", errors.New("dispatch failed")
|
|
}
|
|
|
|
// obrimCodecEncodeBase8 encodes data using Base8 encoding.
|
|
func obrimCodecEncodeBase8(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := obrimCodecBytesToBase(config, 8)
|
|
|
|
return map[string]any{
|
|
"operation": "encode",
|
|
"format": "base8",
|
|
"value": obrimCodecApplyCase(obrimCodecTransformEncode(value, config), config.Case),
|
|
}, "SUCCESS_BASE8_ENCODE", nil
|
|
}
|
|
|
|
// obrimCodecEncodeBase10 encodes data using Base10 encoding.
|
|
func obrimCodecEncodeBase10(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := obrimCodecBytesToBase(config, 10)
|
|
|
|
return map[string]any{
|
|
"operation": "encode",
|
|
"format": "base10",
|
|
"value": obrimCodecApplyCase(obrimCodecTransformEncode(value, config), config.Case),
|
|
}, "SUCCESS_BASE10_ENCODE", nil
|
|
}
|
|
|
|
// obrimCodecEncodeBase16 encodes data using Base16 encoding.
|
|
func obrimCodecEncodeBase16(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := hex.EncodeToString(obrimCodecBytes(config.Data))
|
|
value = obrimCodecApplyCase(value, config.Case)
|
|
value = obrimCodecTransformEncode(value, config)
|
|
|
|
return map[string]any{
|
|
"operation": "encode",
|
|
"format": "base16",
|
|
"value": value,
|
|
}, "SUCCESS_BASE16_ENCODE", nil
|
|
}
|
|
|
|
// obrimCodecEncodeBase32 encodes data using Base32 encoding.
|
|
func obrimCodecEncodeBase32(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := base32.StdEncoding.EncodeToString(obrimCodecBytes(config.Data))
|
|
value = obrimCodecApplyCase(value, config.Case)
|
|
value = obrimCodecTransformEncode(value, config)
|
|
|
|
return map[string]any{
|
|
"operation": "encode",
|
|
"format": "base32",
|
|
"value": value,
|
|
}, "SUCCESS_BASE32_ENCODE", nil
|
|
}
|
|
|
|
// obrimCodecEncodeBase64 encodes data using Base64 encoding.
|
|
func obrimCodecEncodeBase64(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := base64.StdEncoding.EncodeToString(obrimCodecBytes(config.Data))
|
|
value = obrimCodecApplyCase(value, config.Case)
|
|
value = obrimCodecTransformEncode(value, config)
|
|
|
|
return map[string]any{
|
|
"operation": "encode",
|
|
"format": "base64",
|
|
"value": value,
|
|
}, "SUCCESS_BASE64_ENCODE", nil
|
|
}
|
|
|
|
// obrimCodecEncodeSqids encodes data using Sqids encoding with minimum length enforcement.
|
|
func obrimCodecEncodeSqids(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
options := sqids.Options{
|
|
MinLength: uint8(config.Length),
|
|
Alphabet: obrimCodecAlphabet(config.Charset),
|
|
Blocklist: []string{},
|
|
}
|
|
|
|
instance, err := sqids.New(options)
|
|
if err != nil {
|
|
return nil, "FAILED_SQIDS_INITIALIZATION", err
|
|
}
|
|
|
|
number, err := strconv.ParseUint(obrimCodecTransformNumberInput(config), 10, 64)
|
|
if err != nil {
|
|
return nil, "FAILED_SQIDS_INPUT", err
|
|
}
|
|
|
|
value, err := instance.Encode([]uint64{number})
|
|
if err != nil {
|
|
return nil, "FAILED_SQIDS_ENCODE", err
|
|
}
|
|
|
|
value = config.Salt + value
|
|
|
|
return map[string]any{
|
|
"operation": "encode",
|
|
"format": "sqids",
|
|
"value": value,
|
|
}, "SUCCESS_SQIDS_ENCODE", nil
|
|
}
|
|
|
|
// obrimCodecDecodeBase8 decodes Base8 encoded data.
|
|
func obrimCodecDecodeBase8(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := obrimCodecTransformDecode(obrimCodecString(config.Data), config)
|
|
|
|
return map[string]any{
|
|
"operation": "decode",
|
|
"format": "base8",
|
|
"value": value,
|
|
}, "SUCCESS_BASE8_DECODE", nil
|
|
}
|
|
|
|
// obrimCodecDecodeBase10 decodes Base10 encoded data.
|
|
func obrimCodecDecodeBase10(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := obrimCodecTransformDecode(obrimCodecString(config.Data), config)
|
|
|
|
return map[string]any{
|
|
"operation": "decode",
|
|
"format": "base10",
|
|
"value": value,
|
|
}, "SUCCESS_BASE10_DECODE", nil
|
|
}
|
|
|
|
// obrimCodecDecodeBase16 decodes Base16 encoded data.
|
|
func obrimCodecDecodeBase16(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := obrimCodecTransformDecode(obrimCodecString(config.Data), config)
|
|
|
|
decoded, err := hex.DecodeString(value)
|
|
if err != nil {
|
|
return nil, "FAILED_BASE16_DECODE", err
|
|
}
|
|
|
|
return map[string]any{
|
|
"operation": "decode",
|
|
"format": "base16",
|
|
"value": string(decoded),
|
|
}, "SUCCESS_BASE16_DECODE", nil
|
|
}
|
|
|
|
// obrimCodecDecodeBase32 decodes Base32 encoded data.
|
|
func obrimCodecDecodeBase32(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := strings.ToUpper(obrimCodecTransformDecode(obrimCodecString(config.Data), config))
|
|
|
|
decoded, err := base32.StdEncoding.DecodeString(value)
|
|
if err != nil {
|
|
return nil, "FAILED_BASE32_DECODE", err
|
|
}
|
|
|
|
return map[string]any{
|
|
"operation": "decode",
|
|
"format": "base32",
|
|
"value": string(decoded),
|
|
}, "SUCCESS_BASE32_DECODE", nil
|
|
}
|
|
|
|
// obrimCodecDecodeBase64 decodes Base64 encoded data.
|
|
func obrimCodecDecodeBase64(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
value := obrimCodecTransformDecode(obrimCodecString(config.Data), config)
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(value)
|
|
if err != nil {
|
|
return nil, "FAILED_BASE64_DECODE", err
|
|
}
|
|
|
|
return map[string]any{
|
|
"operation": "decode",
|
|
"format": "base64",
|
|
"value": string(decoded),
|
|
}, "SUCCESS_BASE64_DECODE", nil
|
|
}
|
|
|
|
// obrimCodecDecodeSqids decodes Sqids encoded data using deterministic length validation.
|
|
func obrimCodecDecodeSqids(config ObrimCodecConfig) (map[string]any, string, error) {
|
|
options := sqids.Options{
|
|
MinLength: uint8(config.Length),
|
|
Alphabet: obrimCodecAlphabet(config.Charset),
|
|
Blocklist: []string{},
|
|
}
|
|
|
|
instance, err := sqids.New(options)
|
|
if err != nil {
|
|
return nil, "FAILED_SQIDS_INITIALIZATION", err
|
|
}
|
|
|
|
value := strings.TrimPrefix(obrimCodecString(config.Data), config.Salt)
|
|
|
|
decoded := instance.Decode(value)
|
|
if len(decoded) == 0 {
|
|
return nil, "FAILED_SQIDS_DECODE", errors.New("invalid sqids value")
|
|
}
|
|
|
|
return map[string]any{
|
|
"operation": "decode",
|
|
"format": "sqids",
|
|
"value": fmt.Sprintf("%d", decoded[0]),
|
|
}, "SUCCESS_SQIDS_DECODE", nil
|
|
}
|
|
|
|
// obrimCodecBytes converts supported data types to bytes.
|
|
func obrimCodecBytes(data any) []byte {
|
|
switch value := data.(type) {
|
|
case string:
|
|
return []byte(value)
|
|
case []byte:
|
|
return value
|
|
default:
|
|
return []byte{}
|
|
}
|
|
}
|
|
|
|
// obrimCodecString converts supported data types to string.
|
|
func obrimCodecString(data any) string {
|
|
switch value := data.(type) {
|
|
case string:
|
|
return value
|
|
case []byte:
|
|
return string(value)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// obrimCodecApplyCase applies output casing rules.
|
|
func obrimCodecApplyCase(value string, caseRule string) string {
|
|
if caseRule == "upper" {
|
|
return strings.ToUpper(value)
|
|
}
|
|
|
|
return strings.ToLower(value)
|
|
}
|
|
|
|
// obrimCodecTransformEncode applies charset and salt transformations.
|
|
func obrimCodecTransformEncode(value string, config ObrimCodecConfig) string {
|
|
return config.Salt + config.Charset + ":" + value
|
|
}
|
|
|
|
// obrimCodecTransformDecode reverses charset and salt transformations.
|
|
func obrimCodecTransformDecode(value string, config ObrimCodecConfig) string {
|
|
value = strings.TrimPrefix(value, config.Salt)
|
|
value = strings.TrimPrefix(value, config.Charset+":")
|
|
return value
|
|
}
|
|
|
|
// obrimCodecAlphabet resolves Sqids alphabet.
|
|
func obrimCodecAlphabet(charset string) string {
|
|
if charset == "" {
|
|
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
}
|
|
|
|
return charset
|
|
}
|
|
|
|
// obrimCodecTransformNumberInput converts input into deterministic numeric content.
|
|
func obrimCodecTransformNumberInput(config ObrimCodecConfig) string {
|
|
value := obrimCodecString(config.Data)
|
|
|
|
if _, err := strconv.ParseUint(value, 10, 64); err == nil {
|
|
return value
|
|
}
|
|
|
|
return fmt.Sprintf("%d", len(value)+len(config.Salt)+len(config.Charset))
|
|
}
|
|
|
|
// obrimCodecBytesToBase converts bytes into arbitrary base string representation.
|
|
func obrimCodecBytesToBase(config ObrimCodecConfig, base int) string {
|
|
data := obrimCodecBytes(config.Data)
|
|
|
|
var builder strings.Builder
|
|
|
|
for _, item := range data {
|
|
builder.WriteString(strconv.FormatInt(int64(item), base))
|
|
}
|
|
|
|
return builder.String()
|
|
}
|