Files
go-sip/internal/agent/recording_client.go
T

112 lines
4.4 KiB
Go

package agent
import (
"bytes"
"context"
"errors"
"fmt"
agentpb "git.ipao.vip/rogee/go-sip/gen/agent"
"google.golang.org/protobuf/proto"
)
var ErrRecordingClientUnavailable = errors.New("Agent recording delivery requires an active Dispatcher session and Unary client")
// RecordingClient belongs to one approved call. It requests upload tokens only
// when its caller explicitly asks, and it never retries an OSS PUT or invents a
// successful call end or result after an RPC error.
type RecordingClient struct {
Client agentpb.AgentControlServiceClient
Session func(context.Context) (*agentpb.RequestMeta, error)
DispatcherID string
TenantID int64
SourceEventID string
}
func (c RecordingClient) requestMeta(ctx context.Context, action string) (*agentpb.RequestMeta, error) {
if c.Client == nil || c.Session == nil || c.DispatcherID == "" || c.TenantID <= 0 || c.SourceEventID == "" {
return nil, ErrRecordingClientUnavailable
}
if err := ctx.Err(); err != nil {
return nil, err
}
meta, err := c.Session(ctx)
if err != nil {
return nil, fmt.Errorf("obtain current Agent session: %w", err)
}
if meta == nil || meta.GetAgentId() == "" || meta.GetCellId() == "" || meta.GetBootId() == "" || meta.GetDispatcherEpoch() == "" || meta.GetSessionGeneration() == 0 {
return nil, ErrRecordingClientUnavailable
}
copy := proto.Clone(meta).(*agentpb.RequestMeta)
copy.OperationId = c.SourceEventID + "/" + action
copy.IdempotencyKey = copy.OperationId
return copy, nil
}
func (c RecordingClient) RequestUpload(ctx context.Context, asset *agentpb.AssetDescriptor, uploadID string) (*agentpb.UploadGrant, error) {
meta, err := c.requestMeta(ctx, "upload/"+uploadID)
if err != nil {
return nil, err
}
if asset == nil || uploadID == "" || asset.GetKind() != agentpb.AssetKind_ASSET_KIND_RECORDING ||
asset.GetExecutionId() != c.SourceEventID || asset.GetCallId() != c.SourceEventID || asset.GetAssetId() == "" || asset.GetSizeBytes() <= 0 || asset.GetChecksumSha256() == "" {
return nil, errors.New("recording token request does not describe the approved call and original asset")
}
response, err := c.Client.RequestRecordingUpload(ctx, &agentpb.RequestRecordingUploadRequest{
Meta: meta, DispatcherId: c.DispatcherID, TenantId: c.TenantID, SourceEventId: c.SourceEventID,
UploadId: uploadID, Asset: proto.Clone(asset).(*agentpb.AssetDescriptor),
})
if err != nil {
return nil, fmt.Errorf("request original recording upload token: %w", err)
}
grant := response.GetGrant()
if grant == nil || grant.GetUploadId() != uploadID || grant.GetBucket() == "" || grant.GetObjectKey() == "" ||
grant.GetMaxBytes() != asset.GetSizeBytes() || grant.GetRequiredChecksumSha256() != asset.GetChecksumSha256() {
return nil, fmt.Errorf("%w: Dispatcher returned a different recording asset", ErrUploadGrantInvalid)
}
return grant, nil
}
func (c RecordingClient) ReportEnded(ctx context.Context) error {
meta, err := c.requestMeta(ctx, "ended")
if err != nil {
return err
}
response, err := c.Client.ReportCallEnded(ctx, &agentpb.ReportCallEndedRequest{
Meta: meta, DispatcherId: c.DispatcherID, TenantId: c.TenantID, SourceEventId: c.SourceEventID,
})
if err != nil {
return fmt.Errorf("report confirmed call end: %w", err)
}
if receipt := response.GetReceipt(); receipt.GetResult() != agentpb.ResultCode_RESULT_CODE_APPLIED || receipt.GetFactId() != c.SourceEventID {
return errors.New("Dispatcher did not persist the confirmed call end")
}
return nil
}
func (c RecordingClient) ReportFinal(ctx context.Context, payload []byte, upload *agentpb.UploadObservation) (string, error) {
meta, err := c.requestMeta(ctx, "result")
if err != nil {
return "", err
}
if len(payload) == 0 {
return "", errors.New("final call result payload is required")
}
request := &agentpb.ReportCallResultRequest{
Meta: meta, DispatcherId: c.DispatcherID, TenantId: c.TenantID, SourceEventId: c.SourceEventID,
ResultPayloadJson: bytes.Clone(payload),
}
if upload != nil {
request.Upload = proto.Clone(upload).(*agentpb.UploadObservation)
}
response, err := c.Client.ReportCallResult(ctx, request)
if err != nil {
return "", fmt.Errorf("persist unique call result: %w", err)
}
if receipt := response.GetReceipt(); receipt.GetResult() == agentpb.ResultCode_RESULT_CODE_ACCEPTED && receipt.GetFactId() != "" {
return receipt.GetFactId(), nil
}
return "", errors.New("Dispatcher did not persist the unique call result")
}