Files
go-sip/cmd/sip-go-agent/upload.go
T

140 lines
4.5 KiB
Go

package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"path/filepath"
"strings"
"time"
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
"git.ipao.vip/rogee/go-sip/internal/agent"
"git.ipao.vip/rogee/go-sip/internal/callruntime"
"git.ipao.vip/rogee/go-sip/internal/config"
"git.ipao.vip/rogee/go-sip/internal/rpc"
)
func uploadCallRecordings(ctx context.Context, cfg config.Config, result callruntime.Result) ([]map[string]any, error) {
if strings.TrimSpace(cfg.DispatcherGRPCEndpoint) == "" {
return nil, nil
}
for name, value := range map[string]string{
"AGENT_CALL_TENANT_ID": cfg.CallTenantID,
"AGENT_CALL_TENANT_KEY": cfg.CallTenantKey,
"AGENT_CALL_TASK_ID": cfg.CallTaskID,
"AGENT_CALL_TASK_ITEM_ID": cfg.CallTaskItemID,
} {
if strings.TrimSpace(value) == "" {
return nil, fmt.Errorf("%s is required when Dispatcher OSS upload is enabled", name)
}
}
if result.ChannelID == "" {
return nil, errors.New("call result has no channel ID for upload binding")
}
executionID := cfg.CallExecutionID
if executionID == "" {
executionID = result.ChannelID
}
binding := &agentpb.ExecutionBinding{
TenantId: cfg.CallTenantID,
TenantKey: cfg.CallTenantKey,
ExecutionId: executionID,
TaskId: cfg.CallTaskID,
TaskItemId: cfg.CallTaskItemID,
TaskRevision: 1,
CallId: result.ChannelID,
AttemptId: result.ChannelID,
AgentVersionId: cfg.Version,
}
client, err := rpc.DialFromFiles(cfg.DispatcherGRPCEndpoint, cfg.MTLSCAFile, cfg.MTLSCertFile, cfg.MTLSKeyFile, cfg.DispatcherGRPCServerName)
if err != nil {
return nil, fmt.Errorf("dial Dispatcher gRPC service: %w", err)
}
defer client.Close()
spool, err := agent.NewSpool(cfg.SpoolRoot, time.Now)
if err != nil {
return nil, err
}
uploader := agent.UploadClient{Now: time.Now}
facts := append(append([]callruntime.RecordingFact(nil), result.InboundRecordings...), result.OutboundRecordings...)
if len(facts) == 0 {
return nil, errors.New("call produced no recordings for OSS upload")
}
uploaded := make([]map[string]any, 0, len(facts))
for index, recording := range facts {
if recording.Path == "" || recording.Bytes <= 0 || recording.SHA256 == "" {
return nil, fmt.Errorf("recording %d is missing path, size or checksum", index+1)
}
assetID := recordingAssetID(recording.Segment, recording.Path, index+1)
asset := &agentpb.AssetDescriptor{
Kind: agentpb.AssetKind_ASSET_KIND_RECORDING,
AssetId: assetID,
CallId: result.ChannelID,
ExecutionId: executionID,
Format: "wav",
SizeBytes: int64(recording.Bytes),
ChecksumSha256: recording.SHA256,
Channels: 1,
SampleRateHz: 16000,
DurationMs: recording.DurationMS,
}
record, err := uploadRecording(ctx, cfg, client, uploader, spool, binding, asset, recording.Path)
if err != nil {
return nil, err
}
uploaded = append(uploaded, map[string]any{
"upload_id": record.UploadID, "asset_id": asset.AssetId, "object_key": record.ObjectKey,
"bytes": record.Result.SizeBytes, "sha256": record.Result.SHA256,
"status_code": record.Result.StatusCode, "notification_state": record.State,
})
}
return uploaded, nil
}
func recordingAssetID(segment, path string, index int) string {
raw := segment
if raw == "" {
raw = fmt.Sprintf("segment-%d", index)
}
raw += "-" + filepath.Base(path)
var builder strings.Builder
for _, r := range raw {
if r == '/' || r == '\\' || r == ' ' || r == '\t' || r == '\n' || r == '\r' {
builder.WriteByte('-')
continue
}
builder.WriteRune(r)
}
assetID := strings.Trim(builder.String(), "-")
if assetID == "" {
assetID = fmt.Sprintf("segment-%d", index)
}
if len([]byte(assetID)) > 120 {
digest := sha256.Sum256([]byte(assetID))
assetID = "recording-" + hex.EncodeToString(digest[:16])
}
return assetID
}
func stableUploadID(binding *agentpb.ExecutionBinding, asset *agentpb.AssetDescriptor) string {
value := binding.ExecutionId + "\x00" + asset.AssetId + "\x00" + asset.ChecksumSha256
digest := sha256.Sum256([]byte(value))
return "upload-" + hex.EncodeToString(digest[:16])
}
func uploadMeta(cfg config.Config, phase, uploadID string) *agentpb.RequestMeta {
operationID := "recording-" + phase + "-" + uploadID
return &agentpb.RequestMeta{
ProtocolVersion: "agent.v1",
RequestId: operationID,
TraceId: operationID,
OperationId: operationID,
IdempotencyKey: operationID,
AgentId: cfg.AgentID,
CellId: cfg.CellID,
}
}