2025-12-22 13:37:57 +08:00
|
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2025-12-23 18:59:43 +08:00
|
|
|
|
"go.yandata.net/iod/iod/go-trustlog/api/logger"
|
|
|
|
|
|
"go.yandata.net/iod/iod/go-trustlog/internal/helpers"
|
2025-12-22 13:37:57 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 操作来源类型 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// Source 表示操作来源,用于区分不同系统模块(IRP、DOIP)。
|
|
|
|
|
|
type Source string
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
OpSourceIRP Source = "IRP"
|
|
|
|
|
|
OpSourceDOIP Source = "DOIP"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
//
|
2025-12-26 13:47:55 +08:00
|
|
|
|
// ===== 操作代码枚举 (OpCode) =====
|
2025-12-22 13:37:57 +08:00
|
|
|
|
//
|
|
|
|
|
|
|
2025-12-26 13:47:55 +08:00
|
|
|
|
// OpCode 表示操作的具体代码(int32类型)
|
|
|
|
|
|
type OpCode int32
|
2025-12-22 13:37:57 +08:00
|
|
|
|
|
2025-12-26 13:47:55 +08:00
|
|
|
|
// 标准 Handle System 操作代码
|
2025-12-22 13:37:57 +08:00
|
|
|
|
const (
|
2025-12-26 13:47:55 +08:00
|
|
|
|
OpCodeReserved OpCode = 0 // Reserved
|
|
|
|
|
|
OpCodeResolution OpCode = 1 // Identifier query
|
|
|
|
|
|
OpCodeGetSiteInfo OpCode = 2 // Get HS_SITE element
|
|
|
|
|
|
|
|
|
|
|
|
OpCodeCreateID OpCode = 100 // Create new identifier
|
|
|
|
|
|
OpCodeDeleteID OpCode = 101 // Delete existing identifier
|
|
|
|
|
|
OpCodeAddElement OpCode = 102 // Add element(s)
|
|
|
|
|
|
OpCodeRemoveElement OpCode = 103 // Remove element(s)
|
|
|
|
|
|
OpCodeModifyElement OpCode = 104 // Modify element(s)
|
|
|
|
|
|
OpCodeListIDs OpCode = 105 // List identifiers
|
|
|
|
|
|
OpCodeListDerivedPrefixes OpCode = 106 // List derived prefixes
|
|
|
|
|
|
|
|
|
|
|
|
OpCodeChallengeResponse OpCode = 200 // Response to challenge
|
|
|
|
|
|
OpCodeVerifyResponse OpCode = 201 // Verify challenge response
|
|
|
|
|
|
|
|
|
|
|
|
OpCodeHomePrefix OpCode = 300 // Home prefix
|
|
|
|
|
|
OpCodeUnhomePrefix OpCode = 301 // Unhome prefix
|
|
|
|
|
|
OpCodeListHomedPrefixes OpCode = 302 // List homed prefixes
|
|
|
|
|
|
|
|
|
|
|
|
OpCodeSessionSetup OpCode = 400 // Session setup request
|
|
|
|
|
|
OpCodeSessionTerminate OpCode = 401 // Session termination request
|
|
|
|
|
|
|
|
|
|
|
|
// Yandata 扩展操作代码
|
|
|
|
|
|
OpCodeQueryIDs OpCode = 500 // Query DOIDs
|
|
|
|
|
|
OpCodeRenameID OpCode = 501 // Rename DOID
|
|
|
|
|
|
OpCodeResolveAltID OpCode = 502 // Resolve by alternative ID
|
|
|
|
|
|
OpCodeRegisterAltID OpCode = 503 // Register alternative ID
|
2025-12-22 13:37:57 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-26 13:47:55 +08:00
|
|
|
|
// OpCodeName 返回操作代码的名称
|
|
|
|
|
|
func (c OpCode) String() string {
|
|
|
|
|
|
switch c {
|
|
|
|
|
|
case OpCodeReserved:
|
|
|
|
|
|
return "RESERVED"
|
|
|
|
|
|
case OpCodeResolution:
|
|
|
|
|
|
return "RESOLUTION"
|
|
|
|
|
|
case OpCodeGetSiteInfo:
|
|
|
|
|
|
return "GET_SITEINFO"
|
|
|
|
|
|
case OpCodeCreateID:
|
|
|
|
|
|
return "CREATE_ID"
|
|
|
|
|
|
case OpCodeDeleteID:
|
|
|
|
|
|
return "DELETE_ID"
|
|
|
|
|
|
case OpCodeAddElement:
|
|
|
|
|
|
return "ADD_ELEMENT"
|
|
|
|
|
|
case OpCodeRemoveElement:
|
|
|
|
|
|
return "REMOVE_ELEMENT"
|
|
|
|
|
|
case OpCodeModifyElement:
|
|
|
|
|
|
return "MODIFY_ELEMENT"
|
|
|
|
|
|
case OpCodeListIDs:
|
|
|
|
|
|
return "LIST_IDS"
|
|
|
|
|
|
case OpCodeListDerivedPrefixes:
|
|
|
|
|
|
return "LIST_DERIVED_PREFIXES"
|
|
|
|
|
|
case OpCodeChallengeResponse:
|
|
|
|
|
|
return "CHALLENGE_RESPONSE"
|
|
|
|
|
|
case OpCodeVerifyResponse:
|
|
|
|
|
|
return "VERIFY_RESPONSE"
|
|
|
|
|
|
case OpCodeHomePrefix:
|
|
|
|
|
|
return "HOME_PREFIX"
|
|
|
|
|
|
case OpCodeUnhomePrefix:
|
|
|
|
|
|
return "UNHOME_PREFIX"
|
|
|
|
|
|
case OpCodeListHomedPrefixes:
|
|
|
|
|
|
return "LIST_HOMED_PREFIXES"
|
|
|
|
|
|
case OpCodeSessionSetup:
|
|
|
|
|
|
return "SESSION_SETUP"
|
|
|
|
|
|
case OpCodeSessionTerminate:
|
|
|
|
|
|
return "SESSION_TERMINATE"
|
|
|
|
|
|
case OpCodeQueryIDs:
|
|
|
|
|
|
return "QUERY_IDS"
|
|
|
|
|
|
case OpCodeRenameID:
|
|
|
|
|
|
return "RENAME_ID"
|
|
|
|
|
|
case OpCodeResolveAltID:
|
|
|
|
|
|
return "RESOLVE_ALT_ID"
|
|
|
|
|
|
case OpCodeRegisterAltID:
|
|
|
|
|
|
return "REGISTER_ALT_ID"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return fmt.Sprintf("UNKNOWN(%d)", c)
|
|
|
|
|
|
}
|
2025-12-22 13:37:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 13:47:55 +08:00
|
|
|
|
// IsValid 检查操作代码是否有效
|
|
|
|
|
|
func (c OpCode) IsValid() bool {
|
|
|
|
|
|
validCodes := []OpCode{
|
|
|
|
|
|
OpCodeReserved, OpCodeResolution, OpCodeGetSiteInfo,
|
|
|
|
|
|
OpCodeCreateID, OpCodeDeleteID, OpCodeAddElement,
|
|
|
|
|
|
OpCodeRemoveElement, OpCodeModifyElement, OpCodeListIDs,
|
|
|
|
|
|
OpCodeListDerivedPrefixes,
|
|
|
|
|
|
OpCodeChallengeResponse, OpCodeVerifyResponse,
|
|
|
|
|
|
OpCodeHomePrefix, OpCodeUnhomePrefix, OpCodeListHomedPrefixes,
|
|
|
|
|
|
OpCodeSessionSetup, OpCodeSessionTerminate,
|
|
|
|
|
|
OpCodeQueryIDs, OpCodeRenameID, OpCodeResolveAltID, OpCodeRegisterAltID,
|
|
|
|
|
|
}
|
2025-12-22 13:37:57 +08:00
|
|
|
|
|
2025-12-26 13:47:55 +08:00
|
|
|
|
for _, valid := range validCodes {
|
|
|
|
|
|
if c == valid {
|
2025-12-22 13:37:57 +08:00
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 操作记录结构 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// Operation 表示一次完整的操作记录。
|
|
|
|
|
|
// 用于记录系统中的操作行为,包含操作元数据、数据标识、操作者信息以及请求/响应的哈希值。
|
|
|
|
|
|
type Operation struct {
|
2025-12-23 18:59:43 +08:00
|
|
|
|
OpID string `json:"opId" validate:"max=32"`
|
|
|
|
|
|
Timestamp time.Time `json:"timestamp" validate:"required"`
|
|
|
|
|
|
OpSource Source `json:"opSource" validate:"required,oneof=IRP DOIP"`
|
2025-12-26 13:47:55 +08:00
|
|
|
|
OpCode OpCode `json:"opCode" validate:"required"`
|
2025-12-23 18:59:43 +08:00
|
|
|
|
DoPrefix string `json:"doPrefix" validate:"required,max=512"`
|
|
|
|
|
|
DoRepository string `json:"doRepository" validate:"required,max=512"`
|
|
|
|
|
|
Doid string `json:"doid" validate:"required,max=512"`
|
|
|
|
|
|
ProducerID string `json:"producerId" validate:"required,max=512"`
|
|
|
|
|
|
OpActor string `json:"opActor" validate:"max=64"`
|
|
|
|
|
|
RequestBodyHash *string `json:"requestBodyHash" validate:"omitempty,max=128"`
|
|
|
|
|
|
ResponseBodyHash *string `json:"responseBodyHash" validate:"omitempty,max=128"`
|
|
|
|
|
|
// ClientIP 客户端IP地址,仅用于数据库持久化,不参与存证哈希计算
|
|
|
|
|
|
ClientIP *string `json:"clientIp,omitempty" validate:"omitempty,max=32"`
|
|
|
|
|
|
// ServerIP 服务端IP地址,仅用于数据库持久化,不参与存证哈希计算
|
|
|
|
|
|
ServerIP *string `json:"serverIp,omitempty" validate:"omitempty,max=32"`
|
|
|
|
|
|
Ack func() bool `json:"-"`
|
|
|
|
|
|
Nack func() bool `json:"-"`
|
|
|
|
|
|
binary []byte
|
2025-12-22 13:37:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 构造函数 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// NewFullOperation 创建包含所有字段的完整 Operation。
|
|
|
|
|
|
// 自动完成哈希计算和字段校验,确保创建的 Operation 是完整且有效的。
|
|
|
|
|
|
func NewFullOperation(
|
|
|
|
|
|
opSource Source,
|
2025-12-26 13:47:55 +08:00
|
|
|
|
opCode OpCode,
|
2025-12-22 13:37:57 +08:00
|
|
|
|
doPrefix, doRepository, doid string,
|
|
|
|
|
|
producerID string,
|
|
|
|
|
|
opActor string,
|
|
|
|
|
|
requestBody, responseBody interface{},
|
|
|
|
|
|
timestamp time.Time,
|
|
|
|
|
|
) (*Operation, error) {
|
|
|
|
|
|
log := logger.GetGlobalLogger()
|
|
|
|
|
|
log.Debug("Creating new full operation",
|
|
|
|
|
|
"opSource", opSource,
|
2025-12-26 13:47:55 +08:00
|
|
|
|
"opCode", opCode,
|
2025-12-22 13:37:57 +08:00
|
|
|
|
"doPrefix", doPrefix,
|
|
|
|
|
|
"doRepository", doRepository,
|
|
|
|
|
|
"doid", doid,
|
|
|
|
|
|
"producerID", producerID,
|
|
|
|
|
|
"opActor", opActor,
|
|
|
|
|
|
)
|
|
|
|
|
|
op := &Operation{
|
|
|
|
|
|
Timestamp: timestamp,
|
|
|
|
|
|
OpSource: opSource,
|
2025-12-26 13:47:55 +08:00
|
|
|
|
OpCode: opCode,
|
2025-12-22 13:37:57 +08:00
|
|
|
|
DoPrefix: doPrefix,
|
|
|
|
|
|
DoRepository: doRepository,
|
|
|
|
|
|
Doid: doid,
|
|
|
|
|
|
ProducerID: producerID,
|
|
|
|
|
|
OpActor: opActor,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Setting request body hash")
|
|
|
|
|
|
if err := op.RequestBodyFlexible(requestBody); err != nil {
|
|
|
|
|
|
log.Error("Failed to set request body hash",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Debug("Setting response body hash")
|
|
|
|
|
|
if err := op.ResponseBodyFlexible(responseBody); err != nil {
|
|
|
|
|
|
log.Error("Failed to set response body hash",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
log.Debug("Checking and initializing operation")
|
|
|
|
|
|
if err := op.CheckAndInit(); err != nil {
|
|
|
|
|
|
log.Error("Failed to check and init operation",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Full operation created successfully",
|
|
|
|
|
|
"opID", op.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return op, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 接口实现 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
func (o *Operation) Key() string {
|
|
|
|
|
|
return o.OpID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// OperationHashData 实现 HashData 接口,用于存储 Operation 的哈希计算结果。
|
|
|
|
|
|
type OperationHashData struct {
|
|
|
|
|
|
key string
|
|
|
|
|
|
hash string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o OperationHashData) Key() string {
|
|
|
|
|
|
return o.key
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o OperationHashData) Hash() string {
|
|
|
|
|
|
return o.hash
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o OperationHashData) Type() HashType {
|
|
|
|
|
|
return Sha256Simd
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DoHash 计算 Operation 的整体哈希值,用于数据完整性验证。
|
|
|
|
|
|
// 哈希基于序列化后的二进制数据计算,确保操作记录的不可篡改性。
|
|
|
|
|
|
func (o *Operation) DoHash(_ context.Context) (HashData, error) {
|
|
|
|
|
|
log := logger.GetGlobalLogger()
|
|
|
|
|
|
log.Debug("Computing hash for operation",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
hashTool := GetHashTool(Sha256Simd)
|
|
|
|
|
|
binary, err := o.MarshalBinary()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Error("Failed to marshal operation for hash",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, fmt.Errorf("failed to marshal operation: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Computing hash bytes",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
"binaryLength", len(binary),
|
|
|
|
|
|
)
|
|
|
|
|
|
hash, err := hashTool.HashBytes(binary)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Error("Failed to compute hash",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, fmt.Errorf("failed to compute hash: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Hash computed successfully",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
"hash", hash,
|
|
|
|
|
|
)
|
|
|
|
|
|
return OperationHashData{
|
|
|
|
|
|
key: o.OpID,
|
|
|
|
|
|
hash: hash,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== CBOR 序列化相关 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// operationData 用于 CBOR 序列化/反序列化的中间结构。
|
|
|
|
|
|
// 排除函数字段和缓存字段,仅包含可序列化的数据字段。
|
|
|
|
|
|
type operationData struct {
|
|
|
|
|
|
OpID *string `cbor:"opId"`
|
|
|
|
|
|
Timestamp *time.Time `cbor:"timestamp"`
|
|
|
|
|
|
OpSource *Source `cbor:"opSource"`
|
2025-12-26 13:47:55 +08:00
|
|
|
|
OpCode *OpCode `cbor:"opCode"`
|
2025-12-22 13:37:57 +08:00
|
|
|
|
DoPrefix *string `cbor:"doPrefix"`
|
|
|
|
|
|
DoRepository *string `cbor:"doRepository"`
|
|
|
|
|
|
Doid *string `cbor:"doid"`
|
|
|
|
|
|
ProducerID *string `cbor:"producerId"`
|
|
|
|
|
|
OpActor *string `cbor:"opActor"`
|
|
|
|
|
|
RequestBodyHash *string `cbor:"requestBodyHash"`
|
|
|
|
|
|
ResponseBodyHash *string `cbor:"responseBodyHash"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// toOperationData 将 Operation 转换为 operationData,用于序列化。
|
|
|
|
|
|
func (o *Operation) toOperationData() *operationData {
|
|
|
|
|
|
return &operationData{
|
|
|
|
|
|
OpID: &o.OpID,
|
|
|
|
|
|
Timestamp: &o.Timestamp,
|
|
|
|
|
|
OpSource: &o.OpSource,
|
2025-12-26 13:47:55 +08:00
|
|
|
|
OpCode: &o.OpCode,
|
2025-12-22 13:37:57 +08:00
|
|
|
|
DoPrefix: &o.DoPrefix,
|
|
|
|
|
|
DoRepository: &o.DoRepository,
|
|
|
|
|
|
Doid: &o.Doid,
|
|
|
|
|
|
ProducerID: &o.ProducerID,
|
|
|
|
|
|
OpActor: &o.OpActor,
|
|
|
|
|
|
RequestBodyHash: o.RequestBodyHash,
|
|
|
|
|
|
ResponseBodyHash: o.ResponseBodyHash,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// fromOperationData 从 operationData 填充 Operation,用于反序列化。
|
|
|
|
|
|
func (o *Operation) fromOperationData(opData *operationData) {
|
|
|
|
|
|
if opData == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if opData.OpID != nil {
|
|
|
|
|
|
o.OpID = *opData.OpID
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.Timestamp != nil {
|
|
|
|
|
|
o.Timestamp = *opData.Timestamp
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.OpSource != nil {
|
|
|
|
|
|
o.OpSource = *opData.OpSource
|
|
|
|
|
|
}
|
2025-12-26 13:47:55 +08:00
|
|
|
|
if opData.OpCode != nil {
|
|
|
|
|
|
o.OpCode = *opData.OpCode
|
2025-12-22 13:37:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
if opData.DoPrefix != nil {
|
|
|
|
|
|
o.DoPrefix = *opData.DoPrefix
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.DoRepository != nil {
|
|
|
|
|
|
o.DoRepository = *opData.DoRepository
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.Doid != nil {
|
|
|
|
|
|
o.Doid = *opData.Doid
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.ProducerID != nil {
|
|
|
|
|
|
o.ProducerID = *opData.ProducerID
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.OpActor != nil {
|
|
|
|
|
|
o.OpActor = *opData.OpActor
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.RequestBodyHash != nil {
|
|
|
|
|
|
hash := *opData.RequestBodyHash
|
|
|
|
|
|
o.RequestBodyHash = &hash
|
|
|
|
|
|
}
|
|
|
|
|
|
if opData.ResponseBodyHash != nil {
|
|
|
|
|
|
hash := *opData.ResponseBodyHash
|
|
|
|
|
|
o.ResponseBodyHash = &hash
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MarshalBinary 将 Operation 序列化为 CBOR 格式的二进制数据。
|
|
|
|
|
|
// 实现 encoding.BinaryMarshaler 接口。
|
|
|
|
|
|
// 使用 Canonical CBOR 编码确保序列化结果的一致性,使用缓存机制避免重复序列化。
|
|
|
|
|
|
func (o *Operation) MarshalBinary() ([]byte, error) {
|
|
|
|
|
|
log := logger.GetGlobalLogger()
|
|
|
|
|
|
log.Debug("Marshaling operation to CBOR binary",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
if o.binary != nil {
|
|
|
|
|
|
log.Debug("Using cached binary data",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return o.binary, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
opData := o.toOperationData()
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Marshaling operation data to canonical CBOR",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
binary, err := helpers.MarshalCanonical(opData)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Error("Failed to marshal operation to CBOR",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil, fmt.Errorf("failed to marshal operation to CBOR: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
o.binary = binary
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Operation marshaled successfully",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
"binaryLength", len(binary),
|
|
|
|
|
|
)
|
|
|
|
|
|
return binary, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetProducerID 返回 ProducerID,实现 Trustlog 接口。
|
|
|
|
|
|
func (o *Operation) GetProducerID() string {
|
|
|
|
|
|
return o.ProducerID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UnmarshalBinary 从 CBOR 格式的二进制数据反序列化为 Operation。
|
|
|
|
|
|
// 实现 encoding.BinaryUnmarshaler 接口。
|
|
|
|
|
|
func (o *Operation) UnmarshalBinary(data []byte) error {
|
|
|
|
|
|
log := logger.GetGlobalLogger()
|
|
|
|
|
|
log.Debug("Unmarshaling operation from CBOR binary",
|
|
|
|
|
|
"dataLength", len(data),
|
|
|
|
|
|
)
|
|
|
|
|
|
if len(data) == 0 {
|
|
|
|
|
|
log.Error("Data is empty")
|
|
|
|
|
|
return errors.New("data is empty")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
opData := &operationData{}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Unmarshaling operation data from CBOR")
|
|
|
|
|
|
if err := helpers.Unmarshal(data, opData); err != nil {
|
|
|
|
|
|
log.Error("Failed to unmarshal operation from CBOR",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("failed to unmarshal operation from CBOR: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
o.fromOperationData(opData)
|
|
|
|
|
|
|
|
|
|
|
|
o.binary = data
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Operation unmarshaled successfully",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 哈希设置方法 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// setBodyHashFlexible 根据输入数据类型计算哈希,支持 string 和 []byte。
|
|
|
|
|
|
// 使用固定的 Sha256Simd 算法。
|
|
|
|
|
|
// 如果输入为 nil 或空,则目标指针设置为 nil,表示该字段未设置。
|
|
|
|
|
|
func (o *Operation) setBodyHashFlexible(data interface{}, target **string) error {
|
|
|
|
|
|
log := logger.GetGlobalLogger()
|
|
|
|
|
|
log.Debug("Setting body hash flexible",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
"dataType", fmt.Sprintf("%T", data),
|
|
|
|
|
|
)
|
|
|
|
|
|
if data == nil {
|
|
|
|
|
|
log.Debug("Data is nil, setting target to nil")
|
|
|
|
|
|
*target = nil
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hashTool := GetHashTool(Sha256Simd)
|
|
|
|
|
|
var raw []byte
|
|
|
|
|
|
|
|
|
|
|
|
switch v := data.(type) {
|
|
|
|
|
|
case string:
|
|
|
|
|
|
if v == "" {
|
|
|
|
|
|
log.Debug("String data is empty, setting target to nil")
|
|
|
|
|
|
*target = nil
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
raw = []byte(v)
|
|
|
|
|
|
log.Debug("Converting string to bytes",
|
|
|
|
|
|
"stringLength", len(v),
|
|
|
|
|
|
)
|
|
|
|
|
|
case []byte:
|
|
|
|
|
|
if len(v) == 0 {
|
|
|
|
|
|
log.Debug("Byte data is empty, setting target to nil")
|
|
|
|
|
|
*target = nil
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
raw = v
|
|
|
|
|
|
log.Debug("Using byte data directly",
|
|
|
|
|
|
"byteLength", len(v),
|
|
|
|
|
|
)
|
|
|
|
|
|
default:
|
|
|
|
|
|
log.Error("Unsupported data type",
|
|
|
|
|
|
"dataType", fmt.Sprintf("%T", v),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("unsupported data type %T", v)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Computing hash for body data",
|
|
|
|
|
|
"dataLength", len(raw),
|
|
|
|
|
|
)
|
|
|
|
|
|
hash, err := hashTool.HashBytes(raw)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Error("Failed to compute hash",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*target = &hash
|
|
|
|
|
|
log.Debug("Body hash set successfully",
|
|
|
|
|
|
"hash", hash,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RequestBodyFlexible 设置请求体哈希值。
|
|
|
|
|
|
// 支持 string 和 []byte 类型,nil 或空值会将 RequestBodyHash 设置为 nil。
|
|
|
|
|
|
func (o *Operation) RequestBodyFlexible(data interface{}) error {
|
|
|
|
|
|
return o.setBodyHashFlexible(data, &o.RequestBodyHash)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ResponseBodyFlexible 设置响应体哈希值。
|
|
|
|
|
|
// 支持 string 和 []byte 类型,nil 或空值会将 ResponseBodyHash 设置为 nil。
|
|
|
|
|
|
func (o *Operation) ResponseBodyFlexible(data interface{}) error {
|
|
|
|
|
|
return o.setBodyHashFlexible(data, &o.ResponseBodyHash)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 链式调用支持 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// WithRequestBody 设置请求体哈希并返回自身,支持链式调用。
|
|
|
|
|
|
func (o *Operation) WithRequestBody(data []byte) *Operation {
|
|
|
|
|
|
_ = o.RequestBodyFlexible(data)
|
|
|
|
|
|
return o
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WithResponseBody 设置响应体哈希并返回自身,支持链式调用。
|
|
|
|
|
|
func (o *Operation) WithResponseBody(data []byte) *Operation {
|
|
|
|
|
|
_ = o.ResponseBodyFlexible(data)
|
|
|
|
|
|
return o
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
// ===== 初始化与验证 =====
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// CheckAndInit 校验并初始化 Operation。
|
|
|
|
|
|
// 自动填充缺失字段(OpID、OpActor),执行业务逻辑验证(doid 格式),
|
|
|
|
|
|
// 字段非空验证由 validate 标签处理。
|
|
|
|
|
|
func (o *Operation) CheckAndInit() error {
|
|
|
|
|
|
log := logger.GetGlobalLogger()
|
|
|
|
|
|
log.Debug("Checking and initializing operation",
|
|
|
|
|
|
"opSource", o.OpSource,
|
2025-12-26 13:47:55 +08:00
|
|
|
|
"opCode", o.OpCode,
|
2025-12-22 13:37:57 +08:00
|
|
|
|
"doid", o.Doid,
|
|
|
|
|
|
)
|
|
|
|
|
|
if o.OpID == "" {
|
|
|
|
|
|
o.OpID = helpers.NewUUIDv7()
|
|
|
|
|
|
log.Debug("Generated new OpID",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if o.OpActor == "" {
|
|
|
|
|
|
o.OpActor = "SYSTEM"
|
|
|
|
|
|
log.Debug("Set default OpActor to SYSTEM")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expectedPrefix := fmt.Sprintf("%s/%s", o.DoPrefix, o.DoRepository)
|
|
|
|
|
|
if !strings.HasPrefix(o.Doid, expectedPrefix) {
|
|
|
|
|
|
log.Error("Doid format validation failed",
|
|
|
|
|
|
"doid", o.Doid,
|
|
|
|
|
|
"expectedPrefix", expectedPrefix,
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("doid must start with '%s'", expectedPrefix)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Validating operation struct")
|
|
|
|
|
|
if err := helpers.GetValidator().Struct(o); err != nil {
|
|
|
|
|
|
log.Error("Operation validation failed",
|
|
|
|
|
|
"error", err,
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Debug("Operation checked and initialized successfully",
|
|
|
|
|
|
"opID", o.OpID,
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|