2023-03-24 20:07:55 +00:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package bsr
2023-03-15 14:35:09 +00:00
import (
"context"
2023-05-24 20:04:35 +00:00
"encoding/json"
2023-04-17 10:37:17 -04:00
"errors"
2023-03-15 14:35:09 +00:00
"fmt"
"io"
2023-08-04 14:31:20 -04:00
"strings"
2023-03-15 14:35:09 +00:00
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
"github.com/hashicorp/boundary/internal/bsr/internal/is"
"github.com/hashicorp/boundary/internal/bsr/kms"
"github.com/hashicorp/boundary/internal/storage"
2023-05-08 15:49:21 -04:00
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
2023-04-17 10:37:17 -04:00
"google.golang.org/protobuf/proto"
2023-03-15 14:35:09 +00:00
)
const (
2023-05-26 13:07:52 +00:00
bsrFileNameTemplate = "%s.bsr"
connectionFileNameTemplate = "%s.connection"
channelFileNameTemplate = "%s.channel"
messagesFileNameTemplate = "messages-%s.data"
requestsFileNameTemplate = "requests-%s.data"
sessionMetaFileName = "session-meta.json"
bsrPubKeyFileName = "bsrKey.pub"
wrappedBsrKeyFileName = "wrappedBsrKey"
wrappedPrivKeyFileName = "wrappedPrivKey"
pubKeyBsrSignatureFileName = "pubKeyBsrSignature.sign"
pubKeySelfSignatureFileName = "pubKeySelfSignature.sign"
2024-01-10 16:43:35 -05:00
// bsrBufferSize is the buffer size for files in a BSR.
// 65 * storage.LogicalBlockSize is equivalent to 260KiB
bsrBufferSize = 65 * storage . LogicalBlockSize
2023-03-15 14:35:09 +00:00
)
// Session is the top level container in a bsr that contains the files for
// a recorded session.
type Session struct {
* container
multiplexed bool
2023-05-24 20:04:35 +00:00
Meta * SessionRecordingMeta
SessionMeta * SessionMeta
2023-07-13 10:25:01 -04:00
Summary SessionSummary
2023-03-15 14:35:09 +00:00
}
2024-01-08 14:27:40 +01:00
// GetBsrFileName formats a session recording id into the BSR filename format
func GetBsrFileName ( sessionRecordingId string ) string {
return fmt . Sprintf ( bsrFileNameTemplate , sessionRecordingId )
}
2023-03-15 14:35:09 +00:00
// NewSession creates a Session container for a given session id.
2023-05-24 20:04:35 +00:00
func NewSession ( ctx context . Context , meta * SessionRecordingMeta , sessionMeta * SessionMeta , f storage . FS , keys * kms . Keys , options ... Option ) ( * Session , error ) {
2023-03-15 14:35:09 +00:00
const op = "bsr.NewSession"
switch {
case is . Nil ( meta ):
2023-05-24 20:04:35 +00:00
return nil , fmt . Errorf ( "%s: missing meta: %w" , op , ErrInvalidParameter )
case is . Nil ( sessionMeta ):
2023-03-15 14:35:09 +00:00
return nil , fmt . Errorf ( "%s: missing session meta: %w" , op , ErrInvalidParameter )
case meta . Id == "" :
return nil , fmt . Errorf ( "%s: missing session id: %w" , op , ErrInvalidParameter )
2023-05-24 20:04:35 +00:00
case ! is . Nil ( sessionMeta . StaticHost ) && ! is . Nil ( sessionMeta . DynamicHost ):
return nil , fmt . Errorf ( "%s: sessionMeta cannot contain both static and dynamic host information: %w" , op , ErrInvalidParameter )
2023-05-25 14:42:07 -07:00
case len ( sessionMeta . StaticJSONCredentials ) == 0 &&
len ( sessionMeta . StaticUsernamePasswordCredentials ) == 0 &&
len ( sessionMeta . StaticSshPrivateKeyCredentials ) == 0 &&
2023-05-26 15:45:20 -07:00
len ( sessionMeta . VaultGenericLibraries ) == 0 &&
len ( sessionMeta . VaultSshCertificateLibraries ) == 0 :
2023-05-16 10:21:27 -04:00
return nil , fmt . Errorf ( "%s: missing credential information: %w" , op , ErrInvalidParameter )
2023-05-24 20:04:35 +00:00
case is . Nil ( sessionMeta . User ):
2023-05-03 09:11:52 -04:00
return nil , fmt . Errorf ( "%s: missing session user: %w" , op , ErrInvalidParameter )
2023-05-24 20:04:35 +00:00
case is . Nil ( sessionMeta . Target ):
2023-05-03 09:11:52 -04:00
return nil , fmt . Errorf ( "%s: missing session target: %w" , op , ErrInvalidParameter )
2023-05-24 20:04:35 +00:00
case is . Nil ( sessionMeta . Worker ):
2023-05-18 09:53:35 -04:00
return nil , fmt . Errorf ( "%s: missing session worker: %w" , op , ErrInvalidParameter )
2023-03-15 14:35:09 +00:00
case is . Nil ( f ):
return nil , fmt . Errorf ( "%s: missing storage fs: %w" , op , ErrInvalidParameter )
case is . Nil ( keys ):
return nil , fmt . Errorf ( "%s: missing kms keys: %w" , op , ErrInvalidParameter )
}
opts := getOpts ( options ... )
2024-01-08 14:27:40 +01:00
c , err := f . New ( ctx , GetBsrFileName ( meta . Id ))
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
2023-07-13 10:25:01 -04:00
nc , err := newContainer ( ctx , SessionContainer , c , keys )
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
2023-04-17 10:37:17 -04:00
// Sync keys and signatures
err = persistBsrSessionKeys ( ctx , keys , nc )
if err != nil {
return nil , errors . Join ( err , fmt . Errorf ( "%s: %w" , op , ErrBsrKeyPersistenceFailure ))
}
2023-05-26 13:07:52 +00:00
sm , err := nc . create ( ctx , sessionMetaFileName )
2023-05-24 20:04:35 +00:00
if err != nil {
return nil , err
}
smw , err := checksum . NewFile ( ctx , sm , nc . checksums )
if err != nil {
return nil , err
}
enc := json . NewEncoder ( smw )
if err := enc . Encode ( sessionMeta ); err != nil {
smw . Close ()
return nil , err
}
if err := smw . Close (); err != nil {
return nil , err
}
2023-05-03 09:11:52 -04:00
err = meta . writeMeta ( ctx , nc )
2023-04-19 12:33:52 -04:00
if err != nil {
return nil , err
}
2023-05-03 09:11:52 -04:00
2023-03-15 14:35:09 +00:00
return & Session {
container : nc ,
multiplexed : opts . withSupportsMultiplex ,
Meta : meta ,
2023-05-24 20:04:35 +00:00
SessionMeta : sessionMeta ,
2023-03-15 14:35:09 +00:00
}, nil
}
2023-04-17 10:37:17 -04:00
// persistBsrSessionKeys will marshal, write, and close BSR keys locally before syncing to
// storage. Any error while syncing the key files should result in the termination of
// the session this recording is for.
// The key files synced are:
// * the BSR public key, bsrKey.pub
// * the wrapped BSR key, wrappedBsrKey
// * the wrapped private key, wrappedPrivKey
// * the public key BSR signature, pubKeyBsrSignature.sign
// * the public key self signature, pubKeySelfSignature.sign
func persistBsrSessionKeys ( ctx context . Context , keys * kms . Keys , c * container ) error {
const op = "bsr.persistBsrSessionKeys"
switch {
case keys . PubKey == nil :
return fmt . Errorf ( "%s: missing kms pub key: %w" , op , ErrInvalidParameter )
case keys . WrappedBsrKey == nil :
return fmt . Errorf ( "%s: missing kms wrapped BSR key: %w" , op , ErrInvalidParameter )
case keys . WrappedPrivKey == nil :
return fmt . Errorf ( "%s: missing kms wrapped priv key: %w" , op , ErrInvalidParameter )
case keys . PubKeyBsrSignature == nil :
return fmt . Errorf ( "%s: missing kms pub key BSR signature: %w" , op , ErrInvalidParameter )
case keys . PubKeySelfSignature == nil :
return fmt . Errorf ( "%s: missing kms pub key self signature: %w" , op , ErrInvalidParameter )
}
keyFiles := map [ string ] proto . Message {
2023-05-26 13:07:52 +00:00
bsrPubKeyFileName : keys . PubKey ,
wrappedBsrKeyFileName : keys . WrappedBsrKey ,
wrappedPrivKeyFileName : keys . WrappedPrivKey ,
pubKeyBsrSignatureFileName : keys . PubKeyBsrSignature ,
pubKeySelfSignatureFileName : keys . PubKeySelfSignature ,
2023-04-17 10:37:17 -04:00
}
for f , k := range keyFiles {
b , err := proto . Marshal ( k )
if err != nil {
return fmt . Errorf ( "%s: failed to marshal data for %s: %w" , op , f , err )
}
err = c . syncBsrKey ( ctx , f , b )
if err != nil {
return fmt . Errorf ( "%s: failed syncing bsr key %s: %w" , op , f , err )
}
}
return nil
}
2023-04-21 14:41:28 -04:00
// OpenSession retrieves a BSR from storage using the sessionRecordingId and initializes it for reading.
// Encryption keys necessary for checking signed files will be unwrapped using the keyUnwrapFn
// Signature and checksum files will then be verified.
// Fields on the underlying container will be populated so that the returned Session can be used for BSR
// playback and conversion to formats such as asciinema
2023-07-31 17:13:24 -04:00
func OpenSession ( ctx context . Context , sessionRecordingId string , f storage . FS , keyUnwrapFn kms . KeyUnwrapCallbackFunc ) ( s * Session , err error ) {
2023-05-08 15:49:21 -04:00
const op = "bsr.OpenSession"
switch {
case sessionRecordingId == "" :
return nil , fmt . Errorf ( "%s: missing session recording id: %w" , op , ErrInvalidParameter )
case f == nil :
return nil , fmt . Errorf ( "%s: missing storage: %w" , op , ErrInvalidParameter )
case keyUnwrapFn == nil :
return nil , fmt . Errorf ( "%s: missing key unwrap function: %w" , op , ErrInvalidParameter )
}
2024-01-08 14:27:40 +01:00
c , err := f . Open ( ctx , GetBsrFileName ( sessionRecordingId ))
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
keyPopFn := func ( c * container ) ( * kms . Keys , error ) {
return c . loadKeys ( ctx , keyUnwrapFn )
}
2023-07-13 10:25:01 -04:00
cc , err := openContainer ( ctx , SessionContainer , c , keyPopFn )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
2023-05-24 20:04:35 +00:00
// Load and verify recording metadata
2023-05-08 15:49:21 -04:00
sha256Reader , err := crypto . NewSha256SumReader ( ctx , cc . metaFile )
if err != nil {
2023-07-31 17:13:24 -04:00
cc . metaFile . Close ()
2023-05-08 15:49:21 -04:00
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
2023-07-31 17:13:24 -04:00
defer func () {
if closeErr := sha256Reader . Close (); closeErr != nil {
err = errors . Join ( err , fmt . Errorf ( "%s: %w" , op , closeErr ))
}
}()
2023-05-24 20:04:35 +00:00
meta , err := decodeSessionRecordingMeta ( ctx , sha256Reader )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
err = cc . verifyMetadata ( ctx , sha256Reader )
if err != nil {
return nil , err
}
2023-05-24 20:04:35 +00:00
sessionMeta := & SessionMeta {}
2023-05-26 13:07:52 +00:00
if err := cc . decodeJsonFile ( ctx , sessionMetaFileName , sessionMeta ); err != nil {
2023-05-24 20:04:35 +00:00
return nil , err
}
2023-07-13 10:25:01 -04:00
af , ok := summaryAllocFuncs . get ( meta . Protocol , SessionContainer )
if ! ok {
return nil , fmt . Errorf ( "%s: failed to get summary type" , op )
}
summary := af ( ctx )
if err := cc . decodeJsonFile ( ctx , fmt . Sprintf ( summaryFileNameTemplate , SessionContainer ), summary ); err != nil {
return nil , err
}
sessionSummary := summary .( SessionSummary )
2023-05-08 15:49:21 -04:00
session := & Session {
2023-05-24 20:04:35 +00:00
container : cc ,
Meta : meta ,
SessionMeta : sessionMeta ,
2023-07-13 10:25:01 -04:00
Summary : sessionSummary ,
2023-05-08 15:49:21 -04:00
}
return session , nil
}
2023-08-04 14:31:20 -04:00
// Validation provides the results from validating a bsr.
type Validation struct {
SessionRecordingId string
Valid bool
SessionRecordingValidation * ContainerValidation
}
// ContainerValidation contains the results from validating a container in a bsr.
type ContainerValidation struct {
Name string
ContainerType ContainerType
Error error
FileChecksumValidations ContainerChecksumValidation
SubContainers [] * ContainerValidation
}
// Validate retrieves a BSR from storage using the sessionRecordingId and validates the BSR.
// All files and sub container files will be verified by comparing it against checksums for
// each file in SHA256SUM file.
//
// Validation will continue even if there's an error encountered during validation.
// The validation error will be added to the ContainerValidation struct "Error" field for that container.
func Validate ( ctx context . Context , sessionRecordingId string , f storage . FS , keyUnwrapFn kms . KeyUnwrapCallbackFunc ) ( v * Validation , err error ) {
const op = "bsr.Validate"
switch {
case sessionRecordingId == "" :
return nil , fmt . Errorf ( "%s: missing session recording id: %w" , op , ErrInvalidParameter )
case f == nil :
return nil , fmt . Errorf ( "%s: missing storage: %w" , op , ErrInvalidParameter )
case keyUnwrapFn == nil :
return nil , fmt . Errorf ( "%s: missing key unwrap function: %w" , op , ErrInvalidParameter )
}
session , err := OpenSession ( ctx , sessionRecordingId , f , keyUnwrapFn )
if err != nil {
return nil , fmt . Errorf ( "%s: failed to retrieve session for %s: %w" , op , sessionRecordingId , err )
}
defer func () {
if closeErr := session . Close ( ctx ); closeErr != nil {
err = errors . Join ( err , fmt . Errorf ( "%s: %w" , op , closeErr ))
}
}()
validation := & Validation {
SessionRecordingId : sessionRecordingId ,
Valid : true ,
}
sessionContainerValidation := validateContainer ( ctx , validation , SessionContainer , session . container , session . Meta . Id )
validation . SessionRecordingValidation = sessionContainerValidation
// Validate all connections under session
for connId := range session . Meta . connections {
// Get connection id
connKey := connId
lastDotIndex := strings . LastIndex ( connId , ".connection" )
if lastDotIndex == - 1 {
validation . Valid = false
sessionContainerValidation . SubContainers = append ( sessionContainerValidation . SubContainers , & ContainerValidation {
Name : connId ,
ContainerType : ConnectionContainer ,
Error : fmt . Errorf ( "%s: malformed BSR for: %s" , op , connId ),
})
continue
}
connKey = connId [: lastDotIndex ]
// Open current connection
connection , err := session . OpenConnection ( ctx , connKey )
if err != nil {
validation . Valid = false
sessionContainerValidation . SubContainers = append ( sessionContainerValidation . SubContainers , & ContainerValidation {
Name : connId ,
ContainerType : ConnectionContainer ,
Error : fmt . Errorf ( "%s: failed to retrieve connection for %s: %w" , op , connId , err ),
})
continue
}
connectionContainerValidation := validateContainer ( ctx , validation , ConnectionContainer , connection . container , connection . Meta . Id )
sessionContainerValidation . SubContainers = append ( sessionContainerValidation . SubContainers , connectionContainerValidation )
// Validate all channels under current connection
for chId := range connection . Meta . channels {
// Get channel id
chKey := chId
lastDotIndex := strings . LastIndex ( chId , ".channel" )
if lastDotIndex == - 1 {
validation . Valid = false
sessionContainerValidation . SubContainers = append ( connectionContainerValidation . SubContainers , & ContainerValidation {
Name : chId ,
ContainerType : ChannelContainer ,
Error : fmt . Errorf ( "%s: malformed BSR for: %s" , op , chId ),
})
continue
}
chKey = chId [: lastDotIndex ]
// Open current connection
channel , err := connection . OpenChannel ( ctx , chKey )
if err != nil {
validation . Valid = false
sessionContainerValidation . SubContainers = append ( connectionContainerValidation . SubContainers , & ContainerValidation {
Name : chId ,
ContainerType : ChannelContainer ,
Error : fmt . Errorf ( "%s: failed to retrieve channel for %s: %w" , op , chId , err ),
})
continue
}
channelContainerValidation := validateContainer ( ctx , validation , ChannelContainer , channel . container , channel . Meta . Id )
err = channel . Close ( ctx )
if err != nil {
channelContainerValidation . Error = errors . Join ( channelContainerValidation . Error , fmt . Errorf ( "%s: failed to close channel for %s: %w" , op , chId , err ))
}
connectionContainerValidation . SubContainers = append ( connectionContainerValidation . SubContainers , channelContainerValidation )
}
err = connection . Close ( ctx )
if err != nil {
connectionContainerValidation . Error = errors . Join ( connectionContainerValidation . Error , fmt . Errorf ( "%s: failed to close connection for %s: %w" , op , connId , err ))
}
}
return validation , nil
}
// ValidateContainer validates the checksums of all files in a container
func validateContainer ( ctx context . Context , v * Validation , ct ContainerType , c * container , name string ) * ContainerValidation {
const op = "bsr.(Validate).ValidateContainer"
containerValidation := & ContainerValidation {
Name : name ,
ContainerType : ct ,
}
containerChecksumValidation , err := c . ValidateChecksums ( ctx )
if err != nil {
v . Valid = false
containerValidation . Error = fmt . Errorf ( "%s: failed to validate %s: %w" , op , name , err )
return containerValidation
}
containerValidation . FileChecksumValidations = containerChecksumValidation
failedChecksums := containerValidation . FileChecksumValidations . GetFailedItems ()
// Update validation field only if there are failed checksums
if len ( failedChecksums ) > 0 {
v . Valid = false
}
return containerValidation
}
2023-05-08 15:49:21 -04:00
// Close closes the Session container.
func ( s * Session ) Close ( ctx context . Context ) error {
2023-08-01 10:19:12 -04:00
if ! is . Nil ( s . container ) {
return s . container . close ( ctx )
}
return nil
2023-04-21 14:41:28 -04:00
}
2023-05-08 15:49:21 -04:00
// Connection is a container in a bsr for a specific connection in a session
// container. It contains the files for the recorded connection.
type Connection struct {
* container
multiplexed bool
2023-07-13 10:25:01 -04:00
Meta * ConnectionRecordingMeta
session * Session
Summary ConnectionSummary
2023-04-26 11:40:51 -04:00
}
2023-03-15 14:35:09 +00:00
// NewConnection creates a Connection container for a given connection id.
2023-05-24 20:04:35 +00:00
func ( s * Session ) NewConnection ( ctx context . Context , meta * ConnectionRecordingMeta ) ( * Connection , error ) {
2023-03-15 14:35:09 +00:00
const op = "bsr.(Session).NewConnection"
switch {
case is . Nil ( meta ):
return nil , fmt . Errorf ( "%s: missing connection meta: %w" , op , ErrInvalidParameter )
case meta . Id == "" :
return nil , fmt . Errorf ( "%s: missing connection id: %w" , op , ErrInvalidParameter )
}
2023-05-26 13:07:52 +00:00
name := fmt . Sprintf ( connectionFileNameTemplate , meta . Id )
2023-05-09 18:31:18 +00:00
sc , err := s . container . container . SubContainer ( ctx , name , storage . WithCreateFile (), storage . WithFileAccessMode ( storage . WriteOnly ))
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
if _ , err := s . WriteMeta ( ctx , "connection" , name ); err != nil {
return nil , err
}
2023-07-13 10:25:01 -04:00
nc , err := newContainer ( ctx , ConnectionContainer , sc , s . keys )
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
if _ , err := nc . WriteMeta ( ctx , "id" , meta . Id ); err != nil {
return nil , err
}
return & Connection {
container : nc ,
multiplexed : s . multiplexed ,
Meta : meta ,
2023-07-13 10:25:01 -04:00
session : s ,
2023-03-15 14:35:09 +00:00
}, nil
}
2023-05-08 15:49:21 -04:00
// OpenConnection will open and validate a BSR connection
2023-07-31 17:13:24 -04:00
func ( s * Session ) OpenConnection ( ctx context . Context , connId string ) ( conn * Connection , err error ) {
2023-05-12 18:01:09 +00:00
const op = "bsr.(Session).OpenConnection"
2023-05-08 15:49:21 -04:00
switch {
case connId == "" :
return nil , fmt . Errorf ( "%s: missing connection id: %w" , op , ErrInvalidParameter )
2023-05-12 18:01:09 +00:00
}
2023-05-26 13:07:52 +00:00
name := fmt . Sprintf ( connectionFileNameTemplate , connId )
2023-05-12 18:01:09 +00:00
switch {
case ! s . Meta . connections [ name ]:
2023-05-08 15:49:21 -04:00
return nil , fmt . Errorf ( "%s: connection id does not exist within this session: %w" , op , ErrInvalidParameter )
}
2023-03-15 14:35:09 +00:00
2023-05-12 18:01:09 +00:00
c , err := s . container . container . SubContainer ( ctx , name )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
2023-03-15 14:35:09 +00:00
2023-05-08 15:49:21 -04:00
keyPopFn := func ( c * container ) ( * kms . Keys , error ) {
return s . keys , nil
}
2023-07-13 10:25:01 -04:00
cc , err := openContainer ( ctx , ConnectionContainer , c , keyPopFn )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
// Load and verify connection metadata
sha256Reader , err := crypto . NewSha256SumReader ( ctx , cc . metaFile )
if err != nil {
2023-07-31 17:13:24 -04:00
cc . metaFile . Close ()
2023-05-08 15:49:21 -04:00
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
2023-07-31 17:13:24 -04:00
defer func () {
if closeErr := sha256Reader . Close (); closeErr != nil {
err = errors . Join ( err , fmt . Errorf ( "%s: %w" , op , closeErr ))
}
}()
2023-05-24 20:04:35 +00:00
sm , err := decodeConnectionRecordingMeta ( ctx , sha256Reader )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
err = cc . verifyMetadata ( ctx , sha256Reader )
if err != nil {
return nil , err
}
2023-07-13 10:25:01 -04:00
af , ok := summaryAllocFuncs . get ( s . Meta . Protocol , ConnectionContainer )
if ! ok {
return nil , fmt . Errorf ( "%s: failed to get summary type" , op )
}
summary := af ( ctx )
if err := cc . decodeJsonFile ( ctx , fmt . Sprintf ( summaryFileNameTemplate , ConnectionContainer ), summary ); err != nil {
return nil , err
}
connectionSummary := summary .( ConnectionSummary )
2023-05-08 15:49:21 -04:00
connection := & Connection {
container : cc ,
Meta : sm ,
2023-07-13 10:25:01 -04:00
session : s ,
Summary : connectionSummary ,
2023-05-08 15:49:21 -04:00
}
return connection , nil
2023-03-15 14:35:09 +00:00
}
// NewChannel creates a Channel container for a given channel id.
2023-05-24 20:04:35 +00:00
func ( c * Connection ) NewChannel ( ctx context . Context , meta * ChannelRecordingMeta ) ( * Channel , error ) {
2023-03-15 14:35:09 +00:00
const op = "bsr.(Connection).NewChannel"
if ! c . multiplexed {
return nil , fmt . Errorf ( "%s: connection cannot make channels: %w" , op , ErrNotSupported )
}
switch {
case is . Nil ( meta ):
return nil , fmt . Errorf ( "%s: missing channel meta: %w" , op , ErrInvalidParameter )
case meta . Id == "" :
return nil , fmt . Errorf ( "%s: missing channel id: %w" , op , ErrInvalidParameter )
}
2023-05-26 13:07:52 +00:00
name := fmt . Sprintf ( channelFileNameTemplate , meta . Id )
2023-05-09 18:31:18 +00:00
sc , err := c . container . container . SubContainer ( ctx , name , storage . WithCreateFile (), storage . WithFileAccessMode ( storage . WriteOnly ))
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
if _ , err := c . WriteMeta ( ctx , "channel" , name ); err != nil {
return nil , err
}
2023-07-13 10:25:01 -04:00
nc , err := newContainer ( ctx , ChannelContainer , sc , c . keys )
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
if _ , err := nc . WriteMeta ( ctx , "id" , meta . Id ); err != nil {
return nil , err
}
2023-04-26 11:40:51 -04:00
if _ , err := nc . WriteMeta ( ctx , "channelType" , meta . Type ); err != nil {
return nil , err
}
2023-03-15 14:35:09 +00:00
return & Channel {
container : nc ,
Meta : meta ,
}, nil
}
2023-05-08 15:49:21 -04:00
// OpenChannel will open and validate a BSR channel
2023-07-31 17:13:24 -04:00
func ( c * Connection ) OpenChannel ( ctx context . Context , chanId string ) ( ch * Channel , err error ) {
2023-05-08 15:49:21 -04:00
const op = "bsr.OpenChannel"
switch {
case chanId == "" :
return nil , fmt . Errorf ( "%s: missing channel id: %w" , op , ErrInvalidParameter )
2023-05-12 18:01:09 +00:00
}
2023-05-26 13:07:52 +00:00
name := fmt . Sprintf ( channelFileNameTemplate , chanId )
2023-05-12 18:01:09 +00:00
switch {
case ! c . Meta . channels [ name ]:
2023-05-08 15:49:21 -04:00
return nil , fmt . Errorf ( "%s: channel id does not exist within this connection: %w" , op , ErrInvalidParameter )
}
2023-05-12 18:01:09 +00:00
con , err := c . container . container . SubContainer ( ctx , name )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
keyPopFn := func ( cn * container ) ( * kms . Keys , error ) {
return c . keys , nil
}
2023-07-13 10:25:01 -04:00
cc , err := openContainer ( ctx , ChannelContainer , con , keyPopFn )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
// Load and verify channel metadata
sha256Reader , err := crypto . NewSha256SumReader ( ctx , cc . metaFile )
if err != nil {
2023-07-31 17:13:24 -04:00
cc . metaFile . Close ()
2023-05-08 15:49:21 -04:00
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
2023-07-31 17:13:24 -04:00
defer func () {
if closeErr := sha256Reader . Close (); closeErr != nil {
err = errors . Join ( err , fmt . Errorf ( "%s: %w" , op , closeErr ))
}
}()
2023-05-24 20:04:35 +00:00
sm , err := decodeChannelRecordingMeta ( ctx , sha256Reader )
2023-05-08 15:49:21 -04:00
if err != nil {
return nil , err
}
err = cc . verifyMetadata ( ctx , sha256Reader )
if err != nil {
return nil , err
}
2023-07-13 10:25:01 -04:00
af , ok := summaryAllocFuncs . get ( c . session . Meta . Protocol , ChannelContainer )
if ! ok {
return nil , fmt . Errorf ( "%s: failed to get summary type" , op )
}
summary := af ( ctx )
if err := cc . decodeJsonFile ( ctx , fmt . Sprintf ( summaryFileNameTemplate , ChannelContainer ), summary ); err != nil {
return nil , err
}
channelSummary := summary .( ChannelSummary )
2023-05-08 15:49:21 -04:00
channel := & Channel {
container : cc ,
Meta : sm ,
2023-07-13 10:25:01 -04:00
Summary : channelSummary ,
2023-05-08 15:49:21 -04:00
}
return channel , nil
}
2023-03-15 14:35:09 +00:00
// NewMessagesWriter creates a writer for recording channel messages.
func ( c * Connection ) NewMessagesWriter ( ctx context . Context , dir Direction ) ( io . Writer , error ) {
const op = "bsr.(Connection).NewMessagesWriter"
switch {
case ! ValidDirection ( dir ):
return nil , fmt . Errorf ( "%s: invalid direction: %w" , op , ErrInvalidParameter )
}
2023-05-26 13:07:52 +00:00
messagesName := fmt . Sprintf ( messagesFileNameTemplate , dir . String ())
2023-04-26 14:55:23 -04:00
_ , err := c . container . WriteMeta ( ctx , "messages" , dir . String ())
if err != nil {
return nil , err
}
2024-01-10 16:43:35 -05:00
2023-12-11 16:22:39 -08:00
m , err := c . container . create ( ctx , messagesName ,
storage . WithCreateFile (),
storage . WithFileAccessMode ( storage . ReadWrite ),
2024-01-10 16:43:35 -05:00
storage . WithBuffer ( bsrBufferSize ),
2023-12-11 16:22:39 -08:00
)
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
return checksum . NewFile ( ctx , m , c . checksums )
}
// NewRequestsWriter creates a writer for recording connection requests.
2024-01-08 14:36:21 -05:00
func ( c * Connection ) NewRequestsWriter ( ctx context . Context , dir Direction ) ( storage . Writer , error ) {
2023-03-15 14:35:09 +00:00
const op = "bsr.(Connection).NewRequestsWriter"
switch {
case ! ValidDirection ( dir ):
return nil , fmt . Errorf ( "%s: invalid direction: %w" , op , ErrInvalidParameter )
}
2023-05-26 13:07:52 +00:00
requestName := fmt . Sprintf ( requestsFileNameTemplate , dir . String ())
2023-04-26 14:55:23 -04:00
_ , err := c . container . WriteMeta ( ctx , "requests" , dir . String ())
if err != nil {
return nil , err
}
2024-01-10 16:43:35 -05:00
2023-12-11 16:22:39 -08:00
m , err := c . container . create ( ctx , requestName ,
storage . WithCreateFile (),
storage . WithFileAccessMode ( storage . ReadWrite ),
2024-01-10 16:43:35 -05:00
storage . WithBuffer ( bsrBufferSize ),
2023-12-11 16:22:39 -08:00
)
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
return checksum . NewFile ( ctx , m , c . checksums )
}
// Close closes the Connection container.
func ( c * Connection ) Close ( ctx context . Context ) error {
2023-08-01 10:19:12 -04:00
if ! is . Nil ( c . container ) {
return c . container . close ( ctx )
}
return nil
2023-03-15 14:35:09 +00:00
}
// Channel is a container in a bsr for a specific channel in a session
// container. It contains the files for the recorded channel.
type Channel struct {
* container
2023-07-13 10:25:01 -04:00
Meta * ChannelRecordingMeta
Summary ChannelSummary
2023-03-15 14:35:09 +00:00
}
// Close closes the Channel container.
func ( c * Channel ) Close ( ctx context . Context ) error {
2023-08-01 10:19:12 -04:00
if ! is . Nil ( c . container ) {
return c . container . close ( ctx )
}
return nil
2023-03-15 14:35:09 +00:00
}
// NewMessagesWriter creates a writer for recording channel messages.
2024-01-08 14:36:21 -05:00
func ( c * Channel ) NewMessagesWriter ( ctx context . Context , dir Direction ) ( storage . Writer , error ) {
2023-03-15 14:35:09 +00:00
const op = "bsr.(Channel).NewMessagesWriter"
switch {
case ! ValidDirection ( dir ):
return nil , fmt . Errorf ( "%s: invalid direction: %w" , op , ErrInvalidParameter )
}
2023-05-26 13:07:52 +00:00
messagesName := fmt . Sprintf ( messagesFileNameTemplate , dir . String ())
2023-04-26 14:55:23 -04:00
_ , err := c . container . WriteMeta ( ctx , "messages" , dir . String ())
if err != nil {
return nil , err
}
2023-12-23 21:27:23 -08:00
m , err := c . container . create ( ctx , messagesName ,
storage . WithCreateFile (),
storage . WithFileAccessMode ( storage . ReadWrite ),
2024-01-10 16:43:35 -05:00
storage . WithBuffer ( bsrBufferSize ),
2023-12-23 21:27:23 -08:00
)
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
return checksum . NewFile ( ctx , m , c . checksums )
}
// NewRequestsWriter creates a writer for recording channel requests.
2024-01-08 14:36:21 -05:00
func ( c * Channel ) NewRequestsWriter ( ctx context . Context , dir Direction ) ( storage . Writer , error ) {
2023-03-15 14:35:09 +00:00
const op = "bsr.(Channel).NewRequestsWriter"
switch {
case ! ValidDirection ( dir ):
return nil , fmt . Errorf ( "%s: invalid direction: %w" , op , ErrInvalidParameter )
}
2023-05-26 13:07:52 +00:00
requestName := fmt . Sprintf ( requestsFileNameTemplate , dir . String ())
2023-04-26 14:55:23 -04:00
_ , err := c . container . WriteMeta ( ctx , "requests" , dir . String ())
if err != nil {
return nil , err
}
2023-12-23 21:27:23 -08:00
m , err := c . container . create ( ctx , requestName ,
storage . WithCreateFile (),
storage . WithFileAccessMode ( storage . ReadWrite ),
2024-01-10 16:43:35 -05:00
storage . WithBuffer ( bsrBufferSize ),
2023-12-23 21:27:23 -08:00
)
2023-03-15 14:35:09 +00:00
if err != nil {
return nil , err
}
return checksum . NewFile ( ctx , m , c . checksums )
}
2023-05-11 20:36:59 +00:00
// OpenMessageScanner opens a ChunkScanner for a channel's recorded messages.
func ( c * Channel ) OpenMessageScanner ( ctx context . Context , dir Direction ) ( * ChunkScanner , error ) {
const op = "bsr.(Channel).OpenMessageScanner"
2023-05-26 13:07:52 +00:00
messagesName := fmt . Sprintf ( messagesFileNameTemplate , dir . String ())
2023-05-11 20:36:59 +00:00
m , err := c . container . container . OpenFile ( ctx , messagesName , storage . WithFileAccessMode ( storage . ReadOnly ))
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
expectedSum , err := c . shaSums . Sum ( messagesName )
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
return NewChunkScanner ( ctx , m , WithSha256Sum ( expectedSum ))
}
// OpenRequestScanner opens a ChunkScanner for a channel's recorded requests.
func ( c * Channel ) OpenRequestScanner ( ctx context . Context , dir Direction ) ( * ChunkScanner , error ) {
const op = "bsr.(Channel).OpenRequestScanner"
2023-05-26 13:07:52 +00:00
requestName := fmt . Sprintf ( requestsFileNameTemplate , dir . String ())
2023-05-11 20:36:59 +00:00
m , err := c . container . container . OpenFile ( ctx , requestName , storage . WithFileAccessMode ( storage . ReadOnly ))
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
expectedSum , err := c . shaSums . Sum ( requestName )
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , op , err )
}
return NewChunkScanner ( ctx , m , WithSha256Sum ( expectedSum ))
}