2026-09-06 17:04:11 +08:00
package hardwareprovision
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"regexp"
"strings"
"time"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/deviceactivation"
"data-terminal-cloud/internal/deviceauth"
2026-09-12 08:57:29 +08:00
"data-terminal-cloud/internal/devicebinding"
2026-09-06 17:04:11 +08:00
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
ErrUnavailable = errors . New ( "hardware provider provisioning is unavailable" )
ErrInvalid = errors . New ( "invalid hardware provider provisioning input" )
ErrBindingMissing = errors . New ( "hardware agent binding is missing" )
ErrBindingMismatch = errors . New ( "hardware agent product binding does not match platform provisioning configuration" )
ErrHardwareMismatch = errors . New ( "hardware identity does not match the provisioned credential" )
ErrActivationState = errors . New ( "device activation state does not allow provider provisioning" )
)
var hardwareIDPattern = regexp . MustCompile ( `^[a-zA-Z0-9_\-.:@]{1,200}$` )
type Options struct {
Enabled bool
InstanceID string
ProductKey string
BotID string
Transport string
MinimumSDKVersion string
ProductSecret string
CredentialMasterKey string
RegisterURL string
RegisterTimeout time . Duration
DeviceNamePrefix string
Registrar Registrar
}
type ProvisionInput struct {
HardwareID string `json:"hardware_id"`
}
type ProvisionResponse struct {
InstanceID string `json:"instance_id"`
ProductKey string `json:"product_key"`
DeviceName string `json:"device_name"`
HardwareID string `json:"hardware_id"`
DeviceSecret string `json:"device_secret"`
CredentialRef string `json:"credential_ref"`
CredentialVersion int64 `json:"credential_version"`
CredentialSource string `json:"credential_source"`
IssuedAt time . Time `json:"issued_at"`
}
type Service struct {
enabled bool
pool * pgxpool . Pool
audit * audit . Repository
registrar Registrar
aead cipher . AEAD
instanceID string
productKey string
botID string
transport string
minimumSDK string
deviceNamePrefix string
}
func NewService ( pool * pgxpool . Pool , auditRepository * audit . Repository , options Options ) ( * Service , error ) {
service := & Service { enabled : options . Enabled }
if ! options . Enabled {
return service , nil
}
if pool == nil || auditRepository == nil {
return nil , errors . New ( "hardware provider provisioning requires database and audit repository" )
}
service . instanceID = strings . TrimSpace ( options . InstanceID )
service . productKey = strings . TrimSpace ( options . ProductKey )
service . botID = strings . TrimSpace ( options . BotID )
service . transport = strings . TrimSpace ( options . Transport )
service . minimumSDK = strings . TrimSpace ( options . MinimumSDKVersion )
service . deviceNamePrefix = strings . TrimSpace ( options . DeviceNamePrefix )
if service . deviceNamePrefix == "" {
service . deviceNamePrefix = "dt-"
}
if service . instanceID == "" || len ( service . instanceID ) > 200 || service . productKey == "" || len ( service . productKey ) > 200 ||
service . botID == "" || len ( service . botID ) > 200 || ( service . transport != "high_quality" && service . transport != "low_load" ) ||
service . minimumSDK == "" || len ( service . minimumSDK ) > 80 ||
! regexp . MustCompile ( `^[a-zA-Z0-9_\-.:@]{1,32}$` ). MatchString ( service . deviceNamePrefix ) {
return nil , errors . New ( "hardware provider instance, product defaults and device-name prefix are invalid" )
}
key , err := hex . DecodeString ( strings . TrimSpace ( options . CredentialMasterKey ))
if err != nil || len ( key ) != 32 {
return nil , errors . New ( "VOLC_HARDWARE_DEVICE_CREDENTIAL_MASTER_KEY must be 32 bytes encoded as 64 hexadecimal characters" )
}
block , err := aes . NewCipher ( key )
if err != nil {
return nil , fmt . Errorf ( "create hardware provider credential cipher: %w" , err )
}
service . aead , err = cipher . NewGCM ( block )
if err != nil {
return nil , fmt . Errorf ( "create hardware provider credential AEAD: %w" , err )
}
service . registrar = options . Registrar
if service . registrar == nil {
service . registrar , err = NewHTTPRegistrar ( options . RegisterURL , options . ProductSecret , options . RegisterTimeout )
if err != nil {
return nil , err
}
}
service . pool = pool
service . audit = auditRepository
return service , nil
}
func ( s * Service ) Provision ( ctx context . Context , identity deviceauth . Identity , requestID string , input ProvisionInput ) ( ProvisionResponse , error ) {
if ! s . enabled {
return ProvisionResponse {}, ErrUnavailable
}
hardwareID := strings . TrimSpace ( input . HardwareID )
if ! hardwareIDPattern . MatchString ( hardwareID ) {
return ProvisionResponse {}, ErrInvalid
}
tx , err := s . pool . Begin ( ctx )
if err != nil {
return ProvisionResponse {}, fmt . Errorf ( "begin hardware provider provisioning: %w" , err )
}
defer tx . Rollback ( ctx )
if _ , err := tx . Exec ( ctx , `SELECT pg_advisory_xact_lock(hashtextextended($1, 0))` , identity . ID ); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "lock hardware provider provisioning: %w" , err )
}
2026-09-12 08:57:29 +08:00
if err := devicebinding . RequireLegacy ( ctx , tx , identity . ID ); err != nil {
return ProvisionResponse {}, err
}
2026-09-06 17:04:11 +08:00
activationState , err := s . prepareActivation ( ctx , tx , identity , requestID )
if err != nil {
return ProvisionResponse {}, err
}
if err := s . ensureProductBinding ( ctx , tx , identity ); err != nil {
return ProvisionResponse {}, err
}
existing , found , err := s . readExisting ( ctx , tx , identity . ID )
if err != nil {
return ProvisionResponse {}, err
}
if found {
if existing . HardwareID != hardwareID {
return ProvisionResponse {}, ErrHardwareMismatch
}
if existing . InstanceID != s . instanceID || existing . ProductKey != s . productKey {
return ProvisionResponse {}, ErrBindingMismatch
}
secret , err := s . aead . Open ( nil , existing . Nonce , existing . EncryptedSecret , credentialAAD ( existing . CredentialRef , existing . KeyVersion , existing . InstanceID , existing . ProductKey , existing . DeviceName ))
if err != nil {
return ProvisionResponse {}, errors . New ( "decrypt hardware provider device credential" )
}
response := ProvisionResponse { InstanceID : existing . InstanceID , ProductKey : existing . ProductKey , DeviceName : existing . DeviceName ,
HardwareID : existing . HardwareID , DeviceSecret : string ( secret ), CredentialRef : existing . CredentialRef ,
CredentialVersion : existing . KeyVersion , CredentialSource : "platform_stored" , IssuedAt : existing . IssuedAt }
if existing . Status == "SUSPENDED" {
if _ , err := tx . Exec ( ctx , `UPDATE hardware_agent_provider_credential SET status='ACTIVE',revoked_at=NULL,updated_at=now() WHERE device_id=$1::uuid AND status='SUSPENDED'` , identity . ID ); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "reactivate hardware provider credential: %w" , err )
}
}
if activationState == deviceactivation . Provisioning {
if _ , err := deviceactivation . Apply ( ctx , tx , deviceactivation . Input { DeviceID : identity . ID , TenantID : identity . TenantID ,
Target : deviceactivation . Active , Reason : "火山运行凭据已就绪" , Source : "provider-provisioning" , ActorType : "device" ,
ActorPublicID : & identity . PublicID , RequestID : requestID }); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "complete provider activation: %w" , err )
}
}
if err := s . recordAudit ( ctx , tx , identity , requestID , "hardware_agent.provider_credential.resolve" , response , true ); err != nil {
return ProvisionResponse {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "commit hardware provider credential resolution: %w" , err )
}
return response , nil
}
var configuredProduct , configuredDeviceName string
err = tx . QueryRow ( ctx , `
SELECT COALESCE(product_key,''),COALESCE(provider_device_ref,'')
FROM hardware_agent_device_binding
WHERE device_id=$1::uuid AND tenant_id=$2::uuid AND provider='volcengine' AND status='ACTIVE'
FOR UPDATE` , identity . ID , identity . TenantID ). Scan ( & configuredProduct , & configuredDeviceName )
if errors . Is ( err , pgx . ErrNoRows ) {
return ProvisionResponse {}, ErrBindingMissing
}
if err != nil {
return ProvisionResponse {}, fmt . Errorf ( "read hardware agent provider binding: %w" , err )
}
if configuredProduct != "" && configuredProduct != s . productKey {
return ProvisionResponse {}, ErrBindingMismatch
}
deviceName := strings . TrimSpace ( configuredDeviceName )
if deviceName == "" {
deviceName = s . deviceNamePrefix + strings . ReplaceAll ( identity . PublicID , "-" , "" )
}
if len ( deviceName ) > 128 || ! regexp . MustCompile ( `^[a-zA-Z0-9_\-.:@]{1,128}$` ). MatchString ( deviceName ) {
return ProvisionResponse {}, ErrInvalid
}
registered , err := s . registrar . Register ( ctx , RegisterInput { InstanceID : s . instanceID , ProductKey : s . productKey , DeviceName : deviceName })
if err != nil {
return ProvisionResponse {}, fmt . Errorf ( "register hardware provider device: %w" , err )
}
secret := [] byte ( strings . TrimSpace ( registered . DeviceSecret ))
if len ( secret ) < 16 || len ( secret ) > 4096 {
return ProvisionResponse {}, errors . New ( "hardware provider returned an invalid device credential" )
}
credentialRef := "volc-device-secret://" + identity . PublicID
const keyVersion int64 = 1
nonce := make ([] byte , s . aead . NonceSize ())
if _ , err := io . ReadFull ( rand . Reader , nonce ); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "generate hardware provider credential nonce: %w" , err )
}
encrypted := s . aead . Seal ( nil , nonce , secret , credentialAAD ( credentialRef , keyVersion , s . instanceID , s . productKey , deviceName ))
issuedAt := time . Now (). UTC ()
_ , err = tx . Exec ( ctx , `
INSERT INTO hardware_agent_provider_credential(
tenant_id,device_id,credential_ref,instance_id,product_key,provider_device_name,hardware_id,
key_version,encrypted_secret,nonce,status,issued_at
) VALUES($1::uuid,$2::uuid,$3,$4,$5,$6,$7,$8,$9,$10,'ACTIVE',$11)` ,
identity . TenantID , identity . ID , credentialRef , s . instanceID , s . productKey , deviceName , hardwareID ,
keyVersion , encrypted , nonce , issuedAt )
if err != nil {
return ProvisionResponse {}, fmt . Errorf ( "store hardware provider device credential: %w" , err )
}
_ , err = tx . Exec ( ctx , `
UPDATE hardware_agent_device_binding
SET product_key=$2,provider_device_ref=$3,credential_ref=$4,updated_at=now()
WHERE device_id=$1::uuid` , identity . ID , s . productKey , deviceName , credentialRef )
if err != nil {
return ProvisionResponse {}, fmt . Errorf ( "update hardware agent provider binding: %w" , err )
}
response := ProvisionResponse { InstanceID : s . instanceID , ProductKey : s . productKey , DeviceName : deviceName , HardwareID : hardwareID ,
DeviceSecret : string ( secret ), CredentialRef : credentialRef , CredentialVersion : keyVersion ,
CredentialSource : "platform_dynamic_registration" , IssuedAt : issuedAt }
if activationState == deviceactivation . Provisioning {
if _ , err := deviceactivation . Apply ( ctx , tx , deviceactivation . Input { DeviceID : identity . ID , TenantID : identity . TenantID ,
Target : deviceactivation . Active , Reason : "火山动态注册与运行凭据已就绪" , Source : "provider-provisioning" , ActorType : "device" ,
ActorPublicID : & identity . PublicID , RequestID : requestID }); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "complete provider activation: %w" , err )
}
}
if err := s . recordAudit ( ctx , tx , identity , requestID , "hardware_agent.provider_credential.provision" , response , false ); err != nil {
return ProvisionResponse {}, err
}
if err := tx . Commit ( ctx ); err != nil {
return ProvisionResponse {}, fmt . Errorf ( "commit hardware provider provisioning: %w" , err )
}
return response , nil
}
// ensureProductBinding resolves the device runtime from the platform-owned
// product defaults. DynamicRegister only creates the provider device identity;
// it does not return a Bot ID, so devices must never supply or persist that
// routing choice themselves.
func ( s * Service ) ensureProductBinding ( ctx context . Context , tx pgx . Tx , identity deviceauth . Identity ) error {
var runtime string
if err := tx . QueryRow ( ctx , `SELECT COALESCE(desired_config->>'voice_runtime','legacy_voicechat') FROM device_twin WHERE device_id=$1::uuid AND tenant_id=$2::uuid FOR UPDATE` , identity . ID , identity . TenantID ). Scan ( & runtime ); errors . Is ( err , pgx . ErrNoRows ) {
return ErrBindingMissing
} else if err != nil {
return fmt . Errorf ( "read hardware agent runtime: %w" , err )
}
if runtime != "volc_hardware_agent" {
return ErrBindingMissing
}
_ , err := tx . Exec ( ctx , `
INSERT INTO hardware_agent_device_binding(
tenant_id,device_id,provider,product_key,bot_id,transport,minimum_sdk_version,status
) VALUES($1::uuid,$2::uuid,'volcengine',$3,$4,$5,$6,'ACTIVE')
ON CONFLICT(device_id) DO UPDATE SET
product_key=EXCLUDED.product_key,bot_id=EXCLUDED.bot_id,transport=EXCLUDED.transport,
minimum_sdk_version=EXCLUDED.minimum_sdk_version,status='ACTIVE',updated_at=now()` ,
identity . TenantID , identity . ID , s . productKey , s . botID , s . transport , s . minimumSDK )
if err != nil {
return fmt . Errorf ( "bind hardware agent product defaults: %w" , err )
}
_ , err = tx . Exec ( ctx , `
UPDATE device_twin SET desired_revision=desired_revision+1,
desired_config=desired_config||jsonb_build_object(
'voice_runtime','volc_hardware_agent','hardware_agent_transport',$2::text,'minimum_sdk_version',$3::text
),updated_at=now()
WHERE device_id=$1::uuid AND (
COALESCE(desired_config->>'hardware_agent_transport','')<>$2 OR
COALESCE(desired_config->>'minimum_sdk_version','')<>$3
)` , identity . ID , s . transport , s . minimumSDK )
if err != nil {
return fmt . Errorf ( "publish hardware agent product defaults: %w" , err )
}
return nil
}
type storedCredential struct {
Status string
CredentialRef string
InstanceID string
ProductKey string
DeviceName string
HardwareID string
KeyVersion int64
EncryptedSecret [] byte
Nonce [] byte
IssuedAt time . Time
}
func ( s * Service ) readExisting ( ctx context . Context , tx pgx . Tx , deviceID string ) ( storedCredential , bool , error ) {
var item storedCredential
err := tx . QueryRow ( ctx , `
SELECT status,credential_ref,instance_id,product_key,provider_device_name,hardware_id,key_version,encrypted_secret,nonce,issued_at
FROM hardware_agent_provider_credential
WHERE device_id=$1::uuid AND provider='volcengine' AND status IN ('ACTIVE','SUSPENDED')
FOR UPDATE` , deviceID ). Scan ( & item . Status , & item . CredentialRef , & item . InstanceID , & item . ProductKey , & item . DeviceName , & item . HardwareID ,
& item . KeyVersion , & item . EncryptedSecret , & item . Nonce , & item . IssuedAt )
if errors . Is ( err , pgx . ErrNoRows ) {
return storedCredential {}, false , nil
}
if err != nil {
return storedCredential {}, false , fmt . Errorf ( "read hardware provider device credential: %w" , err )
}
return item , true , nil
}
func ( s * Service ) prepareActivation ( ctx context . Context , tx pgx . Tx , identity deviceauth . Identity , requestID string ) ( deviceactivation . State , error ) {
var stateText string
if err := tx . QueryRow ( ctx , `SELECT activation_state FROM device WHERE id=$1::uuid AND tenant_id=$2::uuid FOR UPDATE` , identity . ID , identity . TenantID ). Scan ( & stateText ); err != nil {
return "" , fmt . Errorf ( "read device activation for provisioning: %w" , err )
}
state , err := deviceactivation . Parse ( stateText )
if err != nil {
return "" , ErrActivationState
}
if state == deviceactivation . Bound {
result , err := deviceactivation . Apply ( ctx , tx , deviceactivation . Input { DeviceID : identity . ID , TenantID : identity . TenantID ,
Target : deviceactivation . Provisioning , Reason : "设备请求火山运行凭据" , Source : "provider-provisioning" , ActorType : "device" ,
ActorPublicID : & identity . PublicID , RequestID : requestID })
if err != nil {
return "" , fmt . Errorf ( "start provider activation: %w" , err )
}
return result . To , nil
}
if state != deviceactivation . Provisioning && state != deviceactivation . Active {
return "" , ErrActivationState
}
return state , nil
}
func ( s * Service ) recordAudit ( ctx context . Context , tx pgx . Tx , identity deviceauth . Identity , requestID , action string , response ProvisionResponse , reused bool ) error {
return s . audit . Record ( ctx , tx , audit . Event { TenantID : & identity . TenantID , ActorType : "device" , ActorPublicID : & identity . PublicID ,
Action : action , ResourceType : "device" , ResourceID : & identity . PublicID , RequestID : requestID , Result : "success" ,
Detail : map [ string ] any { "provider" : "volcengine" , "product_key" : response . ProductKey , "provider_device_name" : response . DeviceName ,
"credential_version" : response . CredentialVersion , "reused" : reused }})
}
func credentialAAD ( reference string , version int64 , instanceID , productKey , deviceName string ) [] byte {
return [] byte ( fmt . Sprintf ( "hardware-provider-credential-v1\n%s\n%d\n%s\n%s\n%s" , reference , version , instanceID , productKey , deviceName ))
}