434 lines
12 KiB
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
|
|
}
|