diff --git a/essential/hidden/service/worker/clock/clock.go b/essential/hidden/service/worker/clock/clock.go index e69de29..9130222 100644 --- a/essential/hidden/service/worker/clock/clock.go +++ b/essential/hidden/service/worker/clock/clock.go @@ -0,0 +1,514 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Clock +| +| Purpose: +| - Provides the clock synchronization service for maintaining a +| consistent system time reference across automation tasks. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package clock + +import ( + "encoding/binary" + "encoding/json" + "errors" + "net" + "os" + "path/filepath" + "runtime" + "sync" + "time" +) + +const ( + obrimClockSoftwareName = "obrim" + + obrimClockNTPPort = 123 + obrimClockNTPPacketSize = 48 + obrimClockNTPVersion = 4 + obrimClockNTPClientMode = 3 + obrimClockNTPUnixOffset = 2208988800 + obrimClockMaxRTT = 2 * time.Second + obrimClockSynchronizationInterval = 15 * time.Minute + obrimClockServerTimeout = 2 * time.Second + + obrimClockSuccess = 0 + obrimClockFailure = 1 +) + +var ( + obrimClockMutex sync.RWMutex + obrimClockStop chan struct{} + obrimClockDone chan struct{} + + obrimClockOffset int64 + obrimClockLastSync int64 + + obrimClockStarted bool +) + +// obrimClockConfig represents the persistent clock configuration. +type obrimClockConfig struct { + Clock *obrimClockState `json:"clock,omitempty"` +} + +// obrimClockFile represents the persistent configuration document. +type obrimClockFile struct { + Config *obrimClockConfig `json:"config,omitempty"` +} + +// obrimClockState represents the runtime and persistent synchronization state. +type obrimClockState struct { + ClockOffset int64 `json:"clockOffset"` + LastSync int64 `json:"lastSync"` +} + +// obrimClockMeasurement represents one accepted or rejected NTP measurement. +type obrimClockMeasurement struct { + ServerTime time.Time + RequestTime time.Time + ResponseTime time.Time + RTT time.Duration + Offset time.Duration + Valid bool +} + +// obrimClockResponse represents the timestamps extracted from an NTP response. +type obrimClockResponse struct { + ReceiveTimestamp uint64 + TransmitTimestamp uint64 +} + +// ObrimClockStart starts the clock synchronization goroutine. +func ObrimClockStart() { + obrimClockMutex.Lock() + + if obrimClockStarted { + obrimClockMutex.Unlock() + return + } + + obrimClockStop = make(chan struct{}) + obrimClockDone = make(chan struct{}) + obrimClockStarted = true + + obrimClockInitializeClock() + + stop := obrimClockStop + done := obrimClockDone + + obrimClockMutex.Unlock() + + go func() { + defer close(done) + + obrimClockSynchronizeClock() + + ticker := time.NewTicker(obrimClockSynchronizationInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + obrimClockSynchronizeClock() + case <-stop: + return + } + } + }() +} + +// ObrimClockStop gracefully stops the clock synchronization goroutine. +func ObrimClockStop() { + obrimClockMutex.Lock() + + if !obrimClockStarted { + obrimClockMutex.Unlock() + return + } + + stop := obrimClockStop + done := obrimClockDone + + obrimClockStop = nil + obrimClockDone = nil + obrimClockStarted = false + + obrimClockMutex.Unlock() + + close(stop) + <-done +} + +// obrimClockInitializeClock initializes runtime clock state from persistent storage. +func obrimClockInitializeClock() { + state, result := obrimClockLoadClock() + + obrimClockMutex.Lock() + defer obrimClockMutex.Unlock() + + if result == obrimClockSuccess && state != nil { + obrimClockOffset = state.ClockOffset + obrimClockLastSync = state.LastSync + return + } + + obrimClockOffset = 0 + obrimClockLastSync = 0 + + _ = obrimClockPersistClock() +} + +// obrimClockSynchronizeClock performs one NTP synchronization cycle. +func obrimClockSynchronizeClock() { + servers := []string{ + "time.cloudflare.com", + "time.google.com", + "pool.ntp.org", + } + + measurements := make([]obrimClockMeasurement, 0, len(servers)) + + for _, server := range servers { + measurement, result := obrimClockQueryServer(server) + + if result != obrimClockSuccess { + continue + } + + if obrimClockMeasureDelay(&measurement) != obrimClockSuccess { + continue + } + + if obrimClockCalculateOffset(&measurement) != obrimClockSuccess { + continue + } + + if obrimClockValidateMeasurement(&measurement) != obrimClockSuccess { + continue + } + + measurements = append(measurements, measurement) + } + + if len(measurements) == 0 { + return + } + + offset, result := obrimClockCalculateAverageOffset(measurements) + + if result != obrimClockSuccess { + return + } + + clockOffset := offset.Nanoseconds() + lastSync := time.Now().UnixNano() + + obrimClockUpdateClock(clockOffset, lastSync) + _ = obrimClockPersistClock() +} + +// obrimClockQueryServer sends an NTP request and receives a server response. +func obrimClockQueryServer(server string) (obrimClockMeasurement, int) { + var measurement obrimClockMeasurement + + address := net.JoinHostPort(server, "123") + + connection, err := net.DialTimeout("udp", address, obrimClockServerTimeout) + if err != nil { + return measurement, obrimClockFailure + } + defer connection.Close() + + request := make([]byte, obrimClockNTPPacketSize) + request[0] = (obrimClockNTPVersion << 3) | obrimClockNTPClientMode + + requestTime := time.Now() + + if _, err := connection.Write(request); err != nil { + return measurement, obrimClockFailure + } + + if err := connection.SetReadDeadline(time.Now().Add(obrimClockServerTimeout)); err != nil { + return measurement, obrimClockFailure + } + + response := make([]byte, obrimClockNTPPacketSize) + + if _, err := connection.Read(response); err != nil { + return measurement, obrimClockFailure + } + + responseTime := time.Now() + + if len(response) < obrimClockNTPPacketSize { + return measurement, obrimClockFailure + } + + receiveTimestamp := binary.BigEndian.Uint64(response[32:40]) + transmitTimestamp := binary.BigEndian.Uint64(response[40:48]) + + if transmitTimestamp == 0 { + return measurement, obrimClockFailure + } + + serverTime, err := obrimClockNTPToTime(transmitTimestamp) + if err != nil { + return measurement, obrimClockFailure + } + + measurement = obrimClockMeasurement{ + ServerTime: serverTime, + RequestTime: requestTime, + ResponseTime: responseTime, + } + + _ = receiveTimestamp + + return measurement, obrimClockSuccess +} + +// obrimClockMeasureDelay calculates network round-trip time. +func obrimClockMeasureDelay(measurement *obrimClockMeasurement) int { + if measurement == nil { + return obrimClockFailure + } + + if measurement.ResponseTime.Before(measurement.RequestTime) { + return obrimClockFailure + } + + measurement.RTT = measurement.ResponseTime.Sub(measurement.RequestTime) + + if measurement.RTT <= 0 || measurement.RTT > obrimClockMaxRTT { + return obrimClockFailure + } + + return obrimClockSuccess +} + +// obrimClockCalculateOffset calculates the local clock offset from an NTP response. +func obrimClockCalculateOffset(measurement *obrimClockMeasurement) int { + if measurement == nil { + return obrimClockFailure + } + + midpoint := measurement.RequestTime.Add(measurement.RTT / 2) + measurement.Offset = measurement.ServerTime.Sub(midpoint) + + return obrimClockSuccess +} + +// obrimClockValidateMeasurement validates an NTP synchronization measurement. +func obrimClockValidateMeasurement(measurement *obrimClockMeasurement) int { + if measurement == nil { + return obrimClockFailure + } + + if measurement.RTT <= 0 || measurement.RTT > obrimClockMaxRTT { + return obrimClockFailure + } + + if measurement.ServerTime.IsZero() { + return obrimClockFailure + } + + if measurement.RequestTime.IsZero() || measurement.ResponseTime.IsZero() { + return obrimClockFailure + } + + if measurement.ResponseTime.Before(measurement.RequestTime) { + return obrimClockFailure + } + + measurement.Valid = true + + return obrimClockSuccess +} + +// obrimClockCalculateAverageOffset calculates the trusted average clock offset. +func obrimClockCalculateAverageOffset(measurements []obrimClockMeasurement) (time.Duration, int) { + var total int64 + var count int64 + + for _, measurement := range measurements { + if !measurement.Valid { + continue + } + + total += measurement.Offset.Nanoseconds() + count++ + } + + if count == 0 { + return 0, obrimClockFailure + } + + return time.Duration(total / count), obrimClockSuccess +} + +// obrimClockUpdateClock updates the runtime clock state. +func obrimClockUpdateClock(clockOffset int64, lastSync int64) { + obrimClockMutex.Lock() + defer obrimClockMutex.Unlock() + + obrimClockOffset = clockOffset + obrimClockLastSync = lastSync +} + +// obrimClockPersistClock writes the runtime clock state to persistent storage. +func obrimClockPersistClock() int { + obrimClockMutex.RLock() + + state := obrimClockState{ + ClockOffset: obrimClockOffset, + LastSync: obrimClockLastSync, + } + + obrimClockMutex.RUnlock() + + configPath, err := obrimClockConfigPath() + if err != nil { + return obrimClockFailure + } + + data, err := os.ReadFile(configPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return obrimClockFailure + } + + return obrimClockFailure + } + + var document obrimClockFile + + if len(data) != 0 { + if err := json.Unmarshal(data, &document); err != nil { + return obrimClockFailure + } + } + + if document.Config == nil { + document.Config = &obrimClockConfig{} + } + + document.Config.Clock = &state + + updatedData, err := json.MarshalIndent(document, "", " ") + if err != nil { + return obrimClockFailure + } + + if err := os.WriteFile(configPath, updatedData, 0644); err != nil { + return obrimClockFailure + } + + return obrimClockSuccess +} + +// obrimClockCurrentClock returns the current synchronized UTC. +func obrimClockCurrentClock() time.Time { + obrimClockMutex.RLock() + offset := obrimClockOffset + obrimClockMutex.RUnlock() + + return time.Now().UTC().Add(time.Duration(offset)) +} + +// obrimClockLoadClock loads the persisted clock state. +func obrimClockLoadClock() (*obrimClockState, int) { + configPath, err := obrimClockConfigPath() + if err != nil { + return nil, obrimClockFailure + } + + data, err := os.ReadFile(configPath) + if err != nil { + return nil, obrimClockFailure + } + + var document obrimClockFile + + if err := json.Unmarshal(data, &document); err != nil { + return nil, obrimClockFailure + } + + if document.Config == nil || document.Config.Clock == nil { + return nil, obrimClockFailure + } + + return document.Config.Clock, obrimClockSuccess +} + +// obrimClockConfigPath returns the platform-specific persistent configuration path. +func obrimClockConfigPath() (string, error) { + var basePath string + + switch runtime.GOOS { + case "windows": + basePath = os.Getenv("APPDATA") + + if basePath == "" { + return "", errors.New("APPDATA is not defined") + } + + case "darwin": + homePath, err := os.UserHomeDir() + if err != nil { + return "", err + } + + basePath = filepath.Join(homePath, "Library", "Application Support") + + default: + homePath, err := os.UserHomeDir() + if err != nil { + return "", err + } + + basePath = filepath.Join(homePath, ".config") + } + + return filepath.Join( + basePath, + obrimClockSoftwareName, + "persistent", + "config", + "config.json", + ), nil +} + +// obrimClockNTPToTime converts an NTP timestamp into Unix UTC time. +func obrimClockNTPToTime(timestamp uint64) (time.Time, error) { + seconds := timestamp >> 32 + fraction := timestamp & 0xffffffff + + if seconds < obrimClockNTPUnixOffset { + return time.Time{}, errors.New("invalid NTP timestamp") + } + + unixSeconds := int64(seconds) - obrimClockNTPUnixOffset + nanoseconds := int64((fraction * 1_000_000_000) >> 32) + + return time.Unix(unixSeconds, nanoseconds).UTC(), nil +} diff --git a/essential/hidden/service/worker/manager/manager.go b/essential/hidden/service/worker/manager/manager.go index e69de29..7f9814a 100644 --- a/essential/hidden/service/worker/manager/manager.go +++ b/essential/hidden/service/worker/manager/manager.go @@ -0,0 +1,57 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Manager +| +| Purpose: +| - Provides the clock synchronization service for maintaining a +| consistent system time reference across automation tasks. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package manager + +import ( + "go/module/essential/hidden/service/worker/clock" + // Import more workers as needed. +) + +// ObrimWorkerManagerAction defines a worker manager action. +type ObrimWorkerManagerAction string + +const ( + // ObrimWorkerManagerActionStart starts all framework workers. + ObrimWorkerManagerActionStart ObrimWorkerManagerAction = "start" + + // ObrimWorkerManagerActionStop stops all framework workers. + ObrimWorkerManagerActionStop ObrimWorkerManagerAction = "stop" +) + +// ObrimWorkerManager starts or stops all framework workers. +func ObrimWorkerManager(action ObrimWorkerManagerAction) { + switch action { + case ObrimWorkerManagerActionStart: + clock.ObrimClockStart() + // Add more worker start functions as needed. + + case ObrimWorkerManagerActionStop: + clock.ObrimClockStop() + // Add more worker stop functions as needed. + } +} diff --git a/essential/visible/service/helper/cipher/cipher.go b/essential/visible/service/helper/cipher/cipher.go index e69de29..c6f5386 100644 --- a/essential/visible/service/helper/cipher/cipher.go +++ b/essential/visible/service/helper/cipher/cipher.go @@ -0,0 +1,641 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Cipher +| +| Purpose: +| - Provide reusable AES-256 encryption and decryption for file content. +| - Support standard and salted encryption formats through a unified interface. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use encrypt-standard to encrypt file content using AES-256 without salt. +| - Use encrypt-salted to encrypt file content using AES-256 with a cryptographically secure generated salt. +| - Use decrypt-standard to decrypt AES-256 encrypted file content without salt metadata. +| - Use decrypt-salted to decrypt self-contained encrypted files with embedded signature, version, salt, and ciphertext metadata. +| - Supply the AES-256 compatible key, input file path, and output file path through the configuration. +| +| Example: +| - ObrimCipher("encrypt-standard", map[string]any{ +| "key": key, +| "input": inputPath, +| "output": outputPath, +| }) +| +| - ObrimCipher("encrypt-salted", map[string]any{ +| "key": key, +| "input": inputPath, +| "output": outputPath, +| }) +| +| - ObrimCipher("decrypt-standard", map[string]any{ +| "key": key, +| "input": inputPath, +| "output": outputPath, +| }) +| +| - ObrimCipher("decrypt-salted", map[string]any{ +| "key": key, +| "input": inputPath, +| "output": outputPath, +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package cipher + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "errors" + "fmt" + "os" + "path/filepath" +) + +// Utility execution type identifiers. +const ( + obrimCipherTypeEncryptStandard = "encrypt-standard" + obrimCipherTypeEncryptSalted = "encrypt-salted" + obrimCipherTypeDecryptStandard = "decrypt-standard" + obrimCipherTypeDecryptSalted = "decrypt-salted" +) + +// Cipher format metadata values. +const ( + obrimCipherAlgorithm = "AES-256-GCM" + obrimCipherFormat = "OBRIM-CIPHER" + obrimCipherVersion = "1" + obrimCipherSaltSize = 32 +) + +// Utility execution success result codes. +const ( + obrimCipherSuccessEncryptStandard = "SUCCESS_ENCRYPT_STANDARD" + obrimCipherSuccessEncryptSalted = "SUCCESS_ENCRYPT_SALTED" + obrimCipherSuccessDecryptStandard = "SUCCESS_DECRYPT_STANDARD" + obrimCipherSuccessDecryptSalted = "SUCCESS_DECRYPT_SALTED" +) + +// Utility execution failure result codes. +const ( + obrimCipherFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimCipherFailureInvalidConfig = "FAILURE_INVALID_CONFIG" + obrimCipherFailureMissingKey = "FAILURE_MISSING_KEY" + obrimCipherFailureInvalidKey = "FAILURE_INVALID_KEY" + obrimCipherFailureMissingInput = "FAILURE_MISSING_INPUT" + obrimCipherFailureInvalidInput = "FAILURE_INVALID_INPUT" + obrimCipherFailureMissingOutput = "FAILURE_MISSING_OUTPUT" + obrimCipherFailureInvalidOutput = "FAILURE_INVALID_OUTPUT" + obrimCipherFailureExternalSalt = "FAILURE_EXTERNAL_SALT" + obrimCipherFailureReadInput = "FAILURE_READ_INPUT" + obrimCipherFailureWriteOutput = "FAILURE_WRITE_OUTPUT" + obrimCipherFailureCipherInitialization = "FAILURE_CIPHER_INITIALIZATION" + obrimCipherFailureNonceGeneration = "FAILURE_NONCE_GENERATION" + obrimCipherFailureEncryption = "FAILURE_ENCRYPTION" + obrimCipherFailureDecryption = "FAILURE_DECRYPTION" + obrimCipherFailureSaltGeneration = "FAILURE_SALT_GENERATION" + obrimCipherFailureHeaderBuild = "FAILURE_HEADER_BUILD" + obrimCipherFailureSignature = "FAILURE_INVALID_SIGNATURE" + obrimCipherFailureVersion = "FAILURE_INVALID_VERSION" + obrimCipherFailureSaltExtraction = "FAILURE_SALT_EXTRACTION" + obrimCipherFailurePayloadExtraction = "FAILURE_PAYLOAD_EXTRACTION" +) + +// Standardized utility output. +type obrimCipherOutput struct { + Status bool + Code string + Payload any +} + +// Salted encrypted file header. +type obrimCipherHeader struct { + Signature []byte + Version []byte + Salt []byte +} + +// Standardized operation payload. +type obrimCipherPayload struct { + Operation string + Algorithm string + Format string + Version string + InputPath string + OutputPath string +} + +// ObrimCipher executes encryption or decryption operations through a unified interface. +func ObrimCipher(typeName string, config map[string]any) map[string]any { + if code := obrimCipherValidateInput(typeName, config); code != "" { + return obrimCipherBuildOutput(false, code, nil) + } + + payload, code := obrimCipherRouteRequest(typeName, config) + if code != "" { + return obrimCipherBuildOutput(false, code, nil) + } + + return obrimCipherBuildOutput(true, code, payload) +} + +// obrimCipherValidateInput validates the requested operation and its configuration. +func obrimCipherValidateInput(typeName string, config map[string]any) string { + switch typeName { + case obrimCipherTypeEncryptStandard, + obrimCipherTypeEncryptSalted, + obrimCipherTypeDecryptStandard, + obrimCipherTypeDecryptSalted: + default: + return obrimCipherFailureInvalidType + } + + if config == nil { + return obrimCipherFailureInvalidConfig + } + + key, ok := config["key"] + if !ok || key == nil { + return obrimCipherFailureMissingKey + } + + if !obrimCipherValidKey(key) { + return obrimCipherFailureInvalidKey + } + + input, ok := config["input"] + if !ok || input == nil { + return obrimCipherFailureMissingInput + } + + inputPath, ok := input.(string) + if !ok || inputPath == "" { + return obrimCipherFailureInvalidInput + } + + output, ok := config["output"] + if !ok || output == nil { + return obrimCipherFailureMissingOutput + } + + outputPath, ok := output.(string) + if !ok || outputPath == "" { + return obrimCipherFailureInvalidOutput + } + + switch typeName { + case obrimCipherTypeEncryptSalted, obrimCipherTypeDecryptSalted: + if _, exists := config["salt"]; exists { + return obrimCipherFailureExternalSalt + } + } + + return "" +} + +// obrimCipherRouteRequest routes a validated request to its operation-specific implementation. +func obrimCipherRouteRequest(typeName string, config map[string]any) (map[string]any, string) { + switch typeName { + case obrimCipherTypeEncryptStandard: + return obrimCipherEncryptStandard(config) + case obrimCipherTypeEncryptSalted: + return obrimCipherEncryptSalted(config) + case obrimCipherTypeDecryptStandard: + return obrimCipherDecryptStandard(config) + case obrimCipherTypeDecryptSalted: + return obrimCipherDecryptSalted(config) + default: + return nil, obrimCipherFailureInvalidType + } +} + +// obrimCipherBuildOutput builds the standardized utility output. +func obrimCipherBuildOutput(status bool, code string, payload map[string]any) map[string]any { + return map[string]any{ + "status": status, + "code": code, + "payload": payload, + } +} + +// obrimCipherEncryptStandard encrypts file content without salt metadata. +func obrimCipherEncryptStandard(config map[string]any) (map[string]any, string) { + key, ok := obrimCipherKey(config["key"]) + if !ok { + return nil, obrimCipherFailureInvalidKey + } + + inputPath, outputPath, ok := obrimCipherPaths(config) + if !ok { + return nil, obrimCipherFailureInvalidConfig + } + + plaintext, err := os.ReadFile(inputPath) + if err != nil { + return nil, obrimCipherFailureReadInput + } + + ciphertext, code := obrimCipherEncrypt(key, plaintext, nil) + if code != "" { + return nil, code + } + + if err := os.WriteFile(outputPath, ciphertext, 0600); err != nil { + return nil, obrimCipherFailureWriteOutput + } + + return map[string]any{ + "operation": obrimCipherTypeEncryptStandard, + "algorithm": obrimCipherAlgorithm, + "inputPath": inputPath, + "outputPath": outputPath, + }, obrimCipherSuccessEncryptStandard +} + +// obrimCipherGenerateSalt generates a cryptographically secure salt. +func obrimCipherGenerateSalt() ([]byte, string) { + salt := make([]byte, obrimCipherSaltSize) + + if _, err := rand.Read(salt); err != nil { + return nil, obrimCipherFailureSaltGeneration + } + + return salt, "" +} + +// obrimCipherBuildHeader builds the salted cipher signature, version, and salt header. +func obrimCipherBuildHeader(salt []byte) ([]byte, string) { + if len(salt) != obrimCipherSaltSize { + return nil, obrimCipherFailureHeaderBuild + } + + signature := []byte(obrimCipherFormat) + version := []byte(obrimCipherVersion) + + header := make([]byte, 0, len(signature)+len(version)+len(salt)) + header = append(header, signature...) + header = append(header, version...) + header = append(header, salt...) + + return header, "" +} + +// obrimCipherEncryptSalted encrypts file content with embedded salt metadata. +func obrimCipherEncryptSalted(config map[string]any) (map[string]any, string) { + key, ok := obrimCipherKey(config["key"]) + if !ok { + return nil, obrimCipherFailureInvalidKey + } + + inputPath, outputPath, ok := obrimCipherPaths(config) + if !ok { + return nil, obrimCipherFailureInvalidConfig + } + + plaintext, err := os.ReadFile(inputPath) + if err != nil { + return nil, obrimCipherFailureReadInput + } + + salt, code := obrimCipherGenerateSalt() + if code != "" { + return nil, code + } + + header, code := obrimCipherBuildHeader(salt) + if code != "" { + return nil, code + } + + ciphertext, code := obrimCipherEncrypt(key, plaintext, salt) + if code != "" { + return nil, code + } + + output := make([]byte, 0, len(header)+len(ciphertext)) + output = append(output, header...) + output = append(output, ciphertext...) + + if err := os.WriteFile(outputPath, output, 0600); err != nil { + return nil, obrimCipherFailureWriteOutput + } + + return map[string]any{ + "operation": obrimCipherTypeEncryptSalted, + "algorithm": obrimCipherAlgorithm, + "format": obrimCipherFormat, + "version": obrimCipherVersion, + "inputPath": inputPath, + "outputPath": outputPath, + }, obrimCipherSuccessEncryptSalted +} + +// obrimCipherDecryptStandard decrypts file content without salt metadata. +func obrimCipherDecryptStandard(config map[string]any) (map[string]any, string) { + key, ok := obrimCipherKey(config["key"]) + if !ok { + return nil, obrimCipherFailureInvalidKey + } + + inputPath, outputPath, ok := obrimCipherPaths(config) + if !ok { + return nil, obrimCipherFailureInvalidConfig + } + + ciphertext, err := os.ReadFile(inputPath) + if err != nil { + return nil, obrimCipherFailureReadInput + } + + plaintext, code := obrimCipherDecrypt(key, ciphertext, nil) + if code != "" { + return nil, code + } + + if err := os.WriteFile(outputPath, plaintext, 0600); err != nil { + return nil, obrimCipherFailureWriteOutput + } + + return map[string]any{ + "operation": obrimCipherTypeDecryptStandard, + "algorithm": obrimCipherAlgorithm, + "inputPath": inputPath, + "outputPath": outputPath, + }, obrimCipherSuccessDecryptStandard +} + +// obrimCipherParseHeader parses the signature and version metadata. +func obrimCipherParseHeader(data []byte) (obrimCipherHeader, string) { + signatureLength := len(obrimCipherFormat) + versionLength := len(obrimCipherVersion) + + minimumLength := signatureLength + versionLength + obrimCipherSaltSize + if len(data) < minimumLength { + return obrimCipherHeader{}, obrimCipherFailureSignature + } + + signature := data[:signatureLength] + if string(signature) != obrimCipherFormat { + return obrimCipherHeader{}, obrimCipherFailureSignature + } + + versionStart := signatureLength + versionEnd := versionStart + versionLength + version := data[versionStart:versionEnd] + + if string(version) != obrimCipherVersion { + return obrimCipherHeader{}, obrimCipherFailureVersion + } + + saltStart := versionEnd + saltEnd := saltStart + obrimCipherSaltSize + + return obrimCipherHeader{ + Signature: append([]byte(nil), signature...), + Version: append([]byte(nil), version...), + Salt: append([]byte(nil), data[saltStart:saltEnd]...), + }, "" +} + +// obrimCipherExtractSalt extracts the embedded salt from a parsed salted file. +func obrimCipherExtractSalt(data []byte, header obrimCipherHeader) ([]byte, string) { + if len(header.Salt) != obrimCipherSaltSize { + return nil, obrimCipherFailureSaltExtraction + } + + signatureLength := len(header.Signature) + versionLength := len(header.Version) + saltStart := signatureLength + versionLength + saltEnd := saltStart + obrimCipherSaltSize + + if saltStart < 0 || saltEnd > len(data) { + return nil, obrimCipherFailureSaltExtraction + } + + return append([]byte(nil), data[saltStart:saltEnd]...), "" +} + +// obrimCipherExtractPayload extracts the encrypted payload after the salted header. +func obrimCipherExtractPayload(data []byte, header obrimCipherHeader) ([]byte, string) { + payloadStart := len(header.Signature) + len(header.Version) + len(header.Salt) + + if payloadStart >= len(data) { + return nil, obrimCipherFailurePayloadExtraction + } + + payload := data[payloadStart:] + if len(payload) <= 0 { + return nil, obrimCipherFailurePayloadExtraction + } + + return append([]byte(nil), payload...), "" +} + +// obrimCipherDecryptSalted decrypts file content using its embedded salt metadata. +func obrimCipherDecryptSalted(config map[string]any) (map[string]any, string) { + key, ok := obrimCipherKey(config["key"]) + if !ok { + return nil, obrimCipherFailureInvalidKey + } + + inputPath, outputPath, ok := obrimCipherPaths(config) + if !ok { + return nil, obrimCipherFailureInvalidConfig + } + + data, err := os.ReadFile(inputPath) + if err != nil { + return nil, obrimCipherFailureReadInput + } + + header, code := obrimCipherParseHeader(data) + if code != "" { + return nil, code + } + + salt, code := obrimCipherExtractSalt(data, header) + if code != "" { + return nil, code + } + + payload, code := obrimCipherExtractPayload(data, header) + if code != "" { + return nil, code + } + + plaintext, code := obrimCipherDecrypt(key, payload, salt) + if code != "" { + return nil, code + } + + if err := os.WriteFile(outputPath, plaintext, 0600); err != nil { + return nil, obrimCipherFailureWriteOutput + } + + return map[string]any{ + "operation": obrimCipherTypeDecryptSalted, + "algorithm": obrimCipherAlgorithm, + "format": string(header.Signature), + "version": string(header.Version), + "inputPath": inputPath, + "outputPath": outputPath, + }, obrimCipherSuccessDecryptSalted +} + +// obrimCipherEncrypt performs AES-256-GCM encryption and prefixes the nonce to the ciphertext. +func obrimCipherEncrypt(key []byte, plaintext []byte, salt []byte) ([]byte, string) { + derivedKey := obrimCipherDeriveKey(key, salt) + + block, err := aes.NewCipher(derivedKey) + if err != nil { + return nil, obrimCipherFailureCipherInitialization + } + + aead, err := cipher.NewGCM(block) + if err != nil { + return nil, obrimCipherFailureCipherInitialization + } + + nonce := make([]byte, aead.NonceSize()) + if _, err := rand.Read(nonce); err != nil { + return nil, obrimCipherFailureNonceGeneration + } + + ciphertext := aead.Seal(nil, nonce, plaintext, nil) + + output := make([]byte, 0, len(nonce)+len(ciphertext)) + output = append(output, nonce...) + output = append(output, ciphertext...) + + return output, "" +} + +// obrimCipherDecrypt performs AES-256-GCM decryption after extracting the nonce. +func obrimCipherDecrypt(key []byte, ciphertext []byte, salt []byte) ([]byte, string) { + derivedKey := obrimCipherDeriveKey(key, salt) + + block, err := aes.NewCipher(derivedKey) + if err != nil { + return nil, obrimCipherFailureCipherInitialization + } + + aead, err := cipher.NewGCM(block) + if err != nil { + return nil, obrimCipherFailureCipherInitialization + } + + nonceSize := aead.NonceSize() + if len(ciphertext) <= nonceSize { + return nil, obrimCipherFailureDecryption + } + + nonce := ciphertext[:nonceSize] + payload := ciphertext[nonceSize:] + + plaintext, err := aead.Open(nil, nonce, payload, nil) + if err != nil { + return nil, obrimCipherFailureDecryption + } + + return plaintext, "" +} + +// obrimCipherDeriveKey derives the AES-256 key from the supplied key and optional salt. +func obrimCipherDeriveKey(key []byte, salt []byte) []byte { + if len(salt) == 0 { + return append([]byte(nil), key...) + } + + digest := sha256.New() + _, _ = digest.Write(key) + _, _ = digest.Write(salt) + + return digest.Sum(nil) +} + +// obrimCipherKey normalizes a supported AES-256 key value. +func obrimCipherKey(value any) ([]byte, bool) { + switch key := value.(type) { + case string: + if len([]byte(key)) != 32 { + return nil, false + } + + return []byte(key), true + case []byte: + if len(key) != 32 { + return nil, false + } + + return append([]byte(nil), key...), true + default: + return nil, false + } +} + +// obrimCipherValidKey validates that a supplied key is AES-256 compatible. +func obrimCipherValidKey(value any) bool { + _, ok := obrimCipherKey(value) + return ok +} + +// obrimCipherPaths resolves and validates the configured input and output paths. +func obrimCipherPaths(config map[string]any) (string, string, bool) { + input, inputOK := config["input"].(string) + output, outputOK := config["output"].(string) + + if !inputOK || !outputOK || input == "" || output == "" { + return "", "", false + } + + inputPath, err := filepath.Abs(input) + if err != nil { + return "", "", false + } + + outputPath, err := filepath.Abs(output) + if err != nil { + return "", "", false + } + + if inputPath == outputPath { + return "", "", false + } + + if _, err := os.Stat(inputPath); err != nil { + return "", "", false + } + + return inputPath, outputPath, true +} + +// obrimCipherError provides a deterministic error representation for internal callers. +func obrimCipherError(code string) error { + return errors.New(fmt.Sprintf("cipher: %s", code)) +} diff --git a/essential/visible/service/helper/codec/codec.go b/essential/visible/service/helper/codec/codec.go index e69de29..ef52cd6 100644 --- a/essential/visible/service/helper/codec/codec.go +++ b/essential/visible/service/helper/codec/codec.go @@ -0,0 +1,689 @@ +/* +|-------------------------------------------------------------------------- +| 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 diff --git a/essential/visible/service/helper/datetime/datetime.go b/essential/visible/service/helper/datetime/datetime.go index e69de29..d58e64c 100644 --- a/essential/visible/service/helper/datetime/datetime.go +++ b/essential/visible/service/helper/datetime/datetime.go @@ -0,0 +1,344 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Datetime +| +| Purpose: +| - Retrieve the current date and time from the host system clock or +| trusted clock state and return the formatted result using a supported +| framework date/time pattern. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use local to retrieve the current date and time while preserving the +| host system timezone. +| - Use cloud to retrieve the current trusted UTC date and time using the +| resolved clock state. +| - Provide the format configuration when a specific supported date/time +| representation is required. +| - Use the utility through ObrimDatetime(type string, config map[string]any). +| +| Example: +| - ObrimDatetime("local", map[string]any{ +| "format": "yyyy-MM-dd", +| }) +| +| - ObrimDatetime("local", map[string]any{ +| "format": "HH:mm:ss", +| }) +| +| - ObrimDatetime("cloud", map[string]any{ +| "format": "yyyy-MM-dd HH:mm:ss z", +| }) +| +| - ObrimDatetime("cloud", map[string]any{ +| "format": "yyyy-MM-dd'T'HH:mm:ssXXX", +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package datetime + +import ( + "time" + + "go/module/essential/hidden/service/worker/clock" +) + +// Reusable datetime input and result constants. +const ( + obrimDatetimeTypeLocal = "local" + obrimDatetimeTypeCloud = "cloud" + + obrimDatetimeFormatTimestampSec = "timestampsec" + obrimDatetimeFormatTimestampMil = "timestampmil" + obrimDatetimeFormatYearMonthDay = "yyyy-MM-dd" + obrimDatetimeFormatMonthDayYear = "MMM dd, yyyy" + obrimDatetimeFormatFullMonthDayYear = "MMMM dd, yyyy" + obrimDatetimeFormatWeekdayMonthDayYear = "EEE, MMM dd, yyyy" + obrimDatetimeFormatWeekdayFullMonthDayYear = "EEE, MMMM dd, yyyy" + obrimDatetimeFormatFullWeekdayMonthDayYear = "EEEE, MMM dd, yyyy" + obrimDatetimeFormatFullWeekdayFullMonthDayYear = "EEEE, MMMM dd, yyyy" + obrimDatetimeFormatTime = "HH:mm:ss" + obrimDatetimeFormatTime12 = "hh:mm:ss a" + obrimDatetimeFormatDateTime = "yyyy-MM-dd HH:mm:ss" + obrimDatetimeFormatDateTimeZone = "yyyy-MM-dd HH:mm:ss z" + obrimDatetimeFormatISO8601 = "yyyy-MM-dd'T'HH:mm:ssXXX" + + obrimDatetimeSuccessRetrieved = "SUCCESS_DATETIME_RETRIEVED" + obrimDatetimeFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimDatetimeFailureInvalidFormat = "FAILURE_INVALID_FORMAT" + obrimDatetimeFailureTrustedTimeSource = "FAILURE_TRUSTED_TIME_SOURCE_ERROR" +) + +// Datetime clock state contains the resolved synchronization information. +type obrimDatetimeClockState struct { + clockOffset int64 + lastSync int64 +} + +// Datetime output contains the standardized utility response. +type obrimDatetimeOutput struct { + Status bool `json:"status"` + Code string `json:"code"` + Payload any `json:"payload"` +} + +// Datetime payload contains the formatted datetime result. +type obrimDatetimePayload struct { + Value string `json:"value"` +} + +// ObrimDatetime retrieves and formats the current date and time. +func ObrimDatetime(datetimeType string, config map[string]any) map[string]any { + format, code := obrimDatetimeValidateInput(datetimeType, config) + if code != "" { + return obrimDatetimeBuildOutput(false, code, nil) + } + + state, available := obrimDatetimeResolveState() + + switch datetimeType { + case obrimDatetimeTypeLocal: + value := obrimDatetimeLocal(format, state, available) + return obrimDatetimeBuildOutput( + true, + obrimDatetimeSuccessRetrieved, + &obrimDatetimePayload{Value: value}, + ) + + case obrimDatetimeTypeCloud: + if !available { + return obrimDatetimeBuildOutput( + false, + obrimDatetimeFailureTrustedTimeSource, + nil, + ) + } + + value := obrimDatetimeCloud(format, state) + return obrimDatetimeBuildOutput( + true, + obrimDatetimeSuccessRetrieved, + &obrimDatetimePayload{Value: value}, + ) + + default: + return obrimDatetimeBuildOutput(false, obrimDatetimeFailureInvalidType, nil) + } +} + +// obrimDatetimeValidateInput validates the requested datetime type and format. +func obrimDatetimeValidateInput(datetimeType string, config map[string]any) (string, string) { + switch datetimeType { + case obrimDatetimeTypeLocal, obrimDatetimeTypeCloud: + default: + return "", obrimDatetimeFailureInvalidType + } + + format := "" + if config != nil { + if value, exists := config["format"]; exists { + var valid bool + format, valid = value.(string) + if !valid || format == "" { + return "", obrimDatetimeFailureInvalidFormat + } + } + } + + if format == "" { + format = obrimDatetimeFormatDateTime + } + + switch format { + case obrimDatetimeFormatTimestampSec, + obrimDatetimeFormatTimestampMil, + obrimDatetimeFormatYearMonthDay, + obrimDatetimeFormatMonthDayYear, + obrimDatetimeFormatFullMonthDayYear, + obrimDatetimeFormatWeekdayMonthDayYear, + obrimDatetimeFormatWeekdayFullMonthDayYear, + obrimDatetimeFormatFullWeekdayMonthDayYear, + obrimDatetimeFormatFullWeekdayFullMonthDayYear, + obrimDatetimeFormatTime, + obrimDatetimeFormatTime12, + obrimDatetimeFormatDateTime, + obrimDatetimeFormatDateTimeZone, + obrimDatetimeFormatISO8601: + return format, "" + default: + return "", obrimDatetimeFailureInvalidFormat + } +} + +// obrimDatetimeRouteRequest routes the request to its type-specific operation. +func obrimDatetimeRouteRequest( + datetimeType string, + format string, + state obrimDatetimeClockState, + available bool, +) (string, string) { + switch datetimeType { + case obrimDatetimeTypeLocal: + return obrimDatetimeLocal(format, state, available), "" + case obrimDatetimeTypeCloud: + if !available { + return "", obrimDatetimeFailureTrustedTimeSource + } + return obrimDatetimeCloud(format, state), "" + default: + return "", obrimDatetimeFailureInvalidType + } +} + +// obrimDatetimeBuildOutput builds the standardized utility output. +func obrimDatetimeBuildOutput( + status bool, + code string, + payload any, +) map[string]any { + output := obrimDatetimeOutput{ + Status: status, + Code: code, + Payload: payload, + } + + return map[string]any{ + "status": output.Status, + "code": output.Code, + "payload": output.Payload, + } +} + +// obrimDatetimeResolveState resolves trusted clock state through the clock service. +func obrimDatetimeResolveState() (obrimDatetimeClockState, bool) { + now := time.Now() + trusted := clock.ObrimClockCurrentClock() + + offset := trusted.Sub(now.UTC()) + + if offset == 0 { + return obrimDatetimeClockState{ + clockOffset: 0, + lastSync: 0, + }, false + } + + return obrimDatetimeClockState{ + clockOffset: offset.Nanoseconds(), + lastSync: trusted.UnixNano(), + }, true +} + +// obrimDatetimeApplyOffset applies the resolved clock offset to system time. +func obrimDatetimeApplyOffset(value time.Time, state obrimDatetimeClockState) time.Time { + return value.Add(time.Duration(state.clockOffset)) +} + +// obrimDatetimeFormat formats a datetime using a framework-supported pattern. +func obrimDatetimeFormat(value time.Time, format string) string { + switch format { + case obrimDatetimeFormatTimestampSec: + return formatTimestampSeconds(value) + + case obrimDatetimeFormatTimestampMil: + return formatTimestampMilliseconds(value) + + case obrimDatetimeFormatYearMonthDay: + return value.Format("2006-01-02") + + case obrimDatetimeFormatMonthDayYear: + return value.Format("Jan 02, 2006") + + case obrimDatetimeFormatFullMonthDayYear: + return value.Format("January 02, 2006") + + case obrimDatetimeFormatWeekdayMonthDayYear: + return value.Format("Mon, Jan 02, 2006") + + case obrimDatetimeFormatWeekdayFullMonthDayYear: + return value.Format("Mon, January 02, 2006") + + case obrimDatetimeFormatFullWeekdayMonthDayYear: + return value.Format("Monday, Jan 02, 2006") + + case obrimDatetimeFormatFullWeekdayFullMonthDayYear: + return value.Format("Monday, January 02, 2006") + + case obrimDatetimeFormatTime: + return value.Format("15:04:05") + + case obrimDatetimeFormatTime12: + return value.Format("03:04:05 PM") + + case obrimDatetimeFormatDateTime: + return value.Format("2006-01-02 15:04:05") + + case obrimDatetimeFormatDateTimeZone: + return value.Format("2006-01-02 15:04:05 MST") + + case obrimDatetimeFormatISO8601: + return value.Format("2006-01-02T15:04:05Z07:00") + + default: + return "" + } +} + +// formatTimestampSeconds formats a datetime as a Unix timestamp in seconds. +func formatTimestampSeconds(value time.Time) string { + return value.Format("1136239445") +} + +// formatTimestampMilliseconds formats a datetime as a Unix timestamp in milliseconds. +func formatTimestampMilliseconds(value time.Time) string { + return value.Format("1136239445123") +} + +// obrimDatetimeLocal retrieves and formats corrected local system time. +func obrimDatetimeLocal( + format string, + state obrimDatetimeClockState, + available bool, +) string { + value := time.Now() + + if available { + value = obrimDatetimeApplyOffset(value, state) + } + + return obrimDatetimeFormat(value, format) +} + +// obrimDatetimeCloud retrieves and formats corrected trusted UTC time. +func obrimDatetimeCloud( + format string, + state obrimDatetimeClockState, +) string { + value := obrimDatetimeApplyOffset(time.Now().UTC(), state).UTC() + return obrimDatetimeFormat(value, format) +} diff --git a/essential/visible/service/helper/filesystem/filesystem.go b/essential/visible/service/helper/filesystem/filesystem.go index e69de29..632c3ee 100644 --- a/essential/visible/service/helper/filesystem/filesystem.go +++ b/essential/visible/service/helper/filesystem/filesystem.go @@ -0,0 +1,1212 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Filesystem +| +| Purpose: +| - Provide a reusable, OS-agnostic utility to deterministically perform +| filesystem operations through a unified execution interface supporting +| listing, validation, creation, deletion, and permission management with +| consistent behavior and structured results. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use ObrimFilesystem() as the single public entry point. +| - Execute one filesystem operation per invocation. +| - Validate the operation type and type-specific configuration before +| processing. +| - Resolve filesystem paths using the supported user or custom strategy. +| - Use list for filesystem traversal, filtering, sorting, and discovery. +| - Use check for filesystem existence, entity type, and accessibility +| validation. +| - Use create for creating exactly one file or directory. +| - Use delete for deleting exactly one file or directory, with optional +| recursive directory deletion. +| - Use permission for retrieving or updating filesystem permissions. +| - Return standardized structured results for both successful and failed +| executions. +| +| Example: +| - ObrimFilesystem("list", map[string]any{ +| "entity": "file", +| "strategy": "user", +| "base": "", +| "name": "Documents", +| "recursive": true, +| "include_hidden": false, +| "sort_by": "name", +| "sort_order": "asc", +| "filter_pattern": "*.txt", +| "follow_symlink": false, +| "absolute_path": true, +| }) +| +| - ObrimFilesystem("check", map[string]any{ +| "entity": "file", +| "strategy": "custom", +| "base": "/tmp", +| "name": "example.txt", +| "readable": true, +| "writable": true, +| }) +| +| - ObrimFilesystem("create", map[string]any{ +| "entity": "file", +| "strategy": "custom", +| "base": "/tmp", +| "name": "example.txt", +| "hidden": false, +| }) +| +| - ObrimFilesystem("delete", map[string]any{ +| "entity": "directory", +| "strategy": "custom", +| "base": "/tmp", +| "name": "workspace", +| "recursive": true, +| }) +| +| - ObrimFilesystem("permission", map[string]any{ +| "strategy": "custom", +| "base": "/tmp", +| "name": "example.txt", +| "mode": "write", +| "permission": "0644", +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package filesystem + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +// Defines filesystem operation types. +const ( + obrimFilesystemTypeList = "list" + obrimFilesystemTypeCheck = "check" + obrimFilesystemTypeCreate = "create" + obrimFilesystemTypeDelete = "delete" + obrimFilesystemTypePermission = "permission" +) + +// Defines supported filesystem entity types. +const ( + obrimFilesystemEntityFile = "file" + obrimFilesystemEntityDirectory = "directory" +) + +// Defines supported filesystem path resolution strategies. +const ( + obrimFilesystemStrategyUser = "user" + obrimFilesystemStrategyCustom = "custom" +) + +// Defines supported permission operation modes. +const ( + obrimFilesystemModeRead = "read" + obrimFilesystemModeWrite = "write" +) + +// Defines supported listing sort criteria. +const ( + obrimFilesystemSortName = "name" + obrimFilesystemSortSize = "size" + obrimFilesystemSortModified = "modified" + obrimFilesystemSortCreated = "created" +) + +// Defines supported listing sort orders. +const ( + obrimFilesystemSortOrderAsc = "asc" + obrimFilesystemSortOrderDesc = "desc" +) + +// Defines filesystem execution result codes. +const ( + obrimFilesystemSuccessList = "SUCCESS_LIST" + obrimFilesystemSuccessCheck = "SUCCESS_CHECK" + obrimFilesystemSuccessCreate = "SUCCESS_CREATE" + obrimFilesystemSuccessDelete = "SUCCESS_DELETE" + obrimFilesystemSuccessPermission = "SUCCESS_PERMISSION" + + obrimFilesystemFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimFilesystemFailureInvalidConfig = "FAILURE_INVALID_CONFIG" + obrimFilesystemFailureInvalidEntity = "FAILURE_INVALID_ENTITY" + obrimFilesystemFailureInvalidStrategy = "FAILURE_INVALID_STRATEGY" + obrimFilesystemFailureInvalidSortBy = "FAILURE_INVALID_SORT_BY" + obrimFilesystemFailureInvalidSortOrder = "FAILURE_INVALID_SORT_ORDER" + obrimFilesystemFailureInvalidMode = "FAILURE_INVALID_MODE" + obrimFilesystemFailureInvalidPermission = "FAILURE_INVALID_PERMISSION" + obrimFilesystemFailureResolvePath = "FAILURE_RESOLVE_PATH" + obrimFilesystemFailureList = "FAILURE_LIST" + obrimFilesystemFailureCheck = "FAILURE_CHECK" + obrimFilesystemFailureCreate = "FAILURE_CREATE" + obrimFilesystemFailureDelete = "FAILURE_DELETE" + obrimFilesystemFailurePermission = "FAILURE_PERMISSION" + obrimFilesystemFailureEntityNotFound = "FAILURE_ENTITY_NOT_FOUND" + obrimFilesystemFailureEntityTypeMismatch = "FAILURE_ENTITY_TYPE_MISMATCH" + obrimFilesystemFailureReadAccess = "FAILURE_READ_ACCESS" + obrimFilesystemFailureWriteAccess = "FAILURE_WRITE_ACCESS" +) + +// Defines a filesystem listing entry. +type obrimFilesystemEntry struct { + Path string `json:"path"` + Name string `json:"name"` + Entity string `json:"entity"` + Size int64 `json:"size"` + Modified string `json:"modified"` + Created string `json:"created"` + Hidden bool `json:"hidden"` + Read bool `json:"readable"` + Write bool `json:"writable"` +} + +// Defines the filesystem utility result. +type obrimFilesystemResult struct { + Status bool `json:"status"` + Code string `json:"code"` + Payload map[string]any `json:"payload"` +} + +// Defines listing configuration. +type obrimFilesystemListConfig struct { + Entity string + Strategy string + Base string + Name string + Recursive bool + IncludeHidden bool + SortBy string + SortOrder string + FilterPattern string + FollowSymlink bool + AbsolutePath bool +} + +// Defines check configuration. +type obrimFilesystemCheckConfig struct { + Entity string + Strategy string + Base string + Name string + Readable bool + Writable bool +} + +// Defines create configuration. +type obrimFilesystemCreateConfig struct { + Entity string + Strategy string + Base string + Name string + Hidden bool +} + +// Defines delete configuration. +type obrimFilesystemDeleteConfig struct { + Entity string + Strategy string + Base string + Name string + Recursive bool +} + +// Defines permission configuration. +type obrimFilesystemPermissionConfig struct { + Strategy string + Base string + Name string + Mode string + Permission string +} + +// Executes a filesystem operation and returns a structured result. +func ObrimFilesystem(operationType string, config map[string]any) map[string]any { + if err := obrimFilesystemValidateInput(operationType, config); err != nil { + return obrimFilesystemBuildOutput(false, err.code, nil) + } + + payload, code, err := obrimFilesystemRouteRequest(operationType, config) + if err != nil { + return obrimFilesystemBuildOutput(false, code, nil) + } + + return obrimFilesystemBuildOutput(true, code, payload) +} + +// Defines a filesystem validation error. +type obrimFilesystemValidationError struct { + code string +} + +// Validates the requested operation and its configuration. +func obrimFilesystemValidateInput(operationType string, config map[string]any) *obrimFilesystemValidationError { + if config == nil { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + switch operationType { + case obrimFilesystemTypeList: + return obrimFilesystemValidateListConfig(config) + case obrimFilesystemTypeCheck: + return obrimFilesystemValidateCheckConfig(config) + case obrimFilesystemTypeCreate: + return obrimFilesystemValidateCreateConfig(config) + case obrimFilesystemTypeDelete: + return obrimFilesystemValidateDeleteConfig(config) + case obrimFilesystemTypePermission: + return obrimFilesystemValidatePermissionConfig(config) + default: + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidType} + } +} + +// Routes a validated filesystem request to its operation-specific function. +func obrimFilesystemRouteRequest(operationType string, config map[string]any) (map[string]any, string, error) { + switch operationType { + case obrimFilesystemTypeList: + payload, err := obrimFilesystemList(config) + if err != nil { + return nil, obrimFilesystemFailureList, err + } + return payload, obrimFilesystemSuccessList, nil + + case obrimFilesystemTypeCheck: + payload, code, err := obrimFilesystemCheck(config) + if err != nil { + return nil, code, err + } + return payload, obrimFilesystemSuccessCheck, nil + + case obrimFilesystemTypeCreate: + payload, err := obrimFilesystemCreate(config) + if err != nil { + return nil, obrimFilesystemFailureCreate, err + } + return payload, obrimFilesystemSuccessCreate, nil + + case obrimFilesystemTypeDelete: + payload, err := obrimFilesystemDelete(config) + if err != nil { + return nil, obrimFilesystemFailureDelete, err + } + return payload, obrimFilesystemSuccessDelete, nil + + case obrimFilesystemTypePermission: + payload, err := obrimFilesystemPermission(config) + if err != nil { + return nil, obrimFilesystemFailurePermission, err + } + return payload, obrimFilesystemSuccessPermission, nil + + default: + return nil, obrimFilesystemFailureInvalidType, fmt.Errorf("unsupported filesystem operation") + } +} + +// Builds the standardized filesystem output. +func obrimFilesystemBuildOutput(status bool, code string, payload map[string]any) map[string]any { + return map[string]any{ + "status": status, + "code": code, + "payload": payload, + } +} + +// Validates list configuration. +func obrimFilesystemValidateListConfig(config map[string]any) *obrimFilesystemValidationError { + entity := obrimFilesystemString(config, "entity") + strategy := obrimFilesystemString(config, "strategy") + sortBy := obrimFilesystemString(config, "sort_by") + sortOrder := obrimFilesystemString(config, "sort_order") + + if entity != "" && entity != obrimFilesystemEntityFile && entity != obrimFilesystemEntityDirectory { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidEntity} + } + + if strategy != "" && strategy != obrimFilesystemStrategyUser && strategy != obrimFilesystemStrategyCustom { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidStrategy} + } + + if sortBy != "" { + switch sortBy { + case obrimFilesystemSortName, obrimFilesystemSortSize, obrimFilesystemSortModified, obrimFilesystemSortCreated: + default: + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidSortBy} + } + } + + if sortOrder != "" && sortOrder != "asc" && sortOrder != "desc" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidSortOrder} + } + + if strategy == obrimFilesystemStrategyCustom && obrimFilesystemString(config, "base") == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + return nil +} + +// Validates check configuration. +func obrimFilesystemValidateCheckConfig(config map[string]any) *obrimFilesystemValidationError { + entity := obrimFilesystemString(config, "entity") + strategy := obrimFilesystemString(config, "strategy") + name := obrimFilesystemString(config, "name") + + if entity != obrimFilesystemEntityFile && entity != obrimFilesystemEntityDirectory { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidEntity} + } + + if strategy != "" && strategy != obrimFilesystemStrategyUser && strategy != obrimFilesystemStrategyCustom { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidStrategy} + } + + if name == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + if strategy == obrimFilesystemStrategyCustom && obrimFilesystemString(config, "base") == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + return nil +} + +// Validates create configuration. +func obrimFilesystemValidateCreateConfig(config map[string]any) *obrimFilesystemValidationError { + entity := obrimFilesystemString(config, "entity") + strategy := obrimFilesystemString(config, "strategy") + name := obrimFilesystemString(config, "name") + + if entity != obrimFilesystemEntityFile && entity != obrimFilesystemEntityDirectory { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidEntity} + } + + if strategy != "" && strategy != obrimFilesystemStrategyUser && strategy != obrimFilesystemStrategyCustom { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidStrategy} + } + + if name == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + if strategy == obrimFilesystemStrategyCustom && obrimFilesystemString(config, "base") == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + return nil +} + +// Validates delete configuration. +func obrimFilesystemValidateDeleteConfig(config map[string]any) *obrimFilesystemValidationError { + entity := obrimFilesystemString(config, "entity") + strategy := obrimFilesystemString(config, "strategy") + name := obrimFilesystemString(config, "name") + + if entity != obrimFilesystemEntityFile && entity != obrimFilesystemEntityDirectory { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidEntity} + } + + if strategy != "" && strategy != obrimFilesystemStrategyUser && strategy != obrimFilesystemStrategyCustom { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidStrategy} + } + + if name == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + if strategy == obrimFilesystemStrategyCustom && obrimFilesystemString(config, "base") == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + return nil +} + +// Validates permission configuration. +func obrimFilesystemValidatePermissionConfig(config map[string]any) *obrimFilesystemValidationError { + strategy := obrimFilesystemString(config, "strategy") + name := obrimFilesystemString(config, "name") + mode := obrimFilesystemString(config, "mode") + + if strategy != "" && strategy != obrimFilesystemStrategyUser && strategy != obrimFilesystemStrategyCustom { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidStrategy} + } + + if name == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + if mode != obrimFilesystemModeRead && mode != obrimFilesystemModeWrite { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidMode} + } + + if mode == obrimFilesystemModeWrite { + permission := obrimFilesystemString(config, "permission") + if _, err := strconv.ParseUint(permission, 8, 32); err != nil || len(permission) < 3 || len(permission) > 4 { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidPermission} + } + } + + if strategy == obrimFilesystemStrategyCustom && obrimFilesystemString(config, "base") == "" { + return &obrimFilesystemValidationError{code: obrimFilesystemFailureInvalidConfig} + } + + return nil +} + +// Executes a filesystem listing operation. +func obrimFilesystemList(config map[string]any) (map[string]any, error) { + options := obrimFilesystemListConfig{ + Entity: obrimFilesystemString(config, "entity"), + Strategy: obrimFilesystemStringDefault(config, "strategy", obrimFilesystemStrategyUser), + Base: obrimFilesystemString(config, "base"), + Name: obrimFilesystemString(config, "name"), + Recursive: obrimFilesystemBool(config, "recursive"), + IncludeHidden: obrimFilesystemBool(config, "include_hidden"), + SortBy: obrimFilesystemStringDefault(config, "sort_by", obrimFilesystemSortName), + SortOrder: obrimFilesystemStringDefault(config, "sort_order", "asc"), + FilterPattern: obrimFilesystemString(config, "filter_pattern"), + FollowSymlink: obrimFilesystemBool(config, "follow_symlink"), + AbsolutePath: obrimFilesystemBool(config, "absolute_path"), + } + + basePath, err := obrimFilesystemResolvePath(options.Strategy, options.Base, options.Name) + if err != nil { + return nil, err + } + + entries, err := obrimFilesystemListTraverse(basePath, options) + if err != nil { + return nil, err + } + + entries = obrimFilesystemListFilter(entries, options) + obrimFilesystemListSort(entries, options.SortBy, options.SortOrder) + + return map[string]any{ + "entries": entries, + "total": len(entries), + "recursive": options.Recursive, + "base_path": basePath, + }, nil +} + +// Traverses the filesystem according to listing options. +func obrimFilesystemListTraverse(basePath string, options obrimFilesystemListConfig) ([]map[string]any, error) { + info, err := os.Lstat(basePath) + if err != nil { + return nil, err + } + + if !info.IsDir() { + return nil, fmt.Errorf("listing root is not a directory") + } + + entries := make([]map[string]any, 0) + + if !options.Recursive { + items, err := os.ReadDir(basePath) + if err != nil { + return nil, err + } + + for _, item := range items { + entry, include, err := obrimFilesystemListEntry(item, basePath, options) + if err != nil { + return nil, err + } + if include { + entries = append(entries, entry) + } + } + + return entries, nil + } + + err = filepath.Walk(basePath, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if path == basePath { + return nil + } + + if info.Mode()&os.ModeSymlink != 0 && options.FollowSymlink { + targetInfo, targetErr := os.Stat(path) + if targetErr != nil { + return targetErr + } + info = targetInfo + } + + entry := obrimFilesystemEntryFromInfo(path, info, options.AbsolutePath) + + if entry["hidden"] == true && !options.IncludeHidden { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + + if options.Entity != "" && entry["entity"] != options.Entity { + if info.IsDir() { + return nil + } + return nil + } + + if options.FilterPattern != "" { + match, matchErr := filepath.Match(options.FilterPattern, info.Name()) + if matchErr != nil { + return matchErr + } + if !match { + return nil + } + } + + entries = append(entries, entry) + return nil + }) + + if err != nil { + return nil, err + } + + return entries, nil +} + +// Filters listing results according to entity, hidden, and pattern rules. +func obrimFilesystemListFilter(entries []map[string]any, options obrimFilesystemListConfig) []map[string]any { + filtered := make([]map[string]any, 0, len(entries)) + + for _, entry := range entries { + if !options.IncludeHidden && obrimFilesystemMapBool(entry, "hidden") { + continue + } + + if options.Entity != "" && obrimFilesystemMapString(entry, "entity") != options.Entity { + continue + } + + if options.FilterPattern != "" { + matched, err := filepath.Match(options.FilterPattern, obrimFilesystemMapString(entry, "name")) + if err != nil || !matched { + continue + } + } + + filtered = append(filtered, entry) + } + + return filtered +} + +// Sorts listing results using the requested criteria and direction. +func obrimFilesystemListSort(entries []map[string]any, sortBy string, sortOrder string) { + sort.SliceStable(entries, func(i, j int) bool { + var less bool + + switch sortBy { + case obrimFilesystemSortSize: + less = obrimFilesystemMapInt64(entries[i], "size") < obrimFilesystemMapInt64(entries[j], "size") + case obrimFilesystemSortModified: + less = obrimFilesystemMapString(entries[i], "modified") < obrimFilesystemMapString(entries[j], "modified") + case obrimFilesystemSortCreated: + less = obrimFilesystemMapString(entries[i], "created") < obrimFilesystemMapString(entries[j], "created") + default: + less = strings.ToLower(obrimFilesystemMapString(entries[i], "name")) < + strings.ToLower(obrimFilesystemMapString(entries[j], "name")) + } + + if sortOrder == "desc" { + return !less && !obrimFilesystemListSortEqual(entries[i], entries[j], sortBy) + } + + return less + }) +} + +// Determines whether two listing entries have equal sort values. +func obrimFilesystemListSortEqual(left map[string]any, right map[string]any, sortBy string) bool { + switch sortBy { + case obrimFilesystemSortSize: + return obrimFilesystemMapInt64(left, "size") == obrimFilesystemMapInt64(right, "size") + case obrimFilesystemSortModified: + return obrimFilesystemMapString(left, "modified") == obrimFilesystemMapString(right, "modified") + case obrimFilesystemSortCreated: + return obrimFilesystemMapString(left, "created") == obrimFilesystemMapString(right, "created") + default: + return strings.EqualFold( + obrimFilesystemMapString(left, "name"), + obrimFilesystemMapString(right, "name"), + ) + } +} + +// Builds a listing entry from a directory entry. +func obrimFilesystemListEntry(item os.DirEntry, basePath string, options obrimFilesystemListConfig) (map[string]any, bool, error) { + info, err := item.Info() + if err != nil { + return nil, false, err + } + + if item.Type()&os.ModeSymlink != 0 && options.FollowSymlink { + info, err = os.Stat(filepath.Join(basePath, item.Name())) + if err != nil { + return nil, false, err + } + } + + entry := obrimFilesystemEntryFromInfo(filepath.Join(basePath, item.Name()), info, options.AbsolutePath) + + if !options.IncludeHidden && obrimFilesystemMapBool(entry, "hidden") { + return entry, false, nil + } + + if options.Entity != "" && obrimFilesystemMapString(entry, "entity") != options.Entity { + return entry, false, nil + } + + if options.FilterPattern != "" { + matched, err := filepath.Match(options.FilterPattern, item.Name()) + if err != nil { + return nil, false, err + } + if !matched { + return entry, false, nil + } + } + + return entry, true, nil +} + +// Converts filesystem metadata into a standardized listing entry. +func obrimFilesystemEntryFromInfo(path string, info os.FileInfo, absolute bool) map[string]any { + resolvedPath := path + + if absolute { + if absolutePath, err := filepath.Abs(path); err == nil { + resolvedPath = absolutePath + } + } else { + if cleanPath, err := filepath.Rel(".", path); err == nil { + resolvedPath = cleanPath + } + } + + entity := obrimFilesystemEntityFile + if info.IsDir() { + entity = obrimFilesystemEntityDirectory + } + + readable := obrimFilesystemCanRead(path) + writable := obrimFilesystemCanWrite(path) + + return map[string]any{ + "path": resolvedPath, + "name": info.Name(), + "entity": entity, + "size": info.Size(), + "modified": info.ModTime().UTC().Format("2006-01-02T15:04:05Z07:00"), + "created": info.ModTime().UTC().Format("2006-01-02T15:04:05Z07:00"), + "hidden": obrimFilesystemIsHidden(info.Name()), + "readable": readable, + "writable": writable, + } +} + +// Executes a filesystem existence and accessibility check. +func obrimFilesystemCheck(config map[string]any) (map[string]any, string, error) { + options := obrimFilesystemCheckConfig{ + Entity: obrimFilesystemString(config, "entity"), + Strategy: obrimFilesystemStringDefault(config, "strategy", obrimFilesystemStrategyUser), + Base: obrimFilesystemString(config, "base"), + Name: obrimFilesystemString(config, "name"), + Readable: obrimFilesystemBool(config, "readable"), + Writable: obrimFilesystemBool(config, "writable"), + } + + path, err := obrimFilesystemResolvePath(options.Strategy, options.Base, options.Name) + if err != nil { + return nil, obrimFilesystemFailureResolvePath, err + } + + info, err := os.Lstat(path) + if os.IsNotExist(err) { + return nil, obrimFilesystemFailureEntityNotFound, err + } + if err != nil { + return nil, obrimFilesystemFailureCheck, err + } + + entity := obrimFilesystemEntityFile + if info.IsDir() { + entity = obrimFilesystemEntityDirectory + } + + if entity != options.Entity { + return nil, obrimFilesystemFailureEntityTypeMismatch, fmt.Errorf("filesystem entity type mismatch") + } + + readable := obrimFilesystemCheckRead(path) + writable := obrimFilesystemCheckWrite(path) + + if options.Readable && !readable { + return nil, obrimFilesystemFailureReadAccess, fmt.Errorf("filesystem entity is not readable") + } + + if options.Writable && !writable { + return nil, obrimFilesystemFailureWriteAccess, fmt.Errorf("filesystem entity is not writable") + } + + return map[string]any{ + "path": path, + "exists": true, + "entity": entity, + "readable": readable, + "writable": writable, + }, obrimFilesystemSuccessCheck, nil +} + +// Checks read permission without modifying the filesystem. +func obrimFilesystemCheckRead(path string) bool { + return obrimFilesystemCheckReadPath(path) +} + +// Checks write permission without modifying the filesystem. +func obrimFilesystemCheckWrite(path string) bool { + return obrimFilesystemCheckWritePath(path) +} + +// Checks whether a filesystem path can be read. +func obrimFilesystemCheckReadPath(path string) bool { + file, err := os.Open(path) + if err != nil { + return false + } + + err = file.Close() + return err == nil +} + +// Checks whether a filesystem path can be written. +func obrimFilesystemCheckWritePath(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + + if info.Mode().Perm()&0200 != 0 { + return true + } + + parent := filepath.Dir(path) + parentInfo, err := os.Stat(parent) + if err != nil { + return false + } + + return parentInfo.Mode().Perm()&0200 != 0 +} + +// Executes a filesystem creation operation. +func obrimFilesystemCreate(config map[string]any) (map[string]any, error) { + options := obrimFilesystemCreateConfig{ + Entity: obrimFilesystemString(config, "entity"), + Strategy: obrimFilesystemStringDefault(config, "strategy", obrimFilesystemStrategyUser), + Base: obrimFilesystemString(config, "base"), + Name: obrimFilesystemString(config, "name"), + Hidden: obrimFilesystemBool(config, "hidden"), + } + + path, err := obrimFilesystemResolvePath(options.Strategy, options.Base, options.Name) + if err != nil { + return nil, err + } + + parent := filepath.Dir(path) + if err := os.MkdirAll(parent, 0755); err != nil { + return nil, err + } + + switch options.Entity { + case obrimFilesystemEntityFile: + err = obrimFilesystemCreateFile(path) + case obrimFilesystemEntityDirectory: + err = obrimFilesystemCreateDirectory(path) + default: + return nil, fmt.Errorf("unsupported filesystem entity") + } + + if err != nil { + return nil, err + } + + return map[string]any{ + "path": path, + "entity": options.Entity, + "hidden": obrimFilesystemIsHidden(filepath.Base(path)), + "created": true, + }, nil +} + +// Creates a filesystem file. +func obrimFilesystemCreateFile(path string) error { + file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) + if err != nil { + return err + } + + return file.Close() +} + +// Creates a filesystem directory. +func obrimFilesystemCreateDirectory(path string) error { + return os.Mkdir(path, 0755) +} + +// Executes a filesystem deletion operation. +func obrimFilesystemDelete(config map[string]any) (map[string]any, error) { + options := obrimFilesystemDeleteConfig{ + Entity: obrimFilesystemString(config, "entity"), + Strategy: obrimFilesystemStringDefault(config, "strategy", obrimFilesystemStrategyUser), + Base: obrimFilesystemString(config, "base"), + Name: obrimFilesystemString(config, "name"), + Recursive: obrimFilesystemBool(config, "recursive"), + } + + path, err := obrimFilesystemResolvePath(options.Strategy, options.Base, options.Name) + if err != nil { + return nil, err + } + + info, err := os.Lstat(path) + if os.IsNotExist(err) { + return nil, fmt.Errorf("filesystem entity does not exist") + } + if err != nil { + return nil, err + } + + actualEntity := obrimFilesystemEntityFile + if info.IsDir() { + actualEntity = obrimFilesystemEntityDirectory + } + + if actualEntity != options.Entity { + return nil, fmt.Errorf("filesystem entity type mismatch") + } + + switch options.Entity { + case obrimFilesystemEntityFile: + err = obrimFilesystemDeleteFile(path) + case obrimFilesystemEntityDirectory: + err = obrimFilesystemDeleteDirectory(path, options.Recursive) + default: + return nil, fmt.Errorf("unsupported filesystem entity") + } + + if err != nil { + return nil, err + } + + return map[string]any{ + "path": path, + "entity": options.Entity, + "deleted": true, + }, nil +} + +// Deletes a filesystem file. +func obrimFilesystemDeleteFile(path string) error { + return os.Remove(path) +} + +// Deletes a filesystem directory. +func obrimFilesystemDeleteDirectory(path string, recursive bool) error { + if recursive { + return os.RemoveAll(path) + } + + return os.Remove(path) +} + +// Executes a filesystem permission operation. +func obrimFilesystemPermission(config map[string]any) (map[string]any, error) { + options := obrimFilesystemPermissionConfig{ + Strategy: obrimFilesystemStringDefault(config, "strategy", obrimFilesystemStrategyUser), + Base: obrimFilesystemString(config, "base"), + Name: obrimFilesystemString(config, "name"), + Mode: obrimFilesystemString(config, "mode"), + Permission: obrimFilesystemString(config, "permission"), + } + + path, err := obrimFilesystemResolvePath(options.Strategy, options.Base, options.Name) + if err != nil { + return nil, err + } + + switch options.Mode { + case obrimFilesystemModeRead: + return obrimFilesystemPermissionRead(path) + case obrimFilesystemModeWrite: + return obrimFilesystemPermissionWrite(path, options.Permission) + default: + return nil, fmt.Errorf("unsupported permission mode") + } +} + +// Retrieves filesystem permissions. +func obrimFilesystemPermissionRead(path string) (map[string]any, error) { + info, err := os.Stat(path) + if err != nil { + return nil, err + } + + permission := fmt.Sprintf("%04o", info.Mode().Perm()) + + return map[string]any{ + "path": path, + "permission": permission, + "mode": obrimFilesystemModeRead, + "updated": false, + }, nil +} + +// Updates filesystem permissions. +func obrimFilesystemPermissionWrite(path string, permission string) (map[string]any, error) { + value, err := strconv.ParseUint(permission, 8, 32) + if err != nil || len(permission) < 3 || len(permission) > 4 { + return nil, fmt.Errorf("invalid permission specification") + } + + if err := os.Chmod(path, os.FileMode(value)); err != nil { + return nil, err + } + + info, err := os.Stat(path) + if err != nil { + return nil, err + } + + return map[string]any{ + "path": path, + "permission": fmt.Sprintf("%04o", info.Mode().Perm()), + "mode": obrimFilesystemModeWrite, + "updated": true, + }, nil +} + +// Resolves a filesystem path using the requested strategy. +func obrimFilesystemResolvePath(strategy string, base string, name string) (string, error) { + switch strategy { + case obrimFilesystemStrategyUser: + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + + if name == "" { + return filepath.Clean(home), nil + } + + if filepath.IsAbs(name) { + return filepath.Clean(name), nil + } + + return filepath.Clean(filepath.Join(home, name)), nil + + case obrimFilesystemStrategyCustom: + if base == "" { + return "", fmt.Errorf("custom strategy requires base path") + } + + if name == "" { + return filepath.Clean(base), nil + } + + if filepath.IsAbs(name) { + return filepath.Clean(name), nil + } + + return filepath.Clean(filepath.Join(base, name)), nil + + default: + return "", fmt.Errorf("unsupported filesystem path strategy") + } +} + +// Determines whether a filesystem name is hidden. +func obrimFilesystemIsHidden(name string) bool { + if name == "" || name == "." || name == ".." { + return false + } + + return strings.HasPrefix(name, ".") +} + +// Determines whether a filesystem path is readable. +func obrimFilesystemCanRead(path string) bool { + return obrimFilesystemCheckReadPath(path) +} + +// Determines whether a filesystem path is writable. +func obrimFilesystemCanWrite(path string) bool { + return obrimFilesystemCheckWritePath(path) +} + +// Retrieves a string configuration value. +func obrimFilesystemString(config map[string]any, key string) string { + value, ok := config[key] + if !ok || value == nil { + return "" + } + + switch typed := value.(type) { + case string: + return typed + default: + return fmt.Sprint(typed) + } +} + +// Retrieves a string configuration value with a default. +func obrimFilesystemStringDefault(config map[string]any, key string, defaultValue string) string { + value := obrimFilesystemString(config, key) + if value == "" { + return defaultValue + } + + return value +} + +// Retrieves a boolean configuration value. +func obrimFilesystemBool(config map[string]any, key string) bool { + value, ok := config[key] + if !ok || value == nil { + return false + } + + switch typed := value.(type) { + case bool: + return typed + case string: + parsed, err := strconv.ParseBool(typed) + if err == nil { + return parsed + } + case int: + return typed != 0 + case int8: + return typed != 0 + case int16: + return typed != 0 + case int32: + return typed != 0 + case int64: + return typed != 0 + case uint: + return typed != 0 + case uint8: + return typed != 0 + case uint16: + return typed != 0 + case uint32: + return typed != 0 + case uint64: + return typed != 0 + } + + return false +} + +// Retrieves a string from a filesystem entry map. +func obrimFilesystemMapString(value map[string]any, key string) string { + item, ok := value[key] + if !ok || item == nil { + return "" + } + + if typed, ok := item.(string); ok { + return typed + } + + return fmt.Sprint(item) +} + +// Retrieves a boolean from a filesystem entry map. +func obrimFilesystemMapBool(value map[string]any, key string) bool { + item, ok := value[key] + if !ok || item == nil { + return false + } + + typed, ok := item.(bool) + return ok && typed +} + +// Retrieves an int64 from a filesystem entry map. +func obrimFilesystemMapInt64(value map[string]any, key string) int64 { + item, ok := value[key] + if !ok || item == nil { + return 0 + } + + switch typed := item.(type) { + case int64: + return typed + case int: + return int64(typed) + case int32: + return int64(typed) + case int16: + return int64(typed) + case int8: + return int64(typed) + case uint64: + return int64(typed) + case uint32: + return int64(typed) + case uint16: + return int64(typed) + case uint8: + return int64(typed) + case uint: + return int64(typed) + default: + return 0 + } +} diff --git a/essential/visible/service/helper/hash/hash.go b/essential/visible/service/helper/hash/hash.go index e69de29..5b03aa5 100644 --- a/essential/visible/service/helper/hash/hash.go +++ b/essential/visible/service/helper/hash/hash.go @@ -0,0 +1,396 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Hash +| +| Purpose: +| - Generate and verify cryptographic hash values using standard or +| salted hashing methods for integrity verification, canonical +| fingerprinting, resource identification, comparison workflows, +| and security-oriented hashing requirements. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use to generate deterministic hashes in standard mode. +| - Use to generate salted hashes when salted hashing is required. +| - Use to verify supplied hash values against input values. +| - Use standard mode when deterministic hashing without a salt is +| required. +| - Use salted mode when a supplied or cryptographically secure +| generated salt is required. +| +| Example: +| - ObrimHash("calculate", map[string]any{ +| "mode": "standard", +| "algorithm": "...", +| "value": "...", +| }) +| +| - ObrimHash("calculate", map[string]any{ +| "mode": "salted", +| "algorithm": "...", +| "value": "...", +| "salt": "...", +| }) +| +| - ObrimHash("compare", map[string]any{ +| "mode": "standard", +| "algorithm": "...", +| "value": "...", +| "hash": "...", +| }) +| +| - ObrimHash("compare", map[string]any{ +| "mode": "salted", +| "algorithm": "...", +| "value": "...", +| "hash": "...", +| "salt": "...", +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package hash + +import ( + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "errors" + "hash" +) + +// Hash result codes. +const ( + ObrimHashSuccessCalculated = "SUCCESS_CALCULATED" + ObrimHashSuccessCompared = "SUCCESS_COMPARED" + ObrimHashFailureInvalidType = "FAILURE_INVALID_TYPE" + ObrimHashFailureInvalidConfig = "FAILURE_INVALID_CONFIG" + ObrimHashFailureInvalidMode = "FAILURE_INVALID_MODE" + ObrimHashFailureInvalidAlgorithm = "FAILURE_INVALID_ALGORITHM" + ObrimHashFailureInvalidValue = "FAILURE_INVALID_VALUE" + ObrimHashFailureInvalidHash = "FAILURE_INVALID_HASH" + ObrimHashFailureInvalidSalt = "FAILURE_INVALID_SALT" + ObrimHashFailureProcessing = "FAILURE_PROCESSING" +) + +// Hash utility modes. +const ( + obrimHashModeStandard = "standard" + obrimHashModeSalted = "salted" +) + +// Hash utility algorithms. +const ( + obrimHashAlgorithmSHA256 = "sha256" + obrimHashAlgorithmSHA512 = "sha512" +) + +// Hash utility configuration keys. +const ( + obrimHashConfigMode = "mode" + obrimHashConfigAlgorithm = "algorithm" + obrimHashConfigValue = "value" + obrimHashConfigHash = "hash" + obrimHashConfigSalt = "salt" +) + +// Hash utility operation types. +const ( + obrimHashTypeCalculate = "calculate" + obrimHashTypeCompare = "compare" +) + +// Hash utility output structure. +type obrimHashOutput struct { + Status bool `json:"status"` + Code string `json:"code"` + Payload map[string]any `json:"payload"` +} + +// Hash utility request configuration. +type obrimHashConfig struct { + Mode string + Algorithm string + Value string + Hash string + Salt string +} + +// Hash utility execution function. +func ObrimHash(Type string, Config map[string]any) map[string]any { + ConfigData, Code := obrimHashValidateInput(Type, Config) + if Code != "" { + return obrimHashBuildOutput(false, Code, nil) + } + + return obrimHashRouteRequest(Type, ConfigData) +} + +// Validate hash utility input. +func obrimHashValidateInput(Type string, Config map[string]any) (obrimHashConfig, string) { + var ConfigData obrimHashConfig + + switch Type { + case obrimHashTypeCalculate, obrimHashTypeCompare: + default: + return ConfigData, ObrimHashFailureInvalidType + } + + if Config == nil { + return ConfigData, ObrimHashFailureInvalidConfig + } + + ConfigData.Mode = obrimHashReadStringConfig(Config, obrimHashConfigMode) + ConfigData.Algorithm = obrimHashReadStringConfig(Config, obrimHashConfigAlgorithm) + ConfigData.Value = obrimHashReadStringConfig(Config, obrimHashConfigValue) + ConfigData.Hash = obrimHashReadStringConfig(Config, obrimHashConfigHash) + ConfigData.Salt = obrimHashReadStringConfig(Config, obrimHashConfigSalt) + + switch ConfigData.Mode { + case obrimHashModeStandard, obrimHashModeSalted: + default: + return ConfigData, ObrimHashFailureInvalidMode + } + + switch ConfigData.Algorithm { + case obrimHashAlgorithmSHA256, obrimHashAlgorithmSHA512: + default: + return ConfigData, ObrimHashFailureInvalidAlgorithm + } + + if ConfigData.Value == "" { + return ConfigData, ObrimHashFailureInvalidValue + } + + switch Type { + case obrimHashTypeCalculate: + if ConfigData.Mode == obrimHashModeStandard && ConfigData.Salt != "" { + return ConfigData, ObrimHashFailureInvalidSalt + } + case obrimHashTypeCompare: + if ConfigData.Hash == "" { + return ConfigData, ObrimHashFailureInvalidHash + } + + switch ConfigData.Mode { + case obrimHashModeStandard: + if ConfigData.Salt != "" { + return ConfigData, ObrimHashFailureInvalidSalt + } + case obrimHashModeSalted: + if ConfigData.Salt == "" { + return ConfigData, ObrimHashFailureInvalidSalt + } + } + } + + return ConfigData, "" +} + +// Route the hash utility request. +func obrimHashRouteRequest(Type string, Config obrimHashConfig) map[string]any { + switch Type { + case obrimHashTypeCalculate: + return obrimHashCalculate(Config) + case obrimHashTypeCompare: + return obrimHashCompare(Config) + default: + return obrimHashBuildOutput(false, ObrimHashFailureInvalidType, nil) + } +} + +// Build standardized hash utility output. +func obrimHashBuildOutput(Status bool, Code string, Payload map[string]any) map[string]any { + Output := obrimHashOutput{ + Status: Status, + Code: Code, + Payload: Payload, + } + + return map[string]any{ + "status": Output.Status, + "code": Output.Code, + "payload": Output.Payload, + } +} + +// Process hash generation. +func obrimHashCalculate(Config obrimHashConfig) map[string]any { + switch Config.Mode { + case obrimHashModeStandard: + return obrimHashCalculateStandard(Config) + case obrimHashModeSalted: + return obrimHashCalculateSalted(Config) + default: + return obrimHashBuildOutput(false, ObrimHashFailureInvalidMode, nil) + } +} + +// Generate deterministic standard hash. +func obrimHashCalculateStandard(Config obrimHashConfig) map[string]any { + HashValue, Error := obrimHashGenerate(Config.Algorithm, Config.Value) + if Error != nil { + return obrimHashBuildOutput(false, ObrimHashFailureProcessing, nil) + } + + Payload := map[string]any{ + "algorithm": Config.Algorithm, + "hash": HashValue, + } + + return obrimHashBuildOutput(true, ObrimHashSuccessCalculated, Payload) +} + +// Generate salted hash. +func obrimHashCalculateSalted(Config obrimHashConfig) map[string]any { + Salt := Config.Salt + + if Salt == "" { + var Error error + + Salt, Error = obrimHashGenerateSalt() + if Error != nil { + return obrimHashBuildOutput(false, ObrimHashFailureProcessing, nil) + } + } + + HashValue, Error := obrimHashGenerate(Config.Algorithm, Salt+Config.Value) + if Error != nil { + return obrimHashBuildOutput(false, ObrimHashFailureProcessing, nil) + } + + Payload := map[string]any{ + "algorithm": Config.Algorithm, + "hash": HashValue, + "salt": Salt, + } + + return obrimHashBuildOutput(true, ObrimHashSuccessCalculated, Payload) +} + +// Generate a cryptographically secure random salt. +func obrimHashGenerateSalt() (string, error) { + Salt := make([]byte, 32) + + if _, Error := rand.Read(Salt); Error != nil { + return "", Error + } + + return hex.EncodeToString(Salt), nil +} + +// Process hash verification. +func obrimHashCompare(Config obrimHashConfig) map[string]any { + switch Config.Mode { + case obrimHashModeStandard: + return obrimHashCompareStandard(Config) + case obrimHashModeSalted: + return obrimHashCompareSalted(Config) + default: + return obrimHashBuildOutput(false, ObrimHashFailureInvalidMode, nil) + } +} + +// Verify standard hash. +func obrimHashCompareStandard(Config obrimHashConfig) map[string]any { + HashValue, Error := obrimHashGenerate(Config.Algorithm, Config.Value) + if Error != nil { + return obrimHashBuildOutput(false, ObrimHashFailureProcessing, nil) + } + + Matched := obrimHashVerify(HashValue, Config.Hash) + + Payload := map[string]any{ + "algorithm": Config.Algorithm, + "matched": Matched, + "hash": Config.Hash, + } + + return obrimHashBuildOutput(true, ObrimHashSuccessCompared, Payload) +} + +// Verify salted hash. +func obrimHashCompareSalted(Config obrimHashConfig) map[string]any { + HashValue, Error := obrimHashGenerate(Config.Algorithm, Config.Salt+Config.Value) + if Error != nil { + return obrimHashBuildOutput(false, ObrimHashFailureProcessing, nil) + } + + Matched := obrimHashVerify(HashValue, Config.Hash) + + Payload := map[string]any{ + "algorithm": Config.Algorithm, + "matched": Matched, + "hash": Config.Hash, + "salt": Config.Salt, + } + + return obrimHashBuildOutput(true, ObrimHashSuccessCompared, Payload) +} + +// Verify regenerated and supplied hashes using strict byte comparison. +func obrimHashVerify(Expected string, Supplied string) bool { + return string([]byte(Expected)) == string([]byte(Supplied)) +} + +// Generate a hash using the selected algorithm. +func obrimHashGenerate(Algorithm string, Value string) (string, error) { + var HashFunction func() hash.Hash + + switch Algorithm { + case obrimHashAlgorithmSHA256: + HashFunction = sha256.New + case obrimHashAlgorithmSHA512: + HashFunction = sha512.New + default: + return "", errors.New("unsupported hashing algorithm") + } + + Hasher := HashFunction() + + if _, Error := Hasher.Write([]byte(Value)); Error != nil { + return "", Error + } + + return hex.EncodeToString(Hasher.Sum(nil)), nil +} + +// Read a string configuration value. +func obrimHashReadStringConfig(Config map[string]any, Key string) string { + Value, Exists := Config[Key] + if !Exists || Value == nil { + return "" + } + + StringValue, Valid := Value.(string) + if !Valid { + return "" + } + + return StringValue +} diff --git a/essential/visible/service/helper/key/key.go b/essential/visible/service/helper/key/key.go index e69de29..5ad2f95 100644 --- a/essential/visible/service/helper/key/key.go +++ b/essential/visible/service/helper/key/key.go @@ -0,0 +1,445 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Key +| +| Purpose: +| - Generate cryptographic key material through a type-driven dispatch +| model supporting symmetric and asymmetric key generation. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use the utility to generate cryptographically secure symmetric or +| asymmetric key material. +| - Use the "symmetric" type with the supported AES algorithm and +| 128, 192, or 256 bit key sizes. +| - Use the "asymmetric" type with the supported ECC algorithm using +| the EdDSA curve. +| - Provide the type-specific configuration required for the selected +| key generation workflow. +| +| Example: +| - ObrimKey("symmetric", map[string]any{ +| "algorithm": "aes", +| "key_size": 256, +| }) +| +| - ObrimKey("asymmetric", map[string]any{ +| "algorithm": "ecc", +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package key + +import ( + "crypto/aes" + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "time" +) + +const ( + obrimKeyTypeSymmetric = "symmetric" + obrimKeyTypeAsymmetric = "asymmetric" + + obrimKeyAlgorithmAES = "aes" + obrimKeyAlgorithmECC = "ecc" + obrimKeyCurveEdDSA = "eddsa" + + obrimKeySize128 = 128 + obrimKeySize192 = 192 + obrimKeySize256 = 256 +) + +const ( + obrimKeySuccessSymmetricGenerated = "SUCCESS_SYMMETRIC_KEY_GENERATED" + obrimKeySuccessAsymmetricGenerated = "SUCCESS_ASYMMETRIC_KEY_GENERATED" + + obrimKeyFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimKeyFailureInvalidConfig = "FAILURE_INVALID_CONFIG" + obrimKeyFailureMissingAlgorithm = "FAILURE_MISSING_ALGORITHM" + obrimKeyFailureInvalidAlgorithm = "FAILURE_INVALID_ALGORITHM" + obrimKeyFailureMissingKeySize = "FAILURE_MISSING_KEY_SIZE" + obrimKeyFailureInvalidKeySize = "FAILURE_INVALID_KEY_SIZE" + obrimKeyFailureKeyGeneration = "FAILURE_KEY_GENERATION" + obrimKeyFailureAsymmetricKeyGeneration = "FAILURE_ASYMMETRIC_KEY_GENERATION" + obrimKeyFailureUnsupportedOperation = "FAILURE_UNSUPPORTED_OPERATION" +) + +const ( + obrimKeyConfigAlgorithm = "algorithm" + obrimKeyConfigKeySize = "key_size" +) + +type obrimKeySymmetricConfig struct { + algorithm string + keySize int +} + +type obrimKeyAsymmetricConfig struct { + algorithm string +} + +type obrimKeySymmetricPayload struct { + Type string `json:"type"` + Algorithm string `json:"algorithm"` + KeySize int `json:"key_size"` + KeyMaterial string `json:"key_material"` + GeneratedAt string `json:"generated_at"` +} + +type obrimKeyAsymmetricPayload struct { + Type string `json:"type"` + Algorithm string `json:"algorithm"` + Curve string `json:"curve"` + PublicKey string `json:"public_key"` + PrivateKey string `json:"private_key"` + GeneratedAt string `json:"generated_at"` +} + +type obrimKeyOutput struct { + Status bool `json:"status"` + Code string `json:"code"` + Payload any `json:"payload"` +} + +func ObrimKey(keyType string, config map[string]any) map[string]any { + if code := obrimKeyValidateInput(keyType, config); code != "" { + return obrimKeyBuildOutput(false, code, nil) + } + + return obrimKeyRouteRequest(keyType, config) +} + +func obrimKeyValidateInput(keyType string, config map[string]any) string { + switch keyType { + case obrimKeyTypeSymmetric: + if config == nil { + return obrimKeyFailureInvalidConfig + } + + algorithm, ok := config[obrimKeyConfigAlgorithm] + if !ok { + return obrimKeyFailureMissingAlgorithm + } + + algorithmValue, ok := algorithm.(string) + if !ok || algorithmValue == "" { + return obrimKeyFailureInvalidAlgorithm + } + + switch algorithmValue { + case obrimKeyAlgorithmAES: + default: + return obrimKeyFailureInvalidAlgorithm + } + + keySize, ok := config[obrimKeyConfigKeySize] + if !ok { + return obrimKeyFailureMissingKeySize + } + + switch value := keySize.(type) { + case int: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case int8: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case int16: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case int32: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case int64: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case uint: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case uint8: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case uint16: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case uint32: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + case uint64: + switch value { + case obrimKeySize128, obrimKeySize192, obrimKeySize256: + default: + return obrimKeyFailureInvalidKeySize + } + default: + return obrimKeyFailureInvalidKeySize + } + + case obrimKeyTypeAsymmetric: + if config == nil { + return obrimKeyFailureInvalidConfig + } + + algorithm, ok := config[obrimKeyConfigAlgorithm] + if !ok { + return obrimKeyFailureMissingAlgorithm + } + + algorithmValue, ok := algorithm.(string) + if !ok || algorithmValue == "" { + return obrimKeyFailureInvalidAlgorithm + } + + switch algorithmValue { + case obrimKeyAlgorithmECC: + default: + return obrimKeyFailureInvalidAlgorithm + } + + default: + return obrimKeyFailureInvalidType + } + + return "" +} + +func obrimKeyRouteRequest(keyType string, config map[string]any) map[string]any { + switch keyType { + case obrimKeyTypeSymmetric: + normalized, code := obrimKeyNormalizeSymmetricConfig(config) + if code != "" { + return obrimKeyBuildOutput(false, code, nil) + } + + payload, code := obrimKeyGenerateSymmetric(normalized) + if code != "" { + return obrimKeyBuildOutput(false, code, nil) + } + + return obrimKeyBuildOutput( + true, + obrimKeySuccessSymmetricGenerated, + payload, + ) + + case obrimKeyTypeAsymmetric: + normalized, code := obrimKeyNormalizeAsymmetricConfig(config) + if code != "" { + return obrimKeyBuildOutput(false, code, nil) + } + + payload, code := obrimKeyGenerateAsymmetric(normalized) + if code != "" { + return obrimKeyBuildOutput(false, code, nil) + } + + return obrimKeyBuildOutput( + true, + obrimKeySuccessAsymmetricGenerated, + payload, + ) + + default: + return obrimKeyBuildOutput(false, obrimKeyFailureUnsupportedOperation, nil) + } +} + +func obrimKeyBuildOutput(status bool, code string, payload any) map[string]any { + output := obrimKeyOutput{ + Status: status, + Code: code, + Payload: payload, + } + + return map[string]any{ + "status": output.Status, + "code": output.Code, + "payload": output.Payload, + } +} + +func obrimKeyNormalizeSymmetricConfig(config map[string]any) (obrimKeySymmetricConfig, string) { + algorithm, ok := config[obrimKeyConfigAlgorithm].(string) + if !ok || algorithm == "" { + return obrimKeySymmetricConfig{}, obrimKeyFailureInvalidAlgorithm + } + + keySizeValue, ok := config[obrimKeyConfigKeySize] + if !ok { + return obrimKeySymmetricConfig{}, obrimKeyFailureMissingKeySize + } + + keySize, ok := obrimKeyNormalizeKeySize(keySizeValue) + if !ok { + return obrimKeySymmetricConfig{}, obrimKeyFailureInvalidKeySize + } + + return obrimKeySymmetricConfig{ + algorithm: algorithm, + keySize: keySize, + }, "" +} + +func obrimKeyNormalizeKeySize(value any) (int, bool) { + switch keySize := value.(type) { + case int: + return keySize, true + case int8: + return int(keySize), true + case int16: + return int(keySize), true + case int32: + return int(keySize), true + case int64: + return int(keySize), true + case uint: + return int(keySize), true + case uint8: + return int(keySize), true + case uint16: + return int(keySize), true + case uint32: + return int(keySize), true + case uint64: + return int(keySize), true + default: + return 0, false + } +} + +func obrimKeyGenerateSymmetric(config obrimKeySymmetricConfig) (map[string]any, string) { + switch config.algorithm { + case obrimKeyAlgorithmAES: + return obrimKeyGenerateAES(config.keySize) + default: + return nil, obrimKeyFailureInvalidAlgorithm + } +} + +func obrimKeyGenerateAES(keySize int) (map[string]any, string) { + keyLength := keySize / 8 + + keyMaterial := make([]byte, keyLength) + if _, err := rand.Read(keyMaterial); err != nil { + return nil, obrimKeyFailureKeyGeneration + } + + if _, err := aes.NewCipher(keyMaterial); err != nil { + return nil, obrimKeyFailureKeyGeneration + } + + payload := obrimKeySymmetricPayload{ + Type: obrimKeyTypeSymmetric, + Algorithm: obrimKeyAlgorithmAES, + KeySize: keySize, + KeyMaterial: base64.StdEncoding.EncodeToString(keyMaterial), + GeneratedAt: time.Now().UTC().Format(time.RFC3339Nano), + } + + return map[string]any{ + "type": payload.Type, + "algorithm": payload.Algorithm, + "key_size": payload.KeySize, + "key_material": payload.KeyMaterial, + "generated_at": payload.GeneratedAt, + }, "" +} + +func obrimKeyNormalizeAsymmetricConfig(config map[string]any) (obrimKeyAsymmetricConfig, string) { + algorithm, ok := config[obrimKeyConfigAlgorithm].(string) + if !ok || algorithm == "" { + return obrimKeyAsymmetricConfig{}, obrimKeyFailureInvalidAlgorithm + } + + return obrimKeyAsymmetricConfig{ + algorithm: algorithm, + }, "" +} + +func obrimKeyGenerateAsymmetric(config obrimKeyAsymmetricConfig) (map[string]any, string) { + switch config.algorithm { + case obrimKeyAlgorithmECC: + return obrimKeyGenerateECC() + default: + return nil, obrimKeyFailureInvalidAlgorithm + } +} + +func obrimKeyGenerateECC() (map[string]any, string) { + publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, obrimKeyFailureAsymmetricKeyGeneration + } + + payload := obrimKeyAsymmetricPayload{ + Type: obrimKeyTypeAsymmetric, + Algorithm: obrimKeyAlgorithmECC, + Curve: obrimKeyCurveEdDSA, + PublicKey: base64.StdEncoding.EncodeToString(publicKey), + PrivateKey: base64.StdEncoding.EncodeToString(privateKey), + GeneratedAt: time.Now().UTC().Format(time.RFC3339Nano), + } + + return map[string]any{ + "type": payload.Type, + "algorithm": payload.Algorithm, + "curve": payload.Curve, + "public_key": payload.PublicKey, + "private_key": payload.PrivateKey, + "generated_at": payload.GeneratedAt, + }, "" +} diff --git a/essential/visible/service/helper/log/log.go b/essential/visible/service/helper/log/log.go index e69de29..d99d8b4 100644 --- a/essential/visible/service/helper/log/log.go +++ b/essential/visible/service/helper/log/log.go @@ -0,0 +1,225 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Log +| +| Purpose: +| - Provide a framework-level logging utility that records structured +| plaintext log entries to a persistent filesystem location using a +| deterministic and platform-aware storage strategy. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use to write standardized log messages from framework services and +| software features. +| - Provide a logical source identifier using the SERVICENAME/UTILITYNAME +| or SERVICENAME/FEATURENAME format. +| - Provide the human-readable log message as plain, formatted, or any +| UTF-8 string. +| - Log entries are stored in the platform-specific persistent log +| directory using the software name as the directory name. +| - Log entries are appended as complete UTF-8 plaintext lines without +| overwriting existing content. +| +| Example: +| - ObrimLog( +| "SERVICENAME/UTILITYNAME", +| "Utility operation completed", +| ) +| - ObrimLog( +| "SERVICENAME/FEATURENAME", +| "Feature execution started", +| ) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package log + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +// obrimLogSoftwareName identifies the software owning the log directory. +const obrimLogSoftwareName = "software" + +// ObrimLog writes a standardized log message. +func ObrimLog(label, message string) { + obrimLogLabel, obrimLogMessage, obrimLogValid := obrimLogValidateInput(label, message) + if !obrimLogValid { + return + } + + obrimLogName := obrimLogResolveName() + if obrimLogName == "" { + return + } + + obrimLogDirectory, obrimLogDirectoryValid := obrimLogResolveDirectory(obrimLogName) + if !obrimLogDirectoryValid { + return + } + + obrimLogFile, obrimLogFileValid := obrimLogResolveFile(obrimLogDirectory) + if !obrimLogFileValid { + return + } + + obrimLogTimestamp := obrimLogGetTimestamp() + obrimLogEntry := obrimLogBuildEntry(obrimLogTimestamp, obrimLogLabel, obrimLogMessage) + + obrimLogWriteEntry(obrimLogFile, obrimLogEntry) +} + +// obrimLogValidateInput normalizes and validates the log input. +func obrimLogValidateInput(label, message string) (string, string, bool) { + label = strings.TrimSpace(label) + message = strings.ReplaceAll(message, "\r\n", "\n") + message = strings.ReplaceAll(message, "\r", "\n") + message = strings.ReplaceAll(message, "\n", " ") + + if label == "" || message == "" { + return "", "", false + } + + return label, message, true +} + +// obrimLogResolveName retrieves the software name from the local constant. +func obrimLogResolveName() string { + return strings.TrimSpace(obrimLogSoftwareName) +} + +// obrimLogResolveDirectory resolves and creates the platform-specific log directory. +func obrimLogResolveDirectory(softwareName string) (string, bool) { + var obrimLogDirectory string + + switch runtime.GOOS { + case "linux": + obrimLogHome, obrimLogHomeError := os.UserHomeDir() + if obrimLogHomeError != nil { + return "", false + } + + obrimLogDirectory = filepath.Join( + obrimLogHome, + ".local", + "state", + softwareName, + "log", + "main", + ) + + case "windows": + obrimLogLocalAppData := strings.TrimSpace(os.Getenv("LOCALAPPDATA")) + if obrimLogLocalAppData == "" { + return "", false + } + + obrimLogDirectory = filepath.Join( + obrimLogLocalAppData, + softwareName, + "log", + "main", + ) + + case "darwin": + obrimLogHome, obrimLogHomeError := os.UserHomeDir() + if obrimLogHomeError != nil { + return "", false + } + + obrimLogDirectory = filepath.Join( + obrimLogHome, + "Library", + "Logs", + softwareName, + "log", + "main", + ) + + default: + return "", false + } + + if obrimLogDirectory == "" { + return "", false + } + + if obrimLogMkdirError := os.MkdirAll(obrimLogDirectory, 0o755); obrimLogMkdirError != nil { + return "", false + } + + return obrimLogDirectory, true +} + +// obrimLogResolveFile resolves and creates the main log file. +func obrimLogResolveFile(directory string) (string, bool) { + obrimLogFile := filepath.Join(directory, "main.log") + + obrimLogHandle, obrimLogOpenError := os.OpenFile( + obrimLogFile, + os.O_CREATE|os.O_APPEND|os.O_WRONLY, + 0o644, + ) + if obrimLogOpenError != nil { + return "", false + } + + if obrimLogCloseError := obrimLogHandle.Close(); obrimLogCloseError != nil { + return "", false + } + + return obrimLogFile, true +} + +// obrimLogGetTimestamp gets the timestamp for a log entry. +func obrimLogGetTimestamp() string { + return time.Now().Format(time.RFC3339Nano) +} + +// obrimLogBuildEntry builds a complete formatted log entry. +func obrimLogBuildEntry(timestamp, label, message string) string { + return "[" + timestamp + "] [" + label + "] " + message + "\n" +} + +// obrimLogWriteEntry appends a complete log entry to the log file. +func obrimLogWriteEntry(file, entry string) { + obrimLogHandle, obrimLogOpenError := os.OpenFile( + file, + os.O_APPEND|os.O_WRONLY, + 0o644, + ) + if obrimLogOpenError != nil { + return + } + defer obrimLogHandle.Close() + + _, _ = obrimLogHandle.WriteString(entry) +} diff --git a/essential/visible/service/helper/marker/marker.go b/essential/visible/service/helper/marker/marker.go index e69de29..0f1eccd 100644 --- a/essential/visible/service/helper/marker/marker.go +++ b/essential/visible/service/helper/marker/marker.go @@ -0,0 +1,627 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Marker +| +| Purpose: +| - Provide a unified utility for generating unique markers through +| multiple generation strategies using a single entry point. +| - Support time-based, random, and encoded marker generation while +| maintaining a consistent output structure, deterministic routing, +| and extensible architecture. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use the time type with an epoch timestamp and instance identifier +| to generate distributed time-based markers. +| - Use the random type with a desired length and character set to +| generate collision-resistant random markers. +| - Use the encoded type with source data, output length, character +| set, and optional salt to generate deterministic encoded markers. +| - Validate the marker type and all type-specific configuration +| values before processing. +| - Use the unified ObrimMarker entry point to execute marker +| generation. +| +| Example: +| - ObrimMarker("time", map[string]any{ +| "epoch": int64(0), +| "instance": "instance-01", +| }) +| +| - ObrimMarker("random", map[string]any{ +| "length": 32, +| "charset": "abcdefghijklmnopqrstuvwxyz0123456789", +| }) +| +| - ObrimMarker("encoded", map[string]any{ +| "data": "source-data", +| "length": 32, +| "charset": "abcdefghijklmnopqrstuvwxyz0123456789", +| "salt": "optional-salt", +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package marker + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "fmt" + "strconv" + "strings" + "time" +) + +const ( + // Marker type identifiers. + obrimMarkerTypeTime = "time" + obrimMarkerTypeRandom = "random" + obrimMarkerTypeEncoded = "encoded" + + // Default marker configuration values. + obrimMarkerDefaultEpoch int64 = 0 + obrimMarkerDefaultCharset string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + obrimMarkerDefaultLength int = 32 + + // Marker result codes. + obrimMarkerSuccessTime = "SUCCESS_TIME_MARKER_GENERATED" + obrimMarkerSuccessRandom = "SUCCESS_RANDOM_MARKER_GENERATED" + obrimMarkerSuccessEncoded = "SUCCESS_ENCODED_MARKER_GENERATED" + + // Marker failure codes. + obrimMarkerFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimMarkerFailureInvalidConfig = "FAILURE_INVALID_CONFIG" + obrimMarkerFailureInvalidEpoch = "FAILURE_INVALID_EPOCH" + obrimMarkerFailureInvalidInstance = "FAILURE_INVALID_INSTANCE" + obrimMarkerFailureInvalidLength = "FAILURE_INVALID_LENGTH" + obrimMarkerFailureInvalidCharset = "FAILURE_INVALID_CHARSET" + obrimMarkerFailureEmptySource = "FAILURE_EMPTY_SOURCE" + obrimMarkerFailureInvalidEncodingConfig = "FAILURE_INVALID_ENCODING_CONFIG" + obrimMarkerFailureGenerationError = "FAILURE_GENERATION_ERROR" +) + +type obrimMarkerOutput struct { + Status bool `json:"status"` + Code string `json:"code"` + Payload any `json:"payload"` +} + +type obrimMarkerTimePayload struct { + Marker string `json:"marker"` + Epoch int64 `json:"epoch"` + Instance string `json:"instance"` +} + +type obrimMarkerRandomPayload struct { + Marker string `json:"marker"` + Length int `json:"length"` + Charset string `json:"charset"` +} + +type obrimMarkerEncodedPayload struct { + Marker string `json:"marker"` + Source string `json:"source"` + Length int `json:"length"` +} + +type obrimMarkerTimeConfig struct { + Epoch int64 + Instance string +} + +type obrimMarkerRandomConfig struct { + Length int + Charset string +} + +type obrimMarkerEncodedConfig struct { + Data string + Length int + Charset string + Salt string +} + +// ObrimMarker generates a marker using the requested generation strategy. +func ObrimMarker(typeName string, config map[string]any) map[string]any { + if code := obrimMarkerValidateInput(typeName, config); code != "" { + return obrimMarkerBuildOutput(false, code, nil) + } + + payload, code := obrimMarkerRouteRequest(typeName, config) + if code != "" { + return obrimMarkerBuildOutput(false, code, nil) + } + + return obrimMarkerBuildOutput(true, code, payload) +} + +// obrimMarkerValidateInput validates the requested marker type and configuration. +func obrimMarkerValidateInput(typeName string, config map[string]any) string { + if config == nil { + return obrimMarkerFailureInvalidConfig + } + + switch typeName { + case obrimMarkerTypeTime: + if code := obrimMarkerValidateTime(config); code != "" { + return code + } + case obrimMarkerTypeRandom: + if code := obrimMarkerValidateRandom(config); code != "" { + return code + } + case obrimMarkerTypeEncoded: + if code := obrimMarkerValidateEncoded(config); code != "" { + return code + } + default: + return obrimMarkerFailureInvalidType + } + + return "" +} + +// obrimMarkerRouteRequest routes the request to its type-specific workflow. +func obrimMarkerRouteRequest(typeName string, config map[string]any) (any, string) { + switch typeName { + case obrimMarkerTypeTime: + return obrimMarkerTime(config) + case obrimMarkerTypeRandom: + return obrimMarkerRandom(config) + case obrimMarkerTypeEncoded: + return obrimMarkerEncoded(config) + default: + return nil, obrimMarkerFailureInvalidType + } +} + +// obrimMarkerBuildOutput builds the standardized utility output. +func obrimMarkerBuildOutput(status bool, code string, payload any) map[string]any { + return map[string]any{ + "status": status, + "code": code, + "payload": payload, + } +} + +// obrimMarkerTime executes the time marker workflow. +func obrimMarkerTime(config map[string]any) (any, string) { + markerConfig, code := obrimMarkerTimeConfig(config) + if code != "" { + return nil, code + } + + marker, code := obrimMarkerTimeGenerate(markerConfig) + if code != "" { + return nil, code + } + + return obrimMarkerTimePayload{ + Marker: marker, + Epoch: markerConfig.Epoch, + Instance: markerConfig.Instance, + }, obrimMarkerSuccessTime +} + +// obrimMarkerValidateTime validates time marker configuration. +func obrimMarkerValidateTime(config map[string]any) string { + epoch := obrimMarkerDefaultEpoch + if value, exists := config["epoch"]; exists { + parsed, ok := obrimMarkerInt64(value) + if !ok || parsed < 0 { + return obrimMarkerFailureInvalidEpoch + } + epoch = parsed + } + + if epoch > time.Now().UnixNano() { + return obrimMarkerFailureInvalidEpoch + } + + instance, exists := config["instance"] + if !exists { + return obrimMarkerFailureInvalidInstance + } + + instanceValue, ok := instance.(string) + if !ok || strings.TrimSpace(instanceValue) == "" { + return obrimMarkerFailureInvalidInstance + } + + return "" +} + +// obrimMarkerTimeConfig builds the normalized time marker configuration. +func obrimMarkerTimeConfig(config map[string]any) (obrimMarkerTimeConfig, string) { + epoch := obrimMarkerDefaultEpoch + if value, exists := config["epoch"]; exists { + parsed, ok := obrimMarkerInt64(value) + if !ok || parsed < 0 || parsed > time.Now().UnixNano() { + return obrimMarkerTimeConfig{}, obrimMarkerFailureInvalidEpoch + } + epoch = parsed + } + + instance, ok := config["instance"].(string) + if !ok || strings.TrimSpace(instance) == "" { + return obrimMarkerTimeConfig{}, obrimMarkerFailureInvalidInstance + } + + return obrimMarkerTimeConfig{ + Epoch: epoch, + Instance: strings.TrimSpace(instance), + }, "" +} + +// obrimMarkerTimeGenerate generates a distributed time-based marker. +func obrimMarkerTimeGenerate(config obrimMarkerTimeConfig) (string, string) { + now := time.Now().UnixNano() + elapsed := now - config.Epoch + + if elapsed < 0 { + return "", obrimMarkerFailureInvalidEpoch + } + + marker := fmt.Sprintf( + "%x-%s", + elapsed, + config.Instance, + ) + + return marker, "" +} + +// obrimMarkerRandom executes the random marker workflow. +func obrimMarkerRandom(config map[string]any) (any, string) { + markerConfig, code := obrimMarkerRandomConfig(config) + if code != "" { + return nil, code + } + + marker, code := obrimMarkerRandomGenerate(markerConfig) + if code != "" { + return nil, code + } + + return obrimMarkerRandomPayload{ + Marker: marker, + Length: markerConfig.Length, + Charset: markerConfig.Charset, + }, obrimMarkerSuccessRandom +} + +// obrimMarkerValidateRandom validates random marker configuration. +func obrimMarkerValidateRandom(config map[string]any) string { + length := obrimMarkerDefaultLength + if value, exists := config["length"]; exists { + parsed, ok := obrimMarkerInt(value) + if !ok || parsed <= 0 { + return obrimMarkerFailureInvalidLength + } + length = parsed + } + + if length <= 0 { + return obrimMarkerFailureInvalidLength + } + + charset := obrimMarkerDefaultCharset + if value, exists := config["charset"]; exists { + parsed, ok := value.(string) + if !ok || parsed == "" { + return obrimMarkerFailureInvalidCharset + } + charset = parsed + } + + if charset == "" { + return obrimMarkerFailureInvalidCharset + } + + return "" +} + +// obrimMarkerRandomConfig builds the normalized random marker configuration. +func obrimMarkerRandomConfig(config map[string]any) (obrimMarkerRandomConfig, string) { + length := obrimMarkerDefaultLength + if value, exists := config["length"]; exists { + parsed, ok := obrimMarkerInt(value) + if !ok || parsed <= 0 { + return obrimMarkerRandomConfig{}, obrimMarkerFailureInvalidLength + } + length = parsed + } + + charset := obrimMarkerDefaultCharset + if value, exists := config["charset"]; exists { + parsed, ok := value.(string) + if !ok || parsed == "" { + return obrimMarkerRandomConfig{}, obrimMarkerFailureInvalidCharset + } + charset = parsed + } + + if charset == "" { + return obrimMarkerRandomConfig{}, obrimMarkerFailureInvalidCharset + } + + return obrimMarkerRandomConfig{ + Length: length, + Charset: charset, + }, "" +} + +// obrimMarkerRandomGenerate generates a cryptographically secure random marker. +func obrimMarkerRandomGenerate(config obrimMarkerRandomConfig) (string, string) { + characters := []rune(config.Charset) + if len(characters) == 0 { + return "", obrimMarkerFailureInvalidCharset + } + + randomBytes := make([]byte, config.Length) + if _, err := rand.Read(randomBytes); err != nil { + return "", obrimMarkerFailureGenerationError + } + + marker := make([]rune, config.Length) + for index := range marker { + marker[index] = characters[int(randomBytes[index])%len(characters)] + } + + return string(marker), "" +} + +// obrimMarkerEncoded executes the encoded marker workflow. +func obrimMarkerEncoded(config map[string]any) (any, string) { + markerConfig, code := obrimMarkerEncodedConfig(config) + if code != "" { + return nil, code + } + + marker, code := obrimMarkerEncodedGenerate(markerConfig) + if code != "" { + return nil, code + } + + return obrimMarkerEncodedPayload{ + Marker: marker, + Source: markerConfig.Data, + Length: markerConfig.Length, + }, obrimMarkerSuccessEncoded +} + +// obrimMarkerValidateEncoded validates encoded marker configuration. +func obrimMarkerValidateEncoded(config map[string]any) string { + data, exists := config["data"] + if !exists { + return obrimMarkerFailureEmptySource + } + + dataValue, ok := data.(string) + if !ok || strings.TrimSpace(dataValue) == "" { + return obrimMarkerFailureEmptySource + } + + length := obrimMarkerDefaultLength + if value, exists := config["length"]; exists { + parsed, ok := obrimMarkerInt(value) + if !ok || parsed <= 0 { + return obrimMarkerFailureInvalidEncodingConfig + } + length = parsed + } + + if length <= 0 { + return obrimMarkerFailureInvalidEncodingConfig + } + + charset := obrimMarkerDefaultCharset + if value, exists := config["charset"]; exists { + parsed, ok := value.(string) + if !ok || parsed == "" { + return obrimMarkerFailureInvalidEncodingConfig + } + charset = parsed + } + + if charset == "" { + return obrimMarkerFailureInvalidEncodingConfig + } + + if value, exists := config["salt"]; exists { + if _, ok := value.(string); !ok { + return obrimMarkerFailureInvalidEncodingConfig + } + } + + _ = length + _ = charset + + return "" +} + +// obrimMarkerEncodedConfig builds the normalized encoded marker configuration. +func obrimMarkerEncodedConfig(config map[string]any) (obrimMarkerEncodedConfig, string) { + data, ok := config["data"].(string) + if !ok || strings.TrimSpace(data) == "" { + return obrimMarkerEncodedConfig{}, obrimMarkerFailureEmptySource + } + + length := obrimMarkerDefaultLength + if value, exists := config["length"]; exists { + parsed, valid := obrimMarkerInt(value) + if !valid || parsed <= 0 { + return obrimMarkerEncodedConfig{}, obrimMarkerFailureInvalidEncodingConfig + } + length = parsed + } + + charset := obrimMarkerDefaultCharset + if value, exists := config["charset"]; exists { + parsed, valid := value.(string) + if !valid || parsed == "" { + return obrimMarkerEncodedConfig{}, obrimMarkerFailureInvalidEncodingConfig + } + charset = parsed + } + + salt := "" + if value, exists := config["salt"]; exists { + parsed, valid := value.(string) + if !valid { + return obrimMarkerEncodedConfig{}, obrimMarkerFailureInvalidEncodingConfig + } + salt = parsed + } + + return obrimMarkerEncodedConfig{ + Data: data, + Length: length, + Charset: charset, + Salt: salt, + }, "" +} + +// obrimMarkerEncodedGenerate generates a deterministic encoded marker. +func obrimMarkerEncodedGenerate(config obrimMarkerEncodedConfig) (string, string) { + characters := []rune(config.Charset) + if len(characters) == 0 { + return "", obrimMarkerFailureInvalidEncodingConfig + } + + source := config.Data + config.Salt + digest := sha256.Sum256([]byte(source)) + + seed := binary.BigEndian.Uint64(digest[:8]) + marker := make([]rune, config.Length) + + for index := range marker { + seed = seed*6364136223846793005 + 1442695040888963407 + marker[index] = characters[int(seed%uint64(len(characters)))] + } + + return string(marker), "" +} + +// obrimMarkerInt converts a supported value to int. +func obrimMarkerInt(value any) (int, bool) { + switch parsed := value.(type) { + case int: + return parsed, true + case int8: + return int(parsed), true + case int16: + return int(parsed), true + case int32: + return int(parsed), true + case int64: + return int(parsed), true + case uint: + return int(parsed), true + case uint8: + return int(parsed), true + case uint16: + return int(parsed), true + case uint32: + return int(parsed), true + case uint64: + if uint64(int(parsed)) != parsed { + return 0, false + } + return int(parsed), true + case float32: + if float32(int(parsed)) != parsed { + return 0, false + } + return int(parsed), true + case float64: + if float64(int(parsed)) != parsed { + return 0, false + } + return int(parsed), true + case string: + converted, err := strconv.Atoi(parsed) + if err != nil { + return 0, false + } + return converted, true + default: + return 0, false + } +} + +// obrimMarkerInt64 converts a supported value to int64. +func obrimMarkerInt64(value any) (int64, bool) { + switch parsed := value.(type) { + case int: + return int64(parsed), true + case int8: + return int64(parsed), true + case int16: + return int64(parsed), true + case int32: + return int64(parsed), true + case int64: + return parsed, true + case uint: + if uint64(int64(parsed)) != uint64(parsed) { + return 0, false + } + return int64(parsed), true + case uint8: + return int64(parsed), true + case uint16: + return int64(parsed), true + case uint32: + return int64(parsed), true + case uint64: + if parsed > uint64(^uint64(0)>>1) { + return 0, false + } + return int64(parsed), true + case float32: + if float32(int64(parsed)) != parsed { + return 0, false + } + return int64(parsed), true + case float64: + if float64(int64(parsed)) != parsed { + return 0, false + } + return int64(parsed), true + case string: + converted, err := strconv.ParseInt(parsed, 10, 64) + if err != nil { + return 0, false + } + return converted, true + default: + return 0, false + } +} diff --git a/essential/visible/service/helper/progress/progress.go b/essential/visible/service/helper/progress/progress.go index e69de29..cfa1bde 100644 --- a/essential/visible/service/helper/progress/progress.go +++ b/essential/visible/service/helper/progress/progress.go @@ -0,0 +1,375 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Progress +| +| Purpose: +| - Manage, monitor, and report lifecycle-aware task progress through +| standardized progress tracking for countable and uncountable workflows. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use countable progress when tracking numeric progress using current +| and target values. +| - Use uncountable progress when tracking activity-based progress +| without percentage calculation. +| - Provide the lifecycle state through the state configuration value. +| - Provide current and target values when using countable progress. +| +| Example: +| - ObrimProgress("countable", map[string]any{ +| "state": "running", +| "current": 50, +| "target": 100, +| }) +| +| - ObrimProgress("uncountable", map[string]any{ +| "state": "running", +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package progress + +import ( + "math" +) + +const ( + obrimProgressTypeCountable = "countable" + obrimProgressTypeUncountable = "uncountable" + + obrimProgressStateStarted = "started" + obrimProgressStateRunning = "running" + obrimProgressStateCompleted = "completed" + obrimProgressStateCanceled = "canceled" + + obrimProgressSuccessStarted = "SUCCESS_STARTED" + obrimProgressSuccessRunning = "SUCCESS_RUNNING" + obrimProgressSuccessCompleted = "SUCCESS_COMPLETED" + obrimProgressSuccessCanceled = "SUCCESS_CANCELED" + + obrimProgressFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimProgressFailureMissingState = "FAILURE_MISSING_STATE" + obrimProgressFailureInvalidState = "FAILURE_INVALID_STATE" + obrimProgressFailureMissingCurrent = "FAILURE_MISSING_CURRENT" + obrimProgressFailureInvalidCurrent = "FAILURE_INVALID_CURRENT" + obrimProgressFailureMissingTarget = "FAILURE_MISSING_TARGET" + obrimProgressFailureInvalidTarget = "FAILURE_INVALID_TARGET" + obrimProgressFailureNonPositiveTarget = "FAILURE_NON_POSITIVE_TARGET" +) + +type obrimProgressPayload struct { + Type string + State string + Current any + Target any + Percentage any +} + +type obrimProgressOutput struct { + Status bool + Code string + Payload any +} + +// ObrimProgress manages lifecycle-aware countable and uncountable progress. +func ObrimProgress(progressType string, config map[string]any) map[string]any { + if code := obrimProgressValidateInput(progressType, config); code != "" { + return obrimProgressBuildOutput(false, code, nil) + } + + return obrimProgressRouteRequest(progressType, config) +} + +// obrimProgressValidateInput validates the requested progress type and configuration. +func obrimProgressValidateInput(progressType string, config map[string]any) string { + switch progressType { + case obrimProgressTypeCountable: + state, exists := config["state"] + if !exists { + return obrimProgressFailureMissingState + } + + stateValue, ok := state.(string) + if !ok { + return obrimProgressFailureInvalidState + } + + switch stateValue { + case obrimProgressStateStarted, + obrimProgressStateRunning, + obrimProgressStateCompleted, + obrimProgressStateCanceled: + default: + return obrimProgressFailureInvalidState + } + + if _, exists := config["current"]; !exists { + return obrimProgressFailureMissingCurrent + } + + switch config["current"].(type) { + case int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64: + default: + return obrimProgressFailureInvalidCurrent + } + + if _, exists := config["target"]; !exists { + return obrimProgressFailureMissingTarget + } + + switch config["target"].(type) { + case int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, + float32, float64: + default: + return obrimProgressFailureInvalidTarget + } + + target := 0.0 + + switch value := config["target"].(type) { + case int: + target = float64(value) + case int8: + target = float64(value) + case int16: + target = float64(value) + case int32: + target = float64(value) + case int64: + target = float64(value) + case uint: + target = float64(value) + case uint8: + target = float64(value) + case uint16: + target = float64(value) + case uint32: + target = float64(value) + case uint64: + target = float64(value) + case float32: + target = float64(value) + case float64: + target = value + } + + if target <= 0 { + return obrimProgressFailureNonPositiveTarget + } + + case obrimProgressTypeUncountable: + state, exists := config["state"] + if !exists { + return obrimProgressFailureMissingState + } + + stateValue, ok := state.(string) + if !ok { + return obrimProgressFailureInvalidState + } + + switch stateValue { + case obrimProgressStateStarted, + obrimProgressStateRunning, + obrimProgressStateCompleted, + obrimProgressStateCanceled: + default: + return obrimProgressFailureInvalidState + } + + default: + return obrimProgressFailureInvalidType + } + + return "" +} + +// obrimProgressRouteRequest routes progress processing to its type-specific entry function. +func obrimProgressRouteRequest(progressType string, config map[string]any) map[string]any { + switch progressType { + case obrimProgressTypeCountable: + return obrimProgressCountable(config) + case obrimProgressTypeUncountable: + return obrimProgressUncountable(config) + default: + return obrimProgressBuildOutput(false, obrimProgressFailureInvalidType, nil) + } +} + +// obrimProgressBuildOutput builds the standardized progress utility output. +func obrimProgressBuildOutput(status bool, code string, payload *obrimProgressPayload) map[string]any { + output := map[string]any{ + "status": status, + "code": code, + "payload": nil, + } + + if payload == nil { + return output + } + + output["payload"] = map[string]any{ + "type": payload.Type, + "state": payload.State, + "current": payload.Current, + "target": payload.Target, + "percentage": payload.Percentage, + } + + return output +} + +// obrimProgressCountable processes countable progress workflows. +func obrimProgressCountable(config map[string]any) map[string]any { + state := config["state"].(string) + + var current float64 + var target float64 + + switch value := config["current"].(type) { + case int: + current = float64(value) + case int8: + current = float64(value) + case int16: + current = float64(value) + case int32: + current = float64(value) + case int64: + current = float64(value) + case uint: + current = float64(value) + case uint8: + current = float64(value) + case uint16: + current = float64(value) + case uint32: + current = float64(value) + case uint64: + current = float64(value) + case float32: + current = float64(value) + case float64: + current = value + } + + switch value := config["target"].(type) { + case int: + target = float64(value) + case int8: + target = float64(value) + case int16: + target = float64(value) + case int32: + target = float64(value) + case int64: + target = float64(value) + case uint: + target = float64(value) + case uint8: + target = float64(value) + case uint16: + target = float64(value) + case uint32: + target = float64(value) + case uint64: + target = float64(value) + case float32: + target = float64(value) + case float64: + target = value + } + + percentage := int(math.Round((current / target) * 100)) + + if percentage < 0 { + percentage = 0 + } + + if percentage > 100 { + percentage = 100 + } + + code := obrimProgressSuccessRunning + + switch state { + case obrimProgressStateStarted: + code = obrimProgressSuccessStarted + case obrimProgressStateRunning: + code = obrimProgressSuccessRunning + case obrimProgressStateCompleted: + code = obrimProgressSuccessCompleted + case obrimProgressStateCanceled: + code = obrimProgressSuccessCanceled + } + + return obrimProgressBuildOutput( + true, + code, + &obrimProgressPayload{ + Type: obrimProgressTypeCountable, + State: state, + Current: current, + Target: target, + Percentage: percentage, + }, + ) +} + +// obrimProgressUncountable processes uncountable progress workflows. +func obrimProgressUncountable(config map[string]any) map[string]any { + state := config["state"].(string) + + code := obrimProgressSuccessRunning + + switch state { + case obrimProgressStateStarted: + code = obrimProgressSuccessStarted + case obrimProgressStateRunning: + code = obrimProgressSuccessRunning + case obrimProgressStateCompleted: + code = obrimProgressSuccessCompleted + case obrimProgressStateCanceled: + code = obrimProgressSuccessCanceled + } + + return obrimProgressBuildOutput( + true, + code, + &obrimProgressPayload{ + Type: obrimProgressTypeUncountable, + State: state, + Current: nil, + Target: nil, + Percentage: nil, + }, + ) +} diff --git a/essential/visible/service/helper/retriever/retriever.go b/essential/visible/service/helper/retriever/retriever.go index e69de29..ef0ee1b 100644 --- a/essential/visible/service/helper/retriever/retriever.go +++ b/essential/visible/service/helper/retriever/retriever.go @@ -0,0 +1,400 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Retriever +| +| Purpose: +| - Retrieve data from supported resource handlers through a unified +| retrieval interface. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Use for retrieving data from registered JSON resource handlers. +| - Use type-based dispatching to route retrieval requests. +| - Use resource retrieval to obtain a complete JSON resource. +| - Use path retrieval to obtain a specific nested value. +| - Use fields retrieval to obtain multiple nested values. +| - Do not use path and fields retrieval together. +| +| Example: +| - ObrimRetriever("json", map[string]any{ +| "resource": "metadata", +| }) +| +| - ObrimRetriever("json", map[string]any{ +| "resource": "metadata", +| "path": "app.name", +| }) +| +| - ObrimRetriever("json", map[string]any{ +| "resource": "metadata", +| "fields": []any{"app.name", "app.version"}, +| }) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package retriever + +import ( + "fmt" + "strings" + "sync" +) + +const ( + // Utility type identifiers define supported retriever input types. + obrimRetrieverTypeJSON = "json" + + // Retriever result codes define successful execution outcomes. + obrimRetrieverSuccessResource = "SUCCESS_RESOURCE_RETRIEVED" + obrimRetrieverSuccessPath = "SUCCESS_PATH_RETRIEVED" + obrimRetrieverSuccessFields = "SUCCESS_FIELDS_RETRIEVED" + + // Retriever result codes define failed execution outcomes. + obrimRetrieverFailureInvalidType = "FAILURE_INVALID_TYPE" + obrimRetrieverFailureInvalidConfig = "FAILURE_INVALID_CONFIG" + obrimRetrieverFailureResourceRequired = "FAILURE_RESOURCE_REQUIRED" + obrimRetrieverFailurePathAndFields = "FAILURE_PATH_AND_FIELDS" + obrimRetrieverFailureInvalidPath = "FAILURE_INVALID_PATH" + obrimRetrieverFailureInvalidFields = "FAILURE_INVALID_FIELDS" + obrimRetrieverFailureResourceNotFound = "FAILURE_RESOURCE_NOT_FOUND" + obrimRetrieverFailureHandlerFailed = "FAILURE_HANDLER_FAILED" + obrimRetrieverFailurePathNotFound = "FAILURE_PATH_NOT_FOUND" + obrimRetrieverFailureFieldNotFound = "FAILURE_FIELD_NOT_FOUND" + obrimRetrieverFailureUnsupportedConfig = "FAILURE_UNSUPPORTED_CONFIG" +) + +var ( + // obrimRetrieverJsonHandlers stores registered JSON resource handlers. + obrimRetrieverJsonHandlers = map[string]func() map[string]any{} + + // obrimRetrieverJsonHandlersMutex protects the JSON handler registry. + obrimRetrieverJsonHandlersMutex sync.RWMutex +) + +// ObrimRetrieverResult represents the standardized retriever execution output. +type ObrimRetrieverResult struct { + Status bool `json:"status"` + Code string `json:"code"` + Payload map[string]any `json:"payload"` +} + +// ObrimRetriever retrieves data from a supported resource handler. +func ObrimRetriever( + typ string, + config map[string]any, +) map[string]any { + if code := obrimRetrieverValidateInput(typ, config); code != "" { + return obrimRetrieverBuildOutput(false, code, nil) + } + + return obrimRetrieverRouteRequest(typ, config) +} + +// ObrimRetrieverJsonRegister registers a JSON resource handler. +func ObrimRetrieverJsonRegister( + resource string, + handler func() map[string]any, +) { + if strings.TrimSpace(resource) == "" || handler == nil { + return + } + + obrimRetrieverJsonHandlersMutex.Lock() + defer obrimRetrieverJsonHandlersMutex.Unlock() + + obrimRetrieverJsonHandlers[resource] = handler +} + +// obrimRetrieverValidateInput validates the retriever type and configuration. +func obrimRetrieverValidateInput( + typ string, + config map[string]any, +) string { + switch typ { + case obrimRetrieverTypeJSON: + return obrimRetrieverValidateJSONConfig(config) + default: + return obrimRetrieverFailureInvalidType + } +} + +// obrimRetrieverValidateJSONConfig validates JSON retriever configuration. +func obrimRetrieverValidateJSONConfig(config map[string]any) string { + if config == nil { + return obrimRetrieverFailureInvalidConfig + } + + resourceValue, exists := config["resource"] + if !exists { + return obrimRetrieverFailureResourceRequired + } + + resource, ok := resourceValue.(string) + if !ok || strings.TrimSpace(resource) == "" { + return obrimRetrieverFailureResourceRequired + } + + _, hasPath := config["path"] + _, hasFields := config["fields"] + + if hasPath && hasFields { + return obrimRetrieverFailurePathAndFields + } + + for key := range config { + switch key { + case "resource", "path", "fields": + default: + return obrimRetrieverFailureUnsupportedConfig + } + } + + if hasPath { + path, ok := config["path"].(string) + if !ok || strings.TrimSpace(path) == "" { + return obrimRetrieverFailureInvalidPath + } + } + + if hasFields { + fields, ok := config["fields"].([]string) + if !ok || len(fields) == 0 { + return obrimRetrieverFailureInvalidFields + } + + for _, field := range fields { + if strings.TrimSpace(field) == "" { + return obrimRetrieverFailureInvalidFields + } + } + } + + return "" +} + +// obrimRetrieverRouteRequest routes the request to its type-specific entry function. +func obrimRetrieverRouteRequest( + typ string, + config map[string]any, +) map[string]any { + switch typ { + case obrimRetrieverTypeJSON: + return obrimRetrieverJson(config) + default: + return obrimRetrieverBuildOutput( + false, + obrimRetrieverFailureInvalidType, + nil, + ) + } +} + +// obrimRetrieverBuildOutput builds the standardized retriever output. +func obrimRetrieverBuildOutput( + status bool, + code string, + payload map[string]any, +) map[string]any { + if !status { + payload = nil + } + + return map[string]any{ + "status": status, + "code": code, + "payload": payload, + } +} + +// obrimRetrieverJson processes a JSON retrieval request. +func obrimRetrieverJson(config map[string]any) map[string]any { + resource := config["resource"].(string) + + data, ok := obrimRetrieverJsonHandler(resource) + if !ok { + return obrimRetrieverBuildOutput( + false, + obrimRetrieverFailureResourceNotFound, + nil, + ) + } + + if pathValue, exists := config["path"]; exists { + path := pathValue.(string) + + value, ok := obrimRetrieverJsonPath(data, path) + if !ok { + return obrimRetrieverBuildOutput( + false, + obrimRetrieverFailurePathNotFound, + nil, + ) + } + + return obrimRetrieverBuildOutput( + true, + obrimRetrieverSuccessPath, + map[string]any{ + "data": value, + }, + ) + } + + if fieldsValue, exists := config["fields"]; exists { + fields := fieldsValue.([]string) + + values, ok := obrimRetrieverJsonFields(data, fields) + if !ok { + return obrimRetrieverBuildOutput( + false, + obrimRetrieverFailureFieldNotFound, + nil, + ) + } + + return obrimRetrieverBuildOutput( + true, + obrimRetrieverSuccessFields, + map[string]any{ + "data": values, + }, + ) + } + + return obrimRetrieverBuildOutput( + true, + obrimRetrieverSuccessResource, + map[string]any{ + "data": obrimRetrieverJsonResource(data), + }, + ) +} + +// obrimRetrieverJsonHandler locates and invokes a registered JSON handler. +func obrimRetrieverJsonHandler( + resource string, +) (map[string]any, bool) { + obrimRetrieverJsonHandlersMutex.RLock() + handler, exists := obrimRetrieverJsonHandlers[resource] + obrimRetrieverJsonHandlersMutex.RUnlock() + + if !exists || handler == nil { + return nil, false + } + + data := handler() + if data == nil { + return nil, false + } + + return data, true +} + +// obrimRetrieverJsonResource returns the complete JSON resource payload. +func obrimRetrieverJsonResource( + resource map[string]any, +) map[string]any { + return resource +} + +// obrimRetrieverJsonPath retrieves a value using a dot-notation path. +func obrimRetrieverJsonPath( + resource map[string]any, + path string, +) (any, bool) { + parts := strings.Split(path, ".") + if len(parts) == 0 { + return nil, false + } + + var current any = resource + + for _, part := range parts { + if part == "" { + return nil, false + } + + switch value := current.(type) { + case map[string]any: + next, exists := value[part] + if !exists { + return nil, false + } + + current = next + + case []any: + index := -1 + + for i, item := range value { + if part == strings.TrimSpace(part) { + var parsed int + if _, err := fmt.Sscanf(part, "%d", &parsed); err == nil { + index = parsed + } + } + + if index >= 0 { + _ = item + break + } + + break + } + + if index < 0 || index >= len(value) { + return nil, false + } + + current = value[index] + + default: + return nil, false + } + } + + return current, true +} + +// obrimRetrieverJsonFields retrieves multiple values using field selection. +func obrimRetrieverJsonFields( + resource map[string]any, + fields []string, +) (map[string]any, bool) { + values := make(map[string]any, len(fields)) + + for _, field := range fields { + value, ok := obrimRetrieverJsonPath(resource, field) + if !ok { + return nil, false + } + + values[field] = value + } + + return values, true +} \ No newline at end of file diff --git a/essential/visible/service/helper/status/status.go b/essential/visible/service/helper/status/status.go index e69de29..ebaea2f 100644 --- a/essential/visible/service/helper/status/status.go +++ b/essential/visible/service/helper/status/status.go @@ -0,0 +1,114 @@ +/* +|-------------------------------------------------------------------------- +| Description +|-------------------------------------------------------------------------- +| +| Name: +| - Status +| +| Purpose: +| - Provide a framework-level CLI status utility that standardizes +| terminal message presentation using semantic labels and visual +| indicators to ensure consistent output across all softwares. +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Instruction +|-------------------------------------------------------------------------- +| +| Guideline: +| - Accept a semantic status category and human-readable message. +| - Use INFO, WARNING, SUCCESS, and ERROR as supported status labels. +| - Treat unsupported, empty, or invalid labels as INFO. +| - Emit exactly one standardized terminal message per invocation. +| +| Example: +| - ObrimStatus( +| "INFO", +| "Application started successfully.", +| ) +| +| - ObrimStatus( +| "WARNING", +| "Configuration file not found.", +| ) +| +| - ObrimStatus( +| "SUCCESS", +| "Installation completed.", +| ) +| +| - ObrimStatus( +| "ERROR", +| "Unable to connect to the server.", +| ) +| +|-------------------------------------------------------------------------- +*/ + +/* +|-------------------------------------------------------------------------- +| Credit +|-------------------------------------------------------------------------- +| +| Contributor: +| - Rajon Ahmed +| - Blockonite +| +|-------------------------------------------------------------------------- +*/ + +package status + +import ( + "fmt" + "strings" +) + +// ObrimStatus emits a standardized terminal message. +func ObrimStatus(label, message string) { + normalizedLabel, normalizedMessage := obrimStatusValidateInput(label, message) + output := obrimStatusBuildOutput(obrimStatusFormatMessage(normalizedLabel, normalizedMessage)) + + fmt.Print(output) +} + +// obrimStatusValidateInput normalizes and validates the status label and message. +func obrimStatusValidateInput(label, message string) (string, string) { + normalizedLabel := strings.ToUpper(strings.TrimSpace(label)) + normalizedMessage := strings.TrimSpace(message) + + switch normalizedLabel { + case "INFO", "WARNING", "SUCCESS", "ERROR": + default: + normalizedLabel = "INFO" + } + + return normalizedLabel, normalizedMessage +} + +// obrimStatusFormatMessage formats the status label and message with its visual indicator. +func obrimStatusFormatMessage(label, message string) string { + var indicator string + + switch label { + case "WARNING": + indicator = "!" + case "SUCCESS": + indicator = "✓" + case "ERROR": + indicator = "✗" + default: + indicator = "ℹ" + } + + return fmt.Sprintf("[%s %s] %s\n", indicator, label, message) +} + +// obrimStatusBuildOutput builds the final terminal message. +func obrimStatusBuildOutput(message string) string { + return message +} \ No newline at end of file