Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b94b05a436 | |||
| dfef069ffe | |||
| e42bad16d7 | |||
| b85230299b | |||
| 94dc8f2e42 | |||
| 01890b5fdc | |||
| cc208548e5 | |||
| d7d20533ca | |||
| 69c58563a5 | |||
| cf0f8b0198 | |||
| 0e9de5eb97 | |||
| e2997bf475 | |||
| f0c6e11265 | |||
| f535b19bae | |||
| c94dcc176c | |||
| 96e12699b9 | |||
| c18176f240 | |||
| 3500a9248d | |||
| a4a0dcad16 | |||
| 8140c95af1 | |||
| fb710e77fa | |||
| 4e93ea3b28 | |||
| 460711676c | |||
| ca62057073 | |||
| f8bb9caa65 | |||
| a20bfdd1e6 | |||
| b70eabba52 | |||
| 3c10bf436e | |||
| 5a99f80a55 | |||
| 0924f2cd28 | |||
| d5bd6da037 | |||
| 6b282b76c2 | |||
| c093afe12a | |||
| c7ffbbb78a | |||
| 8b3a76ebda |
27
.gitignore
vendored
27
.gitignore
vendored
@ -0,0 +1,27 @@
|
||||
# Environment
|
||||
.env
|
||||
.env/
|
||||
|
||||
# Temporary
|
||||
.tmp
|
||||
*.tmp
|
||||
.tmp/
|
||||
|
||||
# Backup
|
||||
.bkp
|
||||
*.bkp
|
||||
.bkp/
|
||||
|
||||
# Archives
|
||||
*.zip
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
*.gz
|
||||
*.bz2
|
||||
*.tar.bz2
|
||||
*.xz
|
||||
*.tar.xz
|
||||
*.7z
|
||||
*.rar
|
||||
*.Z
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@ -0,0 +1,20 @@
|
||||
# Change Log
|
||||
|
||||
Description
|
||||
|
||||
## [Version] - [Date]
|
||||
|
||||
### Added
|
||||
|
||||
- [Title]
|
||||
Short Description
|
||||
|
||||
### Changed
|
||||
|
||||
- [Title]
|
||||
Short Description
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Title]
|
||||
Short Description
|
||||
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Startupore
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
284
Makefile
284
Makefile
@ -0,0 +1,284 @@
|
||||
# =========================================================
|
||||
# Variable
|
||||
# =========================================================
|
||||
|
||||
# Framework
|
||||
# Identity and metadata of framework. Human-readable name, machine-readable names(lower and upper), and semantic version.
|
||||
FRAMEWORK_REGULAR=Framework Regular
|
||||
FRAMEWORK_LOWER=frameworklower
|
||||
FRAMEWORK_UPPER=FRAMEWORKUPPER
|
||||
FRAMEWORK_VERSION=0.0.0
|
||||
|
||||
# Software
|
||||
# Identity and metadata of software. Human-readable name, machine-readable names(lower and upper), and semantic version.
|
||||
SOFTWARE_REGULAR=Software Regular
|
||||
SOFTWARE_LOWER=softwarelower
|
||||
SOFTWARE_UPPER=SOFTWAREUPPER
|
||||
SOFTWARE_VERSION=0.0.0
|
||||
|
||||
# Go Module
|
||||
# App module and import path used by go.mod and source imports. Valid Go module path.
|
||||
GO_MODULE=go/module
|
||||
|
||||
# Directory
|
||||
# Locations used by build and management commands. Relative or absolute paths.
|
||||
SOURCE_DIR=./run/main
|
||||
RELEASE_DIR=./release
|
||||
FRAMEWORK_METADATA_DIR=./data/framework/metadata
|
||||
SOFTWARE_METADATA_DIR=./data/software/metadata
|
||||
|
||||
# File
|
||||
# Metadata file names without extension. File name only.
|
||||
FRAMEWORK_METADATA_FLE=resource
|
||||
SOFTWARE_METADATA_FLE=resource
|
||||
|
||||
# Derived Path
|
||||
# Automatically generated from directory and file variables. Do not edit.
|
||||
FRAMEWORK_METADATA_PATH=$(FRAMEWORK_METADATA_DIR)/$(FRAMEWORK_METADATA_FLE)
|
||||
SOFTWARE_METADATA_PATH=$(SOFTWARE_METADATA_DIR)/$(SOFTWARE_METADATA_FLE)
|
||||
|
||||
# Target File Type
|
||||
# File types scanned by update commands. Valid find expression.
|
||||
TARGET_FILE_TYPE=\
|
||||
-name "*.go" \
|
||||
-o -name "*.json" \
|
||||
-o -name "*.yaml" \
|
||||
-o -name "*.yml"
|
||||
|
||||
# Go Module Placeholder
|
||||
# Replacement rules applied by upd-gm-ph. Valid sed replacement expressions.
|
||||
GO_MODULE_REPLACEMENT=\
|
||||
-e 's|gomodule|$(GO_MODULE)|g' \
|
||||
-e 's|go/module|$(GO_MODULE)|g' \
|
||||
-e 's|go-module|$(GO_MODULE)|g' \
|
||||
-e 's|go_module|$(GO_MODULE)|g'
|
||||
|
||||
# =========================================================
|
||||
# Command
|
||||
# =========================================================
|
||||
|
||||
# PHONY Target
|
||||
.PHONY: \
|
||||
help \
|
||||
upd-gm-ph \
|
||||
upd-fw-md \
|
||||
upd-sw-md \
|
||||
go-ini \
|
||||
go-tid \
|
||||
go-stp \
|
||||
pre-rel \
|
||||
go-bld \
|
||||
go-bld-lin \
|
||||
go-bld-win \
|
||||
go-bld-mac \
|
||||
bld-lin-amd \
|
||||
bld-lin-arm \
|
||||
bld-win-amd \
|
||||
bld-win-arm \
|
||||
bld-mac-amd \
|
||||
bld-mac-arm
|
||||
|
||||
# Default Command
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# =========================================================
|
||||
# Help
|
||||
# =========================================================
|
||||
|
||||
help:
|
||||
@echo ""
|
||||
@echo "Available Commands:"
|
||||
@echo ""
|
||||
@printf " %-18s %s\n" "upd-gm-ph" "Update Go module placeholder"
|
||||
@printf " %-18s %s\n" "upd-fw-md" "Update framework metadata"
|
||||
@printf " %-18s %s\n" "upd-sw-md" "Update software metadata"
|
||||
@echo ""
|
||||
@printf " %-18s %s\n" "go-ini" "Initialize Go module"
|
||||
@printf " %-18s %s\n" "go-tid" "Tidy Go module dependencies"
|
||||
@printf " %-18s %s\n" "go-stp" "Initialize Go module and tidy dependencies"
|
||||
@echo ""
|
||||
@printf " %-18s %s\n" "go-bld" "Build all platform binaries"
|
||||
@printf " %-18s %s\n" "go-bld-lin" "Build all Linux binaries"
|
||||
@printf " %-18s %s\n" "go-bld-win" "Build all Windows binaries"
|
||||
@printf " %-18s %s\n" "go-bld-mac" "Build all macOS binaries"
|
||||
@echo ""
|
||||
@printf " %-18s %s\n" "bld-lin-amd" "Build Linux AMD64 binary"
|
||||
@printf " %-18s %s\n" "bld-lin-arm" "Build Linux ARM64 binary"
|
||||
@echo ""
|
||||
@printf " %-18s %s\n" "bld-win-amd" "Build Windows AMD64 binary"
|
||||
@printf " %-18s %s\n" "bld-win-arm" "Build Windows ARM64 binary"
|
||||
@echo ""
|
||||
@printf " %-18s %s\n" "bld-mac-amd" "Build macOS AMD64 binary"
|
||||
@printf " %-18s %s\n" "bld-mac-arm" "Build macOS ARM64 binary"
|
||||
@echo ""
|
||||
|
||||
# =========================================================
|
||||
# Update
|
||||
# =========================================================
|
||||
|
||||
upd-gm-ph:
|
||||
|
||||
@find . -type f \
|
||||
\( $(TARGET_FILE_TYPE) \) \
|
||||
-exec sed -i \
|
||||
$(GO_MODULE_REPLACEMENT) \
|
||||
{} +
|
||||
|
||||
@if [ -f go.mod ]; then \
|
||||
sed -i \
|
||||
$(GO_MODULE_REPLACEMENT) \
|
||||
go.mod; \
|
||||
fi
|
||||
|
||||
@go mod tidy
|
||||
|
||||
@echo "Go Module Updated!"
|
||||
|
||||
upd-fw-md:
|
||||
|
||||
@jq \
|
||||
--arg framework_regular "$(FRAMEWORK_REGULAR)" \
|
||||
--arg framework_lower "$(FRAMEWORK_LOWER)" \
|
||||
--arg framework_upper "$(FRAMEWORK_UPPER)" \
|
||||
--arg framework_version "$(FRAMEWORK_VERSION)" \
|
||||
'.framework.regular=$$framework_regular|.framework.lower=$$framework_lower|.framework.upper=$$framework_upper|.framework.version=$$framework_version' \
|
||||
$(FRAMEWORK_METADATA_PATH).json \
|
||||
> $(FRAMEWORK_METADATA_PATH).json.tmp
|
||||
|
||||
@mv $(FRAMEWORK_METADATA_PATH).json.tmp $(FRAMEWORK_METADATA_PATH).json
|
||||
|
||||
@echo "Framework Metadata Updated!"
|
||||
|
||||
upd-sw-md:
|
||||
|
||||
@jq \
|
||||
--arg software_regular "$(SOFTWARE_REGULAR)" \
|
||||
--arg software_lower "$(SOFTWARE_LOWER)" \
|
||||
--arg software_upper "$(SOFTWARE_UPPER)" \
|
||||
--arg software_version "$(SOFTWARE_VERSION)" \
|
||||
'.software.regular=$$software_regular|.software.lower=$$software_lower|.software.upper=$$software_upper|.software.version=$$software_version' \
|
||||
$(SOFTWARE_METADATA_PATH).json \
|
||||
> $(SOFTWARE_METADATA_PATH).json.tmp
|
||||
|
||||
@mv $(SOFTWARE_METADATA_PATH).json.tmp $(SOFTWARE_METADATA_PATH).json
|
||||
|
||||
@echo "Software Metadata Updated!"
|
||||
|
||||
# =========================================================
|
||||
# Release
|
||||
# =========================================================
|
||||
|
||||
pre-rel:
|
||||
@test -n "$(RELEASE_DIR)"
|
||||
@mkdir -p "$(RELEASE_DIR)"
|
||||
@find "$(RELEASE_DIR)" -mindepth 1 -delete
|
||||
|
||||
# =========================================================
|
||||
# Initialize
|
||||
# =========================================================
|
||||
|
||||
go-ini:
|
||||
@test -f go.mod || go mod init $(GO_MODULE)
|
||||
|
||||
go-tid:
|
||||
go mod tidy
|
||||
|
||||
go-stp: \
|
||||
go-ini \
|
||||
go-tid
|
||||
|
||||
# =========================================================
|
||||
# Build Group
|
||||
# =========================================================
|
||||
|
||||
go-bld: \
|
||||
pre-rel \
|
||||
go-tid \
|
||||
bld-lin-amd \
|
||||
bld-lin-arm \
|
||||
bld-win-amd \
|
||||
bld-win-arm \
|
||||
bld-mac-amd \
|
||||
bld-mac-arm
|
||||
|
||||
go-bld-lin: \
|
||||
pre-rel \
|
||||
go-tid \
|
||||
bld-lin-amd \
|
||||
bld-lin-arm
|
||||
|
||||
go-bld-win: \
|
||||
pre-rel \
|
||||
go-tid \
|
||||
bld-win-amd \
|
||||
bld-win-arm
|
||||
|
||||
go-bld-mac: \
|
||||
pre-rel \
|
||||
go-tid \
|
||||
bld-mac-amd \
|
||||
bld-mac-arm
|
||||
|
||||
# =========================================================
|
||||
# Build Target
|
||||
# =========================================================
|
||||
|
||||
# Linux AMD64
|
||||
bld-lin-amd:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildvcs=false \
|
||||
-ldflags="-s -w" \
|
||||
-o $(RELEASE_DIR)/$(SOFTWARE_LOWER)-v$(SOFTWARE_VERSION)-linux-amd64 \
|
||||
$(SOURCE_DIR)
|
||||
|
||||
# Linux ARM64
|
||||
bld-lin-arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildvcs=false \
|
||||
-ldflags="-s -w" \
|
||||
-o $(RELEASE_DIR)/$(SOFTWARE_LOWER)-v$(SOFTWARE_VERSION)-linux-arm64 \
|
||||
$(SOURCE_DIR)
|
||||
|
||||
# Windows AMD64
|
||||
bld-win-amd:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildvcs=false \
|
||||
-ldflags="-s -w" \
|
||||
-o $(RELEASE_DIR)/$(SOFTWARE_LOWER)-v$(SOFTWARE_VERSION)-windows-amd64.exe \
|
||||
$(SOURCE_DIR)
|
||||
|
||||
# Windows ARM64
|
||||
bld-win-arm:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildvcs=false \
|
||||
-ldflags="-s -w" \
|
||||
-o $(RELEASE_DIR)/$(SOFTWARE_LOWER)-v$(SOFTWARE_VERSION)-windows-arm64.exe \
|
||||
$(SOURCE_DIR)
|
||||
|
||||
# macOS AMD64
|
||||
bld-mac-amd:
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildvcs=false \
|
||||
-ldflags="-s -w" \
|
||||
-o $(RELEASE_DIR)/$(SOFTWARE_LOWER)-v$(SOFTWARE_VERSION)-macos-amd64 \
|
||||
$(SOURCE_DIR)
|
||||
|
||||
# macOS ARM64
|
||||
bld-mac-arm:
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-buildvcs=false \
|
||||
-ldflags="-s -w" \
|
||||
-o $(RELEASE_DIR)/$(SOFTWARE_LOWER)-v$(SOFTWARE_VERSION)-macos-arm64 \
|
||||
$(SOURCE_DIR)
|
||||
236
README.md
236
README.md
@ -0,0 +1,236 @@
|
||||
# Go TUI Framework
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the TUI source tree represented by the project-root placeholder `source`. The structure separates shared data resources, framework-owned foundations, software-specific extensions, runtime composition, and project-level files.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Data
|
||||
|
||||
The `data` directory provides the common resource system for framework-owned and software-owned embedded data, using a central cabinet, data-specific ledgers, and resource handlers to register and resolve resources through a controlled and lightweight access path.
|
||||
|
||||
- `data` — provides the common resource system for framework-owned and software-owned embedded data, using a central cabinet, data-specific ledgers, and resource handlers to register and resolve resources through a controlled and lightweight access path.
|
||||
- `cabinet` — serves as the common coordination point for framework-owned and software-owned data ledgers, determining which ledger is responsible for a requested resource.
|
||||
- `cabinet.go` — implements the cabinet that coordinates registration and access across the framework and software data ledgers.
|
||||
- `framework` — contains the data resources owned and maintained by the framework developer.
|
||||
- `ledger` — acts as the registrar for framework-owned resources and identifies the handler responsible for each resource.
|
||||
- `ledger.go` — implements the framework-owned resource ledger and its resource registration and resolution.
|
||||
- `metadata` — contains framework-owned metadata resources.
|
||||
- `handler.go` — provides the resource-specific logic for accessing and parsing the embedded framework metadata when requested.
|
||||
- `resource.json` — contains the embedded framework metadata resource.
|
||||
- `software` — contains the data resources owned and maintained by the software developer.
|
||||
- `ledger` — acts as the registrar for software-owned resources and identifies the handler responsible for each resource.
|
||||
- `ledger.go` — implements the software-owned resource ledger and its resource registration and resolution.
|
||||
- `metadata` — contains software-owned metadata resources.
|
||||
- `handler.go` — provides the resource-specific logic for accessing and parsing the embedded software metadata when requested.
|
||||
- `resource.json` — contains the embedded software metadata resource.
|
||||
|
||||
### Essential
|
||||
|
||||
The `essential` directory provides the framework-owned foundation used by software built on the framework, including framework interactions, internal services, and reusable services exposed to the software layer.
|
||||
|
||||
- `essential` — provides the framework-owned foundation used by software built on the framework, including framework interactions, internal services, and reusable services exposed to the software layer.
|
||||
- `exchange` — serves as the registrar for framework-owned interactions, allowing framework services to register their interactive, imperative, and directive logic for later composition by the runtime.
|
||||
- `directive` — contains the framework-owned directive interaction path.
|
||||
- `directive.go` — implements framework-owned directive interaction handling.
|
||||
- `imperative` — contains the framework-owned imperative interaction path.
|
||||
- `imperative.go` — implements framework-owned imperative interaction handling.
|
||||
- `interactive` — contains the framework-owned interactive interaction path.
|
||||
- `interactive.go` — implements framework-owned interactive interaction handling.
|
||||
- `router` — organizes the routing of framework-owned interactions to their appropriate interaction handling path.
|
||||
- `router.go` — implements the framework-owned interaction routing.
|
||||
- `hidden` — contains internal framework services that may be used within the essential layer but are not permitted to be called from the software layer, regardless of whether their functions are exported.
|
||||
- `service` — contains internal framework services available to the framework.
|
||||
- `management` — provides services for managing the lifecycle of the compiled binary.
|
||||
- `clean` — provides the compiled binary cleanup service.
|
||||
- `clean.go` — implements the compiled binary cleanup service.
|
||||
- `install` — provides the compiled binary installation service.
|
||||
- `install.go` — implements the compiled binary installation service.
|
||||
- `uninstall` — provides the compiled binary uninstallation service.
|
||||
- `uninstall.go` — implements the compiled binary uninstallation service.
|
||||
- `update` — provides the compiled binary update service.
|
||||
- `update.go` — implements the compiled binary update service.
|
||||
- `worker` — provides the automation layer for the compiled binary while keeping its automation configuration outside the binary.
|
||||
- `clock` — provides the clock syncronization service.
|
||||
- `clock.go` — implements the clock syncronization service.
|
||||
- `manager` — manages the workers responsible for executing automation.
|
||||
- `manager.go` — implements worker management.
|
||||
- `visible` — contains framework services that the software layer is permitted to call, providing reusable abstractions over framework capabilities.
|
||||
- `service` — contains reusable framework services available to the software layer.
|
||||
- `helper` — contains reusable helper services for common software needs.
|
||||
- `cipher` — provides reusable encryption and decryption functionality.
|
||||
- `cipher.go` — implements cipher functionality.
|
||||
- `codec` — provides reusable encoding and decoding functionality.
|
||||
- `codec.go` — implements codec functionality.
|
||||
- `datetime` — provides reusable date and time utilities.
|
||||
- `datetime.go` — implements date and time utilities.
|
||||
- `filesystem` — provides reusable filesystem utilities.
|
||||
- `filesystem.go` — implements filesystem utilities.
|
||||
- `hash` — provides reusable hashing functionality.
|
||||
- `hash.go` — implements hashing functionality.
|
||||
- `key` — provides reusable key-generation functionality.
|
||||
- `key.go` — implements key generation functionality.
|
||||
- `log` — provides reusable logging utilities.
|
||||
- `log.go` — implements logging utilities.
|
||||
- `marker` — provides generalized identifier-generation functionality for uses that require generated markers or identifiers.
|
||||
- `marker.go` — implements marker generation functionality.
|
||||
- `progress` — provides reusable progress tracking utilities.
|
||||
- `progress.go` — implements progress utilities.
|
||||
- `retriever` — provides a unified interface for accessing both framework-owned and software-owned data.
|
||||
- `retriever.go` — implements the unified data retrieval interface.
|
||||
- `status` — provides reusable status utilities.
|
||||
- `status.go` — implements status utilities.
|
||||
|
||||
### Operational
|
||||
|
||||
The `operational` directory contains the software-specific implementation and interaction components maintained by the software developer within the architecture provided by the framework.
|
||||
|
||||
- `operational` — contains the software-specific implementation and interaction components maintained by the software developer within the architecture provided by the framework.
|
||||
- `exchange` — serves as the registrar for software-specific interactions, allowing software services to register their interactive, imperative, and directive logic for composition by the runtime.
|
||||
- `directive` — contains the software-specific directive interaction path.
|
||||
- `directive.go` — implements software-specific directive interaction handling.
|
||||
- `imperative` — contains the software-specific imperative interaction path.
|
||||
- `imperative.go` — implements software-specific imperative interaction handling.
|
||||
- `interactive` — contains the software-specific interactive interaction path.
|
||||
- `interactive.go` — implements software-specific interactive interaction handling.
|
||||
- `router` — organizes the routing of software-specific interactions to their appropriate interaction handling path.
|
||||
- `router.go` — implements the software-specific interaction routing.
|
||||
- `mechanism` — contains the software-specific mechanisms through which application services and their features are implemented.
|
||||
- `service` — serves as the container for software-specific services.
|
||||
- `service` — placeholder for a software-specific service that is replaced with the actual service name.
|
||||
- `feature` — contains the features provided by the software-specific service.
|
||||
- `function.go` — implements a feature of the software-specific service.
|
||||
|
||||
### Run
|
||||
|
||||
The `run` directory provides the stable runtime entry and interaction composition layer for the compiled application.
|
||||
|
||||
- `run` — provides the stable runtime entry and interaction composition layer for the compiled application.
|
||||
- `exchange` — combines the registered framework-owned and software-owned interactions into the unified interaction structure used by the running application.
|
||||
- `directive` — receives and processes runtime directive interactions.
|
||||
- `directive.go` — implements runtime directive interaction handling.
|
||||
- `imperative` — receives and processes runtime imperative interactions.
|
||||
- `imperative.go` — implements runtime imperative interaction handling.
|
||||
- `interactive` — receives and processes runtime interactive interactions.
|
||||
- `interactive.go` — implements runtime interactive interaction handling.
|
||||
- `router` — routes incoming runtime interactions to the appropriate framework or software interaction path.
|
||||
- `router.go` — implements runtime interaction routing.
|
||||
- `main` — provides the executable entry point that initializes the required packages and starts the application without directly defining its menus, commands, flags, or arguments.
|
||||
- `main.go` — bootstraps the runtime and starts the compiled application.
|
||||
|
||||
## Project Files
|
||||
|
||||
The project root contains the following project-level files:
|
||||
|
||||
- `.gitignore` — defines files and directories that should be excluded from version control.
|
||||
- `CHANGELOG.md` — records changes made to the framework across versions.
|
||||
- `go.mod` — defines the Go module configuration and dependency context for the project.
|
||||
- `LICENSE.md` — defines the licensing terms governing the framework.
|
||||
- `Makefile` — provides build, update, and automation tasks for maintaining and operating the framework.
|
||||
- `README.md` — provides documentation describing the framework and its usage.
|
||||
|
||||
## Source Tree
|
||||
|
||||
The following structure represents the TUI source tree:
|
||||
|
||||
source
|
||||
├── data
|
||||
│ ├── cabinet
|
||||
│ │ └── cabinet.go
|
||||
│ ├── framework
|
||||
│ │ ├── ledger
|
||||
│ │ │ └── ledger.go
|
||||
│ │ └── metadata
|
||||
│ │ ├── handler.go
|
||||
│ │ └── resource.json
|
||||
│ └── software
|
||||
│ ├── ledger
|
||||
│ │ └── ledger.go
|
||||
│ └── metadata
|
||||
│ ├── handler.go
|
||||
│ └── resource.json
|
||||
├── essential
|
||||
│ ├── exchange
|
||||
│ │ ├── directive
|
||||
│ │ │ └── directive.go
|
||||
│ │ ├── imperative
|
||||
│ │ │ └── imperative.go
|
||||
│ │ ├── interactive
|
||||
│ │ │ └── interactive.go
|
||||
│ │ └── router
|
||||
│ │ └── router.go
|
||||
│ ├── hidden
|
||||
│ │ └── service
|
||||
│ │ ├── management
|
||||
│ │ │ ├── clean
|
||||
│ │ │ │ └── clean.go
|
||||
│ │ │ ├── install
|
||||
│ │ │ │ └── install.go
|
||||
│ │ │ ├── uninstall
|
||||
│ │ │ │ └── uninstall.go
|
||||
│ │ │ └── update
|
||||
│ │ │ └── update.go
|
||||
│ │ └── worker
|
||||
│ │ ├── clock
|
||||
│ │ │ └── clock.go
|
||||
│ │ └── manager
|
||||
│ │ └── manager.go
|
||||
│ └── visible
|
||||
│ └── service
|
||||
│ └── helper
|
||||
│ ├── cipher
|
||||
│ │ └── cipher.go
|
||||
│ ├── codec
|
||||
│ │ └── codec.go
|
||||
│ ├── datetime
|
||||
│ │ └── datetime.go
|
||||
│ ├── filesystem
|
||||
│ │ └── filesystem.go
|
||||
│ ├── hash
|
||||
│ │ └── hash.go
|
||||
│ ├── key
|
||||
│ │ └── key.go
|
||||
│ ├── log
|
||||
│ │ └── log.go
|
||||
│ ├── marker
|
||||
│ │ └── marker.go
|
||||
│ ├── progress
|
||||
│ │ └── progress.go
|
||||
│ ├── retriever
|
||||
│ │ └── retriever.go
|
||||
│ └── status
|
||||
│ └── status.go
|
||||
├── operational
|
||||
│ ├── exchange
|
||||
│ │ ├── directive
|
||||
│ │ │ └── directive.go
|
||||
│ │ ├── imperative
|
||||
│ │ │ └── imperative.go
|
||||
│ │ ├── interactive
|
||||
│ │ │ └── interactive.go
|
||||
│ │ └── router
|
||||
│ │ └── router.go
|
||||
│ └── mechanism
|
||||
│ └── service
|
||||
│ └── service
|
||||
│ └── feature
|
||||
│ └── function.go
|
||||
├── run
|
||||
│ ├── exchange
|
||||
│ │ ├── directive
|
||||
│ │ │ └── directive.go
|
||||
│ │ ├── imperative
|
||||
│ │ │ └── imperative.go
|
||||
│ │ ├── interactive
|
||||
│ │ │ └── interactive.go
|
||||
│ │ └── router
|
||||
│ │ └── router.go
|
||||
│ └── main
|
||||
│ └── main.go
|
||||
├── .gitignore
|
||||
├── CHANGELOG.md
|
||||
├── go.mod
|
||||
├── LICENSE.md
|
||||
├── Makefile
|
||||
└── README.md
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Description
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Name:
|
||||
| - Cabinet
|
||||
|
|
||||
| Purpose:
|
||||
| - Provides a centralized coordination point for framework-owned and
|
||||
| software-owned data ledgers, determining the appropriate ledger for
|
||||
| resource registration and access.
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Credit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Contributor:
|
||||
| - Rajon Ahmed
|
||||
| - Blockonite
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
package cabinet
|
||||
|
||||
import (
|
||||
_ "go/module/data/framework/ledger"
|
||||
_ "go/module/data/software/ledger"
|
||||
)
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Description
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Name:
|
||||
| - Ledger
|
||||
|
|
||||
| Purpose:
|
||||
| - Registers framework-owned resources and resolves each resource to
|
||||
| its responsible handler.
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Credit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Contributor:
|
||||
| - Rajon Ahmed
|
||||
| - Blockonite
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
package ledger
|
||||
|
||||
import (
|
||||
"go/module/data/framework/metadata"
|
||||
// Additional framework resources.
|
||||
// e.g.: "go/module/data/framework/glossary"
|
||||
)
|
||||
|
||||
// Initialize the framework resource ledger.
|
||||
func init() {
|
||||
obrimLedger()
|
||||
}
|
||||
|
||||
// Register all framework resource handlers.
|
||||
func obrimLedger() {
|
||||
obrimLedgerRegister()
|
||||
}
|
||||
|
||||
// Register all framework handlers with Retriever.
|
||||
func obrimLedgerRegister() {
|
||||
// Framework metadata resource.
|
||||
metadata.ObrimHandlerJson()
|
||||
|
||||
// Additional framework resources.
|
||||
// e.g.: glossary.ObrimHandlerJson()
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Description
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Name:
|
||||
| - Handler
|
||||
|
|
||||
| Purpose:
|
||||
| - Provides access to embedded framework metadata resources through
|
||||
| resource-specific retrieval and parsing logic.
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Credit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Contributor:
|
||||
| - Rajon Ahmed
|
||||
| - Blockonite
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"go/module/essential/visible/service/helper/retriever"
|
||||
)
|
||||
|
||||
// ObrimHandlerResource is the registered JSON resource identifier.
|
||||
const ObrimHandlerResource = "framework/metadata"
|
||||
|
||||
// Success result codes.
|
||||
const (
|
||||
ObrimHandlerCodeSuccessLoaded = "SUCCESS_RESOURCE_LOADED"
|
||||
)
|
||||
|
||||
// Failure result codes.
|
||||
const (
|
||||
ObrimHandlerCodeFailureNotFound = "FAILURE_RESOURCE_NOT_FOUND"
|
||||
ObrimHandlerCodeFailureLoad = "FAILURE_RESOURCE_LOAD"
|
||||
ObrimHandlerCodeFailureParse = "FAILURE_RESOURCE_PARSE"
|
||||
)
|
||||
|
||||
// handlerResourceFile is the embedded JSON resource filename.
|
||||
const handlerResourceFile = "resource.json"
|
||||
|
||||
// handlerEmbeddedResource stores the embedded JSON resource.
|
||||
//
|
||||
//go:embed resource.json
|
||||
var handlerEmbeddedResource embed.FS
|
||||
|
||||
// obrimHandlerCacheMutex protects the resource cache.
|
||||
var obrimHandlerCacheMutex sync.RWMutex
|
||||
|
||||
// obrimHandlerCachedResource stores the parsed resource for the application lifetime.
|
||||
var obrimHandlerCachedResource map[string]any
|
||||
|
||||
// ObrimHandlerPayload defines the successful payload structure.
|
||||
type ObrimHandlerPayload struct {
|
||||
Resource string `json:"resource"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
|
||||
// ObrimHandlerOutput defines the standardized utility output.
|
||||
type ObrimHandlerOutput struct {
|
||||
Status bool `json:"status"`
|
||||
Code string `json:"code"`
|
||||
Payload *ObrimHandlerPayload `json:"payload"`
|
||||
}
|
||||
|
||||
// ObrimHandlerJson registers the JSON handler callback with Retriever.
|
||||
func ObrimHandlerJson() {
|
||||
retriever.ObrimRetrieverJsonRegister(
|
||||
ObrimHandlerResource,
|
||||
obrimHandlerFetch,
|
||||
)
|
||||
}
|
||||
|
||||
// obrimHandler is the internal handler entry function.
|
||||
func obrimHandler() ObrimHandlerOutput {
|
||||
return obrimHandlerFetch()
|
||||
}
|
||||
|
||||
// obrimHandlerFetch loads, parses, caches, and returns the complete JSON resource.
|
||||
func obrimHandlerFetch() ObrimHandlerOutput {
|
||||
if cached, ok := obrimHandlerCache(); ok {
|
||||
return obrimHandlerOutput(
|
||||
true,
|
||||
ObrimHandlerCodeSuccessLoaded,
|
||||
cached,
|
||||
)
|
||||
}
|
||||
|
||||
data, err := obrimHandlerLoad()
|
||||
if err != nil {
|
||||
return obrimHandlerOutput(
|
||||
false,
|
||||
ObrimHandlerCodeFailureLoad,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
resource, err := obrimHandlerParse(data)
|
||||
if err != nil {
|
||||
return obrimHandlerOutput(
|
||||
false,
|
||||
ObrimHandlerCodeFailureParse,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
obrimHandlerCache(resource)
|
||||
|
||||
return obrimHandlerOutput(
|
||||
true,
|
||||
ObrimHandlerCodeSuccessLoaded,
|
||||
resource,
|
||||
)
|
||||
}
|
||||
|
||||
// obrimHandlerCache retrieves or stores the parsed JSON resource cache.
|
||||
func obrimHandlerCache(resource ...map[string]any) (map[string]any, bool) {
|
||||
obrimHandlerCacheMutex.Lock()
|
||||
defer obrimHandlerCacheMutex.Unlock()
|
||||
|
||||
switch len(resource) {
|
||||
case 0:
|
||||
if obrimHandlerCachedResource == nil {
|
||||
return nil, false
|
||||
}
|
||||
return obrimHandlerCachedResource, true
|
||||
|
||||
default:
|
||||
obrimHandlerCachedResource = resource[0]
|
||||
return obrimHandlerCachedResource, true
|
||||
}
|
||||
}
|
||||
|
||||
// obrimHandlerEmbed returns the embedded JSON resource filesystem.
|
||||
func obrimHandlerEmbed() embed.FS {
|
||||
return handlerEmbeddedResource
|
||||
}
|
||||
|
||||
// obrimHandlerLoad loads the embedded JSON resource.
|
||||
func obrimHandlerLoad() ([]byte, error) {
|
||||
return obrimHandlerEmbed().ReadFile(handlerResourceFile)
|
||||
}
|
||||
|
||||
// obrimHandlerParse parses the embedded JSON resource.
|
||||
func obrimHandlerParse(data []byte) (map[string]any, error) {
|
||||
resource := make(map[string]any)
|
||||
|
||||
if err := json.Unmarshal(data, &resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// obrimHandlerOutput builds the standardized utility output.
|
||||
func obrimHandlerOutput(
|
||||
status bool,
|
||||
code string,
|
||||
resource map[string]any,
|
||||
) ObrimHandlerOutput {
|
||||
|
||||
if !status || resource == nil {
|
||||
return ObrimHandlerOutput{
|
||||
Status: false,
|
||||
Code: code,
|
||||
Payload: nil,
|
||||
}
|
||||
}
|
||||
|
||||
return ObrimHandlerOutput{
|
||||
Status: true,
|
||||
Code: code,
|
||||
Payload: &ObrimHandlerPayload{
|
||||
Resource: ObrimHandlerResource,
|
||||
Data: resource,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"framework": {
|
||||
"regular": "Framework Regular",
|
||||
"lower": "frameworklower",
|
||||
"upper": "FRAMEWORKUPPER",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Description
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Name:
|
||||
| - Ledger
|
||||
|
|
||||
| Purpose:
|
||||
| - Registers software-owned resources and resolves each resource to
|
||||
| its responsible handler.
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Credit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Contributor:
|
||||
| - Rajon Ahmed
|
||||
| - Blockonite
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
package ledger
|
||||
|
||||
import (
|
||||
"go/module/data/software/metadata"
|
||||
// Additional software resources.
|
||||
// e.g.: "go/module/data/software/glossary"
|
||||
)
|
||||
|
||||
// Initialize the software resource ledger.
|
||||
func init() {
|
||||
obrimLedger()
|
||||
}
|
||||
|
||||
// Register all software resource handlers.
|
||||
func obrimLedger() {
|
||||
obrimLedgerRegister()
|
||||
}
|
||||
|
||||
// Register all software handlers with Retriever.
|
||||
func obrimLedgerRegister() {
|
||||
// software metadata resource.
|
||||
metadata.ObrimHandlerJson()
|
||||
|
||||
// Additional software resources.
|
||||
// e.g.: glossary.ObrimHandlerJson()
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Description
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Name:
|
||||
| - Handler
|
||||
|
|
||||
| Purpose:
|
||||
| - Provides access to embedded software metadata resources through
|
||||
| resource-specific retrieval and parsing logic.
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Credit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Contributor:
|
||||
| - Rajon Ahmed
|
||||
| - Blockonite
|
||||
|
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"go/module/essential/visible/service/helper/retriever"
|
||||
)
|
||||
|
||||
// ObrimHandlerResource is the registered JSON resource identifier.
|
||||
const ObrimHandlerResource = "framework/metadata"
|
||||
|
||||
// Success result codes.
|
||||
const (
|
||||
ObrimHandlerCodeSuccessLoaded = "SUCCESS_RESOURCE_LOADED"
|
||||
)
|
||||
|
||||
// Failure result codes.
|
||||
const (
|
||||
ObrimHandlerCodeFailureNotFound = "FAILURE_RESOURCE_NOT_FOUND"
|
||||
ObrimHandlerCodeFailureLoad = "FAILURE_RESOURCE_LOAD"
|
||||
ObrimHandlerCodeFailureParse = "FAILURE_RESOURCE_PARSE"
|
||||
)
|
||||
|
||||
// handlerResourceFile is the embedded JSON resource filename.
|
||||
const handlerResourceFile = "resource.json"
|
||||
|
||||
// handlerEmbeddedResource stores the embedded JSON resource.
|
||||
//
|
||||
//go:embed resource.json
|
||||
var handlerEmbeddedResource embed.FS
|
||||
|
||||
// obrimHandlerCacheMutex protects the resource cache.
|
||||
var obrimHandlerCacheMutex sync.RWMutex
|
||||
|
||||
// obrimHandlerCachedResource stores the parsed resource for the application lifetime.
|
||||
var obrimHandlerCachedResource map[string]any
|
||||
|
||||
// ObrimHandlerPayload defines the successful payload structure.
|
||||
type ObrimHandlerPayload struct {
|
||||
Resource string `json:"resource"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
|
||||
// ObrimHandlerOutput defines the standardized utility output.
|
||||
type ObrimHandlerOutput struct {
|
||||
Status bool `json:"status"`
|
||||
Code string `json:"code"`
|
||||
Payload *ObrimHandlerPayload `json:"payload"`
|
||||
}
|
||||
|
||||
// ObrimHandlerJson registers the JSON handler callback with Retriever.
|
||||
func ObrimHandlerJson() {
|
||||
retriever.ObrimRetrieverJsonRegister(
|
||||
ObrimHandlerResource,
|
||||
obrimHandlerFetch,
|
||||
)
|
||||
}
|
||||
|
||||
// obrimHandler is the internal handler entry function.
|
||||
func obrimHandler() ObrimHandlerOutput {
|
||||
return obrimHandlerFetch()
|
||||
}
|
||||
|
||||
// obrimHandlerFetch loads, parses, caches, and returns the complete JSON resource.
|
||||
func obrimHandlerFetch() ObrimHandlerOutput {
|
||||
if cached, ok := obrimHandlerCache(); ok {
|
||||
return obrimHandlerOutput(
|
||||
true,
|
||||
ObrimHandlerCodeSuccessLoaded,
|
||||
cached,
|
||||
)
|
||||
}
|
||||
|
||||
data, err := obrimHandlerLoad()
|
||||
if err != nil {
|
||||
return obrimHandlerOutput(
|
||||
false,
|
||||
ObrimHandlerCodeFailureLoad,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
resource, err := obrimHandlerParse(data)
|
||||
if err != nil {
|
||||
return obrimHandlerOutput(
|
||||
false,
|
||||
ObrimHandlerCodeFailureParse,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
obrimHandlerCache(resource)
|
||||
|
||||
return obrimHandlerOutput(
|
||||
true,
|
||||
ObrimHandlerCodeSuccessLoaded,
|
||||
resource,
|
||||
)
|
||||
}
|
||||
|
||||
// obrimHandlerCache retrieves or stores the parsed JSON resource cache.
|
||||
func obrimHandlerCache(resource ...map[string]any) (map[string]any, bool) {
|
||||
obrimHandlerCacheMutex.Lock()
|
||||
defer obrimHandlerCacheMutex.Unlock()
|
||||
|
||||
switch len(resource) {
|
||||
case 0:
|
||||
if obrimHandlerCachedResource == nil {
|
||||
return nil, false
|
||||
}
|
||||
return obrimHandlerCachedResource, true
|
||||
|
||||
default:
|
||||
obrimHandlerCachedResource = resource[0]
|
||||
return obrimHandlerCachedResource, true
|
||||
}
|
||||
}
|
||||
|
||||
// obrimHandlerEmbed returns the embedded JSON resource filesystem.
|
||||
func obrimHandlerEmbed() embed.FS {
|
||||
return handlerEmbeddedResource
|
||||
}
|
||||
|
||||
// obrimHandlerLoad loads the embedded JSON resource.
|
||||
func obrimHandlerLoad() ([]byte, error) {
|
||||
return obrimHandlerEmbed().ReadFile(handlerResourceFile)
|
||||
}
|
||||
|
||||
// obrimHandlerParse parses the embedded JSON resource.
|
||||
func obrimHandlerParse(data []byte) (map[string]any, error) {
|
||||
resource := make(map[string]any)
|
||||
|
||||
if err := json.Unmarshal(data, &resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// obrimHandlerOutput builds the standardized utility output.
|
||||
func obrimHandlerOutput(
|
||||
status bool,
|
||||
code string,
|
||||
resource map[string]any,
|
||||
) ObrimHandlerOutput {
|
||||
|
||||
if !status || resource == nil {
|
||||
return ObrimHandlerOutput{
|
||||
Status: false,
|
||||
Code: code,
|
||||
Payload: nil,
|
||||
}
|
||||
}
|
||||
|
||||
return ObrimHandlerOutput{
|
||||
Status: true,
|
||||
Code: code,
|
||||
Payload: &ObrimHandlerPayload{
|
||||
Resource: ObrimHandlerResource,
|
||||
Data: resource,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"software": {
|
||||
"regular": "Software Regular",
|
||||
"lower": "softwarelower",
|
||||
"upper": "SOFTWAREUPPER",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
@ -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,
|
||||
}, ""
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user