112 lines
4.4 KiB
Go
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")
|
|
}
|